模板进阶

模板在之前已经初步介绍过:模板初阶

函数模板回顾

语法

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

调试观察
在这里插入图片描述

注:父类也称为基类,子类也称为派生类

父类成员的访问权限有三种:privateprotectedpublic
子类继承父类,其继承方式也有三种:privateprotectedpublic
那父类的成员在子类中的访问权限是什么呢?

以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成员在子类中不可见在子类中不可见在子类中不可见

观察发现:

  1. 父类private成员,子类无论以什么方式继承,都是不可见的
  2. 父类的private、protected成员在子类的访问方式 == Min(成员在父类的访问限定符,继承方式),public > protected > private

class默认继承方式是privatestruct默认继承方式是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. 父类成员
  2. 自定义类型
  3. 内置类型

子类的构造函数对内置类型、自定义类型的处理规则与之前相同,对父类成员的处理流程如下:
在这里插入图片描述
这点与自定义类型的处理规则类似


代码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;
}

运行结果
在这里插入图片描述

子类的析构函数

子类不写析构函数,编译器会生成一个。编译器默认生成的析构函数处理如下:

  1. 内置类型:不处理
  2. 自定义类型:调用其析构
  3. 父类成员:调用父类的析构
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;
}

运行结果
在这里插入图片描述

子类的拷贝构造

子类不写拷贝构造,编译器会生成一个。编译器默认生成的拷贝构造处理如下:

  1. 父类成员:调用父类的拷贝构造
  2. 自定义类型:调用其拷贝构造
  3. 内置类型:值拷贝
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)	// 值拷贝
    {}
};

子类的赋值重载

与拷贝构造类似。子类不写赋值重载,编译器会生成一个。编译器默认生成的赋值重载处理如下:

  1. 父类成员:调用父类的赋值重载
  2. 自定义类型:调用其赋值重载
  3. 内置类型:直接赋值
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;
}

调试观察
在这里插入图片描述

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