1.继承(掌握)
1.1 概念
继承就是在一个已经存在的类的基础上创建一个新的类,并且拥有其特性,体现了代码复用性。
已存在的类:被称之为 基类 或者 父类
新创建的类:被称之为 派生类 或者 子类
例:
动物(Animal)是生物的一个种类。它们一般以有机物为食,是能够自主运动或能够活动的有感觉的生物。人类也是动物之一。第一批被送上太空的动物是一群果蝇。 [9]
狗,是食肉目犬科 [11]犬属 [13]哺乳动物 [12],别称犬,与马、牛、羊、猪和鸡并称“六畜” [13]。狗的体型大小、毛色因品种不同而不同,体格匀称;鼻吻部较长;眼呈卵圆形;两耳或竖或垂;四肢矫健,前肢5趾,后肢4趾,具爪,但爪不能伸缩;尾呈环形或镰刀形 [14]。据李时珍《本草纲目》记载:“狗,叩的意思。狗吠声有节奏,如同叩击物体一般,也有人说是因其苟且,故称之为狗 [15]。”
贵宾犬(英文名:Poodle),别名贵妇犬、蒲尔犬、蒲多犬。 [2]美国养犬俱乐部(AKC)将其归类为玩具犬组,按体型可分为玩具型、迷你型和标准型三种。 [3]玩具型贵宾犬体高小于28厘米,体重小于4千克;迷你型体高小于38厘米,体重小于12千克;标准型体高大于38厘米,体重大于12千克。 [3]贵宾犬被毛颜色为纯色, [2]毛发类型分为卷曲型和绳索型两种。卷曲型被毛粗硬浓密;绳索型被毛下垂,身体各部位被毛长度不等,颈部、躯干、头部和耳部的被毛较长。 [2] [6]贵宾犬颅骨呈圆形,耳朵下垂,耳郭长而宽,眼睛呈卵圆形,牙齿呈剪状咬合。
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
// 基类
class Father
{
private:
string fname="康";
public:
void money()
{
cout << 10000000000000 << endl;
}
void work()
{
cout << "当保安" << endl;
}
string get_name()
{
return fname;
}
};
// 派生类
class Son:public Father
{
};
int main()
{
Son s1;
s1.money ();// 10000000000000
cout <<s1.get_name ()<< endl; // 康
s1.work ();// 当保安
return 0;
}
上面的代码,Son类和Father类几乎重叠,在实际使用过程中,派生类往往会做出一些与基类的差异化。
- 修改继承基类的内容
属性:1. 公有属性 直接修改 2. 私有属性 通过接口
行为:修改函数:函数隐藏,在派生类中创建一个同名的函数,如果想要调用基类的同名函数,需要类名:
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
// 基类
class Father
{
private:
string fname="康";
public:
int age =40;
void money()
{
cout << 10000000000000 << endl;
}
void work()
{
cout << "当保安" << endl;
}
string get_name()
{
return fname;
}
void set_name(string n)
{
fname=n;
}
};
// 派生类
class Son:public Father
{
private:
string a;
public:
Son()
{
age=18;
}
void set11()
{
set_name ("张");
// fname="zhang"; // 报错
}
// 函数隐藏
void work()
{
cout << "当程序员 敲代码" << endl;
}
void sing()
{
cout << "唱歌" << endl;
}
};
int main()
{
Son s1;
s1.money ();// 10000000000000
cout << s1.get_name () << endl; // 康
// s1.work ();// 当保安
s1.work ();// 当程序员 敲代码
cout << s1.age << endl; // 40
s1.set11 ();
cout << s1.get_name () << endl; // 张
// 调用隐藏的函数
s1.Father::work (); // 当保安
return 0;
}
派生类往往是基类的具象化,基类则是派生类的抽象化。
基类和派生类时相对的,一个类可能存在又是基类又是派生类的情况,取决于哪两个类比较。
【思考】 构造函数和析构函数 会被继承吗? 不能
1.2 构造函数
1.2.1 派生类与基类的关系
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
// 基类
class Father
{
private:
string fname="康";
public:
Father(string n):fname(n){}
void money()
{
cout << 10000000000000 << endl;
}
void work()
{
cout << "当保安" << endl;
}
string get_name()
{
return fname;
}
void set_name(string n)
{
fname=n;
}
};
// 派生类
class Son:public Father
{
public:
// 构造函数默认添加
Son():Father(){ }
};
int main()
{
Son s1; // 报错
Son s2("zhang");// 报错
return 0;
}

