内存分区模型
程序运行前
编译后生成 exe 可执行程序,分为两个区域
代码区:
- 存放CPU执行的机器指令
- 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可
- 代码区是只读的,使其只读的原因是防止程序意外地修改了它的指令
全局区:
- 全局变量和静态变量存放在此
- 局部变量(即使用 const 修饰)不在全局区中
- 全局区还包含了常量区,字符串常量和其他常量也存放在此
- const 修饰的全局常量、字符串常量
- 该区域的数据在程序结束后由操作系统释放
程序运行后
栈区:
- 由编译器自动分配释放,存放函数的参数值,局部变量等
- 注意事项:不要返回局部变量的地址,栈区开辟的数据由编译器自动释放(如函数的形参、局部变量)
堆区:
- 由程序员分配释放,若程序员不释放,程序结束时由操作系统回收
- 在 C++ 中主要利用 new 在堆区开辟内存
|
|
new 操作符
C++中利用new操作符在堆区开辟数据
-
堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符delete
- delete 对应指针
-
利用new创建的数据,会返回该数据对应的类型的指针
|
|
引用
给变量起别名
- 变量的含义:用于指代,操作某块内存
|
|
注意事项
- 引用必须初始化
- 初始化后不可再改变
引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
|
|
引用做返回值
注意:不要返回局部变量引用
用法:函数调用作为左值
|
|
引用的本质
本质是指针常量,由编译器转换为指针
|
|
常量引用
|
|
函数
默认参数
C++中,函数的形参列表中的形参是可以有默认值的
|
|
- 如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值
- 如果函数声明有默认参数,函数实现就不能有默认参数
占位参数
|
|
重载
作用:函数名可以相同,提高复用性
函数重载满足条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同或者个数不同或者顺序不同
- 返回值不同不可以作为条件
注意
-
引用作为重载条件
- 以 const 为标志的区分,是否可写
-
数重载碰到函数默认参数
- 出现二义性,报错
类和对象
封装
意义:
- 属性和行为作为一个整体,表现生活中的事物
- 属性和行为加以权限控制
语法:class 类名 { 访问权限:属性/行为 };
|
|
类中的属性和行为统一称为成员
- 成员属性 –> 成员变量
- 成员函数 –> 成员方法
访问权限
public:类内可以访问,类外可以访问
protected:类内可以访问,类外不可以访问,继承类可访问
private:类内可以访问,类外不可以访问。
Struct 和 Class
唯一的区别就在于默认的访问权限不同
- struct 默认权限为公共
- class 默认权限为私有
成员属性私有化
优点:
- 所有成员属性设置为私有,可以自己控制读写权限
- 对于写权限,我们可以检测数据的有效性
用 set 、get 方法控制权限、操作
对象的初始化和清理
构造函数和析构函数
-
构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
- 没有返回值也不写void
- 函数名称与类名相同
类名(){} - 构造函数可以有参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
-
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
- 没有返回值也不写void
- 函数名称与类名相同,在名称前加上符号
~ - 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
构造函数分类与调用
分类:
- 按参数分为:有参构造和无参构造
- 使用默认无参构造时,不要加括号,否则会认为是函数声明
- 按类型分为:普通构造和拷贝构造
- 不要用拷贝构造函数初始化匿名对象,编译器会认为
Person(p3) === Person p3
- 不要用拷贝构造函数初始化匿名对象,编译器会认为
|
|
调用方式:括号法,显式法,隐式转换法
|
|
|
|
|
|
拷贝构造函数的调用时机
- 使用一个已经创建完毕的对象来初始化一个新对象
- 传递的方式给函数参数传值
- 值方式返回局部对象
- 在函数内部创建的对象被返回时,会创建一个新的对象返回
拷贝构造函数的调用规则
默认情况下,C++编译器至少给一个类添加3个函数:
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 认拷贝构造函数,对属性进行值拷贝
定义有参构造函数,C++ 不再提供默认无参构造,但是会提供默认拷贝构造
定义拷贝构造函数,C++ 不会再提供其他构造函数
深浅拷贝
浅:简单的赋值拷贝
- 默认提供的是浅拷贝
深:在堆内存重新创建一块内存,进行拷贝
- 涉及到申请堆区时,需要深拷贝
初始化列表
|
|
类作为类成员
当类有嵌套时,会先构造内部对象,析构顺序与构造相反
静态成员
静态成员变量:
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
静态成员函数:
- 所有对象共享同一个函数
- 只能访问静态成员变量
- 因为调用函数时,无法定位非静态成员变量(属于特定对象)
可以通过对象,也可以直接通过类名访问,有访问权限
内存模型和 this 指针
内存:
-
类内的成员变量和成员函数分开存储
-
只有非静态成员变量才属于类的对象上
this 指针:
- 指向被调用的成员函数所属的对象,隐含在每一个非静态成员函数内
- 当形参和成员变量同名时,可用this指针来区分
this -> name
- 在类的非静态成员函数中返回对象本身,可使用
return *this- 实现链式编程
|
|
注意返回 Person**&**,值返回会调用拷贝函数创建一个新的对象
空指针访问成员函数
|
|
const 修饰成员函数
常函数:
-
成员函数后加const后我们称为这个函数为常函数
-
常函数内不可以修改成员属性
-
成员属性声明时加关键字mutable后,则在常函数中也可以修改
|
|
常对象:
- 声明对象前加const称该对象为常对象
- 对象只能调用常函数
友元
使一个函数或者类访问另一个类中私有成员,关键字friend
三种实现:
- 局函数做友元
- 类做友元
- 成员函数做友元
friend void clazz::method();
在类中声明 friend,被声明的可以访问本类的私有属性
运算符重载
定义自定义类型的运算方式
成员函数重载:
|
|
左移
只能通过全局函数重载
|
|
递增
|
|
赋值
默认实现为浅拷贝,p1 = p2
|
|
注意:返回引用,可以操作自身;返回值,会拷贝出一个对象。目的是为了实现连等
关系
|
|
函数调用 ()
仿函数
- 非常灵活
|
|
继承
下级别的成员除了拥有上一级的共性,还有自己的特性,抽取出父、子
- 用于减少重复代码
|
|
继承方式

