Day43、向下造型、继承方式和访问控制属性、子类的拷贝构造和拷贝赋值、多重继承

3.3向下造型

将基类类型的指针或引用转换为子类类型的指针或引用

这种转换扩大了指针或引用的操作范围,让编译器看来是不安全的,所以不能隐式转换,但是可以显示转换。

#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)const{//常函数(const *this)不需要修改成员变量

       cout<<"我在吃"<<food<<endl;

    }

   void sleep(int time)const{

       cout<<"我睡了"<<time<<"小时"<<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){}//初始化

   void who(void)const{//不修改成员变量的都尽量使用常函数

       cout<<"我叫"<<m_name<<",今年"<< \

           m_age<<"岁,学号"<<m_no<<endl;

    }

    void learn(const string& course)const{

       cout<<"我在学"<<course<<endl;

    }

private:

   int m_no;

};

//教师类(人类派生的另外一个子类)

class Teacher:public Human{

public:

   Teacher(const string& name,int age,double salary):

       Human(name,age),m_salary(salary){}

   void teach(const string& course)const{

       cout<<"我在讲"<<course<<endl;

    }

   void who(void)const{

       cout<<"我叫"<<m_name<<",今年"<< \

           m_age<<"岁,工资:"<<m_salary<<endl;

    }

private:

   double m_salary;

};

int main(void){

    Students("悟空",30,10001);

   s.who();

   s.eat("桃子");

   s.sleep(8);

   s.learn("打坐");

   Teacher t("唐僧",31,5000.5);

   t.who();

   t.eat("蔬菜");

   t.sleep(6);

   t.teach("佛法");

   //向上造型,缩小了指针的操作范围,所以安全,可以做隐式类型转换

   //(12个字节只访问前8个字节,安全!有12块钱,拿8块钱)

   Human* ph=&s; //ph指向子类对象的基类指针

   ph->eat("桃");//可以访问基类中有的部分

   ph->sleep(10);//可以访问基类中有的部分

   //ph->who();error

   //Student* ps=ph; 向下造型,理论上可以,但扩大了范围,编译不过

   //安全的向下造型

   Student *ps=static_cast<Student*>(ph);

   ps->who();

   Human h("黛玉",19);

   //Teacher* rt=&h; 向下造型  编译不过

   //危险的向下造型    (有8块钱,拿12块钱)

   Teacher* pt=static_cast<Teacher*>(&h);

   pt->who();

   return 0;

}

图:

 

3.4子类继承基类的成员

1)在子类中,可以直接访问基类中的所有公有成员和保护成员,如同这些成员在子类中声明的一样。

2)基类的私有成员,在子类中依然存在但是不可见,所以无法直接访问,但是通过基类提供的公有或保护的接口函数来间接地访问。

3)基类的构造函数和析构函数子类无法继承,但是可以在子类自己的构造函数中显示指明基类部分(基类子对象)的初始化方式。

#include <iostream>

using namespace std;

//人类(基类)

class Human{

public:

   Human(const string& name,int age):

       m_name(name),m_age(age),m_id(1234){}

   void eat(const string& food)const{

       cout << "我在吃" << food << endl;

    }

   void sleep(int time)const{

       cout << "我睡了" << time << "小时" <<endl;

    }

protected://保护成员可以在子类中直接访问

   const int& id(void)const{

       return m_id;

    }

   string m_name;

   int m_age;

private:

   int m_id;

};

//基类子对象:子类对象中包含基类的部分

//学生类(人类派生的子类)

class Student:public Human{

public:

   /*可以在子类构造函数的初始化表中显式指明基类子对象

       的初始化方式,如果不指明,基类子对象将以无参的方式

         进行初始化。*/

   Student(const string& name,int age,int no):

       Human(name,age),m_no(no){}

   void who(void)const{

       cout << "我叫" << m_name << ",今年" <<

           m_age << "岁,学号:"<< m_no << ",id:" << id() << endl;

    }

private:

   int m_no;   

};

int main(void)

{

   Student s("悟空",30,10011);

   cout << sizeof(s) << endl;

   s.who();

   return 0;

}

tarena@tarena-virtual-machine:~/day43$./a.out

16

我叫悟空,今年30岁,学号:10011,id:1234

 

3.5 子类隐藏基类的成员

1)子类和基类中定义同名的成员函数(包括静态成员函数),因为作用域不同,不能构成重载关系,而是一种隐藏的关系。

2)如果需要在子类中访问隐藏的成员函数,可以通过作用域限定操作符“::”显示的指明

*3)通过using声明可以将基类的成员函数引入到子类的作用域,形成重载

