内存四大分区概念简介
  • 在 C++ 程序执行时,将内存大方向划分为四个区域
    • 代码区:存放函数体的二进制代码,由操作系统进行管理的
    • 全局区:存放全局变量和静态变量以及常量
    • 栈区:由编译器自动分配释放,存在函数的参数值,局部变量等
    • 堆区:由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收
    • 内存四区意义:不同区域存放的数据,赋予不同的生命周期,给我们更大的灵活编程

# new 操作符 - 内存分区模型

C++ 中利用 new 操作符在堆区开辟数据,堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符 delete
, 语法: new 数据类型 ,利用 new 创建的数据,会返回该数据对应的类型指针

new操作符
#include <iostream>
using namespace std;
int *func() {
    // 使用 new 关键字开辟空间可以返回指针地址
    int *a = new int(10);
    return a;
}
int main() {
    int *p = func();
    cout << *p << endl;
    cout << *p << endl;
    // 利用 delete 释放堆区数据
    delete p;
//    cout << *p << endl;// 释放后的空间不可访问
    // 在堆中开辟应该数组
    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;// 引用必须初始化
    int &c = a;// 引用一旦初始化就不可以更改
    c = b;// 这是赋值操作不是更改引用
    cout << "a:" << a << endl;
    cout << "b:" << b << endl;
    cout << "c:" << c << endl;
}

# 引用做函数参数

作用:函数传参时,可以利用引用的技术让形参修饰实参

优点:可以简化指针修改实参

引用做函数参数
/**
 * 引用做函数参数
 * 作用:函数传参时,可以利用引用的技术让形参修饰实参
 * 优点:可以简化指针修改实参
 * 总结:通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单
*/
#include <iostream>
using namespace std;
//1. 值传递
void mySwap01(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
//    cout << "mySwap01 a= " << a << endl;
//    cout << "mySwap01 b= " << b << endl;
}
//2. 地址传递
void mySwap02(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}
//3. 地址传递
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 &ref = test01();
//    cout << "ref =" << ref << endl;// 因为内存已经被释放
    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;
// 发现是引用,转换为 int * const ref = &a;
void func(int &ref) {
    ref = 100;//ref 是引用,转换为 *ref = 100
}
int main() {
    int a = 10;
    // 自动转换为 intconstref=&a; 指针常量是指针指向不可改,也说明为什么引用不可更改
    int &ref = a;
    ref = 20;// 内部发现 ref 是引用,自动帮我们转换为: *ref = 20;
    cout << "a: " << a << endl;
    cout << "ref: " << ref << endl;
    func(a);
}

# 常量引用

作用:常量引用主要用来修饰形参,防止误操作

在函数形参列表中,可以加 const 修饰形参,防止形参改变实参

常量引用
/**
 * 常量引用
 * 作用:常量引用主要用来修饰形参,防止误操作
 * 在函数形参列表中,可以添加 const 修饰形参,防止形参改变实参
 */
#include <iostream>
using namespace std;
// 打印数据函数
void showValue(const int &val) {
//    v += 10;
//    v = 1000;
    cout << val << endl;
}
int main() {
    //int & ref = 10; 引用本身需要一个合法的内存空间,因此这行错误
    // 加入 const 就可以了,编译器优化代码,int temp = 10; const int& ref = temp;
//    int a  = 10;
//    int &ref = 10;  // 错误
    // 加上 const 之后编译器将代码修改 int temp = 10; const int & ref = temp;
    const int &ref = 10;// 引用必须引一块合法的内存空间
//    ref = 20; // 加入 const 之后变为只读,不可以修改
    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;
}
//1,如果某个位置参数有默认值,那么从这个位置往后,从左向右,必须都要有默认值
//2,如果函数声明有默认值,函数实现的时候就不能有默认参数
//3、如果函数声明有默认参数,函数实现就不能有默认参数,声明和实现只能有一个有默认参数
int func2(int a = 10, int b = 10);
int func2(int a, int b) {
    return a + b;
}
// 注意事项
//1、如果某个位置已经有了默认参数,那么从这个位置往后,从左到右都必须有默认值
//int func3(int a = 10, int b, int c) {
//    return a + b + c;
//}
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;
// 1. 引用作为重载条件
void func(int &a) {
    cout<< "func (int &a) 调用" << endl;
}
void func(const int &a) {
    cout<< "func (const int &a) 调用" << endl;
}
// 2. 函数重载碰到函数默认参数
void func2(int a) {
    cout << "func2 (int &a) 调用" <<  endl;
}
void func2(int a,int b = 10) {
    cout << "func2 (int &a) 调用" <<  endl;
}
int main() {
//    int a = 10;
//    func(a);
//    func(10);
//    func2 (10);// 当函数重载碰到默认参数,会出现二义性,报错,尽量避免这种情况
}