实际上派生类对象的创建需要默认先调用基类的无参构造函数

1.2.2解决:
1.2.2.1 手动添加
![]()
1.2.2.2 透传构造(重点)
指的是:在派生类中的构造函数中显式的调用基类的构造函数


1.2.2.3 委托构造(掌握)
一个类的构造函数可以调用当前类的另一个构造函数,但是要避免循环委托。


委托构造的性能低于透传构造,但是代码的维护性更好,因为通常一个类中构造函数都会委托给能力最强(参数最多)的构造函数,代码重构时,只需要更改这个能力最强的构造函数即可。
1.2.2.4 继承构造
C++11新增的写法,只需要一句话,就可以给派生类添加n个构造函数(n为基类构造函数的个数)。并且每一个派生类的构造函数格式都与基类相同。每个派生类的构造函数都通过透传构造调用对应格式的基类构造函数。
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
// 基类
class Father
{
private:
string fname="康";
public:
Father(string n):fname(n){}
Father(){}
Father(string n,int a){}
void money()
{
cout << 10000000000000 << endl;
}
void work()
{
cout << "当保安" << endl;
}
string get_name()
{
return fname;
}
void set_name(string n)
{
fname=n;
}
};
// 派生类
class Son:public Father
{
public:
// 继承构造
using Father::Father;
// 有参调用有参 无参调用无参
// Son():Father(){ }
// Son(string n):Father(n){}
// Son(string n,int a):Father(n,a){}
};
int main()
{
Son s1;
Son s2("zhang");
Son s3("zhang",10);
return 0;
}
1.3 对象创建和销毁的流程
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
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 val=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 val=Value("Son类的成员变量");
Son()
{
cout << "Son的构造函数" << endl;
}
~Son()
{
cout << "Son的析构函数" << endl;
}
};
Value Son::s_value=Value("Son的静态成员变量");
int main()
{
cout << "主函数执行" << endl;
{
Son s;
cout << "对象使用中~~~~~~" << endl;
}
cout << "主函数执行结束" << endl;
return 0;
}
执行结果:

规律:
- 创建 :静态早于非静态, 销毁:非静态早于静态
- 以“对象使用中”为轴,呈上下对称结构
- 创建(先基后派) 销毁(先派后基)
1.4 多重继承
1.4.1 概念
c++支持多重继承,即一个派生类可以有多个基类,派生类对于每一个基类的关系都可以单程是一个单继承
#include <iostream>
#include <string>
#include <string.h>
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 sb1;
sb1.lay ();
sb1.sit ();
return 0;
}
1.4.2 可能出现的问题
#include <iostream>
#include <string>
#include <string.h>
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 sb1;
sb1.lay ();
sb1.sit ();
// sb1.position (); // 报错
return 0;
}
报错:

解决方案:使用 类名::方式调用

问题二 — 菱形继承
当一个派生类有多个基类,并且这些基类又有一个共同的基类时,则会出现二义性问题,这种现象也叫菱形继承(钻石继承)。

