Skip to content

面向对象

约 7971 字大约 27 分钟

C++

2025-03-04

本文作者:程序员飞云

本站地址:https://www.flycode.icu

类的设计和对象创建

类的设计

使用关键字class描述一个类

类是有若干个相同的特征和功能对象的集合,类里面书写对象共有的特征和行为。有属性和函数组成。

默认创建的class是私有权限,也就是private

访问权限:

private:私有权限,当前类里面才能访问

protected: 保护权限,类外不能访问,当前类和子类访问

public: 公开权限,任意地方访问


class Person {
public:
	string name;
	int age;
	double score;
	int gender;

	void eat() {
		cout << "吃饭" << endl;
	}

	void sleep() {
		cout << "睡觉" << endl;
	}
};

类的创建

两种方式

// 在栈里面开辟
Person person1 = Person();
// 在堆上开辟
Person* person = new Person();
image-20240223152401423
image-20240223152401423

类里面有属性,类的占用空间的大小是所有属性占用的空间和

类里面没有属性,类的占用的空间大小是1

成员访问

没有使用new

cout << person1.age << endl;
cout << person1.name << endl;
cout << person1.gender << endl;
cout << person1.score << endl;
person1.eat();
person1.sleep();

使用new

person->age = 90;
person->name = "test2";
person->gender = 2;
person->score = 100;
cout << person->age << endl;
cout << person->name << endl;
cout << person->gender << endl;
cout << person->score << endl;
person->eat();
person->sleep();
image-20240223154255951
image-20240223154255951

因为是一个指针,所有我们也可以通过指针的方式来访问,比如(*person).age

引入另一个类

class Dog {
public:
	string name;
	int age;
};

class Person
{
public:
	string name;
	int age;
	Dog dog1;

	Dog* dog2;
};

两个引用Dog的区别在于

Dog dog1 = Dog()

Dog* dog2 = NULL

所以我们进行访问对应的属性的时候,如果这个属性类是使用指针来创建的话,因为这个指针一开始是空的,所以直接访问里面的元素,会报空指针异常,需要注意。

	Person p1 = Person();
	p1.age = 100;
	p1.name = "test";
	p1.dog1.age = 2;
	p1.dog1.name = "test dog";
	cout << p1.age << endl;
	cout << p1.name << endl;
	cout << p1.dog1.age << endl;
	cout << p1.dog1.name << endl;


	p1.dog2 = new Dog();

案例一:老师-学生

xx老师让xx学生上台自我介绍

老师类:

  • 属性:姓名
  • 功能:让学生自我介绍

学生类:

  • 属性 姓名,年龄,性别,成绩
  • 功能:自我介绍
#include<iostream>
using namespace std;


class Student {
public:
	string name;
	int age;
	int sex;
	double score;

	void show() {
		cout << name<<": 开始自我介绍" <<" 年龄:"<<age<<"  性别:"<<sex<<" 分数:"<<score << endl;
	}
};

class Teacher {
public:
	string name;
	
	void call(Student stu) {
		cout << "让学生:"<<stu.name<<"自我介绍" << endl;
		stu.show();
	}
};

int main() {
	// 学生初始化
	Student stu1 = Student();
	stu1.name = "张三";
	stu1.age = 18;
	stu1.sex = 1;
	stu1.score = 80;

	// 老师初始化
	Teacher te = Teacher();
	te.name = "李四";

	// 核心业务
	te.call(stu1);

}

案例二:判断圆是否包含一个点

圆:

  • 属性:圆心,半径
  • 功能:判断圆是否包含一个点

点:

  • 属性:x,y
#include<iostream>
using namespace std;

class Point {
public:
	double x;
	double y;
};


class Circle {
public:
	double r;
	Point center;

	bool contains(Point point) {
		// 计算两点距离的平方
		double dis = (center.x - point.x) * (center.x - point.x) + (center.y - point.y) * (center.y - point.y);
		// 计算平方和和半径的关系
		return dis >= r * r;
	}
};

int main() {
	// 
	Point point = Point();
	point.x = 2;
	point.y = 4;

	// 
	Circle circle = Circle();
	circle.r=1;
	circle.center.x = 2;
	circle.center.y = 4;

	cout << (circle.contains(point) == 1 ? "true" : "false") << endl;;
}

类外和其他文件实现类函数

将函数移到外面创建

class Person {
public:
	void sleep();
};

void Person::sleep(){
	cout << "sleep" << endl;
}

可以在新建里面选择对应的类,会创建对应的头文件和cpp文件实现

image-20240223163110354
image-20240223163110354

静态

使用关键字static

静态常量

const static int

静态函数

static void show

静态属性

静态的属性存放在全局区,程序编译的时候已经完成了空间的开辟与初始化的赋值操作。

静态属性的空间开辟早于对象的创建,静态属性不属于对象,被所有的对象共享。

访问方式

  1. 通过对象访问
  2. 通过类访问(推荐)
#include<iostream>
using namespace std;

class MyNumber {
public:
	// 静态成员
	static int a;
	// 静态常量,数据类型是整型,允许在定义的时候进行初始化赋值,其他类型不允许
	const static int PI = 3;

	const static double PI2;

	// 静态函数
	static void show() {
		cout << "静态函数的调用" << endl;
	}
};

int MyNumber::a = 10;
const double MyNumber::PI2 = 3.1;


