C++基础
-
内联函数
- 关键字inline
- 功能简单、规模较小且使用频繁的函数
- 在编译的时候将函数嵌入在调用它的地方,可以减少参数传递和控制转移等的开销
- 定义位于类声明中的函数会自动被认为内联函数
-
函数重载(多态)
- 在同一个作用域中,两个及以上函数的函数名相同,但是参数类型或者参数数量不同,编译器将根据参数类型和参数个数自动匹配并调用,这就是函数重载。
- 注意:编译器将类型引用和类型本身视为相同的参数。
- 类的成员函数可以重载,不同类的同名函数不是重载
-
extern “C”
- 实现C++调用C语言的代码,提示编译器这部分代码按照C语言进行编译。
- 编译不同之处:C++支持函数重载,函数编译过程中会将函数名以及参数类型加入到编译后的代码中,C语言不支持函数重载,因此编译过程中只将参数名加入到编译后的代码中。
-
函数模板和类模板
- C++多态性的体现,实现高效的代码重用
- 关键字template
- 函数模板
- 函数模板可以重载
- 遇上同名普通函数时,编译器会优先选择普通函数,除非函数模板能产生更好的参数类型匹配,可以通过空模板实参列表的语法限定编译器只能通过模板匹配
- 类模板
- 子类从模板类继承时,需要制定基类的参数类型,如class B: public A
-
类的成员函数
- 可以重载
- 可以使用默认参数
- 一般在头文件中声明,在源文件中定义
-
封装
- 类的定义过程,将抽象得到的数据成员和函数成员形成一个统一的整体
-
类的访问修饰符
- public:类本身、子类、对象
- protect:类本身、子类
- private:类本身
-
构造函数
- 完成对象的初始化工作
- 如果定义了一个带参数的构造函数且不是所有的参数都有默认值,那么必须定义一个不带参数的构造函数,否则调用不带参数的构造函数时会报错
- 子类中构造函数的调用顺序:基类构造函数(按继承顺序)、派生类对象成员的构造函数(按声明顺序)、派生类构造函数
-
复制构造函数
-
什么时候调用
- 用已经定义的对象去初始化其它对象的时候
- 函数的形参是对象类型,完成形参和实参结合的时候
- 函数返回值是对象,函数执行完返回结果的时候
-
什么时候需要自定义复制构造函数(完成深复制)
- 类数据成员有指针的时候
- 类数据成员管理资源的时候,如打开文件
- 需要析构函数释放资源的时候
-
禁止调用复制构造函数
class Uncopyable { private: Uncopyable(const Uncopyable &); //阻止拷贝构造 Uncopyable &operator=(const Uncopyable &); //阻止赋值运算符 };
-
-
析构函数
- 完成对象删除前的清理工作,对象删除前系统自动调用
- 不可以重载
- 可能会被继承的类的虚函数应该声明为虚函数,这样可以使用基类的指针释放子类的空间,否则会发生内存泄漏
- 析构函数调用顺序与构造函数相反:派生类析构函数体、派生类成员对象析构函数、基类析构函数
-
static数据成员
-
实现同类不同对象之间的数据共享
-
访问 类名::静态对象名
-
需要在类外初始化,初始化时不加static
int Martain::martain_count = 2;
-
存储在静态存储区,对象的成员变量在堆或栈中生成
-
优点:避免二义性,节省存储空间
-
-
static函数成员
- 主要用于访问静态数据成员,需要通过对象名才能访问非静态数据成员,维护不同对象之间共享的数据
- 可以通过类名和对象名调用,但是其意义都是相同的
- 没有this指针
-
动态内存分配与释放
- new/delete:C++提供的关键字
- malloc/free:C提供的函数,需要指定申请空间的大小
-
深复制和浅复制
- 默认复制构造函数都是浅复制
- 浅复制在遇到含有指针的对象类型的时候容易出现问题,复制后的对象和原来的对象指向同一块内存空间
- 一个对象对内存存储数据的修改会影响到另一个对象
- 可能会重复释放同一块内存,造成运行错误
-
const关键字
- 数据保护机制
- 常数据成员只能使用初始化列表初始化
- 常函数成员可以使用类中所有的数据成员,但是不可以修改它们,在声明和定义的时候都需要在结尾加上const关键字
- 指向常量的指针(指针本身可变,*与变量名被分隔开),常指针(指针不能再指向其它的值了,*与变量名在一起)
- 常对象,只能访问类的const成员
-
运算符重载
- operator op(参数列表)
- 可以重载为成员函数或者非成员函数,重载为成员函数参数个数是实际参数个数-1(对象本身就是一个参数),非成员函数(要声明为类的友元函数)是实际参数个数
- 只能重载为成员函数的:=/[]/()/->
- 不能重载的:类型转换操作符(const_cast synamic_cast reinterpret_cast static_cast)/typeid//.*/?:/sizeof
- 重载后的参数至少有一个是用户自定义的类型,不能创建新的运算符、不能修改原有运算符的规则
-
继承
- 公有继承、私有继承、保护继承
- 公有继承:继承之后,父类成员访问属性在子类中不变,子类成员及子类对象可以访问父类的公有成员和保护成员,但是无法访问父类的私有成员
- 私有继承:继承之后,父类的所有成员都变为子类的私有成员,子类成员可以访问父类的公有成员和保护成员,不可以访问父类的私有成员
- 子类对象不可以访问父类的任何成员,子类再派生新的子类的时候,父类成员无法再被继承(已经全部变为子类的私有成员了),相当于阻断了父类的继承机制
- 保护继承:继承之后,父类成员访问属性在子类中不变,子类成员可以访问父类的公有成员和保护成员,但是无法访问父类的私有成员,子类对象无法访问父类的成员,但是子类可以继续派生新的子类
- 赋值兼容原则:派生类对象可以隐含转换为基类对象、派生类的对象可以初始化基类的引用、派生类指针可以隐含转换为基类指针
-
多态
- 特殊多态:重载多态、强制多态;一般多态:包含多态、参数多态
- 虚函数:基类中用virtual关键字声明的函数,可以用基类的指针调用派生类的函数,动态绑定的基础,基于虚函数表和虚表指针实现
- 抽象类:有虚函数的类
- 纯虚函数:只有函数声明,没有函数定义,格式是虚函数声明之后加上 =0,有纯虚函数的类是接口类,更高层次的抽象,无法实例化,必须定义析构函数
- 多态基类必须定义虚析构函数,否则会内存泄漏
-
STL
- 由一些集合类及在这些类上操作的算法构成,包括容器、算法、迭代器、函数对象
- 容器
- 顺序容器:向量vector、双端队列dqueue、列表list
- vector:动态数组,自动分配内存
- list:双向链表
- 关联容器:集合set、多重集合multiset、映射map、多重映射multimap
- 顺序容器:向量vector、双端队列dqueue、列表list
- 迭代器iterator:面向对象版本的指针,遍历容器
- map:内部实现一般为平衡二叉树
-
设计模式
- 面向对象设计原则1:针对接口编程,而非针对实现编程
- 面向对象设计原则2:优先使用对象组合,而不是类继承
- 观察者模式:定义对象间的一对多依赖关系,当一方的对象改变状态时,所有依赖者都会被通知更新,也叫发布-订阅模式,被依赖的一方叫目标或主题(Subject),依赖方叫观察者(Observers)
-
异常捕获
- 只用来处理确实可能不正常的情况
- 构造器和析构器不应该使用异常
- 如果try语句块无法找到与之匹配的catch语句块,则它抛出的异常将中止程序的执行
- 使用对象作为异常,用值传递的方式抛出对象,引用传递的方式捕获对象
- 函数的异常声明列表void func() throw (int, double, A, B, C){…}
- 交代可能抛出的异常类型
- 可以在声明时写,也可以在定义时写,若都写要保持一致
- void func() throw ()表示不抛出任何类型的异常
- 若不交代能抛出什么类型的异常,那么可以抛出任何类型的异常
- 若抛出没有交待的异常,编译时不会出错,但在运行时, Dev C++ 编译出来的程序会出错;用 Visual Studio 2010 编译出来的程序则不会出错,异常声明列表不起实际作用
- 标准异常类,从exception派生而来
-
其它
- 系统不会初始化局部变量
- 一个函数体中可以存在同名变量,前提是作用域不同
- 存储在静态存储区的变量会在程序刚开始运行时会被初始化,这是唯一一次被初始化
- 存储在静态存储区的变量:全局变量、静态变量
- 全局变量和局部变量同名时,通过域名在函数中引用全局变量,否则引用的全是局部变量
-
变量在内存中的位置(自上而下)
-
代码段:存放程序的执行代码,通常只读,程序运行前大小就已经确定了,const修饰的全局变量存储在这里
-
数据段:生命周期是整个程序的运行周期
已被初始化的全局变量或者静态变量
-
bbs段:未被初始化的全局变量和静态变量,被初始化为0的全局变量和静态变量
-
堆区:存放动态分配的内存,大小不固定,动态申请后向上添加到栈区,释放后从堆中删除,程序员负责分配释放,如果不释放,程序结束后OS释放;分配小内存时使用,brk系统调用
-
映射区:存储动态链接库等文件映射、申请大内存(malloc调用mmap函数)
-
栈区:存储临时创建的局部变量、局部变量、函数参数、函数返回值等,编译器自动分配释放,向下有限扩展,const修饰的局部变量
-
-
RTTI,运行时类型检查
- C++多态性的体现,使用基类的指针或引用查询它指向的子类对象的实际派生类型
- 主要体现在dynamic_cast和typeid两方面
- dynamic_cast用于将基类的指针或者引用安全地转换为派生类的指针或引用
- typeid用于返回指针或者引用实际指向的派生类类型
- 虚函数表-1位置存放了指向type_info的指针
-
静态函数和虚函数
- 静态函数:编译时确定运行时机
- 虚函数:运行时动态绑定;使用虚函数表,调用时增加一次内存开销
-
C++默认函数栈
- 大小与OS和平台有关
- CentOS7 64位:8M;mac OS:8M;Windows:2M;Linux:1M
-
隐式类型转换
- 内置类型:低精度向高精度转换
- “可以用 单个形参来调用 的构造函数定义了从 形参类型 到 该类类型 的一个隐式转换。”
- 构造函数形参类型 到 类类型 的隐式转换
- 不是只能有一个形参,可以有其它形参,但其它形参要有默认实参
- 生成临时对象,可能存在风险,用explicit声明抑制这种隐式类型转换,强迫用户只能显式创建对象
-
vector和list
- 联系:都在堆上分配内存
- 区别:
- vector底层是数组,list底层是双向链表
- vector分配连续内存,list内存不连续
- vector存储空间不够时成倍增加(也不一定),list每次插入和删除都需要申请和释放内存
- vector对中间元素进行插入或删除操作时,需要进行内存拷贝,list不需要
- vector随机访问性能好,插入删除性能差,list随机访问性能差,插入删除性能好
- vector顺序内存,list不是顺序内存
- 应用
- vector:适用于需要进行高效随机访问,对插入和删除性能要求不高的情况
- list:适用于需要高效插入和删除,但是对随机访问性能要求不高的情况
-
resize和reverse
- resize:对已有元素的相关判断,resize(len),如果len大于原来的size,则补充len-resize个元素,否则不变;如果len大于数组容量(capacity),则capacity也会扩容到len
- reverse:对vector容量的相关判断,reverse(len),如果len大于原来的reverse,则容量扩充到len,否则不变;
- resize操作可能会影响reverse,reverse操作不会影响resize
-
C++类内的引用数据成员和const常量都只能通过构造函数的初始化列表进行初始化。因为一旦进入构造函数函数体内部,就不再是初始化操作而是赋值操作,引用数据成员和const常量都不能进行赋值操作。
-
struct和class
- struct是值类型,class是引用类型;复制时,值类型的数据是值复制,引用类型的数据是引用复制
- struct分配在栈区,class分配在堆区;栈的空间较小,但在栈中存储的数据访问效率相对较高;堆的空间较大,但在堆中存储的数据访问效率较低
- 结构体使用完后自动解除内存分配,类实例有垃圾回收机制保证内存的回收处理
- 结构体的默认访问和继承属性是public,类的默认访问和继承属性是private
- 轻量级的对象(点、矩形、颜色)用结构体,大量逻辑对象用类
-
类成员访问权限:public、private、protected
- public:类内成员函数、类的对象、子类的对象
- private:类内成员函数
- protected:类内成员函数、子类成员函数、友元函数
-
右值引用、移动语义和完美转发
https://www.jianshu.com/p/d19fc8447eaa
- 左值&右值
- 左值:可取址,具名的持久化对象
- 右值:不可取址,不具名的临时对象(常量、临时对象)
- 左值引用&右值引用
- 左值引用:T&,只能绑定左值
- 常量左值引用:const T&,可以绑定非常量左值、常量左值和右值;只能读不能写
- 右值引用:T&&,只能绑定右值,可以延长右值的生命期;右值引用具名之后就变成了一个左值
- 通用引用:类型由初始化时绑定的值的类型确定;必须初始化;只有发生自动类型推断时才是通用引用
- 移动语义
- 避免多余复制,减少无谓的内存拷贝;有些编译器进行了优化
- 参数是右值引用
- 通过移动构造函数和移动赋值构造函数实现
- std::move()可以将左值转换成右值使用;适用于生命周期很短的局部变量;用于转移资源的控制权,对含有文件句柄、内存等资源的对象更有意义;资源转移后不会立即析构,左值离开作用域之后才会析构
- 完美转发
- 通过一个函数将参数转交给另一个函数处理,若能继续保持原有特征,那么是完美的
- std::forward()+通用引用实现完美转发
- emplace_back()替换push_back()减少内存拷贝,提升性能
- 左值&右值
-
段错误:发生非法内存访问时
- 野指针
- 修改字符串常量
-
内存泄漏:程序未能释放掉不再使用的内存,造成内存浪费
- 堆内存泄漏:使用new/malloc动态分配的内存没有使用delete/free释放
- 系统资源泄漏:没有释放系统分配的资源,如Bitmap、handle、SOCKET,会导致系统效能降低,系统运行不稳定
- 没有将基类的虚构函数定义为虚函数,基类指针指向子类对象时,子类的析构函数不会被调用,子类的资源没有被正确释放,造成内存泄漏
-
new和malloc
- new是C++的关键字,可以重载,malloc是C的库函数
- new按数据类型申请内存,malloc按大小分配内存
- new分配内存时调用构造函数,delete释放内存时调用析构函数;malloc不调用构造函数和析构函数
- new返回指向对象的指针,malloc返回void*指针,需要再进行类型转换
- new申请失败抛出bad_malloc异常,malloc申请失败返回NULL
- malloc可以用realloc扩充内存空间;new没有这种机制
-
STL的内存优化
- 使用allocate向内存池请求size大小的内存空间,如果需要请求的内存大小大于128bytes,直接使用malloc。、
- 如果需要的内存大小小于128bytes,allocate根据size找到最适合的自由链表。
- 如果链表不为空,返回第一个node,链表头改为第二个node。
- 如果链表为空,使用blockAlloc请求分配node。
- 如果内存池中有大于一个node的空间,分配尽可能多的node(但是最多20个),将一个node返回,其他的node添加到链表中
- 如果内存池只有一个node的空间,直接返回给用户。
- 若果如果连一个node都没有,再次向操作系统请求分配内存。
- 分配成功,再次进行b过程。
- 分配失败,循环各个自由链表,寻找空间。
- 找到空间,再次进行过程b。
- 找不到空间,抛出异常。
- 用户调用deallocate释放内存空间,如果要求释放的内存空间大于128bytes,直接调用free。
- 否则按照其大小找到合适的自由链表,并将其插入。
-
C++ 11的新特性
- auto关键字:编译器根据初始值自动推导出类型,不能用于函数传参和数组类型的推导
- nullptr关键字:可以被转换为任意其它的指针类型,NULL会被宏定义为0,在重载时可能会出现问题
- 智能指针:解决内存管理问题
- 初始化列表:初始化列表对类进行初始化
- 右值引用:基于右值引用实现移动语义和完美转发,消除两个对象间不必要的对象拷贝,节省运算存储资源
- atomic原子操作用于多线程资源互斥操作
- 新增STL容器array以及tuple