针对《程序员面试笔记——C/C++、算法、数据结构篇》一书的读书记录整理
答:#include<>从编译器指定路径处搜索,#include""首先在程序当前目录中进行搜索,然后再从编译器指定的路径进行搜索。
答:宏定义中#运算符将其后面的参数转换成字符串,宏定义中的##运算符前后的参数进行字符串拼接
答:assert用于在程序的DEBUG版本中检测条件表达式,如果为假,则输出诊断信息并终止程序运行。
前者先加1后赋值,后者先赋值后加1
静态全局变量的作用域为仅限于定义位置的文件内部。如果放在头文件里,其他文件包含以后,会对此静态全局变量进行拷贝,每个拷贝相互独立。
对比 | 宏定义 | 内联 |
---|---|---|
替换阶段 | 预处理阶段 | 编译阶段 |
替换方式 | 简单字符串替换 | 函数内嵌 |
调试模式 | 不可调试 | 可调式 |
参数检查 | 无参数检查 | 有参数检查 |
作为类内成员函数 | 无法访问类内所有成员 | 可以访问类内所有成员 |
缺点 | 没有参数检查 | 代码膨胀 |
注意:在定义内联函数的时一定要在函数定义时使用inline关键字,在函数声明中使用inline时没有效果的。
数据对齐指在处理结构体中的成员时,成员在内存中的起始地址编码必须时成员类型所占字节数的整数倍。
结构体sizeof的计算结果必须是结构体中所占空间最多的成员所占空间的整数倍。
在数据对齐时,要以结构体最深层的基本数据类型为准。
对比 | malloc/free | new/delete |
---|---|---|
定义 | C语言的库函数 | C++的运算符 |
应用 | 只能用于基本类型 | 可以用于基本类型以及用户自定义类型 |
返回值 | 返回void*,需要显式转换成所需要的类型 | 直接指明对象类型 |
操作 | 只负责在堆上申请空间,返回首地址 | 申请空间后会调用对象的构造函数/析构函数 |
作为类内成员函数 | 无法访问类内所有成员 | 可以访问类内所有成员 |
当new[]中的数组元素是基本类型时,通过delete和delete[]都可以释放数组空间
当new[]中的数组元素时自定义类型时,只能通过delete[]释放数组空间
一个异或操作的交换律公式,a ^ b ^ a == a ^ a ^ b == b
一个数与其自身异或操作结果为0,一个数与0进行异或操作结果还是这个数本身。
将原数字每次右移1位,与1按位与操作,返回的是1,计数+1
或者把1按位左移进行按位与。
所有数字异或一次,最后与0异或,返回值就是不成对的数字
main函数第一行代码执行前会调用全局对象和静态对象的构造函数,初始化全局变量和静态变量,main函数最后一行代码执行之后会调用atexit注册的函数,并且调用顺序与注册顺序相反。
数组名等价于数组首元素的地址,当数组名作为参数时,相当于传递了数组首元素的地址,而且只要实参是地址,那么形参一定是指针。
数组名是不能进行自增操作的,而指针可以。
句柄是一个32位的无符号证数,表示一个内存地址列表的整数索引,是分配给资源的唯一标识。句柄是间接指向资源对象的。有句柄的原因是因为如果资源对象在系统中一直处于空闲状态,那么操作系统的内存模块会将其内存回收,如果重新访问这个资源,系统会再重新分配内存,这个操作可能导致资源对象的物理地址放生改变。
实际上句柄中记录着资源对象列表中某个成员对象的缩影。
指针就是内存地址。
常量地址不能初始化普通指针
指针常量定义时必须初始化
常量指针不能修改指向的内容
指针常量不能修改指针的值
字符串常量是存在常量区中,如果定义一个const指针获取常量的指针,是不可以通过该定义的指针去修改常量的,此时这个指针相当于指向常量的常量指针。
数组名始终等价于数组首元素的地址
如果想通过指针在被调函数中修改主调函数的变量,必须将主调函数变量的地址作为参数,在被调函数中修改指针指向的内容。
指针变量可以指向任意类型的数据,也可以指向一个函数,每个函数在内存中都占用一段存储单元,这段存储单元的首地址称为函数的入口地址,指向这个函数入口地址的指针称为函数指针。函数名等价于函数的入口地址。
空指针是一种特殊的指针:处于空闲状态,没有指向任何变量。
野指针不是空指针,而是指向不明或不当的内存地址。出现野指针的原因
常量引用的初始化操作实际上分为两步:1将常量存放在一个临时变量里面,然后使用临时变量初始化常量引用
栈空间存储函数参数局部变量,空间由操作系统自动分配回收。
堆空间用于动态分配内存块,由程序员分配跟释放
堆空间的频繁分配与释放会造成空间碎片化
栈空间默认在windows下都是1MB,堆空间理论有几G
生长方向:栈是自上而下,内存地址逐渐减小。堆是自下而上,内存地址逐渐增大
在递归调用过程中,每次递归都会保留现场,把当前的上下文压入函数栈,调用过多,压栈过多会造成栈溢出,可以考虑使用循环代替递归调用。
使用智能指针代替普通指针
保证malloc和free,new和delete成对出现
检查释放内存时有没有提前return的情况
通过下标访问vector 的元素时不会进行边界检查,程序不会报错。
如果想在访问vector中的元素时首先进行边界检查,可以使用at函数
两者都是C标准库函数
memcpy时C语言的内存拷贝函数,提供的是内存拷贝功能,不是只针对字符串
strcpy专为字符串定义的拷贝函数,用于标准字符串拷贝(有字符串结束标志\0)
面向过程是分析问题的解决步骤,明确步骤的输入输出跟流程,结构化自上而下的程序设计方法。
面向过程是把构成问题的事务分解成对象,从局部着手,通过迭代的方式逐步构建整个程序,以数据为核心,类设计为主要工作的程序设计方法。
面向过程使程序性能更高,系统开销更小。
面向过程使程序有更好的可扩展可复用可维护性。
抽象:对象抽象成类。
继承:继承一个类使得继承的类有被继承类的特性。
封装:把属性方法隐藏起来,只暴露有限的信息。
多态:不用对象对于同一消息的不同响应。
类的默认访问控制是private,struct的默认访问控制是public
C++在C语言上对struct进行了拓展,使得struct也可以包含方法成员。
静态数据成员属于整个类,不属于某个对象,必须在类内声明,类外初始化
静态成员函数也是属于整个类,没有this指针,所以不能访问非静态成员变量。
访问静态成员时可以通过类访问,也可以通过对象访问。
修饰成员变量代表成员变量初始化之后不可变,需要在初始化列表中进行初始化
修饰成员函数代表该成员函数不能改变成员变量的值
常量对象只能调用类的常量成员函数
友元函数不是类的成员函数,而是类外部的函数,能够访问类的非公有成员。
友元类的所有成员函数都是另一个类的友元函数。
友元关系是单方向的,且不能被继承。
自上而下构造,自下而上析构
C++在编译过程中对函数重命名,而C语言保留原始函数名
告诉编译器使用C函数编译,不进行重命名
函数覆盖发生在子类和父类之间,父类定义虚函数,子类重新实现这个函数,函数原型相同,根据对象的选择调用的函数。
函数重载是同一类不同方法,参数列表不同,根据参数类型选择调用的函数。
父类中有一组重载函数,子类在继承时如果覆盖了这组重载函数的任意一个,则其他没有被覆盖的同名函数在子类中是不可见的。
继承是is-a关系,组合是has-a的关系
公有继承可以访问父类的公有成员保护成员
保护继承可以访问共有成员保护成员,继承过来的共有成员都变为保护成员
私有继承可以可以访问公有成员保护成员,继承过来的共有成员保护成员都变成私有成员
创建一个子类对象时,系统在执行子类构造函数的函数体前,首先调用父类的构造函数。
在菱形继承中存在访问二义性的问题,使用虚继承保证了子类只有被菱形继承的爷爷类只有一份拷贝,虚继承保证继承关系中的虚基类只被初始化一次
1字节,占位符
如果一个类中有虚函数,那么这个类就会对应一个虚函数表,虚函数表中的元素是一组指向函数的指针,每个指针指向一个虚函数的入口地址,在访问虚函数的时候通过虚函数表进行函数调用。
在含有虚函数的类对象中,除了对象的数据成员之外,还有一个指向虚函数表的指针,位于顶部。
先找参数完全匹配的普通函数
寻找模板参数完全匹配的函数模板,并实例化一个模板函数
通过隐式转换匹配普通函数
都失败编译失败
如果向一个已满的vector插入元素,会重新分配一块内存空间,并将原有元素和新插入的元素拷贝到新空间中。内存增长大小一般为1.5-2倍
size返回容器中已经保存的元素个数
capacity返回容器当前容量大小
reserve函数可以让容器重新分配指定大小的空间
shrink_to_fit函数可以回收所有尚未使用的剩余空间
resize函数可以强制调整容器中已保存的元素个数
双端队列deque是一种双向开口的存储空间分段连续的数据结构,每段数据空间内部是连续的,而每段数据空间不一定连续。在向deque删除和插入元素的过程中,会根据数据空间的状态,动态分配和释放空间,数据空间段的数量会发生变化。
对容器进行删除操作时,容器中元素的数量发生变化,这种变化可能会导致某些元素的物理地址发生变化,使指向这些元素的迭代器失效。
有可能出现环状引用的地方使用weak_ptr弱指针代替shared_ptr共享指针可以有效地避免环状引用的问题
由于unique_ptr在内存安全性,充当容器元素和支持动态数组方面优于auto_ptr,因此C++11使用unique_ptr代替auto_ptr