- 计算机实力强,和我想要学的ai研究方向相符
- 学计算机就是从陈越老师的数据结构和翁恺老师的C语言开始的
- Zhejiang University is good at computer sicence which is matching to my favorable research direction.
- I started to learn computer science though Chen Yue's data structure and Weng Kai's C language so I have a good impression on Zhejiang University.
- 代码区
存放函数体的二进制代码,由操作系统进行管理的。共享、只读。 - 全局区
存放全局变量和静态变量(static)以及常量(const修饰的全局变量、字符串常量) - 堆区
new出来的;由程序员分配和释放,若程序员不释放,程序结束后由操作系统回收。 - 栈区
由编译器自动分配释放,存放函数的参数值,局部变量等。
- char存储大小1字节,值范围-128~127;
- unsigned char存储大小1字节,值范围0~255;unsigned short等同理
- short存储大小2字节,值范围-32768~32767;
- int
16位系统存储大小2字节,值范围-32768 ~ 32767
32、64位系统存储大小4字节,值范围-2^31 ~ 2^31-1(大约-10^9 ~ 10^9) - long
16、32位系统存储大小4字节,值范围0 ~ 2^32-1(大约0 ~ 2 * 10^9)
64位系统存储大小8字节,值范围9 * 10^18 - long long 存储大小8字节,值范围-2^63 ~ 2^63-1(大约9 * 10^18)
- float
4字节 -3.4 * 10^38~3.4 * 10^38 - double
8字节 -1.7 * 10^308~1.7 * 10^308 - long double
8字节 -1.7 * 10^308~1.7 * 10^308
指针使得 C 语言能够更高效地实现对计算机底层硬件的操作,而计算机硬件的操作很大程度上依赖地址,指针便提供了一种对地址操作的方法
- 指针本身也是一个变量
地址是指针的地址;数据是指向对象的地址。char * 、double * 和 int * - 指针加减
加/减的是整个指针类型的长度,不如说成指针的偏移更合适。
- 数组名当作指针
- 参数传递:把数组作为参数传递的时候,会当作指针,数组首元素的地址作为参数来传递或者调用
- 数组名可作为指针常量(类似引用,指针的常量,指向不可变,内容可以变)
- 内存分配
- 数组是开辟一块连续的内存空间,数组本身的标示符代表整个数组,可以用sizeof取得真实的大小;
- 指针则是只分配一个指针大小的内存,并可把它的值指向某个有效的内存空间
- 汇编的角度
const给出内存地址,在内存中仅有一份拷贝;而define给出立即数,在内存中有多份拷贝。 - 效率角度
编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,使得const的效率比较高。 - 编译器处理方式
define是在预处理阶段展开,const常量是编译运行阶段使用。 - 类型和安全检查不同
define没有类型,没有类型检查;const有数据类型,在编译运行阶段检查类型。 - 可否修改
const修饰的量不是常量,仅仅是个只读量,可以通过指针进行修改。
- gcc是编译器
- make可以批处理gcc命令,需要编写makefile
- cmake可以简化编写makefile的过程
- 语言类型不同
C++为编译性编程语言,Python 则为解释性编程语言。
- 编译型语言要求使用编译器一次性将所有源代码编译为一个可执行程序。(一般不可以跨平台)
- 解释型语言是使用解释器一边执行一边转换,用到些源代码就转换哪些,不会生成可执行程序。(一般可以跨平台)
- 执行效率不同
C++更快一些,Python更慢,因为编译后的代码执行速度非常快,因为它直接在底层计算机硬件上运行。而在解释器上运行比较慢 - 内存管理机制不同
- Python是动态类型语言,不需要明确声明变量的类型,提供了被称为垃圾收集器的自动内存管理机制,不允许直接进行内存处理操作。
- C++是静态类型语言,需要声明变量的类型,所有内存管理操作都需要自行处理。
- 预处理:处理所有的头文件
#include
和宏展开#define
;hello.c->hello.i - 编译:编译器生成生成汇编代码文件;hello.i->hello.s
- 汇编:汇编器把汇编代码文件转换成中间目标文件;hello.s->hello.o
- 链接:分为静态链接和动态链接(默认)两种;hello.o->hello
- 静态链接:链接阶段:所有相关的目标文件与牵涉到的函数库被链接合成一个可执行文件,占用空间。
- 动态链接:链接阶段:仅仅建立与所需库函数之间的关系;在运行时重定位
- C语言是面向过程语言,⽽C++是面向对象语言
- 函数重载
C语言不存在函数重载,C++根据函数名参数个数参数类型判断重载。(属于静多态,必须同一作用域下才叫重载) - 缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使⽤指定的参数。
C语言不支持缺省参数,C++支持。 - 默认返回类型
C语言中,如一个函数没有指定返回值类型,默认返回int类型;C++中,如果一个函数没有返回值则必须指定为void。 - 未指定参数列表
在C语言中,函数没有指定参数列表时,默认可以接收任意多个参数;但在C++中,因为严格的参数类型检测,没有参数列表的函数,默认为 void,不接收任何参数。 - malloc,free && new,delete
都可以用来在堆上分配和回收空间。
执行new的过程:
- 分配未初始化的内存空间(malloc)
- 对象的构造函数对空间进行初始化,返回空间的首地址;构造对象时出现异常,则自动调用 delete 释放内存。
执行delete的过程:
- 使用对象的析构函数对对象进行析构。
- 回收内存空间(free)
可以看出区别:new 得到的是经过初始化的空间,⽽ malloc 得到的是未初始化的空间。
- 面向过程:
就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。 - 面向对象: 是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。
其实就是两句话,面向对象就是高度实物抽象化、面向过程就是自顶向下的编程!
面向对象的思想就是把一切事物都看成对象,而对象一般都是由属性和方法组成。
- 属性属于对象静态的一面,用来形容对象的一些特性。
- 方法属于对象动态的一面。
- 类:具有同种属性的对象称为类,对象是类的实例化。
具体举例的话:以增加一个七夕77折的活动为例:
面向过程:增加新的小case
if (todayIsLoversDay()) {
return price * 0.77
}
面向对象:新的收银方式属于新的对象,让新的收银方式继承 Bill类
//先在 Bill 类中新增 discount 方法
open fun discount(price: Double): Double{
return price
}
//七夕节的收费方式则继承此类,在 discount 函数中实现打 77折
class LoversDayBill : Bill(){
override fun discount(price: Double): Double {//函数重写(虚函数)
return price * 0.77
}
}
-
封装:
封装就是把过程和数据包围起来,对数据的访问只能通过特定的界面。能降低耦合性。 -
继承:
子类继承父类的特征和行为。
子类可以有父类的方法,属性(非private)。子类也可以对父类进行扩展,也可以重写父类类的方法。缺点就是提高代码之间的耦合性。
- super关键字:通过super实现对父类成员的访问。用来引用当前对象的父类。通过super显试调用父类的有参构造,无参构造可以隐式调用。
- this:用来引用当前对象,指向自己。
- final:可以修饰类,方法,修饰的类不能继承,修饰的方法不能重写,修饰的属性不能修改。
- private/protected:
在没有继承的情况下,protected跟private相同。在派生类的时候才出现分化。
父类对象不能访问父类的protected成员,子类中可以访问父类的protected成员。也就是说private成员是不能被继承的
- 多态:
多态就是相同对象收到不同消息(静态)或不同对象收到相同消息(动态)时产生不同的实现动作,说白点就是调用哪个虚函数,取决于引用的对象是哪种类型的对象。
- 编译时多态性(静态多态):通过重载函数实现
- 运行时多态性(动态多态):通过虚函数实现
C++运行时多态性是通过虚函数来实现的。 - 虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(Override),或者称为重写。
- 多态最常见的用法就是声明父类类型的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。
理解多态(假如有⼀个父类Father和子类Children)
- 向上转型是自动的 Father f = new Children();不需要强转
- 向下转型需要强转 Children c = (Children)new Father()需要强转。让父类知道具体转成哪个子类
- 父类引用指向子类对象,子类重写了父类的方法,调用父类的方法,实际是调用子类重写父类的方法后的方法。
父类f指向子类Children对象,Father f = new Children();f.toString()
实际是调用子类重写后的方法。
- 对函数的要求不同:重载函数只要求函数有相同的函数名,虚函数要求函数原型完全一样。因此虚函数体现在父类与子类之中。
- 构造函数可以重载,析构函数不能重载。相反,构造函数不能定义为虚函数,析构函数能定义为虚函数。
- 调用过程:重载函数的调用取决于参数列表,而虚函数是根据对象的不同。
- 显式的调用父类带参构造函数
- 初始化列表中无法直接初始化基类的数据成员,所以你需要在列表中指定基类的构造函数,如果不指定,编译器则会调用基类的默认构造函数。
- 要显式的调用基类Base()带参构造函数->初始化列表。
BaseChild():Base(1)
{
cout << "create is BaseChild()" << endl;
}
- 可以初始化类本身的数据成员 对BaseChild成员m_num进行初始化
BaseChild():Base(1), m_num(0){...};//调用基类Base的构造函数,对BaseChild成员m_num进行初始化
- 这样初始化成员效率高 它会比在函数体内初始化派生类成员更快,这是因为在分配内存后,在函数体内又多进行了一次赋值操作。
- 初始化类本身的数据成员的顺序 初始化列表并不能指定初始化的顺序,正确的顺序是,首先初始化基类,其次根据派生类成员声明次序依次初始化。
- 如果没有定义任何构造函数,C++编译器会自动创建一个默认构造函数。
- 如果已经定义了一个构造函数,编译器不会自动创建默认构造函数,只能显式调用该构造函数。
- 定义一个对象时先调用基类的构造函数、然后调用派生类的构造函数。
- 无参数无返回值。
- 析构函数调用的次序是先派生类的析构后基类的析构。
- 对象的内存空间尚未初始化
虚函数对应一个虚指针,虚指针其实是存储在对象的内存空间的。如果构造函数是虚的,对象还没有实例化,也就是内存空间还没有,就没有虚指针,所以构造函数不能是虚函数。 - 不能通过父类的指针或者引用去调用
虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成子类的那个成员函数(虚函数->对应成员函数)。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。
- 释放内存时: 内存如果基类析构函数不是虚函数:基类指针指向子类对象,delete基类指针,调用基类析构函数,不会调用子类析构函数,造成内存泄露。 所以要使用虚函数实现动态绑定,先调动子类的析构函数,再调动父类的析构函数(delete父类指针所指的空间,要调用父类的析构函数)
- 子类是否需要重写
虚函数是有代码的并明确允许子类去覆盖,但子类也可不覆盖,就是说可以直接用,不用重写;抽象函数是没有代码,子类继承后⼀定要重写。 - 虚函数变为抽象函数
在virtual函数后⾯写上=0,虚函数就成了纯虚函数,也就是抽象函数,包含抽象函数的类称为抽象类。
是指针常量,对已存在变量取了一个别名,特点如下:
- 引用在定义时必须初始化。
- 引用的指向不可以改变,引用指向的值可以改变。
- 指针常量int * const (a):表示该指针本身是一个常量。(指针是常量)
- 常量指针const int (* a):表示指向一个常量的指针。(指向常量的指针)
- 引用可以作函数的形参,这是因为引用的效率更高,以值作为参数会传递一份临时拷贝。
- 没有NULL引用,但有NULL指针。
卡特兰数:对于n个不同元素进栈,出栈序列的个数为 (1/n+1) * C^(n)(2n)