C++语言学习(十)——继承与派生
C++语言学习(十)--继承与派生
一、类之间的关系
1、类之间的组合关系
组合关系是整体与部分的关系。
组合关系的特点:
A、将其它类的对象作为当前类的成员使用
B、当前类的对象与成员对象的生命周期相同
C、成员对象在用法上与普通对象相同
Computer类由其它多个部件类组合而成,当Computer销毁时,其它部件对象同时销毁。
#include using namespace std;class Memory{public: Memory() { cout << "Memory()" << endl; } ~Memory() { cout << "~Memory()" << endl; }};class Disk{public: Disk() { cout << "Disk()" << endl; } ~Disk() { cout << "~Disk()" << endl; }};class CPU{public: CPU() { cout << "CPU()" << endl; } ~CPU() { cout << "~CPU()" << endl; }};class MainBoard{public: MainBoard() { cout << "MainBoard()" << endl; } ~MainBoard() { cout << "~MainBoard()" << endl; }};class Computer{ Memory mMem; Disk mDisk; CPU mCPU; MainBoard mMainBoard;public: Computer() { cout << "Computer()" << endl; } void power() { cout << "power()" << endl; } void reset() { cout << "reset()" << endl; } ~Computer() { cout << "~Computer()" << endl; }};int main(int argc, char *argv[]){ Computer c; c.reset(); return 0;}
2、类之间的继承关系
继承关系是类之间的父子关系。继承关系的特点如下:
A、子类拥有父类的所有属性和行为
B、子类也是一种特殊的父类
C、子类对象可以当父类对象使用
D、子类中可以添加父类没有的属性和方法
E、子类对象可以直接初始化为父类对象
F、子类对象可以直接赋值给父类对象
G、继承是面向对象编程中代码复用的重要手段
#include using namespace std;class Parent{public: Parent(int i = 0) { member = i; } void method() { cout << "member = " << member << endl; }private: int member;};class Child : public Parent{public: Child(int i = 0, int j = 0):Parent(i) { childMember = j; } void childMethod() { method(); cout << "childMember = "<< childMember << endl; }private: int childMember;};int main(int argc, char *argv[]){ Child child(1,2); child.method(); child.childMethod(); return 0;}
3、类成员的访问级别选择
定义类时根据类的设计需求确定成员的访问级别,规则如下:
A、public修饰的成员可以被外部访问。
B、protected修饰的成员不可以被外部访问,但可以被子类访问。
C、private修饰的成员不可以被外部和子类访问。
#include using namespace std;class Parent{public: Parent(int a = 0, int b = 0, int c = 0) { pub = a; pro = b; pri = c; } void method() { cout << "pub = " << pub << endl; cout << "pro = " << pro << endl; cout << "pri = " << pri << endl; }public: int pub;protected: int pro;private: int pri;};class Child : public Parent{public: Child(int a = 0, int b = 0):Parent(a, b) { } void childMethod() { pub = 100; pro = 200; //pri = 300;//error,子类不可见 }};int main(int argc, char *argv[]){ Parent parent(1,2); parent.pub = 1000; //parent.pro = 2000;//error,外部不可见 //parent.pri = 3000;//error,外部不可见 Child child(1,2); child.pub = -1000; //child.pro = -2000;//error,外部不可见 //child.pri = -3000;//error,外部不可见 child.childMethod(); return 0;}
二、继承
1、继承简介
在C++编程中,软件可重用性(software reusability)是通过继承(inheritance)机制来实现的。类的继承,是新的类从已有类得到已有的特性。从已有类产生新类的过程就是类的派生。原有的类称为基类或父类,产生的新类称为派生类或子类。
继承是一种封装模型之间关系的抽象,是不同封装模型的层次分类。
2、派生类的定义
派生类的声明:
class 派生类名:[继承方式] 基类名{ 派生类成员声明;};
如果一个派生类同时有多个基类,称为多重继承;如果派生类只有一个基类,称为单继承。
3、继承方式
继承方式规定了如何访问基类继承的成员。继承方式有public、 private,、protected。继承方式指定了派生类成员以及类外对象对于从基类继承来的成员的访问权限。继承方式如下:
A、公有继承
基类的公有成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类的私有成员。
B、私有继承
基类的公有成员和保护成员在派生类中成了私有成员,其私有成员仍为基类的私有成员。
C、保护继承
基类的公有成员和保护成员在派生类中成了保护成员,其私有成员仍为基类的私有成员。
不同继承方式下父类成员的访问级别如下:
继承成员的访问属性 = Max{继承方式,父类成员的访问属性}
4、默认继承方式
使用class关键字定义派生类时,默认继承方式为private。
使用struct关键字定义派生类时,默认继承方式为public。
C++工程项目中,通常只使用public继承方式。
5、不同继承方式示例
#include using namespace std;class Parent{public: Parent(int a = 0, int b = 0, int c = 0) { pub = a; pro = b; pri = c; }public: int pub;protected: int pro;private: int pri;};class ChildA : public Parent{public: ChildA(int a = 0, int b = 0):Parent(a, b) { } void print() { cout << "pub = " << pub << endl; cout << "pro = " << pro << endl; //cout << "pri = " << pri << endl;//error,私有成员不可见 }};class ChildB : protected Parent{public: ChildB(int a = 0, int b = 0):Parent(a, b) { } void print() { cout << "pub = " << pub << endl; cout << "pro = " << pro << endl; //cout << "pri = " << pri << endl;//error,私有成员不可见 }};class ChildC : private Parent{public: ChildC(int a = 0, int b = 0):Parent(a, b) { } void print() { cout << "pub = " << pub << endl; cout << "pro = " << pro << endl; //cout << "pri = " << pri << endl;//error,私有成员不可见 }};//默认继承方式class ChildD : Parent{public: ChildD(int a = 0, int b = 0):Parent(a, b) { } void print() { cout << "pub = " << pub << endl; cout << "pro = " << pro << endl; //cout << "pri = " << pri << endl;//error,私有成员不可见 }};int main(int argc, char *argv[]){ ChildA childa(1000,2000); childa.pub = 0; //childa.pro = 2000;//error,外部不可见 //childa.pri = 3000;//error,外部不可见 childa.print(); ChildB childb(1001,2001); //childb.pub = 1001;//error,外部不可见 //childb.pro = 2001;//error,外部不可见 //childb.pri = 3001;//error,外部不可见 childb.print(); ChildC childc(1002,2002); //childc.pub = 1002;//error,外部不可见 //childc.pro = 2002;//error,外部不可见 //childc.pri = 3002;//error,外部不可见 childc.print(); ChildD childd(1003,2003); //childd.pub = 1003;//error,外部不可见 //childd.pro = 2003;//error,外部不可见 //childd.pri = 3003;//error,外部不可见 childd.print(); return 0;}
三、派生类
1、派生类简介
派生类中的成员包含两大部分,一类是从基类继承过来的,一类是自己增加的成员。从基类继承的成员表现其共性,而新增的成员体现其个性。
派生类中由基类继承而来的成员的初始化工作还是由基类的构造函数完成,然后派生类中新增的成员在派生类的构造函数中初始化。派生类没有继承基类的构造函数和析构函数。
2、派生类的构造函数
派生类的构造函数语法如下:
派生类名::派生类名(参数总表):基类名(参数表),内嵌子对象(参数表){ 派生类新增成员的初始化语句; //也可出现在参数列表中}
子类构造函数必须对继承来的成员进行初始化,可以直接通过初始化列表或赋值方式进行,也可以调用父类构造函数进行初始化。
父类构造函数在子类中的调用方式:
A、默认调用,适用于无参构造函数和使用默认参数的构造函数
B、显示调用,适用于所有父类构造函数,通过初始化列表调用
#include #include using namespace std;class Parent{public: Parent() { cout << "Parent()" << endl; } Parent(string s) { cout << "Parent(string s): " << s << endl; } ~Parent() { cout << "~Parent()" << endl; }};class Child : public Parent{public: //隐式调用父类的无参构造函数或默认参数构造函数 Child() { cout << "Child()" << endl; } //显示调用父类构造函数,如果不显示调用,默认调用 //父类的无参构造函数或默认参数构造函数 Child(string s):Parent(s) { name = s; cout << "Child(): " << s << endl; } ~Child() { cout << "~Child(): " << name << endl; }private: string name;};int main(int argc, char *argv[]){ Child child1("bauer"); // output: // Parent(string s): bauer // Child(): bauer // ~Child(): bauer // ~Parent() return 0;}
构造函数的调用顺序不以初始化列表中的调用顺序进行,而是根据类中对成员变量声明的顺序进行调用。如果基类中没有默认构造函数(无参),那么在派生类的构造函数中必须显示调用基类构造函数,以初始化基类成员。
派生类对象创建时构造函数的调用顺序如下:
A、调用基类构造函数,调用顺序按照基类被继承时声明的顺序(从左到右);
B、调用成员变量的构造函数,调用顺序按照成员变量在类中声明的顺序;
C、调用派生类自身的构造函数。
子类构造函数中,要么显示的调用父类的构造函数(传参),要么隐式的调用。发生隐式调用时,父类要有无参构造函数或是可以包含无参构造函数的默认参数函数。子类构造函数必须对继承而来的成员进行初始化,可以通过初始化列表或者赋值的方法进行初始化,也可以通过调用父类构造函数进行初始化。
子类对象创建时构造函数的调用顺序如下:
A、调用父类的构造函数
B、调用成员变量的构造函数
C、调用类自身的构造函数
#include using namespace std;class Parent{public: Parent() { cout << "Parent()" << endl; } Parent(string s) { cout << "Parent(string s): " << s << endl; } ~Parent() { cout << "~Parent()" << endl; }};class Member{public: Member(int i = 0) { this->i = i; cout << "Member(int i = 0) i = " << i << endl; } ~Member() { cout << "~Member() i = " << i << endl; }private: int i;};class Child : public Parent{public: //隐式调用父类的无参构造函数或默认参数构造函数 Child() { cout << "Child()" << endl; } //显示调用父类构造函数,如果不显示调用,默认调用 //父类的无参构造函数或默认参数构造函数 Child(string s, int i):Parent(s),member(i) { name = s; cout << "Child(): " << s << endl; } ~Child() { cout << "~Child(): " << name << endl; }private: string name; Member member;};int main(int argc, char *argv[]){ Child childA("bauer", 10); // output: // Parent(string s): bauer // Member(int i = 0) i = 10 // Child(): bauer // ~Child(): bauer // ~Member() i = 10 // ~Parent() return 0;}
3、派生类的拷贝构造函数
派生类拷贝构造函数的定义如下:
派生类::派生类(const 派生类& another):基类(another),派生类新成员(another.新成员){ }
派生类中的默认拷贝构造函数会调用父类中默认或自实现拷贝构造函数,若派生类中自实现拷贝构造函数,则必须显示的调用父类的拷贝构造函数。
#include #include using namespace std;class Student{public: Student(string sn,int n,char s) { name = sn; num = n; sex = s; cout << "Student(string sn,int n,char s)" << endl; } Student(const Student& another) { name = another.name; num = another.num; sex = another.sex; } ~Student() { cout << "~Student()" << endl; } void print() { cout << name << endl; cout << num << endl; cout << sex << endl; }private: string name; int num; char sex;};class Graduate : public Student{public: Graduate(string sn,int in,char cs,int fs):Student(sn, in, cs) { salary = fs; cout << "Graduate(string sn,int in,char cs,float fs)" << endl; } ~Graduate() { cout << "~Graduate()" << endl; } Graduate(const Graduate& another):Student(another) { salary = another.salary; } void display() { print(); cout<
4、派生类的赋值运算符重载
派生类的赋值操作符重载函数的定义如下:
子类& 子类::operator=(const 子类& another){ if(this != &another){ 父类::operator =(another); // 调用父类的赋值运算符重载 this->salary = another.salary;//子类成员初始化} return * this;}
派生类的如果没有实现赋值操作符函数,C++编译器会提供一个默认赋值操作符重载函数,默认的赋值操作符重载函数会调用父类的赋值操作符重载函数(编译器提供的默认赋值操作符重载函数或是开发者提供的赋值操作符重载函数)。
#include #include using namespace std;class Student{public: Student(string sn,int n,char s) { name = sn; num = n; sex = s; cout << "Student(string sn,int n,char s)" << endl; } Student(const Student& another) { name = another.name; num = another.num; sex = another.sex; } Student& operator = (const Student& another) { if(this != &another) { name = another.name; num = another.num; sex = another.sex; } return *this; } ~Student() { cout << "~Student()" << endl; } void print() { cout << name << endl; cout << num << endl; cout << sex << endl; }private: string name; int num; char sex;};class Graduate : public Student{public: Graduate(string sn,int in,char cs,int fs):Student(sn, in, cs) { salary = fs; cout << "Graduate(string sn,int in,char cs,float fs)" << endl; } ~Graduate() { cout << "~Graduate()" << endl; } Graduate(const Graduate& another):Student(another) { salary = another.salary; } void display() { print(); cout<
派生类如果实现了赋值操作符重载函数,则需在赋值操作符重载函数内显示地调用父类的赋值操作符重载函数。
#include #include using namespace std;class Student{public: Student(string sn,int n,char s) { name = sn; num = n; sex = s; cout << "Student(string sn,int n,char s)" << endl; } Student(const Student& another) { name = another.name; num = another.num; sex = another.sex; } Student& operator = (const Student& another) { if(this != &another) { name = another.name; num = another.num; sex = another.sex; } return *this; } ~Student() { cout << "~Student()" << endl; } void print() { cout << name << endl; cout << num << endl; cout << sex << endl; }private: string name; int num; char sex;};class Graduate : public Student{public: Graduate(string sn,int in,char cs,int fs):Student(sn, in, cs) { salary = fs; cout << "Graduate(string sn,int in,char cs,float fs)" << endl; } ~Graduate() { cout << "~Graduate()" << endl; } Graduate(const Graduate& another):Student(another) { salary = another.salary; } Graduate& operator = (const Graduate& another) { if(this != &another) { Student::operator =(another); salary = another.salary; } return *this; } void display() { print(); cout<
5、派生类的析构函数
派生类的析构函数的功能是在对象销毁前进行一些必要的清理工作,析构函数没有类型,也没有参数。析构函数的调用顺序与构造函数相反。
析构函数只有一种,无重载,无默参。
子类对象销毁时析构函数的调用顺序如下:
A、调用类自身的析构函数
B、调用成员变量的析构函数
C、调用父类的析构函数
四、父类与子类的同名覆盖
子类定义父类中的同名成员时发生同名覆盖,规则如下:
A、子类可以定义父类中的同名成员(成员变量和成员函数)
B、子类中的成员将隐藏父类中的同名成员
C、父类中的同名成员依然存在于子类中
D通过作用域访问符访问父类中的同名成员
如果某派生类的多个基类拥有同名的成员,派生类又新增与基类同名的成员,派生类成员将shadow(隐藏)所有基类的同名成员,需要作用域的调用方式才能调用基类的同名成员。
#include using namespace std;class Parent{public: int m_count; void print() { cout << &m_count << endl; }};class Child : public Parent{public: int m_count; void print() { cout << &(Parent::m_count) << endl; cout << &m_count << endl; }};int main(int argc, char *argv[]){ Parent p; p.print(); cout << &p.m_count << endl; Child child; child.print(); //子类对象的父类同名成员变量访问 cout << &child.Parent::m_count <
函数重载发生在同一作用域 ,父类和子类的同名函数不构成函数重载,属于同名覆盖,子类会隐藏父类中的同名成员。
子类可以定义父类中的同名成员,子类中的成员将隐藏父类中的同名成员,父类中的同名成员依然存在子类中,通过作用域分辨符::访问父类中的同名成员。
子类中的成员函数将隐藏父类中的同名成员函数,子类无法重载父类中同名成员函数,使用作用域分辨符可以访问父类中的同名成员函数。
#include using namespace std;class Parent{public: int mi; void add(int i) { mi += i; } void add(int a, int b) { mi += (a + b); }};class Child : public Parent{public: int mi; void add(int x, int y, int z) { mi += (x + y + z); }};int main(int argc, char *argv[]){ Parent p; p.add(1); p.add(1,2); Child child; child.add(1,2,3); //child.add(1);//error child.Parent::add(1); //child.add(1,2);//error child.Parent::add(1,2); return 0;}
五、赋值兼容原则
1、赋值兼容原则简介
赋值兼容原则是指子类对象可以作为父类对象使用。
赋值兼容原则的特点如下:
A、子类对象可以直接赋值给父类对象
B、子类对象可以直接初始化父类对象
C、父类指针可以直接指向子类对象
D、父类引用可以直接引用子类对象
当使用父类指针指向子类对象、父类引用对子类对象进行引用时,子类对象退化为父类对象,只能访问父类中定义的成员,因此可以直接访问被子类覆盖的同名成员。
#include using namespace std;class Parent{public: int mi; void add(int i) { mi += i; } void add(int a, int b) { mi += (a + b); }};class Child : public Parent{public: int mi; void add(int x, int y, int z) { mi += (x + y + z); }};int main(int argc, char *argv[]){ Child child; Parent p = child; p.add(1); p.add(1,2); Parent& rp = child; rp.add(1); rp.add(1,2); //rp.add(1,2,3);//error Parent* pp = &child; pp->add(1); pp->add(1,2); //pp->add(1,2,3);//error return 0;}
2、函数重写
子类中重定义父类中已经存在的成员函数,称为函数重写。
函数重写是同名覆盖的一种特殊情况。
#include using namespace std;class Parent{public: int mi; void add(int i) { mi += i; } void add(int a, int b) { mi += (a + b); } void print() { cout << "Parent" << endl; }};class Child : public Parent{public: int mi; //函数重写 void add(int x, int y) { mi += (x + y); } //函数重写 void print() { cout << "Child" << endl; }};int main(int argc, char *argv[]){ Child child; Parent p = child; p.add(1); p.add(1,2); Parent& rp = child; rp.add(1); rp.add(1,2); rp.print();//Parent Parent* pp = &child; pp->add(1); pp->add(1,2); pp->print();//Parent return 0;}
C++是一种静态编译语言,在编译期间,C++编译器只能根据指针的类型判断所指向的对象。根据赋值兼容原则,C++编译器认为父类指针指向的是父类对象,父类引用是对父类对象的引用,因此编译结果只能是调用父类中定义的同名函数。