C++学习笔记(八)

二十一.继承(Inheritance)

1.继承的概念(了解)

通过一种机制表达类型之间共性和特性方式,利用已有的数据类型定义的新的数据类型,这种机制就是继承。
eg:
在这里插入图片描述
基类(父类)–派生–>子类(派生类)
子类(派生类)–继承–>基类(父类)

2.继承的语法

class 子类:继承方式 基类1,继承方式 基类2...{
		...
};
继承方式:
1)公有继承 public
2)保护继承 protected(保护成员:可以在类的内部和子类中访问) 
3)私有继承 private

eg.

#include <iostream>
using namespace std;
class Human{//人类(基类)
public:
    Human(const string& name,int age):m_name(name),m_age(age){}

    void eat(const string& food){
        cout << "我在吃" << food << endl;
    }

    void sleep(int hour){
        cout << "我睡了" << hour <<"小时"<< endl;
    }
protected:
    //保护成员:可以在类的内部和子类中访问
    string m_name;
    int m_age;
};
//学生类(人类派生的子类)
class Student:public Human{
public:
    //Human(..):显式指明基类部分的初始化方式
    Student(const string& name,int age,int no):Human(name,age),m_no(no){}
    void who(void){
        cout << "我叫" << m_name << ",今年" << m_age << "岁,学号是" << m_no << endl;
    }
    void learn(const string& course){
        cout << "我在学" << course << endl;
    }
private:
    int m_no;
};
//教师类(人类派生的子类)
class Teacher:public Human{
public:
    Teacher(const string& name,int age,int sal):Human(name,age),m_sal(sal){}
    void who(void){
        cout << "我叫" << m_name << ",今年" << m_age << ",工资为" << m_sal << endl;
    }
    void teach(const string& course){
        cout << "我在讲" << course << endl;
    }
private:
    int m_sal;
};
int main(void)
{
    Student s("张三",30,10011);
    s.who();
    s.eat("面包");
    s.sleep(8);
    s.learn("C++课程");
    Teacher t("小明",35,30000);
    t.who();
    t.eat("桃子");
    t.sleep(6);
    t.teach("unix系统编程");

    return 0;
}

3.公有继承的特性(public)

3.1 特性

可以把一个子类对象看做是基类对象

子类对象会继承基类的属性和行为,通过子类对象可以访问到基类的成员,如同是基类对象在访问它们一样。
(注:子类对象中包含的基类部分被称为"基类子对象")

3.2 向上造型(upcast)(重点掌握)

在这里插入图片描述
向上造型:子类指针/引用 —》基类指针/引用

将子类类型指针或引用转换为基类类型的指针或引用;这种操作性缩小的类型转换,在编译器看来是安全的,可以直接隐式转换。
基 类

子 类
eg.

	class A{};
	class B:public A{};
	class C:public A{};
	...
	
	void func1(A* pa){}
	void func2(A& ra){}
	int main(void)
	{
		B b;
		func1(&b);//向上造型
		func2(b);//向上造型
		C c;
		func1(&c);//向上造型
		func2(c);//向上造型
	}

eg.

#include <iostream>
using namespace std;
class Human{//人类(基类)
public:
    Human(const string& name,int age):m_name(name),m_age(age){}

    void eat(const string& food){
        cout << "我在吃" << food << endl;
    }