int main() {
	// 对象进行访问
	MyNumber num1;
	MyNumber num2;

	cout << num1.a << endl;
	cout << num2.a << endl;

	// 修改了一个值会修改所有的值
	num1.a = 100;
	cout << num1.a << endl;
	cout << num2.a << endl;



	// 类访问
	MyNumber::a = 200;
	cout << num1.a << endl;
	cout << num2.a << endl;


	// 调用函数
	MyNumber::show();
}

构造函数

构造函数分类

  • 参数:有参和无参构造
  • 类型:普通构造,拷贝构造

构造函数的定义

对对象属性初始化的赋值操作

class Per {
public:
	Per() {
		cout << "无参构造" << endl;
	}

	Per(int age) {
		cout << "有参构造" << endl;
	}

};

构造函数的调用

	// 1.显示调用
	Per per1 = Per();               // Per per1; 缩写不能加上()
	Per per2 = Per(10);				// Per per2(10)
	Per per3 = Per(10, 10);			// Per per3(10,10)

	//2. 隐式调用
	Per per4 = {};
	Per per5 = { 10 };
	Per per6 = { 10,10 };

explict关键字

放在构造函数前面修饰构造函数,无法使用隐式调用

	explicit Per(int age) {
		cout << "{int}有参构造" << endl;
	}

注意事项

如果我们创建了一个空的对象。系统会默认帮我们创建一个空的构造函数。但是如果我们重写了新的构造函数,那么原本的空参就不会存在了,此时如果还是空参调用,就会报错。

所以一般情况下,我们会主动再写一个空参构造。

构造函数初始化

#include<iostream>
using namespace std;


class Pers {
public:
	string name;
	int age;
	string sex;
	int score;

	Pers() {
		name = "";
		age = 0;
		sex = "";
		score = 0;
	}

	//Pers(string n,int a,string g,int s) {
	//	name = n;
	//	age = a;
	//	sex = g;
	//	score = s;
	//}

	Pers(string n, int a, string g, int s) :name(n), age(a), sex(g), score(s) {
	}

	void show() {
		cout << "name=" << name << " age=" << age << " sex=" << sex << " score=" << score << endl;
	}
};

int main() {
	Pers per = Pers("test",12,"nan",88);
	per.show();
	return 0;
}

拷贝构造函数

根据一个对象拷贝出另一个对象,这两个对象的值相等,但是地址不相同

可以看作一个常量引用,如果不创建,就会使用默认的拷贝构造函数

	Pers(const Pers& per) {
		cout << "拷贝构造函数调用了" << endl;
		name = per.name;
		age = per.age;
		sex = per.sex;
		score = per.score;
	}
Pers per = Pers("test",12,"nan",88);
Pers per2 = per;

析构函数

对象生命周期的终点,对象被销毁之前调用

资源释放,堆内存释放

使用~

#include<iostream>
using namespace std;

class Perso {
public:
	int a;
	~Perso() {
		cout << "析构函数被调用了" << endl;
	}
};


void test() {
	Perso person;  // 栈里面,test调用结束后才会释放
}


int main() {
	test();

	//Perso* person = new Perso();
    // 释放堆空间
	//delete person;

	system("pause");
	return 0;
}

因为函数里面的对象是在栈里面,调用方法结束后会自动化销毁

image-20240223180125420
image-20240223180125420

如果使用的是指针创建, Perso* person = new Perso();这个是存放在堆里面,不会调用析构函数。

image-20240223180221186
image-20240223180221186

所以必须要使用delete来删除占用

浅拷贝和深拷贝

浅拷贝:在拷贝函数中,直接完成属性的值拷贝

深拷贝:在拷贝函数中,创建新的空间,属性中的指针指向一个新的空间

浅拷贝

#include<iostream>
using namespace std;


class Cat {
public:
	string name;
	int age;
};


class Person {
public :
	int age;
	Cat* pet;

	Person() {
		age = 0;
		pet = new Cat();
	}

	// 拷贝构造函数
	Person(const Person& p) {
		age = p.age;
		// 默认是浅拷贝
		pet = p.pet;
		// 深拷贝
		//pet = new Cat();
		//pet->name = p.pet->name;
		//pet->age = p.pet->age;
	} 

	// 析构函数
	~Person()
	{	
		cout << "开始调用析构函数" << endl;
		if (pet != nullptr) {
			delete pet;
			pet = nullptr;
		}

	}
};

int main() {
	Person test1;

	Person test2 = test1;

	system("pause");
	return 0;
}
image-20240224160804209
image-20240224160804209

上图我们可以看到两个对象都是使用的同一个cat空间,但是一旦test2执行完成了,会执行析构函数,变成下图

image-20240224161018696
image-20240224161018696

所以会导致第一个对象里面遗留了一个野指针。

深拷贝

pet = new Cat();
pet->name = p.pet->name;
pet->age = p.pet->age;

这样就会创建两个pet在堆里面,析构函数执行的时候就不会出现野指针的情况

image-20240224161305380
image-20240224161305380

this指针

使用

指向当前对象的指针,谁调用这个函数,这个this就指向谁

#include<iostream>
using namespace std;

class Person {
public:
	int age;
	Person() {
		age = 0;
	}
	Person(int age) {
		age = age;
	}
	//Person(int age):age(age){}
};

