继承和派生

本章内容为coursera课程C++程序设计中第五周的课件的整理

一、继承和派生

继承和派生的概念

继承:在定义一个新的类B时,如果该类与某个已有的类A相似(指的是B拥有A的全部特点),

那么就可以把A作为一个基类,而把B作为基类的一个派生类(也称子类)。

派生类是通过对基类进行修改和扩充得到的。在派生类中,可以扩充新的成员变量和成员函数。

派生类一经定义后,可以独立使用,不依赖于基类。

派生类拥有基类的全部成员函数和成员变量,不论是private、 protected、 public 。

在派生类的各个成员函数中,不能访问基类中的private成员。


需要继承机制的例子
所有的学生都有的共同属性:姓名、学号、性别、成绩
所有的学生都有的共同方法(成员函数):是否该留级、是否该奖励

而不同的学生,又有各自不同的属性和方法:

研究生:导师、系
大学生:系
中学生:竞赛特长加分

如果为每类学生都从头编写一个类,显然会有不少重复的代码,浪费。
比较好的做法是编写一个“学生”类,概括了各种学生的共同特点,

然后从“学生”类派生出“大学生”类,“中学生”类,“研究生类”。


派生类对象的内存空间

派生类对象的体积,等于基类对象的体积,再加上派生类对象自己的成员变量的体积。在派生类对象中,包
含着基类对象,而且基类对象的存储位置位于派生类对象新增的成员变量之前。

class CBase
{
int v1,v2;
};
class CDerived:public CBase
{
int v3;
};



继承实例程序:学籍管理 

#include <iostream>
#include <string>
using namespace std;
class CStudent {
private:
string name;
string id; //学号
char gender; //性别,'F'代表女, 'M'代表男
int age;
public:
void PrintInfo();
void SetInfo( const string & name_,const string & id_,
int age_, char gender_ );
string GetName() { return name; }
};
class CUndergraduateStudent:public CStudent
{//本科生类,继承了CStudent类
private:
string department; //学生所属的系的名称
public:
void QualifiedForBaoyan() { //给予保研资格
cout << “qualified for baoyan” << endl;
}
void PrintInfo() {
CStudent::PrintInfo(); //调用基类的PrintInfo
cout << “Department:” << department <<endl;
}
void SetInfo( const string & name_,const string & id_,
int age_,char gender_ ,const string & department_) {
CStudent::SetInfo(name_,id_,age_,gender_); //调用基类的SetInfo
department = department_;
}
};
void CStudent::PrintInfo()
{
cout << "Name:" << name << endl;
cout << "ID:" << id << endl;
cout << "Age:" << age << endl;
cout << "Gender:" << gender << endl;
}
void CStudent::SetInfo( const string & name_,const string & id_,
int age_,char gender_ )
{
name = name_;
id = id_;
age = age_;
gender = gender_;
}
1
int main()
{
CUndergraduateStudent s2;
s2.SetInfo(“Harry Potter ”, “118829212”,19,‘M’,“Computer Science”);
cout << s2.GetName() << “ ” ;
s2.QualifiedForBaoyan ();
s2.PrintInfo ();
return 0;
}
输出结果:
Harry Potter qualified for baoyan
Name:Harry Potter
ID:118829212
Age:19
Gender:M
Department:Computer Science

二、基类/派生类同名成员与Protected关键字
基类和派生类有同名成员的情况

class base {
int j;
public:
int i;
void func();
};
class derived : public base{
public:
int i;
void access();
void func();
}
void derived::access()
{
j = 5; //error 因为j是基类的私有成员,派生类不能访问
i = 5; //引用的是派生类的 i
base::i = 5; //引用的是基类的 i
func(); //派生类的
base::func(); //基类的
}
derived obj;
obj.i = 1;
obj.base::i = 1;


访问范围说明符(默认继承方式为私有继承)

基类的private成员: 可以被下列函数访问
• 基类的成员函数
• 基类的友员函数
基类的public成员: 可以被下列函数访问
• 基类的成员函数
• 基类的友员函数
• 派生类的成员函数
• 派生类的友员函数

• 其他的函数


这里我们首先要明白下面几点。

1.类的一个特征就是封装,publicprivate作用就是实现这一目的。所以:

用户代码(类外)可以访问public成员而不能访问private成员;private成员只能由类成员(类内)和友元访问。

2.类的另一个特征就是继承,protected的作用就是实现这一目的。所以:

protected成员可以被派生类对象访问,不能被用户代码(类外)访问。

继承中的特点:

先记住:不管是否继承,上面的规则永远适用!

有public, protected, private三种继承方式,它们相应地改变了基类成员的访问属性。