    void sleep(int hour){
        cout << "我睡了" << hour <<"小时"<< endl;
    }
protected:
    //保护成员:可以在类的内部和子类中访问
    string m_name;
    int m_age;
};
//学生类(人类派生的子类)
class Student:public Human{
public:
    //Human(..):显式指明基类部分的初始化方式
    Student(const string& name,int age,int no):Human(name,age),m_no(no){}
    void who(void){
        cout << "我叫" << m_name << ",今年" << m_age << "岁,学号是" << m_no << endl;
    }
    void learn(const string& course){
        cout << "我在学" << course << endl;
    }
private:
    int m_no;
};
//教师类(人类派生的子类)
class Teacher:public Human{
public:
    Teacher(const string& name,int age,int sal):Human(name,age),m_sal(sal){}
    void who(void){
        cout << "我叫" << m_name << ",今年" << m_age << ",工资为" << m_sal << endl;
    }
    void teach(const string& course){
        cout << "我在讲" << course << endl;
    }
private:
    int m_sal;
};
int main(void){
    Student s("张三",30,10011);
    s.who();
    s.eat("面包");
    s.sleep(8);
    s.learn("C++课程");
    Teacher t("小明",35,30000);
    t.who();
    t.eat("桃子");
    t.sleep(6);
    t.teach("unix系统编程");

    //Student*->Human*:向上造型
    Human* ph = &s;
    ph->eat("面包");
    ph->sleep(8);
    //子类继承基类,子类可以访问基类的成员函数,但基类无法访问子类的成员函数
    //ph->who(); error

    return 0;
}

3.3 向下造型(downcast)

在这里插入图片描述
向下造型:基类指针/引用 —》子类指针/引用

将基类类型的指针或引用转换为子类类型指针或引用;这种操作性放大的类型转换,在编译器看来是危险的,不能直接隐式转换,但可以显式转转(推荐使用static_cast)。(PS:这种错误编译器发现不了,全靠程序员自己判断,所以使用较少)
基 类

子 类
eg.

#include <iostream>
using namespace std;
class Human{//人类(基类)
public:
    Human(const string& name,int age):m_name(name),m_age(age){}

    void eat(const string& food){
        cout << "我在吃" << food << endl;
    }

    void sleep(int hour){
        cout << "我睡了" << hour <<"小时"<< endl;
    }
protected:
    //保护成员:可以在类的内部和子类中访问
    string m_name;
    int m_age;
};
//学生类(人类派生的子类)
class Student:public Human{
public:
    //Human(..):显式指明基类部分的初始化方式
    Student(const string& name,int age,int no):Human(name,age),m_no(no){}
    void who(void){
        cout << "我叫" << m_name << ",今年" << m_age << "岁,学号是" << m_no << endl;
    }
    void learn(const string& course){
        cout << "我在学" << course << endl;
    }
private:
    int m_no;
};
//教师类(人类派生的子类)
class Teacher:public Human{
public:
    Teacher(const string& name,int age,int sal):Human(name,age),m_sal(sal){}
    void who(void){
        cout << "我叫" << m_name << ",今年" << m_age << ",工资为" << m_sal << endl;
    }
    void teach(const string& course){
        cout << "我在讲" << course << endl;
    }
private:
    int m_sal;
};
int main(void){
    Student s("张三",30,10011);
    s.who();
    s.eat("面包");
    s.sleep(8);
    s.learn("C++课程");
    Teacher t("小明",35,30000);
    t.who();
    t.eat("桃子");
    t.sleep(6);
    t.teach("unix系统编程");

    //Student*->Human*:向上造型
    Human* ph = &s;
    ph->eat("面包");
    ph->sleep(8);
    //子类继承基类,子类可以访问基类的成员函数,但基类无法访问子类的成员函数
    //ph->who(); error

    //Human*->Student*:向下造型(合理),安全
    Student* ps = static_cast<Student*>(ph); //将ph指针强转为Student类型
    ps->who();

    Human h("赵云",25);
    //Human*->Student*:向下造型(不合理),危险
    Student* ps2 = static_cast<Student*>(&h);
    ps2->who(); //会打印异常值

    return 0;
}

3.4 子类继承基类的成员

子类继承基类的成员:公有、保护、私有

–》在子类中,可以直接访问基类中的公有和保护成员,就如同它们是子类自己的成员一样。
–》基类的私有成员,子类也可以继承,但是会受到访问控制属性的影响,无法直接使用;如果需要在子类中访问到基类中的私有成员,可以让基类提供公有的或保护的成员函数,来间接访问。
eg.

#include <iostream>
using namespace std;

class Base 
{
public:
    Base(void) :m_public(100),m_protected(200),m_private(300) {}
public:
    int m_public;
protected:
    int m_protected;
private:
    int m_private;

