继承与派生
面向对象程序设计4个主要特征:抽象,封装,继承,多态性
c++通过继承实现可重用性。有时两个类的内容基本相同或有一部分相同,这时可以利用原来声明的类作为基础,再加上新的内容即可,减少了工作量。
面向对象程序设计4个主要特征:抽象,封装,继承,多态性
c++通过继承实现可重用性。有时两个类的内容基本相同或有一部分相同,这时可以利用原来声明的类作为基础,再加上新的内容即可,减少了工作量。
//比如有一个类
class student { public: void display() {cout<<"num:"<num<<endl; cout<<"name"<<nam<<endl; cout<<"sex"<<sex<<endl; } private: int num; string nam; char sex; };
现在想增加student 类的信息,可以重新写
class student1 { public: void display() {cout<<"num:"<num<<endl; cout<<"name"<<nam<<endl; cout<<"sex"<<sex<<endl; cout<<"age"<<age<<endl; cout<<"address"<<addr<<endl; } private: int num; string nam; char sex; int age; char addr{20}; };
也可以通过继承来实现
class student1: public student //声明基类是student, { public: void display() //新增加的成员函数 {cout<<"age"<<age<<endl; cout<<"address"<<addr<<endl;} private: int age; //新增加的数据成员 char addr{20}; //新增加的数据成员 };
声明派生类的一般形式
class 派生类名 :[继承方式] 基类名
{
派生类新增加的成员
};
{
派生类新增加的成员
};
[继承方式]有 public(公用的) private(私有的) protected(受保护的)
如果不写,则默认为private
如果不写,则默认为private
派生类的构成过程:
1. 从基类接收成员(派生类把基类全部成员[不包括构造函数,析构函数]接收过来)
2. 调整从基类接收的成员(通过指定基类的继承方式来改变基类成员在派生类中访问属性)
3. 在声明派生类时增加的成员
1. 从基类接收成员(派生类把基类全部成员[不包括构造函数,析构函数]接收过来)
2. 调整从基类接收的成员(通过指定基类的继承方式来改变基类成员在派生类中访问属性)
3. 在声明派生类时增加的成员
派生类成员的访问属性:
1. public 基类的公有成员和保护成员 在派生类中保持原有的访问属性,私有成员仍为基类私有,
只有基类的成员函数可以引用它,而不能被派生类的成员函数引用,因此就成为派生类中的不可访问的成员
2. private 私有继承方式建立的派生类称为私有派生类,其基类称为私有基类。
私有基类的公用成员和保护成员在派生类中访问属性相当于派生类中的私有成员,( 派生类的成员函数能访问它们,而在派生类外不能访问它们。)
私有基类的私有成员在派生类中成为不可访问的成员,只有基类的成员函数可以引用它们。
1. public 基类的公有成员和保护成员 在派生类中保持原有的访问属性,私有成员仍为基类私有,
只有基类的成员函数可以引用它,而不能被派生类的成员函数引用,因此就成为派生类中的不可访问的成员
2. private 私有继承方式建立的派生类称为私有派生类,其基类称为私有基类。
私有基类的公用成员和保护成员在派生类中访问属性相当于派生类中的私有成员,( 派生类的成员函数能访问它们,而在派生类外不能访问它们。)
私有基类的私有成员在派生类中成为不可访问的成员,只有基类的成员函数可以引用它们。
3.
protected 基类的公有成员和保护成员 在派生类中变为保护成员,私有成员仍为基类私有
保护成员 ( 不能被外界引用,但可以被派生类的成员引用)
保护成员 ( 不能被外界引用,但可以被派生类的成员引用)
#include<iostream> #include<string> using namespace std; class student //基类 { public: student(int n, string nam, char s) //定义基类构造函数 { num = n; name = nam; sex = s; } ~student() {} //基类的虚构函数 void display() { cout << "num:" << num << endl; cout << "name" << name << endl; cout << "sex" << sex << endl; } protected: int num; string name; char sex; }; class student1 : public student //公共派生类 { public: student1(int n, string nam, char s, int a) :student(n, nam, s) //派生类构造函数 { age = a; } void display1() //新增加的成员函数 { display(); //调用基类函数访问基类私有数据 cout << "age:" << age << endl; } private: int age; //新增加的数据成员 }; int main() { student1 stud(10010, "liu", 'm', 29); stud.display1(); return 0; }
student1虽然继承了student类,但是在派生类中的函数不能调用基类的私有成员,
不过可以通过
派生类成员函数调用基类函数来间接访问基类的私有成员
派生类的构造函数
因为基类的构造函数是不能继承的,所以在声明派生类时,派生类并没有把基类的构造函数继承过来,
因此,对继承过来的基类成员初始化的工作也要由派生类的构造函数承担。
思路:在执行派生类的构造函数时,调用基类的构造函数。
因此,对继承过来的基类成员初始化的工作也要由派生类的构造函数承担。
思路:在执行派生类的构造函数时,调用基类的构造函数。
派生类构造函数一般形式:
派生类构造函数名 (总参数表):基类构造函数名(参数表)
{派生类中新增数据成员初始化语句}
派生类构造函数名 (总参数表):基类构造函数名(参数表)
{派生类中新增数据成员初始化语句}
或者:派生类构造函数名 (总参数表):基类构造函数名(参数表),...{}
如:student1(int n, string nam, char s, int a) :student(n, nam, s),age(a){}
派生类构造函数名后面的参数表中包括参数类型和参数名 如:(int n)
而基类构造函数名后面括号内的参数表只有参数名,而不包括类型名 如:(n,nam,s)
这是因为这里不是定义基类构造函数,而是对基类函数进行调用这些参数是实参而不是形参。
而基类构造函数名后面括号内的参数表只有参数名,而不包括类型名 如:(n,nam,s)
这是因为这里不是定义基类构造函数,而是对基类函数进行调用这些参数是实参而不是形参。
在建立一个对象时,执行构造函数的顺序是,1.派生类先调用基类构造函数,
2.执行派生类构造函数本身
2.执行派生类构造函数本身
有子对象的派生类的构造函数
类中的数据成员可以是标准类型(int,char ),系统提供的类型(string),还可以包含类对象
class student
{....
};
class student1
{
int num;
student monitor; //定义子对象
};
类中的数据成员可以是标准类型(int,char ),系统提供的类型(string),还可以包含类对象
class student
{....
};
class student1
{
int num;
student monitor; //定义子对象
};
上面中的对象monitor 就是类student1的子对象。
数据成员初始化时如何对子对象初始化?
#include<iostream> #include<string> using namespace std; class student //基类 { public: student(string nam) //定义基类构造函数 { name = nam; } ~student() {} //基类的虚构函数 void display() { cout << "the name is:" << name << endl; } protected: string name; }; class student1 : public student //公共派生类 { public: student1(string nam,string nam1 ,int a) :student(nam),monitor(nam1) //派生类构造函数,子对象的初始化是在建立派生类时通过调用派生类构造函数来实现的 { age = a; } void display1() //新增加的成员函数 { display(); cout << "age:" << age << endl; } void show_monitor() { cout<<"class monior is :"<<endl; monitor.display(); } private: int age; //新增加的数据成员 student monitor; }; int main() { student1 stud("A171", "li", 29); stud.display1(); return 0; }
以上派生类构造函数实现了
1.对基类数据成员初始化
2.对子对象数据成员初始化
3.对派生类数据成员初始化
2.对子对象数据成员初始化
3.对派生类数据成员初始化
多重派生时的构造函数
class A { A(int a,int b,int c); }; class B:public A { B(int a,int b,int c,int d ):A(a,b,c) { dive=d; } ... }; class C:public B { C(int a,int b,int c,int d,int e):B(a,b,c,d) { exchange=e; } };
不要列出每一层派生类的构造函数,只需写出其上一层派生类的构造函数即可。
派生类构造函数的特殊形式
1.当不需要对派生类新增的成员进行任何初始化操作时,派生类构造函数的函数体可以为空
2。如在基类中没有定义构造函数或定义了没有参数的构造函数,那么在定义派生类构造函数时可以不写基类构造函数
派生类的析构函数
在派生时,派生类是不能继承基类的析构函数的,也就需要通过派生类的析构函数去调用基类的析构函数。
派生类的析构函数对增加的成员进行清理工作,基类的清理工作仍然由基类的析构函数负责。
在执行派生类的析构函数时,系统会自动调用基类的析构函数和子对象的析构函数,对基类和子类进行清理。
派生类的析构函数对增加的成员进行清理工作,基类的清理工作仍然由基类的析构函数负责。
在执行派生类的析构函数时,系统会自动调用基类的析构函数和子对象的析构函数,对基类和子类进行清理。
多重继承
一个派生类中有两个或多个基类,派生类从两个或多个基类中继承所需的属性
声明多重继承的方法
class D :public A,public B,public C
{类D是新增的成员}
一个派生类中有两个或多个基类,派生类从两个或多个基类中继承所需的属性
声明多重继承的方法
class D :public A,public B,public C
{类D是新增的成员}
多重继承派生类的构造函数
与单继承形式基本相同,只是在初始化表中包含多个基类构造函数
派生类构造函数名(总参数表):基类1 构造函数(参数表),基类2 构造函数(参数表)...
{派生类中新增数据成员初始化语句}
{派生类中新增数据成员初始化语句}
多重继承引起的二义性问题
class A { public: int a; void display(); }; class B { public: int a; void display(); }; class C :public:A,public:B { public: int b; void show(); }; (为了简化,。没有写函数的定义) int main() { C c1; c1.a=3; //出错,编译器无法判别要访问的是哪个基类的a, c1.dispaly(); //出错,编译器无法判别要访问的是哪个基类的display() c1.A::a=3; //正确,使用基类名A来限定 }
如果类A和类B是从同一个基类派生的
class N { public: int a; void display(){ cout<<"A::a"<<a<<endl; } }; class A { public: int a1; void display(); }; class B { public: int a2; void display(); }; class C :public:A,public:B { public: int a3; void show(){ cout<<"a3="<<a3<<endl; } }; int main() { C c1; ... }
如何访问类A中从基类N继承下来的成员呢
c1.a //不行
c1.N::a //不行
以上两者均不能区别是类A从基类N继承的成员,还是类B从基类继承的成员
c1.A::a=3 //可行
c1.a //不行
c1.N::a //不行
以上两者均不能区别是类A从基类N继承的成员,还是类B从基类继承的成员
c1.A::a=3 //可行
虚函数
如果一个派生类有多个直接基类,而这些直接基类都有同一个共同基类,则最终会在派生类中保留该间接共同基类数据成员的多份同名成员。
c++提供虚基类的方法,使在继承间接共同基类时只保留一份成员
class A
{...
};
class B: virtual public A
{...
};
class c: virtual public A
{...
};
{...
};
class B: virtual public A
{...
};
class c: virtual public A
{...
};
虚基类的初始化
class A {A(int i){} //虚基类构造函数,有一个参数 }; class B: virtual public A {B(int i):A(i){} //在初始化表对虚基类初始化 }; class c: virtual public A { c (int i):A(i){} };、 class d: public B,public c { d(int i):A(i),B(i),c(i){} //对初始化表中所有基类初始化 };
规定:在最后的派生类中不仅要负责对直接基类初始化,还要对虚基类初始化。