Cpp类和对象

Posted by LemonWhale on May 15, 2024
  • C++面向对象的三大特性为:封装、继承和多态
  • 万事万物皆为对象,对象上有其属性和行为
  • 具有相同性质的对象可以抽象成一个类

    一、封装

    1.1 封装的意义
  • 将属性和行为作为一个整体,表现生活中的事物
  • 类在设计的时候,可以把属性和行为放在不同的权限下,加以控制
名称 权限 访问权限 备注
public 公共权限 成员类内可以访问,类外也可以访问  
protected 保护权限 成员类内可以访问,类外不可以访问 儿子可访问父亲中的保护内容
private 私有权限 成员类内可以访问,类外不可以访问 儿子不可访问父亲中的保护内容

继承的时候用

eg.设计一个类,求出圆的周长:

// 公式:2*Pi*半径
#include <iostream>
using namespace std;

#define PI 3.14

class Circle {
    // 访问权限
    // 公共权限
public:
    // 属性
    // 半径
    int m_r;

    // 行为
    // 获取圆的周长
    double calculacteZC() {
        return 2 * PI * m_r;
    }
};

int main() {
    // 通过圆类,创建具体的圆(对象)
    // 实例化:通过一个类创建一个对象的过程
    Circle cl;

    // 给圆对象的属性进行赋值
    cl.m_r = 10;

    cout << "圆的周长为:" << cl.calculacteZC() << endl;

    system("pause");
    return 0;
}

e.g.设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生的姓名和学号

#include <iostream>
#include <string>
using namespace std;

class Student {
    // 定义权限
public:
    string m_name;
    string m_ID;

    void setName(string name) {
        m_name = name;
    }
    void setID(string ID) {
        m_ID = ID;
    }

    void showStu() {
        cout << "学生的姓名为:" << m_name << endl;
        cout << "学生的学号为:" << m_ID << endl;
    }
};

int main() {
    Student stu;
    stu.setName("张三");
    stu.setID("2022201293");

    stu.showStu();
    return 0;
}
  • 类中的属性和行为都称为成员
  • 属性:成员属性 成员变量
  • 行为:成员函数 成员方法
    1.2 struct和class的区别
  • 唯一的区别在于默认的访问权限不同

| struct | class | | :—-: | :—: | | 默认公共 | 默认私有 |

1.3 成员属性设置为私有
  • 优点1:可以自己控制读写权限 设置setValue()getValue函数控制读写权限;
  • 优点2:对于写权限,我们可以检测数据的有效性 ```Cpp #include #include using namespace std;

class Person { private: string m_Name; int m_Age; int m_ID = 2022201;

public: // 可读可写 void setName(string name) { m_Name = name; } string getName() { return m_Name; }

// 只写
int setAge(int age) {
    // 验证数据的有效性
    if (age < 0 || age > 150) {
        cout << ((age < 0) ? "输入的年龄为负数,请重新输入:" : "输入的年龄大于150,请重新输入:") << endl;
        return -1;
    }
    m_Age = age;
    return 1;
}

// 只读
int getID() {
    return m_ID;
} };

int main() { Person p1;

string name;
cout << "请输入此人姓名:" << endl;
cin >> name;
p1.setName(name);
cout << "此人名字为:" << p1.getName() << endl;

int ret = -1;
int age = 0;
cout << "请输入此人年龄:" << endl;
while (ret < 0) {
    cin >> age;
    ret = p1.setAge(age);
}

cout << "此人的id为:" << p1.getID() << endl;

system("pause");
return 0; } ```

e.g.1、设计一个长方体类 e.g.2、点和圆的关系

  • 在类中可以让另一个类作为本来的类的成员
  • 类和主函数的分文件编写

    二、对象的初始化和清理

  • 每个对象都有初始设置以及对象销毁前的清理数据的设置
    2.1 构造函数和析构函数

    对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。

  • 析构函数:主要作用在于创建对象时为对象的成员属性赋值,由编译器自动调用,无须手动调用;
    • 构造函数语法:类名(){}
      1. 构造函数没有返回值也不用写void
      2. 函数名称与类名相同
      3. 构造函数可以有参数,因此可以发生重载
      4. 程序在调用对象时会自动调用构造函数,无须手动调用,而且只会调用一次
  • 析构函数:主要作用在于对象的销毁前系统自动调用,执行一些清理工作。
    • 析构函数语法:~类名(){}
      1. 析构函数没有返回值也不写void
      2. 函数名称与类名相同,在名称前加上符号~
      3. 析构函数不可以有参数,因此不可以发生重载
      4. 程序在对象销毁前会自动调用析构函数,无须手动调用,而且只会调用一次
        2.2 构造函数的分类以及调用
      1. 构造函数分类: 1. 按照参数分类:有参构造和无参构造,无参又称为默认函数构造
        2. 按照类型分类:普通构造和拷贝构造
      2. 构造函数的调用 1. 括号法 2. 显式法 3. 隐式转换法
#include<iostream>
using namespace std;

class Person{
public:
	// 无参构造,也称默认构造函数
	Person(){
		cout << "使用默认构造函数" << endl;
	}
	
	// 有参构造函数
	Person(int a){
		age = a;
		cout << "使用有参构造函数" << endl;
	}
	
	// 拷贝构造函数,使用const修饰变量,一定要使用引用传参
	Person(const Person &p){
		age = p.age;
		cout << "使用拷贝构造函数" << endl;
	}
	
