- 面向对象
- 封装、继承、多态
- 安全性:const常量、引用、类型转换、智能指针
- 可复用:模板、STL
- 新特性
- C++新特性
- C++面向对象
- C++安全性
- C++可复用
- struct 中默认的访问控制权限是 public 的,而 class 中默认的访问控制权限是 private 的
- 在继承关系中,struct 默认是公有继承,而 class 是私有继承
- class 关键字可以用于定义模板参数,就像 typename,而 struct 不能用于定义模板参数
- public继承: 子类可访问父类的public、protect成员;子类对象只能访问父类的public成员
- protect继承: 子类可访问父类的public、protect成员;
- private继承: 子类可访问父类的public、protect成员;
多重继承问题:从不同途径继承来的同一基类,会在子类中存在多份拷贝。
- 浪费存储空间
- 存在二义性问题: 存在对一个基类的多重拷贝
虚继承解决上述两个问题
- 通过虚基类指针和虚基类表实现:虚基类指针指向了一个虚基类表,虚表中记录了虚基类与本类的偏移地址;通过偏移地址,这样就找到了虚基类成员
- 都使用虚指针(占用类的存储空间)和虚表(不占用类的存储空间)
- 虚基类存在于继承类中,占用存储空间;虚函数不占用存储空间
- 虚基类表存储的是虚基类相对直接继承类的偏移;虚函数表存储的是虚函数的地址
用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。
- 实现多态,有两种方式,重写,重载。
- 函数重载:参数的类型,个数,顺序不同,和返回值无关
- 运算符重载:实质是编写以运算符作为名称的函数
分别对应重载和重写
重载和重写是如何实现- 重载:在编译阶段为函数赋予不同的函数名加以区分不同参数的同名函数
- 重写:在基类中定义虚函数,在派生类中重写函数,运行时根据对象的实际类型调用相应的函数
- 虚函数是类的成员函数
- 存在虚函数的类,有一张虚表。类的对象有一个指向虚表开始的虚指针
虚函数的作用主要是实现**多态的机制:**用父类型的指针指向其子类的实例
- 虚函数在派生类层面相同的重载函数都保持虚特性
- 虚函数必须是类的成员函数
- 析构函数可以是虚函数,构造函数不能是虚函数
- 纯虚函数是在基类中声明的虚函数
- 含有纯虚拟函数的类称为抽象类,它不能生成对象,要求在派生类中必须予以重写以实现多态性
- 定义纯虚函数是为了实现一个接口,用来规范派生类的行为,也即规范继承这个类的程序员必须实现这个函数。纯虚函数的意义在于,让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作
- 虚函数和纯虚函数可以定义在同一个类中,含有纯虚函数的类被称为抽象类,而只含有虚函数的类不能被称为抽象类。
- 虚函数可以被直接使用,也可以被子类重载以后,以多态的形式调用,而纯虚函数必须在子类中实现该函数才可以使用,因为纯虚函数在基类有声明而没有定义。
- 虚函数和纯虚函数都可以在子类中被重载,以多态的形式被调用。
- 定义形式不同
- 虚析构函数使得在删除指向子类对象的基类指针时可以调用子类的析构函数达到释放子类中堆内存的目的,而防止内存泄露的
- 由于父类的析构函数为虚函数,所以子类会在所有属性的前面形成虚表,而虚表内部存储的就是父类的虚函数
- 当delete父类的指针时,由于子类的析构函数与父类的析构函数构成多态,所以得先调动子类的析构函数;之所以再调动父类的析构函数,是因为delete的机制所引起的,delete 父类指针所指的空间,要调用父类的析构函数。
// #1 模板定义 templatestruct TemplateStruct { TemplateStruct() { cout << sizeof(T) << endl; } };
- 模板实例化:分为显式实例化和隐式实例化,前者由开发人员规定类型,后者由编译器规定
// 模板显示实例化 template struct TemplateStruct
; - **模板具体化:**修改原模板的定义,相当于对某些类型做特殊处理。下面的例子中就是将原来模板的构造函数内容修改了。
template<> struct TemplateStruct
{ TemplateStruct() { cout << "--8--" << endl; } };
C++关键字及其详解
new和malloc的区别- new 是关键字和操作符,malloc是函数
- new在调用时先分配内存,再调用构造函数,释放时调用析构函数。malloc没有
- malloc需要申请内存的大小
- new 可重载
- new分配内存更安全
用这两个运算符的时候它会在底层调用全局函数operator new和operator delete
- 底层也是利用malloc和free分配内存的,因此可以说new和delete不过是malloc和free的一层封装
- operator new
内部通过malloc分配空间
如果malloc申请空间失败,设置空间不足的应对措施,则执行,执行之后继续调用malloc再次申请,直到申请成功。
如果没有设置应对措施,直接抛异常(内存空间不足)。 - operator delete
封装free,不会抛异常。
- operator new
- 禁止继承:c++11特性中,将类标记为final,意味着无法继承。
class test final { ...... };
- 禁止重写方法:当方法被标记为final时,在子类中无法重写该方法。
virtual void func() final;
- 如果派生类在虚函数声明时使用了override描述符,那么该函数必须重载其基类中的同名函数,否则代码将无法通过编译。
- override表示函数应当重写基类中的虚函数(用于派生类的虚函数中)。
- 定义全局静态变量和局部静态变量:初始化的静态变量会在数据段分配内存,未初始化的静态变量会在BSS段分配内存。静态变量只能在本源文件中使用
- **定义静态函数:**静态函数只能在本源文件中使用
- 定义类内的静态成员变量和函数:类中的static静态数据成员拥有一块单独的存储区,所有这些对象的静态数据成员都共享这一块静态存储空间。
C++规定:全局或静态对象当且仅当对象首次用到时才进行构造
静态变量的作用域、存储空间和生命周期- 作用域
- 静态全局变量 :全局作用域+文件作用域,所以无法在其他文件中使用。
- 静态局部变量 :局部作用域,只被初始化一次,直到程序结束。
- 类静态成员变量:类作用域。
- 存储空间
- 静态变量都在静态存储区
- 生命周期
- 静态全局变量、静态局部变量都在静态存储区,直到程序结束才会回收内存。
- 类静态成员变量在静态存储区,当超出类作用域时回收内存。
- 静态是针对类的 , 成员变量为对象所有。
- 静态成员函数不属于任何一个类对象,非静态成员必须随类对象产生, 所以说 静态成员函数 看不到 非静态成员,无法访问。
- 非静态成员随对象的产生时分配内存,通过类的对象去访问。
- 32位平台下,无论指针的类型是什么,sizeof(指针名)都是4
- 64位平台下,无论指针的类型是什么,sizeof(指针名)都是8。
- **指针:**是一个变量,变量储存的是一个地址,指向内存的一个储存单元
- **引用:**本质上和原来的变量是同一个东西,只不过是原变量的一个别名。引用是加了const修饰的指针,它所指向的空间不能改变,但里面的内容可以修改。
- 指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
- 指针的值可以为空,但是引用的值不能为NULL,并且引用在定义的时候必须初始化;
- 作为函数参数
- 用指针传递参数,可以实现对实参进行改变的目的,是因为传递过来的是实参的地址,因此使用*a实际上是取存储实参的内存单元里的数据,即是对实参进行改变,因此可以达到目的。
- 引用作为函数参数进行传递时,实质上传递的是实参本身
- 函数指针就是指向函数的指针变量。每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。
- 对象在编译时就绑定了函数地址,nullptr也可以访问函数
- 指针函数是函数,返回值为指针
- 函数指针是指针,指向一个函数
释放内存后指针不及时置空(野指针),依然指向了该内存,那么可能出现非法访问的错误。
避免办法:
- 初始化置NULL
- 申请内存后判空
- 指针释放后置NULL
- const用于定义常量:编译器可以进行 数据静态类型安全检查
- const修饰函数形式参数:输入参数为用户自定义的类型和抽象数据类型时,应将值传递改为const &传递,可以提高效率。
- 使用引用传递不需要产生临时对象,节省临时对象的构造、复制、析构过程消耗的时间。引用有可能改变变量值,所以使用const修饰
- const修饰函数返回值:如果给指针传递函数返回值加const,返回值不能被直接修改,且该返回值只能被赋值给加const修饰的同类型的指针。
- 有 const 修饰的成员函数(void Fun() const),只能读取数据成员,不能改变数据成员;
- 常量(即 const)对象可以调用 const 成员函数,而不能调用非const修饰的函数
- 不能同时使用
- 类内静态成员要在类外初始化。const变量要在定义时初始化。
- const位于 * 左侧,表明指针指向的数据是常量(常量指针),不能通过解引用修改数据,指针变量可以指向其他的内存单元。指针变量本身不做限制
const int * a;
- const位于 * 右侧,表明指针变量本身是常量(常指针,指针常量),不能指向其他内存地址,指针指向的变量可以修改。
int * const a;
- const位于 * 左右。表明指向常量的指针常量,指针和所指的对象都不能修改。
const int * const a;
都是定义作用
- #define 不带类型 , const 带类型
- #define 不占内存 , const 占内存
#define只在预编译阶段起作用,定义的宏在编译后消失了,不占用内存,const定义的常变量本质上还是变量,在编译运行阶段起作用。 - #define只是简单的字符替换,可能出问题,不如const安全
尽量使用const和inline而不是使用 #define
其他 类内可以定义引用数据成员吗- 不能使用默认构造函数,必须提供构造函数来初始化引用成员变量
- 构造函数的形参也必须是引用类型
- 不能在构造函数的函数体中赋值,所有的成员变量必须在初始化列表中进行初始化。
class node { public: node(int &target) :st(target) { cout << "lalala" << endl; } private: int &st; };拷贝构造函数的参数是什么传递方式
拷贝构造函数的参数必须使用引用传递