一、C和C++的不同点
1、基本的数据类型不同
bool 、 字符串 、 void*
2、复合型的数据类型不同
结构、联合
使用时关键字可以省略
有成员函数,有隐藏的四个成员函数
如果没有成员变量,结构、联合占用大小:C占用0字节 C++占用1字节,成员函数中有隐藏的this指针,因此需要在内存中有一席之地
可以管理成员的访问控制属性
public(默认)、private、protected
3、枚举类型不同
使用时关键字可以省略
枚举变量的值检查更加严格、如果不对会报错
枚举变量不能使用数值赋值,只能通过枚举常量赋值
4、类型限定符的不同
auto C++11 可以自动识别数据类型
const 都可以保护变量不被"显式"修改
C++中不会主动读取被const修饰的变量的内存值,默认只使用初始化的值,做了取值优化,要求const修饰的变量必须初始化
C针对const修饰的变量不做取值优化
const修饰类中的成员函数,变成常函数,修饰的是this指针
const可以与引用配合
static
修饰复合型类型数据的成员变量和成员函数
extern
都可以声明外部变量
C++还可以用于声明编译语法方式
extern “C” 声明用C语言的方式调用函数,不换名
5、强制类型转换的方式不同
(新类型)原数据 一样 结果不报错
xxxx_cast<新类型>(原数据)
四种强制类型转换 能写名字、清楚概念
不匹配时,编译不通过、抛异常
6、堆内存管理方式不同
malloc、free 函数
new\delete 语句、关键字、运算符
7、函数不同
可以重载
成员函数、初始化列表
虚函数
默认形参
8、增加引用机制
引用与指针的对比
9、增加名字空间管理
作用:防止重名
匿名名字空间 ::标识符
10、增加面向对象思想
抽象、封装、继承、多态
11、增加模版编程(泛型编程)
函数模板、类模板、STL、智能指针
模版的特化
12、增加异常处理机制
作用、标准异常、抛、处理异常、自定义异常
13、标准库头文件命名方式
C标准库头文件 #include
14、输入输出方式
printf\scanf 函数 要提供类型占位符
cout\cin 类对象 自动识别基础类型、重载的类型
fstream、ifstream、ofstream
15、运算符当做函数处理
重载运算符、重载运算符的规则、特殊运算符重载代码(自变运算符、输入输出运算符)
二、const的作用
保护变量不被"显式"修改
C++中不会主动读取被const修饰的变量的内存值,默认只使用初始化的值,做了取值优化,要求const修饰的变量必须初始化
C针对const修饰的变量不做取值优化
const修饰类中的成员函数,变成常函数,修饰的是this指针
const可以与引用配合
注意:C++中指向具有const属性的地址时,该指针/引用也需要具备const属性,否则编译报错
const char* str = “hehe”;
const int& num = 3;
修饰成员函数:
void func(int num)const{}
何时使用常函数:具有const属性的对象调用该成员函数时,类中需要有该函数的常函数版本
常函数中只能调用常函数
注意:const会影响函数重载、覆盖
三、static的作用
改变存储位置、延长生命周期、限制作用范围
修饰成员变量:
只会在data\bss段存在一份静态成员变量,被所有类对象共享,而且需要在类内声明、类外全局处定义
可以不需要实例化对象也能通过 类名::静态成员变量名 方式可以访问到静态成员变量,但依然会受访问控制属性限制
修饰成员函数:
静态成员函数中不再有隐藏的this指针,因此不能在静态成员函数中直接访问普通成员变量、成员函数
但是可以直接访问静态成员变量、静态成员函数,因为都属于类内,无需通过域限定符声明
可以不需要实例化对象也能通过 类名::静态成员函数() 方式可以访问到静态成员函数,但依然会受访问控制属性限制
单例模式:
单例原因、懒汉单例、饿汉单例概念、优缺点
实现单例模式的基本原理:
1、把构造、拷贝构造私有化
2、确保类对象只有一份,在类中定义一个静态成员变量(饿汉)或者静态成员指针(懒汉)
3、提供一个获取私有的静态成员变量、指针的公开接口,定义一个静态成员函数来获取(懒汉需要根据情况决定是否实例化、饿汉直接返回静态成员变量)
会写饿汉模式、懒汉模式、线程安全懒汉模式
四、 malloc/free 和 new/delete 的对比
身份: 函数 关键字/运算符
返回值: void* 对应类型的指针
参数: 字节个数(手动计算) 类型(自动计算)
连续内存:手动计算总字节数 new[个数]
扩容: realloc 无法直接处理
失败: 返回NULL 抛异常
构造\析构: 不调用 调用
初始化: 不能初始化 可以初始化
头文件: stdlib.h 不需要
函数重载: 不允许重载 允许
内存分配的位置:
堆内存 自由存储区
相同点:
1、都是用来管理堆内存
2、都不可以重复释放相同内存
五、构造、析构、拷贝构造、赋值操作
作用、能写
string类能写
深拷贝和浅拷贝问题
当类中有成员是指针类型并且分配了堆内存,浅拷贝(默认拷贝构造、赋值操作)只会拷贝指针变量的值,造成两个以上的对象的成员指针指向同一块堆内存,会在析构时重复释放;深拷贝会根据被拷贝对象的内存大小,重新申请内存,拷贝原内存中的数据到新内存中,各自析构自己的内存
当类中有成员是指针类型并且分配了堆内存,需要显式地实现深拷贝的拷贝构造、赋值操作
赋值操作中的问题:
1、自己给自己赋值
2、赋值前原来的内存需要先释放,再申请内存赋值
六、重载、隐藏、覆盖、重写?
重载:在同一作用域下,函数名相同,参数列表不同则构成重载
原理:C++编译器会对函数的名字进行换名操作,会把参数的类型信息添加到新函数名末尾,形成不同的函数名,从而构成函数重载
extern “C”
{
//声明用C语言的方式调用函数,不换名,从而让C++可以继续调用C编译器已经编译好的目标文件、库文件
}
隐藏:内层作用域会隐藏外层作用域中的同名标识符
C++可以通过 ::全局标识符名 指定在内层作用域下访问隐藏的外层标识符
成员隐藏:
子类中会隐藏父类的同名成员,在子类中默认情况下使用的是子类的同名成员,通过 父类名::成员名 指定访问被隐藏的同名成员
同名成员函数:
1、如果父类中同名函数不加virtual,无论参数列表是否相同,构成隐藏关系
2、如果父类中同名函数被virtual修饰,但是参数列表个数不相同,构成隐藏关系
覆盖(重写)
在父子类中,父类中的成员函数被virtual修饰,子类中有同名且参数列表完全相同、返回值也要相同(或者构成继承且能够隐式转换关系),此时构成覆盖关系
虚函数:类的成员函数被virtual修饰
当父类中有虚函数,父类中就会有一个隐藏的虚表指针,该指针指向一张该类的虚函数表,该表中存储该类中所有虚函数的地址
当子类继承了该父类时,会把虚表指针一起继承过来,会指向内容相同的一张虚函数表,如果子类中的成员函数与父类的同名虚函数的参数列表、返回值、常属性、异常说明相同,该子类的同名成员函数就会替换虚函数表中父类虚函数的地址,形成覆盖关系
当使用父类指针或引用指向子类对象,通过父类指针或引用访问到是被覆盖后子类的成员函数,这种现象称为多态
能写一个简单的工厂模式
七、指针与引用的对比
不同点:
指针 引用
数据类型 取别名机制
定义指针变量 取别名
占用4/8字节 不占用空间
可以不初始化 必须初始化
有野指针 没有野引用(可以有悬空引用)
有空指针 没有空引用
可以配合堆内存 无法配合堆内存
可以定义指针数组 不能定义引用数组
可以定义二级指针 没有二级引用
相同点:
1、都可以跨函数共享变量
2、都可以提高函数传参效率,也都需要与const配合
3、都可以定义数组指针、数组引用
4、如果指向的对象具有const属性,都需要与const配合
八、对象的创建和销毁过程
1、对象的创建过程
a、给对象划分内存空间(栈、堆)
b、执行初始化列表
1、根据继承表的顺序调用父类的无参构造或者有参构造
通过 : 父类名(val) 调用父类的有参构造
2、根据成员变量的定义顺序调用类类型成员的无参构造或者有参构造
通过 : 类类型成员名(val) 调用类类型成员的有参构造
3、对其它成员初始化
c、执行自己的构造函数、可能去申请资源
2、对象的销毁过程(创建的逆序)
a、执行自己的析构函数、可能去释放资源
b、根据类类型成员顺序的逆序,调用它们的析构函数
c、根据继承表的逆序,调用父类的析构函数
d、释放对象的内存
九、什么是多态?
同一个指令有多种形态
编译时多态:
函数重载、运算符重载、函数模板、类模板
运行时多态:
基于 类继承(public)+虚函数+函数覆盖,通过父类指针、引用指向子类对象,并且调用被覆盖的虚函数时,形成多态
在父子类中,父类中的成员函数被virtual修饰,子类中有同名且参数列表完全相同、返回值也要相同(或者构成继承且能够隐式转换关系),此时构成覆盖关系
虚函数:类的成员函数被virtual修饰
当父类中有虚函数,父类中就会有一个隐藏的虚表指针,该指针指向一张该类的虚函数表,该表中存储该类中所有虚函数的地址
当子类继承了该父类时,会把虚表指针一起继承过来,会指向内容相同的一张虚函数表,如果子类中的成员函数与父类的同名虚函数的参数列表、返回值、常属性、异常说明相同,该子类的同名成员函数就会替换虚函数表中父类虚函数的地址,形成覆盖关系
当使用父类指针或引用指向子类对象,通过父类指针或引用访问到是被覆盖后子类的成员函数,这种现象称为多态
十、类多态的底层实现机制
同上
十一、智能指针
C++中的智能指针其实就是重载了 * -> []等运算符的类模板
智能指针优点:
本质上是类对象,当类对象离开作用,会自动调用析构函数,并且在析构函数中调用了delete语句释放申请的内存,从而达到自动释放的效果
auto_ptr
unique_ptr
shared_ptr ptr; // 循环引用
weak_ptr
12、运算符重载的限制?
输入、输出运算符重载会写 友元
单目运算符、双目运算符 成员函数、全局函数格式能写
自变运算符会写 哑元
1、运算符的重载中成员函数、全局函数只能二选一实现
2、只能重载为成员函数的运算符 () [] = ->
3、只能重载为全局函数的运算符
<< >> 友元
4、可以重载的特殊运算符 () [] = -> new delete new[] delete[]
5、重载不能改变运算符的优先级、操作本意
13、 STL
六大组件、常用算法(函数模板)、每个容器原理、特征、底层使用
13、vector的扩展机制
vector底层是一个动态顺序表,当vector存储满且再添加数据时,会自动存储空间翻倍(编译器决定),底层通过realloc扩容,如果原位置无法扩容,会重新申请内存,拷贝原内存内容,并释放原内存
可以自定义容量,以此降低扩容的频次,提高效率,但是浪费一定的空间
14、vector与list的区别
顺序表、链式表的区别
vector支持[]运算符、迭代器
list只支持迭代器遍历、访问
15、为什么那么多容器底层采用红黑树存储?
map\set\multimap\multiset
1、二分查找
2、有序二叉树、不够均匀、接近单支状,效率接近顺序查找
3、AVL树,创建、插入、删除效率不高
4、红黑树,伪平衡,创建、插入、删除效率比AVL高,查找效率也很接近AVL树
16、遍历容器的方法(不包括适配器 stack、queue、priority_queue)
迭代器(正向、逆向、常正向、常逆向迭代器)
范围[start,end)
下标+[]遍历
[key] = value 访问
17、支持[]的容器
vector、map、deque、bitset