• c++ 类与对象


    4 类和对象

    C++ 面向对象的三大特征为: 封装,继承,多态

    C++ 认为万物万事皆对象

    4.1 封装

    4.1.1 封装的意义

    • 将属性和行为作为一个整体,表现生活中的事物
    • 将属性和行为加以权限控制

    封装意义一:

    在设计类的时候,属性和行为写在一起,表现事物

    语法:class 类名{访问权限:属性/行为};

    示例: 设计一个圆类,求圆的周长

    #include <iostream>
    using namespace std;
    
    // 设计一个圆类 求周长  2*PI*半径
    const double PI = 3.1415;
    
    // class 代表设计一个类,类后面紧跟着的就是类名称
    class Circle {
    	// 访问权限
    public:
    	// 属性
    	int m_r;
    
    	// 方法
    	// 获取圆周长
    	double get_zc() {
    		return 2 * PI * m_r;
    	}
    };
    
    void main() {
    	// 通过圆类 创建一个具体的圆对象 // 实例化
    	Circle c1;
    	// 给圆的属性赋值
    	c1.m_r = 15.5;
    	double zc = c1.get_zc();
    	cout << zc << endl;
    }
    

    示例2: 设计一个学生类,属性有姓名和学号,可以给姓名学号赋值,可以显示学生的姓名和学号

    #include <iostream>
    using namespace std;
    
    class Student {
    public:
    	string name;
    	int id;
    
    	void showStudentInfo() {
    		cout << "姓名:" << name << " 学号:" << id << endl;
    	}
    
    	void setNameID(string name,int id) {
    		this->name = name;
    		this->id = id;
    	}
    };
    
    void main() {
    	Student s1;
    	s1.setNameID("小明",1);
    	s1.showStudentInfo();
    }
    

    封装意义二:

    类在设计时,可以把属性和行为放在不同权限下,加以控制

    访问权限三种:

    • public 公共权限
    • private 私有权限
    • protected 保护权限
    // 三种权限
    // 公共权限 public    类内可以访问   类外可以访问
    // 私有权限 private   类内可以访问   类外不可以访问		
    // 保护权限 protected 类内可以访问   类外不可以访问		继承中 保护权限 儿子可以访问,而私有不行
    

    4.1.2 struct 和 class区别

    在c++ 中struct 和 class 唯一的区别在于 默认的访问权限不同

    区别:

    • struct 默认权限为公共
    • class 默认权限为私有

    4.1.3 成员属性设置为私有

    优点1: 将所有成员属性设置为私有,可以自己控制读写权限

    优点2: 对于写权限,我们开业检测数据的有效性

    #include <iostream>
    using namespace std;
    
    class Person {
    
    public:
    	string getName() {
    		return name;
    	}
    	
    	int getAge() {
    		return age;
    	}
    
    	void setName(string r_name) {
    		name = r_name;
    	}
    private:
    	string name;
    	int age;
    	int score;
    };
    
    void main() {
    }
    

    在类中 可以让其他类作为一个成员

    #include <iostream>
    #include <math.h>
    using namespace std;
    
    // 点类 
    class Point {
    private:
    	int m_x;
    	int m_y;
    
    public:
    	void SetPont(int x,int y) {
    		m_y = y;
    		m_x = x;
    	}
    
    	int getPointX() {
    		return m_x;
    	}
    
    	int getPointY() {
    		return m_y;
    	}
    };
    
    class Circle {
    private:
    	int m_r;
    	Point p;
    
    public:
    	void setPoint(int x,int y) {
    		p.SetPont(x,y);
    	}
    
    	void setMr(int r) {
    		m_r = r;
    	}
    
    	void isPointInCircle(Point p2) {
    		int bj = m_r * m_r;
    		int jl = pow(p.getPointX() - p2.getPointX(), 2) + pow(p.getPointY() - p2.getPointY(),2);
    		if (jl==bj) {
    			cout << "在圆边" << endl;
    		}else if(jl > bj) {
    			cout << "不在圆内" << endl;
    		}
    		else {
    			cout << "在圆之内" << endl;
    		}
    
    	}
    };
    
    // 圆类
    
    void main() {
    	Circle c1;
    	c1.setPoint(5,5);
    	c1.setMr(10);
    
    	Point p1;
    	p1.SetPont(10, 10);
    
    	c1.isPointInCircle(p1);
    }
    

    4.11重点 分文件写

    circle.h

    #pragma once
    #include <iostream>
    #include "Point.h"
    using namespace std;
    
    class Circle {
    private:
    	int m_r;
    	Point p;
    
    public:
    	void setPoint(int x, int y);
    
    	void setMr(int r);
    
    	void isPointInCircle(Point p2);
    };
    
    

    circle.cpp

    #include "Circle.h"
    void Circle::setPoint(int x, int y) {		// 这个Circle::代表在circle的作用域下
    	p.SetPont(x, y);
    }
    
    void Circle::setMr(int r) {
    	m_r = r;
    }
    
    void Circle::isPointInCircle(Point p2) {
    	int bj = m_r * m_r;
    	int jl = pow(p.getPointX() - p2.getPointX(), 2) + pow(p.getPointY() - p2.getPointY(), 2);
    	if (jl == bj) {
    		cout << "在圆边" << endl;
    	}
    	else if (jl > bj) {
    		cout << "不在圆内" << endl;
    	}
    	else {
    		cout << "在圆之内" << endl;
    	}
    
    }
    

    Point.h

    #pragma once	// 加载一次
    #include <iostream>
    using namespace std;
    
    // 点类 
    class Point {
    private:
    	int m_x;
    	int m_y;
    
    public:
    	void SetPont(int x, int y);
    
    	int getPointX();
    
    	int getPointY();
    };
    
    

    Point.cpp

    #include "Point.h"
    
    void Point::SetPont(int x, int y) {
    	m_y = y;
    	m_x = x;
    }
    
    int Point::getPointX() {
    	return m_x;
    }
    
    int Point::getPointY() {
    	return m_y;
    }
    
    

    4.2 对象的初始化和清理

    • 类似 恢复出厂 设置
    • C++ 中的面向对象来源生活,每个对象也会有初始设置以及对象销毁前的清理数据的设置

    4.2.1 构造函数和析构函数

    对象的初始化和清理 也是两个非常重要的安全问题

    • 一个对象或者变量没有初始状态,对齐使用后果是未知的
    • 同样的使用完一个对象或者变量,没有及时清理,也会造成一定的安全问题!

    ​ c++利用了 构造函数 和 析构函数解决以上问题,这两个函数将会被编译器自动调用,完成对对象初始化和清理工作,对象的初始化和清理是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供 编译器提供的是 空实现

    • 构造函数:主要用于常见对象时对对象赋值操作,编译器自动调用,无需手动调用
    • 析构函数:主要用于对象的销毁,系统自动调用 执行清理工作

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

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

    2.函数名称与类名相同

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

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

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

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

    2.函数名称与类名相同 加 ~

    3.析构函数不可以有参数,因此不能重载

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

    #include <iostream>
    using namespace std;
    //构造函数和析构函数
    class Person {
    public:
    	string m_name;
    	int m_age;
    
    	// 使用构造函数初始化
    	Person(string name,int age) {
    		m_name = name;
    		m_age = age;
    	}
    
    	// 使用析构函数清理
    	~Person() {
    		cout << "析构函数执行" << endl;
    	}
    };
    
    void main() {
    	Person s1("小明",18);				// 栈上的数据,main执行完毕就会执行析构函数
    	cout << s1.m_age << endl;
    	cout << s1.m_name << endl;
    }
    

    4.2.2 构造函数的分类和调用 调用**

    两种分离方式:

    ​ 按参数分为: 有参构造,无参构造

    ​ 按类型分类: 普通构造,拷贝构造

    三种调用方式:

    ​ 括号法

    ​ 显示法

    ​ 隐式转换法

    #include <iostream>
    using namespace std;
    
    // 1. 构造函数的分类及调用
    class Person {
    public:
    	// 构造函数
    
    	Person() {
    		cout << "Person的构造函数调用" << endl;
    	}
    
    	Person(string name) {
    		this->name = name;
    		cout << "Person的构造函数调用" << endl;
    	}
    
    	Person(string name,int age) {
    		this->age = age;
    		this->name = name;
    		cout << "Person的构造函数调用" << endl;
    	}
    
    	// 拷贝构造函数
    	Person(const Person & p) {
    		// 将传入的人身上的所有属性,拷贝到我身上
    		age = p.age;
    		name = p.name;
    		cout << age << endl;
    		cout << name << endl;
    	}
    
    	~Person() {
    		cout << "Person的析构函数调用" << endl;
    	}
    
    	int age = 0;
    	string name;
    };
    
    void main() {
    	// 调用
    	
    
    	// 1.括号法
    	Person p("小明",18);
    	// 拷贝构造函数调用
    	Person p2(p);		
    	// 注意事项
    	// 调用默认构造函数时 不要加小括号! Person p();	编译器会认为是函数的声明
    
    
    
    	// 2.显示法
    	Person p3 = Person("小明");		// 由参构造
    	Person p4 = Person(p2);			// 拷贝构造
    	Person("小明");					// 匿名对象 特点:当前行执行结束后,系统会立即回收掉匿名函数
    	// 注意事项2 不要利用 拷贝构造函数 来初始化匿名对象
    	Person(p2);		// 编译器会认为 Person(p2) == Person p2;  这里p2 == p2 所以会报错 重定义!
    		
    
    
    	// 3.隐式转换法
    	// Person p5 = 10;		// 相当于 Person p5  = Person(10);
    }
    

    4.2.3 拷贝构造函数调用时机 **

    C++ 中 拷贝构造函数调用是通常有三种情况

    • 使用一个已经创建完毕的对象来初始化一个新对象
    • 值传递的方式给函数参数传值
    • 以值方式返回局部对象
    #include <iostream>
    using namespace std;
    // 拷贝构造函数调用时机
    
    
    
    class Person {
    public:
    	int m_age;
    	Person() {
    		cout << "无参构造函数执行" << endl;
    	}
    
    	Person(int age) {
    		m_age = age;
    		cout << "有参构造函数执行" << endl;
    	}
    
    	~Person() {
    		cout << "析构函数执行" << endl;
    	}
    
    	Person(const Person & p) {
    		m_age = p.m_age;
    		cout << "拷贝构造函数执行" << endl;
    	}
    };
    
    
    // 1. 使用一个已经创建完毕的对象初始化一个新对象
    void test01() {
    	Person p1(20);
    	Person p2(p1);
    }
    
    // 2.值传递的方式给函数参数传值
    void doWork(Person p) {		// Person p(函数里的p); 复制了一份
    
    }
    
    void test02() {
    	Person p;		// 这第一次执行调用了默认构造函数
    	doWork(p);		// 实参传型参 调用了 拷贝构造函数 
    }
    
    // 3.值方式返回局部对象
    Person doWork2() {
    	Person p1;		// 这里拷贝了一个新的对象返回
    	p1.m_age = 10;
    	return p1;		// 这里也会调用拷贝构造函数
    }
    
    void test03() {
    	Person p2 = doWork2();
    	// 接收的对象和返回的对象不是一个对象 地址不一样 
    	cout << p2.m_age << endl;
    	cout << p2.m_age << endl;
    	system("pause");
    	// 函数执行完毕后才会执p2的析构函数
    }
    
    void main() {
    	//test01();
    	//test02();
    	test03();
    	
    }
    

    4.2.4 构造函数调用规则

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

    1. 默认构造函数(无参数,函数体为空)
    2. 默认析构函数(无参数,函数体为空)
    3. 拷贝构造函数,对属性进行赋值拷贝

    构造函数调用规则如下:

    • 如果用户定义有参构造函数,c++ 不在提供默认构造函数,但是会提供拷贝构造函数
    • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
    // 测试代码
    
    #include <iostream>
    using namespace std;
    
    class Person {
    public:
    
    	int age;
    	string name;
    	// 无参构造函数
    	Person() {
    		cout << "无参构造函数执行" << endl;
    	}
    
    	// 有参构造函数
    	Person(int a) {
    		cout << "有参构造函数调用,参数为:" << a << endl;
    	}
    
    	// 拷贝构造函数
    	Person(const Person & p) {
    		// 拷贝构造函数进行赋值操作
    		age = p.age;
    		name = p.name;
    		cout << "拷贝构造函数调用" << endl;
    	}
    };
    
    void main() {
    	Person p;
    	p.age = 18;
    	p.name = "hello word";
    	Person p2(p);
    	cout <<"p2的年龄为:" << p2.age << endl;
    	cout <<"p2的姓名为:" << p2.name << endl;
    }
    

    4.2.5 深拷贝和浅拷贝

    深浅拷贝是面试的经典问题,也是常见的一个坑

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

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

    示例:

    // 自己实现拷贝构造函数 解决拷贝带来的问题
    	Person(const Person & p) {
    		cout << "拷贝构造函数调用" << endl;
    		/*
    		编译器写法:
    		m_age = p.m_age;
    		Height = p.Height;
    		*/
    
    		// 深拷贝操作  在申请一块内存给height赋值
    		Height = new int(*p.Height);
    		m_age = p.m_age;
    	}
    // 骚操作
    
    #include <iostream>
    using namespace std;
    // 深浅拷贝
    class Person {
    public:
    	int m_age;
    	int* Height;
    	Person() {
    		cout << "Person的默认构造函数调用" << endl;
    	}
    
    	Person(int age,int height) {
    		m_age = age;
    		Height = new int(height);   // 返回地址 手动开辟 手动释放
    		cout << "Person的有参构造函数" << endl;
    	}
    
    	// 将我们堆区开辟的数据 做释放操作
    	~Person() {
    		// 判断是否是空指针
    		if (Height!=NULL) {
    			delete Height;	// 释放
    			Height = NULL;	// 防止野指针的出现
    		}
    		cout << "析构函数调用" << endl;
    		
    	}
    
    	// 自己实现拷贝构造函数 解决拷贝带来的问题
    	Person(const Person & p) {
    		cout << "拷贝构造函数调用" << endl;
    		/*
    		编译器写法:
    		m_age = p.m_age;
    		Height = p.Height;
    		*/
    
    		// 深拷贝操作  在申请一块内存给height赋值
    		Height = new int(*p.Height);
    		m_age = p.m_age;
    	}
    
    };
    
    void test01() {
    	Person p1(18, 160);
    	cout << "p1的年龄为:" << p1.m_age << "  p1的身高为:" << *p1.Height << endl;
    
    	Person p2(p1);  // 浅拷贝
    	cout << "p2的年龄为:" << p2.m_age << "  p2的身高为:" << *p2.Height << endl;
    	system("pause");
    	// 浅拷贝带来的问题是 堆区的内存重复释放
    	// 浅拷贝的问题利用深拷贝来解决
        
        // 释放的时候是堆栈 先进后出
    }
    
    
    
    
    void main() {
    	test01();
    }
    

    如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题

    4.2.6 初始化列表

    作用:

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

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

    // 初始化列表属性
    Person(string name,int age,int id):m_name(name),m_age(age),m_id(id) {
        // 初始化完毕
    }
    
    #include <iostream>
    using namespace std;
    
    class Person {
    public:
    	string m_name;
    	int m_age;
    	int m_id;
    
    	// 传统方式初始化
    	//Person(string name,int age,int id) {
    	//	m_name = name;
    	//	m_age = age;
    	//	m_id = id;
    	//}
    
    	// 初始化列表属性
    	Person(string name,int age,int id):m_name(name),m_age(age),m_id(id) {
    		// 初始化完毕
    	}
    };
    
    void test01() {
    	Person p1("小明",18,1);
    	cout << p1.m_age << endl;
    	cout << p1.m_name << endl;
    	cout << p1.m_id << endl;
    }
    
    void main() {
    	test01();
    }
    

    4.2.7 类对象作为类成员

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

    例如:

    class A{};
    class B{
    	A a;
        // B类中有对象A作为成员,A为对象成员
    }
    

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

    #include <iostream>
    using namespace std;
    
    class A {
    
    public:
    	A() {
    		cout << "A构造函数执行" << endl;
    	}
    };
    
    class B {
    public:
    	A a;
    	B(){
    		cout << "B构造函数执行" << endl;
    	}
    	
    };
    
    void main() {
    	B b;
    
    }
    

    先执行A 后执行B debug得知到了构造函数并未执行,跳转A构造函数中!

    当其他类对象作为本类成员,构造时候先构造类对象,再构造自身,析构顺序与其相反

    4.2.8 静态成员

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

    静态成员分为:

    • 静态成员变量
      • 所有对象共享一份数据
      • 在编译阶段分配内存
      • 类内声明,类外初始化
    • 静态成员函数
      • 所有对象共享同一个函数
      • 静态成员函数只能访问静态成员变量
      • 访问权限是 公共的

    示例1: 静态成员变量

    #include <iostream>
    using namespace std;
    // 静态成员函数
    // 所有对象共享一个函数
    // 静态函数只能访问静态成员变量
    class Person {
    public :
    	static void func() {
    		cout << "i am func" << endl;
    		m_ip = 192;
    		// port = 20; // 报错 不可改变 因为func是共享的 并不知道那个对象修改!!!
    	}
    
    	int port;
    	static int m_ip;	// 静态成员
    	// 特点,类内声明,类外初始化
    
    };
    
    // 声明
    int Person::m_ip = 0;  // 初始化
    
    void main() {
    	// 对象调用
    	Person p1;
    	p1.func();
    	// 类调用
    	Person::func();
    }
    

    4.3 c++对象模型和this指针

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

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

    #include <iostream>
    using namespace std;
    
    // 初始化
    
    class Person {
    public:
    	int m_age; // 非静态成员
    	static int id; // 静态成员
    };
    
    int Person::id;
    
    void test01() {
    	Person p1;	
    	// 空对象占用内存空间为: 1个字节
    	// C++ 编译器会给每个空对象分配一个字节空间,是为了区分空对象占内存的位置 两个对象不能在同个内存 
    	cout << "size of p1" << sizeof(p1) << endl;   // 有了m_age 变成了4个字节
    	// 非静态成员变量属于类的
    }
    
    void main() {
    	test01();
    }
    

    4.3.2 this指针概念

    静态函数如何区分那个对象调用自己?

    this指针指向被调用的成员函数所属的对象

    this指针是隐含每一个非静态成员函数内的一种指针

    this指针不需要定义,直接使用即可

    this指针的用途:

    • 当形参和成员同名时,可以用this指针
    • 在类的非静态成员函数中返回对象本身, 可以使用 return*this

    链式编程思想:

    #include <iostream>
    using namespace std;
    
    class Person {
    public:
    	int age;
    	Person& get(const Person & p) {
    		this->age += p.age;
    		return *this;
    	}
    };
    
    
    void main() {
    	Person p1;
    	p1.age = 10;
    
    	Person p2;
    	p2.age = 10;
    	p2.get(p1).get(p1).get(p1);
    	cout << p2.age << endl;
    }
    

    4.3.3 空指针访问成员函数

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

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

    #include <iostream>
    using namespace std;
    
    class Person {
    public:
    	int age;
    	void ShowClassName() {
    		cout << "i am person" << endl;
    	}
    
    	void ShowAge() {
    		if (this == NULL) {
    			return;
    		}
    		cout << age << endl;
    	}
    };
    
    void main() {
    	Person* p = NULL;
    	p->ShowClassName();
    	p->ShowAge(); // 报错! 内部age属于 this.age this是空指针 所以报错
    }
    

    4.3.4 coust修饰成员函数

    常函数:

    • 成员函数后加const后我们称这个函数为常函数
    • 常函数不可以修改成员属性
    • 成员属性声明时加, mutable 后,在常函数中依然可以修改

    常对象:

    • 声明对象前加const称该对象为常对象
    • 常对象只能调用常函数
    #include <iostream>
    using namespace std;
    
    class Person {
    public:
    	mutable int age ; // 加了mutable就可修改了
    
    	// this指针本质是 指针常量 指针指向是不可以修改的 加了const后 都不可改了
    	void ShowClassName() const {
    		//age = 15; // 不加const可以修改
    		age  = 15 ;
    	}
    
    };
    
    void main() {
    	const Person p1; // 常对象 不可以修改指针指向的值
    	p1.ShowClassName();
    }
    

    4.4 友元

    在程序中,有些私有属性 也想让类外的一些特殊函数或者类进行访问,就需要友元技术

    友元的目的是让一个函数或者类访问一个类中的私有成员

    友元的关键字为 friend

    友元的三种实现:

    • 全局函数做友元
    • 类做友元
    • 成员函数做友元

    4.4.1 全局函数做友元

    #include <iostream>
    using namespace std;
    
    
    // 房屋类
    class Building 
    {
    	// goodGay 是 building的好朋友,可以访问building中的私有成员
    	friend void goodGay(Building& building); // 类似声明就行
    public:
    	Building() 
    	{
    		m_SittingRoom = "客厅";
    		m_BedRoom = "卧室";
    	}
    	string m_SittingRoom; // 客厅
    
    private:
    	string m_BedRoom; // 卧室
    };
    
    // 全局函数做友元
    void goodGay(Building & building) {
    	cout << "好基友访问" << building.m_SittingRoom << endl;
    	cout << "好基友访问" << building.m_BedRoom << endl;
    }
    
    
    void main() {
    	Building building;
    	goodGay(building);
    }
    

    4.4.2 类做友元

    #include <iostream>
    using namespace std;
    
    
    // 房屋类
    class Building 
    {
    	friend class GoodGay;	// goodGay是本类的好朋友 可以访问私有成员
    public:
    	Building();
    	
    	string m_SittingRoom; // 客厅
    
    private:
    	string m_BedRoom; // 卧室
    };
    
    
    // 好基友
    class GoodGay {
    public:
    	Building* building;
    	GoodGay();
    	void visit();
    };
    
    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 main() {
    	GoodGay gg;
    	gg.visit();
    }
    

    4.4.3 成员函数做友元

    #include <iostream>
    using namespace std;
    
    class Building; // 声明 待会写..
    // 基友类
    class GoodGay {
    public:
    	GoodGay();
    	Building* building;
    	void visit();// 可以访问
    	void visit2();	// 不可访问
    };
    
    // 房屋类
    class 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 << "好基友访问" << building->m_SittingRoom << endl;
    	cout << "好基友访问" << building->m_BedRoom << endl;
    }
    
    void GoodGay::visit2() {
    	cout << "好基友访问" << building->m_SittingRoom << endl;
    	// cout << "好基友访问" << building->m_BedRoom << endl; 不能访问
    }
    
    void main() {
    	GoodGay gg;
    	gg.visit();
    	gg.visit2();
    }
    

    4.5 运算符重载

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

    4.5.1 加号运算符重载

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

    /*
    对于内置数据类型,编译器知道如何进行运算 例如
    int a = 10;int b = 10;
    int c = a + b;
    
    
    class Person{
    public:
    	int m_a;
    	int m_b;
    }
    
    Person p1;
    p1.m_a = 10;
    p1.m_b = 10;
    
    Person p2;
    p2.m_a = 10;
    p2.m_b = 10;
    
    Person p3 = p1 + p2;   // 我们想两个自定义数据的相加运算 
    */
    

    通过自己写的成员函数,实现两个对象相加属性后返回新的对象

    //test
    Person& test(Person & p){
        Person temp;
        temp.m_a = this->m_a + p.m_a;
        temp.m_b = this->m_b + p.m_b;
        return temp
    }
    

    编译器起了一个通用名oprator+

    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
    }
    
    // 调用
    Person p3 = p1.operator+ (p2);
    // 简化
    Person p3 = p1 + p2;
    

    全局函数重载+

    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 p3 = operator+(p1,p2);
    // 简化
    Person p3 = p1 + p2;
    

    示例:

    
    // 加号运算符号重载
    #include <iostream>
    using namespace std;
    
    class Person {
    public:
    	Person() {
    
    	}
    	Person(int a,int b):m_a(a),m_b(b) {
    		
    	}
    	int m_a;
    	int m_b;
    
    
    	// 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;
    	};
    
    	~Person() {
    		cout << "我释放了" << endl;
    	}
    
    	Person(const Person & p) {
    		this->m_a = p.m_a;
    		this->m_b = p.m_b;
    		cout << "copy diao yong" << endl;
    	}
    };
    
    // 全局函数做重载+
    //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;
        // 返回完毕就会析构了 不要返回指针
    }
    
    int main() {
    	cout << "1" << endl;
    	Person p1(10,10);
    	cout << "2" << endl;
    	Person p2(10,10);
    	cout << "3+" << endl;
    	Person  p3 = p1 + p2;
    	cout << p3.m_a << endl;
    	cout << p3.m_b << endl;
    	cout << "4+" << endl;
    	// 运算符号重载,也可以发生函数重载
    	Person p4 = p1 + 11;
    	cout << p4.m_a << endl;
    	cout << p4.m_b << endl;
    	
    	return 0;
    }
    
    

    4.5.2 左移运算符重载

    可以输出自定义的数据类型

    // 左移运算符重载!
    #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;
    	// 成员函数重载
    	//void operator<<(Person &p) {
    
    	//}
    	// 不会利用成员函数重载<<运算符因为无法实现<< 在左侧  
    
    };
    
    // 只能利用全局函数重载
    ostream& operator<<(ostream &cout,Person &p) { // 本质 operator<<(cout,p)  简化 cout<<p; cout只能有一个
    	cout << "m_a= " << p.m_a <<endl;
    	cout << "m_a= " << p.m_b <<endl;
    	return cout; // 实现无限调用
    }
    void test01() {
    	Person p1(21,22);
    	cout << p1 << endl;
    }
    int main() {
    	test01();
    	return 0;
    }
    
    

    4.5.3 递增运算符重载

    实现自己数据的递增

    // 递增运算符号重载
    #include <iostream>
    using namespace std;
    class MyInteger {
    	friend ostream& operator<<(ostream& cout, MyInteger& i);
    public:
    	MyInteger() {
    		// 初始化
    		m_num = 0;
    	}
    
    	// 前置++
    	MyInteger & operator++() {
    		this->m_num++;
    		return *this;
    	}
    	// 后置++  加个int 这个int代表占位参数,用于区分前置和后置递增
    	MyInteger operator++(int) {
    		// 先表达式 记录当前结果 
    		MyInteger temp = *this;
    		// 后递增
    		m_num++;
    		return temp; // 返回值,不然会被释放掉
    	}
    
    	MyInteger(const MyInteger&i) {
    		cout << "拷贝调用" << endl;  // 加引用不加引用区别在于是否调用拷贝构造函数
    	}
    
    private:
    	int m_num;
    
    };
    
    // 左运算符号重载
    ostream& operator<<(ostream &cout,MyInteger &i) {
    	cout << i.m_num << endl;
    	return cout;
    }
    
    int main() {
    	MyInteger i;
    	++(++i);
    	cout << i << endl;
    	++i;
    	cout << i << endl;
    	return 0;
    }
    

    4.5.4 赋值运算符重载

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

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

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

    3 默认拷贝函数 对属性进行copy

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

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

    #include <iostream>
    using namespace std;
    
    // 赋值运算符
    class Person {
    public:
    	Person(int age) {
    		m_age = new int(age);
    	}
    
    	int* m_age;
    	
    	~Person() {
    		if (m_age != NULL) {
    			delete m_age;
    			m_age = NULL;
    		}
    	}
    	
    	// 重载赋值运算符
    	Person & operator=(Person & p2) {
    		// 编译器提供拷贝
    		// m_age = p.m_age;
    
    		// 判断是否有属性在堆,如果有,先释放干净
    		if (this->m_age!=NULL) {
    			delete m_age;
    			m_age = NULL;
    		}
    		// 深拷贝
    		m_age = new int(*p2.m_age);
    		// 返回对象本身
    		return *this;
    	}
    };
    
    void test01() {
    	Person p1(18);
    	Person p2(20);
    	Person p3(50);
    	cout << *p1.m_age << endl;
    	cout << *p2.m_age << endl;	
    	cout << *p3.m_age << endl;	
    	p2 = p1 = p3;
    	cout << *p2.m_age << endl;
    	// 程序结束 报错 重复释放问题
    }
    
    int main() {
    	test01();
    }
    

    4.5.5 关系运算符重载

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

    #include <iostream>
    using namespace std;
    class Person {
    public:
    	Person(int age,string name,int mobile) :m_age(age),m_name(name),m_mobile(mobile) {
    
    	}
    
    	bool operator==(Person & p) {
    		if (this->m_age == p.m_age && this->m_mobile == p.m_mobile && this->m_name == p.m_name) {
    			return true;
    		}
    		return false;
    	}
    
    private:
    	int m_age;
    	string m_name;
    	int m_mobile;
    };
    
    
    int main() {
    	Person p1(18, "tom", 1589);
    	Person p2(19, "tom", 1589);
    	if (p1 == p2) {
    		cout << "相等" << endl;
    	}
    	else {
    		cout << "不相等" << endl;
    	}
    	return 0;
    }
    

    4.5.6 函数调用运算符重载

    • 函数调用运算符() 也可以重载
    • 由于重载后使用的方式非常像函数的调用,因此称为 仿函数
    • 仿函数没有固定写法,非常灵活
    // 仿函数 函数调用运算符号重载
    #include <iostream>
    using namespace std;
    
    // 打印输出类
    class MyPrint {
    public:
    	// 重载函数调用运算符
    	void operator()(string str){
    		cout << str << endl;
    	}
    
    	int operator()(int num, int num2) {
    		return num + num2;
    	}
    };
    
    class MyAdd {
    public:
    	int operator()(int num,int num2) {
    		return num + num2;
    	}
    };
    
    int main() {
    	MyPrint p;
    	p("hello");  // 使用起来特别像函数,所以叫仿函数
    	int res1 = p(22,5);
    	cout << res1 << endl;
    
    	MyAdd add;
    	int res = add(18,20);
    	cout << res << endl;
    
    	// 匿名函数对象
    	cout << MyAdd()(100,200) << endl; // 匿名对象 加了() 执行完毕就释放
    
    	return 0;
    }
    

    4.6继承

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

    有些类之间存在特殊关系如: 动物>狗,猫.....> 猫分为 品种.....

    4.6.1 继承的基本语法

    语法: class 子类: 继承方式 父类 子类称为:派生类,父类称为基类

    #include <iostream>
    using namespace std;
    
    
    
    // 继承实现
    class BasePage {
    public:
    	void header() {
    		cout << "我是header" << endl;
    	}
    
    	void left() {
    		cout << "我是left" << endl;
    	}
    };
    
    
    // 普通页面实现
    // 语法: class 子类: 继承方式 父类
    class Java:public BasePage {
    public:
    	void content(){
    		cout << "javaContent" << endl;
    	}
    };
    
    int main() {
    
    	Java j1;
    	j1.content();
    	j1.left();
    	j1.header();
    	return 0;
    }
    

    4.6.2 继承方式

    继承的语法:class 子类:继承方式 父类

    继承方式一共有三种

    • 公共继承
    • 保护继承
    • 私有继承

    父类中的私有权限 子类都访问不了

    继承方式是 public 父类除了私有,其他都不动 该是什么访问权限是什么访问权限

    继承方式是protected 父类除了私有,所有都变成 protected访问权限

    继承方式是protected 父类除了私有,所有变为私有权限

    //#include <iostream>
    //using namespace std;
    //
    //
    //
    //// 继承实现
    //class BasePage {
    //public:
    //	void header() {
    //		cout << "我是header" << endl;
    //	}
    //
    //	void left() {
    //		cout << "我是left" << endl;
    //	}
    //};
    //
    //
    //// 普通页面实现
    //// 语法: class 子类: 继承方式 父类
    //class Java:public BasePage {
    //public:
    //	void content(){
    //		cout << "javaContent" << endl;
    //	}
    //};
    //
    //int main() {
    //
    //	Java j1;
    //	j1.content();
    //	j1.left();
    //	j1.header();
    //	return 0;
    //}
    
    #include <iostream>
    using namespace std;
    class B;
    class Base {
    	friend class B;
    public:
    	int a;
    protected:
    	int b;
    private:
    	int c;
    };
    
    class A :public Base {
    public:
    	void func() {
    		this->a;  // 能访问
    		this->b;  // 能访问
    	}
    };
    
    class B : protected Base {
    public:
    	void func() {
    		this->a;  // 能访问
    		this->b;  // 能访问
    		this->c;  // 尝试用友元访问
    		c = 100;
    		cout << c << endl;
    	}
    };
    
    class C :private Base {
    public:
    	void func() {
    		// 都不能访问
    	}
    };
    
    int main() {
    	B b;
    	b.func();
    
    }
    

    4.6.3 继承中的对象模型

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

    开始文档>vs>工具查看 打开vs下的命令窗口 然后切换到项目目录 开发人员命令提示工具

    cl /d1 reportSingleClassLayoutSon "15 继承.cpp"

    cl /d1 reportSingleClassLayout类名 项目名

    报告单个类的布局 reportSingleClassLayout

    15 继承.cpp

    class Son size(16):
    +----------------
    0 | +--- (base class Base)
    0 | | m_a
    4 | | m_b
    8 | | m_c
    | +---
    12 | m_d
    +-------------

    // 继承中的对象模型
    
    #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;
    };
    
    int main() {
    	cout << sizeof(Son) << endl;  // 16 4个int大小
    	// 父类中非静态的属性都会被子类继承下去 
    	// 父类中私有的属性是被编译器隐藏了,但是继承了
    }
    

    父类中私有的属性是被编译器隐藏了,但是继承了 父类中非静态的属性都会被子类继承下去

    4.6.4 继承中构造函数和析构函数

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

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

    // 继承中构造和析构顺序
    #include <iostream>
    using namespace std;
    
    class Base {
    public:
    	Base() {
    		cout << "基类构造函数执行" << endl;
    	}
    
    	~Base() {
    		cout << "基类析构函数执行" << endl;
    	}
    };
    
    class Son :public Base {
    public:
    	Son() {
    		cout << "子类构造函数执行" << endl;
    	}
    
    	~Son() {
    		cout << "子类析构函数执行" << endl;
    	}
    };
    
    int main() {
    	Son s1;
    	return 0;
    }
    
    
    // 结果
    /*
        基类构造函数执行
        子类构造函数执行
        子类析构函数执行
        基类析构函数执行
    */
    

    4.6.5 继承同名成员处理方式

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

    • 访问子类同名成员 执行访问即可
    • 访问基类同名成员 需要添加作用域

    示例:

    
    // 继承同名成员处理方式
    #include <iostream>
    using namespace std;
    
    class Base {
    public:
    	int m_a;
    	Base() {
    		m_a = 100;
    	}
    
    	void func() {
    		cout << "base 调用" << endl;
    	}
    };
    
    class Son :public Base {
    public:
    	Son() {
    		m_a = 200;
    	}
    	int m_a;
    
    
    	void func() {
    		cout << "son 调用" << endl;
    	}
    };
    
    // 同名 成员属性处理
    void test01() {
    	Son s;
    	cout <<"Son:" << s.m_a << endl;  // 200
    	cout <<"Base:" << s.Base::m_a << endl;  // 100W
    }
    
    // 同名 成员函数处理
    void test02() {
    	Son s;
    	s.func();
    	s.Base::func();
    }
    
    int main() {
    	test01();
    	test02();
    	return 0;
    }
    

    如果子类中出现了和父类同名的成员函数,编译器就会把父类的隐藏掉 只能访问到子类的 访问父类加作用域

    4.6.6 继承同名静态成员处理

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

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

    • 访问子类同名成员 直接访问即可
    • 访问父类同名成员 需要添加作用域

    示例:

    // 继承中的同名静态成员处理方式
    #include <iostream>
    using namespace std;
    class Base{
    public:
    	int m_a;
    	static int m_b;
    };
    int Base::m_b = 100;
    
    class Son :public Base {
    public:
    	int m_a;
    	static int m_b;
    };
    int Son::m_b = 200;
    
    int main() {
    	Son s1;
    	cout << s1.m_b << endl;
    	cout << s1.Base::m_b << endl;
    
    	//  2 通过类名访问
    	cout << Son::m_b << endl;
    	cout << Base::m_b << endl;
    	cout << Son::Base::m_b << endl; // 类名方式访问父类然后访问m_b
    	return 0;
    }
    

    4.6.7 多继承语法

    c++ 运行一个类继承多个类

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

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

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

    // 多继承
    #include <iostream>
    using namespace std;
    class Base1 {
    public:
    	int m_a = 100;
    };
    
    class Base2 {
    public:
    	int m_a = 200;
    };
    
    class Son :public Base1, public Base2 {
    public:
    	int m_a = 300;
    };
    
    int main() {
    	Son s1;
    	cout << s1.m_a << endl;  // 300
    	cout << s1.Base1::m_a << endl; // 100
    	cout << s1.Base2::m_a << endl; // 200
    }
    

    4.6.8 菱形继承

    菱形继承概念:

    ​ 有两个派生类继承同一基类

    ​ 又有某个类同时继承两个派生类

    ​ 这种继承被称为菱形继承,或者钻石继承

    // 菱形继承
    
    #include <iostream>
    using namespace std;
    
    // 动物类
    class Animal{
    public:
    	int m_age;
    };
    
    // 羊类
    class Sheep :virtual public Animal {
    
    };
    
    // 驼类
    class Tuo :virtual public Animal {
    
    };
    
    // 羊驼
    class SheepTuo : public Sheep, public Tuo {
    
    };
    
    // 利用虚继承 解决菱形继承的问题 在 public前加上 virtual
    // 此时基类称为 虚基类
    
    int main() {
    	SheepTuo s1;
    	s1.m_age = 100;
    	return 0;
    }
    

    4.7 多态

    4.7.1 多态的基本概念

    多态分为两类

    • 静态多态:函数重载和运算符重载属于静态多态,复用函数名
    • 动态多态:派生类和虚函数运行时多态

    静态多态和动态多态区别:

    • 静态多态的函数地址早绑定-编译阶段确定函数地址
    • 动态多态的函数地址晚绑定-运行阶段确定函数地址
    #include <iostream>
    using namespace std;
    
    // 动物类
    class Animal {
    public:
    	virtual void speak() {  // 虚函数
    		cout << "动物在说话" << endl;
    	}
    };
    
    class Cat :public Animal {
    public:
    	void speak() {
    		cout << "小猫在说话" << endl;
    	}
    };
    
    class Dog :public Animal {
    public:
    	void speak() {
    		cout << "小狗在说话" << endl;
    	}
    };
    
    // 执行说话的函数
    void doSpeak(Animal &animal) { // Animal & animal = cat;
    	animal.speak(); // 函数地址早绑定 编译阶段确定了函数的地址是动物的
    }
    
    void test01() {
    	Cat cat;
    	doSpeak(cat);
    	// 如何让猫说话?
    	// 那么这个函数的地址就不能提前绑定,需要运行阶段绑定
    	// 基类函数添加 virtual 虚函数
    	Dog dog;
    	doSpeak(dog);
    
    }
    int main() {
    	test01();  // 走的动物在说话
    	return 0;
    }
    
    // 动态多态满足条件
    // 1.有继承关系
    // 2.子类需要重写父类虚函数
    
    // 动态多态使用
    // 父类的指针或者引用,指向子类的对象
    
    

    总结:

    动态多态满足条件

    • 1.有继承关系
    • 2.子类需要重写父类虚函数

    动态多态使用条件

    • 父类的指针或者引用,指向子类的对象

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

    image-20220403233547831

    image-20220403234433082

    4.7.2 多态案例一 计算器类

    案例描述:

    分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

    多态优点

    • 代码组装结构清晰
    • 可读性强
    • 利于前期和后期的扩展以及维护

    示例:

    // 普通写法
    #include <iostream>
    using namespace std;
    class Calculator
    {
    public:
    	int m_num1; // 操作数1
    	int m_num2; // 操作数2
    
    	int getResult(string oper) {
    		if (oper == "+") {
    			return this->m_num1 + this->m_num2;
    		}
    		else if (oper == "-") {
    			return this->m_num1 - this->m_num2;
    		}
    		else if (oper == "*-") {
    			return this->m_num1* this->m_num2;
    		}
    		else if (oper == "/") {
    			return this->m_num1 / this->m_num2;
    		}
    		else {
    			return 0;
    		}
    	}
    };
    int main() {
    	Calculator c;
    	c.m_num1 = 15;
    	c.m_num2 = 20;
    	int num = c.getResult("+");
    	cout << num << endl;
    }
    // 如果想扩展,需要修改源码
    // 在真实开发中 提倡 开闭原则
    // 对扩展开发,对修改封闭
    
    // 利用多态实现计算器
    #include <iostream>
    using namespace std;
    
    // 计算器基类
    class CalculatorAbstact {
    	// 抽象类 什么功能都不写
    public:
    	int m_num1;
    	int m_num2;
    	virtual int getResult() {
    		return 0;
    	}
    };
    
    // 加法计算器类
    class AddCalculator :public CalculatorAbstact {
    public:
    	int getResult() {
    		return m_num1 + m_num2;
    	}
    };
    
    // 减法计算器类
    class SubCalculator :public CalculatorAbstact {
    public:
    	int getResult() {
    		return m_num1 - m_num2;
    	}
    };
    
    void test02() {
    	// 多态的使用条件
    	// 父类的指针或者引用指向子类的对象
    
    	// 加法
    	CalculatorAbstact* abc = new AddCalculator;
    	abc->m_num1 = 10;
    	abc->m_num2 = 20;
    	cout << abc->getResult() << endl;
    	// 用完释放
    	delete abc;
    
    	// 减法
    	abc = new SubCalculator;
    	abc->m_num1 = 15;
    	abc->m_num2 = 20;
    	cout << abc->getResult() << endl;
    	delete abc;
    }
    
    int main() {
    	test02();
    	return 0;
    }
    

    4.7.3 纯虚函数和抽象类

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

    应次可以将虚函数改为纯虚函数

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

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

    抽象类特点:

    • 无法实例化对象
    • 子类必须重写抽象类,否则也属于抽象类
    // 纯虚函数 和 抽象类
    #include <iostream>
    using namespace std;
    class Base {
    public:
    	virtual void func() = 0; // 纯虚函数
    	// 这个类称为抽象类
    	// 抽象类特点
    	// 1 无法实例化
    	// 2 子类必须实现该方法
    	
    };
    
    class Son :public Base {
    	void func() {
    
    	}
    };
    
    int main() {
    	// Base b; // 报错
    	// new Base; // 报错
    	
    	Son s; // 报错 方法未实现
    
    	Base* res = new Son;
    	res->func(); // 实现多态的条件 父类指针或引用指向子对象
    	return 0;
    }
    

    4.7.4 多态案例二 制作饮品

    案例描述:

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

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

    #include <iostream>
    using namespace std;
    
    class Base {
    public:
    	virtual void zhushui() = 0;
    	virtual void chongpao() = 0;
    	virtual void daoru() = 0;
    	virtual void jiaru() = 0;
    };
    
    class Coffee :public Base {
    public:
    	void zhushui() {
    		cout << "coffee 煮水" << endl;
    	};
    	void chongpao() {
    		cout << "coffee 冲泡" << endl;
    	};
    
    	void daoru() {
    		cout << "coffee 到水" << endl;
    	};
    	void jiaru() {
    		cout << "coffee 假如咖啡" << endl;
    	};
    };
    
    
    class Tee :public Base {
    public:
    	void zhushui() {
    		cout << "tee 煮水" << endl;
    	};
    	void chongpao() {
    		cout << "tee 冲泡" << endl;
    	};
    
    	void daoru() {
    		cout << "tee 到水" << endl;
    	};
    	void jiaru() {
    		cout << "tee 假如咖啡" << endl;
    	};
    };
    
    
    int main() {
    	Base *res = new Coffee;
    	res->zhushui();
    	res->chongpao();
    	res->daoru();
    	res->jiaru();
    
    	delete res;
    	res = new Tee;
    	res->zhushui();
    	res->chongpao();
    	res->daoru();
    	res->jiaru();
    
    }
    

    4.7.5 虚析构和纯析构

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

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

    虚析构和纯析构共性:

    • 可以解决父类指针释放子类对象
    • 都需要有具体的函数实现

    虚析构和纯析构区别:

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

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

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

    案例认真读:

    #include <iostream>
    using namespace std;
    
    class Animal {
    public:
    	virtual void speak() = 0; // 纯虚函数
    	Animal() {
    		cout << "animal构造函数调用" << endl;
    	}
    	// 解决方法 改为虚析构 那么应该就会有虚析构指针指向vftable 然后覆盖后指向cat的析构入口
    	// 父类指针释放子类对象时不干净的问题
    	/*virtual ~Animal() {
    		cout << "animal析构函数调用" << endl;
    	}*/
    	virtual ~Animal() = 0; // 两个只能有一个 纯虚析构 也能解决这个问题 但是会报错 必须要有函数体
    	// 有了纯虚析构 那么也属于抽象类,无法实例化
    
    
    };
    
    // 纯虚析构
    Animal::~Animal() {
    	// 纯虚析构函数体
    	cout << "animal析构函数调用" << endl;
    }
    
    class Cat:public Animal {
    public:
    	void speak() {
    		cout << *this->name <<"猫在说话" << endl;
    	}
    	string * name;
    
    	Cat(string name) {
    		cout << "Cat构造函数调用" << endl;
    		this->name = new string(name); // 堆区开辟内存
    	}
    	
    	// 解决深浅拷贝问题
    	Cat(const Cat & c) {
    		cout <<* c.name << "猫 copy 函数执行了" << endl;
    		this->name = new string(*c.name);  // 防止野指针的出现
    	}
    
    	// 解决深浅拷贝问题
    	// 当animal释放了 cat析构函数没有执行
    	~Cat() {
    		cout << "Cat析构函数执行" << endl;
    		// 堆区内存手动开辟手动释放
    		if (this->name != NULL) {
    			delete this->name;
    			this->name = NULL;
    		}
    	}
    };
    
    
    
    void test01() {
    	Animal* animal = new Cat("汤姆");
    	animal->speak();
    	delete animal; // 释放指针 释放的是animal 并没有走 cat的析构 如果cat内有堆区数据 那么会出现内存的泄露
    }
    
    int main() {
    	test01();
    	return 0;
    }
    

    总结:

    • 虚析构或纯虚析构就是用来解决 通过父类指针释放子类对象
    • 如果子类中没有堆区数据,可以不写 虚析构或纯虚析构
    • 拥有纯析构函数的类也属于抽象类

    4.7.6 多态案例三 电脑组装

    案例描述:

    电脑主要组成部分为CPU(用于计算),显卡(用于显示),内存条(用于储存)

    将每个零件封装出抽象基类,并提供不同产生生产不同零件,例如 intel 厂商 和 Lenovo厂商

    创建电脑类提供让电脑工作的函数,并调用每个零件工作接口

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

    示例:

    #include <iostream>
    using namespace std;
    // 抽象出每个零件
    class Cpu {
    public:
    	virtual void calculate() = 0; // 纯虚函数 抽象计算函数
    };
    
    class VidoCard {
    public:
    	virtual void display() = 0; // 纯虚函数 抽象显示函数
    };
    
    class Memory {
    public:
    	virtual void storage() = 0; // 纯虚函数 抽象存储函数
    };
    
    
    
    // 具体厂商 
    // inter厂商
    class IntelCpu :public Cpu{
    public:
    	void calculate() {
    		cout << "英特尔电脑cpu开始工作" << endl;
    	}
    };
    
    class IntelVidoCard :public VidoCard {
    public:
    	void display() {
    		cout << "英特尔电脑显卡开始工作" << endl;
    	}
    };
    
    
    class IntelMemory :public Memory {
    public:
    	void storage() {
    		cout << "英特尔电脑内存开始工作" << endl;
    	}
    };
    
    
    // lenovo 厂商
    class LenovoCpu :public Cpu {
    public:
    	void calculate() {
    		cout << "Lenovo电脑cpu开始工作" << endl;
    	}
    };
    
    class LenovoVidoCard :public VidoCard {
    public:
    	void display() {
    		cout << "Lenovo电脑显卡开始工作" << endl;
    	}
    };
    
    
    class LenovoMemory :public Memory {
    public:
    	void storage() {
    		cout << "Lenovo电脑内存开始工作" << endl;
    	}
    };
    
    
    // 电脑类
    class Computer {
    	// 构造函数中传入三个零件指针
    	// 提供工作函数 调用每个零件的功能
    public:
    
    	Computer(Cpu* cpu, VidoCard* vidoCard, Memory* memory) {
    		this->m_cpu = cpu;
    		this->m_memory = memory;
    		this->m_vidoCard = vidoCard;
    	}
    
    	void run() {
    		this->m_cpu->calculate();
    		this->m_memory->storage();
    		this->m_vidoCard->display();
    	}
    	~Computer() {
    		if (m_cpu != NULL) {
    			delete m_cpu;
    			m_cpu = NULL;
    		}
    		else if (m_vidoCard != NULL) {
    			delete m_vidoCard;
    			m_vidoCard = NULL;
    		}
    		else if (m_memory != NULL) {
    			delete m_memory;
    			m_memory = NULL;
    		}
    	}
    private:
    	Cpu* m_cpu;
    	VidoCard* m_vidoCard;
    	Memory* m_memory;
    };
    
    
    
    int main() {
    	Cpu* C1 = new LenovoCpu;
    	VidoCard* V1 = new IntelVidoCard;
    	Memory* M1 = new LenovoMemory;
    
    	// 创建台电脑
    	Computer * c1 = new Computer (C1,V1,M1);
    	c1->run();
    	// 释放电脑 同时释放零件
    	delete c1;
    	
    	// 创建电脑2
    	Computer* c2 = new Computer(new LenovoCpu, new IntelVidoCard, new LenovoMemory);
    	c2->run();
    	// 释放电脑 同时释放零件
    	delete c2;
    
    	return 0;
    }
    
  • 相关阅读:
    Linux基操:软件安装的方式:解压缩安装
    Linux基操:linux命令失效解决方法
    Linux基操:软件安装的方式:rpm,&&环境变量配置
    Linux基操:进程管理
    Linux基操:磁盘管理,扩展文件挂载问题
    Linux基操(Centos7):用户管理
    Linux基操:文件内容查看方式&&软链接硬链接(拓展)
    Linux基操:目录相关命令
    多测师肖sir_高级金牌讲师__面试和答案归纳
    多测师肖sir__高级金牌讲师 ___python中len函数
  • 原文地址:https://www.cnblogs.com/lddragon/p/16080204.html
Copyright © 2020-2023  润新知