# 类和对象

C++ 面向对象的三大特性为:封装、继承、多态, C++ 认为万事万物都皆为对象,对象上有其属性和行为

# 封装

  • 封装是 C++ 面向对象三大特性之一
  • 封装的意义:
    • 将属性和行为作为一个整体
    • 将属性和行为加以权限控制
封装的意义
/**
 * 封装的意义
 *
 */
#include <iostream>
using namespace std;
// 设计一个圆类,求圆的周长
// 圆求周长的公式:2 * PI * 半径
//c1ass 代表设计一个类,类后面紧跟着的就是类名称
// 圆周率
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;
//    circle.calculateZC();
   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++structclass 的唯一区别就是在于默认的访问权限不同
  • 区别:
    • 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:
    // 行为
    //1. 设置获取长宽高
    // 设置长
    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>
//#include "point.h"
//#include "circle.h"
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:
    // 1. 构造函数进行初始化操作
    Person() {
        cout << "Person的构造函数被调用" << endl;
    }
    // 2. 析构函数,进行清理操作
    ~Person() {
        cout << "Person的析构函数被调用" << endl;
    }
};
// 构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的构造和析构
void test01(){
    Person p;// 在栈上的数据,test01 执行完毕后,释放这个对象
}
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() {
    // 1. 括号法
//    Person p1;// 默认构造函数调用
//    Person p2 (10);// 有参构造函数
//    Person p3 (p2);// 拷贝构造函数
    // 注意事项 1
    // 调用默认构造函数时候,不要加 ()
//    cout << "p2 的年龄:" << p2.age << endl;
//    cout << "p3 的年龄:" << p3.age << endl;
// 因为下面这行代码,编译器会认为是一个函数的声明,不会认为在创建对象
//    Person p1();
    // 2. 显示法
    Person p1;  // ↓ 匿名对象语法:Person ()
    Person p2 = Person(10);// 有参构造函数
    Person p3 = Person(p2);// 拷贝构造函数
//    Person (20);// 匿名对象特点:当前行执行结束后,系统会立即回收掉匿名对象
    // 注意事项 2
    // 不用利用拷贝构造函数初始化匿名对象 编译器会认为 Person (p3) == Person p3;
//    Person (p3);// 错误
    // 3. 隐式转换法
    Person p4 = 10; // 相当于写了 Person p4 = Person (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;
};
// 1. 使用应该已经创建完毕的对象来初始化应该新对象
void test01() {
    Person p1(20);
    Person p2(p1);
    cout << "P2的年龄:" << p2.mAge << endl;
}
// 2. 值传递的方式给函数参数传值
void doWork(Person p) {
}
void test02() {
    Person p;
    doWork(p);
}
// 3. 以值方式返回局部对象
Person doWork2() {
    Person p1;
    cout << (int*) &p1 << endl;
    return Person(p1);
}
void test03() {
    Person p = doWork2();
    cout << (int*) &p << endl;
}
int main() {
//    test01();
//    test02();
    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;
//        m_Height = new int (*p.m_Height); // 深拷贝 new int 代表重新在堆区开辟一个新内存空间
    }
    // 析构函数
    ~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(int a,int b,int c){
//        this->a = a;
//        this->b = b;
//        this->c = c;
//    }
    // 使用初始化列表初始化属性
    // 方法 1
    Person() : m_A(10), m_B(20), m_C(30) {
    }
    // 方法 2
    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:
    //1. 所有对象都共享同一份数据
    //2. 编译阶段就分配内存
    //3. 类内声明,类外初始化操作
    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() {
    // 静态成员变量不属于某个对象上,所有对象都共享同一份数据
    // 因此静态成员变量有两种访问方式
    //1. 通过对象进行访问
//    Person p;
//    cout << p.m_A << endl;
    //2. 通过类名进行访问
    cout << Person::m_A << endl;
//    cout << Person::m_B << endl;
}
int main() {
    test02();
//    test01();
}
静态成员函数.cpp
/**
 * 静态成员函数
 * 所有对象共享同一个函数
 * 静态成员函数只能访问静态成员变量
 */
#include <iostream>
using namespace std;
class Person {
public:
    static void func() {
        m_A = 100;// 静态成员函数可以访问静态成员变量
//        m_B = 200; 静态成员函数 不可以访问非静态成员变量,因为编译器无法区分到底是哪个对象的 m_B 属性
        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(){
    // 1. 通过对象进行访问
    Person p;
    p.func();
    // 2. 通过类名进行访问
    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 指针指向被调用的成员函数所属的对象
        this->age = age;
    }
    Person& PersonAddAge(Person &p){
        this->age +=p.age;
        return *this;
    }
    int age;
};
// 1. 解决名称冲突问题
void test01(){
    Person p1(18);
    cout << "p1的年龄为:" << p1.age <<endl;
}
// 2. 返回对象本身用 *this
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;
        }
        // 错误:原因是因为传入的指针是为 NULL
        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;
    }
    // 常函数
    //this 指针的本质是指针常量 指针的指向是不可以修改的
    // 在成员函数后面加 const 修饰的是 this 指向,让指针指向的值夜不可以修改
    void showPerson() const {
        m_B = 100;
//        this -> m_A = 100;
//        this = NULL; //this 指针是不可以修改指针的指向的
    }
    int m_A;
    mutable int m_B;// 特殊变量,即使在常函数中,也可以修改这个值
};
void test01() {
    Person p1;
    p1.showPerson();
}
// 常对象
void test02() {
    const Person p{}; // 在对象前加上 const, 变为常对象
//   p.m_A = 100;
    p.m_B = 100; //m_B 因为添加了 mutable 关键字,在常对象下也是可以修改
    // 常对象只能调用常函数
    p.showPerson();
//    p.func (); // 因为常对象不可以调用普通的成员函数,另外普通函数是可以修改成员函数的
    Person p2{};
    p2.func();
}
int main() {
    test02();
}

