c++面向对象基础知识 Day 3

来源:互联网 发布:vmtools mac 10.11 编辑:程序博客网 时间:2024/06/14 11:40

析构函数

析构函数(destructor)与构造函数相反,当对象脱离其作用域时(例如对象所在的函数已调用完毕),系统自动执行析构函数。析构函数往往用来做清理善后的工作(例如在建立对象时用new开辟了一片内存空间,应在退出前在析构函数中用delete释放)。
析构函数名也应与类名相同,只是在函数名前面加一个波浪符~ ,例如~stud(),以区别于构造函数。它不能带任何参数,也没有返回值(包括void类型)。只能有一个析构函数,不能重载。如果用户没有编写析构函数,编译系统会自动生成一个缺省的析构函数,它也不进行任何操作。所以许多简单的类中没有用显式的析构函数。

/*包含构造函数和析构函数的c++程序*/#include<string.h>#include<iostream.h>class stud{ private: int num; char name[]; char sex; public: stud(int n,char nam[],char s) {  n=num;  strcpy(name,nam);  sex=s; } ~stud() {} void display() { cout<<"num:"<<num<<endl; cout<<"name:"<<name<<endl; cout<<"sex:"<<sex<<endl; }};void main(){stud stud1(10010,"Wangjun",'M'),stud2(10011,"jianglin",'F')stud1.display();stud2.display();}

现在把类的声明放在main函数之前,它的作用域是全局的。这样做可以使main函数更简练一些。在main函数中定义了两个对象并且给出了初值。然后输出两个学生的数据。
本程序中,成员函数是在类中定义的,如果成员函数的数目很多以及函数的长度很长,类的声明就会占很大的篇幅,不利于阅读程序。可以在类的外面定义成员函数,而在类中只用函数的原型作声明。

//在类的外面定义成员函数#include<string.h>#include<iostream.h>/*如果你用#include<iostream.h>就不需写这句话(旧标准)。但是如果你用#include<iostream>就必须要写using namespace std。*/class stud{ private:    int num;    char name[10];//错写成char name;    char sex;public:    stud(int n,char nam[],char s);    ~stud();    void display();};stud::stud(int n,char nam[],char s){    num=n;    strcpy(name,nam);    sex=s;}stud::~stud(){}void stud::display()//少写了void{    cout<<"num:"<<num<<endl;    cout<<"name:"<<name<<endl;    cout<<"sex:"<<sex<<endl;}void main(){    stud stud1(10010,"Wangjun",'M'),stud2(10011,"chenglin",'F');    stud1.display();    stud2.display();}

在类声明的外部定义函数,必须指定类名,函数首行的形式为
函数类型 类名::函数名(形参表列)

继承与派生

面向对象技术强调软件的可重用性。在c++中可重用性是通过继承这一机制来实现的。因此,继承是C++的一重要组成部分。
前面介绍了类,一个类中包含了若干数据成员和成员函数。每个类的数据成员和成员函数是不相同的。但是有的时候两个类的内容基本相同或有一部分相同。
例如前面声明了学生基本数据的类stud;

class stud{  private:    int num;    char name;    char sex;  public:    void display()    {      cout<<"num:"<<num<<endl;      cout<<"name:"<<name<<endl;      cout<<"sex:"<<sex<<endl;    }}

如果学校的某个部门除了需要用到学号、姓名、性别以外,还需要用到年龄、地址等信息。当然可以重新声明另外一类:

class stud1{  private:  int num;  char name;  char sex;  int age;  char addr[20];  public:  void display()  {    cout<<"num:"<<num<<endl;    cout<<"name:"<<name<<endl;    cout<<"sex:"<<sex<<endl;    cout<<"age:"<<age<<endl;    cout<<"addr:"<<addr<<endl;  }};

可以看到相当一部分是原来已有的。很少人自然会想到能否利用原有声明的类作为基础,再加上新的内容即可,以减少重复的工作量。C++提供的继承机制就是解决这个问题。

