一、什么是类继承?
类继承是C++中的重要概念,类继承的实质是为一个已有类添加成员和方法,使之成为一个更复杂的新类。我们又称为从一个类派生出另一个类。原始类称为基类,而继承类被称为派生类。
类继承的优势在于,可以在不改动基类的前提下为继承类添加所需的功能,这是比较重要的。试想,如果类A不是你写的,或者是你很久以前写的,你需要为之添加功能,你就需要完整地细读这个类的代码,然后才能做出改动。而且,你很容易在改动时引发错误。
而如果利用类继承,你可以把基类看成一个黑箱。你只要关心你增加的功能就行了。
二、如何定义类继承
1--
继承的前提是有一个简单的基类,下面的Student类便是一个实例:
#include <iostream>
#include <string>
using std::string;
class Student
{
private:
int age;
string name;
int grade;
int college;
public:
Student();
Student(int p_age,string p_name,int p_grade,int p_college);
void reset_name(string p_name);
};
......
Student::Student()
{
}
Student::Student(int p_age,string p_name,int p_grade,int p_college)
{
age=p_age;
name=p_name;
grade=p_grade;
college=p_college;
}
void Student::reset_name(string p_name)
{
name=p_name;
}
(官方编写的string类已经相当完美了,同学们大可放心使用)
2--以公有方式从一个类派生出另一个类
上面我们已经定义了基类,我们可以按照下面的代码来实现以公有方式从一个类派生出另一个类,这个操作简称公有派生。
class CS_Student:public Student
{
......
}
众所周知,学习计算机科学的学生和普通学生不太一样,所以要从普通的Student类派生出一个CS_Student类来统计计算机科学专业的学生。我们要给它添加一些属于计科学生的成员和方法。
class CS_Student:public Student
{
private:
int DM_grade;
int DS_grade;
public:
void set_DM(int p_grade);
void set_DS(int p_grade);
......
};
计科学生需要学习离散数学和数据结构,所以添加了两个成员变量来记录两门课的成绩,以及相应的方法。这说明,派生类也可以有公有和私有部分!
对于公有派生而言,它还有以下的性质:
- 派生类对象包含基类对象。
- 基类的公有成员会成为派生类的公有成员
- 基类的私有成员也成为派生类的一部分,不过仅能通过基类的公有方法和保护方法访问(之后介绍)
- 派生类对象可以使用基类的方法,但反过来,基类对象不能使用派生类方法。
那么事实上,CS_Student类的性质是这样的:
class CS_Student:public Student
{
no direct access:
int age;
string name;
int grade;
int college;
private:
int DM_grade;
int DS_grade;
public:
void set_DM(int p_grade);
void set_DS(int p_grade);
void reset_name(string p_name);
Student();
Student(int p_age,string p_name,int p_grade,int p_college);
......
};
3--继承类的构造函数
你一定发现了上面两端代码我都打了一串......表示省略。这个省略不是强调我们还可以设计更多的方法,而是强调继承类缺少必要的构造函数!
虽然继承类也会自动生成构造函数,但是这种自动生成的构造函数往往不靠谱。为了让代码按照我们的设想正常运行,最好手动为之设计构造函数。
由于继承类不能直接访问基类的私有成员,所以继承类的构造函数必须使用基类构造函数(如果你要在初始化类时就为各个成员赋值的话)。
以下就是两种常用的方式:
- 利用普通构造函数
CS_Student(int p_age,string p_name,int p_grade,int p_college,int p_DM,int p_DS);
......
CS_Student::CS_Student(int p_age,string p_name,int p_grade,int p_college,int p_DM,int p_DS)
:Student(p_age,p_name,p_grade,p_college)
{
DM_grade=p_DM;
DS_grade=p_DS;
}
在定义继承类的时候,利用列表初始化的方法调用基类的普通构造函数,实现基类的构造,然后再把补充部分初始化完毕!
使用构造函数创建继承类对象的方法就显而易见了:
int main(void)
{
CS_Student a1(20,"xuyang",2,6,88,92);
}
完整的代码如下:
#include <iostream>
#include <string>
using std::string;
class Student
{
private:
int age;
string name;
int grade;
int college;
public:
Student(int p_age,string p_name,int p_grade,int p_college);
Student();
void reset_name(string p_name);
};
class CS_Student:public Student
{
private:
int DM_grade;
int DS_grade;
public:
void set_DM(int p_grade);
void set_DS(int p_grade);
CS_Student(int p_age,string p_name,int p_grade,int p_college,int p_DM,int p_DS);
};
int main(void)
{
CS_Student a1(20,"xuyang",2,6,88,92);
}
Student::Student()
{
}
Student::Student(int p_age,string p_name,int p_grade,int p_college)
{
age=p_age;
name=p_name;
grade=p_grade;
college=p_college;
}
void Student::reset_name(string p_name)
{
name=p_name;
}
void CS_Student::set_DM(int p_grade)
{
DM_grade=p_grade;
}
void CS_Student::set_DS(int p_grade)
{
DS_grade=p_grade;
}
CS_Student::CS_Student(int p_age,string p_name,int p_grade,int p_college,int p_DM,int p_DS)
:Student(p_age,p_name,p_grade,p_college)
{
DM_grade=p_DM;
DS_grade=p_DS;
}
- 利用复制构造函数
CS_Student(int p_DM,int p_DS,const Student& ostd);
......
CS_Student::CS_Student(int p_DM,int p_DS,const Student& ostd)
:Student(ostd)
{
DM_grade=p_DM;
DS_grade=p_DS;
}
此处的列表初始化利用了复制构造函数,所以必须先创建一个基类对象。为此,你可以如下操作,通过创建一个基类的匿名临时变量来辅助创建我们需要的继承类对象:
int main(void)
{
CS_Student a1(88,92,Student(20,"xuyang",2,6));
}
当然,你也可以直接创建一个基类对象,然后在它的基础上生成所需的继承类对象(这无疑浪费了空间):
int main(void)
{
Student tmpa;
CS_Student(88,92,tmpa);
}
完整的代码如下所示:
#include <iostream>
#include <string>
using std::string;
class Student
{
private:
int age;
string name;
int grade;
int college;
public:
Student(int p_age,string p_name,int p_grade,int p_college);
Student();
void reset_name(string p_name);
};
class CS_Student:public Student
{
private:
int DM_grade;
int DS_grade;
public:
void set_DM(int p_grade);
void set_DS(int p_grade);
CS_Student(int p_DM,int p_DS,const Student& ostd);
};
int main(void)
{
CS_Student a1(88,92,Student(20,"xuyang",2,6));
}
Student::Student()
{
}
Student::Student(int p_age,string p_name,int p_grade,int p_college)
{
age=p_age;
name=p_name;
grade=p_grade;
college=p_college;
}
void Student::reset_name(string p_name)
{
name=p_name;
}
void CS_Student::set_DM(int p_grade)
{
DM_grade=p_grade;
}
void CS_Student::set_DS(int p_grade)
{
DS_grade=p_grade;
}
CS_Student::CS_Student(int p_DM,int p_DS,const Student& ostd)
:Student(ostd)
{
DM_grade=p_DM;
DS_grade=p_DS;
}
注意,由于使用了复制构造函数,所以如果你设计的类中有动态内存分配,就需要特别小心了!
4--默认情况
如果不手动定义使用哪一个基类的构造函数,编译器会自动使用默认的基类构造函数。但如果基类的默认构造函数被覆盖了,那么编译器会报错。所以以下的两种写法是等效的:
CS_Student::CS_Student(int p_DM,int p_DS)
{
DM_grade=p_DM;
DS_grade=p_DS;
}
CS_Student::CS_Student(int p_DM,int p_DS):Student()
{
DM_grade=p_DM;
DS_grade=p_DS;
}
如果不手动定义继承类的构造函数,编译器会自动生成默认的构造函数。这种构造函数又会自动调用基类的默认构造函数,等价于:
CS_Student::CS_Student():Student()
{
}
5--派生类构造函数的要点如下:
- 首先创建基类对象
- 派生类构造函数应该通过成员初始化列表将基类信息传递给基类构造函数
- 派生类构造函数应初始化派生类新增的数据成员
三、如何使用派生类
1--
成员必须能够访问基类声明,所以最好把两个类的声明放在同一个头文件中。而且,在同一头文件中,派生类要在基类后面声明!
2--
派生类对象可以使用基类的方法,条件是这个方法不是私有的!
int main(void)
{
CS_Student a1(88,92,Student(20,"xuyang",2,6));
a1.reset_name("yuanzhi");
}
3--
基类指针可以在不进行显式类型转换的情况下指向派生类对象,基类引用不进行显式类型转换的情况下引用派生类对象。但是,基类指针和引用只能调用基类方法!
然而,不能把基类的对象和地址赋给派生类的引用和指针。这个规则是有道理的,如果允许把基类对象赋给派生类的引用,那么就可以给基类对象调用派生类方法了,这显然就不合适了!