int main() {
	Person xiaoming(10);
	cout << xiaoming.age << endl;

	Person xiaobai(20);
	cout << xiaobai.age << endl;

	return 0;
}

以上代码运行的时候会存在问题,一个是代码提示,另一个是执行结果的原因

image-20240224162350810
image-20240224162350810

因为这两个变量名重复了,所以无法分辨是哪个变量的赋值,三种解决方案

  • 参数名或者变量名不一样,要有区分
  • 使用this指针this->age = age;
  • 构造函数初始化,Person(int age):age(age){}

this不能省略情况

  1. 局部变量和属性名字相同的时候,需要使用显示的this,一般情况下this可以省略

返回当前对象的函数

在函数上面可以使用引用,这样就能避免在内存上创建两个当前的资源,然后返回的时候可以使用*this指针指向当前对象

#include<iostream>
using namespace std;

class MyNumber {
private:
	int n;

public:
	MyNumber() :n(0) {};
	MyNumber(int n) :n(n) {};


	// 返回当前的对象,可以使用引用,避免值拷贝
	MyNumber& add(int n) {
		this->n += n;
		return *this;
	}


	MyNumber& mins(int n) {
		this->n -= n;
		return *this;
	}
};



int main() {
	MyNumber myNumber = { 10 };
	MyNumber res = myNumber.add(10).add(20).mins(10);
}

而且这样还能使用流式或链式调用。

空指针

可以使用空指针调用成员函数,需要这个函数里面不出现this访问。需要避免空指针,野指针。

#include<iostream>
using namespace std;

class MyNumber {
public:
	int age;

	void func1() {
		cout << this->age << endl;
	}


	void func2() {
		if (this == NULL) {
			cout << "空指针存在" << endl;
            return;
		}
		cout << "不是空指针" << endl;
	}
};

int main() {
	MyNumber* m1 = nullptr;
	m1->func1();
	//m1->func2();
}

运行方法1,出现空指针

image-20240224165236543
image-20240224165236543

运行方法2

image-20240224165347021
image-20240224165347021

常函数

  • 使用const修饰
  • 常函数里面不允许修改属性值
  • 常函数不允许调用普通函数,只能调用其他的常函数
#include<iostream>
using namespace std;

class Person {
public:
	string name;
	int age;
	Person() :name(""), age(0) {};
	Person(string name, int age) :name(name), age(age) {};

	void changePerson(string name, int age) {
		this->name = name;
		this->age = age;
	}
};


int main() {

}

给changePerson加上const,发现无法赋值,报错。

image-20240224171525240
image-20240224171525240

常对象

  • 创建对象的时候使用const修饰对象
  • 可以读取任意属性的值,但是不允许修改
  • 常对象,只能调用常函数,不能调用普通函数
image-20240224171645373
image-20240224171645373

发现无法进行赋值,但是可以读取里面的值,而且能够调用常函数

mutable

修饰属性,表示可变

  • 被修饰的属性可以在常函数中修改,也可在常对象里面修改
class Person {
public:
	string name;
	mutable int age;
	Person() :name(""), age(0) {};
	Person(string name, int age) :name(name), age(age) {};

	void changePerson(string name, int age) const{
		this->age = age;
	}
};


int main() {

	const Person p = { "test",12 };
	// p.age = 20;
	p.age = 20;
	p.changePerson("test2", 22);
}

友元

友元是什么?

类的主要特点是可以隐藏自己内部的数据,外界无法访问,有的时候需要在外部访问类的私有成员,需要友元函数,也就是特权函数

全局函数做友元

定义一个房子,有一个私有属性和公有属性

class MyRoom {
public:
	string livingRoom = "客厅";
private:
	string bedRoom = "卧室";
};

使用函数来访问这个私有属性,很显然是无法访问的,

image-20240224172739679
image-20240224172739679

我们可以将当前函数设置为友元函数,然后可以正常访问

image-20240224172904012
image-20240224172904012

成员函数做友元

实现较为复杂。

#include<iostream>
using namespace std;

// 1.必须要先声明
class MyRoom;

// 2.必须要在MyRoom上面
class GoodFriend {
public:
	MyRoom* myRoom;

	//3. 不能在此处实现
	void visit();
};


class MyRoom {
	// 4.成员函数做友元
	friend void GoodFriend::visit();
public:
	string livingRoom = "客厅";
private:
	string bedRoom = "卧室";
};

//5.实现成员函数
void GoodFriend::visit() {
	cout << myRoom->bedRoom << endl;
	cout << myRoom->livingRoom << endl;
}

int main() {
	GoodFriend gd;
	MyRoom mm ;
	gd.myRoom = &mm;
	gd.visit();
}

类做友元

类里面的所有成员函数都能访问私有属性

class Room {
	friend class Friend;
public:
	string liveingRoom = "客厅";
private:
	string bedRoom = "卧室";
};

class Friend {
	Room* room;
	void visit() {
		room->liveingRoom;
		room->bedRoom;
	}
};

重载运算符

对已有的运算符进行重新定义,适应不同的数据结构

可重载运算符

image-20240224175318219
image-20240224175318219

+运算重载

必须要使用operator+对应的重载运算符号

class Point {
public:
	int x;
	int y;

	Point() :x(0), y(0) {};
	Point(int x, int y) : x(x), y(y) {};

};


