c++语言类的基础(3)——类的多继承及其构造函数和析构函数

C++ 语言类的继承

1、 继承(Inheritance)可以理解为一个类从另一个类获取成员变量和成员函数的过程。例如类 B 继承于类 A,那么 B 就拥有 A 的成员变量和成员函数。

2、继承机制是面向对象程序设计是代码复用的重要手段,它允许程序员在保持类原有特性基础下,进行扩展增加功能。这样产生新的类,称为派生类,继承体现了面向对象设计的层次结构,体现了由简单到复杂的认知过程。

3、在C++中,派生(Derive)和继承是一个概念,只是站的角度不同。继承是儿子接收父亲的产业,派生是父亲把产业传承给儿子。

4、被继承的类称为父类或基类,继承的类称为子类或派生类。“子类”和“父类”通常放在一起称呼,“基类”和“派生类”通常放在一起称呼。

5、派生类除了拥有基类的成员,还可以定义自己的新成员,以增强类的功能。

继承的使用场景:

  1. 当你创建的新类与现有的类相似,只是多出若干成员变量或成员函数时,可以使用继承,这样不但会减少代码量,而且新类会拥有基类的所有功能。
  2. 当你需要创建多个类,它们拥有很多相似的成员变量或成员函数时,也可以使用继承。可以将这些类的共同成员提取出来,定义为基类,然后从基类继承,既可以节省代码,也方便后续修改成员。

继承的三种形式

1) 公有继承——public:基类的公有变为派生类的公有,基类的保护变为派生类的保护,私有派生类不可访问

1、	基类中所有 public 成员在派生类中为 public 属性;
2、	基类中所有 protected 成员在派生类中为 protected 属性;
3、	基类中所有 private 成员在派生类中不能使用。
4、	在派生类中访问基类 private 成员的唯一方法就是借助基类的非 private 成员函数,如果基类没有非 private 成员函数,那么该成员在派生类中将无法访问。

2) 私有继承——private:基类的公有变为派生类的私有,基类的保护变为派生类的私有,基类的私有不可访问

1、	基类中的所有 public 成员在派生类中为 protected 属性;
2、	基类中的所有 protected 成员在派生类中为 protected 属性;
3、	基类中的所有 private 成员在派生类中不能使用。

3) 保护继承——protect:基类的公有变为派生类的保护,基类的保护变为派生类的保护,基类的私有不可访问

1、	基类中的所有 public 成员在派生类中均为 private 属性;
2、	基类中的所有 protected 成员在派生类中均为 private 属性;
3、	基类中的所有 private 成员在派生类中不能使用。

源代码(1)

#include<iostream>
using namespace std;

class people{
	protected:
		int age;
		char *name;
		int hight;
	public:
		void get_people(int old,char *str,int h);
};

void people::get_people(int old,char *str,int h)
{
	age = old;
	name = str;
	hight = h;
}

class father:public people{
	protected:
		char *work;
		char *hobby;
	public:
		void get_father(char *str1,char *str2);
};

void father::get_father(char *str1,char *str2)
{
	work = str1;
	hobby = str2;
}

class son:public father{
	private:
		int Engligh_score;
		int math_score;
		char *son_name; 
	public:
	void get_son(char *str3,int x,int y);
	void show();
};

void son::get_son(char *str3,int x,int y)
{
	son_name = str3;
	Engligh_score = x;
	math_score = y;
	cout<<"初始化句子完成:"<<endl;
}

void son::show()
{
	cout<<"一个父亲的名字是"<<name<<endl<<"他的年龄是"<<age<<" "<<"他的身高是"<<hight<<endl;
	cout<<"他"<<work<<endl<<hobby<<endl;
	cout<<son_name<<endl<<"他考的英语成绩是"<<Engligh_score<<" "<<"数学成绩是"<<math_score<<endl; 
}

int main()
{
	son text;
	text.get_people(45,"小明",176);
	text.get_father("工作是程序员","他最大的爱好是坐等收钱");
	text.get_son("小华是小明的儿子",89,87);
	text.show();
	return 0;
}

源代码(2)

#include<iostream>
using namespace std;

