参考引用
- 阿秀的学习笔记
- 本博客对上述笔记进行较大程度的整合、精简与补充
1. C++ 面向对象三大特性
C++ 面向对象的三大特性为:封装、继承和多态
- 封装
- 在设计类时,属性和行为写在一起,表现事物
- 类在设计时,可以把属性和行为放在不同的权限下加以控制
- 继承
- 让某种类型对象获得另一个类型对象的属性和方法,它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展
- 有些类与类之间存在特殊的关系,定义这些类时,下级别的成员除了拥有上一级的共性,还有自己的特性,这时就可以考虑利用继承的技术,减少重复代码
- 继承中先调用父类构造函数,再调用子类构造函数,析构顺序与构造相反
继承与派生区别
- 1、继承是指一个类(称为子类或派生类)可以继承另一个类(称为父类或基类)的属性和行为。派生是指通过继承,子类可以获得父类的属性和方法,并且可以在此基础上进行扩展或修改
- 2、继承是实现代码重用和建立类之间关系的重要方式,可以减少重复代码的编写,提高代码的可维护性和可扩展性
- 3、派生类可以重写(override)父类的方法,以实现定制化的行为;也可以通过新增方法或属性来扩展父类的功能
- 多态
- 定义:同一操作作用于不同的对象,产生不同的执行结果
- 实现:通过虚函数实现,用 virtual 声明的成员函数就是虚函数,调用虚成员函数时,会根据调用类型对象的实际类型执行不同的操作
- 实现多态的方式
- 覆盖(override):派生类覆盖基类用 virtual 声明的成员函数,函数名、参数类型和返回值均相同
- 重载(overload):函数名相同,参数类型或顺序不同的函数构成重载
- 隐藏(overwrite):派生类的函数屏蔽了与其同名的基类函数
- 多态的类型
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名,地址早绑定(编译阶段确定函数地址)
- 动态多态:派生类和虚函数实现运行时多态,地址晚绑定(运行阶段确定函数地址)
2. main 执行前和执行后的内容
-
main 函数执行之前(主要是初始化系统相关资源)
- 设置栈指针
- 初始化静态(static)变量和全局(global)变量
- 将未初始化部分的全局变量赋初值:数值型 short,int,long 等为 0,bool 为 FALSE,指针为 NULL 等
- 将 main 函数的参数 argc、argv 等传递给 main 函数,然后才真正运行 main 函数
-
main函数执行之后
- 全局对象的析构函数会在 main 函数之后执行
- 可以用 atexit 注册一个函数,它会在 main 之后执行
3. 结构体内存对齐问题
- 结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同
- 未特殊说明时,按结构体中 size 最大的成员对齐(若有 double 成员,按 8 字节对齐)
4. 指针和引用的区别
- 指针是一个变量(存储的是一个地址),引用和原变量实质上是同一个东西(是变量的别名)
- 指针可以有多级,引用只有一级
- 指针本身存储的是地址,而地址也可以是另一个指针的地址,这样就形成了多级指针
- 引用本质上是某个变量的别名,它在声明时就要初始化,并且在整个生命周期中只能绑定到同一变量,无法更改引用的目标,因此引用不能形成多级引用的结构
- 指针可以为空,引用不能为 NULL 且在定义时必须初始化
- 指针在初始化后可以改变指向,而引用在初始化后不可再改变
- sizeof 指针得到的是本指针的大小,sizeof 引用得到的是引用所指向变量的大小
- 当把指针作为参数进行传递时,也是将实参的一个拷贝传递给形参,两者指向的地址相同,但不是同一个变量,在函数中改变这个变量的指向不影响实参,而引用却可以
5. 在传递函数参数时,使用指针/引用的时机
- 需要返回函数内局部变量的内存时用指针
- 使用指针传参需要开辟内存,用完要记得释放指针,不然会内存泄漏
- 而返回局部变量的引用是没有意义的
- 对栈空间大小比较敏感(比如递归)时用引用
- 使用引用传递不需要创建临时变量,开销要更小
- 类对象作为参数传递时用引用
- 这是 C++ 类对象传递的标准方式
6. 堆和栈的区别
堆和栈在不同场景下的含义
- 程序内存布局场景下,堆与栈表示两种内存管理方式
- 数据结构场景下,堆与栈表示两种常用的数据结构
- 栈就像去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,好处是快捷,但是自由度小
- 堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大
- 栈与堆哪个更快?见下图:分配效率
7. 栈和队列(数据结构)的区别
- 规则不同
- 队列:先进先出
- 栈:先进后出
- 对插入和删除的限定不同
- 队列:只能在表的一端进行插入,并在另一端进行删除
- 栈:只能在表的同一端插入和删除
- 遍历数据速度不同
- 队列:基于地址指针进行遍历,可以从头或者尾部进行遍历,不能同时遍历,无需开辟新空间,在遍历过程中不影响数据结构,所以遍历速度快
- 栈:只能从栈顶取数据,如果要取出栈底的数据,需要遍历整个栈,并且遍历的同时开辟空间,保持遍历前的一致性
8. 区别以下指针类型
int *p[10] // 指针数组,强调是数组,是一个数组变量,大小为 10,数组内每个元素都是指向 int 类型的指针变量
int (*p)[10] // 数组指针,强调是指针,只有一个变量,是指针类型,指向的是一个 int 类型的数组,大小是 10
int *p(int) // 函数声明,函数名是 p,参数是 int 类型,返回值是 int* 类型的
int (*p)(int) // 函数指针,强调是指针,该指针指向的函数具有 int 类型参数,并且返回值是 int 类型的
9. new/delete 与 malloc/free 的异同
- 相同点
- 都可用于内存的动态申请和释放
- 不同点
- 前者是 C++ 运算符(支持重载),后者是 C/C++ 语言标准库函数(支持覆盖),需要库文件支持
- new 自动计算要分配的空间大小,malloc 需要手工计算
- new 是类型安全的,而 malloc 不是类型安全的
- new 封装了 malloc,直接 free 不会报错,但是这只是释放内存,而不会析构对象
- malloc/free 返回的是 void 类型指针(必须进行类型转换),new/delete 返回的是具体类型指针
- malloc 仅仅分配内存空间,free 仅仅回收空间,new/delete 除了分配回收功能外,还会调用构造/析构函数
- new 内存分配失败时,会抛出 bac_alloc 异常,malloc 分配内存失败时返回 NULL
10. new 和 delete 是如何实现的
- new 的实现过程是
- 首先调用名为 operator new 的标准库函数,分配足够大的原始为类型化的内存,以保存指定类型的一个对象
- 接下来运行该类型的一个构造函数,用指定初始化构造对象
- 最后返回指向新分配并构造后的的对象的指针
- delete 的实现过程
- 对指针指向的对象运行适当的析构函数
- 然后通过调用名为 operator delete 的标准库函数释放该对象所用内存
delete 和 delete[] 区别
- delete 只会调用一次析构函数
- delete[] 会调用数组中每个元素的析构函数
11. 既然有 malloc/free,C++ 中为什么还需要 new/delete
- 在使用非基本数据类型的对象时,对象创建时需要执行构造函数,销毁时需要执行析构函数,而 malloc/free 是库函数,是已经编译的代码,不能把构造函数和析构函数的功能强加给 malloc/free,所以 new/delete 是必不可少的
12. 宏定义和(内联)函数的区别
- 宏定义在预处理阶段完成替换,之后被替换的文本参与编译,相当于直接插入了代码,运行时不存在函数调用,执行起来更快,函数调用在运行时需要跳转到具体调用函数
- 宏定义没有返回值,函数调用有返回值
- 宏定义参数没有类型,不进行类型检查,函数参数具有类型,需要检查类型
内联函数在编译时直接将函数代码嵌入到目标代码中,省去函数调用的开销来提高执行效率,并且进行参数类型检查,具有返回值,可以实现重载,内联函数适用场景如下
- 使用宏定义的地方都可以使用 inline 函数
- 作为类成员接口函数来读写类的私有成员或者保护成员,会提高效率
- 不适合使用内联函数的场景:函数体内的代码比较长、函数体内有循环
13. 宏定义和 typedef 的区别
- 宏定义主要用于定义常量及书写复杂的内容,typedef 主要用于定义类型别名
- 宏定义替换发生在编译之前,属于文本插入替换,typedef 是编译的一部分
- 宏定义不检查类型,typedef 会检查数据类型
- 宏定义不是语句,不在在最后加分号,typedef 是语句,要加分号标识结束
14. 变量声明和定义区别
- 声明仅仅是把变量的声明位置及类型提供给编译器,并不分配内存空间,定义要在定义的地方为其分配存储空间
- 相同变量可以在多处声明(外部变量 extern),但只能在一处定义
15. strlen 和 sizeof 区别
- sizeof 是运算符,并不是函数,strlen 是字符处理的库函数
- sizeof 参数可以是任何数据的类型或者数据,strlen 的参数只能是字符指针且结尾是 ‘\0’ 的字符串
- 因为 sizeof 值在编译时确定,所以不能用来得到动态分配(运行时分配)存储空间的大小
16. 一个指针占多少字节
- 一个指针占内存的大小跟编译环境有关,而与机器的位数无关
- 32 位编译环境下,指针占用大小为 4 字节
- 64 位编译环境下,指针占用大小为 8 字节
17. 常量指针和指针常量 & 顶层 const 和底层 const
-
常量指针和指针常量
// 从右往左读 // 1、常量指针 // 指针是个常量,必须初始化,一旦初始化完成,它的值(也就是存放在指针中的地址)就不能再改变,即不能中途改变指向 // 特点:指针指向的值可以修改,指针指向不可以修改 int* const p1 = &a; *p1 = 100; //p1 = &b; 报错 // 2、指针常量 // 指向一个只读变量的指针 // 特点:指针指向可以修改,指针指向的值不可修改 const int* p2 = &a; //int const* p2 = &a; 和上一句等价 p2 = &b; //*p2 = 100; 报错 // 3、指向常量对象的常量指针 // 特点:指针指向不可以修改,指针指向的值也不可修改 const int* const p3 = &a; //*p3 = 100; 报错 //p3 = &b; 报错
-
顶层 const 和底层 const
// 顶层 const:const 修饰的变量本身是一个常量,指的是指针 // 底层 const:const 修饰的变量所指向的对象是一个常量,指的是所指对象(变量) int a = 10; int* const b1 = &a; // 顶层 const,b1 本身是一个常量 const int b3 = 20; // 顶层 const,b3 是常量不可变 const int* b2 = &a; // 底层 const,b2 本身可变,所指的对象是常量 const int* const b4 = &a; // 前一个 const 为底层,后一个为顶层,b4 不可变 const int& b5 = a; // 用于声明引用变量,都是底层 const
18. C++ 和 Python 的区别
- Python 是一种脚本语言,是解释执行的,而 C++ 是编译语言,是需要编译后在特定平台运行的,python 可以很方便的跨平台,但是效率没有 C++ 高
- Python 使用缩进来区分不同的代码块,C++ 使用花括号来区分
- C++ 中需要事先定义变量类型,而 Python 不需要,Python 的基本数据类型只有数字、布尔值、字符串、列表和元组等
19. C++ 和 C 语言的区别
- C 是 C++ 的子集,C++ 可以很好兼容 C 语言,但 C++ 又有很多新特性如:引用、智能指针、auto 变量等
- C++ 是面向对象的编程语言,C 语言是面向过程的编程语言
- C++ 引入了模板的概念,可复用性高,在此基础上实现了方便开发的标准模板库 STL
- C++ 允许变量定义在程序中的任何地方,只要在使用它之前就可以,而 C 语言必须要在函数开头部分定义变量
- C++ 除了值和指针外还新增了引用,引用型变量是其他变量的一个别名
- C++ 可以实现函数重载,C 语言不可以
- C++ 中 new/delete 是对内存分配的运算符,取代了 C 中的 malloc/free
- C++ 中用来做控制态输入输出的 iostream 类库替代了C 中的 stdio 函数库
20. C++ 中 struct 和 class 的区别
- 相同点
- 两者都拥有成员函数、公有和私有部分,任何可以使用 class 完成的工作,同样可以使用 struct 完成
- 不同点
- class 默认是 private 继承,而 struct 默认是 public 继承
C++ 和 C 中的 struct 区别
- C 中 struct 是没有权限设置的,且 struct 中只能是一些变量的集合体,可以封装数据却不可以隐藏数据,而且成员不可以是函数
- C++ 中 struct 增加了访问权限,且可以和类一样有成员函数,成员默认访问说明符为public(为了与 C 兼容)