Point operator+(Point p1,Point p2) {
	return Point(p1.x + p2.x, p1.y + p2.y);
}

int main() {
	Point p1 = { 1,2 };
	Point p2 = { 2,2 };

	Point p3 = p1 + p2;

	cout << "x=" << p3.x << " y=" << p3.y << endl;
}

但是以上方式比较耗费空间,因为每次都是值复制,所有我们可以使用引用

Point operator+(const Point& p1,const Point& p2) {
	return Point(p1.x + p2.x, p1.y + p2.y);
}

可以在类里面定义,但是只能有一个函数

class Point {
public:
	int x;
	int y;

	Point() :x(0), y(0) {};
	Point(int x, int y) : x(x), y(y) {};

	// 里面只能有一个参数
	Point operator+(const Point& p1) {
		return { this->x - p1.x,this->y - p1.y };
	}

};

++运算重载

先运算,在取值

Point operator++( Point& p) {
	p.x++;
	p.y++;
	return p;
}

先取值,后运算

Point operator++(Point& p,int) {
	Point point = p;
	p.x++;
	p.y++;
	return point;
}

--运算重载

类里面

先运算后取值

	Point operator--() {
		x--;
		y--;
		return *this;
	}

先取值后运算

	Point operator--(int) {
		Point temp = *this;
		x--;
		y--;
		return temp;
	}

<<重载运算符

#include<iostream>
using namespace std;

class Person {
private:
	string name;
	int age;
	string gender;
	int score;
public:
	Person() :name(""), age(0), gender(""), score(0) {};
	Person(string name, int age, string gender, int score) :name(name), age(age), gender(""), score(score) {};
};


int main() {
	Person person = { "test",1,"1",100 };
	cout << person << endl;
}

但是以上写法肯定是不行的

image-20240225114137007
image-20240225114137007

所以我们需要重载流式运算符

#include<iostream>
using namespace std;

class Person {
	// 友元
	friend ostream& operator<<(ostream& os, const Person& p);
private:
	string name;
	int age;
	string gender;
	int score;
public:
	Person() :name(""), age(0), gender(""), score(0) {};
	Person(string name, int age, string gender, int score) :name(name), age(age), gender(""), score(score) {};
};

// 重载运算符<<
ostream& operator<<(ostream& os,const Person& p) {
	os << "name=" << p.name << "age=" << p.age << "gender=" << p.gender << "score=" << p.score << endl;
	return os;
}

int main() {
	Person person = { "test",1,"1",100 };
	cout << person << endl;
}

<<重载需要使用ostream

==运算符

#include<iostream>
using namespace std;

class Person {
	friend ostream& operator<<(ostream& os, const Person& p);
private:
	string name;
	int age;
	string sex;
	int* score;
public:
	Person() :name(""), age(0), sex(""), score(nullptr) {};
	Person(string name, int age, string sex, int* score) :name(name), age(age), sex(sex), score(score) {};

	Person(const Person& person) {
		this->name = person.name;
		this->age = person.age;
		this->score = new int(*person.score);
		this->sex = person.sex;
	}


	// 重载运算符=
	Person& operator=(const Person& person) {
		name = person.name;
		age = person.age;
		sex = person.sex;
		score = new int(*person.score);

		return *this;
	}

	~Person() {
		if (score != nullptr) {
			delete score;
			score = nullptr;
		}
	}

};


ostream& operator<<(ostream& os, const Person& p) {
	os << "name=" << p.name << "age=" << p.age << "sex=" << p.sex << "score=" << *p.score << endl;
	return os;
}

int main() {
	Person p1 = { "test",10,"1",new int(10)};
	cout << p1 << endl;

	// 此时p2并没有完成空间的开辟,实例化,只是进行了拷贝构造函数
	Person p2 = p1;
	// p2此时已经完成了空间开辟,赋值运算符
	p2 = p1;
	cout << p2 << endl;
}

封装

面向对象三大特征,封装,继承,多态

  • 将具体属性封装实现,把对象成员变量的访问进行私有化,只能在类里面访问,但是可以通过公共方法间接调用
  • 提高了代码的安全性,复用性,可读性
#include<iostream>
using namespace std;

class Person {
private:
	string name;
	int age;

public:
	// 封装set,get属性
	void setAge(const int& age) {
		if (age > 140 || age < 0) {
			cout << "年龄不合法" << endl;
			return;
		}
		this->age = age;
	}

	int getAge() {
		return this->age;
	}

	void setName(const string& name) {
		this->name = name;
	}

	string getName() {
		return this->name;
	}

	// 构造函数
	Person() :name(""), age(0) {};
	Person(string name, int age) :name(name), age(age) {};
	// 拷贝函数
	Person(const Person& p) {
		age = p.age;
		name = p.name;
	}
};

int main() {
	Person p;
    // 属性赋值
	p.setAge(1000);
	p.setName("张三");
	cout << p.getAge() << endl;
	cout << p.getName() << endl;
}

继承

继承的使用

通过子类 :[继承方式]父类

  • 所有的成员都可以继承给子类,私有的成员无法继承
  • 子类可以访问从父类继承到的成员
  • 一个类继承了其他类之后,也可以被其他类继承
  • 简化代码、提高代码的复用性。
#include<iostream>
using namespace std;

