c++类和对象

2023-12-22 18:29:03

目录

一,对象的初始化和清理

1、构造函数和析构函数

2、构造函数的分类及调用

3、拷贝构造函数调用时机

4、构造函数调用规则

5、深拷贝与浅拷贝

6、初始化列表

7、类对象作为类成员

8、静态成员

二,对象模型和this指针

1、成员变量和成员函数分开存储

?2、this指针概念

3、空指针访问成员函数

4、const修饰成员函数

三、友元

1、全局函数作友元

2、类作友元

3、成员函数作友元

四、运算符重载

1、加号运算符重载

2、左移运算符重载

3、递增运算符重载

4、赋值运算符重载

5、关系运算符重载

6、函数调用运算符重载

?五、继承

1、继承基本语法

2、继承方式

3、继承中的对象模型

4、继承中的构造和析构顺序

5、继承同名成员处理方式

?6、继承同名静态成员处理方式

7、多继承语法

8、菱形继承

?六、多态

1.多态的基本概念

2、多态案例--计算器实现

?3、纯虚函数和抽象类

4、多态案例--制作饮品

5、虚析构和纯虚析构

6、多态案例--电脑组装


一,对象的初始化和清理

1、构造函数和析构函数

构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。

析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

构造函数语法:类名(){}

1,构造函数,没有返回值也不写void

2,函数名称与类名相同

3,构造函数可以有参数,因此可以发生重载

4,程序在调用对象时会自动调用构造,无须手动调用,而且只会调用一次

析构函数语法:~类名(){}

1,析构函数,没有返回值也不写void

2,函数名称与类名相同,在名称前加上符号~

3,析构函数不可以有参数,因此不可以发生重载

4,程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次

class Person
{
public:
    //1,1、构造函数
    Person()
    {
        cout<<"Person 的构造函数调用"<<endl;
    }
    //1,2、析构函数
    ~Person()
    {
        cout<<"Person 的析构函数调用"<<endl;
    }
};

void test01()
{
    Person A;//在栈上的数据,test01执行完毕后,释放这个对象,释放后才会调用析构函数
}
int main()
{
    test01();
    return 0;
}

2、构造函数的分类及调用

两种分类方式

按参数分为:有参构造和无参构造

按类型分为:普通构造和拷贝构造

三种调用方式

1,括号法? ?2,显示法? 3,隐式转换法

//构造函数的分类及调用
//分类
// 按照参数分类 无参构造(默认构造)和有参构造
// 按照类型分类  普通构造 拷贝构造 
class Person
{
public:
    //普通构造函数 无参构造
    Person()
    {
        cout<<"Person 的无参构造函数调用"<<endl;
    }
    Person(int a)//有参构造
    {
        age=a;
        cout<<"Person 的有参析构造函数调用"<<endl;
    }

    //拷贝构造函数
    Person(const Person& p)//注意要用const和引用
    {
        //将传入的人身上的所有属性,拷贝到我身上
        age=p.age;
        cout<<"Person 的拷贝函数调用"<<endl;
    }
    int age;
};

//调用
void test01()
{
    //1、括号法
    Person p1;//默认构造函数调用
    Person p2(10);//有参构造函数
    Person p3(p2);//拷贝构造函数调用

    //注意事项
    //调用默认构造函数时候,不要加()
    //因为下面这行代码,编译器会认为是一个函数的声明,不会认为在创建对象
    //Person p1();


    //cout<< "p2的年龄: "<< p2.age <<endl;
    //cout<< "p3的年龄: "<< p3.age <<endl;

    //2、显示法
    Person p1;
    Person p2 = Person(10);//有参构造
    Person p3 = Person(p2);//拷贝构造

    Person(10);//匿名对象  特点:当前执行结束后,系统会立即收掉匿名对象

    //注意事项
    //不要利用拷贝构造函数 初始化匿名对象 编译器会认为Person(p3)==Person p3;对象声明
    //Person(p3);

    //3、隐式转换法
    Person p4 = 10;//相当于写了Person p4 = Person(10); 有参构造
    Person p5 = p4;//拷贝构造
}
int main()
{
    test01();
    return 0;
}

3、拷贝构造函数调用时机

c++中拷贝构造函数调用时机通常有三种情况

1,使用一个已经创建完毕的对象来初始化一个新对象

2,值传递的方式给函数参数传值

3,以值方式返回局部对象

//拷贝构造函数的调用时机

//1,使用一个已经创建完毕的对象来初始化一个新对象
//2,值传递的方式给函数参数传值
//3,值方式返回局部对象

class Person
{
public:
    Person()
    {
        cout<<"Person 的默认构造函数"<<endl;
    }
    Person(int age)
    {
        m_age=age;
        cout<<"Person 的有参构造函数"<<endl;
    }
    Person(const Person& p)
    {
        m_age=p.m_age;
        cout<<"Person 的拷贝构造函数调用"<<endl;
    }
    ~Person()
    {
        cout<<"Person 的析构函数调用"<<endl;
    }
    int m_age;
};

//1,使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
    Person p1(20);
    Person p2(p1);

    cout<<"p2的年龄为: "<<p2.m_age<<endl;
}

//2,值传递的方式给函数参数传值
void dowork(Person p)
{

}
void test02()
{
    Person p;
    dowork(p);
}

//3,值方式返回局部对象

Person dowork2()
{
    Person p1;
    return p1;
}
void test03()
{
    Person p=dowork2();
}
int main()
{
    test01();
    test02();
    test03();
    return 0;
}

4、构造函数调用规则

默认情况下,c++编译器至少给一个类添加3个函数

1、默认构造函数(无参,函数体为空)

