1.C和C++的不同点
-
基本数据类型不同
-
bool
-
字符串(string)
-
void*(不能自动转成其他类型)
-
-
复合型数据类型不同
-
struct union使用时关键字可以省略
-
有成员函数,有四个隐藏的成员函数(构造 析构 拷贝 赋值)
-
如果没有成员变量,结构\联合所占用大小:C 占0字节 C++占1字节,成员函数中隐藏this指针,因此需要再内存中有一席之地
-
可以管理成员的访问控制属性(public(默认)\private\protected)
-
-
枚举类型不同
-
使用时关键字可以省略
-
枚举变量的值检查更加严格,如果不对会报错
-
枚举变量不能使用数值赋值,只能通过枚举常量赋值
-
-
类型限定符不同
-
auto C++11 可以自动识别数据类型
-
const 都可以保护变量不被"显示"修改
-
C++中不会主动读取被const修饰的变量的内存值,默认只使用初始化的值,做了取值优化,要求const修饰的变量必须初始化
-
C针对const修饰变量不做取值优化
-
const修饰类对象中的成员函数,变成常函数,本质上修饰的是this指针
-
const可以与引用配合使用
-
-
static
-
可以修饰复合类型的成员变量和成员函数
-
-
extern
-
都可以声明外部变量
-
C++还可以用于声明编译语法方式
-
extern "C"
-
-
-
强制类型转换的方式不同
-
(新类型) 原数据:这种方式还是可以使用
-
xxx_cast<新类型>(原数据):四种强制类型转换,能写名字,清楚概念
-
static_cast
:用于执行非多态类型之间的转换,常用于基本数据类型或类层次结构中的上行或下行转换。 -
dynamic_cast
:主要用于多态类之间的安全转换。只能用于指针或引用,并且在运行时检查转换的安全性 -
const_cast
:用于添加或去除常量性,可以将const类型转换为非const类型,或者相反。 -
reinterpret_cast
:用于进行低级别的类型转换,几乎可以将任何指针类型转换为任何其他指针类型。这类转换通常是不安全的,应谨慎使用。 -
不匹配时,编译不通过,抛异常
-
-
-
堆内存管理方式不同
-
C: malloc\free(函数)
-
C++: new\delete(语句,关键字,运算符)
-
-
函数不同
-
可以重载
-
可以有成员函数,初始化列表
-
虚函数
-
默认形参
-
-
增加引用机制
-
引用和指针的对比
-
-
增加名字空间管理(namespace)
-
作用: 防止重名
-
匿名名字空间( ::标识符名)
-
-
增加面相对象思想
-
抽象: 先找出(想象)能解决问题的"对象",分析该对象解决问题所需要的属性(成员变量)和行为(成员函数)
-
封装: 把抽象的结构封装成一个类类型(结构),并给类的成员变量、成员函数设置相对应的访问控制权限(public\private\protected)
-
继承:
1、在封装类前先考虑现有的类是否能解决部分问题,如果有则可以通过继承,只需要在此基础上扩展即可,从而缩短解决问题的时间
2、把一个复杂的大问题拆分成若干个不同的小问题,给每个小问题设计一个类去解决,最后把这些类通过继承合并成一个能解决大问题的类,从而降低问题的难度
-
多态: 发出一个指令,系统会根据实际情况执行不同的操作,这种特征称为多态.(一个指令多种形态) 比如重载过的函数、当调用函数时会根据参数的不同调用不同的版本,具体调用哪个版本在编译期间可以确定,称为编译时多态
-
注意:面向对象的细节的本质上还是面向过程,因此面向对象不是解决问题的捷径,而是以更高的维度去思考问题
-
-
增加模板编程(泛型编程)
-
函数模板
-
类模板
-
STL
-
智能指针
-
模板的特化
-
-
增加异常处理机制
-
作用
-
标准异常
-
抛,处理,自定义异常
-
-
标准库头文件命名方式
-
C标准库头文件#include<cstdio>
-
-
输入输出方式
-
C: printf\scanf (函数 要提供类型占位符)
-
C++: cout\cin (类对象 自动识别基础类型,重载的类型)
-
C++文件输入输出:fstream\ifstream\ofstream
-
-
运算符重载当做函数处理
-
重载运算符
-
重载运算符的规则
-
特殊运算符的重载的代码(自变运算符,输入输入运算符)
-
2.const的作用
-
相同点:保护变量不被显示修改
-
C++中不会主动读取被const修饰的变量的内存值,默认只使用初始化的值,做了取值优化,要求const修饰的变量必须初始化
-
C针对const修饰变量不做取值优化
-
const修饰类对象中的成员函数,变成常函数,修饰的是this指针
-
void func(int num)const {}
-
何时使用常函数: 具有const属性的对象调用该成员函数时,类中需要有该函数的常函数版本
-
常函数中只能调用常函数
-
注意:const会影响函数重载和覆盖
-
-
const可以与引用配合使用
注意:C++中指向具有const属性的地址时,该指针\引用也需要具备const属性,否则编译报错
3.static的作用
-
相同点: 改变存储位置,延长生命周期,限制作用范围
-
C++中修饰成员变量:
-
只有一份,只会在data\Bss段存有一份静态成员变量,被类对象共享,
-
而且需要在类内声明,类外全局处定义
-
可以不需要实例化对象也能通过 (类名::静态成员变量) 的方式可以访问到静态成员变量,但是依然会受访问控制属性限制
-
-
C++中修饰成员函数:
-
静态成员函数中不在有隐藏的this指针,因此不能在静态成员函数中直接访问普通的成员变量和成员函数
-
但是可以直接访问静态成员变量或成员函数(因为类内可以直接访问,无需域限定符声明)
-
-
单例模式:只能实例化一个类对象
-
需要单例的原因:
-
进程管理器、日志管理器
-
网站访问计数器、应用配置程序
-
线程池、服务器的连接管理器
-
-
实现单例模式的基本原理:
-
把构造函数,拷贝构造私有化
-
确保类对象只有一份,在类中定义一个静态成员变量(恶汉)或者静态成员指针(懒汉)
-
提供获取静态成员变量\指针的公开接口,定义一个public属性静态成员函数来获取(懒汉需要根据情况来决定是否实例化, 饿汉直接返回静态成员变量)
-
-
会写饿汉模式,懒汉模式,线程安全懒汉模式
-
饿汉单例:
-
优点: 不可能被多个线程同时运行创建多份(线程安全)
-
缺点: 如果后期使用不到,就浪费时间、资源
-
#include <iostream> using namespace std; class HungrySingle { // 声明静态对象 static HungrySingle obj; // 把构造和拷贝构造设置为private,可以防止创建类对象 HungrySingle(void) { cout << "创建了类对象" << endl; } HungrySingle(const HungrySingle& that) {} public: // 只能调用该接口获取静态类对象 static HungrySingle& getSingleObject(void) { return obj; } void show(void) { cout << this << endl; } }; // 定义静态对象 HungrySingle HungrySingle::obj; int main(int argc,const char* argv[]) { cout << "main run ..." << endl; HungrySingle& h1 = HungrySingle::getSingleObject(); HungrySingle& h2 = HungrySingle::getSingleObject(); HungrySingle& h3 = HungrySingle::getSingleObject(); h1.show(); h2.show(); h3.show(); return 0; }
-
-
懒汉单例:
-
优点: 使用时才会创建,节约时间、资源
-
缺点: 可能会被多个线程同时实例化,有可能会创建出多个单例类对象(线程不安全)
-
#include <iostream> using namespace std; class LazySingle { static LazySingle* obj; LazySingle(void) { cout << "创建单例对象" << endl; } LazySingle(const LazySingle& that) { } public: static LazySingle* getSingleObject(void) { if(NULL == obj) { obj = new LazySingle; } return obj; } void show(void) { cout << this << endl; } }; LazySingle* LazySingle::obj; int main(int argc,const char* argv[]) { cout << "main run ..." << endl; LazySingle* l1 = LazySingle::getSingleObject(); LazySingle* l2 = LazySingle::getSingleObject(); LazySingle* l3 = LazySingle::getSingleObject(); l1->show(); l2->show(); l3->show(); return 0; }
-
-
线程安全懒汉模式
-
#include <iostream> using namespace std; class LazySingle { static LazySingle* obj; // 互斥锁 static mutex mtx; LazySingle(void) { cout << "创建单例对象" << endl; } LazySingle(const LazySingle& that) { } public: static LazySingle* getSingleObject(void) { if(NULL == obj) { std::lock_guard<std::mutex> lock(mtx); // 锁定互斥量 if(NULL==obj) { obj = new LazySingle; } } return obj; } void show(void) { cout << this << endl; } }; LazySingle* LazySingle::obj; int main(int argc,const char* argv[]) { cout << "main run ..." << endl; LazySingle* l1 = LazySingle::getSingleObject(); LazySingle* l2 = LazySingle::getSingleObject(); LazySingle* l3 = LazySingle::getSingleObject(); l1->show(); l2->show(); l3->show(); return 0; }
-
4.new\delete和mallo\free的对比
malloc/free new/delete
身份: 函数 关键字/运算符
返回值: void* 对应类型的指针
参数: 字节个数(手动计算) 类型(自动计算)
连续内存:手动计算总字节数 new[个数]
扩容: realloc 无法直接处理
失败: 返回NULL 抛异常
构造\析构: 不调用 调用
初始化: 不能初始化 可以初始化
头文件: stdlib.h 不需要
函数重载: 不允许重载 允许
内存分配的位置:
堆内存 自由存储区(可以是栈或堆)
相同点:
-
都是用来管理堆内存
-
都不可以重复释放相同内存
在C++中如何把已有一块内存(从操作系统获取到的内存,例如:ipc共享内存)分配给结构、联合、类对象,能自动调用构造、析构函数。
// new(内存地址) 类型; new运算符可以重新解释一块内存,并自动调用构造函数
int main(int argc,const char* argv[])
{
void* ptr = malloc(sizeof(Student));
cout << ptr << endl;
Student* stup = new(ptr) Student;
cout << stup << endl;
delete stup;
return 0;
}
5.构造\析构\拷贝构造\赋值操作
-
作用 能写
-
string类能写
-
深拷贝和浅拷贝问题
-
浅拷贝: 当类中有成员是指针类型并分配了堆内存,浅拷贝只会拷贝指针变量的值,造成多个对象的成员指针指向同一块堆内存,会在析构时重复释放
-
深拷贝:会根据被拷贝对象的内存大小,重新申请内存,拷贝原内存中的数据到新内存中,各自析构自己的内存
-
当类中有成员是指针类型并且分配了堆内存,需要显示地实现深拷贝的拷贝构造和赋值操作
-
-
赋值操作问题:
-
自己给自己赋值
-
赋值前原来的内存需要先释放,再申请内存赋值
-
-
string类:
-
class MyString { char* cStr; public: //构造函数 MyString(const char* str="") { cStr = new char[strlen(str)+1]; strcpy(cStr,str); } //析构函数 ~MyString(void) { delete[] cStr; } //拷贝构造(深拷贝) MyString(const MyString& that) { cStr = new char[strlen(that.cStr)+1]; strcpy(cStr,that.cStr); } //赋值操作 const MyString& operator=(const MyString& that) { if(this != &that) { delete[] cStr; cStr = new char[strlen(that.cStr)+1]; strcpy(cStr,that.cStr); } return *this; } };
6.重载\隐藏\覆盖\重写
-
重载: 在同一作用域下,函数名相同,参数列表不同则构成重载
-
原理:C++会对函数名进行换名操作,会把参数的类型信息添加到新函数名末尾,形成不同的函数名,从而构成函数重载
extern "C" { //声明C语言的方式调用函数,不换名,从而让C++可以继续使用C编译器已经编译好的目标文件,库文件 }
-
-
隐藏:内层作用域会隐藏外层作用域的同名标识符
-
C++可以通过 (::全局标识符) 指定在内层作用域下访问隐藏的外层标识符
-
成员隐藏:
-
子类中会隐藏父类的同名成员,在子类中默认情况下使用的是子类的同名成员
-
可以通过(父类名::同名成员) 指定访问隐藏的父类同名成员
-
同名成员函数:
1.如果父类同名函数不加virtual,无论参数列表是否相同都隐藏
2.如果父类中同名函数被virtual修饰,但是参数列表个数不同,也构成隐藏关系
-
-
-
覆盖(重写):
-
在父子类中,父类中的成员函数被virtual修饰,子类中有同名且参数列表完全相同,返回值也要相同(或者构成继承且能够隐式转换关系),此时构成覆盖关系
-
虚函数:类中成员函数被virtual修饰
-
当父类中有虚函数,父类中就会有一个隐藏的虚函数表指针,该指针指向一张该类的虚函数表,该表中存储该类中所有虚函数的地址
-
当子类继承了该父类时,会把虚表指针一起继承过来,会指向内容相同的一张虚函数表,如果子类中的成员函数与父类中的虚函数同名构且参数列表,返回值,常属性,异常说明都相同,该子类的同名成员函数就会替换虚函数表中父类虚函数的地址,从而形成覆盖关系
-
当父类指针或引用指向子类对象,通过父类指针或引用访问到的是被覆盖后的子类的成员函数,这种现象称为多态
-
能写简单的工厂模式
-
#include <iostream> using namespace std; //产品类型 enum ProductType {PRODUCT_A,PRODUCT_B}; //产品接口 class Product { public: virtual void show(void)=0; }; //产品A class ProductA:public Product { public: void show(void) { cout << "产品A" <<endl; } }; //产品B class ProductB:public Product { public: void show(void) { cout << "产品B" <<endl; } }; //工厂 class Factory { public: //创建产品的方法,根据传入的类型,返回对应的产品 Product* creatProduct(ProductType type) { switch(type) { case PRODUCT_A: return new ProductA; case PRODUCT_B: return new ProductB; default: return NULL; } } }; int main(int argc,const char* argv[]) { //创建工厂 Factory* factory=new Factory; //根据传来的值,通过接口创建产品 Product* A=factory->creatProduct(PRODUCT_A); A->show(); Product* B=factory->creatProduct(PRODUCT_B); B->show(); delete factory; delete A; delete B; return 0; }
-
7.指针与引用的对比
不同点:
指针 引用
数据类型 取别名机制
定义指针变量 取别名
占用4/8字节 不占用空间
可以不初始化 必须初始化
有野指针/空指针 没有野引用/空引用(可以有悬空引用)
可以配合堆内存 无法配合堆内存使用
可以定义指针数组 不能定义引用数组
可以定义二级指针 没有二级引用
相同点:
-
都可以夸函数共享变量
-
都可以提高函数传参效率,也都需要与const配合
-
都可以定义数组指针,数组引用
-
如果指向的对象具有const属性,都需要配合const使用
8.对象的创建与销毁过程
1、对象的创建过程(实例化) a、给对象划分内存空间(栈、堆) b、执行初始化列表(按照1 2 3顺序执行) 1、根据继承表的顺序调用父类的无参构造或者有参构造 通过 : 父类名(val) 调用父类的有参构造 2、根据成员变量的定义顺序来调用类类型成员的无参构造或者有参构造 通过 : 类类型 成员名(val) 调用类类型成员的有参构造 3、对其它成员初始化 c、执行自己的构造函数、可能去申请资源
2、对象的销毁过程(创建的逆序) a、执行自己的析构函数、可能去释放资源 b、根据类类型成员顺序的逆序,调用它们的析构函数 c、根据继承表的逆序,调用父类的析构函数 d、释放对象的内存
9.什么是多态
-
同一个指令有多种形态
-
编译时多态:
-
函数重载,运算符重载,函数模板,类模板(vector<xxx>)
-
-
运行时多态:
-
基于 类继承(public)+虚函数+函数覆盖,通过父类指针\引用指向子类对象,并且调用被覆盖的虚函数,形成多态
-
10.类多态的底层实现机制
-
在父子类中,父类中的成员函数被virtual修饰,子类中有同名且参数列表完全相同,返回值也要相同(或者构成继承且能够隐式转换关系),此时构成覆盖关系
-
虚函数:类中成员函数被virtual修饰
-
当父类中有虚函数,父类中就会有一个隐藏的虚函数表指针,该指针指向一张该类的虚函数表,该表中存储该类中所有虚函数的地址
-
当子类继承了该父类时,会把虚表指针一起继承过来,会指向内容相同的一张虚函数表,如果子类中的成员函数与父类中的虚函数同名构且参数列表,返回值,常属性,异常说明都相同,该子类的同名成员函数就会替换虚函数表中父类虚函数的地址,从而形成覆盖关系
-
当父类指针或引用指向子类对象,通过父类指针或引用访问到的是被覆盖后的子类的成员函数,这种现象称为多态
11.智能指针
-
C++中的智能指针其实就是重载了* -> []等运算符的类模板
-
智能指针优点:
-
本质上是类对象,当类对象离开作用域时,会自动调用析构函数,并且在析构函数中,调用了delete语句释放申请的内存,从而达到自动释放的效果
-
auto_ptr // 采用独占拥有模式,不能同时有多个auto_ptr指向同一个内存,但是不能完全实现,有时候会指向同一个内存,有隐患 unique_ptr(独享指针) //是auto_ptr的升级,完全实现独占式拥有模式,保证同一时间中只有一个unique_ptr指向某个内存 //通过把拷贝构造、赋值操作函数声明为delete来实现不能给另一个unique_ptr对象赋值的效果 shared_ptr<int> ptr(共享指针);//循环引用 //采用共享的拥有模式,可以允许多个shared_ptr指向相同内存 //当一个内存被shared_ptr指向时,内部有一个引用计数器+1 //当指向该内存的某个shared_ptr离开作用域或者改变指向或者通过reset()时,引用计数器会-1 //当该内存的引用计数器被减为0时,由最后一个离开的shared_ptr在结束前释放该内存 weak_ptr(弱引用指针) //weak_ptr是为了配合shared_ptr而引入的一种智能指针,它指向一个由shared_ptr管理的对象而不影响该对象的生命周期,也即是说将一个weak_ptr指向一个shared_ptr对象,或者离开时都不会改变该对象的引用计数,只有当最后一个shared_ptr离开该对象才会销毁 //weak_ptr更像是shared_ptr的一个助手,而不是独立的智能指针
-
12.运算符重载的限制?
-
输入 输出运算符重载要会写 友元
-
单目运算符\双目运算符\成员函数\全局函数格式能写
-
自变运算符会写(哑元 int)
-
运算符的重载中成员函数\全局函数只能二选一
-
只能重载为成员函数的运算符: () [] -> =
-
只能重载为全局函数的运算符: << >> 友元(friend)
-
可以重载的特殊运算符: () [] -> = new delete new[] delete[]
-
重载不能改变运算符的优先级和操作本意
13.STL模板库
-
六大组件概念
-
常用的算法(底层是函数模板)
-
每个容器的原理和特征
-
底层实现
14.vector扩容机制
-
vector底层是一个动态顺序表,当vector存储满且再添加数据时,会自动存储空间翻倍(编译器决定),底层通过realloc扩容,如果原位置无法扩容,那么会重新申请内存,拷贝原内存内容,并释放原内存
-
可以自定义容量,以此来降低扩容的频次,提高效率,但是浪费一定的空间
15.vector与list的区别
-
顺序表与链式表的区别
-
vector支持[]运算符,迭代器
-
list只支持迭代器遍历
16.遍历容器的方法(不包括适配器 stack queue priority_queue)
-
迭代器(正向,逆向,常正向,常逆向迭代器)
范围[start,end)
-
下标+[]遍历
-
[key]=value 访问
17.支持[]的容器
-
vector
-
map
-
deque
-
bitset
18.指针与迭代器的区别
1、迭代器实际是为了遍历、操作容器而进行了封装的类模板,重载了指针操作的相关运算符,使用起来像指针一样,更像智能指针 2、迭代器使用后会失效,不能继续直接使用,正常使用指针是可以继续使用 3、指针可以指向函数,迭代器只能指向容器 4、指针是迭代器的一种,只能用于某些特定类型的容器;迭代器是指针的一种泛化类型,可以用于任意类型的容器,所以指针满足迭代器的一切要求
迭代器设计模式:提供一种方法,不需要暴露其容器中的内部数据类型情况下,也能够正常操作、遍历该容器的每个元素,这种设计模式在STL中广泛使用
19.为什么那么多容器底层采用红黑树存储?
set\mulitset\map\mulitmap
-
数据需要频繁查找,也需要进行一些插入、删除操作,普通链式结构、顺序结构性能不高
-
选择二分查找
-
选择有序二叉树、不够均匀、接近单支状、效率低
-
平衡二叉树AVL,查找效率高,创建、插入、删除效率低
-
红黑树,伪平衡、查找速度很接近AVL、创建、插入、删除效率比AVL树高得多,综合性能最优