1.public继承:基类public成员,protected成员,private成员的访问属性在派生类中分别变成:public, protected, private

2.protected继承:基类public成员,protected成员,private成员的访问属性在派生类中分别变成:protected, protected, private

3.private继承:基类public成员,protected成员,private成员的访问属性在派生类中分别变成:private, private, private

但无论哪种继承方式,上面两点都没有改变:

1.private成员只能被本类成员(类内)和友元访问,不能被派生类访问

2.protected成员可以被派生类访问

注意这一个例子:C++类中对同类对象private成员访问

class CTest {  
public:  
    CTest(int i);   
    CTest(const CTest& rhs);  
    CTest& operator=(const CTest& rhs);  
    void printCTest(const CTest& rhs);  
private:  
    int value;  
};  
  
CTest::CTest(int i):value(i)  
{  
    cout<<"Contructor of CTest"<<endl;  
}  
  
CTest::CTest(const CTest& rhs):value(rhs.value)  
{  
    cout<<"Copy contructor of CTest"<<endl;  
}  
  
CTest& CTest::operator=(const CTest& rhs)  
{  
    cout<<"Assign function of CTest"<<endl;  
    if(this == &rhs)  
        return *this;  
    value = rhs.value;                //通过对象访问私有成员变量  
    return *this;  
}  
  
void CTest::printCTest(const CTest& rhs)  
{  
    cout<<rhs.value<<endl;        //通过对象访问私有成员变量  
}  
  
int main()  
{  
    CTest t = 1;  
    CTest tt = 2;  
    //  cout<<t.value<<endl;        //通过对象访问私有成员变量,编译错误  
    //  cout<<tt.value<<endl;        //通过对象访问私有成员变量,编译错误  
    t.printCTest(tt);  
}  
第26行和第32行代码可以编译通过,而第39行和第40行代码会产生编译错误。

访问权限修饰符是针对类级别的,同一个类的不同对象的私有成员,依旧能被同一个类的不同实例的方法访问其private 成员


访问范围说明符: protected

基类的protected成员: 可以被下列函数访问
• 基类的成员函数
• 基类的友员函数
• 派生类的成员函数可以访问当前对象的基类的保护成员

class Father {
private: int nPrivate; //私有成员
public: int nPublic; //公有成员
protected: int nProtected; // 保护成员
};
class Son : public Father {
void AccessFather () {
nPublic = 1; // ok;
nPrivate = 1; // wrong
nProtected = 1; // OK, 访问从基类继承的protected成员
Son f;
f.nProtected = 1; //wrong, f不是当前对象
}
};
int main(){
Father f;
Son s;
f.nPublic = 1; // Ok
s.nPublic = 1; // Ok
f.nProtected = 1; // error
f.nPrivate = 1; // error
s.nProtected = 1; //error
s.nPrivate = 1; // error
return 0;
}


三、派生类的构造函数

派生类对象包含基类对象,那么执行派生类构造函数之前, 先执行基类的构造函数。
派生类交代基类初始化的具体形式:

class Bug {
private :
int nLegs; int nColor;
public:
int nType;
Bug (int legs, int color);
void PrintBug () { };
};
class FlyBug: public Bug { // FlyBug是Bug的派生类
int nWings;
public:
FlyBug(int legs, int color, int wings);
};

Bug::Bug( int legs, int color) {
nLegs = legs;
nColor = color;
}
//错误的FlyBug构造函数:
FlyBug::FlyBug (int legs, int color, int wings) {
nLegs = legs; // 不能访问
nColor = color; // 不能访问
nType = 1; // ok
nWings = wings;
}
//正确的FlyBug构造函数:
FlyBug::FlyBug (int legs, int color, int wings):Bug(legs, color) {
nWings = wings;
}

int main() {
FlyBug fb ( 2,3,4);
fb.PrintBug();
fb.nType = 1;
fb.nLegs = 2 ; // error.nLegs is private
return 0;
}

FlyBug fb (2,3,4);
在创建派生类的对象时,

• 需要调用基类的构造函数:初始化派生类对象中从基类继承的成员
• 在执行一个派生类的构造函数之前,总是先执行基类的构造函数

调用基类构造函数的两种方式
• 显式方式:
派生类的构造函数中的基类的构造函数提供参数
derived::derived(arg_derived-list):base(arg_base-list)
• 隐式方式:
派生类的构造函数中,省略基类构造函数时
派生类的构造函数,自动调用基类的默认构造函数
派生类的析构函数被执行时, 执行完派生类的析构函数后,自动调用基类的析构函数