#include<iostream>

using namespace std;

class A{

public:

   void foo(void){

       cout<<"A::foo()"<<endl;

    }

};

class B:public A{

public:

   void foo(int i){

       cout<<"B::foo(int)"<<endl;

    }

   void bar(void){

//无法直接访问A中继承过来的foo函数,因为被B类自己的foo隐藏了,可以通过A::foo()说明

       A::foo();

       foo(100);

    }

//把A的foo函数声明到子类B中,这样也可以和B中的foo函数形成重载

//       using A::foo();

};

int main(){

    Bb;

   b.bar();   // A::foo()   B::foo(int)

   //b.foo();error

   b.foo(100);  // B::foo(int)

   return 0;

}

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

访问控制            属性             内部        子类       外部     友元

限定符                                     访问        访问       访问     访问

Public             公有成员        OK          OK        OK        OK

Protected       保护成员        OK          OK        no         OK

Private             私有成员        OK          no         no         OK

 

 

基类中的      在公有子      在保护子     在私有子

              类中变成      类中变成     类中变成

公有成员      公有成员      保护成员     私有成员

保护成员      保护成员      保护成员     私有成员

私有成员      私有成员      私有成员     私有成员


#include <iostream>

using namespace std;

class A{

public:

       intm_pub;

protected:

       intm_pro;

private:

       intm_pri;

};

class B:public A{};

class C:protected A{};

class D:private A{

public:

       voidfoo(void){

              m_pub= 10;

              m_pro= 10;

              //m_pri= 10;

       }

 

};

class X:public B{

public:

       voidfoo(void){

              m_pub= 10;

              m_pro= 10;

              //m_pri= 10;

       }

};

class Y:public C{

public:

       voidfoo(void){

              m_pub= 10;

              m_pro= 10;

              //m_pri= 10;

       }

};

class Z:public D{

public:

       voidfoo(void){

              //m_pub= 10;

              //m_pro= 10;

              //m_pri= 10;

       }

};

int main(void)

{

       Bb;

       b.m_pub= 10;//ok

       //b.m_pro= 10;//

       //b.m_pri= 10;

       Cc;

       //c.m_pub= 10;

       //c.m_pro= 10;

       //c.m_pri= 10;

       Dd;

       //d.m_pub= 10;

       //d.m_pro= 10;

       //d.m_pri= 10;

      

}

 

5、私有继承的子类和保护继承的子类,不能向上造型

  1#include<iostream>

  2using namespace std;

  3class Base{

  4public:

 5     int m_pub;

  6};

  7class Derived:/*private*/ protected /*public*/ Base{};

  8int main(void){

 9     Derived d;

 10    Base* pb=&d;//error   pb是基类公有指针,子类是私有的,不能访问

 11    //Base* pb=static_cast<Base*>(&d);error

 12    return 0;

 13 }

6、子类的构造函数

1)如果子类的构造函数没有显示指明基类子对象的初始化方式,那么编译器将会调用基类的无参构造函数初始化基类子对象

2)如果希望基类子对象以有参的方式初始化,就必须在子类构造函数的初始化表中显示指明基类子对象的初始化方式。

#include<iostream>

using namespace std;

class Base{

   public:

       Base(void):m_i(0){

           cout << "Base::Base()" << endl;

       }

       Base(int i):m_i(i){

           cout << "Base::Base(int)"<< endl;

       }

       int m_i;

};

class Derived:public Base{

   public:

       Derived(void){

           cout <<"Derived::Derived()" <<endl;

       }

       Derived(int i):Base(i){

           cout << "Derived::Derived(int)"<<endl;

       }

};

int main(void){

   Derived d;

   cout << d.m_i << endl;

   Derived d2(100);

   cout << d2.m_i << endl;//100

   return 0;

}

tarena@tarena-virtual-machine:~/day43$./a.out

Base::Base()

Derived::Derived()

0

Base::Base(int)

Derived::Derived(int)

100

3)子类对象构造构造过程

à分配内存

à构造基类子对象(继承表的顺序)

à构造成员子对象(按声明顺序)

à执行子类构造函数代码

#include<iostream>

using namespace std;

class Member{

   public:

       Member(void):m_i(0){

           cout<< "Member::Member()" <<endl;

       }

       Member(int i):m_i(i){

           cout<< "Member::Member(int)" <<endl;

       }

       int m_i;

};

class Base{

   public:

       Base(void):m_i(0){

           cout << "Base::Base()" << endl;

       }

       Base(int i):m_i(i){

           cout << "Base::Base(int)"<< endl;

       }

