Section 1
命名空间
- 三种方式
using std::cout;//全部文件使用的cout来自std
std::cout << "hello" << std::endl;//用的地方指定
using namespace std;//把std所有的命名空间全部引入
- 定义一个命名空间
- 07 using声明和using编译指令
对C语言增强的地方
- 在c++中,变量可以随用,随定义;C一般要求定义在前面;
- c中对全局变量重复定义,会解释为一个是声明;c++会报错
- struct student在c中定义变量要加上struct;在c++中不用(把他当成类处理)直接student s1
- 对于函数在c中不写返回值(默认是int),不会报错;c++强制要写
- 在c中可以传多余的参数
- 增加了bool类型
- 三目运算符在c++中可以当左值:返回的值变量的引用
- const的增强:针对直接赋值(通过指针,全局变量都不能改,局部变量c可以改,c++改的是临时变量的值,本身没有改)
-
对const修饰的变量取地址的时候,会分配临时内存(一般来说分配了内存就可以通过指针改,但是这个临时的内存改了没有意义)
-
const前加入关键字external后也会分配内存,(这里通过指针改会报错)
-
使用变量初始化const的变量可以通过指针修改
-
自定义的数据类型也可以通过指针修改
-
c中的const可以改(针对直接赋值)
-
编译器看到a就会到文字常量区的符号表找到key值为a的value替代,类似于宏定义,不同的是宏定义是在预处理阶段,const处理是在编译阶段
-
-
枚举的增强:c中可以用int表示(含义不清)
-
引用的基本语法
-
int &是引用的数据类型,就是别名;
-
通过引用,在函数传参的时候可以用引用做形参接收,回避了传指针的麻烦;
-
引用的本质:int *const p(常指针,内容可变,p值不变)
-
引用一定要定义的时候初始化,跟const一样
-
引用的大小和指针一样
-
int *const p=&a
-
有关常指针:数组为例
-
引用作为函数的返回值:
-
引用作为返回值,不要返回局部变量的引用
-
如果是在堆上开辟的,或者是static定义的局部变量可以返回
-
函数可以作为左值(如果函数返回值是指针,他也是返回的是数值,不能作为左值,但是引用可以)
-
指针引用
- 避免了二级指针操作中容易出错
- 避免了二级指针操作中容易出错
-
const 修饰引用(可以通过指针修改)
- 引用用在形参上(可以不用开辟新的空间),但是为了防止误操作改变外面的量,可以加上const:
- 引用用在形参上(可以不用开辟新的空间),但是为了防止误操作改变外面的量,可以加上const:
内联函数
- 如果反复多次调用一个函数,就会有多次进出栈的操作,为了避免这个开销。引入内联函数
- 内联函数的作用类似于宏函数(但是宏函数不会检查语法错误。,是在预处理阶段做的,没有语法检查的能力)
- 内联函数是编译阶段做的,有语法的检查能力,本质就是在调用处,原处展开。(但是不会无限展开,如果代码量太大,为了避免过于占用内存,还是会进出栈操作,比如在inline里加个1000次的循环),一般用在一两行代码,要反复运行的位置(牺牲空间换时间)
- 成员函数一般都很短,他们都隐藏了关键字inline
进入18
函数的默认参数以及占位参数
- 带默认参数
- 函数的声明和实现只能有一个有默认参数
- 函数的占位参数:符号重载用
- 函数重载(不是复写)
- 原理:编译器针对不同的函数,修改了函数名
externC
- 因为c++独有的函数重载机制,编译器会修改函数名(方便找到函数),所以在c++编译器上运行c++代码要加上一下带条件宏定义:
c语言的结构体不做类型的检测,完全只是内存大小相同即可,只是检测指针的步长。导致不同的结构体,只要内存分配相同,可以同样传递:
- 例如一个函数要求传入的结构他为person但是另一个结构体dog内存分配与人相同也可以传入该函数。
- c语言中结构体里只能放属性,不能放行为(函数,要函数指针才行)
c++中struct和class区别就是默认权限不同,struct默认public,class默认private(class相比struct增加了权限控制)
Section 2
类相关
构造函数和析构函数
-
编译器自动调用
-
编译器自动生成空的无参构造和析构
-
在公共作用域下
-
构造:没有返回值,也不用写void,跟类名写法一样,可以重载
-
析构:函数名前加~
-
拷贝构造函数(就是拷贝一个对象):传的是同类对象的引用,防止修改要加const
- 要用&接收,不用的话就是值传递。要新创建对象,就进入了无限的循环
- 要用&接收,不用的话就是值传递。要新创建对象,就进入了无限的循环
-
调用:
- 括号法:
- 显示法:
- 注意点:
- 隐式法:
- 括号法:
-
拷贝构造函数的调用时机
- 当传值的时候,形参就是调用 了拷贝构造函数:Person p=p1(隐式法)
- 以值的方式返回局部对象
- 当传值的时候,形参就是调用 了拷贝构造函数:Person p=p1(隐式法)
-
构造函数的调用规则
深拷贝与浅拷贝
- 浅拷贝的时候只拷贝了指针,这样在释放的时候,其他有拷贝构造函数创建出来的对象p2先释放了指针,而本对象p1再释放就会崩溃;解决方法就是深拷贝:
- 重写系统默认的拷贝构造函数(系统只会简单的值拷贝):
初始化列表
- 用来初始化属性
- explicit关键字使用
- new和delete运算符:完成了开辟空间,调用构造函数(赋值),判断的复合工作
- new
区别
- 注意事项:
- 利用new创建数组
- new
静态成员
-
为什么要在类外初始化:
- 因为如果没有创建对象,类里面的代码不会运行;
- 编译期(分配了空间)
- 这个时候如果用类名访问就会报错(没有值)
- 但是私有的静态成员,可以不用赋值,类外通过类名也访问不到,但是可以在类外赋值
-
访问方式
-
单例模式:
- 私有有参构造和拷贝构造
- 加了作用域:可以视为是在类内部执行的,所以能访问私有构造;
C++对象模型初探-成员变量和成员函数分开存储
this指向被调用成员函数所属的对象
- 编译器编译的时候,把this指针已经加入了:
- 空指针访问成员函数- 也可以访问没用用到this 的成员函数;否则会崩溃
常函数和常对象
- 加上关键字mutable
- 常对象
- 常对象不能调用普通的成员函数,只能调用常函数。
友元
-
全局函数作为友元函数
- 在类中用friend关键字+全局函数的声明,就能访问该类内部的私有属性
- 在类中用friend关键字+全局函数的声明,就能访问该类内部的私有属性
-
友元类
- 友元类不一定有传递性,可逆性
- 类内的函数,可以放在类外部实现,加上命名空间就行
同样:
-
同理也可以是指成员函数,作为另一个类的友元;
数组类封装
运算符重载
-
加运算符:分别利用成员函数,和全局函数
-
-
左移运算符重载
- 要用全局函数实现
- 返回引用是为了链式编程
- 要用全局函数实现
-
递增运算符重载++
-
前置++:一般放在成员函数,考虑到一直要操作,所以要返回引用,例如:++(++a)
- - 后置++:返回的是临时变量,本体内部已经记录了;临时变量方法调用完会回收,所以不能返回引用;
-
由于后置++产生了临时副本,所以为了效率考虑,推荐日常使用前置++;
-
指针运算符重载-智能指针
-
赋值运算符重载
-
[]运算符重载
-
关系运算符重载
-
函数调用运算符重载
-
不要重载逻辑与和逻辑或符号:因为没法实现短路的效果
Section 3
继承和派生
- 语法:class 子类:继承方式:父类
- 继承方式
- protected 子类内可以访问,其他地方不可访问
- private 只有自己类内可以访问
继承中的对象模型
继承中的构造和析构
-
子类默认走父类的无参构造,但是可以利用初始化列表指定要走的父类构造函数(类似于java中的super):
-
子类不继承父类的构造函数和析构函数,只有父类才知道怎么样构造和析构自己的属性
-
继承中的同名成员处理
-
继承中的同名静态成员处理
-
多继承的语法
-
菱形继承问题以及解决
-
使用虚继承处理两份数据的问题(二义性,浪费内存)
-
虚继承的内部工作原理剖析
- 一旦使用了虚继承,那么内部之前绑定的变量就会变成指针,指向一个表,表中的内容就会根据子类是否重写而覆盖
多态相关
-动态联编,通过虚函数实现-
- 多态原理
- 多态的好处(计算器案例)
- 把要干什么和怎么干分开
- 有纯虚函数的类,也叫抽象类
- 纯虚函数子类一定要重写
- 纯虚函数跟java的抽象函数类似;
- 虚析构和纯虚析构
- 虚析构:当子类有的属性是创建在堆区上的,那么就要重写析构函数,为了子类delete的时候能够调用到,父类的析构函数应该写成虚析构;
- 纯虚析构和纯虚函数不同:由于父类的析构函数一定会被调用(父类自己也有属性可能在堆区,自己也要释放),所以要有函数体;但是纯虚函数已经=0,所以函数体写在外面;
- 纯虚析构 有了之后,也变成了抽象类;
- 向上向下类型转换
- 重写、重载、重定义;
- 重定义:
泛型编程
模板技术
- 引用传递的时候原名的类型和别名的类型一定要一致,编译器不会自己帮你转(例如char 转 int),但是普通的传递会转
- 模板使用:编译器推导出来了也行;推不出来就要指定(比如:函数都没有参数的时候)
- 函数模板和普通函数的区别以及调用规则
- 区别:
-规则:
- 区别:
- 模板机制
- 函数模板通过传入的T的类型生成了模板函数(编译器内部生成的)
- 模板的局限性以及解决:不能真的针对每一个类型
- 函数模板通过传入的T的类型生成了模板函数(编译器内部生成的)
类模板
-
与函数模板的区别:
- 类模板不能使用自动类型推导,必须显示指定
- 类模板中的类型可以有默认参数(默认什么类),函数模板不行;
-
泛型编程:体现在模板技术;特点是将类型参数化;
-
类模板中的成员函数创建时机
- 类模板的成员函数,并不是一开始就创建出来的,而是在运行阶段,传入了T的具体类型才创建出来的;
-
类模板作为函数参数
-
类模板碰到继承的问题以及解决
- 子类创建的时候必须要知道父类模板T是什么类型,否则无法给父类分配内存(因为要走子类父类的构造)
- 如果子类是具体类(不是类模板),那就指定父类T的类型
- 如果子类是类模板,那么把自己的一个泛型交给父类就行:
-
类模板的类外实现
-
类模板的分文件编写问题以及解决
-
类模板碰到友元函数
-
类模板案例—数组类封装
- 需要注意的是,自定义对象的数组初始化前,会先初始化自定义对象(走无参的默认构造0),自定义类里的有参构造会默认删掉无参构造(所以自己手动补上)
- 需要注意的是,自定义对象的数组初始化前,会先初始化自定义对象(走无参的默认构造0),自定义类里的有参构造会默认删掉无参构造(所以自己手动补上)
类型转换-静态类型和动态类型转换
- 自定义类型 支持继承类对象的,指针,引用间的转换;
- 没有继承关系的不支持
-
动态转换(严谨)
- 不安全或者丢失精度都不让转
- 不安全或者丢失精度都不让转
-
类型转换-常量类型
- 不允许把非指针或者非引用做const_cast转换(不能把常量转成变量)
- 不允许把非指针或者非引用做const_cast转换(不能把常量转成变量)
-
重新解释类型转换(不安全,不建议用)
Section 4
-
异常的基本语法
- 为什么要有异常:c中的返回值判断有缺陷(可能是异常,也可能是正常值)
- 为什么要有异常:c中的返回值判断有缺陷(可能是异常,也可能是正常值)
-
抛出自定义异常:跟java不一样,不用继承exception的父类
-
栈解旋
-
异常的接口声明
-
异常变量的生命周期
- 值的方式接受对象,会调用拷贝构造
- 用引用接受
- 值的方式接受对象,会调用拷贝构造
-
用指针接收,匿名对象走完本行没有变量接收就会回收,指针就变成野指针(接收匿名对象的地址没有用)
-
异常变量如果抛出有接收的话,在抛出的函数体内创建的对象不会释放(即使是创建在栈上,创建在堆上的更不会),他的生命周期跑到了catch的函数体里;不像函数返回值(会在本函数结束的时候释放栈里的内容),理解:一个抛字,抛出去了就不规我管了。
-
异常的多态使用
- 继承基类异常,由基类异常的引用接受
- 继承基类异常,由基类异常的引用接受
-
使用C++系统标准异常类
-
编写自己的异常类(继承系统的)
- char* 不可以直接赋值给string,要通过构造函数string(char*)
- char* 不可以直接赋值给string,要通过构造函数string(char*)
-
标准输入流
-
理解:cin就是键盘的缓冲区(输入的数据现在缓冲区,cin.get就是在这个缓冲区拿);同理cout也有缓冲区,endl会刷新缓冲区,并加上\n换行符;
-
标准输出流
-
格式化输出(成员函数法)
- 控制符法:
- 控制符法:
-
文件的读写
- 写:
- 读
- 写: