C++模板进阶与继承
模板进阶
模板在之前已经初步介绍过:模板初阶
函数模板回顾
语法
template <typename T, typename T1, typename T2, ...>
返回值类型 函数名(参数列表) {
// 函数体,可以使用 T、T1、T2... 作为类型
}
T、T1、T2等称为模板参数,可以给缺省值
注意:typename可以换成class,但不可以换成struct。
简单实现一个Swap函数模板
template<class T>
void Swap(T& a, T& b)
{
cout << typeid(T).name() << endl; // 打印T的类型,不同平台对类型的表示可能不同。
T tmp = a;
a = b;
b = tmp;
}
int main()
{
int i1 = 10, i2 = 20;
Swap(i1, i2);
cout << i1 << ' ' << i2 << endl << endl;
double d1 = 1.23, d2 = 2.22;
Swap(d1, d2);
cout << d1 << ' ' << d2 << endl;
return 0;
}
VS2022运行结果

g++运行结果

非类型模板参数
上面的代码中,模板参数表示的是数据类型,C++模板参数还可以是数据的值
template<int N> // N是具体的值,是非类型模板参数
void func()
{
cout << N << endl;
// 注意:N不可修改
}
int main()
{
// func(); // err必须实例化
func<10>(); // 输出:10
func<200>();// 输出:200
// 下面的写法是错误的,尖括号里面的必须是常量
// int a = 99;
// func<a>(); // err。可以将a用const修饰,即: const int a = 99;
return 0;
}
非类型模板参数给缺省值
template<int N = 10> // 给缺省值
void func()
{
cout << N << endl;
}
int main()
{
func(); // 输出 10
func<20>() ;// 输出 20
return 0;
}
C++20之前只支持整数类型(int、long、size_t等),C++20之后可以支持double、char等类型
template<double N> // C++20支持
void func()
{
cout << N << endl;
}
int main()
{
func<10.1>();
func<200.2>();
return 0;
}
类模板也可以有非类型模板参数,用法与上面类似,常用的有bitset,这个跟vector<bool>有点类似,以后再讲
typename
先看一段代码:
// 打印容器T里的数据
template<class T>
void Print(const T& v)
{
auto it = v.begin();
while(it != v.end())
{
cout << *it << ' ';
++it;
}
cout << endl;
}
int main()
{
vector<int> v1 = { 1,2,3,4 };
vector<double> v2 = { 1.1,2.2,3.1,4.2 };
Print(v1);
Print(v2);
return 0;
}
但有些人会写成下面那样
template<class T>
void Print(const T& v)
{
T::const_iterator it = v.begin(); // 会编译错误
while(it != v.end())
{
cout << *it << ' ';
++it;
}
cout << endl;
}
上面写法会编译错误,正确写法如下:
template<class T>
void Print(const T& v)
{
typename T::const_iterator it = v.begin(); // 前面加上typename
// T若是个类,可以从里面取静态变量、数据类型,编译器无法确定const_iterator是变量还是数据类型
// 加上typename就表示const_iterator是数据类型。(相当于是给编译器吃个定心丸)
while(it != v.end())
{
cout << *it << ' ';
++it;
}
cout << endl;
}
总结一下:取模板参数里的数据类型要加typename
priority_queue的模板参数也用到了这个:

Container是第二个模板参数,去Container中取value_type类型要加typename
模板的特化
“特殊类型特殊对待”
函数模板特化
template<class T>
void func(T t)
{
cout << "void func(T t)" << endl;
}
// func特化:当T的类型为int时,走下面的函数
template<>
void func<int>(int t) // 函数名后面的尖括号表示模板参数的类型;小括号里面的模板参数也要替换成对应的类型
{
cout << "void func<int>(<int> t)" << endl;
}
int main()
{
func('a');
func(3.1);
func(1);
return 0;
}
用法样例:
// 判断a是否小于b
template<class T>
bool Less(const T& a, const T& b)
{
return a < b;
}
// 但是当T为char*时,按字典序比较大小
template<>
bool Less<char*>(const char*& a, const char*& b)
{
return strcmp(a, b) < 0;
}
int main()
{
cout << Less(1, 2) << endl;
const char* s1 = "ddd";
const char* s2 = "abd";
cout << Less(s1, s2) << endl;
return 0;
}
但上面的代码竟然编译错误!这里面有坑,下面提供一种解决方法:
typedef char* char_ptr; // 将char*类型重定义一下
template<>
bool Less<char_ptr>(const char_ptr& a, const char_ptr& b)
{
return strcmp(a, b) < 0;
}
由于函数模板与同名的普通函数可以同时存在,我为什么不直接写一个普通函数呢?
template<class T>
bool Less(const T& a, const T& b)
{
return a < b;
}
// 普通函数
bool Less(const char* a, const char* b)
{
return strcmp(a, b) < 0;
}
int main()
{
cout << Less(1, 2) << endl;
const char* s1 = "ddd";
const char* s2 = "abd";
cout << Less(s1, s2) << endl; // 调试观察,会调用普通函数
return 0;
}
类模板特化
由于类模板与同名的普通类不能同时存在,类模板的特化就用的上了
template<class T>
class Test
{
T _val;
};
class Test
{
int _val;
};
// 类模板与同名的普通类同时存在会编译错误
int main()
{
Test t;
return 0;
}
template<class T1, class T2>
class Test
{
T1 _v1;
T2 _v2;
public:
Test() { cout << "class Test" << endl; }
};
// 全特化:T1为int、T2为char时,走下面
template<>
class Test<int, char>
{
// ... 这里可以重定义成员变量
public:
Test() { cout << "class Test<int, char> -->全特化" << endl; }
};
// 半特化:T2为int时,走下面
template<class T1>
class Test<T1, int>
{
// ...
public:
Test() { cout << "class Test<T1, int> -->半特化" << endl; }
};
int main()
{
Test<int, char> t1;
Test<int, int> t2;
Test<double, double> t3;
return 0;
}
运行结果

半特化的一些额外用法:
// 半特化:T1、T2都为指针时,走下面
template<class T1, class T2>
class Test<T1*, T2*>
{
// ...
public:
Test() { cout << "class Test<T1*, T2*> -->半特化" << endl; }
};
// 半特化:T1、T2都为引用时,走下面
template<class T1, class T2>
class Test<T1&, T2&>
{
// ...
public:
Test() { cout << "class Test<T1&, T2&> -->半特化" << endl; }
};
// 半特化:T1为vector类型时,走下面
template<class T1, class T2>
class Test<vector<T1> , T2>
{
// ...
public:
Test() { cout << "class Test<vector<T1>, T2> -->半特化" << endl; }
};
int main()
{
Test<int*, char*> t1;
Test<int&, int&> t2;
Test<vector<double>, double> t3;
return 0;
}
运行结果

模板声明和定义分离
template<class T>
class Test
{
// ...
public:
void func(); // 函数声明
};
// 函数定义,需在函数名前指定类域
template<class T>
void Test<T>::func()
{
cout << "void Test<T>::func()" << endl;
}
注意:上面的代码放在同一个文件中
继承
引入
先看一段代码
实现一个外卖平台,主要有三类人:骑手、商家、顾客
// 骑手
class Delivery_person
{
string _name; // 姓名
string _id; // 身份证
string _address; // 地址
long long _telephone; // 手机号
// ...
};
// 商家
class Merchant
{
string _name; // 姓名
string _id; // 身份证
string _address; // 地址
long long _telephone; // 手机号
// ...
};
// 顾客
class Customer
{
string _name; // 姓名
string _id; // 身份证
string _address; // 地址
long long _telephone; // 手机号
// ...
};
我们发现每个类都要写一堆重复的信息,为此C++引入了继承,我们可以定义个公共的类,然后让那三个类继承就行
class Person
{
string _name; // 姓名
string _id; // 身份证
string _address; // 地址
long long _telephone; // 手机号
public:
void setName(const string& name) { _name = name; }
string& getName() { return _name; }
};
// 继承:继承父类的成员(成员函数、成员变量)
// 子类 继承方式 父类
// 外卖小哥 | | |
class Delivery_person : public Person
{
// ...
};
// 子类也叫派生类,父类也叫基类
// 商家
class Merchant : public Person
{
// ...
};
// 顾客
class Customer : public Person
{
// ...
};
int main()
{
Delivery_person d;
d.setName("张三"); // Delivery_person 类没写setName与getName,由于继承的Person里有,这里能调用
cout << d.getName() << endl;
Merchant m;
m.setName("李四");
cout << m.getName() << endl;
Customer c;
c.setName("王五");
cout << c.getName() << endl;
return 0;
}
调试观察

注:父类也称为基类,子类也称为派生类
父类成员的访问权限有三种:private、protected、public
子类继承父类,其继承方式也有三种:private、protected、public
那父类的成员在子类中的访问权限是什么呢?
以public方式继承
1.父类的public成员在子类内部、外部都可访问。(在子类中的仍是public成员)
class A
{
public:
int _val_public = 1;
void _func_public() { cout << "void _func_public()" << endl; }
};
// public继承
class B : public A
{
public:
void func_B()
{
// 子类类内部可以访问父类的public成员
cout << _val_public << endl;
_func_public();
}
};
int main()
{
B b1;
// 子类外部也可以访问父类的public成员
cout << b1._val_public << endl;
b1._func_public();
return 0;
}
2.父类的private成员在子类内部、外部都无法访问。“不可见”
class A
{
private:
int _val_private = 3;
void _func_private() { cout << "void _func_private()" << endl; }
};
// public继承
class B : public A
{
public:
void func_B()
{
cout << _val_private << endl; // 编译报错,A类的private成员变量在B类中无法访问
_func_private(); // 编译报错,A类的private成员函数在B类中无法访问
}
};
int main()
{
B b1;
cout << b1._val_private << endl; //err 在类外也无法访问A类的private成员变量
b1._func_private(); //err 在类外也无法访问A类的private成员函数
return 0;
}
注意:父类private成员在子类中不可见,并不是说子类中压根就没有那个成员。把上面编译报错的代码注释掉,调试发现b1中存在_val_private成员变量(成员函数是公共的,每个对象都能调用)

3.父类的protected成员在子类内部可以访问,子类外部不可访问。(在子类中仍是protected成员)
class A
{
protected:
int _val_protected = 2;
void _func_protected() { cout << "void _func_protected()" << endl; }
};
// public继承
class B : public A
{
public:
void func_B()
{
// 子类类内部可以访问父类的protected成员
cout << _val_protected << endl;
_func_protected();
}
};
int main()
{
B b1;
// 子类外部不可访问父类的protected成员
cout << b1._val_protected << endl; // 编译错误
b1._func_protected(); // 编译错误
return 0;
}
以protected或者private方式继承
这两种方式用的不多,了解即可。下面提供一个表
| public继承 | protected继承 | private继承 | |
|---|---|---|---|
| 父类public成员 | 子类的public成员 | 子类的protected成员 | 子类的private成员 |
| 父类protected成员 | 子类的protected成员 | 子类的protected成员 | 子类的private成员 |
| 父类private成员 | 在子类中不可见 | 在子类中不可见 | 在子类中不可见 |
观察发现:
- 父类private成员,子类无论以什么方式继承,都是不可见的
- 父类的private、protected成员在子类的访问方式 == Min(成员在父类的访问限定符,继承方式),public > protected > private
class默认继承方式是private,struct默认继承方式是public,不过最好显式写出继承方式
class A
{
//...
};
class B : A // 不写继承方式,默认private继承,即class B : private A
{
//...
};
struct A
{
//...
};
struct B : A // 不写继承方式,默认public继承,即struct B : public A
{
//...
};
父类和子类的赋值转换
以外卖系统为例
class Person
{
public: // 为了方便观察,设为public
string _name; // 姓名
string _address; // 地址
long long _telephone; // 手机号
};
// 顾客
class Customer : public Person
{
public:
vector<string> _favorite; // 顾客收藏的美食
};
两个类的成员变量如下图:

1.子类对象可以赋值给父类对象(仅限public继承)
代码段1:
int main()
{
Customer c1;
c1._name = "牛爷爷";
c1._address = "翻斗花园";
c1._telephone = 1234567;
c1._favorite.push_back("西红柿炒鸡蛋");
Person p;
p = c1; // 这里p是Person类型,c1是Customer类型,Person并没有提供Customer的构造,却可以直接赋值
cout << p._name << endl;
cout << p._address << endl;
cout << p._telephone << endl;
return 0;
}
运行结果

这里有个形象的说法叫切片(或者叫切割),可以把子类中的父类部分赋值过去

注意:子类对象能赋值给父类对象;但反过来不行,即:父类对象能不能赋值给子类对象
2.子类对象可以赋值给父类指针/引用(仅限public继承)
代码段2:
int main()
{
Customer c1;
c1._name = "牛爷爷";
c1._address = "翻斗花园";
c1._telephone = 1234567;
c1._favorite.push_back("西红柿炒鸡蛋");
Person& ref = c1; // 子类对象赋值给父类引用。不用加const,中间不产生临时变量
Person* ptr = &c1; // 子类对象赋值给父类指针
ref._name = "胡英俊";
ptr->_telephone = 99999;
return 0;
}
调试观察,发现执行ref._name = "胡英俊";后,c1、ref、ptr的_name内容都变成了胡英俊,执行ptr->_telephone = 99999;后三者的_telephone都变成了99999


多继承的切片
class Base1
{
protected:
int _b1;
};
class Base2
{
protected:
int _b2;
};
class Derive : public Base1, public Base2
{
protected:
int _d;
};
int main()
{
Derive d;
Base1* p1 = &d;
Base2* p2 = &d;
Derive* p3 = &d;
printf("p1: %p\n", p1);
printf("p2: %p\n", p2);
printf("p3: %p\n", p3);
return 0;
}
运行结果

Derive先继承Base1,再继承Base2

继承中的隐藏
引入
int main()
{
int a = 10;
{
int a = 20; // 这里的a在内部花括号里,跟上面那个a重名。两个a在不同的作用域,可以同时存在
cout << a << endl; // 编译器会现在当前作用域查找(就近原则)。输出20
// 那我该如何在内部花括号里访问第一个a呢?好像不行,第一个a被隐藏了!
}
return 0;
}
与上面道理类似,子类与父类有同名成员,父类的成员会被隐藏起来
成员变量隐藏
class Person
{
public: // 为了方便观察,设为public
string _name; // 姓名
string _address; // 地址
long long _telephone; // 手机号
};
// 顾客
class Customer : public Person
{
public:
string _name; // 子类可以与父类有同名成员
// 子类中的_name属于Customer类域,父类中的_name属于Person类域
void Print_name()
{
cout << _name << endl; // 在Customer类中访问_name,会现在当前类域查找
cout << Person::_name << endl; // 若想访问父类的_name, 需指定作用域
}
};
int main()
{
Customer c1;
c1._name = "胡图图"; // 编译器会优先在Customer中找_name(就近原则)
cout << c1._name << endl;;
c1.Person::_name = "xxx"; // 若想访问父类中的_name,需要指定作用域
cout << c1.Person::_name << endl;
return 0;
}
调试观察

成员函数隐藏
class A
{
public:
int func(int a = 10)
{
cout << "A::func(int a = 10)" << endl;
return a;
}
};
class B : public A
{
public:
// 只要函数名一样就构成隐藏
int func() // 注意这里不是重载,两个func的作用域不同。B类的func在B类的作用域,A类的func在A类的作用域
{
cout << "B::func()" << endl;
return 0;
}
// 类内调用func
void call_func()
{
func(); // 调用B类的func
A::func(); // 调用A类的func
}
};
int main()
{
B b1;
b1.call_func();
cout << endl;
// 类外调用func
b1.func(); // 调用B类的func
b1.A::func(); // 调用A类的func
}
运行结果