//基类 Pelple
class People{
public:
    void setname(char *name);
    void setage(int age);
    char *getname();
    int getage();
private:
    char *m_name;
    int m_age;
};
void People::setname(char *name){ m_name = name; }
void People::setage(int age){ m_age = age; }
char* People::getname(){ return m_name; }
int People::getage(){ return m_age;}

//派生类 Student
class Student: public People{
public:
    void setscore(float score);
    float getscore();
private:
    float m_score;
};
void Student::setscore(float score){ m_score = score; }
float Student::getscore(){ return m_score; }

int main(){
    Student stu;
    stu.setname("小明");
    stu.setage(16);
    stu.setscore(95.5f);
    cout<<stu.getname()<<"的年龄是 "<<stu.getage()<<",成绩是 "<<stu.getscore()<<endl;

    return 0;
}

改变继承的权限

使用 using 关键字可以改变基类成员在派生类中的访问权限,例如将 public 改为 private、将 protected 改为 public。
注意:using 只能改变基类中 public 和 protected 成员的访问权限,不能改变 private 成员的访问权限,因为基类中 private 成员在派生类中是不可见的,根本不能使用,所以基类中的 private 成员在派生类中无论如何都不能访问。

源代码(3)

#include<iostream>
using namespace std;

//基类People
class People {
public:
    void show();
protected:
    char *m_name;
    int m_age;
};
void People::show() {
    cout << m_name << "的年龄是" << m_age << endl;
}

//派生类Student
class Student : public People {
public:
    void learning();
public:
    using People::m_name;  //将protected改为public
    using People::m_age;  //将protected改为public
    float m_score;
private:
    using People::show;  //将public改为private
};
void Student::learning() {
    cout << "我是" << m_name << ",今年" << m_age << "岁,这次考了" << m_score << "分!" << endl;
}

int main() {
    Student stu;
    stu.m_name = "小明";
    stu.m_age = 16;
    stu.m_score = 99.5f;
    stu.show();  //compile error
    stu.learning();

    return 0;
}

类的继承时成员函数的遮蔽问题

如果派生类中的成员(包括成员变量和成员函数)和基类中的成员重名,那么就会遮蔽从基类继承过来的成员。所谓遮蔽,就是在派生类中使用该成员(包括在定义派生类时使用,也包括通过派生类对象访问该成员)时,实际上使用的是派生类新增的成员,而不是从基类继承来的。

一句话简单地说,你是儿子,我是爸爸,我有100
块钱,你这时要买东西,而恰好我也要买东西,我们两个人要买的东西一样,这个时候我会给钱给你这个儿子买东西,毕竟要买东西有个跑腿的还是挺好的,钱呢在这个时候就是儿子花出去的。

源代码(1)

#include<iostream>
using namespace std;

//基类People
class People{
public:
    void show();
protected:
    char *m_name;
    int m_age;
};
void People::show(){
    cout<<"嗨,大家好,我叫"<<m_name<<",今年"<<m_age<<"岁"<<endl;
}

//派生类Student
class Student: public People{
public:
    Student(char *name, int age, float score);
public:
    void show();  //遮蔽基类的show()
private:
    float m_score;
};
Student::Student(char *name, int age, float score){
    m_name = name;
    m_age = age;
    m_score = score;
}
void Student::show(){
    cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<endl;
}

int main(){
    Student stu("小明", 16, 90.5);
    //使用的是派生类新增的成员函数,而不是从基类继承的
    stu.show();
    //使用的是从基类继承来的成员函数
    stu.People::show();

    return 0;
}

基类和派生类成员函数重载问题

基类成员和派生类成员的名字一样时会造成遮蔽,这句话对于成员变量很好理解,对于成员函数要引起注意,不管函数的参数如何,只要名字一样就会造成遮蔽。换句话说,基类成员函数和派生类成员函数不会构成重载,如果派生类有同名函数,那么就会遮蔽基类中的所有同名函数,不管它们的参数是否一样。
还是那句话,这样子的重载问题跟基类和派生类的成员函数的遮蔽问题类似

源代码(1)

#include<iostream>
using namespace std;