	// 析构函数,无参,不可重载
	~Person(){
		cout << "使用析构函数" << endl;
	}
private:
	int age = 18;
};

void test01(){
	// 1. 括号法调用
	Person p1;
	Person p2(10);
	Person p3(p2);
	// 注意:使用默认构造函数不能使用括号,否则会被认为是一个函数声明
	// Person p1(); 错误的
}

void test02(){
	//2. 显式法调用
	Person p1 = Person(10);
	Person p2 = Person(p2);
	// Person(10)单独写叫做匿名对象,当前行结束之后就马上析构
}

void test03(){
	// 3. 隐式转换法
	Person p1 = 10; // Person p1 = Person(10);
	Person p2 = p1;
	// 注意:不能利用拷贝构造函数初始化匿名对象,否则编译器认为是对象声明
	// Person(p1) === Person p1
}

int main(){
	test01();
	test02();
	test03(); 
	system("pause");
	return 0;
}

⚠注意事项:

  1. 调用默认构造函数时,不要加(),否则编译器认为是函数声明
  2. 不能利用拷贝构造函数初始化匿名对象,否则编译器认为是对象声明
    2.3 拷贝构造函数的调用时机
  3. 使用一个已经创建完毕的对象来初始化一个新对象
  4. 值传递的方式给函数参数传值
  5. 值方式返回局部对象 ```Cpp #include using namespace std;

class Person { public: Person() { cout « “调用了默认构造函数” « endl; }

Person(int a) {
    age = a;
    cout << "调用了有参构造函数" << endl;
}
// 1. 使用一个已经创建完毕的对象来初始化一个新对象
Person(const Person &p1) {
    age = p1.age;
    cout << "调用了拷贝构造函数" << endl;
}

~Person() {
    cout << "调用了析构函数" << endl;
}

private: int age = 18; };

void doWork01(Person p) { return; }

Person doWork02() { Person p; // 3. 值方式传回局部对象调用了拷贝构造函数 return p; }

void test() { Person p = doWork02(); }

int main() { // Person p(10); // 2. 传递值时调用了拷贝构造函数 // doWork01(p); test();

return 0; } #include <iostream> using namespace std;

class Person { public: Person() { cout « “调用了默认构造函数” « endl; }

Person(int a) {
    age = a;
    cout << "调用了有参构造函数" << endl;
}
// 1. 使用一个已经创建完毕的对象来初始化一个新对象
Person(const Person &p1) {
    age = p1.age;
    cout << "调用了拷贝构造函数" << endl;
}

~Person() {
    cout << "调用了析构函数" << endl;
}

private: int age = 18; };

void doWork01(Person p) { return; }

Person doWork02() { Person p; // 3. 返回值时调用了拷贝构造函数 // 实际调用时根本没有使用拷贝构造函数,不知道为啥??? return p; }

void test() { Person p = doWork02(); }

int main() { // Person p(10); // 2. 传递值时调用了拷贝构造函数 // doWork01(p); test();

return 0; } ``` ##### 2.4 构造函数调用规则 - 默认情况,c++编译器至少给一个类添加三个函数:
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝 - 构造函数调用规则如下:
- 如果用户定义有参构造函数,c++不再提供默认无参构造函数,但是会有默认拷贝构造函数
- 如果用户定义拷贝构造函数,c++不会再提供其他构造函数 ##### 2.5 深拷贝与浅拷贝 - 浅拷贝:简答的赋值拷贝操作 - 深拷贝:在堆区重新申请空间,进行拷贝操作  ```Cpp #include <iostream> using namespace std;

