C++学习之继承(一)

  1. 继承的概念
    通过一种机制表达类型之间共性和特性方式,利用已有的数据类型定义的新的数据类型,这种机制就是继承。
    子类(派生类)继承父类(基类)

  2. 继承的语法

    class 子类:继承方式 基类1, 继承方式 基类2... ...{
    		...
    };
    

    继承方式:
    1)公有继承public
    2)保护继承 protected
    3)私有继承 private

  3. 公有继承的特性(public)
    1)子类对象会继承基类的属性和行为,通过子类对象可以访问到基类的成员,如同是基类对象在访问它们一样。
    注:子类对象中包含的基类部分被称为“基类子对象”
     
    2)向上造型(upcast)
    将子类类型的指针或引用转换为基类类型的指针或引用:这种操作性缩小的类型转换,在编译器看来是安全的,可以直接隐式转换。
    本质: 改变指针的值来改变访问时的起始地址(这个起始地址一定是在派生类对象多跨地址范围之内),同时通过其他机制,控制该指针的访问长度,即访问范围。
     
    3)向下造型(downcast)
    将基类类型的指针或引用转换为子类类型的指针或引用:这种操作性放大的类型转换,在编译器看来是危险的,不能直接隐式转换,但是可以显示强转(推荐使用static_cast)
     

    #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:
    	Student(const string& name, int age, int no)
    		:Human(name, age), m_no(no){}//Human(..):显式指明基类部分的初始化方式
    
    	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("孙武兵法");
    	Teacher t("孙悟空", 35, 30000);
    	t.who();
    	t.eat("桃子");
    	t.sleep(6);
    	t.teach("unix系统编程");
    
    	//Student* ---> Human*:向上造型
    	//这里是隐式转换
    	Human* ph = &s;
    	ph->sleep(8);
    	ph->eat("面包");
    	//ph->who(); //error
    	
    	//Human* ---> Student*:向下造型(合理),安全
    	Student* ps = static_cast<Student*>(ph);
    	ps->who();
    
    	//Human* ---> Student*:向下造型(不合理),不安全
    	Human h("赵云", 25);
    	Student* ps2 = static_cast<Student*>(&h);
    	ps2->who();
    	return 0;
    }
    

    4)子类继承基类的成员
    在子类中,可以直接访问基类中的公有和保护成员,就如同它们是子类自己的成员一样。
    基类的私有成员,子类也可以继承,但是会受到访问控制属性的影响,无法直接使用。如果需要在子类中访问到基类中的私有成员,可以让基类提供公有的或保护的成员函数来间接访问。
     
    5)子类隐藏基类的成员
    如果子类和基类中有同名的成员函数,因为作用域不同,不能构成重载关系,而是一种隐藏关系,通过子类对象将优先访问子类自己的成员,这时如果还需要访问基类中被隐藏的成员,可以借助作用于限定操作符 类名:: 显示指明
    注:如果形成隐藏关系的成员函数参数有所区别,也可以通过using声明,将基类中的成员函数引入到子类作用域中,让它们形成重载,通过函数重载解决
    下边代码解注18行或者24行其中一行,注掉23行即可不报错误。
     

    #include <iostream>
    
    using namespace std;
    
    class Base{
    public:
    	void func(void){
    		cout << "基类的func" << endl;
    	}
    };
    
    class Derived:public Base{
    public:
    	void func(int i){
    		cout << "子类的func(int)" << endl;
    	}
    	//将基类中的func引入到当前子类的作用域,让其和子类中的func形成重载
    	//using Base::func;
    };
    
    int main(void){
    	Derived d;
    	d.func(); //error
    	//d.Base::func();//加上作用域声明或者在子类中使用using 声明
    	d.func(123);
    
    	return 0;
    }
    
  4. 访问控制属性和继承关系
    1)访问控制属性:影响访问该类成员的位置

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

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

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

    小结:继承方式可以这样理解,如果派生类继承方式为公有继承,那么,基类中的各成员,如果是public的,到了派生类还是public,是protected的,到了派生类还是protected,private也是这样;如果派生类的继承方式是protected,那么基类中的public成员到派生类中会变成protected,基类中的protected和private成员到派生类中,是什么还是什么,不会变化;如果继承方式为private,基类中的成员,到了派生类,都会变成private修饰,这样可以防止基类的成员无限向子类蔓延,派生类中变成了private,派生类的派生类就不能直接访问这些成员了。

    #include <iostream>
    
    using namespace std;
    
    class A{
    public:
    	A(void):m_public(100), m_protected(200), m_private(300){}
    public:
    	int m_public;
    protected:
    	int m_protected;
    private:
    	int m_private;
    };
    
    class B:public A{//公有继承的子类
    				 //该类中可以访问 m_public m_protected
    };
    class C:protected A{//保护继承的子类
    					//该类中可以访问 m_public m_protected
    };
    class D:private A{//私有继承的子类
    				  //该类中可以访问 m_public m_protected
    };
    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;
    	//b.m_protected = 456;
    	//b.m_private = 789;
    	
    	C c;
    	//c.m_public = 123;
    	//c.m_protected = 456;
    	//c.m_private = 789;
    
    	D d;
    	//d.m_public = 123;
    	//d.m_protected = 456;
    	//d.m_private = 789;
    	d.print();
    	return 0;
    }
    
  5. 子类的构造函数
    1)如果子类的构造函数没有显式指明基类子对象的初始化,那么编译器将会自动调用基类的无参构造函数来初始化基类子对象。
     
    2)如果希望以有参的方式初始化基类子对象,则必须使用初始化列表显式指明基类子对象的初始化方式。
     
    3)子类对象创建过程
    –》分配内存
    –》构造基类子对象(按继承表从左到右顺序)
    –》构造成员子对象(按声明自上而下的顺序)
    –》执行子类的构造函数代码

  6. 子类析构函数
    1)子类的析构函数,无论自定义的还是缺省提供的,都会自动调用基类的析构函数,完成基类子对象的销毁。
     
    2)子类对象的销毁过程
    –》执行子类的析构函数代码
    –》析构成员子对象(按声明逆序)
    –》析构基类子对象(按声明逆序)
    –》释放内存
     
    3)基类的析构函数不能调用子类的析构函数,所以delete一个指向子类对象的基类指针,实际被执行的仅是基类的析构函数,子类的析构函数执行不到,有内存泄露的风险
    class A{};
    class B:public A{};
    A* pa = new B;
    delete pa;
    解决:虚析构函数

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

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

  7. 子类的拷贝构造和拷贝赋值
    1)子类的拷贝构造
    –》如果子类没有自己定义拷贝构造函数,那么编译器会为子类提供缺省的拷贝构造函数,该函数会自动调用基类的拷贝构造函数,完成基类子对象的拷贝初始化。
    –》如果子类自己定义拷贝构造函数,那么编译器就不再为子类提供缺省的拷贝构造函数,这时需要使用初始化列表,显式指明基类子对象也要以拷贝的方式进行初始化。
    2)拷贝赋值
    –》如果子类没有定义拷贝赋值操作符函数,那么编译器会为子类提供缺省拷贝赋值函数,该函数会自动调用基类的拷贝赋值函数,完成基类子对象的赋值。
    –》如果子类定义了自己的拷贝赋值函数,那么编译器不再为子类提供缺省拷贝赋值函数,这时需要显式调用基类的拷贝赋值函数,完成基类子对象的赋值。

    #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){
                //显式调用基类的拷贝赋值函数,完成基类
                //子对象的复制操作.
                //怎么写都可以this->Base::operator=(that);//that这里实际上是向上造型
    			//((Base*)this)->operator=(that);//用Base指针调用它自己的operator=,that这里实际上是向上造型
                Base::operator=(that);//相当于用this,即派生对象,显式地调用基类operator=方法,Base::不可省略不写
                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;
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值