C++程序设计教程第8章

更新时间:2023-06-03 15:16:01 阅读量: 实用文档 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

本书比较全面的介绍了C++编程语言

第8章 类 与 对 象

教学提示:C++程序设计语言被认为是一种混合型语言,其主要特点是既支持面向过程的程序设计,又全面支持面向对象的程序设计。面向对象的程序设计语言有3个特性:封装性、继承性和多态性。通过这些特性来模拟自然界真实的世界,从而改进了软件设计的设计理念和方法。

类是面向对象程序设计的核心,它实际上是一种新的数据类型,也是实现数据抽象和过程抽象的综合体。类是某一类对象的抽象化,而对象是某一种类的实例化,因此,类和对象是密切相关的。没有脱离对象的类,也没有不依赖于类的对象。

本章将讨论类的定义、对象的声明以及如何更好地设计、实现一个类的相关知识。 学习目标:

z 掌握类和对象的定义方法;

z 理解类的成员的访问控制的含义,公有、私有和保护成员的区别; z 掌握构造函数和析构函数的含义与作用、定义方式和实现; z 理解静态成员的特性;

z 掌握内联函数和重载函数在类设计上的应用; z 掌握友元函数和友元类的含义和特点;

z 了解嵌套类的定义方式,掌握子对象的初始化方法; z 理解对象指针和对象数组的定义和使用方法; z 掌握常类型函数参数的使用要领;

z 掌握常对象、常成员的定义、使用方法。

8.1 类和对象的定义

8.1.1 类的定义

不同的事物构成了丰富多彩的世界,为了更好地了解世界、改造世界,我们常常将事物分成不同的类,如人类、鸟类等。也就是说,“类”是具有相同属性和行为的事物的归并集合。显然,“类”不代表任何具体的事物,具有一定的抽象性。根据事物属性和行为的不同,可以分化抽象出许多不相同的类。

在现实世界中并不真正存在类,存在的只是属于某类的一个个具体的事物,称之为对象。 在面向对象程序设计中,类(class)就是对现实世界中抽象出的“类”的模拟描述,是用户自己定义的数据类型,它的一般定义格式如下:

class <类名> {

private:

本书比较全面的介绍了C++编程语言

第8章 类与对象

<私有数据成员和成员函数>; protected:

<保护数据成员和成员函数>; public:

<公有数据成员和成员函数>; };

<类中的各个成员函数的实现>

·213·

关于类的定义有以下几点说明。

(1) class是定义类的关键字,<类名>是一个标识符,用于唯一地标识一个类。

(2) 类的定义分为两个部分:第1部分是类的说明部分,说明该类所拥有的成员(包括数据成员和函数成员);第2部分是实现部分,它对类内说明的成员函数进行定义,即给出函数功能的具体实现。

(3) 在类外对类成员函数的定义必须在函数名前加类名和运算符“::”,符号“::”称为作用域运算符(也称作用域分辨符)。它是用来表示某个成员函数属于哪个类的。

(4) public(公有)、private(私有)和protected(保护)3个关键字用来确定类成员的访问权限,其中默认为private。

下面先看一个关于日期类的定义例子。

//说明部分 class Date {

public:

void InputDate(int y,int m,int d); int IsLeapyear( ); void print( ); private:

int year,month,day; };

//实现部分

void Date ::InputDate(int y,int m,int d) {

year=y; month=m; day=d; }

int Date::IsLeapyear( ) {

return (year%4==0&&year%100!=0)||(year%400==0); }

void Date::print() {

cout<<year<<“.”<<month<<“.”<<day<<endl; }

这个日期类有3个公有成员函数和3个私有的数据成员,类的实现放在了说明部分后面,显然需要注意的是必须有作用域运算符“::”。

定义类时应注意以下事项。

·213·

本书比较全面的介绍了C++编程语言

·214· C++程序设计

(1) 在类内不允许对所定义的数据成员进行初始化。

(2) 类中的数据成员的类型可以是任意的,也可以是其他类,但不能用自动(auto)、寄存器(register)或外部(extern)的存储类型进行说明。

(3) 一般在类内先说明公有成员,后说明私有成员,但此顺序并不是固定的。 (4) 一般将类定义的说明部分或者说明与实现两部分放在一个头文件中。 (5) 在类的说明部分之后必须加分号“;”。