    //如果需要在子类中访问到基类中的私有成员,可以让基类提供公有的或保护的成员函数,来间接访问
protected:
    const int& getPri(void) {
        return m_private;
    }
};

class Derived :public Base {
public:
    void func(void) {
        cout << m_public << endl;
        cout << m_protected << endl;
        //cout << m_private << endl; 编译报错
        cout << getPri() << endl;
    }
};
int main(void) {
    Derived d;
    cout << "sizeof(d):" << sizeof(d) << endl;
    //sizeof(d):12,说明基类的私有成员,子类也可以继承,3个int类型,3*4=12个字节
    d.func(); //100  200  300
    return 0;
}

3.5 子类隐藏基类的成员

子类隐藏基类的成员:解决 —>“类名::”,using声明

①如果子类和基类中有同名的成员函数,因为作用域不同,不能构成重载关系,而是一种隐藏关系,通过子类对象将优先访问子类自己的成员;这时如果还需要访问基类中被隐藏的成员,可以借助作用域限定操作符 “类名::”显式指明 推荐

②如果形成隐藏关系的成员函数参数有所区别,也可以通过using声明,将基类中的成员函数引入到子类中做鱼,让它们形成重载,通过函数重载解析来解决。

#include <iostream>
using namespace std;
class Base
{
public:
    void func(void){
        cout << "基类的func(void)" << endl;
    }
};
class Derived:public Base
{
public:
    void func(int i){
        cout << "子类的func(int)" << endl;
    }
    //将基类中的func引入到当前子类作用域,让其和子类中版本形成重载
    //using Base::func;
};

int main(void)
{
    Derived d;
    d.Base::func();
    d.func(123);

    return 0;
}

4.访问控制属性和继承方式

4.1 访问控制属性:影响访问该类成员的位置

访问控制限定符访问控制属性内部访问子类访问外部访问友元访问
public公有成员okokokok
protected保护成员okoknook
private私有成员oknonook

4.2 继承方式:影响通过子类访问基类中成员的可访问性

基类中的在公有继承子类中在保护继承子类中在私有继承子类中
公有成员公有成员保护成员私有成员
保护成员保护成员保护成员私有成员
私有成员私有成员私有成员私有成员

eg.上面两个表要对照着看,举例在该程序中

#include <iostream>
using namespace std;
class A{//基类
public:
    int m_public;
protected:
    int m_protected;
private:
    int m_private;
};
class B:public A{//公有继承的子类
};
class C:protected A{//保护继承的子类
};
class D:private A{//私有继承的子类
};
class X:public B{
public:
    void func(void){
        m_public = 123;//ok
        m_protected = 123;//ok
        //m_private = 123;//no
    }
};
class Y:public C{
public:
    void func(void){
        m_public = 123;//ok
        m_protected = 123;//ok
        //m_private = 123;//no
    }
};
class Z:public D{
public:
    void func(void){
        //m_public = 123;//no
        //m_protected = 123;//no
        //m_private = 123;//no
    }
};
int main(void){
    B b;
    b.m_public = 123;//ok
    //b.m_protected = 123;//no,外部访问保护成员
    //b.m_private = 123;//no,外部访问私有成员
    
    C c;
    //c.m_public = 123;//no,在保护继承子类中,基类中的公有成员变成了保护成员,保护成员在外部不能访问
    //c.m_protected = 123;//no
    //c.m_private = 123;//no

    D d; //在私有继承子类中,基类中的所有成员都变成了私有成员,私有成员在外部均不能访问 
    //d.m_public = 123;
    //d.m_protected = 123;
    //d.m_private = 123;

    return 0;
}

(注:向上造型语法特性在保护和私有继承中不再适用)

eg:
		class _A{
		public:
			void func(void){...}
		};
		class A:private _A{//ok,在A内部可直接访问
		};
		class B:public A{}; //error
		class C:public B{}; //error
		class D:public C{}; //error
		...

eg.