继承中的四个重要的默认成员函数
这点与之前讲的类和对象的默认成员函数类似
子类的构造函数
没引入继承之前,类的成员变量分为内置类型与自定义类型。内置类型不做处理,自定义类型处理流程如下:

class A
{
int _a;
public:
A() { cout << "A()" << endl; }
A(int x)
:_a(x)
{ cout << "A(int x)" << endl; }
};
class B
{
int _b;
A _aa;
public:
B() { cout << "B()" << endl; }
// 若不写构造,编译器会生成构造函数:B() {}
// 上面的构造函数,初始化列表并没有显示调用_aa的构造函数,则会调用_aa的默认构造
};
int main()
{
B b1;
return 0;
}
运行结果

若在B类构造函数改一下:
在其构造函数的初始化列表中显示调用_aa的构造,则就按显式调用的那个
class A
{
int _a;
public:
A() { cout << "A()" << endl; }
A(int x)
:_a(x)
{ cout << "A(int x)" << endl; }
};
class B
{
int _b;
A _aa;
public:
B()
:_aa(5) // 显示调用构造
{ cout << "B()" << endl; }
};
int main()
{
B b1;
return 0;
}
运行结果

现在加入继承,子类的成员变量又多了一个:父类成员(整体)
子类的成员变量如下:
- 父类成员
- 自定义类型
- 内置类型
子类的构造函数对内置类型、自定义类型的处理规则与之前相同,对父类成员的处理流程如下:

这点与自定义类型的处理规则类似
代码1:子类不显式调用调用父类构造,而且父类有默认构造
class A
{
protected:
int _a;
public:
A() { cout << "A()" << endl; }
A(int x)
:_a(x)
{ cout << "A(int x)" << endl; }
};
class B : public A
{
protected:
int _x;
string _s;
public:
B() // 初始化列表中,没有调用父类的构造函数,会调用父类的默认构造
{ cout << "B()" << endl; }
// 补充:B的构造函数的初始化列表中也没有调用自定义类型_s的构造,则会调用string的默认构造
// 若不写B的构造,编译器会生成:B() {}
};
int main()
{
B b1;
return 0;
}
运行结果

代码2:子类不显式调用调用父类构造,而且父类没有默认构造
class A
{
protected:
int _a;
public:
// A类没有默认构造
A(int x)
:_a(x)
{ cout << "A(int x)" << endl; }
};
class B : public A
{
protected:
int _x;
string _s;
public:
B() // 初始化列表中,没有调用父类的构造函数,会调用父类的默认构造。但是父类没有默认构造,会编译报错
{ cout << "B()" << endl; }
};
代码3:显示调用父类构造
class A
{
protected:
int _a;
public:
A() { cout << "A()" << endl; }
A(int x)
:_a(x)
{ cout << "A(int x)" << endl; }
};
class B : public A
{
protected:
int _x;
string _s;
public:
B()
:A(10) // 在子类B的构造函数的初始化列表中显示调用父类A的构造,那就按显示调用的
{ cout << "B()" << endl; }
// 注意不要写成下面的:
// B()
// :_a(10) // 不能直接初始化父类的成员_a。必须用父类的构造函数初始化父类成员!
// { cout << "B()" << endl; }
};
int main()
{
B b1;
return 0;
}
运行结果

注:子类的第一个成员变量是隐含的父类成员,初始化列表会先初始化父类成员。(与this有点类似:成员函数的第一个参数是隐含的this指针)
class A
{
protected:
int _a;
public:
A() { cout << "A()" << endl; }
A(int x)
:_a(x)
{ cout << "A(int x)" << endl; }
};
class C
{
int _c;
public:
C(int c)
{
_c = c;
cout << "C(int c)" << endl;
}
};
// B继承A
class B : public A
{
protected:
int _x;
C _cc; // B还有C类型的成员变量
public:
B()
:_cc(55) // 初始化列表顺序是按声明的顺序来的,第一个成员变量父类成员。即使这里先写_cc的初始化,也不会第一个初始化_cc
,A(10)
{ cout << "B()" << endl; }
};
int main()
{
B b1;
return 0;
}
运行结果

