c++知识点
一、封装
-
类和对象
数据成员
静态成员变量属于类不属于对象,在类外定义
没有默认构造函数的类成员变量必须在初始化列表中给出来
常数据成员必须在初始化列表中初始化 -
成员函数
类默认的六个成员函数
构造函数
拷贝构造函数
析构函数
赋值操作数重载
取地址操作符重载
const修饰的成员函数
const修饰的取地址操作符重载
const修饰的this指针
在函数内部不能this指针的指向
const修饰的成员函数不能去调用非const修饰的成员函数
静态成员函数/static
静态成员函数属于类而不属于对象,继承中只会有一份
最好用静态成员函数来调静态数据成员
非静态成员只能用this指针来访问 -
对象
对象的大小是所有数据成员的额大小之和,遵循内存对齐
对象的大小不包括静态的数据成员
对象是类的实例化,类不分配存储空间,对象才会分配用于存储的空间
空类的对象大小为1 -
类
类的定义方式
直接在类中定义
将类的声明放在.h文件中,将类的定义放在.cpp文件中
类的作用域
局部域、全局域、类域、命名空间域
(类结束时要加上分号,类中的元素称为类的成员,类中的数据称为数据成员,类中函数称为成员函数) -
友元
友元函数:★友元函数可以访问类中的所有成员,友元函数不能被继承;
友元类:友元类可以直接访问本类中的成员;
友元关系:友元关系是单向的,不能进行传递。 -
内联函数
类成员函数默认为内联函数
内联函数是以时间来换取空间的
内联函数只是对编译器的一个建议,具体的还要看编译器
内联必须和函数定义放在一起才会起作用
内联和宏的区别
宏使用预处理器对宏进行替代,而内联函数是通过编译器的控制来实现的且内联函数是真正函数,没有参数压栈减少了调用开销,还不用担心宏产生的一系列问题
内联函数可以访问对象的私有成员函数,内联函数在c++中使用最广的应该是用来定义存取函数的 -
访问限定符
public公有:public成员在类外可以直接访问
protected保护:在类外不能被直接访问
private私有:在类外不能被直接访问
访问限定符的作用域从开始出现的位置到下一个访问限定符出现的位置
struct的默认访问权限是public而class的默认访问权限是private -
★ this指针
每个成员函数都包含一个指向本类对象的指针叫this指针,它的值是当前被调用成员函数所在对象的起始地址
this指针是隐式使用的作为参数被传递给成员函数,由编译器自用实现的不可以在形参中添加this指针
★ 构造函数没有this指针,因为构造函数只在出现在创建对象时调用一次
静态成员函数没有this指针,因为静态成员函数属于类不属于对象
需要注意的地方★1、尽量用const、enum、inline来替换#define
二、继承
继承是面向对象语言为了实现代码复用的重要手段,允许程序保持原有类特性的基础上进行扩展延伸。
- 继承关系
public继承:基类中的除私有成员外其他成员在子类中的访问属性不变,私有成员在子类中无法被访问
protected继承:基类中的公有和保护成员在子类中是保护成员,私有成员在子类中还是不能被访问
private继承:基类中的公有及保护成员在子类中成了私有成员,私有成员在子类中还是不能被访问 - ★ 赋值兼容规则
子类对象可以赋值给父类对象,父类对象不能赋值给自诶对象;
父类对象的指针和引用可以指向子类对象,子类对象的指针和引用不能指向父类对象。 - 继承过程中的作用域
在继承体系中基类和派生类具有独立的作用域;
如果派生类中有和基类同名的成员,子类成员会将基类成员进行"隐藏"访问基类的成员必须加基类的类域限定;
实际在继承体系里不要定义同名成员。
继承关系构造函数的调用顺序:基类构造函数->派生类对象构造函数->派生类构造函数
继承关系析构函数的调用过程:派生类析构函数->派生类对象析构函数->基类析构函数
单继承 子类对象只有一个父类对象
多继承 子类对象有两个或两个以上对象
菱形继承 菱形继承会产生数据冗余以及二义性 - 虚继承
在派生类继承基类时,加上一个virtual关键词则为虚拟继承
虚继承解决了菱形继承中子类对象包含多个父类对象的数据冗余和浪费空间的问题
c++使用虚继承解决不同途径继承来的同名数据成员在内存中拷贝构造函数数据不一致的问题将共同基类设置为虚基类 - 虚基类
在继承体系中通过virtual继承来的基类
struct csubclass∶public virtal cbase{};cbase称之为csubcass的虚基类,而不是说cbase就是个虚基类
★友元关系不能继承,也就是说基类有元不能访问子类私有和保护成员
★基类定义一个static成员,则整个继承体系里只有这样一个成员无论派生出多少都只有一个static成员实例
三、多态
多态就是一个事物有多种状态,多态就是不同功能的函数具有同一个函数名,这样就可以用同一个函数来实现多种功能
-
多态的分类
静态多态 函数重载 泛型编程
动态多态 虚函数
静态多态:编译器在编译期间完成的,编译器根据函数实参的类型可能会进行隐式类型转换),可推断出要调用那个函数,如果有对应的函数就调用该函数,否则出现编译错误
动态多态:在程序执行期间(非编译期)判断所引用对象的实际类型,根据其实际类型调用相应的方法。使用virtual关键字修饰类的成员函数时,指明该函数为虚函数,派生类需要重新实现,编译器将实现动态绑定。 -
虚函数
在类成员函数前面加上virtual关键字,那么这个函数就是虚函数
如果在一个类中声明虚函数,那么在这个类之后的继承体系中这个函数都是虚函数 -
虚函数的使用方法
在派生类中重新定义虚函数,要求函数名,函数类型,参数个数和类型完全与基类中函数相同,根据自己的需求定义函数体。
当一个成员函数被声明为虚函数后,在派生类中的同名函数都是虚函数。如果在派生类中没有对虚函数重新定义,则派生类简单的继承基类的虚函数。 -
★ 静态关联与动态关联
函数重载和通过对象名调用的虚函数,在编译期间即可确定调用的虚函数属于哪一个类,其过程称为静态关联。
在运行阶段,基类变量先指向某一个类对象,然后通过此指针变量调用该对象中的函数,这种过陈称为动态关联。
定义一个指向基类对象的指针变量,并使它指向同一类中需要调用该函数的对象。通过过该指针变量调用此虚函数,就相当于指针变量所指向的对象的同名函数。
当一个类中的成员函数被声明称虚函数,就不能再定义一个非virtual的与其同名的函数
★使用虚函数系统会有一定的空间开销,当类中有虚函数时编译器会为该类构造一个虚函数表。虚表是一个指针数组来存放每个虚函数的入口地址。但是系统进行动态关联的开销很小,所以很高效
★一般将析构函数声明成虚析构函数,即使基类不需要析构函数也要显示的定义一个函数体为空的析构函数,保证撤销对象动态分配的空间时的正确性 -
★动态绑定条件
必须是虚函数
通过基类的引用或指针来调用虚函数 -
继承体系同名成员函数的关系
重载
必须在同一作用域
函数名相同,参数列表不同
返回值可以不同
★ 重写(覆盖)
处在不同的作用域(基类和派生类)
函数名相同,参数相同,返回值相同(协变除外)
必须有virtual关键字
访问修饰符可以不同
重定义(隐藏)
处在不同的作用域(基类和派生类)
函数名相同
在基类和派生类中不构成重写就是重定义 -
纯虚函数
在成员函数的参数列表后面加上=0,构成了纯函数。包含纯虚函数的类被称为抽象类,抽象类不能实例化出对象,纯虚函数在派生类进行重定义之后派生类才可以实例化出对象
抽象类的作用是为类提供一个公共的接口
抽象类不能定义对象但可以定义指向抽象类的指针变量,通过这个指针变量来实现多态
★ 只有类成员函数才能声明为虚函数
★ 静态成员函数不能定义成虚函数
★最好将析构函数定义成虚函数,不然在某些情况下不会调用派生类中的析构函数从而形成内存泄漏 -
重载
由于重载的存在,c++与c的函数命名方式不同,为了引用c的文件时写成“extern c”
重载又被称为静态绑定,在编译期间确定要调用的函数
函数重载
同一作用域
函数名必须相同,参数列表必须不同,返回类型可以不同
可以将功能相似的函数完成重载,减少对于函数名的记忆
运算符重载/operator
输入输出算符进行重载。对于输入输出运算符进行重载要使用的话就要在声明其为友元
返回值是输入输出对象的引用
对++或-重载。
由于此运算符分为前置与后置,在后面跟上(int)习惯表示为后置
不能进行重载的运算符 . ?∶ sieof :: .* -
指针与引用
引用的特点
一个变量可以有多个引用,引用是可以传递的,运用必须进行初始化,在初始化的时候引用一次,不能再改为其他变量的引用
引用并不是创建一个对象而是为一个已经存在的对象起一个别名,引用并不是一个对象所以不能创建引用的引用
引用绑定到const对象上是对常量的引用,不能通过常量的引用来修改它所绑定对象的值
对const的引用可以引用一个非const类型的对象
参数的传递分为值传递与址传递,本质是一样的但是要在函数外改变变量的值就要进行传地址 ,c++有了新的方式是传引用
★传指针与传引用的区别
传引用比指针更加直观方便,传递效率更高
使用引用类型就不必再swap中声明形参是指针变量,指针需要另外开辟空间,内容是地址,引用不是独立单元不需要单独只能用内存只是实参的别名
★ 指针与引用的区别
引用只能在定义时初始化一次,之后不能与其他变量绑定而指针变量的值是可变的
引用必须指向有效的变量,指针可以为空
sieof求取指针与引用对象的值不同,sizeof用的到的所引用对象的的大小,szeof求指针得到的是对象地址的大小
引用的++或-是对引用对象值得加减,而对指针来说是地址的偏移会改变指针的指向
相对于引用来说指针更加安全 -
拷贝
浅拷贝
浅拷贝就是由默认的构造函数进行逐一的赋值,是值传递
浅拷贝只是值传递,将指针的指向给了新成员但并没有对其分配新的内存空间
深拷贝
深拷贝就是由默认的拷贝构造函数对对象的值进行逐一的赋值,并且对其分配了内存空间
写时拷贝
写时拷贝可以说是一种拖延战术,当要进行拷贝时先对其进行“读”操作,不为其分配内存空间,只是将指针的指向给了过去。当为其执行“写”操作时才会为其开辟内存空间
写时拷贝时通过引用计数来实现的
分配空间的时候多为其分配4个字节,有新的指针指向这块空间引用计数加一,释放时引用计数减一。当引用计数为0时这块空间才被释放,当一个指针要改变这块空间的值时才为其分配新的内存空间 -
内存管理
★new/delete
new∶ 调用operator new分配空间,调用构造函数初始化对象
delete∶ 调用析构函数清理对象,调用operator delete函数释放空间。 new[]∶ 调用operator new分配空间,调用N次构造函数初始化N个对象
delete[]∶调用N次析构函数清理N个对象,调用operator delete函数释放空间
★ new/delete 、malloc/free必须成匹配使用,否则会导致内存泄漏和程序崩溃
定位new表达式
定位new表达式是在以分配的原始空间中调用构造函数初始化一个对象
★ 允许程序员要求将对象创建在已经被分配好的内存中,这种形式的new表达式称为定位new表达式
★new/delete和mallc/free的区别
它们都是动态管理内存的入口
malloc/free是C/C++标准库的函数,new/delete是C++操作符
★malloc/free只是动态分配内存空间/释放空间。而new/delete除了分配空间还会调用构造函数和析构函数进行初始化与清理(清理成员)。
malloc/free需要手动计算类型大小且返回值会void*,new/delete可自己计算类型的大小,返回对应类型的指针 -
模板
模板是泛型编程的基础,泛型编程就是与类型无关的编程
模板函数
代表了一个函数家族,该函数与类型无关,使用时被参数化根据实参类型产生函数特定的类型版本
模板函数的格式∶template<typename param1 typename param2,……,class paramn>返回值类型 函数名(参数列表){}
模板函数可以定义为inline函数,inline关键字必须放在模板形参列表之后,返回值之前,不能放在template之前
在函数内部不能指定缺省的模板实参
类模板
定义对象时必须传递类型
在类外定义模板函数必须定义参数列表
模板参数
模板的模板参数实现容器的适配器
非类型的模板参数
类型形参
类型形参由关见字class或typename后接说明符构成,如templatevoid h(T a){}其中T就是一个类型形参,类型形参的名字由用户自已确定
非类型形参
模板的非类型形参也就是内置类型形参,如template<class T,int a>class B{};其中int a就是非类型模板形参
非模板类型的形参只能是整型,指针和引用,像double,String这样的类型是不允许的。但是double &,double*,对象的引用或指针是正确的
非类型形参在模板定义的内部是常量值,也就是说非类型形参在模板的内部是常量。
全局变量的地址或引用,全局对象的地址或引用const类型变量是常量表达式,可以用作非类型模板形参的实参。
全特化与偏特化
全特化和偏特化都是在模板的基础上不能单独的存在
全特化就是全部特化,即针对所有的模板参数进行特化
偏特化就是部分特化,即针对部分模板参数进行特化
★类型萃取
我们通过类型萃取的方式来区分自定义类型和内置类型
类型萃取是通过在模板的基础上区分内置类型和其他类型,原理是将内置类型进行特化再进行具体的区分。 -
智能指针
auto_ptr
auto_ptr是通过资源转移的方式来防止值拷贝所带来的问题,而所谓的资源转移就是开辟的内存空间任何时刻只能由一个指针指向
由于auto_ptr与真正的指针有很大的区别,尤其是资源转移的方法。现在已经很少使用auto_ptr了
scoped_ptr
为了防止值拷贝所带来的问题,在scoped_ptr中根本就不允许拷贝和赋值,所以在scoped_ptr中将拷贝构造和赋值运算符的重载声明为保护类型,并且不去实现(防赋值,防拷贝)
scoped_array
scoped_array是用来管理数组的
在scoped_array中将拷贝构造和赋值运算符的重载声明为保护类型,不去实现(防赋值,防拷贝)
shared_ptr
shared_ptr允许拷贝和赋值,底层是以"引用计数"为基础实现的
通过引用计数来控制空间的释放,一块空间创建时其引用计数为1,当有新的指针指向这块空间时引用计数加1,反之减1.当引用计数减到0的时候才能释放这块空间
shared_array
shared_array也是管理数组的但它与scoped_array不同的是能够允许拷贝和赋值操作 -
★ shared_ptr的循环运用问题
Shared_ptr的缺陷:当我们要创建一个双向链表时,链表的指针域全部用sharde_ptr来进行维护,但是出了作用域要进行释放的时候,这时每块空间的引用计数都是1,两块空间的释放都限制于对方,都释放不了就引起了内存泄漏
循环引用:为了解决循环引用的问题,我们引入了weak_ptr弱指针来辅助shared_ptr。、 weak_ptr是一种不控制所指向对象生存周期的指针能解决,将weak_ptr绑定到shared_ptr.一旦最后一个指向对象的shared_ptr被销毁,对象就会被销毁
★shared_pt定置删除器
智能指针用来管理动态内存,但动态内存智能是资源的一种。如果要用智能指针来管理文件那么就不能用智能指针的删除器来释放资源了,这就需要定置删除器来完成
实现定置删除器就要用到仿函数来实现,仿函数就是将"()"重载 -
异常处理
当函数发现一个错误时,就抛出一个异常
try/throw/catch
异常的捕获是根据类型决定不是根据对象决定
(抛出的异常会被离它最近且类型相同的catch捕获,如果没有捕获程序则自动结束
抛出异常后会释放局部存储对象,所以被抛出的对象也就还给系统了,throw表达式会初始化一个抛出特殊的异常对象副本(匿名对象),异常对象由编译管理,异常对象在传给对应的catch处理之后撤销。
异常捕获的匹配规则
允许从非const对象到const的转换
允许从派生类型到基类型的转换
将数组转换为指向数组类型的指针,将函数转换为指向函数类型的指针
异常与构造函数&析构函数
构造函数完成对象的构造和初始化,需要保证不要在构造函数中抛出异常,否则可能导致对象不 完整或没有完全初始化。
析构函数主要完成资源的清理,需要保证不要在析构函数内抛出异常,否则可能导致资源泄漏 -
IO流
c++在流类库中定义了四个全局流对象∶cin cout cerr clog
cin标准输入流对象,键盘为其对应的标准设备
cout标准输出流对象,显示器为标准设备
cerr和clog标准错误输出流,输出设备是显示器
cin为缓冲流。键盘输入的数据保存在缓冲区中,当要提取时,是从缓冲区中拿。如果一次输入过多,会留在那儿慢慢用,如果输入错了,必须在回车之前修改,如果回车键按下就无法挽回了。只有把输入缓冲区中的数据取完后,才要求输入新的数据不可能用刷新来清除缓冲区,所以不能输错,也不能多输 -
类型转换
Static_cast:用于非多态类型的转换,任何标准转换都可以用它,但它不能用于两个不相关的类型的转换
reinterpret_cast:reinterpret_cast操作符用于将一种类型转换为另一种不同类型
const_cast:const_cast最常用的用途就是删除变量的const属性,方便赋值
dynamic_cast
dynamic_cast用于将一个父类对象的指针转换为子类对象的指针或引用(动态转换)
dynamic cast只用于含有虚函数的类
dynamic cast会先检查是否能转换成功,能成功则转换,不能则返回0