(一三六)基类——第十三章
OOP编程的主要目的之一是提供可以重用的代码(比如类,可以反复使用)。C++有类库。类库由类声明和实现构成。因为类组合了类数据表示和类方法,因此提供了比函数库更加完整的程序包(函数库里的函数,也可以放置在类中作为类方法)。例如,单个类就可以提供用于管理对话框的全部资源。类库通常是以源代码的方式提供的(传统的C函数库则是以函数库的形式,往...
OOP编程的主要目的之一是提供可以重用的代码(比如类,可以反复使用)。
C++有类库。类库由类声明和实现构成。因为类组合了类数据表示和类方法,因此提供了比函数库更加完整的程序包(函数库里的函数,也可以放置在类中作为类方法)。
例如,单个类就可以提供用于管理对话框的全部资源。
类库 通常是以源代码的方式提供的(传统的C函数库则是以函数库的形式,往往没有源代码)。
C++提供了比修改代码更好的方式来扩展和修改类,这种方法叫做 类继承。
类继承可以从已有的类派生出新的类,而派生类继承了原有类(称为 基类 )的特征,包括方法(类中的那些函数)。
通过继承派生出的类通常比设计新类要容易的多。下面是可以通过继承完成的一些工作:
①可以在已有类的基础上添加功能。
例如,对于数组类可以添加数学运算。
②可以给类添加数据。
例如,对于字符串类,可以派生出一个类,并添加指定字符串显示颜色的数据成员。
③可以修改类方法的行为。
例如,对于代表提供给飞机乘客的服务的Passenger类,可以派生出提供更高级别服务的FirstClassPassenger类。
与直接通过复制、修改原始的类代码相比,继承机制只需提供新特性,甚至不需要访问源代码就可以派生出类。
于是,可以在类库源代码不公开的情况下,自行使用库中的类派生出新的类,或者是将自己的类分发给别人也不用担心泄露源代码(并允许他人在类中添加特性)
基类和派生类:
当从一个类派生出另一个类时,原始类称为基类,继承类被称为派生类。
声明一个普通类,即可成为 基类。
例如:
class Name
{
string fname;
string lname;
public:
Name(string fn = "none", string ln = "none");
friend std::ostream& operator<<(std::ostream& os, const Name& na);
};
就是一个基类,而声明一个 派生类,需要在普通类的基础上派生而来,其格式为:
class Student :public Name
{
private:
public:
};
Student后面的冒号,表示Student的基类是Name。这种特殊的声明头,表示Name是一个 公有基类,这被称为 公有派生。
派生类对象包含基类对象,使用公有派生,基类的公有成员将成为派生类的公有成员,基类的私有部分,也将成为派生类的一部分。但只能通过基类的公有和保护方法访问。
经过派生后的Student类,具有以下特征:
①派生类对象储存了基类的数据成员(派生类继承了基类的实现,比如int a之类);
②派生类对象可以使用基类的方法(派生类继承了基类的接口)。
也就是说,就以上Student类而言,可以认为Name类的数据成员和方法,Student类都有,只不过不仅仅这么简单而已(例如不能直接访问原Name类私有部分)。
派生后类需要添加的内容:
①构造函数,用于给基类的数据成员、以及派生类新添加的数据成员提供数据;
②根据实际需要,添加数据成员和类方法。
class Student :public Name
{
private:
int id; //ID编号,新添加的数据成员
public:
Student(string fn = '\0', string ln = '\0', int ID = 0); //Student类的默认构造函数,用于提供数据
Student(int ID, const Name& NA); //构造函数,使用Name类对象作为参数
void reset(string fn, string ln, int ID); //CH函数,用于重置数据
};
派生类的访问权限:
派生类不能直接访问基类的私有成员,而必须通过基类方法进行访问。
例如在Student类的构造函数中:
Student(string fn = '\0', string ln = '\0', int ID = 0)
不能直接写:
{ fname=fn;
lname=ln; }
这两行代码(因为fname和lname是基类的私有成员,不能直接访问)。
必须通过基类的公有方法来访问私有的基类成员,具体地说,派生类构造函数必须使用基类的构造函数。
创建派生类对象时,程序会首先创建基类的对象。从概念上讲,这意味着基类对象应当在程序进入派生类构造函数之前被创建,C++使用成员初始化列表语法来完成这项工作。
也就是说,当创建一个派生类对象时,程序说这么做的:
①调用基类构造函数(且这个基类构造函数是以初始化列表的形式进行的);
如:Student::Student(string fn = '\0', string ln = '\0', int ID = 0) :Name() //Name()是基类Name的默认构造函数
其中,Name()在未直接声明的时候,使用的是Name类的默认构造函数。
如果不想使用默认构造函数,可以显式的应用其他构造函数。(例如复制构造函数,或者其他构造函数,只要在参数列表里使用符合要求的即可)。
但无论使用哪种基类的构造函数,都只能在初始化列表中使用(不能在函数内部使用)
②调用派生类构造函数,用于完成派生类数据成员的初始化(也可以使用初始化列表的方式)。
例如:
Student::Student(string fn = '\0', string ln = '\0', int ID = 0) :Name(fn,ln),id(ID)
{
}
就是使用Name的构造函数来初始化基类的数据,然后使用初始化列表的方式,初始化Student类新增的数据成员id。
其中,Name是类名(使用Name类的构造函数)
派生类构造函数的要点:
①首先创建基类对象;
②派生类构造函数应通过成员初始化列表将基类信息传递给基类构造函数(负责初始化继承的数据成员);
③派生类构造函数应初始化派生类新增的数据成员。
而调用析构函数时,则相反:
①先调用派生类的析构函数;
②再调用基类的析构函数。
使用派生类:
要使用派生类,则程序必须能访问基类声明(否则创建派生类对象这一步就没法做)。
如果在同一个文件/头文件中,则必须先声明基类,再声明派生类(否则会出现问题);
或者放在不同的文件之中。
而派生类定义和基类的类定义,可以放在同一个文件,也可以放在不同的文件之中。
如代码:
//1.h 基类和派生类声明
#pragma once
#include<iostream>
#include<string>
using std::string;
class Name
{
string fname;
string lname;
public:
Name(string fn = "none", string ln = "none") { fname = fn;lname = ln; }
void reset(const string fn, const string ln);
friend std::ostream& operator<<(std::ostream& os, const Name& na);
void show()const { std::cout << lname << " " << fname; }
};
class Student :public Name
{
private:
int id; //ID编号,新添加的数据成员
public:
Student(string fn = "", string ln = "", int ID = 0); //Student类的默认构造函数,用于提供数据
Student(int ID, const Name& NA); //构造函数,使用Name类对象作为参数
void reset(string fn, string ln, int ID); //CH函数,用于重置数据
friend std::ostream& operator<<(std::ostream& os, const Student& st);
};
//2.cpp 基类和派生类的定义
#include"1.h"
using std::cout;
using std::endl;
std::ostream& operator<<(std::ostream& os, const Name& na)
{
os << na.lname << " " << na.fname;
return os;
}
std::ostream& operator<<(std::ostream& os, const Student& na) //在遇见student类时,将优先匹配这个
{
//na.show(); //间接2步,输出同样的结果,但显然更麻烦一些(因为要求基类有对应的类方法)
//os << " " << na.id;
os << Name(na) << " " << na.id; //使用强制转换,将na转换为Name类
return os;
}
void Name::reset(const string fn, const string ln)
{
fname = fn;
lname = ln;
}
Student::Student(string fn, string ln, int ID):Name(fn,ln) //Student类的默认构造函数,用于提供数据
{
id = ID;
}
Student::Student(int ID, const Name& NA):Name(NA),id(ID) //构造函数,使用Name类对象作为参数
{
}
void Student::reset(string fn, string ln, int ID) //CH函数,用于重置数据
{
id = ID;
Name::reset(fn, ln);
}
//1.cpp main函数测试用
#include<iostream>
#include"1.h"
int main()
{
using namespace std;
Name a; //基类对象
cout << a << endl; //基类方法
a.reset("三", "张"); //基类方法
cout << a << endl; //基类方法
Student b; //派生类对象(默认构造函数)
cout << b << endl; //派生类方法(内含基类方法)
Student c("四", "李", 1); //派生类对象(构造函数)
cout << c << endl;
Student d(2, a); //构造函数,调用基类的复制构造函数
cout << d << endl;
d.reset("五", "王", 3); //派生类方法
cout << d << endl;
system("pause");
return 0;
}
派生类和基类之间的特殊关系:
派生类与基类之间的特殊关系有:
①派生类可以使用基类的方法(方法非私有);
②基类指针可以在不进行显示类型转换的情况下指向派生类对象;
③基类引用可以在不进行显示类型转换的情况下引用派生类对象;
④基类指针、引用只能用于调用基类的方法,不能调用派生类方法;
⑤但派生类指针、引用不能指向基类对象;
因此,产生如下情况:
①当函数的形参为基类指针/引用时,派生类对象/或其地址,可以作为函数的参数;
②当初始化一个基类对象时,可以将派生类对象作为参数,在未特殊声明的情况下,可以使用隐式复制构造函数(将其视为一个基类对象);
③可以将派生类对象赋给基类对象(假如没有特殊声明的话),派生类对象将按值传递派生类对象的基类部分数据;
④如果在函数声明中,有重载函数分别将基类对象、派生类对象作为参数的,将匹配更合适的(例如派生类对象将优先匹配给派生类对象的形参);
更多推荐
所有评论(0)