在c++中所谓的继承就是在一个已存在的类的基础上建立一个新的类,已存在的类称为基类或父类;新建立的类称为派生类或子类,派生类继承了基类的所以数据成员和成员函数,并增加新的成员。

建立派生类的方法

先通过一个例子说明怎样通过继承来建立派生类
假设已经声明了一个基类stud,在此基础上声明一个派生类student:

class student:public stud{ private:  int age;  char addr[30]; public:  void display()  {   cout<<"age:"<<age<<endl;   cout<<"addr:"<<addr<<endl;  }};

仔细观察第一行:
class student:public stud
在class后面的student是新建的类名。冒号后面的stud表示是已存在的基类。在stud之前有一关键字public,用来表示基类stud中的成员在派生类student中的使用权限。基类名前有public的称为“公用派生类”。
定义派生类的一般形式:
class 派生类名:[引用权限] 基类名
{
派生类新增加的数据成员
派生类新增加的成员函数
};
引用权限可以是private和public。不写的话默认是private。
派生类包括基类成员和自己增加的成员,派生类的成员函数在引用派生类自己的数据成员时,按前面介绍过的规则处理(即私有数据成员只能被同一类中的成员函数引用,公用成员可以被外界引用)。而对从基类继承来的成员的引用并不是简答地把基类的私有和公用成员直接作为派生类的私有成员和公用成员,而要根据基类成员的引用权限和派生类声明的引用权限共同决定。

公用派生类
在声明一个派生类的时候将基类的引用权限指定为public的,该类称为基类的公用派生类。
在公用派生类中,基类的公用成员和保护成员仍然称为派生类中的公用成员和保护成员,而基类的私有成员不能被派生类引用,即成为派生类不可访问的成员,只有基类的成员函数可以引用它。基类的成员在公用派生类中的引用权限见下表:

