面向对象
约 7971 字大约 27 分钟
C++
2025-03-04
本文作者:程序员飞云
类的设计和对象创建
类的设计
使用关键字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();

类里面有属性,类的占用空间的大小是所有属性占用的空间和
类里面没有属性,类的占用的空间大小是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();

因为是一个指针,所有我们也可以通过指针的方式来访问,比如(*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文件实现

静态
使用关键字static
静态常量
const static int
静态函数
static void show
静态属性
静态的属性存放在全局区,程序编译的时候已经完成了空间的开辟与初始化的赋值操作。
静态属性的空间开辟早于对象的创建,静态属性不属于对象,被所有的对象共享。
访问方式
- 通过对象访问
- 通过类访问(推荐)
#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;
}
因为函数里面的对象是在栈里面,调用方法结束后会自动化销毁

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

所以必须要使用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;
}

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

所以会导致第一个对象里面遗留了一个野指针。
深拷贝
pet = new Cat();
pet->name = p.pet->name;
pet->age = p.pet->age;
这样就会创建两个pet在堆里面,析构函数执行的时候就不会出现野指针的情况

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;
}
以上代码运行的时候会存在问题,一个是代码提示,另一个是执行结果的原因

因为这两个变量名重复了,所以无法分辨是哪个变量的赋值,三种解决方案
- 参数名或者变量名不一样,要有区分
- 使用this指针
this->age = age;
- 构造函数初始化,Person(int age):age(age){}
this不能省略情况
- 局部变量和属性名字相同的时候,需要使用显示的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,出现空指针

运行方法2

常函数
- 使用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,发现无法赋值,报错。

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

发现无法进行赋值,但是可以读取里面的值,而且能够调用常函数
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 = "卧室";
};
使用函数来访问这个私有属性,很显然是无法访问的,

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

成员函数做友元
实现较为复杂。
#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;
}
};
重载运算符
对已有的运算符进行重新定义,适应不同的数据结构
可重载运算符

+运算重载
必须要使用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;
}
但是以上写法肯定是不行的

所以我们需要重载流式运算符
#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();
}

继承的特点
- 父类中的所有非静态成员,都可以继承给子类(除了构造函数,析构函数)
- 一个类可以被多个类继承
- 一个类可以有多个父类(多继承)
- 一个类在继承了一个类后,其他类也能继承这个类
- 私有的属性子类里面也有,但是子类由于权限的问题,无法访问
继承的权限
- public:所有的类都能访问
- protected: 继承的子类和当前类能访问
- private:只能在当前类访问
类的三种继承方式
- 公共继承:继承父类属性(函数),保留原有的访问权限
- 保护继承:继承父类属性(函数),超过proteced权限部分,降为protected权限
- 私有继承:继承父类属性(函数),将访问权限都设置为private权限
C++默认采用的私有继承
子类构造函数和析构函数
- 当子类被创建的时候,会调用父类的构造函数,来初始化父类继承的部分。默认调用的是父类的无参构造
- 父类里面没有无参构造,子类里面也不能使用无参构造
- 解决方式
- 父类添加无参构造,修改访问权限
- 直接显示调用父类有参构造函数
- 解决方式

有参调用父类构造函数
class Dog : public Animal {
public:
Dog() :Animal(10){
cout << "子类构造函数启动" << endl;
}
};
析构函数

子类销毁的时候会先调用自己的析构函数,然后再调用父类的析构函数
父类和子类出现同名成员
子类会将父类继承到的成员隐藏,子类对象直接访问,访问的是子类里面的成员。
但是如果想要调用父类里面的成员,需要通过**子类.父类:成员(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 {
};

可能会带来二义性问题
如果多个父类中存在多个相同名字的成员后,子类无法分辨究竟是调用的哪一个,必须要使用显示指明父类
child.SuperClass1::display()
child.SuperClass2::display()
菱形继承
两个派生类继承到相同的父类,但是他们有相同的子类。

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

多态
一个类的引用指向另一个类的对象,从而产生多种形态,当两者存在直接或间接的继承关系时候,父类引用子类的对象。
编译时多态(静态多态):运算重载,函数重载,函数地址是早绑定(在编译阶段就可以确定函数的调用地址)
运行时多态(动态多态):派生类和虚函数,函数地址是晚绑定,函数的调用地址不能在编译期间确定
对象转型
多态的前提:
父类的引用指向子类的对象,父类的指针指向子类的对象
对象转换为父类的引用/指针,只能访问父类中存在的成员无法访问子类里面定义的成员
#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;
}
很显然父类无法调用子类里面的成员

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

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

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;
}
纯虚函数与抽象类
希望基类只作为其派生类的一个接口

#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;
}
纯虚函数和多继承

#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;
}
多态案例

#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;
}

可以看见,没有执行子类的析构函数。
只需要我们使用析构函数改成虚析构函数,子类重写
#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;
}

结构体
struct
- 定义属性
- 定义构造函数

和类基本上一样
#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
模板
模板的介绍
建立通用的函数,函数类型和形参类型不具体指定,用虚拟的类型来代表

函数模板的定义
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);
}

我们可以看到执行的是普通函数,因为普通函数有类型转换,模板函数无法判断类型
- 普通函数有类型转换
- 如果调用的时候,实参可以匹配普通函数,又可以匹配函数模板,有限调用普通函数
函数模板局限性
如果使用的是类的话,会出现问题
#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);
}
以上代码看不出来什么问题,但是执行后会报错

两种方式
- 重载运算符
// 重载运算符
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);
}
第二种方式:类外实现
- 友元函数需要使用<>
- 友元函数的实现放在类前面
- 类的声明放在最前面
#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);
}
贡献者
flycodeu
版权所有
版权归属:flycodeu