2、默认析构函数(无参,函数体为空)

3、默认拷贝构造函数,对属性进行值拷贝

?构造函数调用规则如下

1、如果用户定义有参数构造函数,c++不在提供默认无参构造函数,但是会提供默认拷贝构造

2、如果用户定义拷贝构造函数,c++不会再提供其他构造函数

5、深拷贝与浅拷贝

深拷贝:在堆区重新申请空间,进行拷贝操作

浅拷贝:简单的赋值拷贝操作

//如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
class Person
{
public:
    //无参(默认)构造函数
    Person()
    {
        cout<<"无参构造函数!"<<endl;
    }
    //有参构造函数
    Person(int age,int height)
    {
        cout<<"有参构造函数!"<<endl;

        //浅拷贝操作
        m_age=age;
        m_height=new int(height);
    }
    //拷贝构造函数
    Person(const Person& p)
    {
        cout<<"拷贝构造函数!"<<endl;
        //如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
        m_age=p.m_age;//浅拷贝,直接赋值
        m_height=new int(*p.m_height);//深拷贝,将p.m_height指向的地址存放的值重新申请一个内存,用于自身的拷贝函数
    }
    //析构函数
    ~Person()
    {
        cout<<"析构函数"<<endl;
        if(m_height!=NULL)//在堆区申请的空间需要我们自己释放,所以需要这一步操作
        {
            delete m_height;
            m_height=NULL;//避免野指针的出现
        }
    }
public:
    int m_age;
    int *m_height;
};
void test01()
{
    Person p1(18,160);
    Person p2(p1);

    cout<<"p1的年龄: "<<p1.m_age<<"  身高: "<<*p1.m_height<<endl;
    cout<<"p2的年龄: "<<p2.m_age<<"  身高: "<<*p2.m_height<<endl;
}
int main()
{
    test01();
    return 0;
}

6、初始化列表

作用:c++提供了初始化列表语法,用来初始化属性

语法:构造函数():属性1(值1),属性2(值2)……{}

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){}

    void printPerson()
    {
        cout<<"m_a: "<<m_a<<endl;
        cout<<"m_b: "<<m_b<<endl;
        cout<<"m_c: "<<m_c<<endl;
    }
private:
    int m_a,m_b,m_c;
};
int main()
{
    Person p(1, 2, 3);
    p.printPerson();
    return 0;
}

7、类对象作为类成员

c++类的成员可以是另一个类的对象,我们称该成员为对象成员

例如:

class A{}
class B
{
    A a;
}

B类中有对象A作为成员,A为对象成员

那么当创建B对象时,A与B的构造和析构的顺序是谁先谁后?

//手机类
class Phone
{
public:

    Phone(string pName)
    {
        cout<<"Phone的构造函数调用"<<endl;
        m_Pname=pName;
    }
    ~Phone()
    {
        cout<<"Phone的析构函数调用"<<endl;
    }
    string m_Pname;    
};
//人类
class Person
{
public:

    //Phone m_Phone=pName 隐式转换法
    Person(string name,string pName):m_Name(name),m_Phone(pName)
    {
        cout<<"Person的构造函数调用"<<endl;
    }
    ~Person()
    {
        cout<<"Person的析构函数调用"<<endl;
    }
    //姓名
    string m_Name;
    //手机
    Phone m_Phone;
};

//当其他类作为本类成员,构造时候先构造类对象,再构造自身,析构的顺序与构造相反
void test01()
{
    Person p("张三","苹果MAX");
    cout<<p.m_Name<<"拿着: "<<p.m_Phone.m_Pname<<endl;
}
int main()
{
    test01();
    return 0;
}

8、静态成员

静态成员就是再成员变量和成员函数前加上关键字static,称为静态成员

静态成员分为:

1.静态成员变量

? ? ? ? ?1.1所有对象共享同一份数据

? ? ? ? 1.2在编译阶段分配内存

? ? ? ? 1.3类内声明,类外初始化

2.静态成员函数

? ? ? ? 1.1所有对象共享同一个函数

? ? ? ? 1.2静态成员函数只能访问静态成员变量

#include <bits/stdc++.h>

using namespace std;

//静态成员变量
class Person
{
public:
    //1所有对象共享同一份数据
    //2在编译阶段分配内存
    //3类内声明,类外初始化
    static int m_a;

    //静态成员变量也是有访问权限的
private:
    static int m_b;
};
//Person作用域下的m_a初始化
int Person::m_a=100;
int Person::m_b=200;

void test01()
{
    Person p;
    cout<<p.m_a<<endl;//100

    Person p2;
    p2.m_a=200;
    cout<<p.m_a<<endl;//200
}

void test02()
{
    //静态成员变量 不属于某个对象上,所有对象都共享同一份数据
    //因此静态成员变量有两种访问方式
    //1、通过对象访问
    Person p;
    cout<<p.m_a<<endl;

    //2、通过类名进行访问
    cout<<Person::m_a<<endl;

    //cout<<"m_b = "<<Person::m_b<<endl;私有权限访问不到,会报错
}

//静态成员函数
//所有对象共享同一个函数
//静态成员函数只能访问静态成员变量
class Person1
{
public:
    static void func()//静态成员函数
    {
        m_A=10;//静态成员函数可以访问静态成员变量
        //m_B=20;//静态成员函数不可以访问非静态成员变量,会报错,无法区分到底是哪个对象的m_B
        cout<<"static void func调用"<<endl;
    }
    static int m_A;//静态成员变量
    int m_B;//非静态成员变量

    //静态成员函数也是有访问权限的
private:
    static void func1()
    {
        cout<<"static void func1 调用"<<endl;
    }
};
int Person1::m_A=10;