基类 私有成员 公用成员 公用派生类 不可访问的成员 公用成员
/*访问基类成员*/class stud{ private:  int num;  char name[10];  char sex; public:  void display()  {   cout<<"num:"<<num<<endl;   cout<<"name:"<<name<<endl;   cout<<"sex:"<<sex<<endl;  }};class student:public stud{ private: int age; char addr[30]; public: void show() {//cout<<"name:"<<name<<endl;引用基类的私有成员,错误。  cout<<"age:"<<age<<endl;  cout<<"addr:"<<addr<<endl; }};

由于基类的私有成员对派生类来说是不可访问的,因此在派生类的show函数中直接引用基类的私有数据成员name是不允许的。可以通过基类的公用成员函数来引用基类的私有数据成员。上面对派生类student的声明可改为

class student:public stud{ private:  int age;  char addr[30]; public:  void show()  {   display();//引用基类的公有成员函数允许。   cout<<"age:"<<age<<endl;   cout<<"addr:"<<addr<<endl;  }};

在派生类成员函数show中引用基类的公用成员函数display,通过display引用基类stud中的私有数据num、name和sex。可以这样写main函数(假设对象a中已有数据);

void main(){ student a;//定义一个student派生类的对象a ... a.show();//输出a对象的5个数据}

在主函数中如下程序:
a.display();//正确,从基类继承的公用成员函数
a.age=18;//错误。外界不能引用基类的私有成员

私有派生类
在声明一个派生类时,将基类的引用权限指定为private的,该类称为基类的私有派生类。
在私有派生类中,基类的公用成员和保护成员成为派生类的私有成员,基类的私有成员称为派生类的不可访问成员。只有基类的成员函数可以引用它。基类的成员在私有派生类中的引用权限见下表:

基类 私有成员 公用成员 私有派生类 不可访问的成员 私有成员

如果派生类首行改为

class student;private stud{ private: int age; char addr[20]; public: void show() {  display();//基类的公有成员函数变成派生类的私有函数  cout<<"age:"<<age<<endl;  cout<<"addr:"<<addr<<endl; }};void main(){ student a;  a.display();  a.age=18;}

可以看到:
(1)不能通过私有派生类对象引用从基类继承过来的任何成员;
(2)在派生类的成员函数中不能访问基类的私有成员,但可以访问基类的公用成员,由于私有派生类限制太多,一般不常用。

保护成员
由protect声明的成员称为保护成员,不能被外界引用(这点和私有成员类似),但可以被派生类的成员函数引用。

基类 私有成员 公用成员 保护成员 公用派生类 不可访问的成员 公用成员 保护成员 私有派生类 不可访问的成员 私有成员 私有成员

从前面的介绍已知基类的私有成员被派生类(不论是私有派生类还是公用派生类)继承后变为不可访问的成员。如果想在派生类引用基类的成员,可以将基类的成员声明为protected。

/*派生类引用保护成员*/class stud              //声明基类{ protect:                //基类保护成员  int num;  char name[10];  char sex; public:                  //基类公用成员  void display()          //基类成员函数  {   cout<<"num:"<<num<<endl;   cout<<"name:"<<name<<endl;   cout<<"sex:"<<sex<<endl;  }};class student:public stud  //声明一个公用派生类{ private: int age; char addr[30]; public: void show() {  cout<<"num:"<<num<<endl;  //引用基类的保护成员,合法  cout<<"name:"<<name<<endl;  cout<<"sex:"<<sex<<endl;  cout<<"age:"<<age<<endl;   //引用派生类的私有成员,合法  cout<<"address:"<<addr<<endl; }};void main(){ student a;  //a是派生类student类的对象 a.show();    //合法,show是派生类中的公用成员函数// a.num=10023; 错误,外界不能访问保护成员}

派生类的构造函数
派生类从基类继承了非私有成员函数和数据成员,但在建立派生类对象时,系统只执行派生类的构造函数,而不会自动执行基类的构造函数。也就是说,基类的构造函数是不能继承的。如果基类的构造函数包含对对变量的初始化,那么在建立派生类对象时,由于没有执行基类的构造函数,因而就会使基类的变量未初始化。所以在设计派生类的构造函数时,不仅要考虑派生类所增加的变量初始化,还应当考虑基类的变量初始化。在执行派生类的构造函数时,应当调用基类的构造函数。

/*派生类的构造函数*/#include<string.h>#include<iostream.h>class stu{ protected: int num; char name[20]; char sex; public: stud(int n,char nam[],char s) {  num=n;  strcpy(name,nam);  sex=s; }~stud(){}};class student:public stud{ private: int age; char addr[30]; public: student(int n,char nam[],char s,int a,char ad[]):stud(n,nam,s) {age=a;  strcpy(addr,ad); } void show() {  cout<<"num:"<<num<<endl;  cout<<"name:"<<name<<endl;  cout<<"sex:"<<sex<<endl; }~student(){}};void main(){ student a(10010,"Wangjun",'f',22,"420hust"); student b(10010,"Chenglin",'m',18,"321hust"); a.show(); b.show();}

请注意派生类构造函数首行的写法:

student(int n,char nam[],char s,int a,char ad[]):stud(n,nam,s)

其一般的形式为
派生类构造函数函数名(参数表列):基类构造函数名(参数表列)
派生类构造函数名后面括号内的参数表列包括参数的类型和参数名。基类构造函数名后面括号内的参数表列只有参数名而不包括参数类型。从基类的声明中可以中可以看到基类构造函数stud有3个参数,派生类构造函数有5个参数,前三个是用来传递给基类构造函数的,后面2个是用来对派生类所增加的变量初始化的。

在上例中也可以将派生类构造函数在类外面定义,而在类的声明中只写函数的声明

student(int n,char nam[],char s,int a,char ad[]);

在类的外面定义派生类构造函数:

student::student(int n,char nam[],char s,int a,char ad[]):stud(n,nam,s){ age=a; strcpy(addr,ad);}

注意:在类中对派生类构造函数作声明时,不包括基类构造函数名和参数表列,只在定义函数时才将它列出。

在建立一个对象时,由派生类构造函数先调用基类构造函数,然后再执行派生类构造函数本身。对上例来说,先初始化num,name,sex,然后再初始化age和addr。
在对象消失时,先执行派生类析构函数,再执行其基类析构函数。

0 0
原创粉丝点击