跳至主要內容

编程术语 - 比较篇

LincZero大约 28 分钟

编程术语 - 比较篇

  • 编程语言受欢迎榜单

    • https://www.tiobe.com/tiobe-index/,影响力比较大,参考源包括搜索引擎等

    • https://pypl.github.io/PYPL.html

    • https://madnight.github.io/githut/#/pull_requests/2023/4,监控Github上的pull数

  • 性能榜单

    • https://goodmanwen.github.io/Programming-Language-Benchmarks-Visualization/
    • https://benchmarksgame-team.pages.debian.net/benchmarksgame/index.html

编程语言性质

动态类型语言、静态类型语言

首先遵循一个原则:

  • 没有绝对的动态语言、也没有绝对的静态语言
  • 没有绝对的强类型、也没有绝对的弱类型

注:下标只是趋向性,并不是绝对的

关联性

动态类型语言(动态性)静态类型语言(静态性)边界模糊性
代表语言:JavaScript代表语言:C代表语言:C++
解释型语言更趋向动态性编译型语言更趋向静态性C++是编译型语言但比C提供了更多动态性支持
很多语言是编译解释语言
动态类型
支持泛型编程
不声明函数的参数和返回类型
静态类型
非泛型编程
声明函数的参数和返回类型
C++也支持泛型编程
动态变量(动态存储变量)
动态分配内存
静态变量(静态存储变量)
编译时分配内存
C语言也能动态分配内存来声明动态变量
TypeScript可声明静态变量、可被编译成js
脚本语言
脚本的形式被执行
非脚本语言
有main函数
Python中
垃圾回收可以更方便更智能垃圾回收C++的智能指针也能够简化垃圾回收机制
写起来更舒服写起来更痛苦

其他:强类型语言弱类型语言,和动态类型语言静态类型语言之间,没有联系性

