本文作者:程序员飞云

本站地址: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);
}