//基类Base
class Base{
public:
    void func();
    void func(int);
};
void Base::func(){ cout<<"Base::func()"<<endl; }
void Base::func(int a){ cout<<"Base::func(int)"<<endl; }

//派生类Derived
class Derived: public Base{
public:
    void func(char *);
    void func(bool);
};
void Derived::func(char *str){ cout<<"Derived::func(char *)"<<endl; }
void Derived::func(bool is){ cout<<"Derived::func(bool)"<<endl; }

int main(){
    Derived d;
    d.func("c.biancheng.net");
    d.func(true);
    d.func();  //compile error
    d.func(10);  //compile error
    d.Base::func();
    d.Base::func(100);

    return 0;
}

C++语言基类和派生类的构造函数

派生类要是有构造函数,在主函数调用派生类成员构造函数的时候,必须先要调用基类的成员构造函数,否则系统报错。

源代码(1)——正确示例

#include<iostream>
using namespace std;

//基类People
class People{
protected:
    char *m_name;
    int m_age;
public:
    People(char*, int);
};
People::People(char *name, int age): m_name(name), m_age(age){}

//派生类Student
class Student: public People{
private:
    float m_score;
public:
    Student(char *name, int age, float score);
    void display();
};
//People(name, age)就是调用基类的构造函数
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
void Student::display(){
    cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<"。"<<endl;
}

int main(){
    Student stu("小明", 16, 90.5);
    stu.display();

    return 0;
}

源代码(2)——错误示例

#include<iostream>
using namespace std;

class A{
	public:
		A(int a,int b);
		void show();
	private:
		int x;
		int y;
}; 

A::A(int a,int b)
{
	x = a;
	y = b;
} 

void A::show() 
{
	cout<<"x 的值为:"<<x<<" "<<"y的值为:"<<y<<endl;
	 
}

class B:public A{
	public :
		B(int m);
		void show_B();
	private:
		int c;
};

B::B(int m)
{
	c = m;
} 

void B::show_B() 
{
	cout<<"c 的值为:"<<c<<endl;
}

int main()
{
	 B text(3);
	 text.show();
	 text.show_B();
	return 0;
}

上面这段错误示例的代码主要原因是没有在派生类中调用基类的构造函数,从而导致系统报错。

通俗的话说呢,你是我的儿子,我是你的爸爸,为啥你可以吃香的喝辣,而我这个做父亲就只能吃你剩下的。这铁定不公平的呀。所以,在儿子吃香的喝辣的之前,先必须孝敬我这个父亲。

主要错误代码段:
B::B(int m)
{
	c = m;
} 
正确代码示例:
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }

不管调用的顺序如何,派生类构造函数总是先调用基类构造函数再执行其他代码(包括参数初始化表以及函数体中的代码)

注意:

Student::Student(char *name, int age, float score){
People(name, age);
m_score = score;
}

这样写的代码是错误的,因为基类构造函数不会被继承,不能当做普通的成员函数来调用。换句话说,只能将基类构造函数的调用放在函数头部,不能放在函数体中。
另外,函数头部是对基类构造函数的调用,而不是声明,所以括号里的参数是实参,它们不但可以是派生类构造函数参数列表中的参数,还可以是局部变量、常量等。

构造函数的调用顺序

基类构造函数总是被优先调用,这说明创建派生类对象时,会先调用基类构造函数,再调用派生类构造函数,

如果多个类的继承顺序为:
A --> B --> C
那么创建 C 类对象时构造函数的执行顺序为:
A类构造函数 --> B类构造函数 --> C类构造函数

构造函数的调用顺序是按照继承的层次自顶向下、从基类再到派生类的。
还有一点要注意,派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的。以上面的 A、B、C 类为例,C 是最终的派生类,B 就是 C 的直接基类,A 就是 C 的间接基类。

不能直接调用间接类的构造函数是有原因的,就好比如上面的继承顺序,调用B类的构造函的同时,A类的构造函数被首先初始化,然后调用B类的构造函数,如果这个时候C类的再次显式地调用A类构造函数的时候,A类的构造函数就被初始化了两次,这样大大地浪费的CPU的内存。

