内存四大分区概念简介
- 在 C++ 程序执行时,将内存大方向划分为四个区域
- 代码区:存放函数体的二进制代码,由操作系统进行管理的
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配释放,存在函数的参数值,局部变量等
- 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
- 内存四区意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程
# new 操作符 - 内存分区模型
C++
中利用 new
操作符在堆区开辟数据,堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 delete
, 语法: new 数据类型
,利用 new
创建的数据,会返回该数据对应的类型指针
new操作符 | #include <iostream> |
| |
| using namespace std; |
| |
| int *func() { |
| |
| int *a = new int(10); |
| return a; |
| } |
| |
| int main() { |
| int *p = func(); |
| |
| cout << *p << endl; |
| cout << *p << endl; |
| |
| |
| delete p; |
| |
| |
| |
| |
| int *arr = new int[10]; |
| for (int i = 0; i < 10; i++) { |
| arr[i] = i + 100; |
| } |
| for (int i = 0; i < 10; i++) { |
| cout << arr[i] << endl; |
| } |
| delete[] arr; |
| } |
# 引用
# 引用的基本使用
作用:给变量起别名,语法: 数据类型 &别名 = 原名
引用的基本使用 | |
| * 引用的基本使用 |
| * 作用:给变量起别名 |
| * 语法:数据类型 & 别名 = 原名 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| int main(){ |
| int a = 10; |
| |
| |
| int &b = a; |
| |
| cout << "a:" << a << endl; |
| cout << "b:" << b << endl; |
| |
| b = 20; |
| |
| cout << "a:" << a << endl; |
| cout << "b:" << b << endl; |
| } |
# 引用注意事项
引用必须初始化,引用在初始化后,不可以改变
引用注意事项 | |
| * 引用注意事项 |
| * 引用必须初始化 |
| * 引用在初始化后不可以改变 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| int main() { |
| |
| int a = 10; |
| int b = 20; |
| |
| |
| |
| int &c = a; |
| |
| c = b; |
| |
| cout << "a:" << a << endl; |
| cout << "b:" << b << endl; |
| cout << "c:" << c << endl; |
| } |
# 引用做函数参数
作用:函数传参时,可以利用引用的技术让形参修饰实参
优点:可以简化指针修改实参
引用做函数参数 | |
| * 引用做函数参数 |
| * 作用:函数传参时,可以利用引用的技术让形参修饰实参 |
| * 优点:可以简化指针修改实参 |
| * 总结:通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| void mySwap01(int a, int b) { |
| int temp = a; |
| a = b; |
| b = temp; |
| |
| |
| } |
| |
| |
| void mySwap02(int *a, int *b) { |
| int temp = *a; |
| *a = *b; |
| *b = temp; |
| } |
| |
| |
| void mySwap03(int &a, int &b) { |
| int temp = a; |
| a = b; |
| b = temp; |
| } |
| |
| int main() { |
| |
| int a = 10; |
| int b = 20; |
| |
| mySwap01(a, b); |
| cout << "a= " << a << endl; |
| cout << "b= " << b << endl; |
| |
| cout << endl; |
| |
| |
| mySwap02(&a, &b); |
| cout << "a= " << a << endl; |
| cout << "b= " << b << endl; |
| |
| cout << endl; |
| |
| |
| mySwap03(a, b); |
| cout << "a= " << a << endl; |
| cout << "b= " << b << endl; |
| } |
# 引用做函数返回值
作用:引用是可以作为函数的返回值存在的
注意:不要返回局部变量引用
用法:函数调用作为左值
引用做函数返回值 | |
| * 引用做函数的返回值 |
| * 作用:引用是可以作为函数的返回值存在的 |
| * 注意:不要返回局部变量的引用 |
| * 用法:函数调用作为左值 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| int &test01() { |
| |
| |
| int a = 10; |
| return a; |
| } |
| |
| |
| int &test02() { |
| |
| static int a = 20; |
| return a; |
| } |
| |
| int main() { |
| |
| |
| |
| |
| int &ref2 = test02(); |
| cout << "ref2 = " << ref2 << endl; |
| cout << "ref2 = " << ref2 << endl; |
| |
| |
| test02() = 1000; |
| |
| cout << "ref2 = " << ref2 << endl; |
| cout << "ref2 = " << ref2 << endl; |
| } |
# 引用的本质
本质:引用的本质在 C++
内部实现是应该指针常量
引用的本质 | |
| * 引用的本质 |
| * 本质:引用的本质在 C++ 内部实现是一个指针常量 |
| * 引用的本质就是一个指针常量 |
| * 引用一旦初始化后,就不可以发生改变 |
| * 总结:C++ 推荐用引用技术,因为语法方便,引用本质是指针常量,但是所有的指针操作编译器都帮我们做了 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| void func(int &ref) { |
| ref = 100; |
| } |
| |
| int main() { |
| |
| int a = 10; |
| |
| int &ref = a; |
| ref = 20; |
| cout << "a: " << a << endl; |
| cout << "ref: " << ref << endl; |
| func(a); |
| } |
# 常量引用
作用:常量引用主要用来修饰形参,防止误操作
在函数形参列表中,可以加 const 修饰形参,防止形参改变实参
常量引用 | |
| * 常量引用 |
| * 作用:常量引用主要用来修饰形参,防止误操作 |
| * 在函数形参列表中,可以添加 const 修饰形参,防止形参改变实参 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| void showValue(const int &val) { |
| |
| |
| cout << val << endl; |
| } |
| |
| int main() { |
| |
| |
| |
| |
| |
| |
| |
| const int &ref = 10; |
| |
| |
| |
| int a = 100; |
| showValue(a); |
| } |
# 函数提高
# 函数的默认参数
在 C++
中,函数的形参列表中的形参是可以有默认值的,语法: 返回值类型 函数名(参数=默认值){ }
函数默认参数 | |
| * 函数默认参数 |
| * 在 C++ 中 函数的形参列表中的形参是可以有默认值的 |
| * 语法:返回值类型 函数名称 (参数 = 默认值) |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| |
| int func(int a, int b = 10, int c = 10) { |
| return a + b + c; |
| } |
| |
| |
| |
| |
| int func2(int a = 10, int b = 10); |
| |
| int func2(int a, int b) { |
| return a + b; |
| } |
| |
| |
| |
| |
| |
| |
| |
| int main() { |
| cout << func(10,20,30) << endl; |
| } |
# 函数的占位参数
C++
中函数的形参列表里可以有占位参数,用来做占位使用,被调用函数时必须填补该位置
语法: 返回值类型 函数名 (数据类型){ }
函数占位参数 | |
| * 函数占位参数 |
| * C++ 中函数的形参列表里可以有占位参数,,用来做占位操作,调用函数时必须填补该位置 |
| * 语法:返回值 函数名称 (数据类型){ } |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| void func(int a, int) { |
| cout << "this is func" << endl; |
| } |
| |
| void func1(int a, int = 10) { |
| cout << "this is func" << endl; |
| } |
| |
| |
| int main() { |
| |
| func(10, 20); |
| func1(10); |
| } |
# 函数重载
- 作用:函数名可以相同,提高复用性
- 函数重载需要满足以下条件:
- 同一个作用域下
- 函数名称相同
- 函数参数类型不同或者个数不同或者顺序不同
- 注意:函数的返回值不可以作为函数重载的条件
函数重载 | |
| * 函数重载 - 函数重载概述 |
| * 作用:函数名可以相同,提高复用性 |
| * 函数重载满足条件: |
| * 同一个作用域下 |
| * 函数名称相同 |
| * 函数参数类型不同或者个数不同或者顺序不同 |
| * |
| * 注意:函数的返回值不可以作为函数重载的条件 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| void func() { |
| cout << "func" << endl; |
| } |
| |
| void func(int a) { |
| cout << "func:" << a << endl; |
| } |
| |
| void func(int a, int b) { |
| cout << "func:" << a << " " << b << endl; |
| } |
| |
| int main() { |
| func(); |
| func(10); |
| func(10, 20); |
| } |
# 函数重载注意事项
函数重载注意事项 | |
| * 函数重载注意事项 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| void func(int &a) { |
| cout<< "func (int &a) 调用" << endl; |
| } |
| |
| void func(const int &a) { |
| cout<< "func (const int &a) 调用" << endl; |
| } |
| |
| void func2(int a) { |
| cout << "func2 (int &a) 调用" << endl; |
| } |
| void func2(int a,int b = 10) { |
| cout << "func2 (int &a) 调用" << endl; |
| } |
| |
| int main() { |
| |
| |
| |
| |
| |
| } |
# 类和对象
C++
面向对象的三大特性为:封装、继承、多态, C++
认为万事万物都皆为对象,对象上有其属性和行为
# 封装
封装的意义 | |
| * 封装的意义 |
| * |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| |
| |
| |
| |
| const double PI = 3.14; |
| class Circle{ |
| |
| |
| public: |
| |
| int m_r; |
| [[nodiscard]] double calculateZC() const{ |
| return 2 * PI * m_r; |
| } |
| |
| private: |
| |
| |
| |
| |
| protected: |
| |
| }; |
| |
| int main(){ |
| |
| |
| Circle circle{}; |
| |
| circle.m_r = 10; |
| |
| cout << "圆的周长为:" << circle.calculateZC() << endl; |
| } |
# 设计学生类
案例:设计一个学生类,属性有姓名和学号,可以给姓名和学号进行赋值,可以显示学生和姓名以及学号
设计学生类 | #include <iostream> |
| |
| using namespace std; |
| |
| |
| class Student { |
| public: |
| int id; |
| string name; |
| int getId() const { |
| return id; |
| } |
| |
| void setId(int id) { |
| Student::id = id; |
| } |
| |
| const string &getName() const { |
| return name; |
| } |
| |
| void setName(const string &name) { |
| Student::name = name; |
| } |
| |
| }; |
| |
| int main(){ |
| |
| Student student; |
| student.id = 1; |
| student.name = "李四"; |
| cout << "ID:" << student.getId() << " name:" << student.getName() << endl; |
| |
| Student student2; |
| student2.setId(2); |
| student2.setName("张三"); |
| cout << "ID:" << student2.getId() << " name:" << student2.getName() << endl; |
| } |
# struct 和 class 区别
- 在
C++
中 struct
和 class
的唯一区别就是在于默认的访问权限不同 - 区别:
struct
: 默认权限为公共class
: 默认权限为私有
struct和class区别 | |
| * struct 和 class 区别 |
| * 在 C++ 中 struct 和 class 唯一的区别就在于默认的访问权限不同 |
| * 区别: |
| * struct 默认权限为公共 |
| * class 默认权限为私有 |
| */ |
| class C1{ |
| |
| int m_A; |
| }; |
| |
| struct C2{ |
| |
| int m_A; |
| }; |
# 立方体案例
设计一个立方体 (Cube)
, 要求:求出立方体的面积和体积,并且分别用全局函数和成员函数判断两个立方体是否相等。
立方体案例 | #include <iostream> |
| |
| using namespace std; |
| |
| class Cube { |
| public: |
| |
| |
| |
| |
| void setML(int ml) { |
| this->m_L = ml; |
| } |
| |
| |
| void setMW(int mw) { |
| this->m_W = mw; |
| } |
| |
| |
| void setMH(int mh) { |
| this->m_H = mh; |
| } |
| |
| |
| int getML() const { |
| return m_L; |
| } |
| |
| |
| int getMW() const { |
| return m_W; |
| } |
| |
| |
| int getMH() const { |
| return m_H; |
| } |
| |
| |
| int getArea() { |
| return 2 * m_L * m_W + 2 * m_W * m_H + 2 * m_L * m_H; |
| } |
| |
| |
| int getVolume() { |
| return m_L * m_W * m_H; |
| } |
| |
| private: |
| |
| int m_L; |
| |
| int m_W; |
| |
| int m_H; |
| }; |
| |
| bool isSame(Cube &c1, Cube &c2) { |
| if (c1.getML() == c2.getML() && c1.getMW() == c2.getMW() && c1.getMH() == c2.getMH()) { |
| return true; |
| } |
| return false; |
| } |
| |
| int main() { |
| |
| Cube c1{}; |
| |
| c1.setML(10); |
| c1.setMW(10); |
| c1.setMH(10); |
| |
| cout << "长:" << c1.getML() << " 宽:" << c1.getMW() << " 髙:" << c1.getMH() << endl; |
| |
| cout << "面积:" << c1.getArea() << " 体积:" << c1.getVolume() << endl; |
| |
| Cube c2{}; |
| |
| c2.setML(10); |
| c2.setMW(10); |
| c2.setMH(10); |
| |
| if (isSame(c1, c2)) { |
| cout << "c1和c2是相等的" << endl; |
| } |
| } |
# 点和圆的关系 - 案例
设计一个圆形类 (Circle)
, 和一个点类 (Point)
, 并计算点和圆的关系。
点和圆的关系 | |
| * 点和圆关系判断 |
| * 点到圆心的距离 == 半径 点在园上 |
| * 点到圆心的距离 > 半径 点在园外 |
| * 点到圆心的距离 < 半径 点在圆内 |
| */ |
| #include <iostream> |
| |
| |
| using namespace std; |
| |
| |
| class Point { |
| public: |
| int getMX() const { |
| return m_X; |
| } |
| |
| void setMX(int mX) { |
| m_X = mX; |
| } |
| |
| int getMY() const { |
| return m_Y; |
| } |
| |
| void setMY(int mY) { |
| m_Y = mY; |
| } |
| |
| private: |
| int m_X; |
| int m_Y; |
| }; |
| |
| |
| class Circle { |
| public: |
| int getMR() const { |
| return m_R; |
| } |
| |
| void setMR(int mR) { |
| m_R = mR; |
| } |
| |
| const Point &getMCenter() const { |
| return m_Center; |
| } |
| |
| void setMCenter(const Point &mCenter) { |
| m_Center = mCenter; |
| } |
| |
| private: |
| int m_R; |
| Point m_Center; |
| }; |
| |
| |
| void isInCircle(Circle &c, Point &p) { |
| |
| int distance = (c.getMCenter().getMX() - p.getMX()) * (c.getMCenter().getMX() - p.getMX()) + |
| (c.getMCenter().getMY() - p.getMY()) * (c.getMCenter().getMY() - p.getMY()); |
| |
| int rDistance = c.getMR() * c.getMR(); |
| |
| if (distance == rDistance) { |
| cout << "点在圆上" << endl; |
| } else if (distance > rDistance) { |
| cout << "点在圆外" << endl; |
| } else { |
| cout << "点在圆内" << endl; |
| } |
| } |
| |
| int main() { |
| |
| Circle c; |
| c.setMR(10); |
| Point center; |
| center.setMX(10); |
| center.setMY(0); |
| c.setMCenter(center); |
| |
| Point p; |
| p.setMX(10); |
| p.setMY(12); |
| isInCircle(c, p); |
| } |
以下是拆分设计
point.h | #pragma once |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| class Point { |
| public: |
| [[nodiscard]] int getMX() const; |
| |
| void setMX(int mX); |
| |
| [[nodiscard]] int getMY() const; |
| |
| void setMY(int mY); |
| |
| private: |
| int m_X; |
| int m_Y; |
| }; |
point.h | #include "point.h" |
| |
| int Point::getMX() const { |
| return m_X; |
| } |
| |
| void Point::setMX(int mX) { |
| m_X = mX; |
| } |
| |
| int Point::getMY() const { |
| return m_Y; |
| } |
| |
| void Point::setMY(int mY) { |
| m_Y = mY; |
| } |
circle.h | #include <iostream> |
| #include "point.h" |
| |
| using namespace std; |
| |
| |
| class Circle { |
| public: |
| [[nodiscard]] int getMR() const; |
| |
| void setMR(int mR); |
| |
| [[nodiscard]] const Point getMCenter() const; |
| |
| void setMCenter(const Point &mCenter); |
| |
| private: |
| int m_R; |
| Point m_Center; |
| }; |
circle.cpp | #include "circle.h" |
| |
| int Circle::getMR() const { |
| return m_R; |
| } |
| |
| void Circle::setMR(int mR) { |
| m_R = mR; |
| } |
| |
| const Point Circle::getMCenter() const { |
| return m_Center; |
| } |
| |
| void Circle::setMCenter(const Point &mCenter) { |
| m_Center = mCenter; |
| } |
点和圆的关系.cpp | |
| * 点和圆关系判断 |
| * 点到圆心的距离 == 半径 点在园上 |
| * 点到圆心的距离 > 半径 点在园外 |
| * 点到圆心的距离 < 半径 点在圆内 |
| */ |
| #include <iostream> |
| #include "point.h" |
| #include "circle.h" |
| using namespace std; |
| |
| |
| void isInCircle(Circle &c, Point &p) { |
| |
| int distance = (c.getMCenter().getMX() - p.getMX()) * (c.getMCenter().getMX() - p.getMX()) + |
| (c.getMCenter().getMY() - p.getMY()) * (c.getMCenter().getMY() - p.getMY()); |
| |
| int rDistance = c.getMR() * c.getMR(); |
| |
| if (distance == rDistance) { |
| cout << "点在圆上" << endl; |
| } else if (distance > rDistance) { |
| cout << "点在圆外" << endl; |
| } else { |
| cout << "点在圆内" << endl; |
| } |
| } |
| |
| int main() { |
| |
| Circle c; |
| c.setMR(10); |
| Point center; |
| center.setMX(10); |
| center.setMY(0); |
| c.setMCenter(center); |
| |
| Point p; |
| p.setMX(10); |
| p.setMY(12); |
| isInCircle(c, p); |
| } |
# 构造函数和析构函数
对象的初始化和清理也是两个非常重要的安全问题,一个对象或者变量没有初始状态,对其使用后果是未知同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题,C++ 利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供,编译器提供的构造函数和析构函数是空实现。构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。
| |
| * 构造函数和析构函数 |
| * 对象的初始化和清理也是两个非常重要的安全问题 |
| * 一个对象或者变量没有初始状态,对其使用后果是未知 |
| * 同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题 |
| * C++ 利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。 |
| * 对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供 |
| * 编译器提供的构造函数和析构函数是空实现。 |
| * 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。 |
| * 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| 构造函数语法:类名 (){} |
| 1. 构造函数,没有返回值也不写 void |
| 2. 函数名称与类名相同 |
| 3. 构造函数可以有参数,因此可以发生重载 |
| 4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次 |
| */ |
| |
| |
| 析构函数语法:类名 (){} |
| 1. 析构函数,没有返回值也不写 void |
| 2. 函数名称与类名相司,在名称前加上符号~ |
| 3. 析构函数不可以有参数,因此不可以发生重载 |
| 4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次 |
| */ |
| |
| |
| class Person{ |
| public: |
| |
| Person() { |
| cout << "Person的构造函数被调用" << endl; |
| } |
| |
| ~Person() { |
| cout << "Person的析构函数被调用" << endl; |
| } |
| }; |
| |
| void test01(){ |
| Person p; |
| } |
| |
| int main(){ |
| test01(); |
| } |
# 构造函数的分类以及调用
- 构造函数的分离及调用有两种分类方式:
- 按参数分为:有参构造和无参构造
- 按类型分为:普通构造和拷贝构造
- 三种调用方式:
构造函数的分离及调用 | |
| * 构造函数的分离及调用 |
| * 两种分类方式: |
| * 按参数分为:有参构造和无参构造 |
| * 按类型分为:普通构造和拷贝构造 |
| * 三种调用方式: |
| * 括号法 |
| * 显示法 |
| * 隐式转换法 |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Person { |
| public: |
| Person() { |
| cout << "Person的无参构造函数被调用" << endl; |
| } |
| |
| Person(int a) { |
| age = a; |
| cout << "Person的有参构造函数被调用" << endl; |
| } |
| |
| |
| Person(const Person &p) { |
| |
| age = p.age; |
| cout << "Person的拷贝构造函数被调用" << endl; |
| } |
| |
| virtual ~Person() { |
| cout << "Person的析构函数被调用" << endl; |
| } |
| |
| int age; |
| }; |
| |
| void test01() { |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| Person p1; |
| Person p2 = Person(10); |
| Person p3 = Person(p2); |
| |
| |
| |
| |
| |
| |
| |
| |
| Person p4 = 10; |
| Person p5 = p4; |
| } |
| |
| int main() { |
| test01(); |
| } |
# 拷贝构造函数调用时机
- C++ 中拷贝构造函数调用时机通常有三种情况
- 1. 使用应该已经创建完毕的对象来初始化应该新对象
- 2. 值传递的方式给函数参数传值
- 3. 以值方式返回局部对象
拷贝构造函数调用时机 | |
| * 拷贝构造函数调用时机 |
| * C++ 中拷贝构造函数调用时机通常有三种情况 |
| * 1. 使用应该已经创建完毕的对象来初始化应该新对象 |
| * 2. 值传递的方式给函数参数传值 |
| * 3. 以值方式返回局部对象 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Person { |
| |
| public: |
| Person() { |
| cout << "Person无参构造函数调用" << endl; |
| mAge = 0; |
| } |
| |
| Person(int age) { |
| cout << "Person有参构造函数调用" << endl; |
| mAge = age; |
| } |
| |
| Person(const Person &p) { |
| cout << "Person拷贝构造函数调用" << endl; |
| mAge = p.mAge; |
| } |
| |
| ~Person() { |
| cout << "Person析构造函数调用" << endl; |
| } |
| |
| int mAge; |
| }; |
| |
| |
| void test01() { |
| Person p1(20); |
| Person p2(p1); |
| cout << "P2的年龄:" << p2.mAge << endl; |
| } |
| |
| |
| void doWork(Person p) { |
| |
| } |
| |
| void test02() { |
| Person p; |
| doWork(p); |
| } |
| |
| |
| Person doWork2() { |
| Person p1; |
| cout << (int*) &p1 << endl; |
| return Person(p1); |
| } |
| |
| void test03() { |
| Person p = doWork2(); |
| cout << (int*) &p << endl; |
| } |
| |
| int main() { |
| |
| |
| test03(); |
| } |
# 构造函数调用规则
- 默认情况下 C++ 编译器至少给一个类添加三个函数
- 默认构造函数 (无参,函数体为空)
- 默认析构函数 (无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 构造函数调用规则如下:
- 如果用户定义有参构造函数 C++ 不在提供默认无参构造,弹会提供默认拷贝构造
- 如果用户定义拷贝构造函数 C++ 不会在提供其他构造函数
- 总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
构造函数调用规则 | |
| * 构造函数调用规则 |
| * 默认情况下 C++ 编译器至少给一个类添加三个函数 |
| * 1. 默认构造函数 (无参,函数体为空) |
| * 2. 默认析构函数 (无参,函数体为空) |
| * 3. 默认拷贝构造函数,对属性进行值拷贝 |
| * |
| * 构造函数调用规则如下: |
| * 如果用户定义有参构造函数 C++ 不在提供默认无参构造,弹会提供默认拷贝构造 |
| * 如果用户定义拷贝构造函数 C++ 不会在提供其他构造函数 |
| * 总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Person { |
| public: |
| |
| Person() { |
| cout << "无参构造函数的调用" << endl; |
| } |
| |
| |
| Person(int a) { |
| age = a; |
| cout << "有参构造函数的调用" << endl; |
| } |
| |
| Person(const Person &p) { |
| cout << "拷贝构造函数的调用" << endl; |
| age = p.age; |
| |
| } |
| |
| |
| ~Person() { |
| cout << "析构函数的调用" << endl; |
| } |
| |
| int age; |
| }; |
| |
| void test01() { |
| Person p; |
| p.age = 18; |
| Person p2(p); |
| |
| cout << "p2的年龄为:" << p2.age << endl; |
| |
| } |
| |
| int main() { |
| test01(); |
| } |
# 深拷贝与浅拷贝
深浅拷贝是面试经典问题,也是常见的一个坑,浅拷贝:简单的赋值拷贝操作,深拷贝:在堆区重新申请空间,进行拷贝操作
# 初始化列表
作用: C++
提供了初始化列表语法,用来初始化属性,语法: 构造函数(): 属性1(值1),属性2(值2)....{ }
初始化列表 | |
| * 初始化列表 |
| * 作用:C++ 提供了初始化列表语法,用来初始化属性 |
| * 语法:构造函数 (): 属性 1 (值 1), 属性 2 (值 2)...{ } |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Person { |
| public: |
| |
| |
| |
| |
| |
| |
| |
| |
| Person() : m_A(10), m_B(20), m_C(30) { |
| |
| } |
| |
| Person(int a,int b,int c) : m_A(a), m_B(b), m_C(c) { |
| |
| } |
| |
| int m_A; |
| int m_B; |
| int m_C; |
| }; |
| |
| void test01() { |
| Person p(10,20,30); |
| cout << "A = " << p.m_A << endl; |
| cout << "B = " << p.m_B << endl; |
| cout << "C = " << p.m_C << endl; |
| } |
| |
| int main() { |
| test01(); |
| } |
# 类对象作为类成员
C++
类中的成员是可以另一个类的对象,我们称该成员为对象成员
类对象作为类成员 | |
| * 类对象作为类成员 |
| * C++ 类中的成员是可以另一个类的对象,我们称该成员为对象成员 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| class A{ |
| |
| public: |
| A() { |
| cout << "A class构造函数被调用" << endl; |
| } |
| |
| virtual ~A() { |
| cout << "A class析构函数被调用" << endl; |
| |
| } |
| |
| int a; |
| }; |
| |
| class B{ |
| public: |
| B() { |
| cout << "B class构造函数被调用" << endl; |
| } |
| |
| virtual ~B() { |
| cout << "B class析构函数被调用" << endl; |
| } |
| |
| A a; |
| }; |
| |
| void test01(){ |
| B b; |
| } |
| |
| int main(){ |
| test01(); |
| |
| * 调用结果: |
| * A class 构造函数被调用 |
| * B class 构造函数被调用 |
| * B class 析构函数被调用 |
| * A class 析构函数被调用 |
| */ |
| } |
# 静态成员
静态成员就是在成员变量和成员函数前加上关键字 static
, 称为静态成员。
- 静态成员分为:
- 静态成员变量:
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
- 静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
静态成员变量.cpp | |
| * 静态成员变量 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Person { |
| public: |
| |
| |
| |
| static int m_A; |
| private: |
| static int m_B; |
| }; |
| |
| int Person::m_A = 100; |
| int Person::m_B = 200; |
| |
| void test01() { |
| Person p; |
| cout << p.m_A << endl; |
| Person p2; |
| p2.m_A = 200; |
| cout << p.m_A << endl; |
| } |
| |
| void test02() { |
| |
| |
| |
| |
| |
| |
| |
| cout << Person::m_A << endl; |
| |
| |
| } |
| |
| int main() { |
| test02(); |
| |
| |
| } |
静态成员函数.cpp | |
| * 静态成员函数 |
| * 所有对象共享同一个函数 |
| * 静态成员函数只能访问静态成员变量 |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Person { |
| public: |
| static void func() { |
| m_A = 100; |
| |
| cout << "static void func调用" << endl; |
| } |
| |
| static int m_A; |
| |
| int m_B; |
| |
| |
| private: |
| static void func2() { |
| cout << "static void func2调用" << endl; |
| } |
| }; |
| |
| int Person::m_A = 0; |
| |
| void test01(){ |
| |
| Person p; |
| p.func(); |
| |
| Person::func(); |
| } |
| |
| int main() { |
| test01(); |
| } |
# C++ 对象模型和 this 指针
# 成员变量和成员函数分开存储
在 C++
中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上
# this 指针概念
this指针概念
this
指针是隐含每一个非静态成员函数内的一种指针,this 指针不需要定义,直接使用即可。
this
指针的用途: 当形参和成员变量同名时,我们就可以使用 this 指针来进行区分,在类的非静态成员函数中返回对象本身,可使用 return *this
。
this指针概念.cpp | |
| * this 指针概念 |
| * this 指针是隐含每一个非静态成员函数内的一种指针 |
| * this 指针不需要定义,直接使用即可 |
| * |
| * this 指针的用途: |
| * 当形参和成员变量同名时,我们就可以使用 this 指针来进行区分 |
| * 在类的非静态成员函数中返回对象本身,,可使用 return *this |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Person{ |
| public: |
| |
| Person(int age){ |
| |
| this->age = age; |
| } |
| |
| Person& PersonAddAge(Person &p){ |
| this->age +=p.age; |
| return *this; |
| } |
| |
| int age; |
| }; |
| |
| |
| void test01(){ |
| Person p1(18); |
| cout << "p1的年龄为:" << p1.age <<endl; |
| } |
| |
| |
| void test02(){ |
| Person p1(10); |
| Person p2(10); |
| |
| p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p2); |
| cout << "p2的年龄为:" << p2.age << endl; |
| } |
| |
| int main(){ |
| test02(); |
| } |
# 空指针访问成员函数
C++
中空指针也是可以调用成员函数的,但是也要注意有没有用到 this
指针,如果用到 this
指针,需要加以判断保证代码的健壮性。
控制在访问成员函数.cpp | |
| * 控制在访问成员函数 |
| * C++ 中空指针也是可以调用成员函数的,但是要注意有没有使用到 this 指针 |
| * 如果有使用到 this 指针,就需要加以判断保证代码的健壮性 |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| class Person{ |
| public: |
| void showClassName(){ |
| cout << "this is person class" << endl; |
| } |
| |
| void showPersonAge(){ |
| if (this ==NULL){ |
| return; |
| } |
| |
| cout << "age = " << this->age << endl; |
| } |
| int age; |
| }; |
| |
| void test01(){ |
| Person *p =nullptr; |
| p->showClassName(); |
| p->showPersonAge(); |
| } |
| |
| int main(){ |
| test01(); |
| } |
# const 修饰成员函数
- 常函数:
- 成员函数后加
const
后我们称为这个函数为常函数 - 常函数内不可以修改成员属性
- 成员属性声明时加上
mutable
关键字后,在常函数中依然可以修改
- 常对象:
- 声明对象前加上
const
称为该对象为常对象 - 常对象只能调用常函数
const修饰成员函数.cpp | |
| * const 修饰成员函数 |
| * 常函数: |
| * 成员函数后加 const 后我们称为这个函数为常函数 |
| * 常函数内不可以修改成员属性 |
| * 成员属性声明时加上 mutable 关键字后,在常函数中依然可以修改 |
| * |
| * 常对象: |
| * 声明对象前加上 const 称为该对象为常对象 |
| * 常对象只能调用常函数 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| class Person { |
| |
| public: |
| |
| void func() { |
| m_B = 200; |
| cout << "m_B的值为:" << m_B << endl; |
| } |
| |
| |
| |
| |
| void showPerson() const { |
| m_B = 100; |
| |
| |
| } |
| |
| int m_A; |
| mutable int m_B; |
| }; |
| |
| void test01() { |
| Person p1; |
| p1.showPerson(); |
| } |
| |
| |
| void test02() { |
| const Person p{}; |
| |
| p.m_B = 100; |
| |
| |
| p.showPerson(); |
| |
| |
| Person p2{}; |
| p2.func(); |
| } |
| |
| int main() { |
| test02(); |
| } |
# 友元
在生活中你的家里有客厅 (public)
有卧室 (private)
, 客厅所有来的客人都可以进去,但是你的卧室是私有的,也加上说只有你自己可以进去。 但是,你也可以允许你的好朋友进去,在程序中,有些是私有属性,也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术了,友元的目的是让一个函数或者类访问另应该类中私有的成员。 友元的关键字为: friend
。
友元-全局函数做友元.cpp | |
| * 友元 |
| * 在生活中你的家里有客厅 (public) 有卧室 (private) |
| * 客厅所有来的客人都可以进去,但是你的卧室是私有的,也加上说只有你自己可以进去 |
| * 但是,你也可以允许你的好朋友进去 |
| * |
| * 在程序中,有些是私有属性,也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术了 |
| * |
| * 友元的目的是让一个函数或者类访问另应该类中私有的成员 |
| * |
| * 友元的关键字为:friend |
| * |
| * 友元的三种实现: |
| * 全局函数做友元 |
| * 类做友元 |
| * 成员函数做友元 |
| */ |
| |
| |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| class Building { |
| |
| |
| |
| friend void goodGay(Building &building); |
| |
| public: |
| Building() { |
| m_SittingRoom = "客厅"; |
| m_BedRoom = "卧室"; |
| } |
| |
| public: |
| string m_SittingRoom; |
| private: |
| string m_BedRoom; |
| }; |
| |
| |
| void goodGay(Building &building) { |
| cout << "好基友全局函数 正在访问:" << building.m_SittingRoom << endl; |
| cout << "好基友全局函数 正在访问:" << building.m_BedRoom << endl; |
| } |
| |
| void test01() { |
| Building building; |
| goodGay(building); |
| } |
| |
| int main() { |
| test01(); |
| } |
友元-类做友元.cpp | |
| * 类做友元 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Building; |
| |
| class GoodGay { |
| |
| public: |
| GoodGay(); |
| |
| void visit(); |
| |
| Building *building; |
| |
| }; |
| |
| class Building { |
| |
| |
| |
| friend class GoodGay; |
| |
| public: |
| Building(); |
| |
| public: |
| string m_SittingRoom; |
| private: |
| string m_BedRoom; |
| }; |
| |
| |
| Building::Building() { |
| m_SittingRoom = "客厅"; |
| m_BedRoom = "卧室"; |
| |
| }; |
| |
| GoodGay::GoodGay() { |
| building = new Building; |
| } |
| |
| void GoodGay::visit() { |
| cout << "GoodGay正在访问:" << building->m_SittingRoom << endl; |
| cout << "GoodGay正在访问:" << building->m_BedRoom << endl; |
| } |
| |
| void test01() { |
| GoodGay g; |
| g.visit(); |
| } |
| |
| int main() { |
| test01(); |
| } |
友元-成员函数做友元.cpp | |
| * 成员函数做友元 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Building; |
| |
| class GoodGay { |
| public: |
| GoodGay(); |
| |
| |
| void visit(); |
| |
| |
| void visit2(); |
| |
| Building *building; |
| }; |
| |
| class Building { |
| |
| friend void GoodGay::visit(); |
| |
| public: |
| Building(); |
| |
| public: |
| |
| string m_SittingRoom; |
| private: |
| |
| string m_BedRoom; |
| }; |
| |
| |
| |
| GoodGay::GoodGay() { |
| |
| building = new Building; |
| } |
| |
| void GoodGay::visit() { |
| cout << "GoodGay.visit()函数正在访问" << building->m_SittingRoom << endl; |
| cout << "GoodGay.visit()函数正在访问" << building->m_BedRoom << endl; |
| } |
| |
| void GoodGay::visit2() { |
| cout << "GoodGay.visit2()函数正在访问" << building->m_SittingRoom << endl; |
| |
| } |
| |
| Building::Building() { |
| m_SittingRoom = "客厅"; |
| m_BedRoom = "卧室"; |
| } |
| |
| void test01() { |
| GoodGay g; |
| g.visit(); |
| g.visit2(); |
| } |
| |
| |
| int main() { |
| test01(); |
| } |
# 运算符重载
运算符重载概念:对已有的运算符重载进行定义,赋予其另一种功能,以适应不同的数据类型。
# 加号运算符重载
作用:实现两个自定义数据类型相加的运算,请勿滥用运算符重载,如:加法运算重载成减法运算。
加号运算符重载.cpp | |
| * 加号运算符重载 |
| * 作用:实现两个自定义数据类型相加的运算 |
| * 总结 1:对于内置的数据类型的表达式的运算符是不可以改变的,如:1+1 = 0 |
| * 总结 2:不要滥用运算符重载 |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Person { |
| public: |
| |
| |
| |
| |
| |
| |
| |
| |
| int m_A; |
| int m_B; |
| }; |
| |
| |
| Person operator+(Person &p1, Person &p2) { |
| Person temp; |
| temp.m_A = p1.m_A + p2.m_A; |
| temp.m_B = p1.m_B + p2.m_B; |
| return temp; |
| } |
| |
| Person operator+(Person &p1, int nuber) { |
| Person temp; |
| temp.m_A = p1.m_A + nuber; |
| temp.m_B = p1.m_B + nuber; |
| return temp; |
| } |
| |
| void test01() { |
| Person p1; |
| p1.m_A = 10; |
| p1.m_B = 10; |
| Person p2; |
| p2.m_A = 10; |
| p2.m_B = 100; |
| |
| |
| |
| |
| |
| |
| |
| Person p3 = p1 + p2; |
| |
| cout << "p3.m_A = " << p3.m_A << endl; |
| cout << "p3.m_B = " << p3.m_B << endl; |
| |
| |
| |
| Person p4 = p1 + 20; |
| |
| cout << "p4.m_A = " << p4.m_A << endl; |
| cout << "p4.m_B = " << p4.m_B << endl; |
| |
| } |
| |
| int main() { |
| test01(); |
| } |
# 左移运算符重载
作用:可以输出自定义数据类型。
左移运算符重载.cpp | |
| * 左移运算符重载 |
| * 作用:可以输出自定义数据类型 |
| * 总结:重载左移运算符配合友元可以实现输出自定义数据类型 |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Person { |
| friend ostream &operator<<(ostream &cout, Person &p); |
| |
| public: |
| Person(int a, int b) { |
| this->m_A = a; |
| this->m_B = b; |
| } |
| |
| private: |
| int m_A; |
| int m_B; |
| }; |
| |
| |
| ostream &operator<<(ostream &cout, Person &p) { |
| cout << "m_A = " << p.m_A << " m_B = " << p.m_B; |
| return cout; |
| } |
| |
| |
| void test01() { |
| Person p(10,10); |
| cout << p << endl; |
| } |
| |
| |
| int main() { |
| test01(); |
| } |
# 递增运算符重载
作用:通过重载递增运算符,实现对自己的整型数据进行自增。
递增运算符重载.cpp | |
| * 递增运算符重载 |
| * 作用:通过重载递增运算符,实现自己的整型数据 |
| * 总结:前置递增返回引用,后置递增返回值 |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| class MyInteger { |
| friend ostream &operator<<(ostream &cout, MyInteger myint); |
| |
| public: |
| |
| |
| |
| MyInteger &operator++() { |
| |
| m_Num++; |
| |
| return *this; |
| } |
| |
| |
| |
| MyInteger operator++(int) { |
| |
| MyInteger temp = *this; |
| |
| m_Num++; |
| |
| return temp; |
| } |
| |
| MyInteger() { |
| m_Num = 0; |
| } |
| |
| private: |
| int m_Num; |
| }; |
| |
| |
| ostream &operator<<(ostream &cout, MyInteger myint) { |
| cout << myint.m_Num; |
| return cout; |
| } |
| |
| void test01() { |
| MyInteger myInteger; |
| cout << ++(++myInteger) << endl; |
| cout << myInteger << endl; |
| } |
| |
| void test02() { |
| MyInteger myInteger; |
| cout << myInteger++ << endl; |
| cout << myInteger++ << endl; |
| cout << myInteger++ << endl; |
| cout << myInteger++ << endl; |
| } |
| |
| int main() { |
| test02(); |
| } |
# 赋值运算符重载
C++
编译器至少给一个类添加四个函数- 默认构造函数 (无参,函数体为空)
- 默认析构函数 (无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符
operator=
, 对属性进行值拷贝
如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝的问题。
赋值运算符重载.cpp | |
| * 赋值运算符重载 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Person { |
| public: |
| Person(int age) { |
| m_Age = new int(age); |
| } |
| |
| ~Person() { |
| if (m_Age != NULL) { |
| delete m_Age; |
| m_Age = NULL; |
| } |
| } |
| |
| |
| Person &operator=(Person &p) { |
| |
| |
| |
| if (m_Age != NULL) { |
| delete m_Age; |
| m_Age = NULL; |
| } |
| |
| |
| m_Age = new int(*p.m_Age); |
| |
| |
| return *this; |
| } |
| |
| |
| int *m_Age; |
| }; |
| |
| void test01() { |
| Person p1(18); |
| Person p2(20); |
| Person p3(30); |
| p3 = p2 = p1; |
| cout << "p1的年龄为:" << *p1.m_Age << endl; |
| cout << "p2的年龄为:" << *p2.m_Age << endl; |
| cout << "p2的年龄为:" << *p3.m_Age << endl; |
| } |
| |
| int main() { |
| test01(); |
| } |
# 关系运算符重载
作用:重载关系运算符,可以让两个自定义类型对象进行对比操作。
关系运算符重载.cpp | |
| * 关系运算符重载 |
| * 作用:重载关系运算符,可以让两个自定义类型对象进行对比操作 |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Person { |
| public: |
| Person(string name, int age) { |
| this->name = name; |
| this->age = age; |
| } |
| |
| |
| bool operator==(Person &p) { |
| if (this->name == p.name && this->age == p.age) { |
| return true; |
| } |
| return false; |
| } |
| |
| bool operator!=(Person &p) { |
| if (this->name == p.name && this->age == p.age) { |
| return false; |
| } |
| return true; |
| } |
| |
| int age; |
| string name; |
| }; |
| |
| void test01() { |
| Person p1("Tom2", 18); |
| Person p2("Tom", 18); |
| if (p1 == p2) { |
| cout << "p1 和 p2是相等的!" << endl; |
| } else { |
| cout << "p1 和 p2是不相等的!" << endl; |
| } |
| |
| if (p1 != p2) { |
| cout << "p1 和 p2是不相等的!" << endl; |
| } else { |
| cout << "p1 和 p2是相等的!" << endl; |
| } |
| |
| } |
| |
| int main() { |
| test01(); |
| } |
# 函数调用运算符重载
函数调用运算符 ()
也是可以重载,由于重载后使用的方式非常像函数的调用,因此称为仿函数,仿函数没有固定的写法,因此非常灵活。
函数调用运算符重载.cpp | |
| * 函数调用运算符重载 |
| * 函数调用运算符 "()" 小括号也可以重载 |
| * 由于重载后使用的方式非常像函数的调用,因此称为仿函数 |
| * 仿函数没有固定写法,非常灵活 |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| class MyPrint { |
| public: |
| |
| |
| void operator()(string test) { |
| cout << test << endl; |
| } |
| |
| }; |
| |
| void test01() { |
| MyPrint myPrint; |
| |
| myPrint("hello"); |
| } |
| |
| |
| class MyAdd { |
| public: |
| int operator()(int num1, int num2) { |
| return num1 + num2; |
| } |
| }; |
| |
| void test02() { |
| MyAdd add; |
| int i = add(100, 100); |
| cout << "i:" << i << endl; |
| |
| cout << MyAdd()(200, 200) << endl; |
| } |
| |
| int main() { |
| test01(); |
| test02(); |
| } |
# 继承
继承是面向对象三大特性之一,继承的基本语法: class 子类类名:访问权限 父类类名
# 继承方式
- 继承的语法:
class 子类:继承方式 父类
- 继承方式一共有三种:
继承方式.cpp | |
| * 继承方式 |
| * 继承语法:class 子类:继承方式 父类 |
| * 继承方式一共有三种: |
| * 公共继承 |
| * 保护继承 |
| * 私有继承 |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| |
| class Base1 { |
| public: |
| int m_A; |
| protected: |
| int m_B; |
| private: |
| int m_C; |
| }; |
| |
| class Son1 : public Base1 { |
| public: |
| void func() { |
| |
| m_A = 10; |
| |
| m_B = 10; |
| |
| cout << "Son1 func() m_A:" << m_A << endl; |
| cout << "Son1 func() m_B:" << m_B << endl; |
| } |
| }; |
| |
| void test01() { |
| Son1 son1; |
| son1.m_A = 25; |
| son1.func(); |
| } |
| |
| |
| class Base2 { |
| public: |
| int m_A; |
| protected: |
| int m_B; |
| private: |
| int m_C; |
| }; |
| |
| class Son2 : protected Base2 { |
| public: |
| void func() { |
| |
| m_A = 100; |
| |
| m_B = 100; |
| |
| |
| cout << "Son2 func() m_A:" << m_A << endl; |
| cout << "Son2 func() m_B:" << m_B << endl; |
| } |
| }; |
| |
| void test02() { |
| Son2 son2; |
| son2.func(); |
| } |
| |
| |
| class Base3 { |
| public: |
| int m_A; |
| protected: |
| int m_B; |
| private: |
| int m_C; |
| }; |
| |
| class Son3 : private Base3 { |
| public: |
| void func() { |
| m_A = 1000; |
| m_B = 1000; |
| |
| cout << "Son3 func() m_A:" << m_A << endl; |
| cout << "Son3 func() m_B:" << m_B << endl; |
| } |
| }; |
| |
| void test03() { |
| Son3 son3; |
| son3.func(); |
| } |
| |
| int main() { |
| test01(); |
| test02(); |
| test03(); |
| } |
# 继承中的对象模型
问题:从父类继承过来的成员,,哪些属于子类对象中?
| |
| * 继承中的对象模型 |
| * 总结:父类中私有成员也是被子类继承下去了,只是由编译器给隐藏后访问不到 |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Base { |
| public: |
| int m_A; |
| protected: |
| int m_B; |
| private: |
| int m_C; |
| }; |
| |
| class Son : public Base { |
| public: |
| int m_D; |
| }; |
| |
| void test01() { |
| |
| |
| |
| cout << "size of Son = " << sizeof(Son) << endl; |
| } |
| |
| int main() { |
| test01(); |
| } |
| |
| * D:\CLionProjects\Demo\ 类和对象 \ 继承 > c1 /d1 reportSingleClassLayoutSon 继承中的对象模型.cpp |
| 'c1' 不是内部或外部命令,也不是可运行的程序 |
| 或批处理文件。 |
| |
| D:\CLionProjects\Demo\ 类和对象 \ 继承 > cl /d1 reportSingleClassLayoutSon 继承中的对象模型.cpp |
| 用于 x86 的 Microsoft (R) C/C++ 优化编译器 19.44.35209 版 |
| 版权所有 (C) Microsoft Corporation。保留所有权利。 |
| |
| 继承中的对象模型.cpp |
| |
| class Son size (16): |
| +--- |
| 0 | +--- (base class Base) |
| 0 | | m_A |
| 4 | | m_B |
| 8 | | m_C |
| | +--- |
| 12 | m_D |
| +--- |
| D:\Software\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\__msvc_ostream.hpp (781): warning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc |
| D:\Software\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\__msvc_ostream.hpp (781): note: 模板实例化上下文 (最早的实例化上下文) 为 |
| 继承中的对象模型.cpp (31): note: 查看对正在编译的函数 模板 实例化 “std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,const char *)” 的引用 |
| Microsoft (R) Incremental Linker Version 14.44.35209.0 |
| Copyright (C) Microsoft Corporation. All rights reserved. |
| |
| /out: 继承中的对象模型.exe |
| 继承中的对象模型.obj |
| |
| D:\CLionProjects\Demo\ 类和对象 \ 继承 > |
| */ |
另外可以使用 Vs 来查看继承类中的属性是否被一同继承了没有,使用 Vs 开发终端,输入: cl /d1 reportSingleClassLayout(类名) 文件名
例如: cl /d1 reportSingleClassLayoutSon 继承中的对象模型.cpp
查看Son类的底层继承属性 | | D:\CLionProjects\Demo\类和对象\继承>cl /d1 reportSingleClassLayoutSon 继承中的对象模型.cpp |
| | 用于 x86 的 Microsoft (R) C/C++ 优化编译器 19.44.35209 版 |
| | 版权所有(C) Microsoft Corporation。保留所有权利。 |
| | |
| | 继承中的对象模型.cpp |
| | |
| | class Son size(16): |
| | +--- |
| | 0 | +--- (base class Base) |
| | 0 | | m_A |
| | 4 | | m_B |
| | 8 | | m_C |
| | | +--- |
| | 12 | m_D |
| | +--- |
| | D:\Software\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\__msvc_ostream.hpp(781): warning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc |
| | D:\Software\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\__msvc_ostream.hpp(781): note: 模板实例化上下文(最早的实例化上下文)为 |
| | 继承中的对象模型.cpp(31): note: 查看对正在编译的函数 模板 实例化“std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,const char *)”的引用 |
| | Microsoft (R) Incremental Linker Version 14.44.35209.0 |
| | Copyright (C) Microsoft Corporation. All rights reserved. |
| | |
| | /out:继承中的对象模型.exe |
| | 继承中的对象模型.obj |
| | |
| | D:\CLionProjects\Demo\类和对象\继承> |
# 继承中构造和析构顺序
在子类继承父类后,当创建子类对象,也会调用父类的构造函数,问题:父类和子类的构造和析构顺序是谁先谁后???
继承中构造和析构顺序.cpp | |
| * 继承中构造和析构顺序 |
| * 子类继承父类后当创建子类对象,也会调用父类的构造函数 |
| * 问题:父类和子类的构造和析构顺序是谁先谁后? |
| * 结论:父有 -> 子有 -> 子无 -> 父无 |
| * 总结:继承中先调用父类构造函数,再调用子类构造函数,析构顺序与构造调用则相反 |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Base { |
| public: |
| Base() { |
| cout << "Base构造函数!" << endl; |
| } |
| |
| virtual ~Base() { |
| cout << "Base析构函数!" << endl; |
| } |
| }; |
| |
| class Son : public Base { |
| public: |
| Son() { |
| cout << "Son构造函数!" << endl; |
| } |
| |
| ~Son() override { |
| cout << "Son析构函数!" << endl; |
| } |
| }; |
| |
| void test01(){ |
| |
| |
| Son son; |
| } |
| |
| int main() { |
| test01(); |
| |
| * 执行结果: |
| * Base 构造函数! |
| * Son 构造函数! |
| * Son 析构函数! |
| * Base 析构函数! |
| */ |
| } |
# 继承同名成员处理方式
问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据吗?访问子类同名成员,直接访问即可,访问父类同名成员,需要加作用域
继承同名成员处理方式.cpp | |
| * 继承同名成员处理方式 |
| * 问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据吗? |
| * 访问子类同名成员,直接访问即可 |
| * 访问父类同名成员,需要加作用域 |
| * 总结: |
| * 1. 子类对象可以直接访问到子类中同名成员 |
| * 2. 子类镀锌加作用域可以访问到父类同名成员 |
| * 3. 当子类与父类拥有同名的成员函数,子类会隐藏掉父类中同名成员函数,只有加上作用域才可以访问到父类中的同名函数,其中包括父类中的重载函数 |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Base { |
| public: |
| Base() { |
| m_A = 100; |
| } |
| |
| void func() { |
| cout << "Base func函数被调用!" << endl; |
| } |
| |
| |
| void func(int a) { |
| cout << "Base func(int a)函数被调用!" << endl; |
| } |
| |
| int m_A; |
| }; |
| |
| class Son : public Base { |
| public: |
| Son() { |
| m_A = 200; |
| } |
| |
| void func() { |
| cout << "Son func函数被调用!" << endl; |
| } |
| |
| |
| int m_A; |
| }; |
| |
| |
| void test01() { |
| Son s; |
| cout << "Son 下的m_A = " << s.m_A << endl; |
| |
| cout << "Base 下的m_A = " << s.Base::m_A << endl; |
| } |
| |
| |
| void test02() { |
| Son son; |
| |
| son.func(); |
| |
| son.Base::func(); |
| |
| |
| son.Base::func(100); |
| } |
| |
| int main() { |
| |
| test02(); |
| } |
# 继承同名静态成员处理方式
问题:继承中同名的静态成员在子类对象上如何进行访问?
- 静态成员和非静态成员出现同名,处理方式一致
- 访问子类同名成员,直接访问即可
- 访问父类同名成员,需要加作用域
继承同名静态成员处理方式.cpp | |
| * 继承同名静态成员处理方式 |
| * 问题:继承中同名的静态成员在子类对象上如何进行访问? |
| * 静态成员和非静态成员出现同名,处理方式一致 |
| * 访问子类同名成员,直接访问即可 |
| * 访问父类同名成员,需要加作用域 |
| * 总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问的方式 (通过对象或类名进行访问) |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Base { |
| |
| public: |
| static int m_A; |
| |
| static void func() { |
| cout << "Base - static void func()" << endl; |
| } |
| static void func(int a) { |
| cout << "Base - static void func(int a)" << endl; |
| } |
| }; |
| |
| int Base::m_A = 100; |
| |
| class Son : public Base { |
| public: |
| static int m_A; |
| |
| static void func() { |
| cout << "Son - static void func()" << endl; |
| } |
| }; |
| |
| int Son::m_A = 200; |
| |
| |
| |
| void test01() { |
| |
| cout << "通过对象进行访问:" << endl; |
| Son s; |
| cout << "Son 下 m_A = " << s.m_A << endl; |
| cout << "Base 下 m_A = " << s.Base::m_A << endl; |
| |
| |
| cout << "通过类名进行访问:" << endl; |
| cout << "Son 下 m_A = " << Son::m_A << endl; |
| |
| cout << "Base 下 m_A = " << Son::Base::m_A << endl; |
| |
| } |
| |
| |
| void test02() { |
| |
| cout << "通过对象访问:" << endl; |
| Son son; |
| son.func(); |
| son.Base::func(); |
| |
| cout << "通过类名访问:" << endl; |
| Son::func(); |
| Base::func(); |
| |
| Son::Base::func(); |
| |
| |
| Son::Base::func(100); |
| } |
| |
| |
| int main() { |
| |
| test02(); |
| } |
# 多继承语法
C++
允许一个类继承多个类,语法: class 子类:继承方式 父类1,继承方式 父类2...
, 多继承可能会引发父类中有同名成员的出现,这时就需要添加作用域来进行区分,但在实际开发中不推荐使用 C++
的多继承方式
多继承语法.cpp | |
| * 多继承语法 |
| * C++ 允许一个类继承多个类 |
| * 语法:class 子类:继承方式 父类 1, 继承方式 父类 2.... |
| * 多继承可能会引发父类中有同名成员的出现,这时就需要添加作用域来进行区分 |
| * 但在实际开发中不推荐使用 C++ 的多继承方式 |
| * 总结:多继承中如果父类中出现了同名的情况,子类要使用父类中的属性变量时,就需要添加父类作用域才可以正常调用 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Base1{ |
| public: |
| Base1(){ |
| m_A = 100; |
| } |
| int m_A; |
| }; |
| |
| class Base2{ |
| public: |
| Base2(){ |
| m_A = 200; |
| } |
| int m_A; |
| }; |
| |
| class Son:public Base1,public Base2{ |
| public: |
| Son(){ |
| m_C = 300; |
| m_D = 400; |
| } |
| int m_C; |
| int m_D; |
| }; |
| |
| void test01(){ |
| Son s; |
| cout << "sizeof Son = " << sizeof(s) << endl; |
| |
| cout << "Base1::m_A = " << s.Base1::m_A << endl; |
| cout << "Base2::m_A = " << s.Base2::m_A << endl; |
| } |
| |
| int main(){ |
| test01(); |
| } |
| |
| |
| |
| D:\CLionProjects\Demo\ 类和对象 \ 继承 > cl /d1 reportSingleClassLayoutSon ".\ 多继承语法.cpp" |
| 用于 x86 的 Microsoft (R) C/C++ 优化编译器 19.44.35209 版 |
| 版权所有 (C) Microsoft Corporation。保留所有权利。 |
| |
| 多继承语法.cpp |
| |
| class Son size (16): |
| +--- |
| 0 | +--- (base class Base1) |
| 0 | | m_A |
| | +--- |
| 4 | +--- (base class Base2) |
| 4 | | m_B |
| | +--- |
| 8 | m_C |
| 12 | m_D |
| +--- |
| D:\Software\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\__msvc_ostream.hpp (781): warning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc |
| D:\Software\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\__msvc_ostream.hpp (781): note: 模板实例化上下文 (最早的实例化上下文) 为 |
| .\ 多继承语法.cpp (44): note: 查看对正在编译的函数 模板 实例化 “std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,const char *)” 的引用 |
| Microsoft (R) Incremental Linker Version 14.44.35209.0 |
| Copyright (C) Microsoft Corporation. All rights reserved. |
| |
| /out: 多继承语法.exe |
| 多继承语法.obj |
| |
| D:\CLionProjects\Demo\ 类和对象 \ 继承 > |
| |
| */ |
查看Son类的底层继承属性 | | D:\CLionProjects\Demo\类和对象\继承>cl /d1 reportSingleClassLayoutSon ".\多继承语法.cpp" |
| | 用于 x86 的 Microsoft (R) C/C++ 优化编译器 19.44.35209 版 |
| | 版权所有(C) Microsoft Corporation。保留所有权利。 |
| | |
| | 多继承语法.cpp |
| | |
| | class Son size(16): |
| | +--- |
| | 0 | +--- (base class Base1) |
| | 0 | | m_A |
| | | +--- |
| | 4 | +--- (base class Base2) |
| | 4 | | m_B |
| | | +--- |
| | 8 | m_C |
| | 12 | m_D |
| | +--- |
| | D:\Software\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\__msvc_ostream.hpp(781): warning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc |
| | D:\Software\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\__msvc_ostream.hpp(781): note: 模板实例化上下文(最早的实例化上下文)为 |
| | .\多继承语法.cpp(44): note: 查看对正在编译的函数 模板 实例化“std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,const char *)”的引用 |
| | Microsoft (R) Incremental Linker Version 14.44.35209.0 |
| | Copyright (C) Microsoft Corporation. All rights reserved. |
| | |
| | /out:多继承语法.exe |
| | 多继承语法.obj |
| | |
| | D:\CLionProjects\Demo\类和对象\继承> |
# 菱形继承
- 菱形继承概念:
- 两个派生类继承同一个基类
- 又有某个类同事继承这两个的派生类
- 这种继承就被称为菱形继承或是钻石继承
- 典型继承案例:
- 动物:
- 羊 鸵
- 羊驼
虚继承关键字 virtual
, 使用 virtual
即可解决菱形继承的多份数据问题。
菱形继承.cpp | |
| * 菱形继承 |
| * 菱形继承概念: |
| * 两个派生类继承同一个基类 |
| * 有某个类同事继承这两个的派生类 |
| * 这种继承就被称为菱形继承或是钻石继承 |
| * 典型继承案例: |
| * 动物: |
| * 羊 鸵 |
| * 羊驼 |
| * 总结: |
| * 1. 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义 |
| * 2. 利用虚继承可以解决菱形继承问题 |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| class Animal { |
| public: |
| int m_Age; |
| }; |
| |
| |
| |
| |
| |
| class Sheep : virtual public Animal { |
| |
| }; |
| |
| |
| class Tuo : virtual public Animal { |
| |
| }; |
| |
| |
| class SheepTuo : public Sheep, public Tuo { |
| |
| }; |
| |
| void test01() { |
| SheepTuo st; |
| st.Sheep::m_Age = 18; |
| st.Tuo::m_Age = 28; |
| |
| cout << "st.Sheep::m_Age = " << st.Sheep::m_Age << endl; |
| cout << "st.Tuo::m_Age = " << st.Tuo::m_Age << endl; |
| |
| |
| cout << "st.m_Age = " << st.m_Age << endl; |
| } |
| |
| int main() { |
| test01(); |
| } |
未使用虚继承后的底层实现
未使用虚继承的底层实现 | | D:\CLionProjects\Demo\类和对象\继承>cl /d1 reportSingleClassLayoutSheepTuo 菱形继承.cpp |
| | 用于 x86 的 Microsoft (R) C/C++ 优化编译器 19.44.35209 版 |
| | 版权所有(C) Microsoft Corporation。保留所有权利。 |
| | |
| | 菱形继承.cpp |
| | 菱形继承.cpp(1): warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失 |
| | |
| | class SheepTuo size(8): |
| | +--- |
| | 0 | +--- (base class Sheep) |
| | 0 | | +--- (base class Animal) |
| | 0 | | | m_Age |
| | | | +--- |
| | | +--- |
| | 4 | +--- (base class Tuo) |
| | 4 | | +--- (base class Animal) |
| | 4 | | | m_Age |
| | | | +--- |
| | | +--- |
| | +--- |
| | 菱形继承.cpp(56): error C2385: 对“m_Age”的访问不明确 |
| | 菱形继承.cpp(56): note: 可以是基 "Animal" 中的 "m_Age" |
| | 菱形继承.cpp(56): note: 也可以是基 "Animal" 中的 "m_Age" |
| | |
| | D:\CLionProjects\Demo\类和对象\继承> |
使用虚继承后的底层实现,虚继承关键字 virtual
使用虚继承后的底层实现 | | D:\CLionProjects\Demo\类和对象\继承>cl /d1 reportSingleClassLayoutSheepTuo 菱形继承.cpp |
| | 用于 x86 的 Microsoft (R) C/C++ 优化编译器 19.44.35209 版 |
| | 版权所有(C) Microsoft Corporation。保留所有权利。 |
| | |
| | 菱形继承.cpp |
| | 菱形继承.cpp(1): warning C4819: 该文件包含不能在当前代码页(936)中表示的字符。请将该文件保存为 Unicode 格式以防止数据丢失 |
| | |
| | class SheepTuo size(12): |
| | +--- |
| | 0 | +--- (base class Sheep) |
| | 0 | | {vbptr} |
| | | +--- |
| | 4 | +--- (base class Tuo) |
| | 4 | | {vbptr} |
| | | +--- |
| | +--- |
| | +--- (virtual base Animal) |
| | 8 | m_Age |
| | +--- |
| | |
| | SheepTuo::$vbtable@Sheep@: |
| | 0 | 0 |
| | 1 | 8 (SheepTuod(Sheep+0)Animal) |
| | |
| | SheepTuo::$vbtable@Tuo@: |
| | 0 | 0 |
| | 1 | 4 (SheepTuod(Tuo+0)Animal) |
| | vbi: class offset o.vbptr o.vbte fVtorDisp |
| | Animal 8 0 4 0 |
| | D:\Software\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\__msvc_ostream.hpp(781): warning C4530: 使用了 C++ 异常处理程序,但未启用展开语义。请指定 /EHsc |
| | D:\Software\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.44.35207\include\__msvc_ostream.hpp(781): note: 模板实例化上下文(最早的实例化上下文)为 |
| | 菱形继承.cpp(52): note: 查看对正在编译的函数 模板 实例化“std::basic_ostream<char,std::char_traits<char>> &std::operator <<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,const char *)”的引用 |
| | Microsoft (R) Incremental Linker Version 14.44.35209.0 |
| | Copyright (C) Microsoft Corporation. All rights reserved. |
| | |
| | /out:菱形继承.exe |
| | 菱形继承.obj |
| | |
| | D:\CLionProjects\Demo\类和对象\继承> |
总结:1. 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义。 2. 利用虚继承可以解决菱形继承问题
# 多态
# 多态的基本概念
- 多态是
C++
面向对象三大特性之一 - 多态分为两类
- 静态多态:函数重载和运算符重载属于静态多态,复用函数名
- 多态多态:派生类和虚函数实现运行时多态
- 静态多态和多态多态的区别:
- 静态多态的函数地址早绑定 - 编译阶段确定函数地址
- 多态多态的函数地址晚绑定 - 运行阶段确定函数地址
多态的基本概念.cpp | |
| * 多态的基本概念 |
| * 多态是 C++ 面向对象三大特性之一 |
| * 多态分为两类 |
| * 静态多态:函数重载和运算符重载属于静态多态,复用函数名 |
| * 多态多态:派生类和虚函数实现运行时多态 |
| * 静态多态和多态多态的区别: |
| * 静态多态的函数地址早绑定 - 编译阶段确定函数地址 |
| * 多态多态的函数地址晚绑定 - 运行阶段确定函数地址 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| class Animal { |
| public: |
| |
| virtual void speak() { |
| cout << "动物在说话" << endl; |
| } |
| }; |
| |
| |
| class Cat : public Animal { |
| public: |
| void speak() { |
| cout << "小猫在说话" << endl; |
| } |
| }; |
| |
| |
| |
| |
| void doSpeak(Animal &animal){ |
| animal.speak(); |
| } |
| |
| void test01(){ |
| Cat cat; |
| doSpeak(cat); |
| } |
| |
| int main() { |
| test01(); |
| } |
总结:多态满足条件 1. 有继承关系 2. 子类重写父类中的虚函数。多态使用条件:父类指针或引用指向子类对象。重写: 函数返回值类型 函数名 参数列表
完全一直称为重写。
# 多态案例一计算器类
案例描述: 分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类。 多态的优点: 代码组织结构清晰,可读性强,利用前期和后期的扩展以及维护。
普通实现.cpp | |
| * 案例描述: |
| * 分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类 |
| * 多态的优点: |
| * 代码组织结构清晰 |
| * 可读性强 |
| * 利用前期和后期的扩展以及维护 |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| class Calculator { |
| public: |
| int getResult(string oper) { |
| if (oper == "+") { |
| return num1 + num2; |
| } else if (oper == "-") { |
| return num1 - num2; |
| } else if (oper == "*") { |
| return num1 * num2; |
| } |
| |
| |
| |
| } |
| |
| int num1; |
| int num2; |
| }; |
| |
| void test01() { |
| |
| Calculator c; |
| c.num1 = 10; |
| c.num2 = 20; |
| cout << c.num1 << "+" << c.num2 << "=" << c.getResult("+") << endl; |
| cout << c.num1 << "-" << c.num2 << "=" << c.getResult("-") << endl; |
| cout << c.num1 << "*" << c.num2 << "=" << c.getResult("*") << endl; |
| } |
| |
| int main() { |
| test01(); |
| } |
多态实现.cpp | |
| * 案例描述: |
| * 分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类 |
| * 多态的优点: |
| * 代码组织结构清晰 |
| * 可读性强 |
| * 利用前期和后期的扩展以及维护 |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| class Calculator { |
| public: |
| int getResult(string oper) { |
| if (oper == "+") { |
| return num1 + num2; |
| } else if (oper == "-") { |
| return num1 - num2; |
| } else if (oper == "*") { |
| return num1 * num2; |
| } |
| |
| |
| |
| } |
| |
| int num1; |
| int num2; |
| }; |
| |
| void test01() { |
| |
| Calculator c; |
| c.num1 = 10; |
| c.num2 = 20; |
| cout << c.num1 << "+" << c.num2 << "=" << c.getResult("+") << endl; |
| cout << c.num1 << "-" << c.num2 << "=" << c.getResult("-") << endl; |
| cout << c.num1 << "*" << c.num2 << "=" << c.getResult("*") << endl; |
| } |
| |
| |
| |
| |
| class AbstractCalculator { |
| public: |
| virtual int getResult() { |
| return 0; |
| } |
| |
| int num1; |
| int num2; |
| }; |
| |
| |
| class AddCalculator : public AbstractCalculator { |
| public: |
| int getResult() { |
| return num1 + num2; |
| } |
| }; |
| |
| |
| class SubCalculator : public AbstractCalculator { |
| public: |
| int getResult() { |
| return num1 - num2; |
| } |
| }; |
| |
| |
| class MulCalculator : public AbstractCalculator { |
| int getResult() { |
| return num1 * num2; |
| } |
| }; |
| |
| void test02() { |
| |
| |
| |
| AbstractCalculator *abc = new AddCalculator; |
| |
| abc->num1 = 10; |
| abc->num2 = 20; |
| cout << abc->num1 << " + " << abc->num2 << " = " << abc->getResult() << endl; |
| |
| delete abc; |
| |
| |
| abc = new SubCalculator; |
| abc->num1 = 100; |
| abc->num2 = 20; |
| cout << abc->num1 << " - " << abc->num2 << " = " << abc->getResult() << endl; |
| |
| |
| abc = new MulCalculator; |
| abc->num1 = 10; |
| abc->num2 = 20; |
| cout << abc->num1 << " * " << abc->num2 << " = " << abc->getResult() << endl; |
| |
| } |
| |
| int main() { |
| test02(); |
| } |
多态简洁版.cpp | |
| * 案例描述: |
| * 分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类 |
| * 多态的优点: |
| * 代码组织结构清晰 |
| * 可读性强 |
| * 利用前期和后期的扩展以及维护 |
| */ |
| |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| |
| |
| class AbstractCalculator { |
| public: |
| virtual int getResult() { |
| return 0; |
| } |
| |
| int num1; |
| int num2; |
| }; |
| |
| |
| class AddCalculator : public AbstractCalculator { |
| public: |
| int getResult() { |
| return num1 + num2; |
| } |
| }; |
| |
| |
| class SubCalculator : public AbstractCalculator { |
| public: |
| int getResult() { |
| return num1 - num2; |
| } |
| }; |
| |
| |
| class MulCalculator : public AbstractCalculator { |
| int getResult() { |
| return num1 * num2; |
| } |
| }; |
| |
| void test01() { |
| |
| |
| |
| AbstractCalculator *abc = new AddCalculator; |
| |
| abc->num1 = 10; |
| abc->num2 = 20; |
| cout << abc->num1 << " + " << abc->num2 << " = " << abc->getResult() << endl; |
| |
| delete abc; |
| |
| |
| abc = new SubCalculator; |
| abc->num1 = 100; |
| abc->num2 = 20; |
| cout << abc->num1 << " - " << abc->num2 << " = " << abc->getResult() << endl; |
| |
| |
| abc = new MulCalculator; |
| abc->num1 = 10; |
| abc->num2 = 20; |
| cout << abc->num1 << " * " << abc->num2 << " = " << abc->getResult() << endl; |
| |
| } |
| |
| int main() { |
| test01(); |
| } |
总结: C++
开发提倡利用多态设计程序架构,因为多态优点很多。
# 纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数,纯虚函数语法: virtual 返回值类型 函数名(参数列表) = 0 ;
,当类中有了纯虚函数,这个类也称为抽象类。抽象类的特点:无法实例化对象,子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
| |
| * 纯虚函数和抽象类 |
| * 在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容 |
| * 因此可以将虚函数改为纯虚函数 |
| * 纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0 ; |
| * 当类中有了纯虚函数,这个类也称为抽象类 |
| * |
| * 抽象类的特点: |
| * 无法实例化对象 |
| * 子类必须重写抽象类中的纯虚函数,否则也属于抽象类 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| class Base { |
| public: |
| |
| |
| |
| virtual void func() = 0; |
| }; |
| |
| class Son : public Base { |
| public: |
| virtual void func() { |
| cout << "func函数调用" << endl; |
| } |
| }; |
| |
| void test01() { |
| |
| |
| |
| Base *base = new Son; |
| base->func(); |
| } |
| |
| int main() { |
| test01(); |
| } |
# 多态案例二制作饮品
案例描述:制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料,利用多态技术实现本案例,提供抽象制作饮品的基类,提供子类制作咖啡和茶叶。
多态案例二制作饮品.cpp | |
| * 案例描述: |
| * 制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 |
| * 利用多态技术实现本案例,提供抽象制作饮品的基类,提供子类制作咖啡和茶叶 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| class AbstractDrinking{ |
| public: |
| |
| virtual void Boil() = 0; |
| |
| virtual void Brew() = 0; |
| |
| virtual void PourInCup() = 0; |
| |
| virtual void PutSomething() = 0; |
| |
| |
| void makeDrink(){ |
| Boil(); |
| Brew(); |
| PourInCup(); |
| PutSomething(); |
| } |
| }; |
| |
| |
| class Coffee:public AbstractDrinking{ |
| public: |
| void Boil() override { |
| cout << "煮农夫山泉"<< endl; |
| } |
| |
| void Brew() override { |
| cout << "冲泡咖啡" << endl; |
| } |
| |
| void PourInCup() override { |
| cout << "倒入杯中" << endl; |
| } |
| |
| void PutSomething() override { |
| cout << "加入冰块" << endl; |
| } |
| }; |
| |
| void test01(){ |
| AbstractDrinking * drinking = new Coffee; |
| drinking->makeDrink(); |
| delete drinking; |
| |
| } |
| |
| |
| class Tea:public AbstractDrinking{ |
| public: |
| void Boil() override { |
| cout << "煮矿泉水" << endl; |
| } |
| |
| void Brew() override { |
| cout << "冲泡茶叶" << endl; |
| } |
| |
| void PourInCup() override { |
| cout << "倒入杯中" << endl; |
| } |
| |
| void PutSomething() override { |
| cout << "加入枸杞" << endl; |
| } |
| }; |
| |
| void test02(){ |
| AbstractDrinking * drinking = new Tea; |
| drinking->makeDrink(); |
| delete drinking; |
| } |
| |
| |
| void doWork(AbstractDrinking *abs){ |
| abs->makeDrink(); |
| delete abs; |
| } |
| |
| int main(){ |
| test01(); |
| cout << "----------------------------------------" << endl; |
| test02(); |
| cout << "----------------------------------------" << endl; |
| doWork(new Coffee); |
| cout << "----------------------------------------" << endl; |
| doWork(new Tea); |
| } |
# 虚构函数和纯虚函数
在使用多态时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码,解决方法:将父类中的析构函数改为虚析构或者是纯虚析构。
- 虚析构和纯虚析构共性:
- 虚析构和纯虚析构的区别:
- 虚析构语法:
virtual ~类名(){ ... }
- 纯虚析构语法:
virtual ~类名() = 0;
, 实现: 类名::~类名(){ ... }
虚析构解决办法.cpp | |
| * 在使用多态时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码 |
| * 解决方法:将父类中的析构函数改为虚析构或者是纯虚析构 |
| * |
| * 虚析构和纯虚析构共性: |
| * 可以解决父类指针释放子类对象 |
| * 都需要具体的函数实现 |
| * |
| * 虚析构和纯虚析构的区别: |
| * 如果是纯虚析构,该类属于抽象类,无法实例化对象 |
| * |
| * 虚析构语法: |
| * virtual ~ 类名 (){ ...} |
| * |
| * 纯虚析构语法: |
| * virtual ~ 类名 () = 0; |
| * |
| */ |
| |
| #include <iostream> |
| #include <string> |
| |
| using namespace std; |
| |
| class Animal { |
| public: |
| Animal() { |
| cout << "Animal构造函数调用" << endl; |
| } |
| |
| |
| virtual ~Animal() { |
| cout << "Animal析构函数调用" << endl; |
| } |
| |
| virtual void speak() = 0; |
| }; |
| |
| class Cat : public Animal { |
| public: |
| Cat(string name) { |
| cout << "Cat构造函数调用" << endl; |
| m_Name = new string(name); |
| } |
| |
| virtual void speak() { |
| cout << *m_Name << "小猫在说话" << endl; |
| } |
| |
| ~Cat() { |
| if (m_Name != NULL) { |
| cout << "Cat析构函数调用" << endl; |
| delete m_Name; |
| m_Name = NULL; |
| } |
| } |
| |
| string *m_Name; |
| }; |
| |
| void test01() { |
| Animal *animal = new Cat("Tom"); |
| animal->speak(); |
| |
| delete animal; |
| } |
| |
| int main() { |
| test01(); |
| } |
纯虚析构解决方法.cpp | |
| * 在使用多态时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码 |
| * 解决方法:将父类中的析构函数改为虚析构或者是纯虚析构 |
| * |
| * 虚析构和纯虚析构共性: |
| * 可以解决父类指针释放子类对象 |
| * 都需要具体的函数实现 |
| * |
| * 虚析构和纯虚析构的区别: |
| * 如果是纯虚析构,该类属于抽象类,无法实例化对象 |
| * |
| * 虚析构语法: |
| * virtual ~ 类名 (){ ...} |
| * |
| * 纯虚析构语法: |
| * virtual ~ 类名 () = 0; |
| * 类名::~ 类名 (){ ... } |
| */ |
| |
| #include <iostream> |
| #include <string> |
| |
| using namespace std; |
| |
| class Animal { |
| public: |
| Animal() { |
| cout << "Animal构造函数调用" << endl; |
| } |
| |
| |
| |
| virtual ~Animal() = 0; |
| |
| |
| virtual void speak() = 0; |
| }; |
| |
| Animal::~Animal() { |
| cout << "Animal纯虚析构函数调用" << endl; |
| } |
| |
| class Cat : public Animal { |
| public: |
| Cat(string name) { |
| cout << "Cat构造函数调用" << endl; |
| m_Name = new string(name); |
| } |
| |
| virtual void speak() { |
| cout << *m_Name << "小猫在说话" << endl; |
| } |
| |
| ~Cat() { |
| if (m_Name != NULL) { |
| cout << "Cat析构函数调用" << endl; |
| delete m_Name; |
| m_Name = NULL; |
| } |
| } |
| |
| string *m_Name; |
| }; |
| |
| void test01() { |
| Animal *animal = new Cat("Tom"); |
| animal->speak(); |
| |
| delete animal; |
| } |
| |
| int main() { |
| test01(); |
| } |
- 总结:
- 虚析构或纯虚析构技术用来解决通过父类指针释放子类对象的。
- 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构。
- 拥有纯虚析构函数的类也属于是抽象类。
# 多态案例三电脑组装
电脑主要组成部件为 CPU(用于计算),显卡(用于显示),内存条(用于存储), 将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如 Intel 厂商和 Lenovo 厂商,创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口,并且测试时组装三台不同的电脑进行工作。
多态案例三电脑组装.cpp | |
| * 案例描述: |
| * 电脑主要组成部件为 CPU(用于计算),显卡(用于显示),内存条(用于存储) |
| * 将每个零件封装出抽象基类,并且提供不同的厂商生产不同的零件,例如 Intel 厂商和 Lenovo 厂商 |
| * 创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口 |
| * 测试时组装三台不同的电脑进行工作 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| |
| class Cpu { |
| public: |
| virtual void calculate() = 0; |
| }; |
| |
| |
| class Graphics { |
| public: |
| virtual void display() = 0; |
| }; |
| |
| |
| class MemorySticks { |
| public: |
| virtual void storage() = 0; |
| }; |
| |
| |
| class Computer { |
| public: |
| Computer(Cpu *cpu, Graphics *graphics, MemorySticks *memorySticks) { |
| m_Cpu = cpu; |
| m_Graphics = graphics; |
| m_MemorySticks = memorySticks; |
| } |
| |
| |
| virtual ~Computer() { |
| |
| if (m_Cpu != NULL) { |
| delete m_Cpu; |
| m_Cpu = NULL; |
| } |
| |
| if (m_Graphics != NULL) { |
| delete m_Graphics; |
| m_Graphics = NULL; |
| } |
| if (m_MemorySticks != NULL) { |
| delete m_MemorySticks; |
| m_MemorySticks = NULL; |
| } |
| } |
| |
| |
| void work() { |
| |
| m_Cpu->calculate(); |
| m_Graphics->display(); |
| m_MemorySticks->storage(); |
| } |
| |
| private: |
| |
| Cpu *m_Cpu; |
| |
| Graphics *m_Graphics; |
| |
| MemorySticks *m_MemorySticks; |
| }; |
| |
| |
| class IntelCpu : public Cpu { |
| public: |
| void calculate() { |
| cout << "英特尔Cpu开始计算了!" << endl; |
| } |
| }; |
| |
| class NvidiaGraphics : public Graphics { |
| public: |
| void display() { |
| cout << "英伟达显卡开始显示了!" << endl; |
| } |
| }; |
| |
| class RogMemorySticks : public MemorySticks { |
| public: |
| void storage() { |
| cout << "Rog内存条开始存储了!" << endl; |
| } |
| }; |
| |
| void test01() { |
| |
| Cpu *intelCpu = new IntelCpu; |
| Graphics *graphics = new NvidiaGraphics; |
| MemorySticks *memorySticks = new RogMemorySticks; |
| |
| Computer *computer = new Computer(intelCpu, graphics, memorySticks); |
| computer->work(); |
| delete computer; |
| |
| cout << "------------------------------------------------------------------------" << endl; |
| |
| |
| Computer *computer2 = new Computer(new IntelCpu, new NvidiaGraphics, new RogMemorySticks); |
| computer2->work(); |
| delete computer2; |
| } |
| |
| int main() { |
| test01(); |
| } |
# 文件操作
程序在运行时的数据都属于临时数据,程序一旦运行结束都会被释放,我们可以通过文件将数据进行持久化处理, C++
中对文件操作需要包含头文件 <fstream>
。
- 文件类型分为两种:
- 文本文件:文件以文本的
ASCLL
码形式存储在计算机中。 - 二进制文件:文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们。
- 操作文件的三大类:
ofstream
: 文件写操作。ifstream
: 文件读操作。fstream
: 文件读写操作。
打开方式 | 解释 |
ios::in | 为读文件而打开文件 |
ios::out | 为写文件而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 追加方式写文件 |
ios::trunc | 如果文件存在先删除,再创建 |
ios::binary | 二进制方式 |
注意:文件打开方式可以配合使用,利用 |
操作符。 例如:用二进制方式写文件 ios::binary | ios::out
。
# 文本文件 - 写文件
- 写文件步骤如下:
- 包含头文件:
#include <fstream>
- 创建流对象:
ofstream ofs;
- 打开文件:
ofs.open("文件路径",打开方式)
- 写数据:
ofs << "写入的数据"
- 关闭文件:
ofs.close();
文本文件 - 写文件.cpp | |
| * 文本文件 - 写文件 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| #include <fstream> |
| |
| void test01(){ |
| |
| |
| |
| ofstream ofs; |
| |
| |
| ofs.open("test.txt",ios::out); |
| |
| ofs << "姓名:张三" << endl; |
| ofs << "性别:男" << endl; |
| ofs << "年龄:18" << endl; |
| |
| |
| ofs.close(); |
| } |
| |
| int main(){ |
| test01(); |
| } |
# 文本文件 - 读文件
读文件与写文件步骤相似,但是读取方式相对于较多。
- 读文件步骤如下:
- 包含头文件:
#include <fstream>
- 创建流对象:
ifstream ifs;
- 打开文件并判断文件是否打开成功:
ifs.open("文件路径",打开方式)
- 读数据:四种方式读取
- 关闭文件:
ifs.close()
读文件.cpp | #include <iostream> |
| |
| #include <fstream> |
| #include <string> |
| using namespace std; |
| |
| |
| void test01() { |
| |
| |
| |
| ifstream ifs; |
| |
| ifs.open("test.txt", ios::in); |
| if (!ifs.is_open()) { |
| cout << "文件打开失败" << endl; |
| return; |
| } |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| string buf; |
| while (getline(ifs,buf)){ |
| cout << buf << endl; |
| } |
| |
| |
| |
| |
| |
| |
| |
| ifs.close(); |
| } |
| |
| int main() { |
| test01(); |
| } |
# 二进制 - 写文件
以二进制的方式对文件进行读写操作,打开方式要指定为 ios::binary
。二进制方式写文件主要利用流对象调用成员函数 write
, 函数原型: ostream &write(const char *buffer,int len);
, 参数注释:字符指针 buffer
指向内存中一段存储空间, len
是读写的字节数。
写文件.cpp | |
| * 二进制文件 - 写文件 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| #include <fstream> |
| |
| |
| class Person{ |
| public: |
| |
| char m_Name[64]; |
| |
| int m_Age; |
| }; |
| |
| void test01(){ |
| |
| |
| ofstream ofs("person.txt",ios::out | ios::binary); |
| |
| |
| |
| Person p = {"张三",18}; |
| ofs.write( (const char *)&p,sizeof(Person)); |
| |
| ofs.close(); |
| } |
| |
| int main(){ |
| test01(); |
| } |
总结:文件输出流对象可以通过 write
函数,以二进制方式写数据。
# 二进制 - 读文件
二进制方式读文件主要利用流对象调用成员函数 read
, 函数原型: istream &read(char *buffer,int len)
, 参数解释:字符指针 buffer
指向内存中一段存储空间, len
是读写的字节数。
读文件.cpp | |
| * 二进制文件 - 读文件 |
| */ |
| #include <iostream> |
| |
| using namespace std; |
| |
| #include <fstream> |
| |
| class Persion { |
| public: |
| char m_Name[64]; |
| int m_Age; |
| }; |
| |
| void test01() { |
| |
| |
| |
| ifstream ifs("person.txt", ios::in | ios::binary); |
| |
| |
| if (!ifs.is_open()) { |
| cout << "文件打开失败" << endl; |
| return; |
| } |
| |
| Persion p; |
| ifs.read((char *) &p, sizeof(Persion)); |
| cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl; |
| |
| ifs.close(); |
| } |
| |
| int main() { |
| test01(); |
| } |
总结:文件输入流对象,可以通过 read
函数,以二进制方式读取数据。