(6) 类的成员函数定义通常可采取两种方式,即外部定义和内部定义。内部定义即是指将成员函数的实现放在类的说明部分内,如日期类可改为:

class Date {

public:

void InputDate(int y,int m,int d) {

year=y; month=m; day=d; }

int IsLeapyear( ) {

return (year%4==0&&year%100!=0)||(year%400==0); }

void print( ) {

cout<<year<<“.”<<month<<“.”<<day<<endl; }

private:

int year,month,day; };

但这种方式是不推荐的,因为它影响到了程序的可读性。 8.1.2 类成员的访问控制

通过类的定义可以看出类的成员包括数据成员和成员函数,它们分别描述一类事物的属性和行为。现在大型软件的设计往往需要很大的一个团队,如微软的Office办公软件,据报道参与设计的有4000多人。因此,为了整个系统的安全,有些重要数据需要保护起来,从规则上就不允许更改。C++中,类内的成员在访问权限上是有不同限制的,总体上可分为:公有的(public)、私有的(private)和保护的(protected)3类。若某个成员未进行任何访问权限声明,则默认为private(私有的)。

(1) 公有的成员用public来说明,这部分往往是一些操作(即成员函数),作为类与外界的接口,所有公有的成员都可以被程序中的任何代码访问。

(2) 私有的成员用private来说明,这部分通常是一些数据成员,这些成员是用来描述该类中对象的属性,只有成员函数或经特殊说明的函数(如友元函数)才可以引用它们,它们是特意被用户隐藏起来的部分,用户在类外其他地方是无法访问它们的。

(3) 保护的成员用protected来说明,其限定能力介于私有和公有之间,除了类本身的

· 214·

本书比较全面的介绍了C++编程语言

第8章 类与对象 ·215·

成员函数、友元函数可以访问成员外,该类的派生类(子类)的成员函数也可以访问,关于保护成员的使用将在以后章节中介绍。

关键字public、private和protected被称为访问权限修饰符或访问控制修饰符。它们在类体内(即一对花括号内)出现的先后顺序没有要求,并且允许多次出现。例如,下面定义的日期类同样是合法的:

class Date {

public:

void InputDate(int y,int m,int d); int IsLeapyear( ); private:

int year,month,day;

public:

void print( ); };

8.1.3 对象的定义

对象是类的实例或实体。表面上看,对象是某个“类”类型的变量,但对象又不是普通的变量,对象是一个数据和操作的封装体。封装的目的就是阻止非法权限访问,因此对象实现了信息的隐藏,外部只能通过操作接口访问对象数据。由于所有的对象都是属于某个已知类的,因此必须先定义类,然后才能定义对象,用类来定义对象在格式上与普通类型定义变量是完全相同的。

定义对象的一般形式:

例如,用前例所定义的类定义对象:

这样就定义了一个Date类的对象date。

另外,在定义类的同时,也可以直接定义对象,即在声明类的右花括号“}”后,直接写出该类的对象名表,这种方法现在很少使用。 8.1.4 对象成员的访问方式

不论是数据成员,还是成员函数,只要是公有的,在类的外部都可以对类的对象成员进行访问,有时简称为对对象的访问。

对象成员访问的一般形式是:

其中“.”称为对象选择符,简称点运算符。

下面的例子中定义了Coord类的两个对象及简单的访问。 【例8.1】 定义、使用类Coord的对象。

·215·

<对象名>.<数据成员名>

或:<对象名>.<成员函数名>([<实参表>]) Date date; <类名> <对象名表>;

本书比较全面的介绍了C++编程语言

·216· C++程序设计

运行结果:

//程序名为ch08_1.cpp: #include<iostream.h> class Coord {

public:

void setCoord(int a,int b) {

x=a; y=b; }

int getx( ) {

return x; }

int gety( ) {

return y; }

private: int x,y; };

void main() {

Coord op1,op2; int i,j;

op1.setCoord(50,60); //初始化对象op1 op2.setCoord(71,85); //初始化对象op2 i=op1.getx( ); //取op1的x值 j=op1.gety( ); //取op1的y值

cout<<“op1 i= ”<<i<<“ op1 j= ”<<j<<endl; i=op2.getx( ); //取op2的x值 j=op2.gety( ); //取op2的y值

cout<<“op2 i= ”<<i<<“ op2 j= ”<<j<<endl; }