class Base {
public:
int n;
Base(int i):n(i)
{ cout << "Base " << n << " constructed" << endl; }
~Base()
{ cout << "Base " << n << " destructed" << endl; }
};
class Derived:public Base {
public:
Derived(int i):Base(i)
{ cout << "Derived constructed" << endl; }
~Derived()
{ cout << "Derived destructed" << endl; }
};
int main() { Derived Obj(3); return 0; } 
输出结果:
Base 3 constructed
Derived constructed
Derived destructed
Base 3 destructed


包含成员对象的派生类的构造函数

class Skill {
public:
Skill(int n) { }
};
class FlyBug: public Bug {
int nWings;
Skill sk1, sk2;
public:
FlyBug(int legs, int color, int wings);
};
FlyBug::FlyBug( int legs, int color, int wings):
Bug(legs, color), sk1(5), sk2(color) {
nWings = wings;
}
创建派生类的对象时,
• 调用 基类 的构造函数: 初始化派生类对象中从基类继承的成员

• 调用 成员对象类 的构造函数: 初始化派生类对象中成员对象

• 调用派生类的构造函数

• 调用完 派生类的析构函数 后:
• 调用 成员对象类 的析构函数
• 调用 基类 的析构函数
析构函数的调用顺序与构造函数的调用顺序相反



四、public继承的赋值兼容规则

class base { };
class derived : public base { };
base b;
derived d;
1) 派生类的对象可以赋值给基类对象
b = d;  //可以想到:会把派生类对象d中的包含的基类对象赋值给基类对象b
//反过来d=b;不可以!不可以把基类对象赋值在派生类对象。
2) 派生类对象可以初始化基类引用

base & br = d;
3) 派生类对象的地址可以赋值给基类指针
base * pb = & d;
• 如果派生方式是 private或protected,则上述三条不可行。

直接基类和间接基类
类A派生类B,类B派生类C,类C派生类D, ……
– 类A是类B的直接基类
– 类B是类C的直接基类,类A是类C的间接基类
– 类C是类D的直接基类,类A、 B是类D的间接基类




在声明派生类时, 只需要列出它的直接基类
– 派生类沿着类的层次自动向上继承它的间接基类
– 派生类的成员包括
• 派生类自己定义的成员
• 直接基类中的所有成员
• 所有间接基类的全部成员

#include <iostream>
using namespace std;
class Base {
public:
int n;
Base(int i):n(i) {
cout << "Base " << n << " constructed" << endl;
}
~Base() {
cout << "Base " << n << " destructed" << endl;
}
};

class Derived:public Base
{
public:
Derived(int i):Base(i) {
cout << "Derived constructed" << endl;
}
~Derived() {
cout << "Derived destructed" << endl;
}
};

class MoreDerived:public Derived {
public:
MoreDerived():Derived(4) {
cout << "More Derived constructed" << endl;
}
~MoreDerived() {
cout << "More Derived destructed" << endl;
}
};
int main()
{
MoreDerived Obj;
return 0;
}
输出结果:
Base 4 constructed
Derived constructed
More Derived constructed
More Derived destructed
Derived destructed
Base 4 destructed


C++中基类和派生类之间的同名函数的重载问题

派生类写一个和基类同名无论参数列表相同或不相同)的函数时,此时发生的动作叫“覆盖”。覆盖的意思,就是基类的同名函数,在派生类内,将变得无法直接调用(但可以间接调用)。

例1:

class A  
{  
public:  
    void fn()  
    {}  
  
    void fn(int a)  
    {}  
};  
  
class B : public A  
{  
public:  
    void fn()   
    {}  
};  
  
int main()  
{  
    B b;  
    b.fn(3);  
    return 0;  
}

编译器报错,B中并不存在fn(int)的函数。
例2:

class A  
{  
    void foo(int d)  
    {  
        cout << "A::foo - int" << endl;  
        cout << d << endl;  
    }  
};  
  
class B : public A  
{  
    void foo(double d) //覆盖了A::foo(int d);  
    {  
        cout << "B::foo - double" << endl;  
        cout << d << endl;  
    }  
};  
  
int main()  
{     
    A a;  
    a.foo(10);  
     
    B b;  
    b.foo(10.2);  
    b.foo(2); //调用的仍然是B::foo,虽然2明显是个整数  
             
    return 0;  
}

以上代码,运行之后输出结果大致如下:(注释为后加内容)
A::foo - int
10
B::foo - double
10.2
B::foo - double //调用的仍然是B::foo,虽然2明显是个整数
2

若要调用基类的foo(int )

B b;  
b.A::foo(2); //显式调用A范围内的foo  




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值