# 友元

在生活中你的家里有客厅 (public) 有卧室 (private) , 客厅所有来的客人都可以进去,但是你的卧室是私有的,也加上说只有你自己可以进去。 但是,你也可以允许你的好朋友进去,在程序中,有些是私有属性,也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术了,友元的目的是让一个函数或者类访问另应该类中私有的成员。 友元的关键字为: friend

  • 友元的三种实现:
    • 全局函数做友元
    • 类做友元
    • 成员函数做友元
友元-全局函数做友元.cpp
/**
* 友元
 * 在生活中你的家里有客厅 (public) 有卧室 (private)
 * 客厅所有来的客人都可以进去,但是你的卧室是私有的,也加上说只有你自己可以进去
 * 但是,你也可以允许你的好朋友进去
 *
 * 在程序中,有些是私有属性,也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术了
 *
 * 友元的目的是让一个函数或者类访问另应该类中私有的成员
 *
 * 友元的关键字为:friend
 *
 * 友元的三种实现:
 *      全局函数做友元
 *      类做友元
 *      成员函数做友元
*/
// 1. 全局函数做友元
#include <iostream>
using namespace std;
// 建筑类
class Building {
    // 函数的声明
    //goodGay 全局函数是 Building 的好朋友,加上 friend 关键字后就可以访问 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 *building;
};
class Building {
//    friend void GoodGay::visit();
    //GoodGay 类是本来的好朋友,可以访问本类中的私有成员
    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();
    // 让 visit 函数可以访问 Building 中的私有成员
    void visit();
    // 让 visit2 函数不可以访问 Building 中的私有成员
    void visit2();
    Building *building;
};
class Building {
    // 告诉编译器 GoodGay 类下的 visit 成员函数作为本类的好朋友,可以访问私有成员
    friend void GoodGay::visit();
public:
    Building();
public:
    // 客厅
    string m_SittingRoom;
private:
    // 卧室
    string m_BedRoom;
};
// 类外实现成员函数
GoodGay::GoodGay() {
    // 创建 Building
    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;
//    cout << "GoodGay.visit2 () 函数正在访问" << building->m_BedRoom << 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:
    // 1. 成员函数重载 + 号
//    Person operator+(Person &p) {
//        Person temp;
//        temp.m_A = this->m_A + p.m_A;
//        temp.m_B = this->m_B + p.m_B;
//        return temp;
//    }
    int m_A;
    int m_B;
};
// 2. 全局函数重载 + 号
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.operator+(p2);
    // 全局函数重载本质调用