op1 i=50 op1 j=60 op2 i=71 op2 j=85

8.2 构 造 函 数

8.2.1 构造函数的定义和分类

构造函数是一种特殊的类的成员函数,它主要用于为对象分配空间和进行初始化工作。它除了具有一般成员函数的特征外,还具有以下一些特殊的性质。

(1) 构造函数的名字必须与类名相同。

(2) 构造函数可以有任意类型和任意个数的参数,故构造函数可以重载,但不能指定

· 216·

本书比较全面的介绍了C++编程语言

第8章 类与对象 ·217·

返回类型。

(3) 构造函数是特殊的成员函数,函数体可以写在类体内,也可写在类体外。

(4) 构造函数被声明为公有函数,但它不能像其他成员函数那样被显式地调用,它是在用类声明对象的同时被系统自动调用的。

理解构造函数的运行机理对理解类和对象非常重要。 【例8.2】 定义一个Person类,设计一个简单的构造函数。

//程序名为ch08_2.cpp: #include <iostream.h> #include <string.h> class Person {

public:

Person(char *input_name) //构造函数 {

name=new char[10];

strcpy(name,input_name); }

void show( ); private:

char *name; };

void Person::show( ) {

cout<<name<<endl; }

void main() {

Person student1("Zhang Ming"); student1.show(); cin.get(); }

运行结果:

程序说明:本程序定义了一个带有字符指针形参的构造函数:Person(char *input_name),调用构造函数的一般使用形式是:

本程序的Person student1("Zhang Ming"),实际上是用Person类声明对象并赋置初始 值时系统自动调用的。这里需要注意,由于在本例中没有不带参数的构造函数,因此不能这样定义对象:

解决这类问题的方法就是重载构造函数,如在Person类中可再加上下面的构造函数:

·217·

Person student;

//错误!因为编译器找不到恰当的构造函数

<类名> <对象名>([<参数表>]);

Zhang Ming

本书比较全面的介绍了C++编程语言

·218· C++程序设计

关于构造函数的规定请注意以下几点说明。

(1) C++规定,每个类必须有构造函数,没有构造函数就不能创建对象。

(2) 若没有提供任何构造函数,那么C++自动提供一个默认构造函数,默认构造函数就是一个没有参数的构造函数,它仅仅负责创建对象而不进行任何初始化操作。

(3) 只要类中提供了任意一个构造函数,那么C++就不再自动提供默认构造函数。 构造函数分为4类,它们是普通构造函数、默认构造函数、复制构造函数和类型转换构造函数。普通构造函数就是指有两个或两个以上参数的构造函数,接下来的几节将详细讨论一下其他3类构造函数。 8.2.2 默认构造函数

默认构造函数指的是没有任何参数的构造函数。如果在设计类时没有定义构造函数,C++编译程序会自动为该类建立一个默认的构造函数。这个默认构造函数没有任何形式参数,并且函数体为空。其格式如下:

按构造函数规定,默认构造函数名与类名相同。默认构造函数也可由程序员直接定义在类体中。例如,上面的Person类可定义为:

class Person {

public:

Person( ){} //默认构造函数 Person(char *input_name); //构造函数 void show( ); private:

char *name; };

<类名>::<默认构造函数名>() { }

Person ( ) { }

另外,如果构造函数的所有参数都是默认参数,这样的构造函数也被称为默认构造函数,例如:

class A {

public:

A( ); A( int x = 10 ); };

//构造函数没有参数

//构造函数的参数有默认值

这两种情况都是默认构造函数,但这个例子中有个错误,这两个默认构造函数若同时存在的话,编译器则无法判断需要调用哪一个,所以规定在一个类中只能有一个默认构造函数。

· 218·

本书比较全面的介绍了C++编程语言

第8章 类与对象

8.2.3 复制构造函数

·219·

复制构造函数是一种特殊的成员函数,用于依据已存在的对象建立一个新对象。其一般形式为:

class T {

public:

T (const T & 对象名); //复制构造函数 ··· }

T::T (const T & 对象名) //复制构造函数的实现 { 函数体}

其中,T代表任何一个类的名字;const是一个类型修饰符,被它修饰的对象是不能被变更的常量。

关于复制构造函数有以下几点说明。

(1) 因为它也是一种构造函数,所以该函数名与类名相同,函数无返回类型。 (2) 该函数只能有一个参数,并且必须是对同类对象的引用,一般应为const型。 (3) 每个类都必须有一个复制构造函数,如果类中没有声明复制构造函数,则编译系统会自动生成一个默认的复制构造函数,作为该类的公有成员。

例如:

class Person {

public:

Person(const Person &p); //复制构造函数 ··· private:

char *name; }

Person ::Person(const Person &p) {

if( ! ) return;

int length = strlen() +1;

name = new char[length]; //必须开辟空间 strcpy(name,); }

注意:复制构造函数的执行机理是“按位值复制”,称之为浅复制,即在执行复制构造函

数时,只是简单地将对象数据成员的值进行简单的复制。值得注意的是,浅复制有时会出错误!比如,如果本例没有设计复制构造函数,系统自动生成默认的复制构造函数将执行“浅复制”,那么就会出现Person类两个对象的name指向同一个字符串,这是十分危险的。后面将讲到析构函数在释放对象的空间时就会出现错误,因为对同一块内存空间释放了两次。因此,需要自己编写能够动态分配所需空间的复制构造函数,就像本例,这种复制称之为深复制。

·219·

本书比较全面的介绍了C++编程语言

·220· C++程序设计

一般来说,只需浅复制时最好利用系统自动生成的复制构造函数,这样效率高。若需要深复制,则必须自己编写出能够开辟空间的复制构造函数。

复制构造函数主要在如下3种情况中起到初始化作用。 (1) 声明语句中用一个对象初始化另一个对象。例如:

这时,系统需要调用复制构造函数。

(2) 函数的参数是值参数时,若对象作为函数实参传递给函数形参,这时需要调用复制构造函数。

(3) 当对象作为函数返回值时,如执行return R时,系统将用对象R来初始化一个匿名对象,这时需要调用复制构造函数。 【例8.3】 复制构造函数的示例。

//程序名为ch08_3.cpp:

#include <iostream.h> class Person {

public:

Person(char *input_name=NULL) //默认构造函数 {

name=new char[10];

strcpy(name,input_name); }

Person(Person &p) //复制构造函数 {

if( ! ) return;

int length = strlen() +1; name = new char[length]; //必须开辟空间 strcpy(name,); }

void show( ); private:

char *name; };

void Person::show( ) {

cout<<name<<endl; }

void showagain(Person p) {

cout<<p.show( ); }

Person pass( ) {

Person s("Li Peng"); · 220·

Person student2(student1);

本书比较全面的介绍了C++编程语言

第8章 类与对象

return s; }

void main( ) {

Person student1("Zhang Ming");

Person student2(student1); //此时会调用复制构造函数 student1.show( ); student2.show( ); Person student3;

student3=pass( ); //此时会调用复制构造函数 showagain(student3); //此时会调用复制构造函数 cin.get(); //系统等待键盘输入 }

·221·

运行结果:

程序说明:用一个对象初始化另一个对象,系统会调用复制构造函数,本例中student1利用此机制,将其值复制给了student2。当对象作为函数参数时,因为要用实参初始化形 参,情况与此类似,也要调用复制构造函数;当函数用return返回某类的对象时,函数返回的值要首先传递给系统产生的一个匿名对象,复制构造函数因此会被调用,然后,匿名对象再传递给表达式中的对象,如本例的student3。此处有点复杂难懂,读者可以简单了解即可。

8.2.4 类型转换构造函数

在实际应用中,若在类定义中提供了单个参数的构造函数时,这个参数的类型不是这个类的类型(即不是复制构造函数),那么该类相当于提供了一种将其他数据类型的数值或变量转换为自身类型的方法。此种构造函数即为类型转换构造函数。 【例8.4】 类型转换构造函数。

//程序名为ch08_4.cpp: #include<iostream.h> class A {

public: A( ) //默认构造函数 {

m=0; }

A(double i) //类型转换构造函数 {

m=i; }

void print( ) {

Zhang Ming Zhang Ming Li Peng

·221·

本书比较全面的介绍了C++编程语言

·222·

cout<<m<<endl; }

