1.面向对象
面向过程:以过程为中心,将程序抽象成数据和对数据的操作。
面向对象:以对象(类)为中心,将程序抽象成对象之间的操作
1.1 封装、继承、多态
封装:代码模块化;
继承:代码重用;
多态:接口重用。
1.2 重载和重写
重载:不同的函数使用相同的函数名,但是参数个数或者类型不同
重写:派生类中对基类的虚函数重新实现,即函数名和参数都一样,但是实现体不一样
1.3 深拷贝和浅拷贝
浅拷贝:值拷贝,指向同一块内存空间,(默认构造函数和默认的赋值运算符重载函数)
深拷贝:拷贝相应的内存,不会出现重复释放同一块内存的现象
1.4 多态:静态多态和动态多态
静态多态有函数重载、运算符重载、泛型编程等,(重载和模板)
动态多态是在程序运行时根据基类的引用(指针)指向的对象来确定自己具体该调用哪一个类的虚函数。当父类指针(引用)指向 父类对象时,就调用父类中定义的虚函数;即当父类指针(引用)指向 子类对象时,就调用子类中定义的虚函数(虚函数和继承)
1. 动态多态行为的表现效果为:同样的调用语句在实际运行时有多种不同的表现形态。
2. 实现动态多态的条件: - 要有继承关系 - 要有虚函数重写(被 virtual 声明的函数叫虚函数) - 要有父类指针(父类引用)指向子类对象
3. 动态多态的实现原理 当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储类虚函数指针的数据结构, 虚函数表是由编译器自动生成与维护的。virtual 成员函数会被编译器放入虚函数表中,存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr 指针)。在多态调用时, vptr 指针就会根据这个对象在对应类的虚函数表中查找被调用的函数,从而找到函数的入口地址(使用虚函数需要额外的内存和执行速度的成本)
1.5 拷贝构造和赋值构造
拷贝构造时机:用一个对象初始化另一个对象,对象以值传递的方式传递给函数参数,函数局部对象以值传递的方式从函数返回
赋值操作:将一个对象赋值给另一个对象
1.6 类如何不能实例化
1. 抽象类(纯虚函数)
2. 构造函数声明为private(单例模式)
1.7 多继承的二义性
1. 利用作用域::;
2. 派生类定义同名成员,覆盖基类的相关成员
3. 使用虚继承,使不同路径只有一份拷贝
1.8 指针数组和数组指针
数组指针 int (*p)[10];
指针数组 int *p[10]
2.不同数据类型的字节大小
不同的系统 | 16位 | 32位 | 64位 |
char | 1 | 1 | 1 |
short | 2 | 2 | 2 |
int | 2 | 4 | 4 |
float | 4 | 4 | 4 |
long | 4 | 4 | 8 |
longlong | 8 | 8 | 8 |
double | 8 | 12 | 16 |
指针 | 2 | 4 | 8 |
3.C++和C的区别
1 C++兼容C
2 C++是面向对象的语言,C是面向过程(主要区别,包括封装、继承、多态)
3 C++可读性强,易于扩展、结构清晰
4 C++效率高,仅比汇编慢10%~20%左右(例如算法题额外要求C++的执行时间更短)
5 C++更安全,加入了类型转换、智能指针、try catch等
6 C++可复用性高,引入了模板的概念,和标准模板库STL
7 C++是在不断发展的语言,目前最新C++20
4.CONST详解(左定值右定向)
4.1 用法
4.1.1 用在变量上,使其值不能改变
4.1.2 用在指针上,使其指向不能改变
4.1.3 函数参数,说明函数体内参数不能修改
4.1.4 类中修饰成员方法,防止在方法中修改非static成员
class A { public: int a; void fun() const { a = 20; // 错误,const
//修饰的成员方法中不能修改非静态成员变量 } }
4.1.5 修饰类成员
4.2 const常见问题
const在变量的左边是定值,右边是定向(左定值右定向)
常量指针:
int a = 5;
const int *p =&a;
*p = 20; //error 不可以通过修改所指向的变量的值
int b =20;
p = &b; //right 指针可以指向别的变量
指针常量:
int a = 5;
int *const p = &a;
*p = 20; //right 可以修改所指向变量的值
int b = 10;
p = &b; //error 不可以指向别的变量
5.函数指针
用法:①回调函数 ②代码量大时候的函数调用
6.delete和free
6.1 delete是操作符,free为函数
6.2 free不会调用析构函数,delete会
6.3 free之前需要检测指针是否为NULL,delete不需要
7.new和malloc区别
1. new是操作符,而malloc是函数。
2. new在调用的时候先分配内存,在调用构造函数,释放的时候调用析构函数;而malloc没有构造函数和析构函数。
3. malloc需要给定申请内存的大小,返回的指针需要强转;new会调用构造函数,不用指定内存的大小,返回指针不用强转。
4. new可以被重载;malloc不行
5. new分配内存更直接和安全。
6. new发生错误抛出异常,malloc返回null
8.引用和指针区别
引用和指针区别:
1. 指针是变量,存储地址,引用本质上是同一个变量,别名
2. 指针可以多级,引用一级;
3. 指针可以NULL,引用不行;
4. 指针可以改变指向,引用不行;
5. 把指针作为参数进行传递时,也是将实参的一个拷贝传递给形参,两者指向的地址相同,但不是同一个变量,在函数中改变这个变量的指向不影响实参,而引用却可以(改形参指向而不改其实参指向)
9.虚函数
虚函数可以是内联函数,但是不能在表现多态时候内联
9.1 纯虚函数
纯虚函数:特殊的虚函数,virtual int func(int a)=0;
从而使该类变为抽象类,无法实例化,派生类需要重写才能实例化
9.2 虚函数和纯虚函数区别
虚函数可以有实现,纯虚函数不能,且有返回值。
9.3 纯虚函数和虚函数作用
虚函数作用:允许子类重新实现该功能;
纯虚函数作用:让基类定义接口而不需要实现,确保派生类重写该函数,提供了一种强制规范。
9.4 纯虚析构函数的作用
防止遗漏资源的释放,防止内存泄漏。如果基类中的析构函数没有声明为虚函数,基类指针指向派生类对象时,则当基类指针释放时不会调用派生类对象的析构函数,而是调用基类的析构函数,如果派生类析构函数中做了某些释放资源的操作,则这时就会造成内存泄露
10. define和typedef区别
typedef主要用于类型别名
1. define简单的字符串替换,没有类型检查,前者不是语句没有;
2. 预处理时处理,后者编译运行处理
3. 可以防止头文件重复引用,不分配内存,后者静态存储区分配内存,运行中程序有一个拷贝
11.内联函数和函数区别
1. 内联多了inline
2. 避免了函数调用的开销
3. 普通函数在被调用时,需要寻址,内联不需要
4. 内联函数要求代码简单
12.类型转换
1. static_cast 静态转换 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换
- 进行上行转换(把派生类的指针或引用转换成基类表示)是安全的 - 进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的 用于基本数据类型之间的转换,如把 int 转换成 char,把 char 转换成 int。这种转换的安全性也要开发人员来保证
2. dynamic_cast 动态转换 dynamic_cast 主要用于类层次间的上行转换和下行转换
在类层次间进行上行转换时,dynamic_cast 和 static_cast 的效果是一样的 在进行下行转换时,dynamic_cast 具有类型检查的功能,比 static_cast 更安全
3. const_cast 常量转换 该运算符用来修改类型的const属性 常量指针被转化成非常量指针,并且仍然指向原来的对象 常量引用被转换成非常量引用,并且仍然指向原来的对象 注意:不能直接对非指针和非引用的变量使用 const_cast 操作符
4. reinterpret_cast 重新解释转换 这是最不安全的一种转换机制,最有可能出问题 主要用于将一种数据类型从一种类型转换为另一种类型,它可以将一个指针转换成一个整数,也可以将一个整数转换成一个指针
13.static关键字作用
修饰局部变量时,使得该变量在静态存储区分配内存;只能在⾸次函数调⽤中进⾏⾸次初始化,之后的函数调⽤不再进⾏初始化;其⽣命周期与程序相同,但其作⽤域为局部作⽤域,并不能⼀直被访问;
修饰全局变量时,使得该变量在静态存储区分配内存;在声明该变量的整个⽂件中都是可⻅的,⽽在⽂件外是不可⻅的;
修饰函数时,在声明该函数的整个⽂件中都是可⻅的,⽽在⽂件外是不可⻅的,从⽽可以在多⼈协作时避免同名的函数冲突;
修饰成员变量时,所有的对象都只维持⼀份拷⻉,可以实现不同对象间的数据共享;不需要实例化对象即可访问;不能在类内部初始化,⼀般在类外部初始化,并且初始化时不加 static ;
修饰成员函数时,该函数不接受 this 指针,只能访问类的静态成员;不需要实例化对象即可访问。
简单来说就是:
1. 隐藏 2. 保持变量内容的持久 3. 默认初始化为0 4. 类中共享
14.智能指针
智能指针是⼀个RAII类模型,⽤于动态分配内存,其设计思想是将基本类型指针封装为(模板)类对象指针,并在离开作⽤域时调⽤析构函数,使⽤ delete 删除指针所指向的内存空间。
RAII手法:采用类封装,构造函数初始化指针,析构函数释放指针,使用时候由于初始化的类在栈上,所以当离开作用域时候会自动调用析构函数,从而释放其指向堆区的指针。
智能指针的作⽤是,能够处理内存泄漏问题和空悬指针问题。
三种智能指针:share、unique、weak、(atuo)
share(内部具有计数器,计数器到达0就释放)
15.悬挂指针和野指针
悬挂指针:指针指向的对象已经被释放,指针指向回收的地址
野指针:未初始化的指针
避免:1.指针变量一定要初始化,可以初始化为 nullptr,因为 nullptr 明确表示空指针
2.释放后置为 nullptr
16. 动态链接和静态链接
1. 静态链接是在编译链接时直接将需要的执⾏代码拷⻉到调⽤处;
优点在于程序在发布时不需要依赖库,可以独⽴执⾏,缺点在于程序的体积会相对较⼤,⽽且如果静态库更新之后,所有可执⾏⽂件需要重新链接;
2. 动态链接是在编译时不直接拷⻉执⾏代码,⽽是通过记录⼀系列符号和参数,在程序运⾏或加载时将这些信息传递给操作系统,操作系统负责将需要的动态库加载到内存中,然后程序在运⾏到指定代码时,在共享执⾏内存中寻找已经加载的动态库可执⾏代码,实现运⾏时链接;
16.1 动态链接的优缺点
优点在于多个程序可以共享同⼀个动态库,节省资源;
缺点在于由于运⾏时加载,可能影响程序的前期执⾏性能