C++ 学习之路(10):继承与派生
2016-03-22 21:01
603 查看
一、
i.类的继承:新的类从已有类那里得到已有的特性.
II.类的派生:从已有类产生新类的过程.
iii.已有类成为 基类 或 父类.
iv.产生的新类成为 派生类 或 子类.
二、为什么要使用继承?
已有学生类Student含有number(学号)、name(姓名)、score(成绩)等数据成员与成员函数print
现需要声明一个大学生类UStudent,它含有number(学号)、name(姓名)、score(成绩)以及major(专业)等数据成员与成员函数print1();
如果要重写一个大学生类,则这两个类都代码严重重复,为了提高代码的重用性,可以引入继承。
三、派生类的声明
class 派生类名:[继承方式] 基类名
{
派生类新增的数据成员和成员函数;
};
i.基类名:一个已经声明都类的名称
ii.派生类名:继承原有类的特性而生成的新类的名称.
III.继承方式:规定了如何访问从基类继承的成员,可以是关键字private、protected或public,分别表示私有继承、保护继承和公有继承.
注:如果不显式地给出继承方式关键字,系统默认为私有继承(private).
iv.类的继承方式制定了派生类成员以及类外对象对于从基类继承来的成员的访问权
四、基类成员在派生类中的访问属性
i.派生类可以继承基类中【除了构造函数与析构函数之外】的成员.
ii.基类成员在派生类中的访问属性(如表)
归纳得:
①基类中的私有成员.
无论哪种继承方式,基类中的私有成员不允许派生类的继承,即在派生类中是不可直接访问的.
②基类中的公有成员.
公有继承时,基类中的所有公有成员在派生类中仍以公有成员的身份出现的.
私有继承时,基类中的所有私有成员在派生类中都是以私有成员的身份出现的.
保护继承时,基类中的所有保护成员在派生类中都是以保护成员的身份出现的.
③基类中都保护成员.
公有继承时,基类中的所有保护成员在派生类中仍以保护成员的身份出现的.
私有继承时,基类中的所有保护成员在派生类中都是以私有成员的身份出现的.
保护继承时,基类中的所有保护成员在派生类中仍以保护成员的身份出现的.
五、派生类对基类成员的访问规则
派生类对基类成员的访问形式主要有以下两种:
①内部访问:由派生类中新增的成员函数对基类继承来的成员的访问.
②对象访问:在派生类外部,通过派生类的对象对从基类继承来的成员的访问.
i.私有继承的访问规则(如表)
ii.公有继承都访问规则(如表)
iii.保护继承的访问规则(如表)
六、派生类的构造函数和析构函数
i.简单的派生类的构造函数和析构函数的构造规则
①当基类的构造函数没有参数,或没有显式定义构造函数时,派生类可以不向基类传递参数,甚至可以不定义构造函数.
②当基类含有带参数的构造函数时,派生类必须定义构造函数,以提供把参数传递给基类构造函数的途径.
③在C++中,派生类构造函数的一般格式为:
派生类名(参数总表):基类名(参数表)
{
派生类新增数据成员的初始化语句;
}
ii.含有子对象的派生类的构造函数
①子对象:基类的对象
②在C++中,派生类中含有子对象时的构造函数的一般形式为:
派生类名(参数总表):基类名(参数表0),子对象名1(参数表1),...,子对象名n(参数表n)
{
派生类新增成员的初始化语句;
}
③说明:
1、当基类构造函数不带参数时,派生类不一定需要定义构造函数;然而当基类的构造函数哪怕只带有一个参数,它所有的派生类都必须定义构造函数,甚至所定义的生类构造函数都函数体可能为空,它仅仅起参数的传递作用.
2、若基类使用默认构造函数或不带参数都构造函数,则在派生类中定义构造函数时可略去“:基类构造函数名(参数表)”,此时若派生类也不需要构造函数,则可不定义造函数.
3、如果派生类的基类也是一个派生类,每个派生类只需负责其直接基类数据成员的初始化,依次上溯.
4、派生类都构造函数可以不显式地写出基类的构造函数.
5、要调用的基类构造函数的参数可在派生类的构造函数中明确地给出.
六、调整基类成员在派生类中的访问属性的其他方法
i.同名成员
派生类可以重新声明与基类成员同名的成员。在没有虚函数的情况下,如果在派生类中定义了与基类成员同名的成员,则称派生类成员覆盖了基类的同名成员此后若要再度使用基类都同名成员则需使用作用域标识符“::”--> 基类名::同名函数.
ii.访问声明
①有时程序员可能希望基类的个别成员能被派生类的对象直接访问,而不是通过派生类的公有成员函数间接访问.为此,C++提供了称为访问声明的特殊机制,可个别调整基类的某些成员在派生类中的访问属性,使之在派生类中保持原来的访问属性.
②访问声明的方法:
把基类的保护成员或公有成员直接写在私有派生类定义式中的同名段中,同时给成员名前冠以基类名和作用于标识符“::”. 例如:基类中的show()以A::show写进派生类B中
③使用访问声明时应注意:
1、数据成员也可以使用访问声明. 例如:A::X1;
2、访问声明中只含不带类型和参数的函数名或变量名.
3、访问声明不能改变成员在基类中的访问属性.
4、对于基类中的重载函数名,访问声明将对基类中所有同名函数起作用。这意味着对于重载函数使用访问声明时要慎重.
七、多继承与虚基类
通常,一个派生类有两个或多个基类,派生类从两个或多个基类中继承所需的属性。
i.声明多继承派生类的一般形式:
class 派生类名:继承方式1 基类名1,...,继承方式i 基类名i ,...,继承方式n 基类名n
{
派生类新增的数据成员和成员函数
};
ii.多继承派生类对基类成员的访问必须是无二义性的.若有二义性,要想办法消除.例如:
obj.X::f();
obj.Y::f();
iii.多继承派生类的构造函数与析构函数
多重继承构造函数定义的一般形式:
派生类名(参数总表) :基类名1(参数表1),...,基类名i(参数表i),...,基类名n(参数表n)
{
派生类新增成员的初始化语句
}
iv.析构函数在多继承派生类里是相互独立的.
虚基类:
i.作用:若果一个类有多个直接基类,而这些直接基类又有一个共同的基类,则在最底层的派生类中会保留这个间接的共同基类数据成员的多份同名成员.因此在访问时可能会产生二义性,虚基类便是为了消除二义性.
ii.声明:
class 派生类名:virtual 继承方式 类名
{ ...... }
iii.虚基类初始化时应注意:
①如果虚基类中定义有带形参的构造函数并没有定义默认形式的构造函数 -->整个继承结构中,所有派生类都必须在构造函数的成员初始化列表中列出对虚基类构造函数的调用,以初始化虚基类中定义的数据成员.
②建立对象时,如果该对象中含有从虚基类继承来的成员,则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。该派生类的其他基类对虚基类构造函数的调用都自动被忽略.
③若同一层次中包含虚基类和非虚基类,先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类的构造函数。
④对于多个虚基类(或者 非虚基类 ),构造函数的执行顺序仍然是先左右后,自上而下.
⑥若虚基类由非虚基类派生而来,则仍然先调用基类构造函数,再调用派生类的构造函数.
八、赋值兼容规则
i.赋值兼容:不同类型数据之间的自动转换和赋值. 这种关系在基类和派生类对象之间也存在.
即 基类对象的任何地方,都可以使用其子类对象来替代.
例:类Derived公有继承类Base
① Base b;
Derived d;
b=d; // 用派生类Derived的对象d对基类对象b赋值
②Base b;
Derived d;
Base &br=d; // 用派生类Derived的对象d对基类Base的对象的引用br进行初始化
③ Derived d;
Base *bp = &d; // 把派生类对象的地址&d赋给指向基类的指针bp,也就是说,使指向基类对象的指针bp也可以指向派生类对象d.
④..........
void fun(Base &bb) // 基类的对象作为参数
{ cout<<bb.i<<endl; } // 可以用派生类Derived的对象赋给基类的数据成员i的值
ii.赋值兼容规则举例:
总结:··程序举例
/*
语言:C++ 编译环境:Visual C++6.0
继承与派生:程序举例
声明一个共同的基类Person,它包含了所有派生类共有的数据,职工类Employee
和学生类Student为虚基类Person的派生类,在职大学生类E_Student是职工类Employee
和学生类Student的共同派生类。每个类定义了一个相对于特定类不同的print()函数,
输出各类的数据成员.
*/
#include <iostream>
#include <string>
using namespace std;
// 声明基类Person
class Person
{
protected:
string name; // 姓名
char sex; // 性别
int age; // 年龄
public:
Person(string name1, char sex1, int age1) // 构造函数
{
name = name1;
sex = sex1;
age = age1;
}
void print()
{
cout<<"姓名: "<<name<<endl;
cout<<"性别: "<<sex<<endl;
cout<<"年龄: "<<age<<endl;
}
};
// 声明类Person是类Student的虚基类
class Student:virtual public Person
{
protected:
string major; // 专业
public:
Student(string name1, char sex1, int age1, string major1): // 构造函数
Person(name1,sex1,age1)
{ major = major1; }
void print()
{
Person::print();
cout<<"专业: "<<major<<endl;
}
};
// 声明类Person是类Employee的虚基类
class Employee:virtual public Person
{
protected:
string dept; // 部门
public:
Employee(string name1, char sex1, int age1, string dept1): // 构造函数
Person(name1,sex1,age1)
{ dept = dept1; }
void print()
{
Person::print();
cout<<"部门: "<<dept<<endl;
}
};
// 声明类E_Student为 类Employee 和 类Studnet 的派生类
class E_Student:public Employee,public Student
{
public:
E_Student(string name1, char sex1, int age1, string major1, string dept1): // 构造函数
Person(name1,sex1,age1),Employee(name1,sex1,age1,dept1),Student(name1,sex1,age1,major1)
{ }
void print()
{
cout<<"姓名: "<<name<<endl;
cout<<"性别: "<<sex<<endl;
cout<<"年龄: "<<age<<endl;
cout<<"部门: "<<dept<<endl;
cout<<"专业: "<<major<<endl;
}
};
int main()
{
Student my_Student("李晓敏",'f',22,"应用数学");
cout<<"大学生: "<<endl;
my_Student.print();
cout<<"---------------------------"<<endl;
Employee my_Employee("黄百松",'m',55,"科研处");
cout<<"职工: "<<endl;
my_Employee.print();
cout<<"---------------------------"<<endl;
E_Student my_E_Student("张大明",'m',35,"教务处","计算机");
cout<<"在职大学生: "<<endl;
my_E_Student.print();
return 0;
}
i.类的继承:新的类从已有类那里得到已有的特性.
II.类的派生:从已有类产生新类的过程.
iii.已有类成为 基类 或 父类.
iv.产生的新类成为 派生类 或 子类.
二、为什么要使用继承?
已有学生类Student含有number(学号)、name(姓名)、score(成绩)等数据成员与成员函数print
现需要声明一个大学生类UStudent,它含有number(学号)、name(姓名)、score(成绩)以及major(专业)等数据成员与成员函数print1();
如果要重写一个大学生类,则这两个类都代码严重重复,为了提高代码的重用性,可以引入继承。
三、派生类的声明
class 派生类名:[继承方式] 基类名
{
派生类新增的数据成员和成员函数;
};
i.基类名:一个已经声明都类的名称
ii.派生类名:继承原有类的特性而生成的新类的名称.
III.继承方式:规定了如何访问从基类继承的成员,可以是关键字private、protected或public,分别表示私有继承、保护继承和公有继承.
注:如果不显式地给出继承方式关键字,系统默认为私有继承(private).
iv.类的继承方式制定了派生类成员以及类外对象对于从基类继承来的成员的访问权
四、基类成员在派生类中的访问属性
i.派生类可以继承基类中【除了构造函数与析构函数之外】的成员.
ii.基类成员在派生类中的访问属性(如表)
归纳得:
①基类中的私有成员.
无论哪种继承方式,基类中的私有成员不允许派生类的继承,即在派生类中是不可直接访问的.
②基类中的公有成员.
公有继承时,基类中的所有公有成员在派生类中仍以公有成员的身份出现的.
私有继承时,基类中的所有私有成员在派生类中都是以私有成员的身份出现的.
保护继承时,基类中的所有保护成员在派生类中都是以保护成员的身份出现的.
③基类中都保护成员.
公有继承时,基类中的所有保护成员在派生类中仍以保护成员的身份出现的.
私有继承时,基类中的所有保护成员在派生类中都是以私有成员的身份出现的.
保护继承时,基类中的所有保护成员在派生类中仍以保护成员的身份出现的.
五、派生类对基类成员的访问规则
派生类对基类成员的访问形式主要有以下两种:
①内部访问:由派生类中新增的成员函数对基类继承来的成员的访问.
②对象访问:在派生类外部,通过派生类的对象对从基类继承来的成员的访问.
i.私有继承的访问规则(如表)
ii.公有继承都访问规则(如表)
iii.保护继承的访问规则(如表)
六、派生类的构造函数和析构函数
i.简单的派生类的构造函数和析构函数的构造规则
①当基类的构造函数没有参数,或没有显式定义构造函数时,派生类可以不向基类传递参数,甚至可以不定义构造函数.
②当基类含有带参数的构造函数时,派生类必须定义构造函数,以提供把参数传递给基类构造函数的途径.
③在C++中,派生类构造函数的一般格式为:
派生类名(参数总表):基类名(参数表)
{
派生类新增数据成员的初始化语句;
}
ii.含有子对象的派生类的构造函数
①子对象:基类的对象
②在C++中,派生类中含有子对象时的构造函数的一般形式为:
派生类名(参数总表):基类名(参数表0),子对象名1(参数表1),...,子对象名n(参数表n)
{
派生类新增成员的初始化语句;
}
③说明:
1、当基类构造函数不带参数时,派生类不一定需要定义构造函数;然而当基类的构造函数哪怕只带有一个参数,它所有的派生类都必须定义构造函数,甚至所定义的生类构造函数都函数体可能为空,它仅仅起参数的传递作用.
2、若基类使用默认构造函数或不带参数都构造函数,则在派生类中定义构造函数时可略去“:基类构造函数名(参数表)”,此时若派生类也不需要构造函数,则可不定义造函数.
3、如果派生类的基类也是一个派生类,每个派生类只需负责其直接基类数据成员的初始化,依次上溯.
4、派生类都构造函数可以不显式地写出基类的构造函数.
5、要调用的基类构造函数的参数可在派生类的构造函数中明确地给出.
六、调整基类成员在派生类中的访问属性的其他方法
i.同名成员
派生类可以重新声明与基类成员同名的成员。在没有虚函数的情况下,如果在派生类中定义了与基类成员同名的成员,则称派生类成员覆盖了基类的同名成员此后若要再度使用基类都同名成员则需使用作用域标识符“::”--> 基类名::同名函数.
ii.访问声明
①有时程序员可能希望基类的个别成员能被派生类的对象直接访问,而不是通过派生类的公有成员函数间接访问.为此,C++提供了称为访问声明的特殊机制,可个别调整基类的某些成员在派生类中的访问属性,使之在派生类中保持原来的访问属性.
②访问声明的方法:
把基类的保护成员或公有成员直接写在私有派生类定义式中的同名段中,同时给成员名前冠以基类名和作用于标识符“::”. 例如:基类中的show()以A::show写进派生类B中
③使用访问声明时应注意:
1、数据成员也可以使用访问声明. 例如:A::X1;
2、访问声明中只含不带类型和参数的函数名或变量名.
3、访问声明不能改变成员在基类中的访问属性.
4、对于基类中的重载函数名,访问声明将对基类中所有同名函数起作用。这意味着对于重载函数使用访问声明时要慎重.
七、多继承与虚基类
通常,一个派生类有两个或多个基类,派生类从两个或多个基类中继承所需的属性。
i.声明多继承派生类的一般形式:
class 派生类名:继承方式1 基类名1,...,继承方式i 基类名i ,...,继承方式n 基类名n
{
派生类新增的数据成员和成员函数
};
ii.多继承派生类对基类成员的访问必须是无二义性的.若有二义性,要想办法消除.例如:
obj.X::f();
obj.Y::f();
iii.多继承派生类的构造函数与析构函数
多重继承构造函数定义的一般形式:
派生类名(参数总表) :基类名1(参数表1),...,基类名i(参数表i),...,基类名n(参数表n)
{
派生类新增成员的初始化语句
}
iv.析构函数在多继承派生类里是相互独立的.
虚基类:
i.作用:若果一个类有多个直接基类,而这些直接基类又有一个共同的基类,则在最底层的派生类中会保留这个间接的共同基类数据成员的多份同名成员.因此在访问时可能会产生二义性,虚基类便是为了消除二义性.
ii.声明:
class 派生类名:virtual 继承方式 类名
{ ...... }
iii.虚基类初始化时应注意:
①如果虚基类中定义有带形参的构造函数并没有定义默认形式的构造函数 -->整个继承结构中,所有派生类都必须在构造函数的成员初始化列表中列出对虚基类构造函数的调用,以初始化虚基类中定义的数据成员.
②建立对象时,如果该对象中含有从虚基类继承来的成员,则虚基类的成员是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。该派生类的其他基类对虚基类构造函数的调用都自动被忽略.
③若同一层次中包含虚基类和非虚基类,先调用虚基类的构造函数,再调用非虚基类的构造函数,最后调用派生类的构造函数。
④对于多个虚基类(或者 非虚基类 ),构造函数的执行顺序仍然是先左右后,自上而下.
⑥若虚基类由非虚基类派生而来,则仍然先调用基类构造函数,再调用派生类的构造函数.
八、赋值兼容规则
i.赋值兼容:不同类型数据之间的自动转换和赋值. 这种关系在基类和派生类对象之间也存在.
即 基类对象的任何地方,都可以使用其子类对象来替代.
例:类Derived公有继承类Base
① Base b;
Derived d;
b=d; // 用派生类Derived的对象d对基类对象b赋值
②Base b;
Derived d;
Base &br=d; // 用派生类Derived的对象d对基类Base的对象的引用br进行初始化
③ Derived d;
Base *bp = &d; // 把派生类对象的地址&d赋给指向基类的指针bp,也就是说,使指向基类对象的指针bp也可以指向派生类对象d.
④..........
void fun(Base &bb) // 基类的对象作为参数
{ cout<<bb.i<<endl; } // 可以用派生类Derived的对象赋给基类的数据成员i的值
ii.赋值兼容规则举例:
#include <iostream> using namespace std; // 声明基类Base class Base { public: int i; Base (int x) // 基类的构造函数 { i = x; } void disp() { cout<<"Base "<<i<<endl; } }; // 声明公有派生类Derived class Derived:public Base { public: Derived(int x):Base(x) // 派生类的构造函数 { }; void disp() { cout<<"Derived "<<i<<endl; } }; int main() { Base b1(11); // 定义基类对象b1 b1.disp(); Derived d1(22); // 定义派生类对象d1 b1 = d1; // 用派生类对象d1给基类对象b1赋值 b1.disp(); Derived d2(33); // 定义派生类对象d2 Base &b2 = d2; // 用派生类对象d2来初始化基类对象的引用b2 b2.disp(); Derived d3(44); // 定义派生类对象d3 Base *b3 = &d3; // 把派生类对象的地址赋给指向基类的指针b3 b3->disp(); Derived *d4 = new Derived(55); // 定义派生类的无名对象,将该对象的地址 // 存放在派生类对象指针d4中 Base *b4 = d4; // 把指向派生类对象指针d4的值赋给基类对象指针b4 b4->disp(); delete d4; return 0; }
总结:··程序举例
/*
语言:C++ 编译环境:Visual C++6.0
继承与派生:程序举例
声明一个共同的基类Person,它包含了所有派生类共有的数据,职工类Employee
和学生类Student为虚基类Person的派生类,在职大学生类E_Student是职工类Employee
和学生类Student的共同派生类。每个类定义了一个相对于特定类不同的print()函数,
输出各类的数据成员.
*/
#include <iostream>
#include <string>
using namespace std;
// 声明基类Person
class Person
{
protected:
string name; // 姓名
char sex; // 性别
int age; // 年龄
public:
Person(string name1, char sex1, int age1) // 构造函数
{
name = name1;
sex = sex1;
age = age1;
}
void print()
{
cout<<"姓名: "<<name<<endl;
cout<<"性别: "<<sex<<endl;
cout<<"年龄: "<<age<<endl;
}
};
// 声明类Person是类Student的虚基类
class Student:virtual public Person
{
protected:
string major; // 专业
public:
Student(string name1, char sex1, int age1, string major1): // 构造函数
Person(name1,sex1,age1)
{ major = major1; }
void print()
{
Person::print();
cout<<"专业: "<<major<<endl;
}
};
// 声明类Person是类Employee的虚基类
class Employee:virtual public Person
{
protected:
string dept; // 部门
public:
Employee(string name1, char sex1, int age1, string dept1): // 构造函数
Person(name1,sex1,age1)
{ dept = dept1; }
void print()
{
Person::print();
cout<<"部门: "<<dept<<endl;
}
};
// 声明类E_Student为 类Employee 和 类Studnet 的派生类
class E_Student:public Employee,public Student
{
public:
E_Student(string name1, char sex1, int age1, string major1, string dept1): // 构造函数
Person(name1,sex1,age1),Employee(name1,sex1,age1,dept1),Student(name1,sex1,age1,major1)
{ }
void print()
{
cout<<"姓名: "<<name<<endl;
cout<<"性别: "<<sex<<endl;
cout<<"年龄: "<<age<<endl;
cout<<"部门: "<<dept<<endl;
cout<<"专业: "<<major<<endl;
}
};
int main()
{
Student my_Student("李晓敏",'f',22,"应用数学");
cout<<"大学生: "<<endl;
my_Student.print();
cout<<"---------------------------"<<endl;
Employee my_Employee("黄百松",'m',55,"科研处");
cout<<"职工: "<<endl;
my_Employee.print();
cout<<"---------------------------"<<endl;
E_Student my_E_Student("张大明",'m',35,"教务处","计算机");
cout<<"在职大学生: "<<endl;
my_E_Student.print();
return 0;
}
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- 关于指针的一些事情
- c++ primer 第五版 笔记前言
- share_ptr的几个注意点
- Lua中调用C++函数示例
- Lua教程(一):在C++中嵌入Lua脚本
- Lua教程(二):C++和Lua相互传递数据示例
- C++联合体转换成C#结构的实现方法
- C++高级程序员成长之路
- C++编写简单的打靶游戏
- C++ 自定义控件的移植问题
- C++变位词问题分析
- C/C++数据对齐详细解析
- C++基于栈实现铁轨问题
- C++中引用的使用总结
- 使用Lua来扩展C++程序的方法
- C++中调用Lua函数实例
- Lua和C++的通信流程代码实例
- C与C++之间相互调用实例方法讲解
- 解析C++中派生的概念以及派生类成员的访问属性