void test03()
{
    //1、通过对象访问
    Person1 p;
    p.func();
    //2、通过类名访问
    Person1::func();
    //Person::func1();类外访问不到私有静态成员函数
}
int main()
{
    test01();
    test02();
    test03();
    return 0;
}

二,对象模型和this指针

1、成员变量和成员函数分开存储

在c++中,类内的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上

#include <bits/stdc++.h>

using namespace std;

//成员变量和成员函数分开存储

class Person
{
    int m_a;//非静态成员变量 属于类的对象上
    static int m_b;//静态成员变量 不属于类的对象上

    void func(){}//非静态成员函数 不属于类的对象上
    static void func2(){}//静态成员函数 不属于类的对象上
};
int Person::m_b = 0;
void test01()
{
    Person p;
    //空对象占用内存空间为:1
    //c++编译器会给每个空对象也分配一个字节空间,是为了区分空对象占内存的位置
    //每个空对象也应该有一个独一无二的内存地址
    cout<<"size of p = "<<sizeof(p)<<endl;
}
void test02()
{
    Person p;
    cout<<"size of p = "<<sizeof(p)<<endl;
}
int main()
{
    // test01();
    test02();
    return 0;
}

?2、this指针概念

通过上一小节,我们知道c++中成员变量和成员函数是分开存储的

每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码,那么问题是:这一块代码是如何区分哪个对象调用自己的呢?

:c++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象。

this指针是隐含每一个非静态成员函数内的一种指针,this指针不需要定义,直接使用即可

this指针的用途

1.当形参和成员变量同名时,可用this指针来区分

2.在类的非静态成员函数中返回对象本身,可使用return *this

#include <bits/stdc++.h>

using namespace std;

class Person
{
public:
    Person(int age)
    {
        //this指针指向被调用的成员函数所属的对象
        this->age=age;
    }
    Person& PersonAddAge(Person& p)//注意返回值要为引用
    {
        this->age+=p.age;

        //this指向p2的指针,而*this指向的就是p2这个对象本体
        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(p1).PersonAddAge(p1);
    cout<<"p2的年龄为: "<<p2.age<<endl;
}
int main()
{
    test01();
    test02();
    return 0;
}

3、空指针访问成员函数

c++中空指针也是可以调用成员函数的,但是也要注意有没有用到this指针

如果用到this指针,需要加以判断保证代码的健壮性

#include <bits/stdc++.h>

using namespace std;

class Person
{
public:
    void showClassName()
    {
        cout<<"this is Person class"<<endl;
    }
    void showPersonAge()
    {
        //报错原因是因为传入的指针为NULL
        cout<<"age = "<<this->m_age<<endl;

        //修正方式
        if(this==NULL)return;
    }

    int m_age;
};
void test01()
{
    Person* p=NULL;

    p->showClassName();
    p->showPersonAge();
}
int main()
{
    test01();
    return 0;
}

4、const修饰成员函数

常函数

1.成员函数后加const后我们可以称这个函数为常函数

2.常函数内不可以修改成员属性

3.成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象

1.声明对象前加const称该对象为常对象

2.常对象只能调用常函数

#include <bits/stdc++.h>

using namespace std;

class Person
{
public:

    //this指针的本质   是指针常量  指针的指向是不可修改的
    //this指针 等价于 Person* const this;
    //在成员函数后面加const,修饰的是this指向,让指针指向的值也不可以修改
    void showPerson()const//等价于 const Person* const this
    {
        this->m_b=100;
        // this->m_a=100;//错误,原因如上
        //this=NULL //错误,原因如上
    }
    void fun()
    {

    }
    int m_a;
    mutable int m_b;//特殊变量,即使在常含数中,也可以修改这个值
};
void test01()
{
    Person p;
    p.showPerson();
}

//常对象

void test02()
{
    const Person p{};//在对象前加const,变为常对象
    // p.m_a=100;//报错
    p.m_b=100;//m_b是特殊值,在常对象也可以修改

    //常对象只能调用常函数
    p.showPerson();//正确
    // p.func();//报错 常对象不可以调用普通成员函数,因为普通成员函数可以修改属性
}
int main()
{
    test01();
    test02();
    return 0;
}

三、友元

生活中你的家有客厅(public),有你的卧室(private),客厅所有来的客人都可以进去,但是你的卧室是私有的,也就是说只有你能进去,但是呢,你也可以允许你的好闺蜜好基友进去。

在程序里,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术。友元的目的就是让一个函数或者类访问另一个类中私有成员

友元关键字为friend

1、全局函数作友元

#include <bits/stdc++.h>

using namespace std;

//建筑物类
class Building
{
    //GoodGay全局函数是Building的好朋友,可以访问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;//想访问这个必须要有friend那一行的代码
}
void test01()
{
    Building building;
    GoodGay(building);
}
int main()
{
    test01();
    return 0;
}

2、类作友元

#include <bits/stdc++.h>

using namespace std;

class Building;
class GoodGay
{
public:
    GoodGay();//构造函数
    void visit();//参观函数访问Building中的属性
    Building* building;
};
class Building
{
    //GoodGay类是本类的好朋友,可以访问本类中私有成员
    friend class GoodGay;
public:
    Building();//构造函数
    string m_SittingRoom;//客厅
private:
    string m_BedRoom;//卧室
};

//类外成员函数
Building::Building()
{
    m_SittingRoom="客厅";
    m_BedRoom="卧室";
}
GoodGay::GoodGay()
{
    //创建建筑物对象
    building = new Building;
}
void GoodGay::visit()
{
    cout<<"好基友类正在访问:"<<building->m_SittingRoom<<endl;
    cout<<"好基友类正在访问: "<<building->m_BedRoom<<endl;
}
void test01()
{
    GoodGay gg;
    gg.visit();
}
int main()
{
    test01();
    return 0;
}

3、成员函数作友元

#include <bits/stdc++.h>

using namespace std;

class Building;
class GoodGay
{
public:
    GoodGay();
    void visit();//让visit函数可以访问Building中私有成员
    void visit2();//让visit2函数不可以访问Building中私有成员