private: double m; };

void main() {

A a(5); a.print();

a=10; a.print(); }

C++程序设计

//调用类型转换构造函数 //也会调用类型转换构造函数

运行结果:

5 10

程序说明:在本程序中,赋值语句“a=10;”中,赋值号两边数值10和对象a是两个不相容的数据类型,可是它却能顺利通过编译程序,并且输出显示正确的结果,其主要原因是得益于单个参数的构造函数。编译系统先通过标准数据类型转换,将整型数值10转换成double型,然后,再通过类中定义的单参数构造函数将double型数值转换为A类类型,最后把它赋值给a。这些转换都是自动隐式完成的。用整数5初始化对象a的执行情况与此类似,不再详述。

8.3 析 构 函 数

析构函数也是类的一种特殊成员函数,其功能是用来释放一个对象的存储空间。它的功能可以说与构造函数正好相反。

析构函数有如下特点。

(1) 析构函数是成员函数,函数体可写在类体内,也可以写在类体外。

(2) 析构函数也是一个特殊的函数,它的名字同类名,并在前面加“~”字符。 (3) 析构函数没有参数,没有返回值,显然不能重载。 在下面两种情况下,析构函数会被自动调用。

z 当一个对象的作用域结束时,该对象的析构函数被自动调用。 z 当一个对象是使用new运算符被动态创建时,在使用delete运算符释放它时,delete将会自动调用析构函数。

【例8.5】 定义一个Person类,检测其构造函数和析构函数的运行。

//程序名为ch08_5.cpp:

#include <iostream.h> class Person {

public:

Person(char *input_name) //构造函数 · 222·

本书比较全面的介绍了C++编程语言

第8章 类与对象

{

name=new char[10];

strcpy(name,input_name); cout<<“Constructor called<<endl; }

~ Person( ) {

delete name;

cout<<“Destructor called<<endl; }

void show( ); private:

char *name; };

void Person::show( ) {

cout<<name<<endl; }

void main() {

Person student1("Zhang Ming"); student1.show(); cin.get(); }

·223·

运行结果:

程序说明:通过输出结果,可以看出构造函数和析构函数都是自动调用的。实际应用中,像“Constructor Called.”、“Destructor called.”这样的输出语句是没用的,有用的是析构函数中的delete语句,这与构造函数中的new语句相呼应。

Constructor Called. Zhang Ming

Destructor called.

8.4 内联和重载

8.4.1 内联成员函数

内联函数是函数体代码被插入到调用者代码处的函数, 引入内联函数的目的是为了解决程序中函数调用的效率问题。定义的方法非常简单,只要在一个函数的定义或声明前加上关键字inline就可把该函数定义为内联函数。例如:

inline void f(int i, char c) {

// ... }

·223·

本书比较全面的介绍了C++编程语言

·224· C++程序设计

内联函数在调用时不像一般函数那样要转去执行被调用函数的函数体,执行完成后再返回到主调函数中,继续执行其后语句;而是在编译时,在调用函数处就用内联函数体代码来替换,这样将会节省调用开销,提高运行速度。但要注意它是以目标代码的增加为代价来换取时间上的高效率的。在第5章中曾对内联函数与带参数的宏定义进行了简单比较,它们的代码效率是一样的,但是内联函数要优于宏定义,因为内联函数遵循函数的参数传递规则(即进行严格的类型匹配检查),这样会使C++的程序更安全可靠。

内联成员函数是那些定义在类体内的成员函数,这些函数的头部和函数体都放进了类的说明部分。内联成员函数在性质上与普通的内联函数完全相同,只不过它是类的一个组成部分—— 一个成员而已。例如:

class Myexample { public:

void f(int i, char c) {

// ... } };

该例中的成员函数f就是内联成员函数。

外联成员函数是指函数原型定义在类体内、函数定义声明在类体外的成员函数,其变成内联成员函数的方法很简单,只要在函数头前面加上关键字inline就可以了。例如将上面的例子改为:

class Myexample { public:

void f(int i, char c); };

inline void Myexample::f(int i, char c) {

// ... }

在实际的编程应用中,一个类往往有很多的成员,如果有很多的成员函数将其函数体都定义在类体内,很显然,这样的类是很不方便阅读的,因此,建议在类体外使用 inline 关键字定义成员函数来避免这种情况。

无论是普通内联函数,还是内联成员函数,它们往往都是比较小的函数,在定义时都要注意以下几点。

(1) 内联成员函数如定义在类体外,需用关键字inline声明。

(2) 内联成员函数中不能含有任何循环语句、switch语句和goto语句。 (3) 内联成员函数中不能声明数组。 (4) 内联成员函数不能是递归函数。 8.4.2 重载成员函数

C++中,函数重载是它的一个重要特性。成员函数作为一种函数也是可以进行重载的。前面介绍过构造函数可以重载,而析构函数不能重载,原因是构造函数可以有参数,析构

· 224·

本书比较全面的介绍了C++编程语言

第8章 类与对象 ·225·

函数不带任何参数,当然无法重载。可以说,可带有不同类型和不同数量参数的成员函数都可以进行重载。

【例

8.6】 分析下列程序的输出结果。

//程序名为ch08_6.cpp: class IntAddtion {

public:

IntAddtion (int x,int y) {

X=x; Y=y; }

IntAddtion (int x) {

X=x; Y=x*x; }

int Add(int x,int y); int Add(int x); int Add( ); int Xout( ) {

return X; }

int Yout( ) {

return Y; }

private: int X,Y; };

int IntAddtion::Add(int x,int y) {

X=x; Y=y;

Return X+Y; }

int IntAddtion::Add(int x) {

X=Y=x;

return X+Y; }

int IntAddtion::Add( ) {

return X+Y; }

#include<iostream.h> void main( ) {

·225·

本书比较全面的介绍了C++编程语言

·226· C++程序设计

运行结果:

a=10,20 b=4,16 30 12 10

IntAddtion a(10,20),b(4);

cout<<“a=”<<a.Xout( )<<“,”<<a.Yout( )<<endl; cout<<“b=”<<b.Xout( )<<“,”<<b.Yout( )<<endl; int i=a.Add( ); int j=a.Add(3,9); int k=b.Add(5);

cout<<i<<endl<<j<<endl<<k<<endl; }

程序说明:在本程序中,IntAddtion类里定义了两个重载的构造函数和3个重载的Add函数。这两组重载函数的区别是使用参数个数的不同。关于参数类型不同的成员函数重载这里就不再详述。

8.5 静 态 成 员

当用类来声明一个对象时,系统就为该对象分配一块内存空间来存放该对象中的所有成员。但在某些应用中,需要程序中属于某个类的成员被该类的所有对象所共享。一个解决的办法是将所要共享的成员说明为类外的全局变量,但这样将破坏类的完整性。在C++中,较好的办法是将所要共享的成员声明为类的静态成员。根据成员性质的不同,可分为静态数据成员和静态成员函数两类。 8.5.1 静态数据成员

C++中,同一个类定义多个对象时,每个对象都拥有各自的成员,而静态数据成员是类的所有对象中共享的成员,它不因对象的建立而产生,也不因对象的失效而删除,它是类定义的一部分,它属于整个类,即属于所有对象。例如:

class string {

public:

string(int sz); ~string( );

void copy(string &s); void printout(void); private:

char *buff; int size;

static char eos; · 226·

//字符串复制函数

//字符串输出函数

//字符串结束符eos

本书比较全面的介绍了C++编程语言

第8章 类与对象

}

string this,that,other;

·227·

想像一下:一个string类的字符串可以有自己的数据空间(buff),也可以有自己的尺寸(size),但在一个程序内,所有string类的对象应共享同样的结束符(eos),这样就需要将其声明为静态数据成员,方法是在类型前面加上关键字static。假如string类有3个对象this、that和other,那么它们如何共享一个静态数据成员如图8.1所示。

对象this

对象that

对象other

成员函数

成员函数

成员函数

eos

图8.1 共享一个静态数据成员示意图

使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象都能存取更新后相同的值。

由于静态数据成员不从属于任何一个具体对象,但任何一个对象在声明前都需要它提前拥有一个值,因此C++规定:必须对静态数据成员初始化,并且对它的初始化不能在构造函数中进行。

前面提到,由于静态数据成员不专属于一个具体的对象,而是属于整个类,因此,其初始化的方法一般采用如下形式:

例如对上例,有:

<类型> <类名> ::<静态数据成员> =<值>;

这样,所有的string类的对象将共同拥有一个成员eos,其初值为字符“#”。

下面是关于静态数据成员初始化的进一步说明。 (1) 初始化在类体外进行,其前面不要加static。

(2) 初始化时不加该成员的访问权限控制符private、public或protected。

(3) 即使静态数据成员是私有的,也可以在类外有文件作用域的地方直接初始化,一般应在类的定义之后马上初始化。

同样的理由,在引用公有的静态数据成员时应采用下面的形式:

改变一个静态数据成员的值一定要格外小心,最好的方法是采用将要在8.5.2节中介绍

·227·

<类名>::<静态数据成员>

char string::eos='#'; //分配并初始化静态数据成员

本书比较全面的介绍了C++编程语言

·228· C++程序设计

的静态成员函数来访问处理。 8.5.2 静态成员函数

静态成员函数的定义和其他成员函数一样。但它的特点与静态数据成员类似,也是不专属于任何一个对象,为整个类所共享。静态成员函数的定义方法是在一般成员函数的定义前加上static关键字。

例如,改变一下string类,增加一个静态成员函数。

class string {

public:

string(int sz); ~string( );

void copy(string &s); void printout(void); static void change_eos(void); private:

char *buff; int size;

static char eos; }

char string::eos='#';

void string::change_eos(void) {

eos='$';

}

//字符串复制函数 //字符串输出函数 //静态成员函数

//字符串结束符eos

//可直接访问静态数据成员,但注意:不能访问 //其他数据成员

这里,静态成员函数change_eos的目的就是访问静态数据成员,比如改变string类的结束符。

下面是关于静态成员函数的说明。

(1) 调用静态成员函数的格式一般采用如下形式:

例如:string:: change_eos();

(2) 静态成员函数只能访问静态数据成员、其他静态成员函数和类以外的函数与数据,不能访问类中的非静态数据成员,因为非静态数据成员只有对象存在时才有意义。

(3) 静态成员函数不得说明为虚函数。 【例8.7】 用静态成员实现班费管理程序。

//程序名为ch08_7.cpp: #include<iostream.h> #include<string.h> class Student {

public: · 228·

<类名>::<静态成员函数名>(<参数表>) ;

本书比较全面的介绍了C++编程语言

第8章 类与对象 ·229·

运行结果:

Student (char *input_name,int input_age); //构造函数 ~ Student(){delete name;} //析构函数

static void getData(int n); //静态成员函数,进款管理 static void spend(int n); //静态成员函数,花销管理 void display( ); private:

char *name; int age;

static int count; //定义静态数据成员,存放班费 }

int Student::count=0; //静态数据成员初始化 Student::Student (char *input_name,int input_age) {

name=new char[10];

strcpy(name,input_name); age= input_age; }

void Student::getData(int n) {

count=count+n; }

void Student::spend(int n) {

count=count-n; }

void Student::display( ) {

cout<<"current amount of money="count<<endl; } };

