“面向对象的程序设计主要有四个特点:抽象、封装、继承和多态”
目录
若需了解更多内容,请微信搜索公众号“知识补盲站”,或扫描文末二维码,欢迎交流!
01 继承与派生
一个新类从已有的类那里获得其已有特性,这种现象称为类的继承。通过继承,一个新建子类从已有的父类那里获得父类的特性。从另一个角度而言,从已有的类(父类)产生一个新的子类,称为类的派生。一个派生类只从一个基类派生,称为单继承。一个派生类不仅可以从一个基类派生,也可以从多个基类派生。一个派生类由一个或多个基类派生称为多重继承。
02 派生类的声明方式
声明派生类的一般形式是
Class 派生类名:[继承方式]基类名
{
派生类新增加的成员
} ;
继承方式包括:public(公用的),private(私有的)和protected(受保护的),此项是可选的,如果不写此项,则默认为private(私有的)。
03 派生类的构成
在声明派生类时,一般还应当自己定义派生类的构造函数和析构函数,因为构造函数和析构函数是不能从基类继承的。
04 派生类成员的访问属性
不同的继承方式决定了基类成员在派生类中的访问属性。
(1)公用继承
基类的公用成员和保护成员在派生类中保持原有访问属性,其私有成员仍为基类私有。
(2)私有继承
基类的公用成员和保护成员在派生类中成了私有成员。其私有成员仍为基类私有。
(3)受保护的继承
基类的公用成员和保护成员在派生类中成了保护成员,其私有成员仍为基类私有。保护成员的意思是:不能被外界引用,但可以被派生类的成员引用,具体的用法将在稍后介绍。
4.1公用继承
采用公用继承方式时,基类的公用成员和保护成员在派生类中仍然保持其公用成员和保护成员的属性,而基类的私有成员在派生类中并没有成为派生类的私有成员,它仍然是基类的私有成员,只有基类的成员函数可以引用它,而不能被派生类的成员函数引用,因此就成为派生类中的不可访问的成员。
4.2私有继承
私有基类的公用成员和保护成员在派生类中的访问属性相当于派生类中的私有成员,即派生类的成员函数能访问它们,而在派生类外不能访问它们。私有基类的私有成员在派生类中成为不可访问的成员,只有基类的成员函数可以引用它们。
4.3保护成员和保护继承
由protected声明的成员称为“受保护的成员”,或简称“保护成员”。从类的用户角度来看,保护成员等价于私有成员。但有一点与私有成员不同,保护成员可以被派生类的成员函数引用。
如果基类声明了私有成员,那么任何派生类都是不能访问它们的,若希望在派生类中能访问它们,应当把它们声明为保护成员。如果在一个类中声明了保护成员,就意味着该类可能要用作基类,在它的派生类中会访问这些成员。
4.4多级派生时的访问属性
在多级派生的情况下,各成员的访问属性仍按以上原则确定。
05 派生类的构造函数和析构函数
5.1 简单派生类的构造函数
任何派生类都包含基类的成员,简单的派生类只有一个基类,而且只有一级派生(只有直接派生类,没有间接派生类),在派生类的数据成员中不包含基类的对象(即子对象)。
简单的派生类的构造函数。
#include
#include
using namespace std;
class Student//声明基类Student
{
public:
Student(intn,stringnam,char s) //基类构造函数
{
num=n;
name=nam;
sex=s;
}
~Student( ){ }//基类析构函数
protected: //保护部分
int num;
string name;
char sex ;
};
class Student1: public Student //声明派生类Student1
{
public: //派生类的公用部分
Student1(int n,string nam,char s,inta,string ad):Student(n,nam,s)//派生类构造函数
{
age=a; //在函数体中只对派生类新增的数据成员初始化
addr=ad;
}
void show( )
{
cout<<< span="">″num:″<<num<<endl;
cout<<< span="">″name:″<<name<<endl;
cout<<< span="">″sex:″<<sex<<endl;
cout<<< span="">″age:″<<age<<endl;
cout<<< span="">″address:″<<addr<<endl<<endl;
}
~Student1( ){ }//派生类析构函数
private: //派生类的私有部分
int age;
string addr;
};
Int main( )
{
Student1 stud1(10010,″Wang-li″,′f′,19,″115 Beijing Road,Shanghai″);
Student1 stud2(10011,″Zhang-fun″,′m′,21,″213 Shanghai Road,Beijing″);
stud1.show( ); //输出第一个学生的数据
stud2.show( ); //输出第二个学生的数据
return 0;
}
派生类构造函数的一般形式为:
派生类构造函数名(总参数表列):基类构造函数名(参数表列)
{派生类中新增数据成员初始化语句}
Box::Box(int h,int w,int len):height(h),width(w),length(len)
{ }
它也有一个冒号,在冒号后面的是对数据成员的初始化表。
不仅可以利用初始化表对构造函数的数据成员初始化,而且可以利用初始化表调用派生类的基类构造函数,实现对基类数据成员的初始化。也可以在同一个构造函数的定义中同时
实现这两种功能。例如:
Student1(int n, string nam,char s,int a, string ad):Student(n,nam,s)
{
age=a;//在函数体中对派生类数据成员初始化
addr=ad;
}
将构造函数改写为以下形式:
Student1(int n, string nam,char s,int a, string ad):Student(n,nam,s),age(a),addr(ad){}
5.2 有子对象的派生类的构造函数
类的数据成员中还可以包含类对象,这种类对象中的内嵌对象称为子对象。派生类构造函数的任务应该包括3部分
1)对基类数据成员初始化
2)对子对象数据成员初始化
3)对派生类数据成员初始化
#include
#include
using namespace std;
class Student//声明基类
{
public: //公用部分
Student(intn, string nam) //基类构造函数,与例11.5相同
{
num=n;
name=nam;
}
void display( ) //成员函数,输出基类数据成员
{
cout<<< span="">″num:″<<num<<endl;
}
protected: //保护部分
int num;
string name;
};
class Student1: public Student //声明公用派生类Student1
{
public:
Student1(int n, string nam,int n1, string nam1,int a, string ad):Student(n,nam),monitor(n1,nam1)
//派生类构造函数
{
age=a;
addr=ad;
}
void show( )
{
cout<<< span="">″This student is:″<<endl;
display(); //输出num和name
cout<<< span="">″age:″<<age<<endl; //输出age
cout<<< span="">″address:″<<addr<<endl<<endl;
//输出addr
}
void show_monitor( ) //成员函数,输出子对象
{
cout<<endl<<< span="">″Class monitor is:″<<endl;
monitor.display( ); //调用基类成员函数
}
private: //派生类的私有数据
Student monitor; //定义子对象(班长)
Int age;
string addr;
};
Int main( )
{
Student1 stud1(10010,″Wang-li″,10001,″Li-sun″,19,″115 BeijingRoad,Shanghai″);
stud1.show( ); //输出学生的数据
stud1.show_monitor(); //输出子对象的数据
return 0;
}
在上面的构造函数中有6个形参,前两个作为基类构造函数的参数,第3、第4个作为子对象构造函数的参数,第5、第6个是用作派生类数据成员初始化的。
归纳起来,定义派生类构造函数的一般形式为:
派生类构造函数名(总参数表列):基类构造函数名(参数表列),子对象名(参数表列)
{派生类中新增数成员据成员初始化语句}
执行派生类构造函数的顺序为:
①调用基类构造函数,对基类数据成员初始化:
②调用子对象构造函数,对子对象数据成员初始化;
③再执行派生类构造函数本身,对派生类数据成员初始化。
基类构造函数和子对象的次序可以是任意的。编译系统是根据相同的参数名(而不是根据参数的顺序)来确立它们的传递关系的,但习惯上一般先写基类构造函数。
5.3 多层派生时的构造函数
#include
#include
using namespace std;
class Student//声明基类
{
public: //公用部分
Student(int n, string nam) //基类构造函数
{
num=n;
name=nam;
}
void display( ) //输出基类数据成员
{
cout<<< span="">″num:″<<num<<endl;
cout<<< span="">″name:″<<name<<endl;
}
protected: //保护部分
intnum; //基类有两个数据成员
string name;
};
class Student1: public Student //声明公用派生类Student1
{
public:
Student1(int n,char nam [10],int a):Student(n,nam)
//派生类构造函数
{
age=a;
} //在此处只对派生类新增的数据成员初始化
void show( ) //输出num,name和age
{
display( ); //输出num和name
cout<<< span="">″age:″<<age<<endl;
}
private: //派生类的私有数据
int age; //增加一个数据成员
};
class Student2:public Student1 //声明间接公用派生类Student2
{
public:
//下面是间接派生类构造函数
Student2(int n, string nam,int a,int s):Student1(n,nam,a)
{
score=s;
}
void show_all( ) //输出全部数据成员
{
show( ); //输出num和name
cout<<< span="">″score:″<<score<<endl; //输出age
}
private:
int score; //增加一个数据成员
};
Int main( )
{
Student2 stud(10010,″Li″,17,89);stud.show_all( );
//输出学生的全部数据
return 0;
}
5.4 派生类构造函数的特殊形式
1)当不需要对派生类新增成员进行任何初始化操作时,派生类构造函数的函数体可以为空,即构造函数是空函数。如上例中
Student1(int n,strin nam,int n1,strin nam1):Student(n,nam),monitor(n1,nam1) { }
2)如果基类中没有定义构造函数或者定义了没有参数的构造函数,那么在定义派生类构造函数时可以不写基类构造函数。
06 多重继承
6.1声明多重继承的方法
如果已经声明了类A、类B、类C,可以声明多重继承派生类D:
Class D:public A,private B,protected C{类D新增数据成员}
6.2 多重继承派生类的构造函数
多重继承派生类的构造函数形式与单继承时的构造函数形式基本相同,只是在初始表中包含多个基类构造函数。如
派生类构造函数名(总参数表列):基类1构造函数(参数表列),基类2构造函数(参数表列),基类3
构造函数(参数表列){派生类中新增数成员据成员初始化语句}
07 多态性和虚函数
7.1 多态性的概念
在C++程序设计中,多态性是指具有不同功能的函数可以拥有同一个函数名,这样就可以用一个函数名调用不同内容的函数。在面向对象的程序设计中,一般是这样描述多态性的:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为(即方法),每个对象可以用自己的方式去响应共同的消息。多态性可分为两类:静态多态性和动态多态性。函数重载和运算符重载属于静态多态性,动态多态性是通过虚函数实现的。
7.2 虚函数
虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且通过基类指针或引用来访问基类和派生类中的重名函数。
#include
#include
using namespace std;//声明基类Student
class Student
{
public:
Student(int, string,float);//声明构造函数
void display( ); //声明输出函数
protected: //受保护成员,派生类可以访问
int num;
string name;
float score;
};
//Student类成员函数的实现
Student::Student(int n, string nam,float s) //定义构造函数
{
num=n;name=nam;score=s;
}
void Student::display( ) //定义输出函数
{
}
//声明公用派生类Graduate
class Graduate:public Student
{
public:
Graduate(int, string, float, float); //声明构造函数
void display( ); //声明输出函数
private:
float pay;
};
// Graduate类成员函数的实现
void Graduate::display( ) //定义输出函数
{
cout<<< span="">″num:″<<num<<
<span="">″ \\nname: ″<<name<<
<span="">″ \\nscore: ″<<score<<
<span="">″ \\</score<<<> </name<<
<> </num<<<>
npay=″<<pay<<endl< span=""></pay<<endl<>;
}
Graduate::Graduate(int n, string nam,float s,float p):Student(n,nam,s),pay(p){ }//主函数
Int main()
{
Student stud1(1001,″Li″,87.5); //定义Student类对象stud1
Graduate grad1(2001,″Wang″,98.5,563.5);
//定义Graduate类对象grad1
Student *pt=&stud1; //定义指向基类对象的指针变量pt
pt->display( );
pt=&grad1;
pt->display( );
return 0;
}
运行结果如下,请仔细分析。
num:1001(stud1的数据)
name:Li
score:87.5
num:2001 (grad1中基类部分的数据)
name:wang
score:98.5
下面对程序作一点修改,在Student类中声明display函数时,在最左面加一个关键字virtual
,即virtual void display( );再编译和运行程序,请注意分析运行结果:
num:1001(stud1的数据)
name:Li
score:87.5
(num:2001 grad1中基类部分的数据)
name:wang
score:98.5
pay=1200 (这一项以前是没有的)
由虚函数实现的动态多态性就是:同一类族中不同类的对象,对同一函数调用做出不同的响应。
虚函数的使用方法是:
(1)在基类用virtual声明成员函数为虚函数。这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便地被调用。在类外定义虚函数时,不必再加virtual。
(2)在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。
(3)定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对
(4)通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。
使用虚函数时,有两点需要注意:
(1)只能用virtual声明类的成员函数,使它成为虚函数,而不能将类外的普通函数声明为虚函数。因为虚函数的作用是允许在派生类中对基类的虚函数重新定义。显然,它只能用于类的继承层次结构中。
(2)一个成员函数被声明为虚函数后,在同一类族中的类就不能再定义一个非virtual的但与该虚函数具有相同的参数(包括个数和类型)和函数返回值类型的同名函数。
根据什么考虑是否把一个成员函数声明为虚函数呢?
(1)首先看成员函数所在的类是否会作为基类。然后看成员函数在类的继承后有无可能被更改功能,如果希望更改其功能的,一般应该将它声明为虚函数。
(2) 如果成员函数在类被继承后功能不需修改,或派生类用不到该函数,则不要把它声明为虚函数。不要仅仅考虑到要作为基类而把类中的所有成员函数都声明为虚函数。
(3) 应考虑对成员函数的调用是通过对象名还是通过基类指针或引用去访问,如果是通过基类指针或引用去访问的,则应当声明为虚函数。
(4) 有时,在定义虚函数时,并不定义其函数体,即函数体是空的。它的作用只是定义了一个虚函数名,具体功能留给派生类去添加。
觉得不错,请点个在看