子类的析构函数
子类不写析构函数,编译器会生成一个。编译器默认生成的析构函数处理如下:
- 内置类型:不处理
- 自定义类型:调用其析构
- 父类成员:调用父类的析构
class A
{
protected:
int _a;
public:
~A() { cout << "~A()" << endl; }
};
class C
{
int _c;
public:
~C() { cout << "~C()" << endl; }
};
// B继承A
class B : public A
{
protected:
C _cc; // B还有C类型的成员变量
int _x;
};
int main()
{
B b1;
return 0;
}
运行结果

每个成员变量析构的顺序:按声明顺序的逆序。这点刚好与构造函数初始化列表的顺序相反
自己实现析构函数:
class B : public A
{
protected:
C _cc; // B还有C类型的成员变量
int _x;
public:
~B()
{
// 只需清理自定义类型(指向动态内存的指针类型)。B类的内置类型是int _x,无需清理资源
// 对于父类成员与自定义类型成员,无需显式调用其析构
// 编译器执行完~B()函数调用后,会自动调用_cc、父类成员的析构
}
};
int main()
{
B b1;
return 0;
}
运行结果

当然也可以显式调用析构,但这样可能会程序崩溃
// B继承A
class B : public A
{
protected:
C _cc; // B还有C类型的成员变量
int _x;
public:
~B()
{
A::~A(); // 显示调用父类的析构,但需要指定父类的类域。原理后面的多态会讲
_cc.~C(); // 自定义类型的对象显式调用其析构
// 但是~B()调用完,还会自动调用_cc与父类成员的析构。这样就会析构两次
}
};
int main()
{
B b1;
return 0;
}
运行结果

子类的拷贝构造
子类不写拷贝构造,编译器会生成一个。编译器默认生成的拷贝构造处理如下:
- 父类成员:调用父类的拷贝构造
- 自定义类型:调用其拷贝构造
- 内置类型:值拷贝
class A
{
protected:
int _a;
public:
A() {}
A(const A& a) { cout << "A(const A& a)" << endl; }
};
class C
{
int _c;
public:
C(){}
C(const C& c) { cout << "C(const C& c)" << endl; }
};
// B继承A
class B : public A
{
protected:
int _x;
C _cc; // B还有C类型的成员变量
public:
// 没写拷贝构造,编译器会生成一个
// 父类成员调用父类的拷贝构造;_cc是自定义类型成员,调用C类的拷贝构造
};
int main()
{
B b1;
B b2(b1);
return 0;
}
运行结果

模拟编译器生成的拷贝构造:
A类和C类的定义同上
// B继承A
class B : public A
{
protected:
int _x;
C _cc; // B还有C类型的成员变量
public:
B() {}
B(const B& b)
:A(b) // 调用父类拷贝构造。这里是切片,子类对象传给父类引用
,_cc(b._cc) // 调用自定义类型拷贝构造
,_x(b._x) // 值拷贝
{}
};
子类的赋值重载
与拷贝构造类似。子类不写赋值重载,编译器会生成一个。编译器默认生成的赋值重载处理如下:
- 父类成员:调用父类的赋值重载
- 自定义类型:调用其赋值重载
- 内置类型:直接赋值
class A
{
protected:
int _a;
public:
A& operator= (const A& a)
{
cout << "A& operator= (const A& a)" << endl;
return *this;
}
};
class C
{
int _c;
public:
C& operator= (const C& c)
{
cout << "C& operator= (const C& c)" << endl;
return *this;
}
};
// B继承A
class B : public A
{
protected:
int _x;
C _cc; // B还有C类型的成员变量
};
int main()
{
B b1;
B b2;
b2 = b1; // 赋值重载
return 0;
}
运行结果