       int m_i;

};

class Derived:public Base{

   public:

       Derived(void){

           cout <<"Derived::Derived()" <<endl;

       }

       Derived(int i,int j):Base(i),m_member(j){

           cout << "Derived::Derived(int)"<<endl;

       }

       Member m_member;

};

int main(void){

/*   Derived d;

   cout << d.m_i << endl;

   Derived d2(100);

   cout << d2.m_i << endl;//100 */

   Derived d3(100,200);

   cout<<d3.m_i <<endl;

   cout << d3.m_member.m_i<< endl;

   return 0;

}

tarena@tarena-virtual-machine:~/day43$./a.out

Base::Base(int)

Member::Member(int)

Derived::Derived(int)

100

200

7、子类的析构函数

1)子类的析构函数会自动调用基类的析构函数,析构基类子对象

2)子类对象析构的过程

-》执行子类的析构函数

-》析构成员子对象(按声明逆序)

-》析构基类子对象(按继承表逆顺序)

-》释放内存

#include<iostream>

using namespace std;

class Member{

   public:

       Member(void):m_i(0){

           cout<< "Member::Member()" <<endl;

       }

       Member(int i):m_i(i){

           cout<< "Member::Member(int)" <<endl;

       }

       ~Member(void){

           cout<< "Member::~Member()" <<endl;

       }

       int m_i;

};

class Base{

   public:

       Base(void):m_i(0){

           cout << "Base::Base()" << endl;

       }

       Base(int i):m_i(i){

           cout << "Base::Base(int)"<< endl;

       }

       ~Base(void){

           cout << "Base::~Base()" << endl;

       }

       int m_i;

};

class Derived:public Base{

    public:

       Derived(void){

           cout <<"Derived::Derived()" <<endl;

       }

       Derived(int i,int j):Base(i),m_member(j){

           cout << "Derived::Derived(int)"<<endl;

       }

       ~Derived(void){

           cout <<"Derived::~Derived()" <<endl;

       }

       Member m_member;

};

int main(void){

/*   Derived d;

   cout << d.m_i << endl;

   Derived d2(100);

   cout << d2.m_i << endl;//100 */

   Derived d3(100,200);

   cout<<d3.m_i <<endl;

   cout << d3.m_member.m_i<< endl;

   return 0;

}

tarena@tarena-virtual-machine:~/day43$./a.out

Base::Base(int)

Member::Member(int)

Derived::Derived(int)

100

200

Derived::~Derived()

Member::~Member()

Base::~Base()

3)基类的析构函数不能调用子类的析构函数,对一个“指向子类对象的基类指针”使用delete运算符,实际被调用的仅仅是基类的析构函数,子类如果有动态资源将会形成内存泄露

class A{….}

class B:public A{};

int main(){

       A*pa=new B(….);

       deletepa;

}

8、子类的拷贝构造和拷贝赋值

8.1 拷贝构造

1)子类没有定义拷贝构造函数,编译器会为子类提供缺省的拷贝构造函数,该函数会自动调用基类的拷贝构造函数,初始化基类子对象

2)子类定义拷贝构造函数,但是没有显示指明基类子对象的初始化方式,那么基类子对象将以无参的方式被初始化。//逻辑错误

3)子类定义拷贝构造函数,同时在初始化表显示指明了基类子对象也以拷贝的方式进行初始化,那么基类子对象也会得到正常的拷贝。//OK

8.2拷贝赋值

1)子类没有定义赋值运算符函数,编译器将提供缺省的拷贝赋值运算符函数,它会自动调用基类的拷贝赋值运算符函数,复制基类子对象

2)子类定义拷贝赋值运算符函数,但是没有复制基类子对象,基类子对象保持赋值之前的状态。//逻辑错误

3)子类定义拷贝赋值运算符函数,显示调用基类的拷贝赋值运算符函数,复制基类子对象,那么也可以得到正确的赋值操作。//OK

 

举例:

#include<iostream>

using namespace std;

class Base{

public:

   Base(void):m_i(0){}

   Base(int i):m_i(i){}

   //自定义拷贝构造函数

   Base(const Base& that):m_i(that.m_i){

       cout<<"Base::Base(const Base&)"<<endl;

    }

   //自定义拷贝赋值函数 (和缺省的一样)

   Base& operator=(const Base& that){

       cout<<"Base::operator="<<endl;

       if(&that!=this){

           m_i=that.m_i;

       }

       return *this;

    }

   int m_i;

};

class Derived:public Base{

public:

   Derived(void):m_i(0){}

   Derived(int i,int j):Base(i),m_i(j){}

