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;
}
posted @ 2022-03-31 09:02  LD_Dragon  阅读(27)  评论(0编辑  收藏  举报