class Animal {
private:
	int age;

public:
	string name;

	void walk() {
		cout << this->name << "走路" << endl;
	}
};

// 子类类名:[继承方式]父类类名
class Dog :public Animal {

};

int main() {
	Dog dog;
	dog.walk();
}
image-20240225152053372
image-20240225152053372

继承的特点

  1. 父类中的所有非静态成员,都可以继承给子类(除了构造函数,析构函数)
  2. 一个类可以被多个类继承
  3. 一个类可以有多个父类(多继承)
  4. 一个类在继承了一个类后,其他类也能继承这个类
  5. 私有的属性子类里面也有,但是子类由于权限的问题,无法访问

继承的权限

  • public:所有的类都能访问
  • protected: 继承的子类和当前类能访问
  • private:只能在当前类访问

类的三种继承方式

  • 公共继承:继承父类属性(函数),保留原有的访问权限
  • 保护继承:继承父类属性(函数),超过proteced权限部分,降为protected权限
  • 私有继承:继承父类属性(函数),将访问权限都设置为private权限

C++默认采用的私有继承

子类构造函数和析构函数

  • 当子类被创建的时候,会调用父类的构造函数,来初始化父类继承的部分。默认调用的是父类的无参构造
  • 父类里面没有无参构造,子类里面也不能使用无参构造
    • 解决方式
        1. 父类添加无参构造,修改访问权限
        2. 直接显示调用父类有参构造函数
image-20240225161434041
image-20240225161434041

有参调用父类构造函数

class Dog : public Animal {
public:
	Dog() :Animal(10){
		cout << "子类构造函数启动" << endl;
	}

};

析构函数

image-20240225162107427
image-20240225162107427

子类销毁的时候会先调用自己的析构函数,然后再调用父类的析构函数

父类和子类出现同名成员

子类会将父类继承到的成员隐藏,子类对象直接访问,访问的是子类里面的成员。

但是如果想要调用父类里面的成员,需要通过**子类.父类:成员(dog.Animal::showAge())**访问

多继承

使用方式,和单继承一样,但是需要在后面使用,隔开,并且需要使用对应的访问权限标识,否则后面的父类都是私有的

class SuperClass1 {
public:
	void disply1() {
		cout << "superclass1" << endl;
	}
};

class SuperClass2 {
public:
	void disply2() {
		cout << "superclass2" << endl;
	}
};

class Child : public SuperClass1, public SuperClass2 {

};
image-20240225163629537
image-20240225163629537

可能会带来二义性问题

如果多个父类中存在多个相同名字的成员后,子类无法分辨究竟是调用的哪一个,必须要使用显示指明父类

child.SuperClass1::display()
child.SuperClass2::display()

菱形继承

两个派生类继承到相同的父类,但是他们有相同的子类。

image-20240225164419560
image-20240225164419560

存在的问题是二义性,

  1. 我们可以要使用显示调用
  2. 虚继承(virtual):为了解决菱形继承的时候命名问题,让派生类中只保留一个相同的间接父类中的成员
image-20240225164733989
image-20240225164733989

多态

一个类的引用指向另一个类的对象,从而产生多种形态,当两者存在直接或间接的继承关系时候,父类引用子类的对象。

编译时多态(静态多态):运算重载,函数重载,函数地址是早绑定(在编译阶段就可以确定函数的调用地址)

运行时多态(动态多态):派生类和虚函数,函数地址是晚绑定,函数的调用地址不能在编译期间确定

对象转型

多态的前提:

  1. 父类的引用指向子类的对象,父类的指针指向子类的对象

  2. 对象转换为父类的引用/指针,只能访问父类中存在的成员无法访问子类里面定义的成员

#include<iostream>
using namespace std;

class Animal {
public:
	void bark() {
		cout << "animal bark" << endl;
	}
};


class Dog :public Animal {

};

int main() {
	// 对象转型
    // 方式1
	Dog dog;
	Animal& animal = dog;
	
    // 方式2
	// 在堆上创建,父类的指针指向子类
	Dog* dog1 = new Dog();
	Animal* animal = dog1;

}

很显然父类无法调用子类里面的成员

image-20240225172906288
image-20240225172906288

除此之外,如果父类函数和子类函数一样,那么多态对象转型的时候,依然是调用的父类函数

image-20240225173141066
image-20240225173141066

虚函数

当绑定在程序运行之前,称为早绑定

image-20240227094745173
image-20240227094745173
class Animal {
public:
	// 虚函数,延迟绑定
	virtual void bark() {
		cout << "animal bark" << endl;
	}
};


class Dog :public Animal {
public:
	int age;
	Dog() :age(0) {};

	// 虚函数的重写
	void bark() override {
		cout << "dog bark" << endl;
	}
};

多态案例

#include<iostream>
using namespace std;

class SF {
public:
	void sendPackage() {
		cout << "SF发送" << endl;
	}
};


class EMS {
public:
	void sendPackage() {
		cout << "EMS发送" << endl;
	}
};


class JD {
public:
	void sendPackage() {
		cout << "JD发送" << endl;
	}
};
// 违背了程序设计的原则:开闭原则
// 有新功能来的时候只需要拓展模块实现,不能修改已有的代码
void send(string name) {
	if (name == "SF") {
		SF().sendPackage();
	}
	else if (name == "EMS") {
		EMS().sendPackage();
	}
	else {
		JD().sendPackage();
	}
}
int main() {
	send("SF");

	return 0;
}