    Building* building;    
};
class Building
{
    //告诉编译器 GoodGay类中的visit成员函数是Building好朋友,可以访问私有内容
    friend void GoodGay::visit();
public:
    Building();//构造函数
    string m_SittingRoom;//客厅
private:
    string m_BedRoom;//卧室
};

//类外实现成员函数
Building::Building()
{
    m_SittingRoom="客厅";
    m_BedRoom="卧室";
}
GoodGay::GoodGay()
{
    //创建建筑物对象
    building = new Building;
}
void GoodGay::visit()
{
    cout<<"visit函数正在访问: "<<building->m_SittingRoom<<endl;
    cout<<"visit函数正在访问: "<<building->m_BedRoom<<endl;
}
void GoodGay::visit2()
{
    cout<<"visit2函数正在访问: "<<building->m_SittingRoom<<endl;
    // cout<<"visit2函数正在访问: "<<building->m_BedRoom<<endl;//报错,因为visit2不是friend
}
void test01()
{
    GoodGay gg;
    gg.visit();
}
int main()
{
    test01();
    return 0;
}

四、运算符重载

运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

1、加号运算符重载

作用:实现两个自定义数据类型相加的运算

#include <bits/stdc++.h>
using namespace std;

//加号运算符重载

//1、成员函数重载+号
class Person
{
public:
    // 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 num)
{
    Person temp;
    temp.m_a=p1.m_a+num;
    temp.m_b=p1.m_b+num;
    return temp;
}
void test01()
{
    Person p1;
    p1.m_a=10;p1.m_b=10;
    Person p2;
    p2.m_a=10;p2.m_b=10;

    //成员函数重载本质调用
    //Person p3 = p1.operator+(p2);

    //全局函数重载本质调用
    //Person p3 = operator+(p1,p2);
    Person p3=p1+p2;

    //运算符重载也可以发生函数重载
    Person p4=p1+100;//相当于 operator+(p1,100);
    cout<<p3.m_a<<" "<<p3.m_b<<"\n";
    cout<<p4.m_a<<" "<<p4.m_b<<"\n";
    
}
int main()
{
    test01();
    return 0;
}

总结1:对于内置的数据类型的表达式的运算符是不可能改变的

总结2:不要滥用运算符重载

2、左移运算符重载

作用:可以输出自定义的数据类型。

#include <bits/stdc++.h>
using namespace std;

//左移运算符重载

class Person
{
public:
    //利用成员函数重载左移运算符 p.operator<<(cout) 简化版本 p<<cout
    //不会利用成员函数重载<<运算符,因为无法实现cout在左侧
    // void operator<<(cout)
    // {

    // }

    int m_a;
    int m_b;
};

//只能利用全局函数重载左移运算符
//ostream对象只能有一个 ,注意要用引用的方式传递cout,返回值同样
ostream& operator<<(ostream &cout,Person &p)//本质 operator<<(cout,p) 简化 cout<<p
{
    cout<<"m_a = "<<p.m_a<<" m_b = "<<p.m_b<<endl; 
    return cout;
}
void test01()
{
    Person p;
    p.m_a=10;p.m_b=10;
    cout<<p;
}
int main()
{
    test01();
    return 0;
}

总结:重载左移运算符配合友元可以实现输出自定义的数据类型

3、递增运算符重载

作用:通过重载递增运算符,实现自己的整型数据

#include <bits/stdc++.h>

using namespace std;

// 自定义整形
class MyInteger
{
    friend ostream& operator<<(ostream& cout,MyInteger myint);
public:
    MyInteger()
    {
        m_num=0;
    }

    //重载前置++运算符,返回引用是为了一直对一个数据进行递增操作
    MyInteger& operator++()
    {
        //先进行++运算
        m_num++;
        //再将自身返回
        return *this;
    }
    //重载后置++运算符,注意这里是返回值而不是引用
    MyInteger operator++(int)//int代表占位参数,可以用于区分前置和后置递增
    {
        //先 记录当时结果
        MyInteger temp=*this;
        //后 递增
        m_num++;
        //最后将记录的结果做返回操作
        return temp;
    }
private:
    int m_num;
};

//重载<<运算符
ostream& operator<<(ostream& cout,MyInteger myint)
{
    cout<<myint.m_num;
    return cout;
}
void test01()
{
    MyInteger myint;
    cout<<++(++myint)<<endl;
    cout<<myint<<endl;
}
void test02()
{
    MyInteger myint;
    cout<<myint++<<endl;
    cout<<myint<<endl;
}
int main()
{
    test01();
    test02();
    return 0;
}

总结:前置++返回引用,后置++返回值

4、赋值运算符重载

c++编译器至少给一个类添加4个函数

1.默认构造函数(无参,函数体为空)

2.默认析构函数(无参,函数体为空)

3.默认拷贝构造函数,对属性进行值拷贝

4,赋值运算符operator=,对属性进行值拷贝

如果类中有属性指向堆区,做赋值操作时也会出现深拷贝问题

#include<bits/stdc++.h>
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<<"p3的年龄为: "<<*p3.m_age<<endl;
}
int main()
{
    test01();
    return 0;
}

5、关系运算符重载

作用:重载关系运算符,可以让两个自定义类型对象进行比对操作。

#include <bits/stdc++.h>
using namespace std;

class Person
{
public:
    Person(string name, int age)
    {
        m_name = name;
        m_age = age;
    }

