基础知识点
●类之间的关系
继承:在已有类的基础上创建新类的过程
一个B类继承A类,或称从类 A 派生B类 A 称为基类(父类),类 B 称为派生类(子类)
●基类和派生类
类继承关系的语法形式
class 派生类名 : 基类名表
{
数据成员和成员函数声明
};
基类名表 构成
访问控制 基类名1, 访问控制 基类名2 ,… , 访问控制 基类名n
访问控制 表示派生类对基类的继承方式,使用关键字:
public 公有继承
private 私有继承
protected 保护继承
不论种方式继承基类,派生类都不能直接使用基类的私有成员
派生类的生成过程经历了三个步骤:
●吸收基类成员(全部吸收(构造、析构除外),但不一定可见)
●改造基类成员
●添加派生类新成员
(1) 吸收基类成员
●在C++的继承机制中,派生类吸收基类中除构造函数和析构函数之外的全部成员。
(2) 改造基类成员
●通过在派生类中定义同名成员(包括成员函数和数据成员)来屏蔽(隐藏)在派生类中不起作用的部分基类成员。
(3) 添加新成员
●仅仅继承基类的成员是不够的,需要在派生类中添加新成员,以保证派生类自身特殊属性和行为的实现
●重名成员
派生类定义了与基类同名的成员,在派生类中访问同名成员时屏蔽(hide)了基类的同名成员
在派生类中使用基类的同名成员,显式地使用类名限定符:
类名 :: 成员
●派生类中访问静态成员
基类定义的静态成员,将被所有派生类共享(基类和派生类共享基类中的静态成员)
根据静态成员自身的访问特性和派生类的继承方式,在类层次体系中具有不同的访问性质
派生类中访问静态成员,用以下形式显式说明:
类名 :: 成员
或通过对象访问 对象名 . 成员
●基类的初始化
在创建派生类对象时用指定参数调用基类的构造函数来初始化派生类继承基类的数据
派生类构造函数声明为
派生类构造函数 ( 变元表 ) : 基类 ( 变元表 ) , 对象成员1( 变元表 )
… 对象成员n ( 变元表 ) ;
构造函数执行顺序:基类 à 对象成员à 派生类
●派生类构造函数和析构函数的定义规则
派生类构造函数和析构函数的使用原则
基类的构造函数和析构函数不能被继承 如果基类没有定义构造函数或有无参的构造函数, 派生类也可以不用定义构造函数 如果基类无无参的构造函数,派生类必须定义构造函数 如果派生类的基类也是派生类,则每个派生类只负责直接基类的构造 派生类是否定义析构函数与所属的基类无关●派生类构造函数的定义
在C++中,派生类构造函数的一般格式为:
派生类::派生类名(参数总表):基类名(参数表)
{
// 派生类新增成员的初始化语句
}
注意:这是基类有构造函数且含有参数时使用
●派生类析构函数
(1)当派生类中不含对象成员时
在创建派生类对象时,构造函数的执行顺序是:基类的构造函数→派生类的构造函数;
在撤消派生类对象时,析构函数的执行顺序是:派生类的析构函数→基类的析构函数;
(2)当派生类中含有对象成员时
在定义派生类对象时,构造函数的执行顺序:基类的构造函数→对象成员的构造函数→派生类的构造函数;
在撤消派生类对象时,析构函数的执行顺序:派生类的析构函数→对象成员的析构函数→基类的析构函数。
●多继承
一个类有多个直接基类的继承关系称为多继承
多继承声明语法
class 派生类名 : 访问控制 基类名1 , 访问控制 基类名2 , … , 访问控制 基类名n
{
数据成员和成员函数声明
};
●多继承的派生类构造和访问
多个基类的派生类构造函数可以用初始式调用基类构造函数初始化数据成员。
执行顺序与单继承构造函数情况类似。多个直接基类构造函数执行顺序取决于定义派生类时指定的各个继承基类的顺序。
一个派生类对象拥有多个直接或间接基类的成员。不同名成员访问不会出现二义性。如果不同的基类有同名成员,派生类对象访问时应该加以识别。
●多继承的析构函数
●析构函数名同样与类名相同,无返回值、无参数,而且其定义方式与基类中的析构函数的定义方式完全相同。
●功能是在派生类中对新增的有关成员进行必要的清理工作。
●析构函数的执行顺序与多继承方式下构造函数的执行顺序完全相反,首先对派生类新增的数据成员进行清理,再对派生类对象成员进行清理,最后才对基类继承来的成员进行清理。
●赋值兼容规则、
赋值兼容规则指在程序中需要使用基类对象的任何地方,都可以用公有派生类的对象来替代。
赋值兼容规则中所指的替代包括以下的情况:
a 派生类的对象可以赋给基类对象
b 派生类的对象可以初始化基类的引用
c 派生类的对象的地址可以赋给基类类型的指针
●虚函数与多态性
多态性(Polymorphism)是指一个名字,多种语义;或界面相同,多种实现。
重载函数是多态性的一种简单形式。
虚函数允许函数调用与函数体的联系在运行时才进行,称为动态联编。
●虚函数和动态联编
冠以关键字 virtual 的成员函数称为虚函数
实现运行时多态的关键首先是要说明虚函数,另外,必须用基类指针调用派生类的不同实现版本
●虚函数和基类指针
一个虚函数,在派生类层界面相同的重载函数都保持虚特性
虚函数必须是类的成员函数
不能将友元说明为虚函数,但虚函数可以是另一个类的友元
析构函数可以是虚函数,但构造函数不能是虚函数
●虚函数的重载特性
在派生类中重载基类的虚函数要求函数名、返回类型、参数个数、参数类型和顺序完全相同
如果仅仅返回类型不同,C++认为是错误重载
如果函数原型不同,仅函数名相同,丢失虚特性
●虚析构函数
构造函数不能是虚函数。建立一个派生类对象时,必须从类层次的根开始,沿着继承路径逐个调用基类的构造函数
析构函数可以是虚的。虚析构函数用于指引 delete 运算符正确析构动态对象
●说明
1.派生类应该从它的基类公有派生。?
2.必须首先在基类中定义虚函数。
3.派生类对基类中声明虚函数重新定义时,关键字virtual可以不写。
4.一般通过基类指针访问虚函数时才能体现多态性。
5.一个虚函数无论被继承多少次,保持其虚函数特性。
6.虚函数必须是其所在类的成员函数,而不能是友元函数,也不能是静态函数。
7.构造函数、内联成员函数、静态成员函数不能是虚函数。
(虚函数不能以内联的方式进行处理)
8.析构函数可以是虚函数,通常声明为虚函数。
●纯虚函数和抽象类
纯虚函数是一种特殊的虚函数,
在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。
这就是纯虚函数的作用。
●纯虚函数和抽象类
纯虚函数是一个在基类中说明的虚函数,在基类中没有定义, 要求任何派生类都定义自己的版本
纯虚函数为各派生类提供一个公共界面
纯虚函数说明形式:
virtual 类型 函数名(参数表)= 0 ;
一个具有纯虚函数的基类称为抽象类。
例子
●公有继承
#include<iostream>
using namespace std;
class A
{ public :
void get_XY() { cout <<"Enter two numbers of x, y : " ; cin >> x >> y ; }
void put_XY() { cout <<"x = "<< x << ", y = " << y << '\n'; }
protected: int x, y ;
};
class B : public A
{ public :
int get_S() { return s ; };
void make_S() { s = x * y ; }; //使用基类数据成员x,y
protected: int s;
};
class C : public B
{ public :
void get_H() { cout <<"Enter a number of h : " ; cin>> h ; }
int get_V() { return v ; }
void make_V() { make_S(); v = get_S()* h ; } // 使用基类成员函数
protected: int h, v;
};
int main()
{ A objA ;
B objB ;
C objC ;
cout << "It is object_A :\n";
objA.get_XY() ;
objA.put_XY() ;
cout << "It is object_B :\n";
objB.get_XY() ;
objB.make_S() ;
cout << "S = " <<objB.get_S() << endl ;
cout << "It is object_C :\n";
objC.get_XY() ;
objC.get_H();
objC.make_V() ;
cout << "V = " <<objC.get_V() << endl ;
}
●定义一个基类person(不定义构造函数)
姓名、性别、年龄(访问权限设置为私有)
定义公有的成员函数set_p()
定义公有的成员函数display_p(),显示person的信息
再由基类派生出学生类(不定义构造函数,采用公有继承的方式)
增加学号、班级、专业和入学成绩
定义公有成员函数set_t()
定义成员函定义公有的成员函数display_s(),显示所有的信息
#include<iostream>
#include<string>
using namespacestd;
class Person
{
string name;
int age;
string sex;
public:
void set_p() {
cout<<"name\tage\tsex"<<endl;
cin>>name>>age>>sex;
}
void show_p() {
cout<<name<<" "<<age<<" "<<sex<<endl;
}
};
class student :publicPerson
{
string no;
string zhuanye;
string t_class;
float score;
public:
void set_t(){
set_p(); //调用继承于基类的成员函数访问继承于基类的私有数据成员
cout<<"zhuanye\tt_class\tscore"<<endl;
cin>>zhuanye>>t_class>>score;
}
void show_t() {
show_p();
cout<<zhuanye<<" "<<t_class<<" "<<score<<endl;
}
};
●
#include<iostream>
#include<string>
using namespacestd;
class Person
{
string name;
int age;
string sex;
public:
void set() {
cout<<"name\tage\tsex"<<endl;
cin>>name>>age>>sex;
}
void show() {
cout<<name<<" "<<age<<" "<<sex<<endl;
}
};
class student:public Person
{
string no;
string zhuanye;
string t_class;
float score;
public:
void set(){ //隐藏了基类中的同名成员
Person::set(); //调用继承于基类的成员函数访问继承于基类的数据成员
cout<<"zhuanye\tt_class\tscore"<<endl;
cin>>zhuanye>>t_class>>score;
}
void show() {
Person::show();
cout<<zhuanye<<" "<<t_class<<" "<<score<<endl;
}
};
●成员函数调用虚函数(采用动态联编)
#include<iostream>
#include<string>
using namespacestd;
class Animal
{
string name;
public:
Animal(string a_name):name(a_name){}
virtual void show(){}
void show_name()
{
cout<< "The nameis "<<name<<".“<<endl;
}
};
class Cat :publicAnimal
{
string kind;
public:
Cat(string a_name,stringa_kind):Animal(a_name),kind(a_kind)
{}
void show();
};
void Cat::show()
{
show_name();
cout<<" It's a"<<kind<<endl;
}
class Dog:publicAnimal
{
string kind;
public:
Dog(string a_name,stringa_kind):Animal(a_name),kind(a_kind)
{}
void show();
};
void Dog::show()
{
show_name();
cout<<" It's a"<<kind<<endl;
}
class Tiger:publicCat
{
public:
Tiger(string a_name,stringa_kind):Cat(a_name,a_kind)
{}
};
int main()
{
Animal *p;
Catcat("Tom","cat");
Dogdog("Jerry","Dog");
Tigertiger("DuDu","Tiger");
p=&cat;
p->show();
p=&dog;
p->show();
p=&tiger;
p->show();
return 0;
}
●抽象类
class point { /*……*/ } ;
class shape ; // 抽象类
{ point center ;
……
public :
point where ( ) { return center ; }
void move ( point p ) {center = p ; draw ( ) ;
}
virtual void rotate ( int ) = 0 ; // 纯虚函数
virtual void draw ( ) = 0 ; //纯虚函数
} ;
shape x ; // error,抽象类不能建立对象
shape *p ; //ok,可以声明抽象类的指针
shape f ( ) ; // error, 抽象类不能作为函数返回类型
void g ( shape ) ; //error, 抽象类不能作为传值参数类型
shape & h ( shape &) ; // ok,可以声明抽象类的引用
学习感悟
通过对继承的学习,我们可以更好地精简代码。所谓继承,顾名思义,是一个类继承另一个类的部分,如果我们想要实现的两个类的部分功能或成员是相同的,那么我们就可以通过继承来缩减代码。
当然,想要熟练地使用继承,首先我们应该有良好的逻辑关系,因为继承是两个类之间的联系,所以这就要求我们在平时的学习中多思考,理清类与类之间的关系,而不是糊里糊涂。其次,是对其具体知识的熟练掌握,这就要求我们课下多练习用继承来巩固基础知识。
多态可以实现向不同的对象发送同意消息,不同的对象会在接受时产生不同的行为。比如更录一个网站,操作都相同,输入账号密码,但是用户登录的页面和管理员的却不相同,真正能够实现了C++的面向对象的程序设计。