1. 继承 inherit
1.1 基本概念
继承是在一个已经存在的类的基础上新建一个类,新建的类拥有已经存在的类的特性。主要体现的是代码复用的思想。
- 已经存在的类被称为“基类(Base Class)”或“父类”
- 新建的类被称为“派生类”或“子类(Sub Class)”
继承是一个核心概念,它允许一个类(子类 / 派生类)继承另一个类(父类 / 基类)的属性和方法。
关键特性:
代码复用:子类自动拥有父类的非私有成员(属性和方法)。
扩展功能:子类可以添加新成员或重写父类的方法(多态)。
#include <iostream>
using namespace std;
class Father
{
private:
string name = "张";
public:
string get_name() const
{
return name;
}
void work()
{
cout << "我是一个厨师" << endl;
}
};
/**
* @brief The Son class 派生类
*/
class Son:public Father
{
public:
// 【注意】基类的private成员可以继承,
// 但是能否访问取决于基类的封装接口
// void set_name(string name)
// {
// this->name = name;
// }
};
int main()
{
Son s;
cout << s.get_name() << endl;
s.work();
// s.set_name("王");
return 0;
}
基类和派生类是相对的,一个类即可能是基类又可能是派生类,取决于对比的类。
1.2 函数隐藏
同名就隐藏,不看参数
#include <iostream>
using namespace std;
class Father
{
private:
string name = "张";
public:
string get_name() const
{
return name;
}
void work()
{
cout << "我是一个厨师" << endl;
}
};
/**
* @brief The Son class 派生类
*/
class Son:public Father
{
public:
void work()
{
cout << "我是一个程序员" << endl;
}
};
int main()
{
Son s;
cout << s.get_name() << endl;
s.work();
// 调用被隐藏的基类函数
s.Father::work();
return 0;
}
通常派生类和基类都有做出一些差异化,其中函数隐藏就是一种修改基类函数的方式,另外也可以通过增加内容等方法做出差异化。
1.3 构造函数
1.3.1 继承中构造函数的限制
C++中规定,派生类无法继承基类的构造函数,派生类的任意一个构造函数都必须直接或间接调用基类的任意一个构造函数。
默认情况下,编译器会为每个类增加一个无参构造函数,同时会在派生类的无参构造函数中调用基类的无参构造函数(因此1.1和1.2代码可以正常运行)。
- 创建子类对象时,先执行父类的构造函数,再执行子类的构造函数。
- 若父类没有默认构造函数(即无参构造函数),子类必须显式调用父类的带参构造函数。
调用方式:
通过初始化列表调用父类构造函数。
构造函数调用规则
- 父类构造函数类型 子类构造函数要求
- 有默认构造函数 可隐式调用(无需显式指定)
- 只有带参构造函数 必须在初始化列表中显式调用
- 私有构造函数 子类无法继承,需通过父类的 public/protected 静态方法创建对象
#include <iostream>
using namespace std;
class Father
{
private:
string name;
public:
// 1. Father没有无参构造函数
Father(string name):name(name){}
string get_name() const
{
return name;
}
};
class Son:public Father
{
public:
// Son() // 2. 尝试调用Father(),没有,报错
// {
// }
};
int main()
{
// Son s; 错误
// Son s("张"); 错误
cout << s.get_name() << endl;
return 0;
}
如果使用之前的知识解决上面的问题,可以给基类增加无参构造函数或给基类的有参构造函数增加参数默认值。
上面的代码可以修改为:
#include <iostream>
using namespace std;
class Father
{
private:
string name;
public:
Father(string name="张"):name(name){} // 2. 解法2
// Father():name("张"){} // 1. 解法1
string get_name() const
{
return name;
}
};
class Son:public Father{};
int main()
{
Son s;
// Son s("张"); 错误
cout << s.get_name() << endl;
return 0;
}
1.3.2 透传构造(最优选择)
如果编译器默认调用不到基类的构造函数,则可以手动在派生类的构造函数中调用基类的构造函数。
#include <iostream>
using namespace std;
class Father
{
private:
string name;
public:
Father(string name):name(name){}
string get_name() const
{
return name;
}
};
class Son:public Father
{
public:
// 编译器自动添加的代码
// Son():Father(){}
Son():Father("张") // 透传构造
{
}
Son(string name):Father(name){} // 透传构造
};
int main()
{
Son s;
cout << s.get_name() << endl;
Son s2("王");
cout << s2.get_name() << endl;
return 0;
}
1.3.3 委托构造
某个类的构造函数可以调用这个类的其他构造函数。
#include <iostream>
using namespace std;
class Father
{
private:
string name;
public:
Father(string name)
:name(name){}
string get_name() const
{
return name;
}
};
class Son:public Father
{
public:
Son():Son("张") // 委托构造
{
}
Son(string name)
:Father(name){} // 透传构造
};
int main()
{
Son s;
cout << s.get_name() << endl;
Son s2("王");
cout << s2.get_name() << endl;
return 0;
}
但是委托构造要避免闭环,例如:
1.3.4 继承构造(熟悉)
这是C++11的新特性,继承构造不是构造函数能继承,而是表现出类似于继承的特性,本质上是透传构造。
#include <iostream>
using namespace std;
class Father
{
private:
string name;
public:
Father():Father("张"){}
Father(string name)
:name(name){}
string get_name() const
{
return name;
}
};
class Son:public Father
{
public:
using Father::Father;
// 有了上面的语句,编译器自动添加下面的代码
// Son():Father(){}
// Son(string name):Father(name){}
};
int main()
{
Son s;
cout << s.get_name() << endl;
Son s2("王");
cout << s2.get_name() << endl;
return 0;
}
1.4 对象的创建与销毁流程
#include <iostream>
using namespace std;
/**
* @brief The Value class 作为其他类的变量使用
*/
class Value
{
private:
string name;
public:
Value(string name):name(name)
{
cout << name << "构造函数" << endl;
}
~Value()
{
cout << name << "析构函数" << endl;
}
};
class Father
{
public:
static Value s_value; // 静态成员变量
Value value = Value("Father的成员变量"); // 成员变量
Father()
{
cout << "Father的构造函数" << endl;
}
~Father()
{
cout << "Father的析构函数" << endl;
}
};
Value Father::s_value = Value("Father的静态成员变量");
class Son:public Father
{
public:
static Value s_value; // 静态成员变量
Value value = Value("Son的成员变量"); // 成员变量
Son()
{
cout << "Son的构造函数" << endl;
}
~Son()
{
cout << "Son的析构函数" << endl;
}
};
Value Son::s_value = Value("Son的静态成员变量");
int main()
{
cout << "主函数开始" << endl;
Son* s = new Son;
cout << "正在使用s中:调用s的各种功能" << endl;
delete s;
cout << "主函数结束" << endl;
return 0;
}
寻找规律:
1. 对象创建与销毁流程相反(对称)。
2. 静态成员变量的生命周期是程序的整个运行周期。
3. 同类型的代码或内存开辟都是基类优先。
1.5 多重继承
1.5.1 基本使用
C++支持多继承,即一个派生类可以有多个基类。
多重继承的派生类对于每个基类的关系都可以看做是一个单一继承。
#include <iostream>
using namespace std;
class Sofa
{
public:
void sit()
{
cout << "坐在沙发上" << endl;
}
};
class Bed
{
public:
void lay()
{
cout << "躺在床上" << endl;
}
};
class SofaBed:public Sofa,public Bed
{
};
int main()
{
SofaBed sb;
sb.lay();
sb.sit();
return 0;
}
1.5.2 二义性1:重名成员
在多重继承时,如果多个基类之间拥有相同名称的成员时,通过派生类对象调用这些重名成员时,会出现二义性问题。
解决方法:在调用重名成员前使用 基类名称:: 来显式调用。
#include <iostream>
using namespace std;
class Sofa
{
public:
void sit()
{
cout << "坐在沙发上" << endl;
}
void position()
{
cout << "放在客厅" << endl;
}
};
class Bed
{
public:
void lay()
{
cout << "躺在床上" << endl;
}
void position()
{
cout << "放在卧室" << endl;
}
};
class SofaBed:public Sofa,public Bed
{
};
int main()
{
SofaBed sb;
sb.lay();
sb.sit();
// sb.position(); 错误
sb.Bed::position();
sb.Sofa::position();
return 0;
}
1.5.3 二义性2:菱形继承(别用)
菱形继承也成为钻石继承,指的是一个派生类有多个基类,这多个基类又有一个共同的基类。
解决方法1:在调用重名成员前使用 基类名称:: 来显式调用。
解决方法2:使用虚继承
虚继承底层原理与编译器相关,当Sofa类和Bed类使用virtual继承Furniture类时,Sofa类的对象和Bed类的对象都会多一个隐藏指针,这个指针指向了Furniture类的一个表——虚基类表(Furniture来持有和维护),查询这个表可以找到Furniture的函数调用位置。
真正的虚继承定义是Sofa与SofaBed、Bed与SofaBed的继承关系,此时SofaBed对象就会拥有两个继承来的隐藏虚基类表指针,在SofaBed对象调用Furniture的相关函数时,通过隐藏成员指针查询的调用位置进行比对来避免二义性问题。
#include <iostream>
using namespace std;
class Furniture
{
public:
void func()
{
cout << "我是家具" << endl;
}
};
class Chair:public Furniture // 没有虚继承
{
};
class Sofa:virtual public Furniture
{
};
class Bed:virtual public Furniture
{
};
class SofaBed:public Sofa,public Bed
{
};
int main()
{
SofaBed sb;
// sb.func(); 错误
sb.Bed::func();
sb.Sofa::func();
Sofa s;
Chair c; // c内部没有任何变量,因此1字节占位
cout << sizeof(s) << " " << sizeof(c) << endl; // 4 1
Furniture f; // 虚基类表不占用Furniture对象空间
cout << sizeof(f) << endl; // 1
// sb内部有两个隐藏指针成员
cout << sizeof(sb) << endl; // 8
sb.func();
return 0;
}
1.6 权限
1.6.1 三种权限修饰符
除了private和public以外,还有protected权限,三种权限修饰符修饰成员的访问区别如下:
在类内 |
派生类内 |
全局(例如主函数) |
|
private(默认) |
√ |
X |
X |
protected |
√ |
√ |
X |
public |
√ |
√ |
√ |
#include <iostream>
using namespace std;
class Father
{
private:
string str1 = "私有成员";
protected:
string str2 = "保护成员";
public:
string str3 = "公有成员";
void test()
{
cout << str2 << endl;
}
};
class Son:public Father
{
public:
void test2()
{
cout << str2 << endl;
cout << str3 << endl;
}
};
int main()
{
Father f;
f.test();
// cout << f.str2 << endl; 错误
Son s;
s.test2();
return 0;
}
1.6.2 公有继承
公有继承下,基类的private成员可以被派生类继承,但是无法直接访问。基类的protected和public成员继承到派生类仍然作为派生类的protected和public的成员(权限不变)。
#include <iostream>
using namespace std;
class Father
{
private:
string str1 = "私有成员";
protected:
string str2 = "保护成员";
public:
string str3 = "公有成员";
};
class Son:public Father
{
public:
// 1. str1继承了,但无法直接访问
// 2. str2继承了,并且作为Son的protected成员
// 3. str3继承了,并切作为Son的public成员
};
class Grandson:public Son
{
public:
Grandson()
{
cout << str2 << endl;
}
};
int main()
{
Son s;
// cout << s.str2 << endl; 错误
cout << s.str3 << endl;
Grandson gs;
return 0;
}
1.6.2 保护继承
保护继承下,基类的private成员可以被派生类继承,但是无法直接访问。基类的protected和public成员继承到派生类都变成派生类的protected成员。
#include <iostream>
using namespace std;
class Father
{
private:
string str1 = "私有成员";
protected:
string str2 = "保护成员";
public:
string str3 = "公有成员";
};
class Son:protected Father
{
public:
// 1. str1继承了,但无法直接访问
// 2. str2继承了,并且作为Son的protected成员
// 3. str3继承了,并切作为Son的protected成员
};
class Grandson:public Son
{
public:
Grandson()
{
cout << str2 << endl;
cout << str3 << endl;
}
};
int main()
{
Son s;
// cout << s.str2 << endl; 错误
// cout << s.str3 << endl; 错误
Grandson gs;
return 0;
}
1.6.3 私有继承
私有继承下,基类的private成员可以被派生类继承,但是无法直接访问。基类的protected和public成员继承到派生类都变成派生类的private成员。
#include <iostream>
using namespace std;
class Father
{
private:
string str1 = "私有成员";
protected:
string str2 = "保护成员";
public:
string str3 = "公有成员";
};
class Son:private Father
{
public:
// 1. str1继承了,但无法直接访问
// 2. str2继承了,并且作为Son的private成员
// 3. str3继承了,并切作为Son的private成员
Son()
{
// cout << str1 << endl; 错误
cout << str2 << endl;
cout << str3 << endl;
}
};
class Grandson:public Son
{
public:
Grandson()
{
// cout << str2 << endl;
// cout << str3 << endl;
}
};
int main()
{
Son s;
// cout << s.str2 << endl; 错误
// cout << s.str3 << endl; 错误
Grandson gs;
return 0;
}
练习
定义学生类,有姓名,学号,性别,年龄等私有成员变量,有构造和析构函数,有打印信息的成员函数。
要求通过构造函数可以给属性赋予初始值。
定义大学生类,继承自学生类,大学生有专业名、成绩的私有成员变量,还有是否获得奖学金的成员函数(成绩为判断依据)。隐藏基类打印信息的成员函数,新的打印信息的成员函数也要能打印姓名、学号、性别、年龄信息。
要求通过构造函数可以给属性赋予初始值。
再定义研究生类,继承自大学生类,有导师姓名和工资的私有成员变量,有打印工资这个成员函数。
要求通过构造函数可以给属性赋予初始值。
#include <iostream>
#include <string>
using namespace std;
// 基类:学生
class Student {
private:
string name; // 姓名
string studentId;// 学号
string gender; // 性别
int age; // 年龄
public:
// 构造函数
Student(string n, string id, string g, int a)
: name(n), studentId(id), gender(g), age(a) {}
// 析构函数
virtual ~Student() = default;
// 打印信息
virtual void printInfo() const {
cout << "姓名: " << name << endl;
cout << "学号: " << studentId << endl;
cout << "性别: " << gender << endl;
cout << "年龄: " << age << endl;
}
};
// 派生类:大学生
class Undergraduate : public Student {
private:
string major; // 专业名
double score; // 成绩
public:
// 构造函数
Undergraduate(string n, string id, string g, int a, string m, double s)
: Student(n, id, g, a), major(m), score(s) {}
// 判断是否获得奖学金
bool hasScholarship() const {
return score >= 85.0;
}
// 隐藏基类的打印函数,打印更详细信息
void printInfo() const override {
Student::printInfo(); // 调用基类的打印函数
cout << "专业: " << major << endl;
cout << "成绩: " << score << endl;
cout << "奖学金: " << (hasScholarship() ? "是" : "否") << endl;
}
};
// 派生类:研究生
class Graduate : public Undergraduate {
private:
string tutor; // 导师姓名
double salary; // 工资
public:
// 构造函数
Graduate(string n, string id, string g, int a, string m, double s, string t, double sal)
: Undergraduate(n, id, g, a, m, s), tutor(t), salary(sal) {}
// 打印工资
void printSalary() const {
cout << "导师: " << tutor << endl;
cout << "工资: " << salary << endl;
}
// 再次隐藏打印函数
void printInfo() const override {
Undergraduate::printInfo(); // 调用大学生类的打印函数
printSalary(); // 打印研究生特有的信息
}
};
// 测试代码
int main() {
Student s("张三", "2023001", "男", 20);
Undergraduate u("李四", "2023002", "女", 21, "计算机科学", 90.5);
Graduate g("王五", "2023003", "男", 25, "人工智能", 88.0, "赵教授", 3000.0);
cout << "===== 学生信息 =====" << endl;
s.printInfo();
cout << "\n===== 大学生信息 =====" << endl;
u.printInfo();
cout << "\n===== 研究生信息 =====" << endl;
g.printInfo();
return 0;
}
2. 多态 Polymorphism
它指的是同一种方法调用,在不同的对象上产生不同的行为
2.1 函数覆盖 override
函数覆盖也被称为函数重写,函数覆盖是多态的触发条件之一。
函数覆盖与函数隐藏相似,最大的区别是基类被覆盖的函数需要使用virtual修饰,被virtual修饰的函数是虚函数。
定义:派生类重新定义基类中已有的虚函数,从而实现运行时多态
基类函数必须为虚函数:使用 virtual 关键字声明。
函数签名必须相同:包括函数名、参数列表、常量性(const)。
虚函数有以下性质:
- 虚函数具有传递性,基类的虚函数可以把派生类新覆盖的函数编程虚函数。(基类的虚函数会使派生类中同名同参的函数自动成为虚函数(即使派生类未显式声明virtual))
- 成员函数可以设置为虚函数,静态成员函数不能设置为虚函数。
- 构造函数不能设置为虚函数,析构函数可以设置为虚函数。
- 如果函数声明与定义分离,只需要使用virtual修饰声明即可。
- 在C++11中,可以使用override关键字验证是否覆盖成功。
函数覆盖与函数重载的区别?
答:覆盖发生在基类和派生类的虚函数之间,要求函数签名相同,支持动态绑定;重载发生在同一类中,通过参数列表区分,是静态绑定。
函数覆盖 vs 函数重载 vs 函数隐藏
特性 | 函数覆盖(Override) | 函数重载(Overload) | 函数隐藏(Hide) |
---|---|---|---|
发生范围 | 基类与派生类之间 | 同一类中 | 基类与派生类之间 |
函数名 | 必须相同 | 必须相同 | 必须相同 |
参数列表 | 必须相同 | 必须不同 | 可同可不同 |
virtual 关键字 | 基类函数必须为虚函数 | 无关 | 无关 |
调用方式 | 动态绑定(运行时根据对象类型决定) | 静态绑定(根据参数类型和数量决定) | 静态绑定 |
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat()
{
cout << "吃东西" << endl;
}
};
class Dog:public Animal
{
public:
void eat() override // 如果不报错,说明覆盖成功
{
cout << "吃狗粮" << endl;
}
// 错误:覆盖失败//函数名不同
// void eat2() override
// {
// cout << "吃肉" << endl;
// }
};
int main()
{
Dog d;
d.eat();
return 0;
}
2.2 多态的基本使用
从广义上讲,多态分为静态多态和动态多态。
静态多态也被称为编译时多态,发生在程序的编译阶段,实际上静态多态就是之前学习函数重载,模板。
动态多态也被称为运行时多态,发生在程序的运行阶段,从狭义上讲多态指的是动态多态,从讲课的角度后续统一默认多态为动态多态。
多态按照字面的理解就是“多种状态”,可以概括为“一个接口,多种状态”,例如一个函数接收参数,程序的运行的过程中根据传入的参数类型的不同,自动决定调用不同的函数逻辑。
多态将接口与实现进行分离,因个体差异,采用不同的策略。
多态的实现需要具有三个条件:
1. 公有继承
2. 函数覆盖
3. 基类指针指向/引用 派生类对象
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat()
{
cout << "吃东西" << endl;
}
};
class Dog:public Animal
{
public:
void eat() override
{
cout << "吃狗粮" << endl;
}
};
class Cat:public Animal
{
public:
void eat()
{
cout << "豪猫有猫条,键帽有数据线" << endl;
}
};
void test_polymorphism(Animal& a)
{
a.eat();
}
void test_polymorphism(Animal* a)
{
a->eat();
}
int main()
{
// 栈内存配合引用
Animal a1;
Cat c1;
Dog d1;
test_polymorphism(a1);
test_polymorphism(c1);
test_polymorphism(d1);
// 堆内存配合指针
Animal* a2 = new Animal;
Cat* c2 = new Cat;
Dog* d2 = new Dog;
test_polymorphism(a2);
test_polymorphism(c2);
test_polymorphism(d2);
delete a2;
delete c2;
delete d2;
return 0;
}
2.3 多态原理
当一个类中有虚函数时,编译器会为这个这个类分配一个表记录这些虚函数,这个表只有一份,并不与对象绑定,对象内部会多出一个隐藏指针成员,指向这张虚函数表。
在继承的过程中,派生类对象也会拥有隐藏的成员指针,指向自己的类所属的虚函数表,这张表是从基类的虚函数表复制而来。
当派生类中实现了函数覆盖,则新覆盖的函数会直接修改虚函数表的被覆盖基类函数。
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat()
{
cout << "吃东西" << endl;
}
virtual void sleep()
{
cout << "呼呼呼" << endl;
}
};
class Dog:public Animal
{
public:
void eat() override
{
cout << "吃狗粮" << endl;
}
virtual void bark()
{
cout << "嗯啊" << endl;
}
};
class Cat:public Animal
{
public:
void eat()
{
cout << "豪猫有猫条,键帽有数据线" << endl;
}
void jump()
{
cout << "跳!" << endl;
}
};
void test_polymorphism(Animal& a)
{
a.eat();
}
void test_polymorphism(Animal* a)
{
a->eat();
}
int main()
{
Animal a;
cout << sizeof(a) << endl; // 4
Dog d;
cout << sizeof(d) << endl; // 4
Cat c;
cout << sizeof(c) << endl; // 4
// 基类引用派生类对象
Animal& aa = d;
return 0;
}
在运行的过程中,使用动态类型绑定的机制调用函数,具体步骤如下:
- 当使用基类指针指向/引用派生类对象时,编译器会在内部生成一段检查类型的代码,用于检查对象真正的类型是什么。
- 在运行时通过对象的成员变量指针找到虚函数表
- 在虚函数表中找到具体的函数调用地址
无论是虚基类表还是虚函数表,都会让代码内部增加一些查询的消耗,因此菱形继承或多态都会降低代码的执行效率,但是提高了编程效率。
2.4 虚析构函数
当基类指针指向或引用派生类对象时,容易出现内存泄漏问题,因为派生类构造函数可能不会被调用。
解决的方法是把析构函数设置为虚函数。
原理:虚析构函数会让编译器在对象的虚函数表中记录析构函数地址。当通过基类指针删除对象时,会根据虚函数表找到实际对象类型的析构函数,先调用派生类析构,再调用基类析构,确保资源完整释放。
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat()
{
cout << "吃东西" << endl;
}
virtual ~Animal()
{
cout << "Animal析构函数" << endl;
}
};
class Dog:public Animal
{
public:
void eat() override
{
cout << "吃狗粮" << endl;
}
~Dog() // 也可以触发虚函数的传递性,特殊情况
{
cout << "Dog析构函数" << endl;
}
};
int main()
{
Animal* a = new Dog;
delete a; //**只基类销毁,Dog泄露了
return 0;
}
在设计类时,如果使用默认的析构函数会存在上面的隐患,建议给所有顶层基类的析构函数都设置为虚析构函数。
3. 类型转换(掌握)
C++也支持之前C的类型转换语法——强制类型转换
但是C的转换方式可能会带来一些隐患,让问题难以发现
在C++11中,提供了四种类型转换函数,每个函数有自己擅长的点:
- static_cast 静态转换
- dynamic_cast 动态转换
- const_cast 常量转换
- reinterpret_cast 重解释转换
3.1 static_cast
主要用于基本数据类型转换。
#include <iostream>
using namespace std;
int main()
{
int x = 1;
// double y = (double)x;
double y = static_cast<double>(x);
cout << y << endl;
return 0;
}
static_cast相比于C转换,可以在编译时进行类型检查,确保转换是合法的。如果转换不合法,编译器将报错,从而帮助开发者在编译阶段就发现并修复问题。
但是它没有运行时类型检查来保证转换的安全性,需要程序员来判断转换是否安全。
static_cast也可以用于类层次结构转换(但不擅长),即基类和派生类之间的指针或引用的转换:
- 可以安全的进行上行转换:派生类指针或引用转换为基类的。
#include <iostream>
using namespace std;
class Father
{
public:
string a = "Father";
};
class Son:public Father
{
public:
string b = "Son";
};
int main()
{
// 上行转换:安全
// 引用
Son s1;
Father& f1 = static_cast<Father&>(s1);
cout << f1.a << endl;
// cout << f1.b << endl; 错误
cout << &s1 << " " << &f1 << endl; // 0x61fe7c 0x61fe7c
// 指针
Son* s2 = new Son;
Father* f2 = static_cast<Father*>(s2);
cout << f2->a << endl;
// cout << f2->b << endl; 错误
cout << s2 << " " << f2 << endl; // 0x1051150 0x1051150
delete f2;
return 0;
}
- 不能安全进行下行转换:基类的指针或引用转换为派生类的。
#include <iostream>
using namespace std;
class Father
{
public:
string a = "Father";
};
class Son:public Father
{
public:
string b = "Son";
};
int main()
{
// 下行转换:不安全
// 引用
Father f1;
Son& s1 = static_cast<Son&>(f1);
cout << s1.a << endl; // Father
cout << s1.b << endl; // ???
cout << &f1 << " " << &s1 << endl;
// 指针
Father* f2 = new Father;
Son* s2 = static_cast<Son*>(f2);
cout << s2->a << endl; // Father
cout << s2->b << endl; // 乱码
cout << f2 << " " << s2 << endl;
delete s2;
return 0;
}
static_cast在特殊情况下,也可以转换为自定义类型,实际上是隐式调用了构造函数。
#include <iostream>
using namespace std;
class Student
{
private:
int age;
string name;
public:
Student(int a):age(a),name("张三"){}
Student(int a,string n):age(a),name(n){}
void show()
{
cout << name << " " << age << endl;
}
};
int main()
{
Student s4 = static_cast<Student>(18);
s4.show();
// Student s5 = static_cast<Student>(18,"李四");错误
// s5.show(); 错误
return 0;
}
3.2 dynamic_cast
dynamic_cast主要用于类层次之间的转换:
- 上行转换与static_cast完全相同
- 下行转换比static_cast更加安全,有类型检查功能:
#include <iostream>
using namespace std;
class Father
{
public:
string a = "Father";
virtual void test()
{
cout << "被覆盖的函数" << endl;
}
};
class Son:public Father
{
public:
string b = "Son";
void test()
{
cout << "新覆盖的函数" << endl;
}
};
int main()
{
// 下行转换
// 引用
Son s0;
Father& f1 = s0;
Son& s1 = dynamic_cast<Son&>(f1);
cout << s1.a << endl;
cout << s1.b << endl;
cout << &s0 << " " << &f1 << " " << &s1 << endl;
// 指针
Father* f2 = new Father;
Son* s2 = dynamic_cast<Son*>(f2);
cout << f2 << " " << s2 << endl;
return 0;
}
2和3
4
3.3 const_cast
const_cast用于添加或移除对象的const修饰符。
主要的用法是改变指针或引用的const特性,以便于在某些情况下修改const变量。正常的代码不应该使用const_cast,而是应该通过设计良好的代码逻辑避免const_cast的出现。
#include <iostream>
using namespace std;
class Test
{
public:
string str = "A";
};
int main()
{
const Test* t1 = new Test;
cout << t1->str << endl;
// t1->str = "B"; 错误
Test* t2 = const_cast<Test*>(t1);
cout << t1 << " " << t2 << endl; // 0x1001118 0x1001118
t2->str = "B";
cout << t1->str << endl; // B
return 0;
}
3.4 reinterpret_cast
reinterpret_cast可以把内存中的值按照制定的规则重新解释,这种转换风险极高,慎用!
#include <iostream>
using namespace std;
class A
{
public:
string str = "A";
};
class B
{
public:
char str = 'B';
};
int main()
{
A* a = new A;
B* b = reinterpret_cast<B*>(a);
cout << a << " " << b << endl; // 0x10b1118 0x10b1118
cout << b->str << endl;
char c = '@';
char* ptr_c = &c;
int* ptr_i = reinterpret_cast<int*>(ptr_c);
cout << *ptr_i << endl; // 1644068672
return 0;
}
4. 抽象类 abstract class(掌握)
4.1 基本使用
如果一个类只表示抽象概念,并不与实际的对象相联系,它可以为派生类提供一个算法框架。
- 定义:包含至少一个纯虚函数的类称为抽象类。
抽象类 ←→ 纯虚函数
- 如果一个类是抽象类,则必然包含至少一个纯虚函数
- 如果一个类有纯虚函数,则这个类必然是抽象类
纯虚函数是一种特殊的虚函数,这种函数只有声明没有定义。
特点:
无法实例化对象(如 Shape s; 会编译错误)。
必须被继承,且派生类需实现所有纯虚函数才能实例化。
可作为指针或引用类型,实现多态调用。
#include <iostream>
using namespace std;
/**
* @brief The Shape class 形状类
*/
class Shape
{
public:
// 纯虚函数
virtual void perimeter() = 0;
virtual void area() = 0;
};
int main()
{
// Shape s; 错误
return 0;
}
4.2 继承
4.2.1 派生类实现所有纯虚函数
实现(reimplement)指的是覆盖基类的纯虚函数,并增加函数体。
如果派生类实现了所有的纯虚函数(继承来的),此时派生类中就没有纯虚函数了,派生类可以作为普通类使用。
#include <iostream>
using namespace std;
/**
* @brief The Shape class 形状类
*/
class Shape
{
public:
// 纯虚函数
virtual void perimeter() = 0;
virtual void area() = 0;
};
/**
* @brief The Circle class 圆形
*/
class Circle:public Shape
{
public:
void perimeter()
{
cout << "2πR" << endl;
}
void area()
{
cout << "πR^2" << endl;
}
};
int main()
{
// Shape s; 错误
Circle c;
c.area();
c.perimeter();
return 0;
}
4.2.2 派生类没有实现所有纯虚函数
这种情况下,派生类(Polygon)中仍然存在纯虚函数(继承来的),此时派生类仍然是一个抽象类,需要继续向下继承,直到某个派生类(Rectangle)全部实现纯虚函数。
#include <iostream>
using namespace std;
/**
* @brief The Shape class 形状类
*/
class Shape
{
public:
// 纯虚函数
virtual void perimeter() = 0;
virtual void area() = 0;
};
/**
* @brief The Circle class 圆形
*/
class Circle:public Shape
{
public:
void perimeter()
{
cout << "2πR" << endl;
}
void area()
{
cout << "πR^2" << endl;
}
};
/**
* @brief The Polygon class 多边形
*/
class Polygon:public Shape
{
public:
void perimeter()
{
cout << "Σ边长" << endl;
}
};
/**
* @brief The Rectangle class 矩形
*/
class Rectangle:public Polygon
{
public:
void area()
{
cout << "w*h" << endl;
}
};
int main()
{
// Shape s; 错误
Circle c;
c.area();
c.perimeter();
// Polygon p; 错误
Rectangle r;
r.area();
r.perimeter();
return 0;
}
4.3 多态
抽象类型不能声明,但是抽象类型的引用和指针可以声明,因为抽象类支持多态。
#include <iostream>
using namespace std;
/**
* @brief The Shape class 形状类
*/
class Shape
{
public:
// 纯虚函数
virtual void perimeter() = 0;
virtual void area() = 0;
virtual ~Shape(){}
};
/**
* @brief The Circle class 圆形
*/
class Circle:public Shape
{
public:
void perimeter()
{
cout << "2πR" << endl;
}
void area()
{
cout << "πR^2" << endl;
}
};
/**
* @brief The Polygon class 多边形
*/
class Polygon:public Shape
{
public:
void perimeter()
{
cout << "Σ边长" << endl;
}
};
/**
* @brief The Rectangle class 矩形
*/
class Rectangle:public Polygon
{
public:
void area()
{
cout << "w*h" << endl;
}
};
void test_poly(Shape& s)
{
s.area();
s.perimeter();
}
void test_poly(Shape* s)
{
s->area();
s->perimeter();
}
int main()
{
Rectangle r1;
Circle c1;
test_poly(r1);
test_poly(c1);
Rectangle* r2 = new Rectangle;
Circle* c2 = new Circle;
test_poly(r2);
test_poly(c2);
delete r2;
delete c2;
return 0;
}
4.4 纯虚析构函数(了解)
如果一个类没有纯虚函数又想成为抽象类,可以把析构函数写成纯虚析构函数“占坑”。类内声明,
#include <iostream>
using namespace std;
class Test
{
public:
// 声明纯虚析构函数
virtual ~Test() = 0;
};
// 为了不产生析构问题,添加析构函数的定义
Test::~Test()
{
cout << "函数体还是必要的" << endl;
}
int main()
{
// Test t; 抽象类
return 0;
}
练习
创建一个场地类Court,包含一个成员函数bounce,在成员函数中输出“反弹”。
要求设置虚析构函数,并输出“Court销毁”。
创建两个网球场地类继承场地类,分别为硬地场地和红土场地,两个类都覆盖基类的bounce函数,分别输出“砰砰砰”和“咚咚咚”。
创建一个网球类Tennis,要求
- 有一个如下格式的构造函数:
Tennis(Court* c)
在Tennis的构造函数中测试场地对象的bounce功能。
- 屏蔽Tennis类的复制语义(拷贝构造、赋值运算符)
- 屏蔽隐式调用构造函数
- 在Tennis的析构函数中delete场地对象。
在主函数中分别创建硬地场地对象、红土场地对象与网球对象测试功能。
#include <iostream>
using namespace std;
// 场地基类
class Court {
public:
virtual void bounce() {
cout << "反弹" << endl;
}
virtual ~Court() {
cout << "Court销毁" << endl;
}
};
// 硬地场地类
class HardCourt : public Court {
public:
void bounce() override {
cout << "砰砰砰" << endl;
}
~HardCourt() override {
cout << "HardCourt销毁" << endl;
}
};
// 红土场地类
class ClayCourt : public Court {
public:
void bounce() override {
cout << "咚咚咚" << endl;
}
~ClayCourt() override {
cout << "ClayCourt销毁" << endl;
}
};
// 网球类
class Tennis {
private:
Court* court;
public:
// 显式构造函数
explicit Tennis(Court* c) : court(c) {
court->bounce(); // 测试场地的bounce功能
}
// 禁用拷贝构造和赋值运算符
Tennis(const Tennis&) = delete;
Tennis& operator=(const Tennis&) = delete;
// 析构函数
~Tennis() {
delete court;
}
};
// 测试代码
int main() {
// 硬地场地测试
{
Tennis tennis(new HardCourt());
} // 离开作用域,Tennis析构,自动释放HardCourt
cout << endl;
// 红土场地测试
{
Tennis tennis(new ClayCourt());
} // 离开作用域,Tennis析构,自动释放ClayCourt
return 0;
}
有抽象类键盘,键盘中有一个纯虚函数show;
有抽象类鼠标,鼠标中有一个纯虚函数show;
有一个微软键盘类继承键盘类,有一个苹果键盘继承键盘类;有一个微软鼠标类继承鼠标类,有一个苹果鼠标类继承鼠标类。上面四个派生类都覆盖实现纯虚函数show
有一个工厂类Factory
伪代码如下
class Factory
{
public:
virtual 键盘类指针 createKeyboard() = 0;
virtual 鼠标类指针 createMouse() = 0;
};
有一个微软工厂类继承工厂类,覆盖实现工厂类所有的纯虚函数。
有一个苹果工厂类继承工厂类,覆盖实现工厂类所有的纯虚函数。
在主函数中使用两个工厂类的对象(苹果工厂和微软工厂)生产各自品牌的键盘和鼠标对象。
并测试键盘鼠标的功能。
#include <iostream>
using namespace std;
// 抽象键盘类
class Keyboard {
public:
virtual void show() = 0;
virtual ~Keyboard() {}
};
// 抽象鼠标类
class Mouse {
public:
virtual void show() = 0;
virtual ~Mouse() {}
};
// 微软键盘类
class MicrosoftKeyboard : public Keyboard {
public:
void show() override {
cout << "微软键盘已创建,正在使用中..." << endl;
}
};
// 苹果键盘类
class AppleKeyboard : public Keyboard {
public:
void show() override {
cout << "苹果键盘已创建,正在使用中..." << endl;
}
};
// 微软鼠标类
class MicrosoftMouse : public Mouse {
public:
void show() override {
cout << "微软鼠标已创建,正在使用中..." << endl;
}
};
// 苹果鼠标类
class AppleMouse : public Mouse {
public:
void show() override {
cout << "苹果鼠标已创建,正在使用中..." << endl;
}
};
// 抽象工厂类
class Factory {
public:
virtual Keyboard* createKeyboard() = 0;
virtual Mouse* createMouse() = 0;
virtual ~Factory() {}
};
// 微软工厂类
class MicrosoftFactory : public Factory {
public:
Keyboard* createKeyboard() override {
return new MicrosoftKeyboard();
}
Mouse* createMouse() override {
return new MicrosoftMouse();
}
};
// 苹果工厂类
class AppleFactory : public Factory {
public:
Keyboard* createKeyboard() override {
return new AppleKeyboard();
}
Mouse* createMouse() override {
return new AppleMouse();
}
};
int main() {
// 创建微软工厂并生产产品
Factory* microsoftFactory = new MicrosoftFactory();
Keyboard* microsoftKeyboard = microsoftFactory->createKeyboard();
Mouse* microsoftMouse = microsoftFactory->createMouse();
// 测试微软产品
microsoftKeyboard->show();
microsoftMouse->show();
// 创建苹果工厂并生产产品
Factory* appleFactory = new AppleFactory();
Keyboard* appleKeyboard = appleFactory->createKeyboard();
Mouse* appleMouse = appleFactory->createMouse();
// 测试苹果产品
appleKeyboard->show();
appleMouse->show();
// 释放资源
delete microsoftKeyboard;
delete microsoftMouse;
delete microsoftFactory;
delete appleKeyboard;
delete appleMouse;
delete appleFactory;
return 0;
}