#include <iostream>
using namespace std;
class Base{
public:
    int m_public;
};
class Derived:protected /*private*/ Base{};
int main(void)
{
    Derived d;
    //向上造型语法特性在保护和私有继承中不再适用
    //Base* pb = &d;//向上造型,error
    Base* pb = static_cast<Base*>(&d);//error
    return 0;
}

5.子类的构造函数

①如果子类的构造函数没有显式指明基类子对象的初始化方式,那么编译器将会自动调用基类的无参构造函数来初始化基类子对象。
②如果希望以有参的方式初始化基类子对象,则必须使用初始化列表显式指明基类子对象需要的构造实参。

5.1 子类对象创建过程

–》分配内存
–》构造基类子对象(按继承表从左到右顺序) class 子类:继承方式 基类1,继承方式 基类2…
–》构造成员子对象(按声明自上而下的顺序)
–》执行子类的构造函数代码

#include <iostream>
using namespace std;
class Member{
public:
    Member(void):m_j(0){
        cout << "Member(void)" << endl;
    }
    Member(int j):m_j(j){
        cout << "Member(int)" << endl;
    }
    int m_j;
};
class Base{
public:
    Base(void):m_i(0){
        cout << "Base(void)" << endl; //基类无参构造函数
    }
    Base(int i):m_i(i){
        cout << "Base(int)" << endl; //基类有参构造函数
    }
    int m_i;
};
class Derived:public Base{
public:
    Derived(void){
        cout << "Derived(void)" << endl; //子类无参构造函数
    }
    //Base(i):指明基类子对象的初始化方式
    //m_mem(i):指明成员子对象的初始化方式
    Derived(int i):Base(i),m_mem(i){
        cout << "Derived(int)" << endl; //子类有参构造函数
    }
    Member m_mem;//成员子对象
};
int main(void){
    Derived d;
    cout << d.m_i << ',' << d.m_mem.m_j << endl;
    Derived d2(123);
    cout << d2.m_i << ','<< d2.m_mem.m_j << endl;
    return 0;
}

//输出:
//Base(void) 基类子对象
//Member(void) 成员子对象
//Derived(void) 子类构造函数代码
//0,0
//Base(int)
//Member(int)
//Derived(int)
//123,123

//子类无论是无参还是有参的,都是先把基类的子对象创建出来,然后再执行子类的构造函数代码

6.子类析构函数

①子类的析构函数,无论自定义的还是缺省提供的,都会自动调用基类的析构函数,完成基类子对象销毁。

6.1 子类对象的销毁过程

–》执行子类的析构函数代码
–》析构成员子对象(按声明逆序)
–》析构基类子对象(按继承表逆序)
–》释放内存

③基类的析构函数不能调用子类的析构函数,所以delete一个指向子类对象的基类指针,实际被执行的仅是基类的析构函数,子类的析构函数执行不到,有内存泄漏风险。

class A{};
class B:public A{};
A* pa = new B;//pa:指向子类对象的基类指针
delete pa;//仅执行基类的析构函数,有内存泄漏风险.
	
解决:虚析构函数

eg.

#include <iostream>
using namespace std;
//普通类
class Member {
public:
    Member(void) :m_j(0) {
        cout << "Member(void)" << endl;
    }
    Member(int j) :m_j(j) {
        cout << "Member(int)" << endl;
    }
    ~Member(void) {
        cout << "~Member(void)" << endl;
    }
    int m_j;
};
//基类
class Base {
public:
    Base(void) :m_i(0) {
        cout << "Base(void)" << endl;
    }
    Base(int i) :m_i(i) {
        cout << "Base(int)" << endl;
    }
    ~Base(void) {
        cout << "~Base(void)" << endl;
    }
    int m_i;
};
//子类
class Derived :public Base {
public:
    Derived(void) {
        cout << "Derived(void)" << endl;
    }
    //Base(i):指明基类子对象的初始化方式
    //m_mem(i):指明成员子对象的初始化方式
    Derived(int i) :Base(i), m_mem(i) {
        cout << "Derived(int)" << endl;
    }
    ~Derived(void) {
        cout << "~Derived(void)" << endl;
    }
    Member m_mem;//成员子对象
};
int main(void) {
    /*Derived d;
    cout << d.m_i << ',' << d.m_mem.m_j << endl;
    Derived d2(123);
    cout << d2.m_i << ','<< d2.m_mem.m_j << endl;
    */
    Base* pb = new Derived;
    delete pb;//内存泄露风险

    return 0;
}