    //重载==号
    bool operator==(Person &p)
    {
        if(this->m_name==p.m_name&&this->m_age==p.m_age)return true;
        else return false;
    }
    //重载!=号
    bool operator!=(Person &p)
    {
        if(this->m_name==p.m_name&&this->m_age==p.m_age)return false;
        else return true;
    }
    string m_name;
    int m_age;
};
void test01()
{
    Person p1("Tom",18);
    Person p2("Tom",18);
    if(p1==p2)cout<<"YES"<<endl;

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

6、函数调用运算符重载

1.函数调用运算符()也可以重载

2.由于重载后使用方式非常像函数的调用,因此称为仿函数

3.仿函数没有固定写法,非常灵活

#include<bits/stdc++.h>
using namespace std;


class MyPrint
{
public:
    void operator()(string test)
    {
        cout<<test<<endl;
    }
};
void MyPrint02(string test)
{
    cout<<test<<endl;
}
void test01()
{
    MyPrint myPrint;
    myPrint("Hello Worle");//由于使用起来非常类似于函数调用,因此称为仿函数
    MyPrint02("Hello Worle");
}

//仿函数非常灵活,没有固定写法
//加法类
class MyAdd
{
public:
    int operator()(int num1,int num2)
    {
        return num1+num2;
    }
};
void test02()
{
    MyAdd myadd;
    int ret=myadd(100,100);
    cout<<"ret = "<<ret<<endl;

    //匿名函数对象
    cout<<MyAdd()(100,100)<<endl;
}
int main()
{
    test01();
    test02();
    return 0;
}

?五、继承

继承是面向对象的三大特性之一

有些类与类之间存在特殊的关系。例如第一级是动物,第二级是猫,狗,那么第二级的成员除了拥有上一级的特性,还有自己的特性,这个时候就可以考虑利用继承的技术,减少重复代码。

1、继承基本语法

#include <bits/stdc++.h>
using namespace std;

//普通实现页面

// Java页面
//  class Java
//  {
//  public:
//      void header()
//      {
//          cout<<"首页、公开课、登录、注册……(公共头部)"<<endl;
//      }
//      void footer()
//      {
//          cout<<"帮助中心、交流合作、站内地图…(公共底部)"<<endl;
//      }
//      void left()
//      {
//          cout<<"Java、python、c++、……(公共分类列表)"<<endl;
//      }
//      void content()
//      {
//          cout<<"Java学科视频"<<endl;
//      }
//  };
//  //Python页面
//  class Python
//  {
//  public:
//      void header()
//      {
//          cout<<"首页、公开课、登录、注册……(公共头部)"<<endl;
//      }
//      void footer()
//      {
//          cout<<"帮助中心、交流合作、站内地图…(公共底部)"<<endl;
//      }
//      void left()
//      {
//          cout<<"Java、python、c++、……(公共分类列表)"<<endl;
//      }
//      void content()
//      {
//          cout<<"Python学科视频"<<endl;
//      }
//  };
//  //c++页面
//  class CPP
//  {
//  public:
//      void header()
//      {
//          cout<<"首页、公开课、登录、注册……(公共头部)"<<endl;
//      }
//      void footer()
//      {
//          cout<<"帮助中心、交流合作、站内地图…(公共底部)"<<endl;
//      }
//      void left()
//      {
//          cout<<"Java、python、c++、……(公共分类列表)"<<endl;
//      }
//      void content()
//      {
//          cout<<"CPP学科视频"<<endl;
//      }
//  };

//继承实现页面

//公共页面类
class BasePage
{
public:
    void header()
    {
        cout << "首页、公开课、登录、注册……(公共头部)" << endl;
    }
    void footer()
    {
        cout << "帮助中心、交流合作、站内地图…(公共底部)" << endl;
    }
    void left()
    {
        cout << "Java、python、c++、……(公共分类列表)" << endl;
    }
};
//继承的好处:减少重复代码
//语法:class 子类 :继承方式 父类
//子类也称为派生类
//父类也称为基类

//Java页面
class Java:public BasePage
{
public:
    void content()
    {
        cout<<"Java学科视频"<<endl;
    }
};
//Python页面
class Python :public BasePage
{
public:
    void content()
    {
        cout<<"Python学科视频"<<endl;
    }
};
//c++页面
class CPP :public BasePage
{
public:
    void content()
    {
        cout<<"CPP学科视频"<<endl;
    }
};
void test01()
{
    cout << "Java下载视频页面如下: " << endl;
    Java ja;
    ja.header();
    ja.footer();
    ja.left();
    ja.content();

    cout << "Python下载视频页面如下: " << endl;
    Python py;
    py.header();
    py.footer();
    py.left();
    py.content();

    cout << "CPP下载视频页面如下: " << endl;
    CPP c;
    c.header();
    c.footer();
    c.left();
    c.content();
}
int main()
{
    test01();
    return 0;
}

总结:

继承的好处:可以减少重复的代码

语法:class A:public B;

A类称为子类或派生类? ? ?B类称为父类或基类

2、继承方式

1.公共继承 2.保护继承 3.私有继承

1.在公共继承中,父类的私有权限不可访问,其余的权限都变成公共权限

2.在保护继承中,父类的私有权限不可访问,其余的权限都变成保护权限

3.在私有继承中,父类的私有权限不可访问,其余的权限都变成私有权限

#include <bits/stdc++.h>
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;//父类中的私有权限成员到子类中访问不到,报错
    }
};