强类型语言弱类型语言
不需要类型检查需要类型检查,但这并不意味着更动态
因为类型检查并转换的操作同样可以在编译时期做到
强类型鉴别可以使用鸭类鉴别器(duck typing principle

常见语言划分

用于对照上面的分析 (表格左性能偏强,往往是静态变量、编译型、无垃圾回收的)

语言CC++(超集)RustGoJavaC#Ts(超集)JsPythonRuby
语言类型1编译型语言编译型语言编译型语言编译型语言编译解释语言编译解释语言编译解释语言解释型语言解释型语言
语言类型2静态变量静态变量静态变量静态变量静态变量静态变量静态变量动态变量动态变量动态变量
可当脚本语言是(编译后)
————————————————
语言类型3弱类型弱类型强类型强类型强类型强类型弱类型弱类型强类型强类型
预声明类型需声明类型需声明类型需声明类型需声明类型需声明类型需声明类型需声明类型仅声明作用域不声明类型
函数原型声明需要需要不需要不需要不需要
————————————————————————————————
语言类型不面向对象面向对象面向对象面向对象原生不支持面向对象
有无垃圾回收有 - CMS有 - 多种

(Ts比较特殊,它是可以不编译直接解释的但这个原理我个人不太清楚,暂时把它理解为一个 “中间语言”,不考虑其的解释性性质)

1460000012372378
1460000012372378

强类型语言、弱类型语言

内存盒子解释Python的赋值

详见内存管理或内存模型部分的笔记

一些区别的表现形式 —— 定义未赋值的情况

  • 动态类型语言:输出定义未赋值时,一般为undefined(如js)

  • 静态类型语言

    • 输出定义未赋值时,编译时一般会报错

    • 若编译不报错的情况时,该变量可能为任意值

      而且C++中定义但未初始化赋值时,可能只有虚拟内存而不一定有对应的物理内存

内存区别

语言的强类型和弱类型的本质区别仅仅只是类型检查的规定、约束、严格性吗?还是说有更深层次的底层区别?

例如是不是说强类型还可以将不同类型的变量存储在不同的区域以进行优化,减少内存碎片化,而弱类型则是将定义的多个不同的变量放在一起,减少内存的总占用(我猜的,不一定对)

GPT 回答:

类型系统的强弱主要是关于编程语言如何处理和执行类型检查的问题。在强类型语言中,类型检查是严格的,这意味着你不能在不进行显式类型转换的情况下混合使用不同的数据类型。例如,你不能将一个字符串和一个整数相加,除非你显式地将其中一个转换为另一个类型。Python、Java 和 C# 都是强类型语言的例子。

相比之下,弱类型语言允许你在不进行显式类型转换的情况下混合使用不同的数据类型。例如,JavaScript就是一个弱类型语言,它允许你将字符串和数字相加,结果会是一个新的字符串。

你的问题中提到的内存优化和内存碎片化问题,这更多的是关于编程语言的内存管理模型,而不是类型系统的强弱。不同的编程语言有不同的内存管理模型,例如,一些语言使用垃圾收集来自动管理内存,而其他语言则需要程序员显式地分配和释放内存。这些内存管理模型的选择会影响到内存的使用效率和碎片化,但这与类型系统的强弱并没有直接关联。

总的来说,类型系统的强弱主要关系到编程语言的类型检查的严格程度,而内存管理则是一个更为复杂的问题,涉及到的因素包括编程语言的设计、运行时系统的实现以及程序的具体行为等等。

动态类型、静态类型,强类型、弱类型

简单解释

编译时就知道变量类型的是静态类型,运行时才知道变量类型的是动态类型

静态类型底层原理

  • 编译时确定类型
    • 编译器在将编译时就确定所有变量的类型
    • 比如:int age = 18;,你不能对变量进行除以0的操作等等,因为类型本身就定义了可操作的集合
    • 比如:C数组的字面量方式定义中,数组长度必须是编译时已知的,即不能为变量(变量的值在程序运行时设置,编译时不能知道)
  • 类型推导的补充
    • 像 C++ 里常见的auto ite = vec.iterator();这种也属于静态类型
    • 这种叫做类型推导,通过已知的类型在编译时期推导出不知道的变量的类型
    • 在静态类型语言中对一个变量做该变量类型所不允许的操作也会报出语法错误
  • 优点
    • 可以提前将内存空间分配好,你运行到某行需要用这个数据时不需要再动态分配内存。节约了时间、提高了效率

动态类型底层原理

  • 运行时缺点类型
    • var name = student.getName();这行 JavaScript 代码就是动态类型的
    • 因为这行代码只有在被执行的时候才知道 name 是字符串类型的,甚至是 null 或 undefined 类型
    • 你也没办法进行类型推导,因为student.getName函数签名根本不包含返回值类型信息
    • 后面会介绍通过一些其他手段来给函数签名加上类型(TypeScript)。在动态类型中对一个变量做该变量类型所不允许的操作会报出运行时错误
  • 优点
    • 对数据的封装性更好、方便开发者
  • 缺点
    • 一个变量做该变量类型所不允许的操作时,静态语言编译时会报错,而动态语言运行时才会报错。编译时报错比运行时报错更容易找到bug和修bug

不允许隐式转换的是强类型,允许隐式转换的是弱类型

  • 动态类型强类型:如在Python中进行'666' / 2你会得到一个类型错误

    这是因为强类型语言中是不允许隐式转换的

  • 动态类型弱类型:如在 JavaScript中进行'666' / 2你会得到整数 333

    这是因为在执行运算的时候字符串 '666' 先被转换成整数 666,然后再进行除法运算

  • 静态类型:如在C++中进行"666" / 2,在程序再运行之前,即编译时,就能够报错

    这绝不是因为它不允许隐式转换的原因,C++作为弱类型语言是允许隐式类型转换的!

    错误原因是说String类不允许进行这种操作(没有该方法)

高级解释

需要先介绍一些基本概念

Program Errors(程序错误)
  • trapped errors(捕获错误)

    导致程序终止执行(程序意识到出错,使用对应的错误处理机制) 如除 0,Java 中数组越界访问

  • untrapped errors(非捕获错误)

    程序出错后继续执行(其实并不一定保证继续执行,程序本身并不知道出错,也没有对应的错误处理机制) 如 C 语言里的缓冲区溢出,Jmp 到错误地址

Forbidden Behaviors(禁止行为)

程序在设计的时候会定义一组 forbidden behaviors,包括了所有的 untrapped errors,可能包括 trapped errors。

Well behaved、ill behaved
  • well behaved: 如果程序的执行不可能出现 forbidden behaviors,则称为 well behaved
  • ill behaved: 只要有可能出现 forbidden behaviors,则称为 ill behaved

关系图

4088525421-5a2e0cbc6e790_articlex
4088525421-5a2e0cbc6e790_articlex

严格定义

  • 强类型:如果一门语言写出来的程序在红色矩形外部,则这门语言是强类型的,也就是上面说的 well behaved
  • 弱类型:如果一门语言写出来的程序可能在红色矩形内部,则这门语言是弱类型的,也就是上面说的 ill behaved
  • 静态类型:一门语言在编译时排除可能出现在红色矩形内的情况(通过语法报错),则这门语言是静态类型
  • 动态类型:一门语言在运行时排除可能出现在红色矩形内的情况(通过运行时报错,但如果是弱类型可能会触发 untrapped error,比如隐式转换,使得程序看起来似乎是正常运行的),则这门语言是动态类型

举个例子

  • Python:在 Python 中执行 test = '666' / 3 你会在运行时得到一个 TypeError 错误,相当于运行时排除了 untrapped error

    因此 Python 是动态类型,强类型语言。

  • JavaScript:执行 var test = '666' / 3' 你会发现 test 的值变成了 222,因为这里发生了隐式转换

    因此 JavaScript 是动态类型,弱类型的。更为夸张的是 [] == ![] 这样的代码在 JavaScript 中返回的是 true,这里是具体的 原因open in new window

  • Java:执行 int[] arr = new int[10]; arr[0] = '666' / 3; 你会在编译时期得到一个语法错误

    这说明 Java 是静态类型的

    执行 int[] arr = new int[10]; arr[11] = 3; 你会在运行时得到数组越界的错误(trapped error)

    这说明 Java 通过自身的类型系统排除了 untrapped error,因此 Java 是强类型的。

  • C语言:C 与 Java 类似,也是静态类型的

    但是对于 int test[] = { 1, 2, 3 }; test[4] = 5; 这样的代码 C 语言是没办法发现你的问题的,因此这是 untrapped error

    因此我们说 C 是弱类型的

常见语言划分

补充:TypeScript作为JavaScript的超集,提供了静态变量,在右下角

1460000012372378
1460000012372378

性能与使用性比较

  • 速度比较
    • 由于强类型语言一般需要在运行时运行一套类型检查系统,因此强类型语言的速度一般比弱类型要慢
    • 动态类型也比静态类型慢
    • 因此在上述所说的四种语言中执行的速度应该是 C > Java > JavaScript > Python
  • 使用性比较
    • 但是强类型,静态类型的语言写起来往往是最安全的

编译型语言、解释型语言

区别:交互模式

能够便捷交互是解释型语言的一大特点,编译型语言无法做到。例如Python和JavaScript的交互模式

  • Python交互模式(会有>>>提示符)
    • cmd输入python
    • python自带的IDEL
    • jupyter notebook
  • JavaScript交互模式
    • 万维网浏览器的调试器

区别:main()方法入口

编译型语言需要在编译前指定好main方法,而解释型语言不需要

解释型语言可以从任意一个文件开始执行。函数外面的代码会被立即执行,即从文件的开头一直执行到文件的结尾

Python补充:

  • python的main函数和编译型语言的main不同,其作用是当当前文件作为执行程序时才会执行,而被作为模块引入时则不会执行

  • python根据入口文件的不同,使用模块时会产生一些坑。比如模块内部的相对路径会被解释为引入模块的文件的相对路径!

    当文件作为执行文件或作为模块,或当入口文件不同时,可能会指定不同路径

    即若总需要在python文件内部使用相当于该文件而言的相对路径时,需要先寻找该文件的绝对路径再作相对路径的转换

边界模糊性

  • python解释型、但这玩意也能编译
  • ts编译成js,但这玩意也能直接解释
  • java,说他编译型吧,编译后还得被人解释,说他解释型吧,还得编译(JIT)

动态编译与JIT

参考:【CSDN】什么是JITopen in new window

  1. 动态编译dynamic compilation

    指的是 “在运行时进行编译”;与之相对的是事前编译(ahead-of-time compilation,简称AOT),也叫静态编译(static compilation)

  2. JIT编译just-in-time compilation

    狭义来说是当某段代码即将第一次被执行时进行编译,因而叫 即时编译JIT编译是动态编译的一种特例。 JIT编译一词后来被泛化,时常与动态编译等价;但要注意广义与狭义的JIT编译所指的区别。

  3. 自适应动态编译adaptive dynamic compilation

    也是一种动态编译,但它通常执行的时机比JIT编译迟,先让程序 “以某种式” 先运行起来,收集一些信息之后再做动态编译。这样的编译可以更加优化

语言举例

  • Java好像就是一种JIT编译?

脚本语言、非脚本语言

原生语言、托管语言

动态变量、静态变量(各语言)

静态存储和动态存储

存储方法说明生存期
静态存储变量通常是在变量定义时就分定存储单元并一直保持不变,直至整个程序结束。静态存储只会初始化一次一直存在
动态存储变量是在程序执行过程中,使用它时才分配存储单元,使用完毕立即释放时而存在时而消失

补充:通常把由于变量存储方式不同而产生的特性称为变量的生存期

静态变量和动态变量

变量存储方式定义方式作用域周期默认初始化值
动态全局变量静态存储static修饰符整个项目。可用extern声明另一文件的全局变量从程序运行到程序退出0
动态局部变量动态存储static修饰符当前函数。从定义位置,到其所在的{}的结束位置从函数调用到函数退出随机值
静态全局变量静态存储static修饰符当前文件。从定义/声明位置到文件结尾从程序运行到程序退出0
静态局部变量静态存储static修饰符当前函数。从定义位置,到其所在的{}的结束位置0

补充:静态变量

  • 静态全局变量:动态全局变量可以通过extern关键字在外部文件中使用,但静态全局变量不可以在外部文件中使用。静态全局变量相当于限制了动态全局变量的作用域
  • 静态局部变量:作用域相同,都在函数内部,但是两者生命周期不一样,静态局部变量的生命周期是到程序的结束,而动态局部变量的生命周期是函数的结束,静态局部变量的生命周期更长,同时静态局部变量只会初始化一次,函数重复调用,但静态局部变量不会被重新初始化

静态变量类型和动态变量类型

静态语言和动态语言

类变量和成员变量

面向对象语言 & 对象模型

面向对象语言、非面向对象语言

基于对象、基于类

对于C++程序员,JavaScript最难的就是它的对象模型

  • 基于对象的面向对象语言(object-based):JavaScript
  • 基于 类 的面向对象语言:C++、C#、Java、Simula、Smalltalk 其中Java比起C++,其面向对象更为极致:Java应用程序中的全部内容都必须放置在类中

面向对象的三大特征与编程思想

  • 封装
    • 是面向对象的立足点
    • 封装数据和行为,成为一个整体
  • 继承
    • 解决问题:可以解决多个类有相同代码的弊端
    • 其他用处:也能通过晚绑定来进行一些设计模式
  • 多态
    • 是同类型的对象表现出不同的形态,是继承/接口所产生的结果
    • 实现方法:子引用可以代替父引用

函数是“一等公民”

参考:【知乎】如何理解“python中函数是一等公民”open in new window

如何理解

在 Python/JavaScript 中,一个整数、一个字符串、一个列表、一个字典和一个元组都是对象 而会让从 C/C++ 或者 Java 语言转过来的人意外的是:Python/JavaScript 中的函数也是对象 (不过Python和JavaScript还是有所不同:前者是基于类,后者是基于对象)

因为一个函数和常见的整数、字符串等”公民“一样,都是对象 可以在运行时创建,并能被赋值给变量,作为集合对象的元素,还能够作为函数的参数和返回值 因而我们称**『Python 中函数是一等公民』** 意思是 Python 中的函数和整数、字符串等常见概念的地位是平等的,一个整数和一个字符串等对象可以干的事,一个函数也可以办到

佐证

Python佐证1

import re

def factorial(n):
    '''returns n!'''
    return 1 if n < 2 else n * factorial(n - 1)

class Dumb:
    pass

items = [1, 1.0, 'hello', [1], {'a': 1}, {1}, factorial, Dumb(), re, None, object]

for item in items:
    print(f'对象 {item} 的类型是 {type(item).__name__},',
          f'对象 {type(item).__name__}类 的类型是 {type(type(item)).__name__}.')

输出结果

对象 1 的类型是 int, 										对象 int类 的类型是 type.
对象 1.0 的类型是 float, 									对象 float类 的类型是 type.
对象 hello 的类型是 str, 									对象 str类 的类型是 type.
对象 [1] 的类型是 list, 									对象 list类 的类型是 type.
对象 {'a': 1} 的类型是 dict, 								对象 dict类 的类型是 type.
对象 {1} 的类型是 set, 									对象 set类 的类型是 type.
对象 <function factorial at ...> 的类型是 function, 		对象 function类 的类型是 type.
对象 <__main__.Dumb object at ...> 的类型是 Dumb, 		对象 Dumb类 的类型是 type.
对象 <module 're' from 'C:\\...\\re.py'> 的类型是 module, 对象 module类 的类型是 type.
对象 None 的类型是 NoneType, 								对象 NoneType类 的类型是 type.
对象 <class 'object'> 的类型是 type, 						对象 type类 的类型是 type. # 无限循环?

Python佐证2

for item in items:
    print(f'类对象 {item.__class__.__name__} 的基类是: {item.__class__.__bases__}')

print(f'类对象 {object.__name__} 的基类是: {object.__bases__}')

输出结果

类对象 int 的基类是: (<class 'object'>,)
类对象 float 的基类是: (<class 'object'>,)
类对象 str 的基类是: (<class 'object'>,)
类对象 list 的基类是: (<class 'object'>,)
类对象 dict 的基类是: (<class 'object'>,)
类对象 set 的基类是: (<class 'object'>,)
类对象 function 的基类是: (<class 'object'>,)
类对象 Dumb 的基类是: (<class 'object'>,)
类对象 module 的基类是: (<class 'object'>,)
类对象 NoneType 的基类是: (<class 'object'>,)
类对象 type 的基类是: (<class 'object'>,)
类对象 object 的基类是: ()

应用

因为函数和整数、字符串等概念一样,都是对象,因而整数、字符串等对象所具有的行为,函数也同样拥有

  • 运行时创建
  • 赋值给变量,可以将整数或函数赋值给一个变量
  • 作为函数参数,可以把整数或函数作为函数参数(callback函数的原理)
  • 作为函数的返回值

字面量方式、new方式

字面量与new表达式

JavaScriptJava等语言中,创建对象的方式包括两种:对象字面量和使用new 表达式

对象字面量是一种灵活方便的方式,如:

var o1 = {
p:'I'm in Object literal',
    alertP: function() {
        alert(this.p);
    }
}

特点:这种写法不需要定义构造函数

缺点:每创建一个新的对象都需要写出完整的定语语句,不便使用继承等高级特性

字面量是一种表示值的记法,字面量分为:

  • 字符串字面量(string literal)
  • 数组字面量(array literal)
  • 对象字面量(object literal)
  • 函数字面量(function literal)

写法比较

对象

// 不用字面量,创建对象的不简洁做法
var customer = new Object();
customer.firstName = 'John';
customer.lastName= 'D';

// 首选方法,使用对象字面量,创建对象,效果等同上面三段代码
var customer = { firstName: 'John', 'lastName: 'D' }

数组(也是对象)

// 不用字面量,创建对象的不简洁做法
var arr= new Array();
arr.push(1, 2, 3, 4, 5);
 
// 首选方法,使用数组字面量,效果等同上面三段代码
var arr = [ 1, 2, 3, 4, 5 ]

性能比较

console.time是开始计算时间,console.timeEnd是停止计时,输出脚本执行时间

console.time('试验1');for(var i=0;i<10000;i++)var a ={};console.timeEnd('试验1')
// 输出:试验1:0.976ms
console.time('试验2');for(var i=0;i<10000;i++)var a =new Object();console.timeEnd('试验2')
// 输出:试验2:1.312ms

字面量效率更高,网上解释:

{}是字面量,可以立即求值

new Object()本质上是方法(只不过这个方法是内置的)调用, 既然是方法调用,就涉及到在proto链中遍历该方法,当找到该方法后,又会生产方法调用必须的堆栈信息,方法调用结束后,还要释放该堆栈

赋值、浅拷贝、深拷贝

数据类型

数据分为

  • 基本数据类型(String, Number, Boolean, Null, Undefined,Symbol
  • 对象数据类型

特点分别为

  • 基本数据类型的特点:直接存储在栈(stack)中的数据

  • 引用数据类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆内存里

    引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体

13253432-74602a0f1cc0d432
13253432-74602a0f1cc0d432

浅拷贝和深拷贝

深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的

深拷贝和浅拷贝的示意图大致如下:

13253432-aca35e0020358b3d
13253432-aca35e0020358b3d

浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。

赋值和浅拷贝

当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据

也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。

浅拷贝是按位拷贝对象,它会创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。

如果属性是基本类型,拷贝的就是基本类型的值

如果属性是内存地址(引用类型),拷贝的就是内存地址

因此如果其中一个对象改变了这个地址,就会影响到另一个对象。即默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。

我们先来看两个例子,对比赋值与浅拷贝会对原对象带来哪些改变?

// 对象赋值
var obj1 = {
   'name' : 'zhangsan',
   'age' :  '18',
   'language' : [1,[2,3],[4,5]],
};
var obj2 = obj1;
obj2.name = "lisi";
obj2.language[1] = ["二","三"];
console.log('obj1',obj1) // "lisi", ["二","三"]
console.log('obj2',obj2) // "lisi", ["二","三"]
// 浅拷贝
var obj1 = {
   'name' : 'zhangsan',
   'age' :  '18',
   'language' : [1,[2,3],[4,5]],
};
var obj3 = shallowCopy(obj1);
obj3.name = "lisi";
obj3.language[1] = ["二","三"];
function shallowCopy(src) { // 浅拷贝函数
   var dst = {};
   for (var prop in src) {
       if (src.hasOwnProperty(prop)) {
           dst[prop] = src[prop];
       }
   }
   return dst;
}
console.log('obj1',obj1) // 'zhangsan', ["二","三"]
console.log('obj3',obj3) // "lisi", ["二","三"]

形象比喻(奇妙比喻)

有一个药铺子,药柜就是一个内存空间,掌柜有事要出门,请了个新人,然后要把店铺的药方教给这个新人

  • 赋值就是掌柜要告诉一个刚聘请的实习生感冒药在哪个盒子里了,第几排第几列有感冒药的药方

    如果有人调皮恶作剧把药柜的那个盒子里的药方调换了,你再去调用、拿那个药方,就会拿到被更改后的数据

    这个过程中,盒子的位置就是地址,药方就是地址所指向的数据

    (不要吐槽为什么药方会在药盒子里面,可能是掌柜的怕自己忘了)

  • 浅拷贝我直接把柜子里的那张药方完整地告诉你了,你也记得药方是什么了,这时候就算要人把药方改了,你所记得的,还是原来那个药方

    但如果这时候你拿完药方还要去配药,你还要根据这个药方去找其他药盒子里面的药

    而药方准确地记载了分别用到哪几个盒子的药而没有提到分别是哪些药,这时候的药方相当于也是一个地址了

    (不要吐槽为什么药方是药盒子的位置而不是记是什么药,有可能是掌柜怕有人偷了药方,所以进行了加密)

    那么如果有人把药盒子里的药胡乱交换、或者放错位置,那么你配出来的药和原药是不一样的

  • 深拷贝就是我不仅把药方药盒子里的药方告诉你了,我还把药方里面所写的那些药材药盒子分别是什么药都告诉你了

    这时候无论有人再怎么捣乱药柜,怎么交换存放药材,这个刚入行的实习生都能准确配出掌柜所交代给他的药

    (实习生:我太难了,药字我都不认不出了)

总结

深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。这里默认比较为引用数据类型。

方式和源数据是否指向同一对象指向对象中的基本类型指向对象中的子对象
赋值改变会使源数据一同改变,反之改变会使源数据一同改变,反之
浅拷贝改变不会使源一同改变,反之改变会使源数据一同改变,反之
深拷贝改变不会使源一同改变,反之改变不会使源一同改变,反之

补充

浅拷贝的实现方式

Object.assign()
Array.prototype.concat()
Array.prototype.slice()

深拷贝的实现方式

JSON.parse(JSON.stringify())
手写递归方法
函数库lodash

函数封装、对象封装

函数封装 VS 对象封装

侧重

  • 函数封装:工具
  • 对象封装:数据

骚操作

  • 函数封装:骚操作挺多,如通过判断传入数据的类型、数量、性质或值作出不同的行为
  • 对象封闭:骚操作更多,能封装的东西更多,而且可以作为一个函数集(对象里的函数称为成员函数)
    • 比如C++的cout是一个智能对象,能当普通函数用
    • 也能使用里面的成员函数,如cout.put()能用来输出char字符

嵌套

  • C/C++中函数内不能包含函数,但Python可以
  • 类可以包含类(还能继承)

变量

  • 函数是要返回的,不能一直占着线程,即一定要一次性处理完
  • 但类本质是个变量,同时可以有多个类的实例

重载

  • 为什么说C++中cout和除法运算符的重载得益于OOP?函数也可以做得到啊??????

决策原理

  • OOP强调运行截断决策:提供灵活性,比如:可以在运行时确定数组的长度
  • 过程性编程是编译阶段决策:比如:需要在编译时就设定好数组的长度

函数原型、函数定义、原型声明

定义与声明

定义和声明

  • 定义:定义式声明defining declaration),简称定义definition)。会建立存储空间,受单定义规则的限制(本质区别)
  • 声明:引用式声明referencing declaration),简称声明declaration)。不会建立存储空间(本质区别)

