C++基础知识篇(三)——继承与派生

一.什么是继承?

继承是类与类之间的关系,是一个很简单很直观的概念,与现实世界中的继承类似。继承可以理解为一个从类从另一个类获取成员变量 和成员函数的过程。例如 B 继承于类 A,那么B 就拥有A 的成员变量和成员函数。被继承的类成为父类或者基类,继承的类成为子类或者派生类。派生类除了拥有基类的成员,还可以定义自己的新成员,增强类的功能。

                                                         

二.为什么要使用继承?

1)当你创建的新类与现有的类相似,只是多出若干成员变量或成员函数时,可以使用继承,这样不但会减少代码量,而且新类会拥有基类的所有功能。

2)当你需要创建多个类,它们拥有很多相似的成员变量或者成员函数时,也可以使用继承。可以将这些类的共同成员提取出来,定义为基类,然后从基类继承,既可以节省代码,也可以方面后续修改成员。 

三.继承的一般语法

class  派生类名: [继承方式]  基类名

{

       派生类新增加的成员

}

四.继承方式

继承方式包括:public(公有的)、private(私有的)、protected(受保护的),如果不写,默认为private。

1)public继承方式

  • 基类中所有的public成员在派生类中为public属性;
  • 基类中所有的protected成员在派生类中为protected属性
  • 基类中所有private成员在派生类中不能使用

2)protected继承方式

  • 基类中所有的public成员在派生类中为protected属性
  • 基类中所有的protected成员在派生类中为protected属性。
  • 基类中所有的private成员在派生类中不能使用。

3)private继承方式

  • 基类中的所有public成员在派生类中均为private属性。
  • 基类中的所有 protected 成员在派生类中均为 private 属性。
  • 基类中的所有 private 成员在派生类中不能使用。

                                         

由于private和protected继承方式会改变基类成员在派生类中访问权限,导致继承关系复杂,所以实际开发过程中一般用public

 1)基类成员在派生类中的访问权限不得高于继承方式中指定的权限。例如,当继承方式为protected时,那么基类成员在派生类中的访问权限最高也为protected,高于protected的会降级为protected,但低于protected不会升级。再如,当继承方式为public时,那么基类成员在派生类中的范围权限将保持不变。也就是说,继承方式中的public、protected、private是用来指明基类成员在派生类中的最高访问权限的。

2)不管继承方如何,基类中的private成员在派生类中始终不能使用(不能在派生类的成员函数中访问或调用)。

3)如果希望基类的成员能够被派生类继承并且毫无保障的使用,那么这些成员只能声明为ublic或者protected,只有哪些不希望在派生类中使用的成员才声明为pprotected。

4)如果希望基类的成员既不向外暴露(不能通过对象访问),还能在派生类中使用,那么只能声明为protected。

#include <iostream>
using namespace std;

//基类People
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(11);
	stu.setscore(100);

	cout << stu.getname() << "的年龄是 " << stu.getage() << ",成绩是 " << stu.getscore() << endl;
	
	return 0;
}

  本例中,People 是基类,Student 是派生类。Student 类继承了 People 类的成员,同时还新增了自己的成员变量 score 和成员函数 setscore()、getscore()。这些继承过来的成员,可以通过子类对象访问,就像自己的一样。

五.改变访问权限

使用 using 关键字可以改变基类成员在派生类中的访问权限,例如将public改为protected或者将protected改为public

 代码中首先定义了基类 People,它包含两个 protected 属性的成员变量和一个 public 属性的成员函数。定义 Student 类时采用 public 继承方式,People 类中的成员在 Student 类中的访问权限默认是不变的。

#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;  //将private改为public
    using People::m_age;  //将private改为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;
}

六.继承时的名字遮蔽

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

#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("小明",12,120); //使用的是派生类新增的成员函数,而不是从基类继承过来的

	stu.show(); //使用的是从基类继承过来的成员函数

	stu.People :: show(); //基类自己的成员函数
	
	return 0;	
}

 

 

#include <iostream>
using namespace std;

class Base
{
public:
	void func();
	void func(int);
};

void Base::func()
{
	cout << "Base::func()" << endl;
}
void Base::func(int a)
{
	cout << "Base ::func(int a)" << endl;
}

class Derived: public Base
{
public:

	void func(char *);
	void func(bool);
};