void test01()
{
    son1 s1;
    s1.m_a=100;
    // s1.m_b=100;//报错,到son1中m_b是保护权限类外方位不到
}

//保护继承
class Base2
{
    public:
        int m_a;
    protected:
        int m_b;
    private:
        int m_c;
};
class son2:protected Base2
{
public:
    void func()
    {
        m_a=10;//父类种的公共权限成员到子类中变为保护权限
        m_b=10;//父类中的保护权限成员到子类中依然是保护权限
        //m_c=10;//父类中的私有权限成员到子类中访问不到,报错
    }
};
void test02()
{
    son2 s2;
    // s2.m_a=100;//在son2中m_a变为保护权限,因此类外访问不到,报错
    // s2.m_b=100;//在son2中m_b变为保护权限,因此类外访问不到,报错
}

//私有继承
class Base3
{
    public:
        int m_a;
    protected:
        int m_b;
    private:
        int m_c;
};
class son3:private Base3
{
public:
    void func()
    {
        m_a=10;//父类种的公共权限成员到子类中变为私有权限
        m_b=10;//父类中的保护权限成员到子类中依然是保护权限
        //m_c=10;//父类中的私有权限成员到子类中访问不到,报错
    }
};
void test03()
{
    son3 s3;
    // s3.m_a=1000;//在son3中m_a变为私有权限,因此类外访问不到,报错
    // s3.m_b=1000;//在son3中m_a变为私有权限,因此类外访问不到,报错
}

int main()
{

    return 0;
}

3、继承中的对象模型

问题:从父类继承过来的成员,哪些属于子类对象中?

#include <bits/stdc++.h>
using namespace std;

class Base
{
    public:
        int m_a;
    protected:
        int m_b;
    private:
        int m_c;
};
class son:public Base
{
public: 
    int m_d;
};
void test01()
{
    //父类中所有非静态成员属性都会被子类继承下去
    //父类中私有成员属性,是被编译器给隐藏了,因此访问不到,但是确实被继承下去了
    cout<<"size of son = "<<sizeof (son)<<endl;//16
}
int main()
{
    test01();
    return 0;
}

4、继承中的构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数

问题:父类和子类的构造和析构顺序是谁先谁后?

? ? 继承中的构造和析构顺序如下:

? ? 先构造父类,再构造子类,析构的顺序与构造的顺序相反

#include <bits/stdc++.h>
using namespace std;

class Base
{
public:
    Base()
    {
        cout << "Base构造函数!" << endl;
    }
    ~Base()
    {
        cout << "Base析构函数" << endl;
    }
};
class son : public Base
{
public:
    son()
    {
        cout << "son构造函数!" << endl;
    }
    ~son()
    {
        cout << "son析构函数" << endl;
    }
};
void test01()
{
    // 继承中的构造和析构顺序如下:
    // 先构造父类,再构造子类,析构的顺序与构造的顺序相反
    son p;
}
int main()
{
    test01();
    return 0;
}

5、继承同名成员处理方式

问题:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢/

1.访问子类同名成员,直接访问即可

2.访问父类同名成员,需要加作用域、

3.当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数,加作用域可以访问到父类中同名函数

#include <bits/stdc++.h>
using namespace std;

class Base
{
public:
    Base()
    {
        m_a=100;
    }
    void func()
    {
        cout<<"Base-func()调用"<<endl;
    }
    void func(int a)
    {
        cout<<"Base-func(int)调用"<<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 s;
    s.func();//直接调用 调用时子类中的同名成员
    //如何调用到父类中同名成员函数?
    s.Base::func();//同样的,需要加作用域

    //如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏父类中的所有同名成员函数
    //如果想访问到父类中被隐藏的同名成员函数,需要加作用域
    // s.func(100);//报错
    s.Base::func(100);
}
int main()
{
    test01();
    test02();
    return 0;
}

?6、继承同名静态成员处理方式

问题:继承中同名静态成员在子类对象上如何进行访问?

静态成员和非静态成员出现同名,处理方式一致

?1.访问子类同名成员 直接访问即可

2.访问父类同名成员 需要加作用域

#include<bits/stdc++.h>
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)的调用"<<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 s;
    s.func();
    s.Base::func();

    //2、通过类名访问
    cout<<"通过类名访问"<<endl;
    son::func();
    son::Base::func();

    //子类出现和父类同名静态成员函数,也会隐藏父类中所有同名成员函数
    //如果想访问父类中被隐藏同名成员,需要加作用域
    son::Base::func(100);
}
int main()
{
    test01();
    test02();
    return 0;
}

总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问方式(通过对象和通过类名)

7、多继承语法

c++允许一个类继承多个类

语法:class 子类:继承方式 父类1,继承方式 父类2……

多继承可能会引发父类中有同名成员出现,需要加作用域区分

c++实际开发中不建议使用多继承

#include<bits/stdc++.h>
using namespace std;


class Base
{
public:
    Base()
    {
        m_a=100;
    }
    int m_a;
};
class Base2
{
public:
    Base2()
    {
        m_a=200;
    }
    int m_a;
};

//子类  需要继承Base1和Base2
class son:public Base,public Base2
{
public:
    son()
    {
        m_c=300;
        m_d=400;
    }
    int m_c;
    int m_d;
};
void test01()
{
    son s;
    cout<<"size of son = "<<sizeof(son)<<endl;

    //当父类中出现同名成员,需要加作用域区分
    cout<<"m_a =  "<<s.Base::m_a<<endl;
    cout<<"m_a =  "<<s.Base2::m_a<<endl;
}
int main()
{
    test01();
    return 0;
}

8、菱形继承

菱形继承概念