class Person { public: Person() { cout « “Person 默认构造函数” « endl; } Person(int age, int height) { m_age = age; m_Height = new int(height); cout « “Person 有参构造函数” « endl; } Person(const Person &p) { m_age = p.m_age; // m_Height = p.m_Height; // 编译器自动实现的浅拷贝 m_Height = new int(*p.m_Height); // 深拷贝 cout « “Person 拷贝构造函数调用” « endl; }

~Person() {
    // 将堆区开辟的数据释放干净
    if (m_Height != NULL) {
        delete m_Height;
        m_Height = NULL;
    }
    cout << "调用了析构函数" << endl;
}

int m_age = 0;
int *m_Height; };

void test() { // 实际上在使用浅拷贝时,编译器只使用了一次析构函数而且没有报错,不知道为啥??? // cout « “this is a test.” « endl; Person p1(10, 180); cout « “p1的年龄为:” « p1.m_age « ” “ « “p1的身高为:” « *p1.m_Height « endl; Person p2(p1); cout « “p2的年龄为:” « p2.m_age « ” “ « “p2的身高为:” « *p2.m_Height « endl; }

int main() { test(); system(“pause”); return 0; }

**疑问**:不知为何,在使用浅拷贝时,编译器只调用了一次析构函数,而在深拷贝时则调用了两次析构函数,怀疑时编译器对代码的优化。
##### 2.6 初始化列表
- 语法:构造函数():变量1(值),变量2(值)...{}
```Cpp
#include <iostream>
using namespace std;

class Person {
public:
    // 传统构造方法
    // Person(int a, int b, int c) {
    //     m_A = a;
    //     m_B = b;
    //     m_C = c;
    // }

    // 初始化列表构造方法
    Person(int a, int b, int c) :
        m_A(a), m_B(b), m_C(c) {
    }
    int getA() {
        return m_A;
    }
    int getB() {
        return m_B;
    }
    int getC() {
        return m_C;
    }

private:
    int m_A;
    int m_B;
    int m_C;
};

int main() {
    Person p(10, 20, 30);
    cout << p.getA() << endl
         << p.getB() << endl
         << p.getC() << endl;
}

三、C++对象模型和this指针

3.1 类对象作为类成员
  • c++类中的成员可以时另一个类的成员,我们称该成员为对象成员 ```Cpp #include #include using namespace std;

class Phone { public: Phone(string PName) { m_PName = PName; cout « “调用了Phone的构造函数” « endl; } ~Phone() { cout « “调用了Phone的析构函数” « endl; }

string m_PName; };

class Person { public: Person(string name, string PName) : m_Name(name), PName(PName) { cout « “调用了Person的构造函数” « endl; }

~Person() {
    cout << "调用了Person的析构函数" << endl;
}

string m_Name;
Phone PName; };

void test() { Person p(“张三”, “iPhone 15 ProMax”); cout « “p的姓名为:” « p.m_Name « “p的手机为:” « p.PName.m_PName « endl; }

int main() { test(); system(“pause”); return 0; }

输出为:    
```Cpp
调用了Phone的构造函数
调用了Person的构造函数
p的姓名为:张三p的手机为:iPhone 15 ProMax
调用了Person的析构函数
调用了Phone的析构函数
Press any key to continue . . . 
  • 由输出结果可知,创建类的时候先创建成员对象的类,再创建本身,析构的顺序相反
    3.2 静态成员
  • 静态成员就是在成员变量和成员函数前加上static关键字
  • 静态成员变量:
    • 所有对象共享同一份数据
    • 在编译阶段分配内存,在全局区
    • 类内声明,类外初始化
  • 静态成员函数
    • 所有对象共享同一个函数
    • 静态成员函数只能防问静态成员变量
      3.3 成员变量和成员函数分别存储
  • 空对象占用一个字节
  • 非静态成员变量:属于类的对象上
  • 静态成员变量:不属于类的对象上
  • 非静态成员函数/静态成员函数:不属于类的对象上
    3.4 this指针概念
  • this指针指向被调用的成员函数所属的对象
  • this指针时隐含在每一个非静态成员函数中的一种指针,不需要定义,直接使用即可
  • 用途:
    • 当形参和成员变量同名时,可用this指针区分
    • 在类的非静态成员函数中返回对象本身,可使用return *this ```Cpp #include using namespace std;

class Person{
public:
// explicit 抑制隐式转换,一般用于只有一个参数的构造函数
explicit Person(int age){
// 1. 此处的this指针可以解决名称的冲突
this->age = age;
}

// 使用引用方式返回,否则将调用拷贝构造函数构造新的类  
Person& personAddAge(Person &p){  
    this->age += p.age;  
    // 返回本身  
    return *this;  
}  
  
int age;   };  

void test01(){
Person p(10);
cout « “p的年龄为:” « p.age « endl;
}

void test02(){
Person p1(10);
Person p2(11);

// 链式编程的思想
p1.personAddAge(p2).personAddAge(p2);  
cout<<p1.age<<endl;   }  

int main(){
// test01();
test02();
system(“pause”);
return 0;
}

- 返回是一个值,创建新的对象;
- 返回引用则返回本身
##### 3.5 空指针访问成员函数
- 空指针也可以访问成员函数,但是不能访问含有成员变量的函数
```Cpp 
#include <iostream>  
using namespace std;  
  
class Person {  
public:  
    void showClass() {  
        cout << "this ia a Person class." << endl;  
    }  
    void showAge() {  
	    // 防止空指针报错
        if (this == NULL) {  
            cout << "当前指针为空" << endl;  
            return;  
        }  
        cout << m_Age << endl;  
    }  
    int m_Age;  
};  
  
void test() {  
    Person *p = NULL;  
    p->showClass();  
    p->showAge();  
}  
  
int main() {   
    test();  
    return 0;  
}
3.6 const修饰成员函数
  • 常函数:
    • 成员函数后加const后我们称之为常函数
    • 常函数内不可以修改成员属性
    • 成员属性声明时加关键字mutable后,在常函数中依然可以修改
  • 常对象
    • 声明对象前加const称之为常对象
    • 常对象只能调用常函数
#include <iostream>  
using namespace std;  
  
class Person {  
public:  
    // this 指针的本质是 指针常量,指针的指向是不可以修改的  
    // Person * const this;  
    // 在成员函数后边加const,修饰的时this指向,让指针指向的值也不可以修改  
    void showPerson() const {  
        //        this = NULL; // 错误的  
        //        this->m_A = 10; // 加上const之后值不可以修改  
        this->m_B = 10; // 加上mutable关键字就可以修改了  
    }  
    void func() {  
        m_A = 21;  
    }  
    int m_A = 10;  
    mutable int m_B = 5;  
};  
  
void test() {  
    Person p;  
    p.showPerson();  
}  
  
void test02() {  
    const Person p1; // 常对象  
    //    p.m_A = 10; // 不可修改  
    p1.m_B = 100; // mutable修饰的变量可以修改  
  
    // 常对象只能调用常函数  
    p1.showPerson();  
    //    p1.func(); // 常对象不可以调用普通成员函数,因为普通成员函数可以修改属性  
}  
  
int main() {  
    test();  
    test02();  
    return 0;  
}

四、友元

  • 在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术
  • 目的:让一个函数或者类访问另一个类中私有成员
  • 关键字:friend
  • 友元的三种实现:
    1. 全局函数作友元
    2. 类作友元
    3. 成员函数作友元
      4.1 全局函数作友元
#include <iostream>  
#include <string>  
using namespace std;  
  
class Building {  
    // 全局变量作友元  
    friend void goodFriend(Building *b);  
  
public:  
    Building() {  
        m_SittingRoom = "客厅";  
        m_BedRoom = "卧室";  
    }  
  
    string m_SittingRoom;  
  
private:  
    string m_BedRoom;  
};  
  
void goodFriend(Building *b) {  
    cout << "好朋友正在参观:" << b->m_SittingRoom << endl;  
    cout << "好朋友正在参观:" << b->m_BedRoom << endl;  
}  
void test() {  
    Building b;  
    goodFriend(&b);  
}  
  
int main() {  
    system("chcp 65001");  
    test();  
    return 0;  
}
4.2 类作友元
#include <iostream>  
#include <string>  
using namespace std;  
  
class Building {  
    friend class goodFriend;  
  
public:  
    Building();  
    string m_SittingRoom;  
  
private:  
    string m_BedRoom;  
};  
  
// 函数的类外实现  
Building::Building() {  
    m_SittingRoom = "客厅";  
    m_BedRoom = "卧室";  
}  
  
class goodFriend {  
public:  
    goodFriend();  
    void visit();  
    Building *building;  
};  
  
// 函数的类外实现  
goodFriend::goodFriend() {  
    building = new Building;  
}  
  
void goodFriend::visit() {  
    cout << "visit 正在访问:" << building->m_SittingRoom << endl;  
    cout << "visit 正在访问:" << building->m_BedRoom << endl;  
}  
  
void test() {  
    goodFriend gf;  
    gf.visit();  
}  
  
int main() {  
    system("chcp 65001");  
    test();  
    return 0;  
}
4.3 成员函数作友元
#include <iostream>  
#include <string>  
using namespace std;  
  
// 先声明一下Building类,不会报错  
class Building;  
  
class goodFriend {  
public:  
    goodFriend();  
    void visit01();  
    void visit02();  
  
private:  
    Building *building;  
};  
  
class Building {  
    // visit01作友元  
    friend void goodFriend::visit01();  
  
public:  
    Building();  
  
public:  
    string m_SittingRoom;  
  
private:  
    string m_BedRoom;  
};  
  
Building::Building() {  
    m_SittingRoom = "客厅";  
    m_BedRoom = "卧室";  
}  
  
goodFriend::goodFriend() {  
    building = new Building;  
}  
  
// 成员函数作友元  
void goodFriend::visit01() {  
    cout << "visit01 正在访问:" << building->m_SittingRoom << endl;  
    cout << "visit01 正在访问:" << building->m_BedRoom << endl;  
}  
  
// 成员函数不作友元  
void goodFriend::visit02() {  
    cout << "visit02 正在访问:" << building->m_SittingRoom << endl;  
}  
  
void test() {  
    goodFriend gf;  
    gf.visit01();  
    gf.visit02();  
}  
  
int main() {  
    system("chcp 65001");  
    test();  
    return 0;  
}

五、运算符重载

  • 运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
  • 对于内置的数据类型,编译器知道如何进行运算
    5.1 加号的运算符重载
  • 重载运算符的方式
    • 成员函数重载运算符
    • 全局函数重载运算符
  • 重载运算符 也可以进行函数重载 ```Cpp #include using namespace std;

class Person {
public:
int m_A;
int m_B;

Person() {  
}  
Person(int a, int b) {  
    m_A = a;  
    m_B = b;  
}  
  
//    成员函数重载加号运算符  
Person operator+(Person &p) {  
    Person temp;  
    temp.m_A = p.m_A + this->m_A;  
    temp.m_B = p.m_B + this->m_B;  
    return temp;  
}  
  
//    运算符重载可以发生函数重载  
Person operator+(int num) {  
    Person temp;  
    temp.m_A = this->m_A + num;  
    temp.m_B = this->m_B + num;  
    return temp;  
}   };  

// 全局函数进行运算符重载
// 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;
//}

void test() {
Person p1(10, 10);
Person p2(10, 10);

int num = 10;  
  
// 成员函数重载调用的本质为 p3 = p1.operator+(p2)    Person p3 = p1 + p2;  
Person p4 = p1 + 100;  
  
// 全局函数重载调用的本质为:p5 = operator+(p1, p2)  
//    Person p5 = p1 + p2;  
cout << "p3.m_A = " << p3.m_A << endl;  
cout << "p3.m_B = " << p3.m_B << endl;  
  
cout << "p4.m_A = " << p4.m_A << endl;  
cout << "p4.m_B = " << p4.m_B << endl;  
//    cout << "p5.m_A = " << p5.m_A << endl;  
//    cout << "p5.m_B = " << p5.m_B << endl;}  

int main() {
test();
return 0;
}

- 对于内置的数据类型的表达式的运算符是不可能改变的
- 不要滥用运算符重载

##### 5.2 左移运算符重载
-  重载左移运算符配合友元可以实现输出自定义数据类型
- 通常不用成员函数重载`<<`运算符,会使`cout`在右侧
```Cpp
#include <iostream>  
using namespace std;  
  
class Person {  
	// 重载函数作友元
    friend ostream &operator<<(ostream &cout, Person &p);  
  
public:  
    Person(int a, int b) :  
        m_A(a), 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;
    // 返回cout,保证可以继续输出(链式编程)  
    return cout;  
};  
  
void test() {  
    Person p(10, 10);  
    cout << p << endl  
         << "Test Successfully!" << endl;  
}  
  
int main() {  
    test();  
    return 0;  
}

如果使用成员函数重载:

#include <iostream>  
using namespace std;  
  
class Person {  
    // 重载函数作友元  
    friend ostream &operator<<(ostream &cout, Person &p);  
  
public:  
    Person(int a, int b) :  
        m_A(a), m_B(b) {  
    }    ostream &operator<<(ostream &cout) {  
        cout << "m_A = " << this->m_A << " " << "m_B = " << this->m_B;  
        // 返回cout,保证可以继续输出(链式编程)  
        return cout;  
    }  
  
private:  
    int m_A;  
    int m_B;  
};
  
void test() {  
    Person p(10, 10);  
    p << cout; // cout在右边
}  
  
int main() {  
    test();  
    return 0;  
}
5.3 递增运算符重载
#include <iostream>  
using namespace std;  
class MyInteger {  
    friend ostream &operator<<(ostream &cout, MyInteger num);  
  
public:  
    MyInteger() {  
        m_num = 0;  
    }  
    // 重载前置++运算符,返回引用  
    MyInteger &operator++() {  
        m_num++;  
        return *this;  
    }  
  
    // 重载后置++运算符,返回值,使用int占位参数区分前置++和后置++  
    // 返回值是因为返回的temp是一个临时参数,用后即销毁  
    MyInteger operator++(int) {  
        MyInteger temp = *this;  
        m_num++;  
        return temp;  
    }  
  
private:  
    int m_num;  
};  
  
ostream &operator<<(ostream &cout, MyInteger num) {  
    cout << "MyInteger : " << num.m_num;  
    return cout;  
}  
  
void test() {  
    MyInteger num;  
    cout << num++ << endl;  
    cout << ++num << endl;  
}  
  
int main() {  
    test();  
    return 0;  
}
  • 重载前置 ++ 运算符返回引用,重载后置 ++ 运算符返回值
  • 重载 – 运算符同样操作
5.4 赋值运算符重载
  • c++编译器至少给一个类添加4个函数
    1. 默认构造函数
    2. 默认析构函数
    3. 默认拷贝构造函数
    4. 赋值运算符 operator= ,对属性进行拷贝 ```Cpp #include using namespace std;

class Person {
public:
explicit Person(int age) {
m_Age = new int(age);
}

// 浅拷贝会导致堆释放时出现重复释放的问题,所以重写赋值运算符  
Person &operator=(Person &p) {  
    if (m_Age != NULL) {  
        delete m_Age;  
        m_Age = NULL;  
    }  
    m_Age = new int(*p.m_Age);  
  
    return *this;  
}  
  
~Person() {  
    if (m_Age != NULL) {  
        delete m_Age;  
        m_Age = NULL;  
    }  
}  

public:
int *m_Age;
};

void test() {
Person p1(10);
Person p2(18);

p2 = p1;  
  
cout << "p1的年龄为:" << *p1.m_Age << endl;  
cout << "p2的年龄为:" << *p2.m_Age << endl;   }  

int main() {
system(“chcp 65001”);
test();
system(“pause”);
return 0;
}


##### 5.5 关系运算符重载
```Cpp
#include <iostream>  
#include <string>  
using namespace std;  
  
class Person {  
public:  
    Person(string name, int age) {  
        m_Name = name;  
        m_Age = age;  
    }  
  
    // 重载==  
    bool operator==(Person &p) {  
        if (this->m_Age == p.m_Age && this->m_Name == p.m_Name) {  
            return true;  
        }  
        return false;  
    }  
  
    // 重载!=  
    bool operator!=(Person &p) {  
        if (this->m_Age != p.m_Age || this->m_Name != p.m_Name) {  
            return true;  
        }  
        return false;  
    }  
  
private:  
    string m_Name;  
    int m_Age;  
};  
  
void test() {  
    Person p1("张三", 19);  
    Person p2("张三", 19);  
    Person p3("李四", 19);  
  
    cout << ((p1 == p2) ? "相等" : "不相等") << endl;  
    cout << ((p1 == p3) ? "相等" : "不相等") << endl;  
}  
  
int main() {  
    system("chcp 65001");  
    test();  
    return 0;  
}
5.6 函数调用运算符重载
  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此被称为仿函数
  • 仿函数没有固定写法,非常灵活 ```Cpp #include using namespace std;

class MyPrint {
public:
void operator()(string text) {
cout « text « endl;
}
};

class MyAdd {
public:
int operator()(int a, int b) {
return a + b;
}
};

void test() {
MyPrint print;
// 仿函数调用
print(“Hello, world!”);
MyAdd add;
// 仿函数调用
int ret = add(10, 15);
cout « ret « endl;

// 匿名函数对象  
cout << MyAdd()(10, 20) << endl;   }  

int main() {
test();
return 0;
}


#### 六、继承
- 继承的好处:减少代码量
##### 6.1 继承的基础语法
- 基础语法:`class 子类:继承方式 父类`
- 父类又称基类
- 子类又称派生类
```Cpp
#include <iostream>  
using namespace std;  
  
class basePage {  
public:  
    void Top() {  
        cout << "首页 注册 登录" << endl;  
    }  
    void Left() {  
        cout << "Python C++ JAVA" << endl;  
    }  
    void Bottom() {  
        cout << "联系我们 相关网页" << endl;  
        cout << "----------------" << endl;  
    }  
};  
  
class Python : public basePage {  
public:  
    void content() {  
        cout << "这是Python课程" << endl;  
    }  
};  
  
class JAVA : public basePage {  
public:  
    void content() {  
        cout << "这是JAVA课程" << endl;  
    }  
};  
  
class Cpp : public basePage {  
public:  
    void content() {  
        cout << "这是Cpp课程" << endl;  
    }  
};  
  
void test() {  
    JAVA ja;  
    ja.Top();  
    ja.Left();  
    ja.content();  
    ja.Bottom();  
  
    Cpp c;  
    c.Top();  
    c.Left();  
    c.content();  
    c.Bottom();  
  
    Python py;  
    py.Top();  
    py.Left();  
    py.content();  
    py.Bottom();  
}  
  
int main() {  
    test();  
    return 0;  
}
6.2 继承方式
  • 公共继承
  • 保护继承
  • 私有继承 三种继承方式的区别:

| 继承方式/权限 | public | protected | private | | :———: | :——-: | :——-: | :—–: | | 公共继承 | public | protected | unavail | | 保护继承 | protected | protected | unavail | | 私有继承 | private | private | unavail |

  • 保护权限子类可以访问,类外不可以访问
  • 私有权限子类不可以访问,类外也不可以访问
    6.3 继承中的对象模型
  • 父类中所有非静态成员属性都会被子类继承下去
  • 父类中私有成员属性是被编译器隐藏了,所以访问不到,但是确实是被继承下去了
  • 利用vs开发人员命令提示工具查看对象模型 切换到相应目录下:cl /dl reportSingleClassLayout类名 文件名
    6.4 构造和析构顺序
  • 子类继承父类后,当创建子类对象,也会调用父类的构造函数
  • 继承中的构造顺序:先父后子
  • 继承中的析构顺序:先子后父
    6.5 同名成员处理
  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域
  • 当子类与父类拥有同名的成员函数,子类会隐藏掉父类中所有同名的成员函数,想要访问父类的成员函数需要加作用域
    6.6 继承同名静态成员处理方式
  • 静态成员和非静态成员处理方式相同
      • 访问子类同名成员 直接访问即可
    • 访问父类同名成员 需要加作用域
    • 当子类与父类拥有同名的成员函数,子类会隐藏掉父类中所有同名的成员函数,想要访问父类的成员函数需要加作用域 ```Cpp #include using namespace std;

class Base {
public:
static void func() {
cout « “Base - static func()” « endl;
}
// 静态变量,类内创建,类外赋值 static int m_A;
};

// 静态变量,类内创建,类外赋值 int Base::m_A = 100;

class Son : public Base {
public:
static void func() {
cout « “Son - static func()” « endl;
}
static int m_A;
};
int Son::m_A = 20;

void test() {
Son son;

cout << "通过对象访问:" << endl;  
cout << "Son m_A =  " << son.m_A << endl;  
cout << "Base m_A = " << son.Base::m_A << endl;  
son.func();  
son.Base::func();  
  
cout << "通过类名访问:" << endl;  
cout << "Son m_A =  " << Son::m_A << endl;  
// 第一个::是通过Son类名访问,第二个::是Base作用域  
cout << "Base m_A = " << Son::Base::m_A << endl;  
Son::func();  
Son::Base::func();   }  

int main() {
test();
return 0;
}

##### 6.7 多继承语法
- 语法:`class 子类:继承方式 父类1,继承方式 父类2 ...`
- 实际开发中不建议使用,容易引起同名冲突
- 如果两个父类出现了同名情况,则子类中需要加上作用域
##### 6.8 菱形继承
- 两个派生类继承自同一个基类
- 又有两个类同时继承这两个派生类
- 这种继承方式叫做菱形继承,也叫做钻石继承
e.g.     
动物类
	羊  (动物)
	骆驼(动物)
		羊驼(羊、骆驼)

菱形继承的问题:    
1. 羊继承了动物的数据,骆驼也继承了动物的数据,当羊驼使用数据时就会产生二义性;
2. 羊驼继承自动物的数据有两份,实际上只需要一份
- 利用虚继承的方法可以解决这些问题:virtual,最大的基0类称为虚基类
```Cpp
#include <iostream>  
using namespace std;  
  
class Animal {  
public:  
    int m_Age;  
};  
  
// 虚继承,实际上是创建了一个指针指向父类,数据还是只有一份  
class Sheep : virtual public Animal {};  
class Camel : virtual public Animal {};  
class Alpaca : public Sheep, public Camel {};  
  
void test() {  
    Alpaca al;  
    al.Sheep::m_Age = 100;  
    al.Camel::m_Age = 200;  
    // 不利用虚继承的技术则会因为有两份m_Age数据而报错  
    cout << al.m_Age << endl;  
}  
  
int main() {  
    test();  
    return 0;  
}

七、多态

7.1 多态的基本概念

多态分为两类:

  1. 静态多态:函数重载 和 运算符重载 属于静态多态,复用函数名
  2. 动态多态:派生类 和 虚函数 实现运行时多态(C++中常说的多态) 静态多态和动态多态的区别:
  3. 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  4. 动态多态的函数地址晚绑定 - 运行阶段确定函数地址 ```Cpp #include using namespace std;

class Animal {
public:
// 地址早绑定
void speak() {
cout « “动物在说话” « endl;
}
};

class Cat : public Animal {
public:
void speak() {
cout « “喵喵喵” « endl;
}
};

void doSpeak(Animal &animal) {
animal.speak();
}

void test() {
Cat cat;
doSpeak(cat);
}

int main() {
test();
return 0;
}

以上函数会输出:    
```Cpp
动物在说话

原因是静态多态的地址是在编译的时候就已经编译好了,无论传入的参数是什么,都会执行Animal下的speak函数。

解决方法:给Animal中的speak函数加上virtual关键字,使之变成一个虚函数:

class Animal {  
public:  
    // 虚函数,地址晚绑定  
    virtual void speak() {  
        cout << "动物在说话" << endl;  
    }  
};

动态多态的条件:

  1. 有继承关系
  2. 子类重写父类的虚函数
    重写:返回值和参数列表完全相同

动态多态的使用:父类的指针或者引用执行子类对象

7.2 多态的原理剖析
class Animal {  
public:  
    // 虚函数,地址晚绑定  
    virtual void speak() {  
        cout << "动物在说话" << endl;  
    }  
};

Animal类内部结构:
vfptr - 虚函数(表)指针 —> 指向一个虚函数表 vftable
v - virtual
f - function
ptr - pointer
虚函数表中记录了虚函数的地址 &Animal::speak

class Cat : public Animal {  
public:  
    void speak() {  
        cout << "喵喵喵" << endl;  
    }  
};  

当Cat重写了speak函数之后:
Animal类内部结构:
vfptr - 虚函数(表)指针 —> 指向一个虚函数表 vftable
v - virtual
f - function
ptr - pointer
虚函数表中记录了虚函数的地址 &Cat::speak

当父类的指针或者引用指向子类对象的时候,发生多态

Animal &animal = cat;
animal.speak();

个人理解为重写之后的函数地址覆盖了父类的函数地址,达到调用时调用子类的函数的作用。

7.3 多态案例 计算器类
  • 分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类 多态的优点:
    1. 代码组织结构清晰
    2. 可读性强
    3. 利于前期和后期的扩展以及维护
      传统实现:
      ```Cpp #include using namespace std;

// 传统实现
class Calculator {
public:
Calculator(int num1, int num2) {
m_num1 = num1;
m_num2 = num2;
}
int Calculate(string sym) {
if (sym == “+”) {
return m_num1 + m_num2;
}
else if (sym == “-“) {
return m_num1 - m_num2;
}
else if (sym == “*”) {
return m_num1 * m_num2;
}
cout « “输入的运算符有误” « endl;
return 0;
}

private:
int m_num1;
int m_num2;
};

void test() {
Calculator cl(10, 10);
int ret = cl.Calculate(“*”);
cout « “The result of the calculating is “ « ret « endl;
}

int main() {
test();
return 0;
}

可见没有加入除法的算法,如果想要进行除法的添加则需要对类中的成员函数进行修改,但是**在真实的开发中提倡开闭原则:对扩展进行开发,对修改进行关闭**,传统的实现方法无法实现这个原则。    
```Cpp
#include <iostream>  
using namespace std;  
  
// 使用多态的技术实现  
class Calculator {  
public:  
    virtual int Calculate() {  
        return 0;  
    }  
  
    int m_num1;  
    int m_num2;  
};  
  
class addNum : public Calculator {  
public:  
    int Calculate() {  
        return m_num1 + m_num2;  
    }  
};  
  
class subNum : public Calculator {  
public:  
    int Calculate() {  
        return m_num1 - m_num2;  
    }  
};  
  
class multiNum : public Calculator {  
public:  
    int Calculate() {  
        return m_num1 * m_num2;  
    }  
};  
  
void test() {  
    // 父类的构造函数不能被子类继承  
    Calculator *cl = new addNum;  
    cl->m_num1 = 10;  
    cl->m_num2 = 20;  
    int ret = cl->Calculate();  
    cout << "The result of the calculating is " << ret << endl;  
    // 删除在堆区开辟的内存  
    delete cl;  
}  
  
int main() {  
    test();  
    return 0;  
}

使用多态的技术增加一个除法方法只需要写一个Calculator的派生类即可。

class divideNum : public Calculator {  
public:  
    int Calculate() {  
        return m_num1 / m_num2;  
    }  
};
7.4 纯虚函数和抽象类
  • 在多态中,父类的虚函数实现往往是毫无意义的,因此可以将虚函数改为纯虚函数
  • 纯虚函数语法:virtual 返回值类型 函数名(参数列表) = 0;
  • 当类中有了纯虚函数,这个类也称为抽象类

抽象类特点

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
    7.5 多态案例2 - 制作饮品
    1. 烧水
    2. 冲泡
    3. 倒入杯中
    4. 加入辅料 ```Cpp #include using namespace std;

// 创建制作饮品抽象类
class makeDrink {
public:
// 创建纯虚函数
virtual void Boil() = 0;
virtual void Brew() = 0;
virtual void Pour() = 0;
virtual void addOther() = 0;
};

// 创建茶类继承抽象类
class Tea : public makeDrink {
public:
// 重写纯虚函数
void Boil() {
cout « “boil the water” « endl;
}
void Brew() {
cout « “brew method” « endl;
}
void Pour() {
cout « “pour the water to the bottle” « endl;
}
void addOther() {
cout « “add the honey to the tea” « endl;
}
};

// 创建咖啡类继承抽象类
class Coffee : public makeDrink {
public:
// 重写纯虚函数
void Boil() {
cout « “boil the water” « endl;
}
void Brew() {
cout « “brew method” « endl;
}
void Pour() {
cout « “pour the water to the bottle” « endl;
}
void addOther() {
cout « “add the milk to the coffee” « endl;
}
};

void test() {
// 将父类的指针指向子类,形成多态
makeDrink *makeTea = new Tea;
makeTea->Boil();
makeTea->Brew();
makeTea->Pour();
makeTea->addOther();
cout « ”————————————” « endl;

// 将父类的指针指向子类,形成多态  
makeDrink *makeCoffee = new Coffee;  
makeCoffee->Boil();  
makeCoffee->Brew();  
makeCoffee->Pour();  
makeCoffee->addOther();   }  

int main() {
test();
return 0;
}


##### 7.6 虚析构和纯虚析构
- 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
- 解决方式:将父类中的析构函数改为虚析构或者纯虚析构
- 虚析构和纯虚析构共性:
	- 可以解决父类指针释放子类对象
	- 都需要有具体的函数实现
- 析构和纯虚析构区别:
	- 如果是纯虚析构,该类属于抽象类,无法实例化对象
##### 7.7 多态案例三 - 电脑组装
- 零件有:CPU、显卡、内存条
- 将每个零件封装成抽象基类,并且提供不同厂商生产不同的零件,例如Intel和Samsung
- 组装三台不同的电脑进行工作
```Cpp
#include <iostream>  
using namespace std;  
  
// 抽象CPU类  
class Cpu {  
public:  
    virtual void calculate() = 0;  
};  
  
// 抽象GPU类  
class Gpu {  
public:  
    virtual void display() = 0;  
};  
  
// 抽象Memory类  
class Memory {  
public:  
    virtual void storage() = 0;  
};  
  
// 具体CPU类  
class IntelCpu : public Cpu {  
public:  
    void calculate() {  
        cout << "IntelCpu 正在计算" << endl;  
    }  
};  
  
// 具体GPU类  
class IntelGpu : public Gpu {  
public:  
    void display() {  
        cout << "IntelGpu 正在显示" << endl;  
    }  
};  
  
// 具体Memory类  
class IntelMemory : public Memory {  
public:  
    void storage() {  
        cout << "IntelMemory 正在储存" << endl;  
    }  
};  
  
class SamsungCpu : public Cpu {  
public:  
    void calculate() {  
        cout << "SamsungCpu 正在计算" << endl;  
    }  
};  
  
class SamsungGpu : public Gpu {  
public:  
    void display() {  
        cout << "SamsungGpu 正在显示" << endl;  
    }  
};  
  
class SamsungMemory : public Memory {  
public:  
    void storage() {  
        cout << "SamsungMemory 正在储存" << endl;  
    }  
};  
  
// 创建电脑类  
class Computer {  
public:  
    // 使用指针传入参数,使父类指针指向子类,形成多态  
    Computer(Cpu *cpu, Gpu *gpu, Memory *memory) {  
        m_Cpu = cpu;  
        m_Gpu = gpu;  
        m_Mem = memory;  
    }  
  
    void Work() {  
        m_Cpu->calculate();  
        m_Gpu->display();  
        m_Mem->storage();  
    }  
  
    // 析构函数释放零件类  
    ~Computer() {  
        if (m_Cpu != NULL) {  
            delete m_Cpu;  
            m_Cpu = NULL;  
        }  
  
        if (m_Gpu != NULL) {  
            delete m_Gpu;  
            m_Gpu = NULL;  
        }  
  
        if (m_Mem != NULL) {  
            delete m_Mem;  
            m_Mem = NULL;  
        }  
    }  
  
private:  
    Cpu *m_Cpu;  
    Gpu *m_Gpu;  
    Memory *m_Mem;  
};  
  
void test() {  
    // 父类指针或者引用指向子类对象的时候,发生多态  
    // 创建电脑配件  
    Cpu *intelcpu = new IntelCpu;  
    Gpu *intelgpu = new IntelGpu;  
    Memory *intelmem = new IntelMemory;  
  
    Cpu *samsungcpu = new SamsungCpu;  
    Gpu *samsunggpu = new SamsungGpu;  
    Memory *samsungmem = new SamsungMemory;  
  
    // 组建第一台电脑  
    Computer *computer1 = new Computer(intelcpu, intelgpu, intelmem);  
    computer1->Work();  
    cout << "-------------" << endl;  
    delete computer1;  
    // 组建第二台电脑  
    Computer *computer2 = new Computer(samsungcpu, samsunggpu, samsungmem);  
    computer2->Work();  
    cout << "-------------" << endl;  
    delete computer2;  
    // 组建第三台电脑  
    Computer *computer3 = new Computer(intelcpu, intelgpu, samsungmem);  
    computer3->Work();  
    cout << "-------------" << endl;  
    delete computer3;  
}  
  
int main() {  
    test();  
    return 0;  
}