void Derived::func(char *str)
{	
	cout << "D::func(char*)" << endl;
}
void Derived::func(bool is)
{
	cout << "D::func(bool)" << endl;
}

int main()
{
	Derived d;
	d.func("d");
	d.func(true);
	d.Base::func();
	d.Base::func(100);	
	return 0;
}

本例中,Base 类的func()、func(int)和 Derived 类的func(char *)、func(bool)四个成员函数的名字相同,参数列表不同,它们看似构成了重载,能够通过对象d访问所有的函数,实则不然,Derived类的func遮蔽了Base类的func,导致代码没有匹配的函数,所以调用失败。

如果说有重载关系,那么也是Base里面的func构成重载,而Derived类的func构成重载

七.派生类的构造函数

基类的成员函数可以被继承,可以通过派生类的对象访问,但是这仅仅指的是普通的成员函数,类的构造函数不能被继承。构造函数不能被继承是有道理的,因为即使继承了,它的名字和派生类的名字也不一样,不能成为派生类的构造函数,当然更不能成为普通的成员函数。

在设计派生类时,对继承过来的成员变量的初始化工作也要由派生类的构造函数完成,但是一大部分基类都有private属性的成员变量,它们在派生类中无法访问,更不能使用派生类的构造函数来初始化。

这种矛盾在C++继承中是普遍存在的,解决这个问题的思路是:在派生类的构造函数调用基类的构造函数

#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;
}

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

People(name, age)就是调用基类的构造函数,并将 name 和 age 作为实参传递给它,m_score(score)是派生类的参数初始化表,它们之间以逗号,隔开。也可以将基类构造函数的调用放在参数初始化表后面:

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

但是不管它们的顺序如何,派生类构造函数总是先调用基类构造函数再执行其他代码(包括参数初始化表以及函数体中的代码),总体上看和下面的形式类似:

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

函数头部是对基类构造函数的调用,而不是声明,所以括号里的参数是实参,它们不但可以是派生类构造函数参数列表中的参数,还可以是局部变量、常量等,例如:

Student::Student(char *name, int age, float score): People("小明", 16),m_score(score){ }

基类构造函数总是被优先调用,这说明创建派生类对象时,会先调用基类构造函数,再调用派生类构造函数,如果继承关系有好几层的,如:A--->B---->c

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

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

C++这样规定是有道理的,因为我们在C中调用了B的构造函数,B又调用了A的构造函数,相当于C间接的调用了A的构造函数,如果再在C中显示调用了A的构造函数,那么A的构造函数就被调用了两次,相应的,初始化工作也做了两次,这不仅是多余的,还会浪费CPU的时候和内存,毫无益处。

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

#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)
{
	cout<<"People()"<<endl;
}
People::People(char *name, int age): m_name(name), m_age(age)
{
	cout<<"People(char *name, int age)"<<endl;
}

//派生类Student
class Student: public People
{
public:
    Student();
    Student(char*, int, float);
public:
    void display();
private:
    float m_score;
};
Student::Student(): m_score(0.0)
{
	cout<<"Student()"<<endl;
}  //派生类默认构造函数
Student::Student(char *name, int age, float score): People(name, age), m_score(score)
{
	cout<<"Student(char*, int, float)"<<endl;
}
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;
}

 执行结果:

 创建对象stul时,执行派生类的构造函数Student::Student(),并没有指明要调用基类的哪一个构造函数,从运行结果可以很明显的看出啦,系统默认调用了不带参数的构造函数。

创建对象stu2时,执行派生类的构造函数Student::Student(char *name,int age.,float score),它指明了基类的构造函数。

如果将People(name,age)去掉,也会调用默认构造函数.

如果将基类People中不带参数的构造函数删除,那么会发生编译器错误,因为创建对象时需要调用People类的默认构造函数,而Peoplel类中已经显示定义了构造函数,编译器不会再生成默认的构造函数。

八.C++派生类的析构函数

和构造函数类似,析构函数也不能被继承,与构造函数不同的是,在派生类的析构函数中不用显示的调用基类的析构函数,因为每个类只有只有一个析构函数,编译器知道如何选择,无需程序员干涉。

另外析构函数的执行顺序和构造函数的执行顺序相反。

创建派生类对象时,构造函数的执行顺序和继承顺序相同,先执行基类构造函数,再执行派生类构造函数。

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

#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;
}

运行结果 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值