向下压级,私有被隐藏,但还是会继承
继承的对象模型
父类中所有非静态成员属性都会被子类继承下去
构造、析构顺序
子类继承父类后,当创建子类对象,也会调用父类的构造函数
- 先有父,后有子;析构相反
同名成员处理
- 父类加作用域
s.base::func();- 只要子类有同名,父类函数全都被隐藏(重载也不行)
- 子类直接访问
同名静态成员
静态成员和非静态成员出现同名,处理方式一致
多继承
class son: type fa1,type fa2
不建议,父类命名可能重复
菱形继承
一出二,二合一
-
孙类继承了两份父类的相同数据,产生冗余
-
利用虚继承解决菱形继承
|
|
多态
基本概念
多态分为两类
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 动态多态:派生类和虚函数实现运行时多态
- 有派生类时,动态绑定子类重写的虚函数(父类指针指向子类对象)
静态多态和动态多态区别:
- 静态多态的函数地址早绑定-编译阶段确定函数地址
- 动态多态的函数地址晚绑定-运行阶段确定函数地址
|
|
原理:
- 子类重写父类虚函数时:子类中的虚函数表内部会替换成子类的虚函数地址
优点:
- 满足“开闭原则”
扩展对外开放,修改对外关闭
- 组织结构清晰,可读性强
- 可维护性强
纯虚函数和抽象类
virtual 返回值类型 函数名(参数列表)= θ;
当类中有了纯虚函数,这个类也称为抽象类
- 抽象类无法实例化
- 子类必须重写父类的纯虚函数,否则子类也是抽象类
虚析构与纯虚析构
共性:
- 解决父类指针释放子类对象
- 原因:父类指针指向子类对象,delete 的时候只调用父类的析构函数
- 都需要有具体的函数实现
差异:
- 如果是纯虚析构,该类属于抽象类,无法实例化对象
- 虚析构函数需要被实现
若堆中没有数据,可以不写
文件操作
文本文件
文件以 ASCII 码存储
操作文件的三大类:
- ofstream:写操作
- ifstream:读操作
- fstream:读写操作
写文件
步骤:
- 包含头文件
#include <fstream> - 创建流对象
ofstream ofs; - 打开文件
ofs.open("文件路径",打开方式); - 写数据
ofs<<"写入的数据"; - 关闭文件
ofs.close()
二进制
ios::binary