C++
复习内容持续更新,或有缺陷…
malloc 和 new 的区别
malloc | new |
---|---|
malloc是C的库函数 | new是C++的运算符 |
malloc成功时返回的是void*,通常需要强制类型转换 | new成功时返回的是指向指定类型的指针 |
malloc是从堆上动态分配内存 | new是从自由存储区上为对象分配内存 |
malloc失败时,返回的是NULL | new失败时,会抛出bad_alloc的错误 |
malloc需要手动设置分配内存的大小 | new根据数据类型分配内存大小 |
malloc分配内存,不会调用对象的构造函数 | new分配内存会调用对象的构造函数 |
malloc和free相对应 | new和delete相对应 |
C++ 编译过程
- 预处理阶段:对源代码中包含关系(头文件),预编译语句(宏定义)进行分析和替换,生成预编译文件。
- 编译阶段:将预编译处理后得到的文件转换为特定的汇编代码,生成汇编文件。
- 汇编阶段:将编译阶段生成的汇编文件转换为机器码,生成可重定位文件。
- 链接阶段:将目标文件和所需要的库链接成最终的可执行文件。
- 静态库和动态库
- 静态库(.a .lib):静态库对函数库的链接是在编译时完成的,静态库进行更新,所有的程序都需要重新编译。
- 动态库(.so .dll):动态库对函数库的链接是在运行时加载的,系统只加载一次,多个程序公用,节省内存。
C++ 多态
-
多态包括编译时多态和运行时多态:
编译时多态:函数重载和泛型编程
运行时多态:虚函数,声明基类指针指向任意子类对象,调用相应的虚函数
-
每个虚函数都有虚函数表,在调用相应的函数时,并不是直接调用这个函数,而是根据虚函数表找到相应的地址,然后调用该函数。
-
只有声明为virtual的函数才有多态的效果,否则为原型调用。
-
构造函数不能够声明为virtual,因为调用虚函数的过程需要用到虚函数表,若将构造函数声明为virtual,则在构造函数调用前无虚函数表,构造函数无法调用,构成矛盾。
-
析构函数需声明为virtual,否则子类对象在释放内存的过程中无法动态调用子类的析构函数,造成内存泄漏。
-
多态的缺点:
- 运行效率低
- 造成空间浪费
-
静态成员函数不能是虚函数,他不属于任何对象,使用virtual会报错。访问虚函数表需要用到虚函数指针,而虚函数指针是类的成员变量,所以静态函数无法实现为虚函数。
-
内联函数不能是虚函数,如果内联函数被声明为virtual,则计算机会忽略inline使其变成纯虚函数。虚函数只有在运行时才知道要调用哪个函数,而内联函数在编译时就已经展开。
-
一个类对应一个虚函数表,一个对象有一个虚函数指针。
静态类成员函数
- 当成员被声明为static,则无论类有多少个对象,它在内存中都只有一份拷贝。
- 未初始化的static成员函数被程序自动初始化为0.
- 静态成员变量在文件内部是可见的,在文件的外部是不可见的。
- 静态变量保存在全局静态区域,包括局部静态变量。
- 使用静态函数的好处是,在其他文件中定义相同的名字,不会发生冲突。
- 类的静态成员是属于类的,而不是属于类的对象的。
- 在调用静态成员的时候,直接用类名和与运算符来获取。
- 类的静态成员函数没有this指针。
- 类的静态成员函数只能访问类的静态成员变量和静态成员函数,不能访问非静态的成员和函数,因为非静态的成员函数是属于类的对象的,而静态成员函数是属于类的,没有具体的对象可以操作。
拷贝构造函数和赋值函数的区别
- 拷贝构造函数是初始化一块内存区域,而赋值函数是对一个已经初始化的区域进行赋值操作。
- 拷贝构造函数大多数情况下是复制,而赋值函数则是引用。
- 拷贝构造函数是通过参数初始化一个对象,赋值函数则是把新对象值赋值给旧的对象,在赋值前需要检测是否是同一个对象,并且如果原先有动态分配内存,需要先将内存释放掉。
深拷贝和浅拷贝的区别
- 浅拷贝:如果复制的对象中有一个外部内容,那么复制这个对象的时候,让新旧两个对象指向相同的一个外部内容,就是浅拷贝。
- 深拷贝:制作了新的外部对象的独立复制。
C++四种强制类型转换
- static_cast:可实现C++内置数据类型的基本转换,但是不能进行无关指针之间的转换。
- const_cast:将非const数据类型转换为const
- reinterpret_cast:数据二进制形式重新解释,可以将任何内置数据类型转换为其他数据类型。
- dynamic_cast:不能用于内置数据类型的转换,可用于基类指针引用安全转换为子类指针引用,使用时,基类一定要用virtual.
智能指针
智能指针主要用来解决悬垂指针的问题,当有多个指针指向同一个对象时,如果此时某个指针释放了该对象,则其他的指针并不知道对象已经被释放掉,这个时候访问指针就会发生错误,程序容易出现崩溃。
智能指针是一个类,类的构造函数传入一个普通指针,析构函数释放传入的指针。
常用的智能指针:std::auto_ptr,unique_ptr,shared_ptr(基于计数器的智能指针),weak_ptr(弱引用,只引用不计数,需检查是否为空)
内存概念
- 内存溢出:在申请动态内存的时候,没有足够的空间。
- 内存泄漏:在使用完动态申请的内存以后,没有进行释放。
- 内存越界:在向内存申请一块区域后,使用的时候超出范围。
指针和引用的区别
-
指针是变量,只不过该变量是地址,指向内存的存储单元;
引用是原变量的别名,与原变量实质上是相同的。
-
有const指针,但是没有const引用:
const int* p = &a; // a不可变,*p=100合法 int* const p = &a; // a可变,*p=100非法,即有const的指针 const int &p = a; //正确 int const &p = a; //错误,即无const的引用
-
指针有多级指针,引用只能1级。
-
指针的值可以为空,引用的值不能为空,并且引用在定义时必须初始化。
-
指针在初始化后可以改变,引用在初始化后不能改变。
-
引用的sizeof得到的是引用变量的大小,指针的sizeof得到的是指针本身的大小。
-
引用和指针的自增运算符的含义不同。
sizeof
定义空类型(里面没有任何成员变量和成员函数),则sizeof的结果为1,因为类型的实例必须要在内存中占用一定的空间,否则无法使用这些实例(区分不同的类实例)。如果类里面加上构造函数和析构函数,sizeof的结果仍然为1,因为这些函数都至于类型有关,与类型的实例无关。如果把析构函数定义为虚函数,则系统会生成虚函数表,实例会生成指向虚函数的指针,在64位机器中,sizeof的结果为8
class Empty {};
class HoldsAnInt {
int x;
Empty e;
};
在上述代码中,空类的一个字节是会被计算到HoldsAnInt的空间中去的,由于地址对齐的机制,所以最后计算的结果为4+4=8
成员函数最终被编译为与对象无关的普通函数,当不涉及到类成员变量的时候,直接调用即可,如果涉及到类成员变量,则需要传入this指针进行操作。
动态绑定:类指针或者引用在调用一个虚函数时,不是指向该函数的地址,而是指向这个虚函数表。
A& a;
a->func()
静态绑定:直接指向函数的地址。
A a;
a.func();
a.func()调用的是A类中的虚函数,而不是父类中的虚函数
struct 和 class 的区别
- 默认情况下,struct 的成员变量是 public,class 的成员变量是 private.
- struct 默认是 public 继承,class 默认是 private 继承。
- struct不能用模版,class可以。
define 和 const 的区别
-
define 定义的是宏,只进行宏替换,生命周期终止于编译器,没有具体数据类型;
-
const 是限定修饰符,const 变量存储于程序的数据段,并且在堆栈中分配了存储空间,有确定的数据类型。