#include <iostream>
#include <string>
#include <string.h>
using namespace std;
// 家具
class Furniture
{
public:
void fun()
{
cout << "家具可以放置在家里的任何位置" << endl;
}
};
class Sofa:public Furniture{};
class Bed:public Furniture{};
// 多重继承
class SofaBed:public Sofa, public Bed
{
};
int main()
{
SofaBed sb1;
sb1.fun ();// 报错
return 0;
}
解决:
1.使用基类::方式
![]()
2.使用虚继承
当出现虚继承的时候,Furniture类(最上层的类)中会产生一张虚基类表,这个表不占用任何对象的存储空间,属于Furniture类持有,在程序启动是加载进内存,表中记录了Furniture的函数的调用地址偏移量
Bed和Sofa会出现一个隐藏的成员变量指针,指向Furniture类中的虚基类表,占用对象四个字节
虚继承(SofaBed继承Sofa和Bed)时,SofaBed类对象会同时拥有两个虚基类表指针,在调用时查表解决二义性问题
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
// 家具
class Furniture
{
public:
void fun()
{
cout << "家具可以放置在家里的任何位置" << endl;
}
};
class Sofa:virtual public Furniture{};
class Bed:virtual public Furniture{};
// 多重继承
class SofaBed:public Sofa, public Bed
{
};
int main()
{
SofaBed sb1;
sb1.fun ();// 家具可以放置在家里的任何位置
return 0;
}
2.权限
2.1 权限修饰符
三种权限修饰符对应了九个场景。
|
|
类内 |
派生类 |
全局 |
|
private:私有 |
√ |
× |
× |
|
protected:保护 |
√ |
√ |
× |
|
public:公开 |
√ |
√ |
√ |
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
class Father
{
protected:
string s="保护权限";
public:
Father()
{
cout << s <<endl; // 保护权限
}
};
class Son:public Father
{
public:
Son()
{
cout << s <<endl;// 保护权限
}
};
int main()
{
Father f1;
cout << " Son:—- " << f1.s << endl; // 报错
Son s;
cout << " Son:—- " << s.s << endl;// 报错
return 0;
}
2.2 不同的权限继承
通过不同权限修饰符,实现继承关系
- 公有继承
- 保护继承
- 私有继承(默认)
2.2.1 公有继承
派生类对象在继承基类的成员时使用public修饰
特点:
- 基类的私有成员,派生类无法直接访问
- 基类的保护和公有成员,到派生类中依旧保持原有权限
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
class Father
{
private:
string s="私有权限";
protected:
string s1="保护权限";
public:
string s2="公有权限";
};
class Son:public Father
{
public:
Son()
{
// cout << s <<endl;// 报错
cout << s1 <<endl;// 保护权限
cout << s2 <<endl;// 公有权限
}
};
int main()
{
Son s;
// cout << s.s1 <<endl;// 报错
cout << s.s2 <<endl;// 公有权限
return 0;
}
2.2.2 保护继承(掌握)
使用protected修饰继承
特点:
- 基类的私有成员,派生类无法直接访问
- 基类的保护和公有成员,到派生类中所有成员都变为保护权限
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
class Father
{
private:
string s="私有权限";
protected:
string s1="保护权限";
public:
string s2="公有权限";
};
class Son:protected Father
{
public:
Son()
{
// cout << s <<endl;// 报错
cout << s1 <<endl;// 保护权限
cout << s2 <<endl;// 公有权限
}
};
int main()
{
Son s;
cout << s.s1 <<endl;// 报错
cout << s.s2 <<endl;// 报错
return 0;
}
2.2.3 私有继承(掌握)
使用private修饰继承
特点:
- 基类的私有成员,派生类无法直接访问
- 基类的保护和公有成员,到派生类中所有成员都变为私有权限
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
class Father
{
private:
string s="私有权限";
protected:
string s1="保护权限";
public:
string s2="公有权限";
};
class Son:private Father
{
public:
Son()
{
// cout << s <<endl;// 报错
cout << s1 <<endl;// 保护权限
cout << s2 <<endl;// 公有权限
}
};
int main()
{
Son s;
cout << s.s1 <<endl;// 报错
cout << s.s2 <<endl;// 报错
return 0;
}
在多重继承时:
#include <iostream>
#include <string>
#include <string.h>
using namespace std;
class Father
{
private:
string s="私有权限";
protected:
string s1="保护权限";
public:
string s2="公有权限";
};
class Son:private Father
{
public:
Son()
{
// cout << s <<endl;// 报错
cout << s1 <<endl;// 保护权限
cout << s2 <<endl;// 公有权限
}
};
class GrandSon:public Son
{
public:
GrandSon()
{
// cout << s <<endl;// 报错
cout << s1 <<endl;// 报错
cout << s2 <<endl;// 报错
}
};
int main()
{
// Son s;
// cout << s.s1 <<endl;// 报错
// cout << s.s2 <<endl;// 报错
GrandSon g1;
return 0;
}
3. 多态(重点)
3.1 概念
多态从广义上可以分为静态多态和动态多态。
静态多态(编译时多态)发生在程序的编译阶段,在编译的过程中,编译器根据函数的签名(编译器用来区分不同函数的标准):函数名、参数类型和参数个数,来确定调用哪个函数,具体的表现就是函数重载overload。
动态多态(运行时多态)发生在程序的运行阶段,动态多态也是狭义的多态。本节后续提到的多态都指的是动态多态。
多态从字面上讲是“多种状态”的意思,可以概括为“一个接口,多种状态”,具体描述是共用一个函数接口,但是因参数或返回值的个体差异而采用不同的策略。
3.2 条件
多态的实现需要具备以下三个条件:
- 公有继承
- 函数覆盖 override
- 基类引用或指针指向派生类对象
3.2.1 函数覆盖 override
函数覆盖也被称为函数重写,英文关键字是override(C++11新增)。
函数覆盖的前提条件:
- 虚函数
使用virtual修饰的成员函数,就是虚函数,虚函数具有以下特点:
-
- 成员函数(包括析构函数,但不包括构造函数)可以是虚函数
- 静态成员函数不可以是虚函数
- 虚函数具有传递性
- Qt Creator中虚函数斜体字
- 函数名称相同,参数数量和类型一致,返回值类型相关
下面的代码仅用于展示函数覆盖的语法,因为没有多态,实际无意义。
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat() // 虚函数
{
cout << "动物吃东西" << endl;
}
};
class Dog:public Animal
{
public:
void eat() override // override可以检测是否覆盖成功
{
cout << "狗吃屎" << endl;
}
};
int main()
{
return 0;
}
3.2.2 基类引用/指针指向派生类对象
下面的代码仅用于说明基类引用/指针指向派生类对象的情况,并没有达成多态。
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat() // 虚函数
{
cout << "动物吃东西" << endl;
}
};
class Dog:public Animal
{
public:
void eat() override // override可以检测是否覆盖成功
{
cout << "狗吃屎" << endl;
}
void bark()
{
cout << "汪汪" << endl;
}
};
int main()
{
Dog* d0 = new Dog;
// 基类指针 → 派生类对象
Animal* a = d0;
cout << d0 << " " << a << endl; // 0x8d11d8 0x8d11d8
// a->bark(); 错误
d0->bark();
Dog d;
// 基类引用派生类对象
Animal& b = d;
cout << &d << " " << &b << endl; // 0x61fe80 0x61fe80
d.bark();
// b.bark(); 错误
return 0;
}
3.3 使用
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat() // 虚函数
{
cout << "动物吃东西" << endl;
}
};
class Dog:public Animal
{
public:
void eat() override // override可以检测是否覆盖成功
{
cout << "狗吃屎" << endl;
}
void bark()
{
cout << "汪汪" << endl;
}
};
class Cat:public Animal
{
public:
void eat()
{
cout << "猫吃<・)))><<" << endl;
}
};
/**
* @brief test_eat
* @param a 基类指针
*/
void test_eat(Animal* a)
{
a->eat();
}
/**
* @brief test_eat
* @param a 基类引用
*/
void test_eat(Animal& a)
{
a.eat();
}
int main()
{
Dog* d1 = new Dog;
Cat* c1 = new Cat;
// 一个接口,多种状态
test_eat(d1);
test_eat(c1);
Dog d2;
Cat c2;
// 一个接口,多种状态
test_eat(d2);
test_eat(c2);
return 0;
}
从上面可以看出,多态可以减少代码量,提升程序的灵活度,缺点是减低了程序的性能,主要通过多态原理可以体现。
3.4 原理
多态的原理是动态类型绑定+虚函数表实现。
当一个类Animal中有虚函数时,这个类会有一个隐藏的成员指针,指向一个当前类所有对象共享的表:虚函数表
当Dog继承Animal时,这个类也会有一个隐藏的成员指针,指向Dog自己的虚函数表,这个虚函数表是从Animal处复制的,复制后根据Dog类中的虚函数,分为以下几种情况:
- 继承基类的虚函数,且无覆盖,这种情况Dog的虚函数表不动此类函数
- 函数覆盖,这种情况Dog会重新修改原Animal被覆盖的函数
- Dog新增了虚函数,这种情况直接在虚函数表的尾部,新增虚函数
在多态代码实际运行的过程中,编译器会根据内部的类型检查代码检查对象真正的类型,根据对象的隐藏成员指针,找到对应的虚函数表,再从虚函数表中查询到真正函数的调用地址。
因此多态会产生额外的调用开销,降低了程序的执行速度。
#include <iostream>
using namespace std;
class Animal
{
//private: vptr*; // 伪代码
public:
virtual void eat() // 虚函数
{
cout << "动物吃东西" << endl;
}
virtual void sleep()
{
cout << "呼呼呼" << endl;
}
};
class Computer
{
public:
void print()
{
cout << "这是一台电脑" << endl;
}
};
class Dog:public Animal
{
public:
void eat() override // override可以检测是否覆盖成功
{
cout << "狗吃屎" << endl;
}
virtual void bark()
{
cout << "汪汪" << endl;
}
};
int main()
{
Animal a;
Computer c;
cout << sizeof(a) << endl; // 4(隐藏成员指针)
cout << sizeof(c) << endl; // 1(占位)
Dog d;
cout << sizeof(d) << endl; // 4
return 0;
}
3.5 虚析构函数
下面的代码会导致多态使用的过程中可能会出现内存泄漏的问题。
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat()
{
cout << "动物吃东西" << endl;
}
~Animal()
{
cout << "Animal析构函数" << endl;
}
};
class Dog:public Animal
{
public:
void eat() override
{
cout << "狗吃屎" << endl;
}
~Dog()
{
cout << "Dog析构函数" << endl;
}
};
int main()
{
Animal* a = new Dog;
a->eat();
delete a; // 只有Animal析构函数被调用
return 0;
}
因此多态的使用过程中,必须将基类的析构函数设置为虚析构函数。
#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;
a->eat();
delete a; // 只有Animal析构函数被调用
return 0;
}
4. 类型转换(掌握)
本节内容基于C++11拓展后的类型转换,在C++11之前使用的是C语言的强制转换(直接使用小括号包裹目标类型转换)。
C++11提供了四种转换函数,适用于不同的场景:
- static_cast(静态转换)
- dynamic_cast(动态转换)
- const_cast(常量转换)
- reinterpret_cast(重解释转换)
4.1 static_cast
主要实现基本数据类型之间的转换,可以提升语法可读性和安全性。
#include <iostream>
using namespace std;
int main()
{
int x = 1;
// int → double
double y = static_cast<double>(x);
cout << y << endl;
return 0;
}
static_cast还可以进行类层次结构的转换——基类和派生类的指针或引用转换:
- 上行转换(派生类指针或引用→基类指针或引用)
static_cast可以安全转换。
- 下行转换(基类指针或引用→派生类指针或引用)
static_cast下行转换不安全。
需要注意的是,本身static_cast的设计不是为了类层次转换,因此不建议。
#include <iostream>
using namespace std;
class Father
{
public:
string a = "Father";
};
class Son:public Father
{
public:
string b = "Son";
};
int main()
{
// 【提示】仅用于展示,开发不建议
// 上行转换
// 1. 指针
Son* s1 = new Son;
Father* f1 = static_cast<Father*>(s1);
cout << s1 << " " << f1 << endl;
cout << f1->a << endl;
// 2. 引用
Son s2;
Father& f2 = static_cast<Father&>(s2);
cout << &s2 << " " << &f2 << endl;
cout << f2.a << endl;
// 下行转换
// 1. 指针
Father* f3 = new Father;
Son* s3 = static_cast<Son*>(f3);
cout << s3 << " " << f3 << endl;
// cout << s3->a << " " << s3->b << endl; // s3->b访问到系统内存了
// 2. 引用
Father f4;
Son& s4 = static_cast<Son&>(f4);
cout << &f4 << " " << &s4 << endl;
// 各有各的错法
cout << s4.a << " " << s4.b << endl; // Father Father
return 0;
}
static_cast还可以间接调用构造函数,需要目标类型含有参数类型的构造函数。
#include <iostream>
using namespace std;
class Student
{
private:
string name;
public:
Student(string name):name(name)
{
cout << "构造函数" << endl;
}
string get_name() const
{
return name;
}
};
int main()
{
string name = "张三";
Student s1 = name; // 隐式调用构造函数
// static_cast也可以间接调用构造函数
Student s2 = static_cast<Student>(name);
cout << s2.get_name() << endl;
return 0;
}
4.2 dynamic_cast
dynamic_cast是专用于类层次转换的函数。
在上行转换时,dynamic_cast与static_cast完全相同。但是在下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
关于下行转换:
情况1:基类没有虚函数,编译不通过
#include <iostream>
using namespace std;
class Father
{
};
class Son:public Father
{
};
int main()
{
Father* f1 = new Father;
// Son* s1 = dynamic_cast<Son*>(f1); 编译出错
return 0;
}
情况2:构成多态,转换成功
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat()
{
cout << "吃东西" << endl;
}
};
class Dog:public Animal // 条件1
{
public:
void eat() // 条件2
{
cout << "吃肉" << endl;
}
void bark()
{
cout << "嘿嘿嘿" << endl;
}
};
int main()
{
Animal* a = new Dog; // 条件3
Dog* d = dynamic_cast<Dog*>(a);
// 支持Dog的所有功能
d->bark();
d->eat();
delete d;
return 0;
}
情况3:不满足多态,且是指针,返回0
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat()
{
cout << "吃东西" << endl;
}
};
class Dog:public Animal // 条件1
{
public:
void eat() // 条件2
{
cout << "吃肉" << endl;
}
void bark()
{
cout << "嘿嘿嘿" << endl;
}
};
int main()
{
Animal* a = new Animal; // 不满足条件3
Dog* d = dynamic_cast<Dog*>(a);
cout << a << " " << d << endl; // 0xfd11d8 0
delete d;
return 0;
}
情况4:不满足多态,且是引用,程序运行终止
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void eat()
{
cout << "吃东西" << endl;
}
};
class Dog:public Animal // 条件1
{
public:
void eat() // 条件2
{
cout << "吃肉" << endl;
}
void bark()
{
cout << "嘿嘿嘿" << endl;
}
};
int main()
{
Animal a; // 不满足条件3
Dog& d = dynamic_cast<Dog&>(a); // 运行终止
cout << "测试" << endl;
return 0;
}
4.3 const_cast
const_cast可以添加或移除对象的const修饰。
在正常情况下,应该尽量避免使用const_cast,而是应该在前期考虑通过良好的接口设计或者其他手段避免const_cast的使用。
#include <iostream>
using namespace std;
class Test
{
public:
string str = "AAA建材老王";
};
int main()
{
const Test* t1 = new Test;
// t1->str = "不吃香菜"; 错误
Test* t2 = const_cast<Test*>(t1);
cout << t1 << " " << t2 << endl; // 0xf611d8 0xf611d8
t2->str = "不吃香菜";
cout << t1->str << endl;
return 0;
}
4.4 reinterpret_cast
reinterpret_cast可以把内存里的值重新解释,这种转换风险极高,慎用!
#include <iostream>
using namespace std;
class Horse
{
public:
string height = "2m";
void speak()
{
cout << "马叫" << endl;
}
void ride()
{
cout << "骑马" << endl;
}
};
class Deer
{
public:
void speak()
{
cout << "哟哟哟" << endl;
}
};
int main()
{
// 不好预计结果
// Deer* d = new Deer;
// Horse* h = reinterpret_cast<Horse*>(d);
// cout << d << " " << h << endl; // 0x10911d8 0x10911d8
// d->speak(); // "哟哟哟"
// h->speak(); // "马叫"
// h->ride(); // "骑马"
// cout << h->height << endl; // 错乱
char c = 'A'; // 8位
char* ptr_c = &c;
// 换int指针指向c
int* ptr_i = reinterpret_cast<int*>(ptr_c); // 32位
cout << *ptr_i << endl;
return 0;
}
5. 抽象类 abstract class(掌握)
5.1 概念
如果基类只表达一些抽象概念,并不与具体的对象相联系,这样的类可以为它的派生类提供一个算法框架,这种基类就是抽象类。
抽象类 ←→ 纯虚函数
- 如果一个类是抽象类,则一定有纯虚函数
- 如果一个类有纯虚函数,则一定是抽象类
纯虚函数是一种特殊的虚函数,纯虚函数没有函数定义,只有函数声明。
抽象类不仅不能创建对象,还不能作为正常的数据类型声明。
#include <iostream>
using namespace std;
/**
* @brief The Shape class 形状类(抽象类)
*/
class Shape
{
public:
virtual void area() = 0; // 纯虚函数的声明
virtual void perimeter() = 0;
};
int main() {
// Shape s; 错误
return 0;
}
5.2 使用
5.2.1 派生类重新实现(reimplement)了基类的所有纯虚函数
这种情况下抽象基类的派生类把基类的所有纯虚函数覆盖,并增加函数体,此时派生类就不再有纯虚函数了,可以作为正常类使用。
#include <iostream>
using namespace std;
/**
* @brief The Shape class 形状类(抽象类)
*/
class Shape
{
public:
virtual void area() = 0; // 纯虚函数的声明
virtual void perimeter() = 0;
};
/**
* @brief The Circle class 圆形类
*/
class Circle:public Shape
{
public:
void area()
{
cout << "πR^2" << endl;
}
void perimeter()
{
cout << "2πR" << endl;
}
};
int main() {
// Shape s; 错误
Circle c;
c.area();
c.perimeter();
return 0;
}
5.2.2 派生类没有重新办实现基类的所有纯虚函数
此时派生类中会有继承的基类的纯虚函数,这个派生类也会变成抽象类,直到它的派生类重新实现剩余的纯虚函数。
#include <iostream>
using namespace std;
/**
* @brief The Shape class 形状类(抽象类)
*/
class Shape
{
public:
virtual void area() = 0; // 纯虚函数的声明
virtual void perimeter() = 0;
};
/**
* @brief The Circle class 圆形类
*/
class Circle:public Shape
{
public:
void area()
{
cout << "πR^2" << endl;
}
void perimeter()
{
cout << "2πR" << 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;
}
5.3 多态
抽象类支持多态,因此:
- 抽象类应该使用虚析构函数
- 抽象类的指针和引用可以被声明
#include <iostream>
using namespace std;
/**
* @brief The Shape class 形状类(抽象类)
*/
class Shape
{
public:
virtual void area() = 0; // 纯虚函数的声明
virtual void perimeter() = 0;
virtual ~Shape(){} // 虚析构函数
};
/**
* @brief The Circle class 圆形类
*/
class Circle:public Shape
{
public:
void area()
{
cout << "πR^2" << endl;
}
void perimeter()
{
cout << "2πR" << 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_shape(Shape* s)
{
s->area();
s->perimeter();
}
void test_shape(Shape& s)
{
s.area();
s.perimeter();
}
int main() {
Circle* c1 = new Circle;
Rectangle* r1 = new Rectangle;
test_shape(c1);
test_shape(r1);
delete c1;
delete r1;
Circle c2;
Rectangle r2;
test_shape(c2);
test_shape(r2);
return 0;
}
5.4 设计模式(了解)
软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。
抽象工厂模式是设计模式中的一种,主要用于处理多个产品族的对象创建问题。
它的核心在于提供一个统一的接口,允许客户端在不指定具体产品类的情况下,创建整个产品族中的各种产品对象。
抽象工厂模式的目的是降低客户端与具体产品创建过程的耦合度(耦合是指两个实体相互依赖于对方的一个量度),确保客户端能够通过相同的接口访问到不同产品族中的产品。
有抽象类键盘,键盘中有一个纯虚函数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()
{
cout << "微软键盘" << endl;
}
};
class AppleKeyboard:public Keyboard
{
void show()
{
cout << "苹果键盘" << endl;
}
};
class MicrosoftMouse:public Mouse
{
void show()
{
cout << "微软鼠标" << endl;
}
};
class AppleMouse:public Mouse
{
void show()
{
cout << "苹果鼠标" << endl;
}
};
class Factory
{
public:
virtual Keyboard* createKeyboard() = 0;
virtual Mouse* createMouse() = 0;
~Factory()
{
}
};
class MicrosoftFactory:public Factory
{
public:
Keyboard* createKeyboard()
{
return new MicrosoftKeyboard;
}
Mouse* createMouse()
{
return new MicrosoftMouse;
}
};
class AppleFactory:public Factory
{
public:
Keyboard* createKeyboard()
{
return new AppleKeyboard;
}
Mouse* createMouse()
{
return new AppleMouse;
}
};
int main() {
// 创建工厂
MicrosoftFactory mf;
AppleFactory af;
// 微软工厂生产产品
Keyboard* mk = mf.createKeyboard();
Mouse* mm = mf.createMouse();
// 苹果工厂生产产品
Keyboard* ak =af.createKeyboard();
Mouse* am =af.createMouse();
// 测试产品功能
mk->show();
mm->show();
ak->show();
am->show();
// 回收产品
delete mk;
delete mm;
delete ak;
delete am;
return 0;
}