构造函数的调用规则

事实上,通过派生类创建对象时必须要调用基类的构造函数,这是语法规定。换句话说,定义派生类构造函数时最好指明基类构造函数;如果不指明,就调用基类的默认构造函数(不带参数的构造函数);如果没有默认构造函数,那么编译失败。

源代码(1)

#include <iostream>
using namespace std;

//基类People
class People{
public:
    People();  //基类默认构造函数
    People(char *name, int age);
protected:
    char *m_name;
    int m_age;
};
People::People(): m_name("xxx"), m_age(0){ }
People::People(char *name, int age): m_name(name), m_age(age){}

//派生类Student
class Student: public People{
public:
    Student();
    Student(char*, int, float);
public:
    void display();
private:
    float m_score;
};
Student::Student(): m_score(0.0){ }  //派生类默认构造函数
Student::Student(char *name, int age, float score): People(name, age), m_score(score){ }
void Student::display(){
    cout<<m_name<<"的年龄是"<<m_age<<",成绩是"<<m_score<<"。"<<endl;
}

int main(){
    Student stu1;
    stu1.display();

    Student stu2("小明", 16, 90.5);
    stu2.display();

    return 0;
}

C++语言基类和派生类的析构函数

和构造函数类似,析构函数也不能被继承。与构造函数不同的是,在派生类的析构函数中不用显式地调用基类的析构函数,因为每个类只有一个析构函数,

另外析构函数的执行顺序和构造函数的执行顺序也刚好相反:
创建派生类对象时,构造函数的执行顺序和继承顺序相同,即先执行基类构造函数,再执行派生类构造函数。

而销毁派生类对象时,析构函数的执行顺序和继承顺序相反,即先执行派生类析构函数,再执行基类析构函数。

源代码(1)

#include <iostream>
using namespace std;

class A{
public:
    A(){cout<<"A constructor"<<endl;}
    ~A(){cout<<"A destructor"<<endl;}
};

class B: public A{
public:
    B(){cout<<"B constructor"<<endl;}
    ~B(){cout<<"B destructor"<<endl;}
};

class C: public B{
public:
    C(){cout<<"C constructor"<<endl;}
    ~C(){cout<<"C destructor"<<endl;}
};

int main(){
    C test;
    return 0;
}

源代码(2)

#include<iostream>
using namespace std;

class A//定义一个基类A
{
public:
	A()
	{
		cout << "creat A process success!" << endl;
	}
	~A()
	{
		cout << "destroy A process success!" << endl;
	}
	void circle(int i)
	{
		if (i == 1)
			cout << "I am class A's father!" << endl;
		else if (i == 0)
			cout << "I am child A,you are my son!" << endl;
	}
};

class B : public A//类B继承类A
{
public:
	B()
	{
		cout << "creat B process success!" << endl;
	}
	~B()
	{
		cout << "destroy B process success!" << endl;
	}
	void circle1(int i)
	{
		if (i == 1)
			cout << "I am class B's father!" << endl;
		else if (i == 0)
			cout << "I am child B,you are my son!" << endl;
	}
};

void display_A(A* test,int a)//访问类A
{
	test->circle(a);
}

void display_B(B* test, int b)//既可以访问类A,也可以访问类B
{
	test->circle(b);
	test->circle1(b);
}

int main()
{
	A* test1=NULL;
	B* test2=NULL;
	int a = 1;
	int b = 0;
	display_A(test1, a);
	display_B(test2, b);
	//test2->~A();//可以优先销毁A类析构函数,但是当B类调用析构函数的时候,
	//A类的析构函数又被重新调用,这样增加的程序运行的时间和负担。
	test2->~B();
	return 0;
}

C++语言类的多继承

类的继承不止一种,派生类中只有一个基类,称为单继承,当派生类中同时继承两个或两个以上的基类的时候,称为多继承。

多继承的语法也很简单,将多个基类用逗号隔开即可。例如已声明了类A、类B和类C,那么可以这样来声明派生类D:
class D: public A, private B, protected C{
    //类D新增加的成员
}

多继承形式下的构造函数和单继承形式基本相同,只是要在派生类的构造函数中调用多个基类的构造函数。