修改,使用多态

#include<iostream>
using namespace std;

// 父类
class ExpressCompany {
public:
	virtual void sendPackage() {};
};

class SF :public ExpressCompany{
public:
	void sendPackage() override {
		cout << "SF发送" << endl;
	}
};


class EMS :public ExpressCompany{
public:
	void sendPackage() override {
		cout << "EMS发送" << endl;
	}
};


class JD :public ExpressCompany{
public:
	void sendPackage() override {
		cout << "JD发送" << endl;
	}
};
#if 0
void send(string name) {
	if (name == "SF") {
		SF().sendPackage();
	}
	else if (name == "EMS") {
		EMS().sendPackage();
	}
	else {
		JD().sendPackage();
	}
}
#else
void send(ExpressCompany& ex) {
	ex.sendPackage();
}

#endif


int main() {
	EMS ems;
	JD jd;
	send(ems);
	send(jd);
	return 0;
}

纯虚函数与抽象类

希望基类只作为其派生类的一个接口

image-20240227102724773
image-20240227102724773
#include<iostream>
using namespace std;

// 纯虚函数:一个虚函数的实现部分设置为了0,那么这样的话就是纯虚函数,纯虚函数只有声明,没有实现
// 抽象类:一个类里面包含了纯虚函数,这个类就是抽象类,抽象类不能创建对象
class TrafficTools {
public:
	// 纯虚函数
	virtual void transport() = 0;
};

// 继承抽象类,必须要重写父类里面的纯虚函数,否则这个类也是抽象类,无法创建对象
class Bus :public TrafficTools {
public:
	void transport() override {
		cout << "bus" << endl;
	}
};

class Bike :public TrafficTools {
public:
	void transport() override {
		cout << "bike" << endl;
	}
};


int main() {
	Bus bus;
	bus.transport();

	Bike bike;
	bike.transport();
	return 0;
}

纯虚函数和多继承

image-20240227103706421
image-20240227103706421
#include<iostream>
using namespace std;


class Cooker {
public:
	virtual void buyFood() = 0;
	virtual void cook() = 0;
	virtual void eat() = 0;
};


class Maid {
public:
	virtual void cook() = 0;
	virtual void wash() = 0;
	virtual void clean() = 0;
};

class Person : public Cooker, public Maid {
public:
	void buyFood() override {
		cout << "buyfood" << endl;
	}

	void wash() override {
		cout << "wash" << endl;
	}

	void cook() override {
		cout << "cook" << endl;
	}

	void clean() override {
		cout << "clean" << endl;
	}

	void eat() override {
		cout << "eat" << endl;
	}
};
int main() {
	Person p;
	p.buyFood();
	p.clean();

	return 0;
}

多态案例

image-20240227104843614
image-20240227104843614
#include<iostream>
using namespace std;

//空运抽象类
class AirTransport {
public:
	// 空运
	virtual void sendAirTransport() = 0;

};

class LandTransportation {
public:
	// 陆运
	virtual void sendLandTransportation() = 0;
};

// 顺丰有陆运和空运
class SF: public AirTransport, public LandTransportation {
public:
	void sendAirTransport() override {
		cout << "顺丰空运" << endl;
	}
	void sendLandTransportation() override {
		cout << "顺丰陆运" << endl;
	}

};

// EMS只有空运
class EMS : public AirTransport {
public:
	void sendAirTransport() override {
		cout << "EMS空运" << endl;
	}
};

// 圆通只有陆运
class YT : public LandTransportation {
public:
	void sendLandTransportation() override {
		cout << "圆通陆运" << endl;
	}
};


void sendAir(AirTransport& air) {
	air.sendAirTransport();
}

void sendLand(LandTransportation& land) {
	land.sendLandTransportation();
}

int main() {
	SF sf;
	EMS ems;
	YT yt;
	sendAir(sf);
	sendAir(ems);
	sendLand(yt);
	sendLand(sf);
}

虚析构函数

析构函数是对象生命周期的终点,在对象被销毁前调用,一般是对资源进行释放。

但是在多态中,如果我们使用父类的引用来销毁空间的话,可能出现子类中引用的堆空间无法销毁的情况,造成内存泄露,解决方案就是给父类添加虚析构函数。

#include<iostream>
using namespace std;

class Animal {
public:
	~Animal(){
		cout << "父类的析构函数执行了" << endl;
	}
};

class Person :public Animal{
public:
	int* n;
	Person() {
		n = new int(10);
	};
	~Person(void) {
		cout << "子类析构函数执行" << endl;
		if (n != nullptr) {
			delete n;
			n = nullptr;
		}
	}
};

int main() {
	Animal* ani = new Person();
	delete ani;
}
image-20240227134611465
image-20240227134611465

可以看见,没有执行子类的析构函数。

只需要我们使用析构函数改成虚析构函数,子类重写

#include<iostream>
using namespace std;

class Animal {
public:
	virtual ~Animal(){
		cout << "父类的析构函数执行了" << endl;
	}
};

class Person :public Animal{
public:
	int* n;
	Person() {
		n = new int(10);
	};
	~Person() override{
		cout << "子类析构函数执行" << endl;
		if (n != nullptr) {
			delete n;
			n = nullptr;
		}
	}
};