两个派生类继承同一个基类 ,又有某个类同时继承着两个派生类,这种继承方式被称为菱形继承,或者钻石继承

#include<bits/stdc++.h>
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();
    return 0;
}

?六、多态

1.多态的基本概念

多态是c++面向对象三大特性之一

多态分为两类

1.静态多态:函数重载和运算符重载属于静态多态,复用函数名

2.动态多态:派生类和虚函数实现运行时多态

静态多态与动态多态区别:

1.静态多态的函数地址早绑定 - 编译阶段确定函数地址

2.动态多态的函数地址晚绑定 - 运行阶段确定函数地址?

#include<bits/stdc++.h>
using namespace std;


//多态

//动物类
class Animal
{
public:
    //函数面前加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了
    virtual void speak()
    {
        cout<<"动物在说话"<<endl;
    }
};
class Cat : public Animal
{
public:
    void speak()
    {
        cout<<"小猫在说话"<<endl;
    }
};
class Dog : public Animal
{
public:
    void speak()
    {
        cout<<"小狗在说话"<<endl;
    }
};
//执行说话的函数
//地址早绑定 在编译阶段确定函数地址
//如果想执行让猫说话,那么这个函数地址就不能提前绑定,需要在运行阶段进行绑定,地址晚绑定

//动态多态满足条件
//1、有继承关系
//2、子类要重写父类的虚函数

//动态多态使用
//父类指针或者引用执行子类对象
void doSpeak(Animal &animal)
{
    animal.speak();
}
void test01()
{
    Cat cat;
    doSpeak(cat);

    Dog dog;
    doSpeak(dog);
}
int main()
{
    test01();
    return 0;
}

重写:函数返回值类型,函数名,参数列表完全一致称为重写

2、多态案例--计算器实现

多态的优点:

1.代码组织结构清晰 2.可读性强 3.利于前期和后期的扩展以及维护

#include<bits/stdc++.h>
using namespace std;

//分别利用普通写法和多态技术实现计算器

//普通写法
class Calculator
{
public:
    int getResult(string oper)
    {
        if(oper=="+")return m_num1+m_num2;
        else if(oper=="-")return m_num1-m_num2;
        else if(oper=="*")return m_num1*m_num2;
        //如果想扩展新的功能,需要修改源码
        //在真实开发中,提倡开闭原则
        //开闭原则:对扩展进行开放,对修改进行关闭
    }
    int m_num1;//操作数1
    int m_num2;//操作数2
};
void test01()
{
    //创建计算器对象
    Calculator c;
    c.m_num1=10;c.m_num2=10;

    cout<<c.m_num1<<" + "<<c.m_num2<<" = "<<c.getResult("+")<<endl;
    cout<<c.m_num1<<" - "<<c.m_num2<<" = "<<c.getResult("-")<<endl;
    cout<<c.m_num1<<" * "<<c.m_num2<<" = "<<c.getResult("*")<<endl;
}

//利用多态实现计算器
//多态好处:
//1、组织结构清晰
//2、可读性强
//3、对于前期和后期扩展以及维护性高

//实现计算器抽象类
class AbstractCalculator
{
public:
    virtual int getResult()
    {
        return 0;
    }
    int m_num1;
    int m_num2;
};
//加法计算器类
class AddCalculator :public AbstractCalculator
{
public:
    int getResult()
    {
        return m_num1+m_num2;
    }
};
//减法计算器类
class SubCalculator :public AbstractCalculator
{
    public:
    int getResult()
    {
        return m_num1-m_num2;
    }
};
//乘法计算器类
class MulCalculator :public AbstractCalculator
{
    public:
    int getResult()
    {
        return m_num1*m_num2;
    }
};
void test02()
{
    //多态使用条件
    //父类指针或者引用指向子类对象

    //加法运算
    AbstractCalculator *abc=new AddCalculator;
    abc->m_num1=10;
    abc->m_num2=10;
    cout<<abc->m_num1<<" + "<<abc->m_num2<<" = "<<abc->getResult()<<endl;
    delete abc;//用完记得销毁

    //减法运算
    abc = new SubCalculator;
    abc->m_num1=100;
    abc->m_num2=100;
    cout<<abc->m_num1<<" - "<<abc->m_num2<<" = "<<abc->getResult()<<endl;
    delete abc;

    //乘法运算
    abc = new MulCalculator;
    abc->m_num1=100;
    abc->m_num2=100;
    cout<<abc->m_num1<<" * "<<abc->m_num2<<" = "<<abc->getResult()<<endl;
    delete abc;
}
int main()
{
    test01();
    test02();
    return 0;
}

?3、纯虚函数和抽象类

在多态中,通常父类中虚函数的实现毫无意义的,主要是调用子类重写的内容

因此可以将虚函数改为纯虚函数

纯虚函数写法:virtual 返回值类型 函数名 (参数列表)= 0;

当类中有了纯虚函数,这个类也称为抽象类。

抽象类特点:

1.无法实例化对象

2.子类必须重写抽象类中的纯虚函数,否则也属于抽象类

#include<bits/stdc++.h>
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();
    return 0;
}

4、多态案例--制作饮品

制作饮品的大致流程为:煮水-冲泡-倒入杯中-加入辅料

利用多态技术实现本案例,提供抽象类制作饮品基类,提供子类制作咖啡和茶叶

#include<bits/stdc++.h>
using namespace std;

//多态案例2 制作饮品
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:
    //煮水
    virtual void Boil()
    {
        cout<<"煮农夫山泉"<<endl;
    }

    //冲泡
    virtual void Brew()
    {
        cout<<"冲泡咖啡"<<endl;
    }

    //倒入杯中
    virtual void PourInCup()
    {
        cout<<"倒入杯中"<<endl;
    }

    //加入辅料
    virtual void PutSomething()
    {
        cout<<"加入糖和牛奶"<<endl;
    }
};
class Tea:public AbstractDrinking
{
public:
    //煮水
    virtual void Boil()
    {
        cout<<"煮矿泉水"<<endl;
    }