   //以拷贝的方式初始化

   Derived(const Derived& that):m_i(that.m_i),Base(that){}

   //赋值(代替缺省,一般不自己写,用缺省.和自定义的赋值函数配套)

   Derived& operator=(const Derived& that){

       if(&that!=this){

           m_i=that.m_i;

           //显示调用基类的拷贝赋值函数,来复制基类子对象

           Base::operator=(that);

       }

       return *this;

    }

   int m_i;

};

int main(void){

   Derived d1(100,200);

   Derived d2(d1);//拷贝构造(缺省的)

   cout<<d1.Base::m_i<<' '<<d1.m_i<<endl;//100 200

   cout<<d2.Base::m_i<<' '<<d2.m_i<<endl;//100 200

   Derived d3;

   d3=d1;//拷贝赋值

   cout<<d3.Base::m_i<<' '<<d3.m_i<<endl;

   return 0;

}

tarena@tarena-virtual-machine:~/day43$./a.out

Base::Base(const Base&)

100 200

100 200

Base::operator=

100 200

9、多重继承

1)一个子类可以同时继承多个基类,这样继承方式称为多重继承。

技术员     经理

   \       /

技术主管

 

电话 播放器计算机

 \    |      /

智能手机

2)将继承自多个基类子类类型的指针或引用,转换为基类类型的指针或引用时,编译器会根据各个基类子对象的内存布局,进行适当的偏移计算,保证指针的类型和所指向的目标对象的类型一致。

#include<iostream>

using namespace std;

class Phone{

public:

   Phone(const string& number):m_number(number){}

   void call(const string& number){

       cout<<m_number<<"打给"<<number<<endl;

    }

private:

   string m_number;

};

class Player{

public:

   Player(const string& media):m_media(media){}

   void play(const string& music){

       cout<<m_media<<"播放器播放"<<music<<endl;

    }

private:

   string m_media;

};

class Computer{

public:

   Computer(const string& os):m_os(os){}

   void run(const string& app){

       cout<<"在"<<m_os<<"系统上运行"<<app<<endl;

    }

private:

   string m_os;

};

class Smartphone:public Phone,publicPlayer,public Computer{

public:

   Smartphone(const string& number,\

       const string& media,const string& os):

       Phone(number),Player(media),Computer(os){}

};

int main(){

   Smartphonesp("15062203320","MP3","Android");

   sp.call("0530-3233192");

   sp.play("最炫小苹果");

sp.run("Angrybird");

 

//向上造型会自动做指针偏移,保证指针类型和所指向的目标对象类型一致

   Smartphone* p1=&sp;

   Phone* p2=p1;//向上造型

   Player* p3=p1;

   Computer* p4=p1;

 

   cout<<"p1="<<p1<<endl;

   cout<<"p2="<<p2<<endl;

   cout<<"p3="<<p3<<endl;

   cout<<"p4="<<p4<<endl;

 

   return 0;

}

tarena@tarena-virtual-machine:~/day43$./a.out

15062203320打给0530-3233192

MP3播放器播放最炫小苹果

在Android系统上运行Angry bird

p1=0xbf957110

p2=0xbf957110

p3=0xbf957118

p4=0xbf95711c

图:

 

 

3)名字冲突问题

一个子类的多个基类中如果存在相同的名字,当通过子类访问这些名字的时候,编译器会报歧义错误----名字冲突;

解决名字冲突的一般做法就是显示的通过作用域限定指明所访问的名字继承自哪个基类。

如果产生冲突的名字是成员函数并且满足重载的条件,则可以通过using声明,在子类形成重载关系,通过重载解析解决名字冲突。

#include<iostream>

using namespace std;

class A{

public:

   void foo(){

       cout<<"A::foo"<<endl;

    }

   int m_data;

};

class B{

public:

   void foo(int i){

       cout<<"B::foo(int)"<<endl;

    }

   typedef int m_data;//给int重命名,m_data是一种类型

};

class C:public A,public B{

public:

   using A::foo;

   using B::foo;

};

int main(void){

    Cc;

   c./*A::*/foo();

   c./*B::*/foo(100);

   c.A::m_data=10;

   C::B::m_data i=20;

   cout<<c.A::m_data<<','<<i<<endl;

   return 0;

}

tarena@tarena-virtual-machine:~/day43$./a.out

A::foo

B::foo(int)

10,20

 

4)钻石继承

一个子类的多个基类源自共同的祖先,这样的继承结构体称为钻石继承

     A

    /  \

  B   C

    \  /

     D

 

       人类

        /   \

   学生 教师

         \   /

      课代表


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值