int main() {
	Animal* ani = new Person();
	delete ani;
	// 如果没有虚析构函数的话,这里通过ani来销毁空间,销毁开辟出来的堆上面的Person对象空间
	// 但是由于没有执行子类中的析构函数,导致子类成员n所对应的堆没有被释放,导致内存泄露
	return 0;
}
image-20240227135650095
image-20240227135650095

结构体

struct

  • 定义属性
  • 定义构造函数
image-20240227135846491
image-20240227135846491

和类基本上一样

#include<iostream>
using namespace std;

struct Student {
	string name;
	int age;
	Student() {
		name = "";
		age = 0;
	}

	Student(string name, int age) :name(name), age(age) {};

	void study() {
		cout << "开始学习" << endl;
	}
	~Student() {
		cout << "析构函数执行了" << endl;
	}
};

int main() {
	// 创建结构体
	 struct Student student;
	 struct Student xiaohei = { "eee",12 };
	 struct Student xiaohong = Student("小红", 22);
	 struct Student* xiaobai = new Student();
	 struct Student* xiaogui = new Student("xiaogui", 22);
	 xiaohei.study();
}

成员默认的访问权限不同的

类的默认成员类型是private类型。结构体默认是public

模板

模板的介绍

建立通用的函数,函数类型和形参类型不具体指定,用虚拟的类型来代表

image-20240227141302089
image-20240227141302089

函数模板的定义

template<typename T,typename M>
void add(T n1, M n2) {
	cout << n1 + n2 << endl;
}
template<typename T>
void mySwap(T& n1, T& n2) {
	T temp = n1;
	n1 = n2;
	n2 = temp;
}

函数模板的使用

	// 显示调用
	add<int, double>(10, 22);
	add<double, int>(22, 10);
	add<int>(20,10); // 自动推导

	// 根据实参进行类型的自动推导
	add(10, 22);
	add('0', '2');

	// 但是需要注意 推导类型的时候,需要注意一致性
	int x = 10;
	int y = 20;
	mySwap(x, y);

可以指定默认的类型

template<typename T = int,typename M = int>
void add(T n1, M n2) {
	cout << n1 + n2 << endl;
}

返回值使用虚拟类型

一般将返回虚拟类型放在前面,一定需要指明返回类型

template<typename R,typename T1,typename T2>
R calculate(T1 x, T2 y) {
	return (R)(x + y);
}
	cout<<calculate<int>(10, 20)<<endl;

普通类型和虚拟类型

#include<iostream>
using namespace std;

// 虚拟函数
template<typename T>
int add(T n1, T n2) {
	cout << "虚拟函数执行了" << endl;
	return n1 + n2;
}

// 普通函数执行了
int add(int n1, int n2) {
	cout << "普通函数执行了" << endl;
	return n1 + n2;
}


int main() {
	int x = 0;
	char ch = 'a';
	add(0, ch);
    
    
	// 普通函数
	int a = 1;
	int b = 2;
	add(a, b);
}
image-20240227150329108
image-20240227150329108

我们可以看到执行的是普通函数,因为普通函数有类型转换,模板函数无法判断类型

  1. 普通函数有类型转换
  2. 如果调用的时候,实参可以匹配普通函数,又可以匹配函数模板,有限调用普通函数

函数模板局限性

如果使用的是类的话,会出现问题

#include<iostream>
using namespace std;


class Person {
public:
	string name;
	int age;
	int score;
	Person() :name(""), age(0), score(88) {};
	Person(string name, int age, int score) :name(name), age(age), score(score) {};

};


template<typename T>
int compare(T& t1, T& t2) {
	if (t1 > t2) {
		return 1;
	}
	else if (t1 < t2) {
		return -1;
	}
	else {
		return 0;
	}
}


int main() {
	Person p1;
	Person p2;
	compare(p1, p2);
}

以上代码看不出来什么问题,但是执行后会报错

image-20240227151724236
image-20240227151724236

两种方式

  • 重载运算符

// 重载运算符
bool operator> (const Person& p1, const Person& p2) {
	return p1.age > p2.age;
}

bool operator< (const Person& p1, const Person& p2) {
	return p1.age < p2.age;
}
  • 函数模板重载

需要使用<>来标明特定的类别

template<>
int compare<Person>( Person& p1,  Person& p2) {
	if (p1.age > p2.age) {
		return -1;
	}
	else if (p1.age < p2.age) {
		return 1;
	}
	else {
		return 0;
	}
}

函数模板的案例

定义一个函数模板,将数组中的元素拼接成字符串返回。

使用sstream类库作为输出

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


template<typename T>
string toString(T * array,int len) {
	if (len == 0 || array == nullptr) {
		return "[]";
	}

	ostringstream oss;
	oss << "[";
	for (int i = 0; i < len-1; i++) {
		oss << array[i] << ",";
	}

	oss << array[len - 1] << "]";

	return oss.str();
}


int main() {
	int arr[] = { 1,2,3,4 };
	cout<<toString(arr, 4)<<endl;

}

数组排序,函数模板

template<typename T>
void mySort(T* array, int len) {
	for (int i = 0; i < len; i++) {
		for (int j = 0; j < len - i - 1; j++) {
			if (array[j] > array[j + 1]) {
				T temp = array[j];
				array[j] = array[j + 1];
				array[ j + 1 ] = temp;
			}
		}
	}
}