单定义规则、以及其“例外”

单定义规则One Definition Rule,OCR)指出:变量只能定义一次

(但是并非意味着不能有多个变量的名称相同)

不能定义两个本质一样变量,但可以定义两个名字相同但本质不同的变量,比如:

  • 不同函数中声明的同名自动变量:彼此独立,都有自己的地址
  • 函数中声明与外部变量同名的变量:被视为一个自动变量的定义,在函数内时该变量会覆盖(隐藏)外部变量
  • 函数的多态重载:名称修饰会将其变为不同名字

变量的定义和声明

一般不叫变量定义,一般叫定义声明

  • 变量定义:哪怕没有对其进行初始化,也属于变量定义。如int a;
  • 变量声明:只是告诉编译器某变量已经在别处定义过了。如extern int a;

函数的定义和声明

  • 函数定义:
  • 函数声明:只是告诉编译器某函数已经在别处定义过了。如函数原型

广义的声明

从广义的角度来讲,声明中包含着定义(定义式声明),但是并非所有的声明都是定义(比如引用时声明就不是)

即,定义性声明既是定义又是声明,而引用性声明只是声明

所以有时书上讲我们声明了一个变量int a;,不太严谨,也是没有本质错误的,是正确的说法

单定义规则与头文件

为了不违反单定义规则One Definition Rule,OCR),所以头文件一般只能声明而不能定义 有两个例外,但这两种情况都不是即时分配内存的

  • 内联函数除外:其本质并非定义函数而定义生成定义的模板,而且会通过预编译方式替换掉调用代码
  • 模板函数除外:其本质并非定义变量和函数而是定义生成定义的模板
  • const变量例外:其本质是在第一次被调用时才分配内存

js的原型和C的原型应该是不同的

C的原型

js的原型

指针辨析:悬垂指针、哑指针、野指针、智能指针

https://blog.csdn.net/zhaojinjia/article/details/8770989

待解决

引用类型、复合类型、对象类型(、基本类型)