void main( ) {

Student stu1("Robert",21),stu2 ("mary",18); Student::getData(1000); stu1.display( ); stu2.display( );

Student::spend(300); stu1.display( ); stu2.display( ); }

current amount of money=1000 current amount of money=1000 current amount of money=700 current amount of money=700

·229·

本书比较全面的介绍了C++编程语言

·230· C++程序设计

程序说明:本程序定义了一个Student类,设计了构造函数、析构函数、两个静态成员函数、一个普通成员函数以及3个数据成员,其中一个为静态的数据成员。通过这个程序的示例,应掌握静态成员的基本使用技巧。

8.6 友 元

前面曾讲过类具有封装和信息隐藏的特性。只有类的成员函数才能访问类的私有成员,程序中的其他函数是无法访问私有成员的。非成员函数可以访问类中的公有成员,但是如果将数据成员都定义为公有的,则又破坏了类的信息隐藏的特性,从而导致C++程序设计上的不可靠性。如果将数据成员都定义为私有的,有时候类外的函数需要这些数据,则必须对某些公有成员函数多次调用才能实现,这样的话,由于参数传递、类型检查和安全性检查等都需要时间开销,从而影响程序的运行效率。

为了解决上述问题,C++提出一种新方案——使用友元。友元是一种定义在类外部的普通函数,但它需要在类体内声明为“朋友”。为了与该类的成员函数加以区别,在类内说明时需要在前面加上关键字friend。注意:友元不是成员函数,但是它可以访问类中的私有成员。友元的作用在于提高程序的运行效率,但是从某种程度上讲,它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员,因此,在程序设计时应该给予严格限制。

