C++学习笔记(二)
1、内区分区模型
1.1 全局区
全局变量和静态变量离得很近
1.2 栈区
由编译器自动分配释放,存放函数的参数值,局部变量等
注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放
1.3 堆区
由程序员分配释放,若程序员不释放,程序结束时由操作系统回收,在C++中主要利用new在堆区开辟内存
1.4 new运算符
C++中利用new操作符在堆区开辟数据;
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete;
语法:new 数据类型;
利用new创建的数据,会返回该数据对应的类型的指针
2 引用
2.1 引用的基本使用
作用:给变量起别名
语法:数据类型 &别名=原名
2.2 引用注意事项
- 引用必须初始化
- 引用在初始化后,不可以改变
2.3 引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
通过引用参数产生的效果同按地址传递是一样的,引用的语法更清楚简单
2.4 引用做函数返回值
作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
2.5 引用的本质
引用的本质是一个指针常量,一旦初始化后,指向不可改变
C++推荐使用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了
2.6 常量引用
作用:常量引用主要用来修饰形参,防止误操作;
在函数形参列表中,可以加const修饰形参,防止形参改变实参
3、函数提高
3.1 函数默认参数
在C++中,函数的形参列表中的形参是可以有默认值的
语法:返回值类型 函数名(参数=默认值){ }
3.2 函数的占位参数
C++中函数的形参列表里可以有占位参数,用来做占位,调用函数时必须填补该位置
3.3 函数重载
作用:函数名可以相同,提高复用性
函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同,或者个数不同或者顺序不同
函数重载注意事项:
1.引用作为重载条件
2.函数重载碰到函数默认参数
4、类和对象
C++面向对象三大特性:封装、继承和多态
4.1 封装
封装的意义
- 将属性和行为作为一个整体,表现生活中的事物
- 将属性和行为加以权限控制
语法:
class 类名{访问权限:属性 /行为}
类在设计时,可以把属性和行为放在不同的权限下,加以控制访问权限有三种:
- public 公共权限
- protected 保护权限
- private 私有权限
4.1.1 struct和class区别
在C++中struct和class唯一的区别就在于默认的访问权限不同:
- struct默认权限为公共
- class默认权限 为私有
4.1.2 成员属性设置为私有
- 优点1:将所有成员属性设置为私有,可以自己控制读写权限
- 优点2:对于写权限,我们可以检测数据的有效性
4.2 对象的初始化和清理
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作
如果我们不提供构造和析构,编译器会提供,编译器提供的构造函数和析构函数是空实现
4.3 构造函数的分类及调用
两种分类方式:
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
三种调用方式:
- 括号法
- 显示法
- 隐式转换法
括号法:
显示法:
匿名对象
隐式转换法
4.3.1 拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况:
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
1.使用一个已经创建完毕的对象来初始化一个新对象
2.值传递的方式给函数参数传值
3.值的方式返回局部对象
4.3.2 构造函数调用规则
默认情况下,C++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
4.3.3 深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
浅拷贝带来的问题就是堆区的内存重复释放
深拷贝就是自己实现拷贝构造函数,在堆区重新创建内存空间 ,解决浅拷贝带来的问题,析构函数可以释放堆区内存;
如果不利于深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
4.3.4 初始化列表
作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)……{}
4.3.5 类对象作为类成员
C++类中的成员可以是另一个类的对象,称该成员为对象成员
构造顺序是:先调用对象成员的构造,再调用本类构造;
析构顺序与构造相反
4.3.6 静态成员
静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员
静态成员变量:
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
静态成员函数:
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
4.4 C++对象模型和this指针
4.4.1 C++对象模型
在C++中,类内的成员变量和成员函数分开存储;
只有非静态成员变量才属于类的对象上
空对象占用内存为1
4.4.2 this指针
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
问题:这一块代码是如何区分哪个对象调用自己呢
C++通过提供特殊的对象指针,this指针,解决上述问题
this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途:
-
当形参和成员变量同名时,可用this指针来区分 ;this可以直接用,不需要定义
-
在类的非静态成员函数中返回对象本身,可使用return *this(可实现链式编程)
如果以值的形式返回 ,会创建新对象,但是以引用形式不会创建新对象
4.4.3 空指针访问成员函数
空指针可以调用成员函数,但是要注意有没有用到this指针
如果用到this指针,需要加以判断保证代码的健壮性
4.3.4 const修饰成员函数
常函数:
- 成员函数加const后称为常函数
- 常函数不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中依旧可以修改
常对象:
- 声明对象前加const称该对象为常对象
- 常对象只能调用常函数
4.4 友元
在程序里,有些私有属性,也想让类外特殊的一些函数或者类进行访问,就需要用到友元技术
友元的目的就是让一个函数或者类访问另一个类中私有成员
友元关键字为friend
友元的三种实现:
-
全局函数做友元
-
类做友元
-
成员函数做友元
4.5 运算符重载
对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
4.5.1 加号运算符重载
作用:实现两个自定义数据类型相加的运算
1.成员函数重载+号
2.通过全局函数重载+号
4.5.2 左移运算符重载
4.5.3 递增运算符重载
通过重载递增运算符,实现自己的整型数据
前置递增返回引用,后置递增返回值
4.5.4 赋值运算符重载
C++编译器至少给一个类添加4个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符operator=对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题
4.5.5 关系运算符重载
作用:重载关系运算符,可以让两个自定义类型对象进行对比操作
4.5.6 关系运算符重载
- 函数调用运算符()也可以重载
- 由于重载后使用的方式非常像函数的调用,因此称为仿函数
- 仿函数没有固定写法,非常灵活
4.6 继承
继承的好处:减少重复代码
语法:class 子类:继承方式 父类
子类也称为派生类,父类也称为基类
派生类中的成员包括两大部分,一类是从基类继承过来的,一类是自己增加的成员,从基类继承过来的表现其共性,而新增的成员体现了其个性
4.6.1 继承方式
- 公共继承
- 保护继承
- 私有继承
4.6.2 继承中的对象模型
4.6.3 继承中的构造和析构顺序
先构造父类,再构造子类,析构的顺序与构造的顺序相反
4.6.4 继承中的同名成员处理
4.6.5 继承同名静态成员处理方式
静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
1.静态成员变量
访问静态成员属性的两种方式:
- 通过对象访问
- 通过类名访问
2.静态成员函数
4.6.6 多继承语法
C++允许一个类继承多个类(不建议)
语法:class 子类:继承方式 父类1,继承方式 父类2
多继承可能会引发父类中有同名成员出现,需要加作用域区分
4.6.7 菱形继承
概念:
两个派生类继承同一个基类,又有某个类同时继承着两个派生类,这种继承被称为菱形继承
菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费
利用虚继承可以解决菱形继承问题
4.7 多态
多态分为两类
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
静态多态和动态多态区别:
- 静态多态的函数地址早绑定-编译阶段确定函数地址
- 动态多态的函数地址晚绑定-运行阶段确定函数地址
动态多态满足条件:
1.有继承关系
2.子类重写父类的虚函数
动态多态的使用:
父类的指针或者引用执行子类对象
4.7.1 多态的原理剖析
当父类的指针或者引用指向子类对象时候,发生多态
4.7.2 多态的优点
- 代码组织结构清晰
- 可读性强
- 利于前期和后期的扩展以及维护
C++开发提倡利用多态设计程序架构,因为多态优点很多
1.普通实现
2.多态实现
4.7.3 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名(参数列表)=0
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:
- 无法实例化对象
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
4.7.4 虚析构和纯虚析构
问题:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构函数
解决方式 :将父类中的析构函数改为虚析构或者纯虚析构
虚析构和纯虚析构共性:
1.可以解决父类指针释放子类对象
2.都需要有具体的函数实现
虚析构和纯虚析构区别:
如果是纯虚析构,该类属于抽象类,无法实例化对象
父类指针在析构时候不会调用子类中析构函数,导致子类如果有堆区属性,出现内存泄漏
将父类析构函数改为虚析构
纯虚函数只需要声明,纯虚析构需要声明也需要实现
有了纯虚析构之后,这个类也属于抽象类,无法实例化对象
总结:
1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3.拥有纯虚析构函数的类也属于抽象类
5 文件操作
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放
通过文件可以将数据持久化
C++中对文件操作需要包含头文件< fstream >
文件类型分为两种:
- 文本文件:文件以文本的ASCII码形式存储在计算机中
- 二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们
操作文件的三大类:
1.ofstream:写操作
2.ifstream:读操作
3.fstream:读写操作
5.1 文本文件
5.1.1 写文件
写文件步骤:
文件打开方式:
注意:文件打开方式可以配合使用,利用 | 操作符
5.1.2 读文件
读文件步骤:
5.2 二进制文件
以二进制的方式对文件进行读写操作
打开方式要指定为ios::binary
5.2.1 写文件
5.2.2 读文件