类模板

和函数模板类似,但是区别在于类模板无法使用类型推断

类模板的定义和使用

#include<iostream>
using namespace std;


template<typename T1, typename T2>
class NumOperator {
private:
	T1 num1;
	T2 num2;
public:
	NumOperator() :num1(0), num2(0) {};
	NumOperator(T1 num1, T2 num2) :num1(num1), num2(num2) {};

	NumOperator(const NumOperator& numOperator) {
		cout << "拷贝构造函数" << endl;
		num1 = numOperator.num1;
		num2 = numOperator.num2;
	}

	~NumOperator() {
		cout << "析构函数" << endl;
	}

	void showAdd() {
		cout << num1 + num2 << endl;
	}

	void showMinus() {
		cout << num1 - num2 << endl;
	}

};


int main() {
	// 必须要声明当前类里面元素的类型
	NumOperator<int, int> numoperator = { 10,20 };
	numoperator.showAdd();
	numoperator.showMinus();
}

类模板作为函数的载体

// 普通函数使用类模板必须要明确类型
void useCalculator(NumOperator<int, int>& numOperator) {
	numOperator.showAdd();
}

// 函数模板使用类模板
template<typename X,typename Y>
void userCalculator2(NumOperator<X, Y>& numoperator) {
	numoperator.showAdd();
}

类模板的继承

#include<iostream>
using namespace std;

template<typename T>
class Animal {
public:
	T arg;
};

// 普通类继承
class Dog : public Animal<int> {

};


// 模板类继承
template<typename E>
class Cat : public Animal<E> {

};

int main() {
	// 普通类
	Dog dog;
	dog.arg = 10;

	// 模板类
	Cat<string> cat;
	cat.arg = "string";
}

类模板创建成员变量的时机

类模板成员函数,是在调用函数的时候才会创建,因为编译的时候只知道有这个对象,但是不知道这个对象的具体类型,在调用函数的时候,才会创建这个函数

类模板的函数类外实现

#include<iostream>
using namespace std;

template<typename T1,typename T2>
class NumberCalculator {
private:
	T1 n1;
	T2 n2;

public:
	NumberCalculator();
	NumberCalculator(T1 n1, T2 n2);
	void add();
};


template<typename T1, typename T2>
NumberCalculator<T1, T2>::NumberCalculator() {
	cout << "类外实现无参构造" << endl;
};

template<typename T1, typename T2>
NumberCalculator<T1, T2>::NumberCalculator(T1 n1, T2 n2) {
	cout << "类外实现有参构造" << endl;
	this->n1 = n1;
	this->n2 = n2;
};

template<typename T1, typename T2>
void NumberCalculator<T1, T2>::add() {
	cout << this->n1 + this->n2 << endl;

};


int main(){
	NumberCalculator<int, int> numcal = { 10,20 };
	numcal.add();
}

类模板的头文件和原文件分离

虽然引入对应的头文件,但是模板类中的函数是在调用的时候才创建的,因此编译的时候,编译器看不到这些函数,不会管理.cpp里面的对应实现。使用这个函数的时候,发现这个函数已经创建了,但是没有实现,因此报错,相当于头文件里面定义了函数,没有实现。

方式1: 引入.cpp文件,而不是.h头文件

方式2: 类的声明和实现放在同一个文件中,一般定义为 .hpp

类模板的友元函数

第一种方式,类内定义友元

#include<iostream>
using namespace std;

template<typename T1,typename T2>
class NumCalculator {
	// 全局友元函数的类内定义实现
	friend void print(const NumCalculator<T1,T2>& cal) {
		cout << "n1=" << cal.n1 << " n2=" << cal.n2 << endl;
	}
private:
	T1 n1;
	T2 n2;

public:
	NumCalculator(T1 n1, T2 n2);
};


template<typename T1, typename T2>
NumCalculator<T1, T2>::NumCalculator(T1 n1, T2 n2)
{
	this->n1 = n1;
	this->n2 = n2;
}


int main() {
	NumCalculator<int, int> cal = {10,20};
	print(cal);
}

第二种方式:类外实现

  1. 友元函数需要使用<>
  2. 友元函数的实现放在类前面
  3. 类的声明放在最前面
#include<iostream>
using namespace std;

// 提前定义类
template<typename T1, typename T2>
class NumCalculator;


// 定义在类前面
template<typename T1,typename T2>
void print(const NumCalculator<T1, T2>& cal) {
	cout << "n1=" << cal.n1 << " n2=" << cal.n2 << endl;
}



template<typename T1,typename T2>
class NumCalculator {

	//friend void print(const NumCalculator<T1, T2>& cal); 无法进行关联
	// <>表示一个模板函数
	friend void print<>(const NumCalculator<T1, T2>& cal);

private:
	T1 n1;
	T2 n2;

public:
	NumCalculator(T1 n1, T2 n2);
};


template<typename T1, typename T2>
NumCalculator<T1, T2>::NumCalculator(T1 n1, T2 n2)
{
	this->n1 = n1;
	this->n2 = n2;
}



int main() {
	NumCalculator<int, int> cal = {10,20};
	print(cal);
}

贡献者

  • flycodeuflycodeu

公告板

2025-03-04正式迁移知识库到此项目