友元可以是一个函数,称之为友元函数;友元也可以是一个类,该类被称为友元类。下面分别介绍这两种友元。 8.6.1 友元函数

定义友元函数的方式是在类定义中用关键字friend说明该函数,其格式如下:

注:友元函数说明的位置可在类的任何部位,意义完全一样。友元函数定义则必须在类的

外部,一般与类的成员函数定义在一起。声明类的友元函数的目的就是为普通函数提供能够直接方便地访问该类的所有成员的权限。 【例8.8】 编程比较两个点离原点的距离。

//程序名为ch08_8.cpp: #include<iostream.h> class Point {

public:

Point(double xx,double yy) {

x=xx; y=yy; }

void Getxy( );

friend int compare(Point &a,Point &b); //友元函数 · 230·

friend <类型><友元函数名>(<参数表>);

本书比较全面的介绍了C++编程语言

第8章 类与对象

private:

double x,y; };

void Point::Getxy( ) {

cout<<“(”<<x<<“,”<<y<<“)”<<endl; }

int compare(Point &a,Point&b) //若a比b更近,则返回0,否则返回1 {

double da=a.x*a.x+a.y*a.y ; double db=b.x*b.x+b.y*b.y ; return (da-db<0?0∶1); }

void main( ) {

Point p1(3.0,4.0),p2(6.0,8.0); cout<<"p1 is "; p1.Getxy( ); cout<<"p2 is "; p2.Getxy( );

int d=compare(p1,p2); if (d= =0)

cout<<"p1 is closer to origin"<<endl;

else cout<<"p2 is closer to origin"<<endl; }

·231·

执行结果:

程序说明:本程序在定义Point类中说明了一个友元函数compare,由friend关键字来标识它不是成员函数,而是友元函数。它的定义方法与普通函数定义一样,不同于成员函数的定义,因为它不属于Point类。

compare函数体中的a.x、b.x、a.y、b.y都是类的私有成员,因为函数compare是类 Point的友元,因此可以直接被调用。

调用友元函数也是同普通函数一样,不要像成员函数那样调用。本例中,p1.Getxy( )和p2.Getxy( )是成员函数的调用方式,函数前面要用对象来表示。compare(p1,p2)是友元函数的调用,它应直接调用,前面不需要也不能加某对象。这里,它的参数是Point类的对象。 8.6.2 友元类

友元除了可以作为前面讲过的友元函数外,还可以是一个类,即将一个类可以作为另一个类的友元。当一个类作为另一个类的友元时,这就意味着这个类的所有成员函数都是另一个类的友元函数。

【例8.9】 友元类的简单应用。

·231·

p1 is(3.0,4.0) p2 is(6.0,8.0)

p1 is closer to origin

本书比较全面的介绍了C++编程语言

·232· C++程序设计

运行结果:

· 232·

//程序名为ch08_9.cpp:

#include<iostream.h> class X {

friend class Y; //声明Y为X的友元类 public:

void Set(int i) //给数据成员赋值,在类内定义,说明它是内联函数 {

x=i; }

void Display( ) {

cout<<"x="<<x<<","; cout<<"y="<<y<<endl; }

private: int x;

static int y; //静态数据成员 };

class Y {

public;

Y(int i,int j); //构造函数

void Display( ); //与类X中的同名函数不是一个函数

private:

X a; //a为类X的对象,称为子对象 }; int X::y=1; //静态数据成员初始化 Y::Y(int i,int j) {

a.x=i; X::y=j; }

void Y::Display( ) {

cout<<"x="<<a.x<<","; cout<<"y="<<X::y<<endl; }

void main() {

X b;

b.Set(5);

b.Display( ); Y c(6,9);

c.Display( ); b.Display( ); }

本文来源:https://www.bwwdw.com/article/kik1.html

Top