一.什么是继承?
继承是类与类之间的关系,是一个很简单很直观的概念,与现实世界中的继承类似。继承可以理解为一个从类从另一个类获取成员变量 和成员函数的过程。例如 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;
}
运行结果