本文作者:程序员飞云
类的设计和对象创建
类的设计
使用关键字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 0void send(string name) { if (name == "SF") { SF().sendPackage(); } else if (name == "EMS") { EMS().sendPackage(); } else { JD().sendPackage(); }}#elsevoid 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);}
评论