基类构造函数的调用顺序和和它们在派生类构造函数中出现的顺序无关,而是和声明派生类时基类出现的顺序相同。

通俗地说,一个儿子继承了多个爸爸的遗产,分为大爸爸,二爸爸,三爸爸等等的遗产。那么儿子吃饭的时候必须要先然爸爸吃饭先,而且是按照大小排序让爸爸们先吃饭。

源代码(1)

#include <iostream>
using namespace std;

//基类
class BaseA{
public:
    BaseA(int a, int b);
    ~BaseA();
protected:
    int m_a;
    int m_b;
};
BaseA::BaseA(int a, int b): m_a(a), m_b(b){
    cout<<"BaseA constructor"<<endl;
}
BaseA::~BaseA(){
    cout<<"BaseA destructor"<<endl;
}

//基类
class BaseB{
public:
    BaseB(int c, int d);
    ~BaseB();
protected:
    int m_c;
    int m_d;
};
BaseB::BaseB(int c, int d): m_c(c), m_d(d){
    cout<<"BaseB constructor"<<endl;
}
BaseB::~BaseB(){
    cout<<"BaseB destructor"<<endl;
}

//派生类
class Derived: public BaseA, public BaseB{
public:
    Derived(int a, int b, int c, int d, int e);
    ~Derived();
public:
    void show();
private:
    int m_e;
};
Derived::Derived(int a, int b, int c, int d, int e): BaseA(a, b), BaseB(c, d), m_e(e){
    cout<<"Derived constructor"<<endl;
}
Derived::~Derived(){
    cout<<"Derived destructor"<<endl;
}
void Derived::show(){
    cout<<m_a<<", "<<m_b<<", "<<m_c<<", "<<m_d<<", "<<m_e<<endl;
}

int main(){
    Derived obj(1, 2, 3, 4, 5);
    obj.show();
    return 0;
}

多继承类的命名冲突

当两个或多个基类中有同名的成员时,如果直接访问该成员,就会产生命名冲突,编译器不知道使用哪个基类的成员。这个时候需要在成员名字前面加上类名和域解析符::,以显式地指明到底使用哪个类的成员,消除二义性。

源代码(1)

#include <iostream>
using namespace std;

//基类
class BaseA{
public:
    BaseA(int a, int b);
    ~BaseA();
public:
    void show();
protected:
    int m_a;
    int m_b;
};
BaseA::BaseA(int a, int b): m_a(a), m_b(b){
    cout<<"BaseA constructor"<<endl;
}
BaseA::~BaseA(){
    cout<<"BaseA destructor"<<endl;
}
void BaseA::show(){
    cout<<"m_a = "<<m_a<<endl;
    cout<<"m_b = "<<m_b<<endl;
}

//基类
class BaseB{
public:
    BaseB(int c, int d);
    ~BaseB();
    void show();
protected:
    int m_c;
    int m_d;
};
BaseB::BaseB(int c, int d): m_c(c), m_d(d){
    cout<<"BaseB constructor"<<endl;
}
BaseB::~BaseB(){
    cout<<"BaseB destructor"<<endl;
}
void BaseB::show(){
    cout<<"m_c = "<<m_c<<endl;
    cout<<"m_d = "<<m_d<<endl;
}

//派生类
class Derived: public BaseA, public BaseB{
public:
    Derived(int a, int b, int c, int d, int e);
    ~Derived();
public:
    void display();
private:
    int m_e;
};
Derived::Derived(int a, int b, int c, int d, int e): BaseA(a, b), BaseB(c, d), m_e(e){
    cout<<"Derived constructor"<<endl;
}
Derived::~Derived(){
    cout<<"Derived destructor"<<endl;
}
void Derived::display(){
    BaseA::show();  //调用BaseA类的show()函数
    BaseB::show();  //调用BaseB类的show()函数
    cout<<"m_e = "<<m_e<<endl;
}

int main(){
    Derived obj(1, 2, 3, 4, 5);
    obj.display();
    return 0;
}

文章的部分文字语言描述和代码块部分转自此网站,特此声明

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值