//输出:
//Base(void)
//Member(void)
//Derived(void)
//~Base(void)
//实际被执行的仅是基类的析构函数,子类的析构函数执行不到

7.子类的拷贝构造和拷贝赋值

7.1 子类的拷贝构造

①如果子类没有自己定义拷贝构造函数,那么编译器会为子类提供缺省的拷贝构造函数,该函数会自动调用基类的拷贝构造函数,完成基类子对象的拷贝初始化。
②如果子类自己定义了拷贝构造函数,那么编译器就不再为子类提供缺省的拷贝构造函数,这时需要使用初始化列表,显式指明基类子对象也要以拷贝的进行初始化。
格式:

class Base{};
class Derived:public Base{
		//Base(that):显式指明基类子对象以拷贝的进行初始化
		Derived(const Derived& that):Base(that),...{}
};

7.2 子类的拷贝赋值

①如果子类没有定义拷贝赋值操作符函数,那么编译器会为子类提供缺省拷贝赋值函数,该函数会自动调用基类的拷贝赋值函数,完成基类子对象的赋值。
②如果子类自己定义了拷贝赋值函数,那么编译器不再为子类提供缺省拷贝赋值函数,这时需要显式调用基类的拷贝赋值函数,完成基类子对象的赋值。
格式:

class Base{};
class Derived:public Base{
		Derived& operator=(const Derived& that){
			if(&that != this){
				//显式调用基类的拷贝赋值函数
				Base::operator=(that);
				...
			}
			return *this;
		}
};

eg.

#include <iostream>
using namespace std;
class Base{
public:
    Base(int i=0):m_i(i){}
    Base(const Base& that):m_i(that.m_i){
        cout << "基类的拷贝构造" << endl;
    }
    Base& operator=(const Base& that){
        cout << "基类的拷贝赋值" << endl;
        if(&that != this){
            m_i = that.m_i;
        }
        return *this;
    }
    int m_i;
};
class Derived:public Base{
public:
    Derived(int i=0,int j=0):Base(i),m_j(j){}

    //Base(that):指明基类子对象以拷贝方式初始化
    Derived(const Derived& that)
        :m_j(that.m_j),Base(that){}

    Derived& operator=(const Derived& that){
        if(&that != this){
            //显式调用基类的拷贝赋值函数,完成基类
            //子对象的复制操作.
            Base::operator=(that);
            m_j = that.m_j;
        }
        return *this;
    }
    int m_j;
};
int main(void){
    Derived d1(123,321);
    cout << d1.m_i << "," << d1.m_j << endl;
    Derived d2(d1);//拷贝构造
    cout << d2.m_i << "," << d2.m_j << endl;
    Derived d3;
    d3 = d1;//拷贝赋值,d3.operator=(d1)
    cout << d3.m_i << "," << d3.m_j << endl;

    return 0;
}

8.子类的操作符重载(了解)

eg.

#include <iostream>
using namespace std;
class Human{//人类(基类)
public:
    Human(const string& name,int age):m_name(name),m_age(age){}
    friend ostream& operator<<(
            ostream& os,const Human& h){
        os << h.m_name << "," << h.m_age;
        return os;
    }
private:
    string m_name;
    int m_age;
};
class Student:public Human{//子类
public:
    Student(const string& name,int age,int no)
        :Human(name,age),m_no(no){}
    friend ostream& operator<<(
            ostream& os,const Student& s){
        //在实现子类的操作符重载函数时,可以复用基类针对该操作符重载定义,完成部分工作
        os << (Human&)s << "," << s.m_no;
        return os;
    }
private:
    int m_no;
};
int main(void){
    Student s("关羽",30,10011);
    cout << s << endl;//operator<<(cout,s)
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

boss-dog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值