    //冲泡
    virtual void Brew()
    {
        cout<<"冲泡茶叶"<<endl;
    }

    //倒入杯中
    virtual void PourInCup()
    {
        cout<<"倒入杯中"<<endl;
    }

    //加入辅料
    virtual void PutSomething()
    {
        cout<<"加入枸杞"<<endl;
    }
};
//制作函数
void doWork(AbstractDrinking *abs)
{
    abs->makeDrink();
    delete abs;//释放
}
void test01()
{
    //制作咖啡
    doWork(new Coffee);

    //制作茶叶
    doWork(new Tea);
}
int main()
{
    test01();
    return 0;
}

5、虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构共性

1.可以解决父类指针释放子类对象

2.都需要有具体的函数实现

虚析构和纯虚析构区别:

1.如果是纯虚析构,该类属于抽象类,无法实例化对象

?虚析构语法:virtual ~类名()

纯虚析构语法:virtual ~类名()=0;类名::~类名(){}

#include<bits/stdc++.h>
using namespace std;

//虚析构和纯虚析构

class Animal
{
public:
    Animal()
    {
        cout<<"Animal构造函数调用"<<endl;
    }
    //利用虚析构可以解决父类指针释放子类对象时不干净的问题
    // virtual ~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();
    return 0;
}

总结:

1.虚析构或纯虚析构就是用来解决通过父类指针释放子类对象

2.如果子类中没有堆区数据,可以不写为虚析构或纯虚析构

3.拥有纯虚析构函数的类也属于抽象类

6、多态案例--电脑组装

案例描述:

电脑主要组成部件为CPU(用于计算),显卡(用于显示),内存条(用于存储)将每个零件封装除抽象基类,并且提供不同的厂商生产不同的零件,例如Intel和Lenovo厂商,创建电脑类提供让电脑工作的函数,并且调用每个零件工作的接口

测试时组装三台不同的电脑进行工作

#include<bits/stdc++.h>
using namespace std;

//抽象不同零件类

//抽象CPU类
class CPU
{
public:
    //抽象的计算函数
    virtual void calculate()=0;
};
//抽象显卡类
class VideoCard
{
public:
    //抽象的显示函数
    virtual void display()=0;
};
//抽象内存条类
class Memory
{
public:
    //抽象的存储函数
    virtual void storage()=0;
};

//电脑类
class Computer
{
public:
    Computer(CPU *cpu,VideoCard *vc,Memory *mem)
    {
        m_cpu=cpu;
        m_vc=vc;
        m_mem=mem;
    }

    //提供工作的函数
    void work()
    {
        //让零件工作起来,调用接口
        m_cpu->calculate();
        m_vc->display();
        m_mem->storage();
    }

    //提供析构函数释放3个电脑零件
    ~Computer()
    {
        //释放CPU零件
        if(m_cpu!=NULL)
        {
            delete m_cpu;
            m_cpu=NULL;
        }

        //释放显卡零件
        if(m_vc!=NULL)
        {
            delete m_vc;
            m_vc=NULL;
        }
        //释放内存条零件
        if(m_mem!=NULL)
        {
            delete m_mem;
            m_mem=NULL;
        }
    }
private:
    CPU *m_cpu;//cpu的零件指针
    VideoCard *m_vc;//显卡零件指针
    Memory *m_mem;//内存条零件指针
};

//具体厂商
//Intel厂商
class IntelCPU:public CPU
{
public:
    virtual void calculate()
    {
        cout<<"Intel的CPU开始计算了"<<endl;
    }
};
class IntelVideoCard:public VideoCard
{
public:
    virtual void display()
    {
        cout<<"Intel的显卡开始显示了"<<endl;
    }
};
class IntelMemory:public Memory
{
public:
    virtual void storage()
    {
        cout<<"Intel的内存条开始存储了"<<endl;
    }
};

//Lenovo厂商
class LenovoCPU:public CPU
{
public:
    virtual void calculate()
    {
        cout<<"Lenovo的CPU开始计算了"<<endl;
    }
};
class LenovoVideoCard:public VideoCard
{
public:
    virtual void display()
    {
        cout<<"Lenovo的显卡开始显示了"<<endl;
    }
};
class LenovoMemory:public Memory
{
public:
    virtual void storage()
    {
        cout<<"Lenovo的内存条开始存储了"<<endl;
    }
};
void test01()
{
    //第一台电脑零件
    cout<<"第一台电脑开始工作"<<endl;
    CPU *intelCpu =new IntelCPU;
    VideoCard *intelCard =new IntelVideoCard;
    Memory *intelMem =new IntelMemory;

    //创建第一台电脑
    Computer *computer1 =new Computer(intelCpu,intelCard,intelMem);
    computer1->work();
    delete computer1;

    //第二台电脑组装
    cout<<"第二台电脑开始工作"<<endl;
    Computer *computer2 =new Computer(new LenovoCPU,new LenovoVideoCard,new LenovoMemory);
    computer2->work();
    delete computer2;

    //第三台电脑组转
    cout<<"第三台电脑开始工作"<<endl;
    Computer *computer3 =new Computer(new LenovoCPU,new IntelVideoCard,new LenovoMemory);
    computer3->work();
    delete computer3;
}
int main()
{
    test01();
    return 0;
}

文章来源:https://blog.csdn.net/m0_74911187/article/details/135070177
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。