C++的类派生与虚拟函数

  • 格式:doc
  • 大小:193.50 KB
  • 文档页数:22

100 第六章 C++的类派生与虚拟函数

第四章中已讨论的类与数据封装概念可以说是C++的第一大特征。而本章将要论述的类派生与多态概念则是C++的另外两大特征。由于有了前两章的基础,所以将这两个特征合并一章讨论也就不太困难了。

§1 C++语言所处理的类的相互关系

§1.1 包容关系的实现

一个类包含有一个以上的类作为其独立的属性成员的关系称为包容关系。被包容的类作为包容类的一个成员当然具备其成员地位应有的各种特性,但由于被包容类自身也含有成员,所以二者间又必然存在有其相互间的关联关系。

例1:

#include

#include

class Course

{

char name[10];

int score;

public:

Course(){}

void init(char* na, int sc)

{

score=sc;

name[9]='\0';

strncpy(name,na,9);

}

int getscore(){return score;}

char *getname(){return name;}

};

class Student

{

char name[10];

int number,total;

Course *p;

public:

Student(char* na, int nu):number(nu)

{

int i,ccs;

char cna[10];

name[9]='\0';

strncpy(name,na,9);

cout<<"How many course are the student selected:";

cin>>total;

p=new Course[total];

cout<<"Please input each course name and it's score:"<

for(i=0;i

{

cout<<"The course name:";

cin>>cna;

cout<<"the score:";

cin>>ccs;

p[i].init(cna,ccs);

}

}

void operator!()

{

int i;

cout<<"The student's name is "<

{

cout<<" "<

}

}

};

void main()

{

Student s1("Wang",123);

!s1;

}

本例中的Student类的private区包容了一个Course类的指针。考虑到一个学生与课程的关系为1:n,所以在初始一个学生对象时临时确定该学生所选的课程数目。考察Course类会发现其内的缺省构造函数非常简单,这便是被包容类的特征。当一个类要包容另一个类的不确定数目的对象且其成员初始工作不能在一次构造函数的引用中完成时,被包容类通常都是以数组或链表等结构出现在包容类中的。此时被包容类的构造函数很难能发挥太大的作用,因此其成员初始化工作只能将内存征用和初始赋值两个步骤分开进行。前者由缺省构造函数实现,而后者自然要由其它的成员函数在以后的引用中完成了。C++语言的包容关系具有下述特征:

·具有包容关系的两个类存在各自的独立性,即自身对象的存在不依赖于对方是否存在(与理论概念有区别);

·被包容类对象只能以一个成员的身份被定义在包容类中,因而要受到成员所在区属性的影响;

·被包容类自身的成员不会由于存在包容关系而成为包容类成员;

§1.2类模板

在客观模型中的类内的成员数据和成员函数的参数或返回值类型也有不能确定类型的情形。此时可应用类模板来取代这些未知的成分,待到运行时再动态地装入具体的类型。类模板的声明的语法格式如下:

template

与函数模板相同的格式要求,类模板的声明的语句也必须置于类声明的前面。如有两个以上的模板参数时,应使用逗号分隔。使用含类模板的类定义对象时也必须在类名的后面带上“”的参数形式。类模板最常用于各种类包容关系的设计模型之中。

例2:

#include

template

class Array

{

T *ar;

public:

Array(int c){ar=new T[c];}

void init(int n,T x){ar[n]=x;}

T& operator[](int n){return ar[n];}

};

void main()

{

Array array(5);

cout<<"Please input every element's value:"<

for(int i=0;i<5;i++)

{

cout<<"No."<

cin>>array[i];

}

}

本例是在类Array内包容了一个未知类型的数组,在全局函数main()中导入的实际类型为int。利用对算苻“[]”的重载而大大简化了数组的初始化。读者还可向Array类内加入排序、插入、删除、交换、求平均数或输出等成员函数。在设计模型中实际导入的类型也可以是另一个类类型。

例3: 102 #include

class A

{

int j;

public:

A(){}

A(int x):j(x){}

A(A *x){j=x->j;}

void operator!(){cout<<"J="<

};

template

class B

{

int i;

T *x;

public:

B(int xa,T *p):i(xa){x=new T(p);}

void operator!(){cout<<"I="<

};

void main()

{

A a(1); //最后的显示结果为:

B b(2,&a); //I=2

!b; //J=1

}

本例体现了类的包容关系。在类的包容关系中常有将一组类对象装进包容类对象中再参加各种运算的需求。

例4:

#include

#include

class Student

{

int number;

static Student *ip;

Student *p;

public:

Student(){p=NULL;}

Student(int n);

static Student* get_first(){return ip;}

int get_number(){return this->number;}

Student* get_next(){return this->p;}

};

Student::Student(int n):number(n) //依据学号的大小顺序将学生对象插入链表

{

p=NULL;

if(ip==NULL)ip=this; //如果是第一个则使头指针指向该对象

else

{

Student *temp=ip;

if(nnumber){ip=this;p=temp;}//如果学号小于第一个学生对象的学号则使头指针指向该对象

else

{

while(temp)

{

if(np->number)

{

p=temp->p; //链中间对象的插入 103 temp->p=this;

break;

}

else

{

if(temp->p->p==NULL) //最后一个链的插入

{

temp->p->p=this;

break;

}

}

temp=temp->p;

}

}

}

}

Student* Student::ip=NULL;

template

class Class

{

int num;

T *p;

public:

Class(){}

Class(int n):num(n){p=NULL;}

T* insert(int n){p=new T(n);return p;}

void list_all_member(T* x)

{

T *temp=x;

while(temp)

{

cout

temp=temp->get_next();

}

}

};

void main()

{

Class x97x(9707);

x97x.insert(23);

x97x.insert(12);

x97x.insert(38);

x97x.insert(22);

x97x.insert(32);

x97x.list_all_member(Student::get_first());

}

本例是一个可包容多个学生的班级类的设计模型的实现。被插入的学生对象按其学号自小到大构成单向的对象链表结构。为突出插入的过程,处理链表的构造函数的定义体被移至class之外。读者可在此基础上很容易地写出相应的删除、查询等成员函数了。

§1.3 派生关系

在C++语言中一个类的的成员是由继承另一个类的部分或全部的成员与自身定义的成员所构成的结构关系称为派生关系。将被继承的类称为基类(或父类),将继承基类后产生的类称为派生类。继承与派生并非是完全透明的,在实现中派生类必须以适当的继承方式来对继承得到的成员实施访问。为了保证数据封装的安全,无论何种继承方式得到的派生类都不能直接访问处在基类的private区内的任何成员。C++语言有专门的语句来描述。其语法格式为:

class 类名:[public |private] 基类名{„定义体};

定义式中若省略“public”或“private”关键字,则缺省继承方式值为 private。