模拟拷贝构造生成的赋值重载:
class B : public A
{
protected:
int _x;
C _cc; // B还有C类型的成员变量
public:
B& operator= (const B& b)
{
A::operator=(b); // 子类的赋值重载与父类的赋值重载同名,父类会被隐藏,需指定类域
_cc = b._cc; // 调用C类的赋值重载
_x = b._x;
return *this;
}
};
继承与友元
友元关系不能继承
class B; // 由于会在B类定义前用到B类,这里先声明一下
class A
{
protected:
int _a;
public:
// A类把Print当朋友
friend void Print(const A& aa, const B& bb);
};
// B继承A
class B : public A
{
protected:
int _b;
};
void Print(const A& aa, const B& bb)
{
cout << aa._a << endl; // A类把Print当朋友,Print可以访问A类private、protected成员
// cout << bb._b << endl; // err,B类没把Print当朋友,Print无法访问B类private、protected成员
}
int main()
{
A aa1;
B bb1;
Print(aa1, bb1);
return 0;
}
继承与静态成员
静态成员会被继承,静态成员会被当前类及其子类所共用
class Person
{
protected:
string _name;
public:
static int _count;
};
int Person::_count = 0; // 类内的静态成员需在类外定义
class Student : public Person
{
protected:
int id;
};
int main()
{
Person p1;
cout << p1._count << endl; // 用对象访问静态成员变量
cout << Person::_count << endl; // 用类域访问静态成员变量
Student s1;
s1._count = 999; // 子类对象也可以访问到父类的静态成员变量
cout << s1._count << endl;
cout << Student::_count << endl; // 也可以用子类的类域访问
cout << &Person::_count << endl;
cout << &Student::_count << endl;
return 0;
}
运行结果

多继承
这部分内容了解即可
多继承:一个类继承多个类
class A
{
protected:
int _a;
};
class B
{
protected:
int _b;
};
// C类继承了A类和B类
class C : public A, public B
{
protected:
int _c;
public:
void Print()
{
_a = _c = _b = 888; // 可以访问父类成员
cout << _a << ' ' << _b << ' ' << _c << endl;
}
};
int main()
{
C cc;
cc.Print();
return 0;
}
菱形继承
// 植物
class Plant
{
public: // 方便后文调试,这里设为public
string _name; // 植物名
};
// 蔬菜
class Vegetable : public Plant
{
protected:
double _price; // 单价
};
// 水果
class Fruit : public Plant
{
protected:
int _pick_date; // 采摘日期
};
// 西红柿, 继承了蔬菜类与水果类
class Tomato : public Vegetable, public Fruit
{
protected:
int _color; // 颜色
};
继承关系如下

int main()
{
Tomato t; // t里面会有两份_name,一份来自Vegetable,一份来自Fruit
// t._name = "xxx"; // 不能直接访问
t.Vegetable::_name = "山东西红柿1"; // 需要指定类域
t.Fruit::_name = "山东西红柿2";
return 0;
}
这样的话会产生数据冗余与二义性。为了解决该问题,引入了虚继承(谁会产生数据冗余,那继承它的时候就要加上virtual)
class Plant
{
public: // 方便调试,这里设为public
string _name; // 植物名
};
// Vegetable与Fruit有冗余数据,在其继承那里加上virtual
class Vegetable : virtual public Plant
{
protected:
double _price; // 单价
};
// 水果 // 加上virtual
class Fruit : virtual public Plant
{
protected:
int _pick_date; // 采摘日期
};
// 西红柿, 继承了蔬菜类与水果类
class Tomato : public Vegetable, public Fruit
{
protected:
int _color; // 颜色
};
int main()
{
Tomato t; // t里面只会有一份_name
t._name = "xxx"; // 可以直接访问
t.Vegetable::_name = "山东西红柿"; // 也可以用类域访问
t.Fruit::_name = "河南西红柿";
return 0;
}
调试观察

更多推荐


所有评论(0)