//    Person p3 = operator+(p1,p2);
    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;
    }
    // 重载后置 ++ 运算符
    //int 代表占位参数,可以用于区分前置和后置递增
    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) {
        // 编译器是提供浅拷贝
//        m_Age = p.m_Age;
        // 应该先判断是否有属性在堆区,如果有就需要先释放干净,然后在进行深拷贝
        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;
//        m_C = 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;
        // 父类中私有成员,子类依旧无法访问
//        m_C = 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;// 父类中保护成员,到子类中变为私有权限
        //m_C = 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() {
    // 父类中所有非静态成员属性都会被子类继承下去
    // 父类中私有成员属性,是被编译器给隐藏了,因此无法访问,但是确实是被继承到了子类中
    // 16 字节
    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() {
//    test01();
    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() {
    // 1. 通过对象访问
    cout << "通过对象进行访问:" << endl;
    Son s;
    cout << "Son  下 m_A = " << s.m_A << endl;
    cout << "Base 下 m_A = " << s.Base::m_A << endl;
    // 2. 通过类名访问
    cout << "通过类名进行访问:" << endl;
    cout << "Son  下 m_A = " << Son::m_A << endl;
    // 第一个::代表通过类名方式访问,第二个::代表访问父类作用域下的成员函数
    cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}
// 同名静态成员函数
void test02() {
    // 1. 通过对象访问
    cout << "通过对象访问:" << endl;
    Son son;
    son.func();
    son.Base::func();
    // 2. 通过类名访问
    cout << "通过类名访问:" << endl;
    Son::func();
    Base::func();
    // 如果子类先访问父类再访问函数也可以
    Son::Base::func();
    // 子类出现和父类同名静态成员函数,也会隐藏父类中所有同名成员函数
    // 如果想访问父类中被隐藏同名成员,需要添加父类作用域
    Son::Base::func(100);
}
int main() {
//    test01();
    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;
};
// 利用虚继承,来解决菱形继承的问题
// 在继承之前,加上关键字 virtual 就会变为虚继承
// Animal 类也就被称为虚基类
// 羊类继承动物
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;
}
// 利用多态实现计算器
// 实现计算器抽象类
// 多态的好处就是:1. 组织结构清晰 2. 可读性强 3. 对于前期和后期扩展以及维护性高
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;
// 利用多态实现计算器
// 实现计算器抽象类
// 多态的好处就是:1. 组织结构清晰 2. 可读性强 3. 对于前期和后期扩展以及维护性高
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:
    // 纯虚函数
    // 只有一个纯虚函数,这个类称为抽象类
    // 抽象类特点:1. 无法实例化对象 2. 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
    virtual void func() = 0;
};
class Son : public Base {
public:
    virtual void func() {
        cout << "func函数调用" << endl;
    }
};
void test01() {
//    Base b;   // 抽象类是无法实例化对象
//    new Base; // 抽象类是无法实例化对象
//    Son s; // 子类必须重写父类中的纯虚函数,否则无法实例化对象
    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;
// CPU 抽象类
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() {
        // 释放 CPU 零件
        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 零件的指针
    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(){
    // 1. 包含头文件 fstream
    // 2. 创建流对象
    ofstream ofs;
    // 3. 指定打开方式
    ofs.open("test.txt",ios::out);
    // 4. 写内容
    ofs << "姓名:张三" << endl;
    ofs << "性别:男" << endl;
    ofs << "年龄:18" << endl;
    // 5. 关闭文件
    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() {
    // 1. 包含头文件
    // 2. 创建流对象
    ifstream ifs;
    // 3. 打开文件
    ifs.open("test.txt", ios::in);
    if (!ifs.is_open()) {
        cout << "文件打开失败" << endl;
        return;
    }
    // 4. 读数据
    // 第一种
//    char buf[1024] = {0};
//    while (ifs >> buf) {
//        cout << buf << endl;
//    }
    // 第二种
//    char buf2[1024] = {0};
//    while (ifs.getline(buf2, sizeof(buf2))) {
//        cout << buf2 << endl;
//    }
    // 第三种
    string buf;
    while (getline(ifs,buf)){
        cout << buf << endl;
    }
    // 第四种
//    char c;
//    while ((c = ifs.get())!=EOF){
//        cout << c << endl;
//    }
    // 5. 关闭文件
    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(){
    // 1. 包含头文件
    // 2. 创建流对象
    ofstream ofs("person.txt",ios::out | ios::binary);
    // 3. 打开文件
//    ofs.open("person.txt",ios::out | ios::binary);
    // 4. 写文件
    Person p = {"张三",18};
    ofs.write( (const char *)&p,sizeof(Person));
    // 5. 释放资源
    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() {
    // 1. 包含头文件
    // 2. 创建流对象
    ifstream ifs("person.txt", ios::in | ios::binary);
    // 3. 打开文件,并且判断文件是否打开成功
//        ifs.open("person.txt",ios::in | ios::binary);
    if (!ifs.is_open()) {
        cout << "文件打开失败" << endl;
        return;
    }
    // 4. 读取数据
    Persion p;
    ifs.read((char *) &p, sizeof(Persion));
    cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
    // 5. 释放资源
    ifs.close();
}
int main() {
    test01();
}

总结:文件输入流对象,可以通过 read 函数,以二进制方式读取数据。

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Light Rain 微信支付

微信支付