C++面向对象(三):类和对象

C++面向对象:类和对象

会有点长,不过读过就全学会喽!!!!!!
会有点长,不过读过就全学会喽!!!!!!
会有点长,不过读过就全学会喽!!!!!!

1.类与对象的基本概念

1.1 结构与类

1 . 结构的扩充
结构是 C 语言的一种自定义的数据类型, 在结构体中可以含有各种不同类型的数 据。C + + 语言对结构类型进行了扩充, 它不仅可以含有不同类型的数据, 而且还可以含有函数。
结构中的数据和函数都是结构的成员,分别称作数据成员和函数成员。在 C + + 中, 通常把 函 数 成 员 称 为 成 员 函 数。
为了访问这些成员函数,必须 定义该结构类型的变量,然后像访问结构中的数据成员一样进行访问。

#include < iostream .h > 
#include < math .h > 
struct complex { 
double real; // 复数的实部 
double imag; // 复数的虚部 
void init( double r, double i) // 给 real 和 imag 赋初值 
{ real = r; imag = i; } 
double realcomplex() // 求复数的实部值 
{ return real; } 
double imagcomplex() // 求复数的虚部值 
{ return imag; } 
double abscomplex() // 求复数的绝对值 
{double t ; 
t = real * real + imag * imag; 
return sqrt(t) ; }
} A; 
int main() 
{
A .init(1 .1 ,2 .2 ) ; 
cout < < ”real of complex A =< < A .realcomplex( ) < < endl; 
cout < < ”imag of complex A =< < A .imagcomplex( ) < < endl;
cout < < ”abs of complex A =< < A .abscomplex ( ) < < endl; 
return 0; 
}

在 C ++ 中,一个结构的成员通常分为两类:私有成员 ( private )和公有成员 ( public )。 私有成员(包括数据和函数) 只能被该结构中的其它成员访问,而公有成员( 包括数据和函数)既可被结构内其它成员访问,也可被结构外的其它部分访问。

struct complex{ 
private : 
	double real; 
	double imag; 
public: 
	void init( double r, double i) { real = r; imag = i; } 
	double realcomplex( ) { return real; } 
	double imagcomplex( ) { return imag; } 
	double abscomplex( ) { double t; t = real * real + imag * imag; return sqrt( t) ; } 
} ;

C + + 规定, 在缺省情况下,结构中的成员是公有的。
2 . 类的声明
C + + 提供了一种比结构类型更安全有效的数据类型———类。类是 C + + 的一个最 重要的特性。类与结构的扩充形式十分相似,其一般形式如下:

class 类名{ 
[ private: ] 
	私有数据成员和成员函数 
public: 
	公有数据成员和成员函数 
} ;

其中: class 是声明类的关键字, 类名是要声明的类的名字; 后面的花括号表示出类的声明 范围;最后的分号表示类声明结束。
类的声明内容包括数据和函数。类中的数据和函数都是类的成员, 分别叫做数据成 员和成员函数。
与结构的扩充形式一样,类的成员也分为私有成员和公有成员。私有成员用 private 说明, private 下面的每一行,不论是数据成员还是成员函数, 都是私有成员。私有成员只 能被该类的成员函数访问,这是 C + + 实现封装的一种方法, 即把特定的成员定义为私有的,就能严格地控制对它的访问。公有成员用 public 说明, public 下面的每一行都是公有 成员。公有成员可被程序中的其它函数访问, 它们是类的对外接口。
在类中, 缺省时成 员是私有的; 而在结构中,缺省时成员是公有的。


C + + 中为什么要用类代替结构呢 ?

原因是,在缺省的情况下, 类成员是私有的, 类提供了缺省的安全性。这一规定符合面向 对象思想中数据隐藏的准则。数据隐藏使得类中的成员比一般的局部变量得到更好的保 护
说明:

  1. 类的声明中的 private 和 public 两个关键字可以按任意顺序出现任意次。
  2. 除了 private 和 public 之外, 类中的成员还可以用另一个关键字 protected 来说 明。被 protected 说明的成员称为保护成员, 它不能被外部函数使用
  3. 数据成员可以是任何数据类型,但是不能用自动( auto)、寄存器 ( register) 或外部 ( extern)进行说明。
  4. 不能在类的声明中给数据成员赋初值

C + + 规定, 只有在类对象定义之后才能给数据成员赋初值。

1.2 成员函数的定义

成员函数的定义通常采用两种方式。
第一种方式是在类声明中只给出成员函数的原型, 而成员函数体在类的外部定义。 这种成员函数定义的一般形式是:

返回类型 类名∷函数名(参数表) { 
// 函数体 
}
class point{
private : 
	int x, y; 
public: void setpoint (int , int) ; // 设置坐标点的成员函数 setpoint( )的函数原型 
	int getx( ) ; // 取 x 坐标点的成员函数 getx( )的函数原型 
	int gety ( ) ; // 取 y 坐标点的成员函数 gety( )的函数原型 
} ; 
void point∷setpoint( int a, int b)// 定义成员函数 setpoint( ) 
{ x = a; y = b; } 
int point∷getx( ) // 定义成员函数 getx() 
{ return x; } 
int point∷gety() // 定义成员函数 gety( ) 
{ return y; }

从这个例子可以看出,虽然函数 setpoint( )、getx( )和 gety( ) 在类外部定义, 但它们属 于类 point 的成员函数, 它们可以直接使用类 point 中的数据成员 x 和 y。
说明:

  1. 在所定义的成员函数名之前应缀上类名, 在类名和函数名之间应加上分隔符 “∷”,例如上面例子中的“point∷”。
  2. 在定义成员函数时, 对函数所带的参数, 不但要说明它的类型, 还要指出其参 数名。
  3. 在定义成员函数时, 其返回类型一定要与函数原型中声明的返回类型匹配。

成员函数的第二种定义方式是:将成员函数定义在类的内部, 即定义为内置函数。
1️⃣隐式定义:所谓内置函数的隐式定义, 就是直接将函数定义在类内部

class point{ 
private : 
	int x, y; 
public: 
	void setpoint(int a, int b) 
	{ x = a; y = b; } 
	int getx( ) 
	{ return x; } 
	int gety ( ) 
	{ retrun y; } 
} ;

此时, 函数 setpoint( )、getx( )和 gety( )就是隐含的内置函数。内置函数的调用类似宏指 令的扩展,它直接在调用处扩展其代码, 而不进行一般函数的调用操作。
2️⃣显式定义:在定义内置函数时, 为了书写清晰,仍将它放在类定义体外。但为了 使它仍然起内置函数的作用, 在函数定义前冠以关键字“inline”, 以此显式地说明这是一 个内置函数。

class point{ 
private : 
	int x, y; 
public: 
	void setpoint(int , int) ; 
	int getx( ) ; int gety ( ) ; 
} ; 
inline void point∷setpoint(int a, int b) 
{ x = a; y = b; } 
inline int point∷getx( ) 
{ return x; } 
inline int point∷gety( ) 
{ return y; }

说明:简单的成员函数定义成内置函数可以提高执行的效率, 但如果函数体较长, 将 使程序量增加很多。因此,一般对非常简单的函数才声明为内置函数。

1.3 对象的定义及引用

1 . 类与对象的关系
通常我们把具有同样性质和功能的东西所构成的集合称为类。在 C + + 中, 可以把 相同内部存储结构和相同操作集的对象看成属于同一类。
2 . 对象的定义
可以用以下两种方法定义对象。
1️⃣在声明类的同时, 直接定义对象,即在声明类的右花括号“}”后, 直接写出属于该 类的对象名表。例如:

class point {
private : 
	int x, y; 
public: 
	void setpoint(int, int) ; 
	int getx( ) ; 
	int gety( ) ; 
} op1, op2;

在声明类 point 的同时, 直接定义了对象 op1 和 op2 ,这时定义的是一个全局对象。
2️⃣声明了类之后, 在使用时再定义对象, 定义的格式与一般变量的定义格式相同

point op1 ,op2;

此时定义了 op1 和 op2 为 point 类的两个对象。
说明:

  1. 声明了一个类便声明了一种类型, 它并不接收和存储具体的值, 只作为生成具体对象的一种“样板”,只有定义了对象后, 系统才为对象并且只为对象分配存储空间。
  2. 在声明类的同时定义的对象是一种全局对象, 在它的生存期内任何函数都可以 使用它。但有时使用它的函数只在极短的时间对它进行操作,而它却总是存在, 直到整个 程序运行结束,采用使用时再定义对象的方法可以消除这种弊端。

3 . 对象的引用
在此, 对象的引用是指对对象成员的引用。不论是数据成员, 还是成员函数, 只要是 公有的,就可以被外部函数直接引用, 引用格式是:

对象名 . 数据成员名 
或 
对象名 . 成员函数名( 实参表)

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

# include < iostream .h >
class point{ 
private: 
	int x, y; 
public: 
	void setpoint(int a, int b) { x = a ; y = b; } 
	int getx ( ) { return x; } 
	int gety( ) { return y; } 
} ; 
void main ( ) {
point op1, op2; 
int i, j; 
op1 .setpoint(1, 2 ) ; // 调用 op1 的 setpoint( ) , 初始化对象 op1 3 
op2 .setpoint(3, 4 ) ; // 调用 op2 的 setpoint( ) , 初始化对象 
op2 i = op1 .getx( ) ; / / 调用 op1 的 getx( ) ,取 op1 的 x 值 
j = op1 .gety( ) ; / / 调用 op1 的 gety( ) , 取 op1 的 y 值 
cout < < ”op1 i =< < i < < ”op1 j =< < j < < endl; 
i = op2 .getx( ) ; / / 调用 op2 的 getx( ) ,取 op2 的 x 值 
j = op2 .gety( ) ; / / 调用 op2 的 gety( ) , 取 op2 的 y 值 
cout < < ”op2 i =< < i < < ”op2 j =< < j < < endl; 
}

说明:

  1. 本例中 op1 .setpoint( 1, 2) 实际上是一种缩写形式, 它表达的意义是 op1 .point∷ setpoint( 1 .2) , 这两种表达式是等价的。
  2. 外部函数不能引用对象的私有成员。
  3. 在定义对象时, 若我们定义的是指向此对象的指针, 则访问此对象的成员时, 不 能用“ .”操作符, 而应使用“ - > ”操作符

4 . 对象赋值语句
如果有两个整型变量 x 和 y, 那么用 y = x,就可以把 x 的值赋给 y。同类型的对象之 间也可以进行赋值,当一个对象赋值给另一个对象时, 所有的数据成员都会逐位拷贝。
说明:

  1. 在使用对象赋值语句进行对象赋值时, 两个对象的类型必须相同,如对象的类型 不同,编译时将出错。
  2. 两个对象之间的赋值, 仅仅使这些对象中数据相同,而两个对象仍是分离的。例 如本例对象赋值后,再调用 o1 .set( ) 设置o1 的值,不会影响 o2 的值。
  3. 将一个对象的值赋给另一个对象时, 多数情况下都是成功的, 但当类中存在指针 时,可能会产生错误。
1.4 类的作用域

所谓类的作用域就是指在类的声明中的一对花括号所形成的作用域。一个类的所有 成员都在该类的作用域内。一个类的任何成员可以引用该类的其它成员。
一个类的成员函数可以不受限制地引用该类的数据成员,而在该类作用域之外, 对该 类的数据成员和成员函数的引用则要受到一定的限制, 有时甚至是不允许的。这体现了 类的封装功能。

# include < iostream .h > 
class abc{ 
public: 
	int i; 
	void set( int) ; 
	void disp( ) { cout < < ”i =< < i < < endl; } // 可以引用类中的数据成员 i 
} ; 
void abc∷set(int si) 
{ 
i = si; } // 可以引用类中的数据成员  
int fun( ) { return i; } // 非法, i 没有定义 
void main( ) {
abc ob; ob .set( 2) ; // 给数据成员 i 赋初值 2 
ob .disp( ) ; i = 1 ; // 非法, i 没有定义, 可改写成
 ob .i = 1 ob .disp( ) ; 
 }

2.构造函数与析构函数

当声明一个类对象时, 编译程序需要为对 象分配存储空间, 进行必要的初始化, 这部分工作随着类的不同而不同。在 C + + 中, 由 构造函数来完成这些工作。构造函数是属于某一个类的,它可以由用户提供, 也可以由系 统自动生成。与构造函数对应的是析构函数, 当撤消类对象时, 析构函数就回收存储空 间,并做一些善后工作。析构函数也属于某一个类, 它可以由用户提供, 也可以由系统自 动生成

2.1 构造函数

构造函数是一种特殊的成员函数,它主要用于为对象分配空间, 进行初始化。构造函 数具有一些特殊的性质:

  1. 构造函数的名字必须与类名相同。
  2. 构造函数可以有任意类型的参数, 但不能具有返回类型。
  3. 定义对象时, 编译系统会自动地调用构造函数。
class complex{ 
private : 
	double real; / / 表示复数的实部 
	double imag; / / 表示复数的虚部 
public: 
	complex ( double r, double i) / / 定义构造函数, 其名与类名相同 
	{ real = r; imag = i; } / / 在构造函数中, 对私有数据 real 和 imag 赋值 
	double abscomplex( ) 
	{double t; t = real * real + imag * imag; return sgrt( t) ; } 
} ;

构造函数不能像其它成员函数那样被显式地调用,它是在定义对象的同时调用的, 其 一般格式为:

类名 对象名(实参表) ;

这里的“类名”与构造函数名相同“, 实参表”是为构造函数提供的实际参数。
说明:

  1. 构造函数的名字必须与类名相同, 否则编译程序将把它当作一般的成员函数来 处理。
  2. 构造函数没有返回值, 在声明和定义构造函数时, 是不能说明它的类型的,甚至 说明为 void 类型也不行。
  3. 在实际应用中, 通常需要给每个类定义构造函数。如果没有给类定义构造函数, 则编译系统自动地生成一个缺省的构造函数。这个缺省的构造函数不带任何参数,它只能为对象开辟一个存储空间, 而不能给对象中的 数据成员赋初值,这时的初始值是随机数, 程序运行时可能会造成错误。
  4. 构造函数可以是不带参数的
  5. 构造函数也可采用构造初始化表对数据成员进行初始化, 这是某些程序员喜欢 使用的方法。
  6. 对没有定义构造函数的类, 其公有数据成员可以用初始值表进行初始化
class A{ 
	int i; 
	char j; 
	float f; 
public: 
	A(int I, char J, float F)i( I) , j(J) , f(F) { } 
} ;
2.2 缺省参数的构造函数

对于带参数的构造函数,在定义对象时必须给构造函数传递参数, 否则构造函数将不 被执行。但在实际使用中,有些构造函数的参数值通常是不变的, 只有在特殊情况下才需 要改变它的参数值,这时可以将其定义成带缺省参数的构造函数

complex ( double r = 0 .0 , double i = 0 .0 ) ; / / 含有缺省参数的构造函数
complex S1; / / 不传递参数, 全部用缺省值
2.3 析构函数

析构函数也是一种特殊的成员函数。它执行与构造函数相反的操作, 通常用于执行 一些清理任务,如释放分配给对象的内存空间等。析构函数有以下一些特点:

  1. 析构函数与构造函数名字相同, 但它前面必须加一个波浪号( ~ )。
  2. 析构函数没有参数, 也没有返回值, 而且不能重载, 因此在一个类中只能有一个析构函数。
  3. 当撤消对象时, 编译系统会自动地调用析构函数。

说明:每个类必须有一个析构函数。若没有显式地为一个类定义析构函数, 编译系统 会自动地生成一个缺省的析构函数。例如, 编译系统为类 complex 生成缺省的构造函数 如下所示: complex∷~ complex ( ) { }

class string _data{ 
	char * str; 
public: 
string - data ( char * s) 
{ 
str = new char[strlen(s) + 1] ; 
strcpy(str,s) ; }
~string - data( ) 
{ 
delete str; } 
void get - info( char * ) ; 
void sent - info( char * ) ; 
} ;

这是构造函数和析构函数常见的用法,即在构造函数中用运算符 new 为字符串分配 存储空间,最后在析构函数中用运算符 delete 释放已分配的存储空间

2.4 重载构造函数

与一般的成员函数一样, C + + 允许重载构造函数, 以适应不同的场合。

 A( ) ; / / 不带参数的构造函数 ?
 A(int) ; / / 只带一个整型参数的构造函数 
 A(int , char) ; / / 带两个参数的构造函数, 一个是整型,另一个是字符型 
 A( float, char) ; / / 带两个参数的构造函数, 一个是浮点型,另一个是字符型

说明:在重载没有参数和带缺省参数的构造函数时, 有可能产生二义性,例如:

class x{ 
public: x( ) ; / / 没有参数的构造函数 
x(int i = 0) ; / / 带缺省参数的构造函数 
} ; 
void main( ) {
x one( 10 ) ; / / 正确,调用 x(int i = 0 ) 
x two; / / 存在二义性 
/ /}

该例定义了两个重载构造函数 x, 其中一个没有参数, 另一个带有一个缺省参数。创 建对象 two 时, 由于没有给出参数,它既可以调用第一个构造函数, 也可以调用第二个构 造函数。这时,编译系统无法确定应该调用哪一个构造函数, 因此产生了二义性。在实际 应用时,一定要注意避免这种情况。

2.5 拷贝构造函数

拷贝构造函数是一种特殊的构造函数。它用于依据已存在的对象建立一个新对象。 典型的情况是,将参数代表的对象逐域拷贝到新创建的对象中。
用户可以根据自己的需要定义拷贝构造函数,系统也可以为类产生一个缺省的拷贝 构造函数。
1.自定义拷贝构造函数
自定义拷贝构造函数的一般形式如下:

classname ( const classname &ob) { / / 拷贝构造函数的函数体 }

其中,ob 是用来初始化另一个对象的对象的引用。

# include < iostream .h > 
class point{ 
int x, y; 
public: point(int a, int b) / / 构造函数 
{ x = a; y = b; } 
point( const point &p) / / 拷贝构造函数 
{x = 2 * p .x; y = 2 * p .y; }
void print( ) { cout < < x < < ” ”< < y < < endl; } 
} ; 
main ( ) 
{point p1 (30, 40 ) ; / / 定义类 point 的对象 p1 
point p2 ( p1) ; / / 显式调用拷贝构造函数,创建对象 p2 
p1 .print( ) ; 
p2 .print( ) ; 
return 0 ;
}

在定义对象 p2 时, 虽然从形式上看是将对象 p1 赋值给了对象 p2, 但实际上调用的 是拷贝构造函数, 在对象 p2 被创建 时, 将对象 p1 的值逐域 拷贝给对 象 p2, 运行结 果 同上。
2.缺省的拷贝构造函数
如果没有编写自定义的拷贝构造函数, C + + 会自动地将一个已存在的对象复制给 新对象,这种按成员逐一复制的过程是由缺省拷贝构造函数自动完成的。
说明:

  1. 与一般的构造函数一样, 拷贝构造函数没有返回值。
  2. 通常缺省的拷贝构造函数是能够胜任工作的, 但若类中有指针类型时, 按成员复 制的方法有时会产生错误。

3.对象数组与对象指针

3.1 对象数组

对象数组是指每一数组元素都是对象的数组, 也就是说, 若一个类有若干个对象, 我 们把这一系列的对象用一个数组来存放。
如果类中含有用户定义的构造函数, 而且构造函数带有参数, 则定义对象数组时, 可 通过初始值表进行赋值。

# include < iostream .h > 
class exam
{ 
int x; 
public: 
exam(int n) { x = n; } 
int get - x( ) { return x; } 
 } ;
  main ( ) 
  {exam ob[4 ] = {11 ,22, 33 ,44} ; / / 通过初始值表给对象数组赋值 
  int i; 
  for (i = 0; i < 4 ; i + + ) 
  cout < < ob[i] .get - x( ) < <′′; cout < < endl; 
  return 0 ; 
  }

若类中含有构造函数,那么定义对象数组时, 也可通过不带参数的构造函数或带有缺 省参数的构造函数给对象数组元素赋值

3.2 对象指针

在 C + + 语言中可以直接引用对象, 也可以通过指向该对象的指针引用对象。对 象指针是 C + + 的重要特性之一。
1 . 用指针引用单个对象成员
说明对象指针的语法和说明其它数据类型指针的语法相同。使用对象指针时, 首先 要把它指向一个已创建的对象,然后才能引用该对象的成员。
在一般情况下,用点运算符(·) 来引用对象成员, 当用指向对象的指针来引用对象成 员时,就要用“ - >”操作符。

main ( ) {
exe ob, * p; / / 声明类 exe 的对象 ob 和类 exe 的对象指针 p 
ob .set - a( 2) ; 
ob .show - a( ) ; 
p = &ob; / / 将 p 指针指向对象 ob 
p - > show - a ( ) ; 
return 0 ; 
}

在这个例子中, 声明了一个类 exe , ob 是类 exe 的一个对象, p 是类 exe 的对象指针, 对象 ob 的地址是用地址操作符( & )获得并赋给对象指针 p 的。
2 . 用对象指针引用对象数组

对象指针不仅能引用单个对象, 也能引用对象数组。

main ( ) {
exe ob[ 2] , * p; 
ob[0 ] .set - a (10) ; 
ob[1 ] .set - a (20) ; 
p = ob; 
p - > show - a ( ) ; 
p + + ; 
p - > show - a ( ) ; 
return 0 ; 
}

一般而言,当指针加 1 或减 1 时, 它总是指向其基本类型中相邻的一个元素, 对象指针也是如此。本例中指针对象 p 加 1 时,指向下一个数组元素。

3.3 this 指针

C + + 提供了一个特殊的对象指针———this 指针。它是成员函数所属对象的指针。 它指向类对象的地址。
成员函数访问类中数据成员的格式可以 写成:this - > 成员变量

# include < iostream .h > 
class exth{ 
int i; 
public: 
void load(int val) { this - > i = val; } / / 与“i = val;”相同 
int get( ) { return this - > i; } / / 与“return i;”相同 
} ; 
void main( ) {
exth obj ; 
obj .load(100 ) ; 
cout < < obj .get( ) ; 
}

当一个对象调用成员函数时, 该成员函数的 this 指针便指向这个对象。如果不同的 对象调用同一个成员函数, C + + 编译器将根据该成员函数的 this 指针指向的对象来确 定应该引用哪一个对象的数据成员。
那么 C + + 如何使 this 指针指向对象 ob 呢 ?
ob .init(′x′,12) ;
将上述调用 init( ) 函数的语句转换成如下形式:
init( &ob,′x′, 12 ) ;
换句话说, C + + 把类对象作为参数传递给函数(传地址) 。虽然程序中的成员函数 init ( ) 没有对应的形参,但程序编译时 C + + 将其转换成以下形式:
void init( abc * this, char ma, int mb) { this - > a = ma ; this - > b = mb; }
由于函数调用时, 第一个参数是按地址传递的, 因此当对象 ob 调用函数 init( ) 时, this 指 针便指向 ob,于是隐式指针 this 保证了成员函数 init( ) 的操作对象确实是对象 ob。

4.向函数传递对象

把对象的拷贝而不是对象本身传给函 数。因此函数中对对象的任何修改均不影响调用该函数的对象本身。

void sqr - it( tr ob ) 
{
ob .set - i(ob .get - i( ) * ob .get - i( ) ) ; 
cout < < ”copy of obj has i value of ”< < ob .get - i( ) ; 
cout < < ”\ n”; 
}

如同其它类型的变量一样, 也可以将对象的地址传递给函数。这时函数对对象的修 改将影响调用该函数的对象本身。

void sqr - it( tr * ob ) {
ob - > set - i(ob - > get - i( ) * ob - > get - i( ) ) ; 
cout < < ”Copy of obj has i value of ”< < ob - > get - i( ) ; 
cout < < ”\ n”; 
}

5.静 态 成 员

静态成员的特性是不管这个类创建了多少个对象, 而其静态 成员只有一个拷贝(副本) ,这个拷贝被所有属于这个类的对象共享。

静态成员在类中有两种情况, 即静态数据成员和静态成员函数。

5.1 静态数据成员

在一个类中,若将一个数据成员说明为 static, 这种成员称为静态数据成员。与一般 的数据成员不同,无论建立多少个类的对象, 都只有一个静态数据的拷贝。
说明:

  1. 静态数据成员属于类( 准确地说,是属于类中一个对象集合 ) , 而不像普通数据成 员那样属于某一对象,因此可以使用“类名∷”访问静态的数据成员。例如上面例子中的 Student∷count。
  2. 静态数据成员不能在类中进行初始化, 因为在类中不给它分配内存空间, 必须在 类外的其它地方为它提供定义。一般在 main( )开始之前, 类的声明之后的特殊地带为它 提供定义和初始化。缺省时,静态成员被初始化为零。
  3. 静态数据成员与静态变量一样, 是在编译时创建并初始化。它在该类的任何对 象被建立之前就存在,它可以在程序内部不依赖于任何对象被访问。
  4. C + + 支持静态数据成员的一个主要原因是可以不必使用全局变量。依赖于全 局变量的类几乎都是违反面向对象程序设计的封装原理的。静态数据成员的主要用途是 定义类的各个对象所公用的数据,如统计总数、平均数等。
5.2 静态成员函数

在类定义中,前面有 static 说明的成员函数称为静态成员函数。静态成员函数属于 整个类,是该类所有对象共享的成员函数, 而不属于类中的某个对象。
静态成员函数首先是一个成员函数,因此它不能像类以外的其它函数那样使用, 在使 用时要用“类名∷”作为它的限定词,或指出它作用在哪个对象上。其次静态成员函数是 一种特殊的成员函数,它不属于某一个特定的对象
一般而言, 静态成员函数访问的基本上是静态 数据成员或全局变量。

下面对静态成员函数的使用再作几点说明:

  1. 静态成员函数可以定义成内嵌的, 也可以在类外定义, 在类外定义时, 不要用 static 前缀。
  2. 编译系统将静态成员函数限定为内部连接, 也就是说,与现行文件相连接的其它 文件中的同名函数不会与该函数发生冲突,维护了该函数使用的安全性, 这是使用静态成 员函数的一个原因。
  3. 使用静态成员函数的另一个原因是, 可以用它在建立任何对象之前处理静态数 据成员,这是普通成员函数不能实现的功能。
  4. 在一般的成员函数中都隐含有一个 this 指针, 用来指向对象自身,而在静态成员 函数中是没有 this 指针的,因为它不与特定的对象相联系, 调用时使用如下格式较好:
    类名∷静态成员函数名( )
  5. 一般而言, 静态成员函数不访问类中的非静态成员。若确实需要,静态成员函数 只能通过对象名(或指向对象的指针) 访问该对象的非静态成员。
    static void display (small - cat & w) { cout < < ”The small - cat weights”< < w .weight < < ”pounds \ n”; }

6.友 元

在不放弃私有数据安全性的情况下, 使得类外部的函 数或类能够访问类中的私有成员,在 C + + 中就用友元作为实现这个要求的辅助手段。

6.1 友元函数

友元函数不是当前类的成员函数,而是独立于当前类的外部函数, 但它可以访问该类 的所有对象的成员,包括私有成员和公有成员。
在类定义中声明友元函数时, 需在其函数名前加上关键字 friend。此声明可以放在 公有部分,也可以放在私有部分。友元函数可以定义在类内部, 也可以定义在类的外部。
说明:

  1. 友元函数虽然可以访问类对象的私有成员, 但它毕竟不是成员函数。因此,在类 的外部定义友元函数时,不必像成员函数那样, 在函数名前加上“类名∷”。
  2. 友元函数一般带有一个该类的入口参数。因为友元函数不是类的成员, 所以它 不能直接引用对象成员的名字,也不能通过 this 指针引用对象的成员, 它必须通过作为入 口参数传递进来的对象名或对象指针来引用该对象的成员。
  3. 当一个函数需要访问多个类时, 友元函数非常有用,普通的成员函数只能访问其所属的类,但是多个类的友元函数能够访问相应的所有类的数据。
    friend void prdata( const girl plg , const boy plb) ; / / 声明函数 prdata( )
  4. 友元函数通过直接访问对象的私有成员,提高了程序运行的效率。
6.2 友元成员

除了一般的函数可以作为某个类的友元外, 一个类的成员函数也可以作为另一个类 的友元,这种成员函数不仅可以访问自己所在类对象中的私有成员和公有成员, 还可以访 问 friend 声明语句所在类对象中的私有成员和公有成员, 这样能使两个类相互合作、协调 工作,完成某一任务。

说明:

  1. 一个类的成员函数作为另一个类的友元函数时, 必须先定义这个类
    friend void boy∷disp( girl & ) ;
  2. 程序中第 3 行“class girl;”为向前引用
6.3 友元类

不仅函数可以作为一个类的友元, 一个类也可以作为另一个类的友元。

class Y{ / /} ; 
class X{ / /friend Y; / / 声明类 Y 为类 X 的友元类
//...
};

当一个类被说明为另一个类的友元时,它的所有的成员函数都成为另一个类的友元 函数,这就意味着作为友元的类中的所有成员函数都可以访问另一个类中的私有成员。
说明:
友元关系是单向的,不具有交换性。若类 X 是类 Y 的友元(即在类 Y 定义中声 明 X 为 friend 类) , 类 Y 是否是 X 的友元,要看在类中是否有相应的声明。友元关系也不 具有传递性,若类 X 是类 Y 的友元, 类 Y 是类 Z 的友元, 不一定类 X 是类 Z 的友元。

7.类对象作为成员

在类定义中定义的数据成员一般都是基本的数据类型。但是类中的成员也可以是对 象,叫做对象成员。使用对象成员着重要注意的问题是构造函数的定义方式, 即类内部对 象的初始化问题。
含有对象成员的类, 其构造函数和不含对象成员的构造函数有所不同

一般来说,类 X 的构造函数的定义形式为: 
X∷X(参数表 0) :成员名 1 (参数表 1) ,, 成员名 n( 参数表 n) { 
/ / 构造函数体 
}

冒号后面的部分是对象成员的初始化列表,各对象成员的初始化列表用逗号分隔, 参数表 i(i 为 1 到 n )给出了初始化对象成员所需要的数据,它们一般来自参数表 0。
当调用构造函数 X∷X( ) 时, 首先按各对象成员在类定义中的顺序依次调用它们的 构造函数,对这些对象初始化, 最后再执行 X∷X( )的函数体。析构函数的调用顺序与此 相反。

说明:

  1. 声明一个含有对象成员的类, 首先要创建各成员对象。本例在声明类 girl 中, 定 义了对象成员 name :
    string name;
  2. girl 类对象在调用构造函数进行初始化的同时, 也要对对象成员进行初始化, 因 为它也是属于此类的成员。因此在写类 girl 的构造函数时, 也缀上了对对象成员的初 始化:
    girl( char * st , int ag) : name (st) 于是在调用 girl 的构造函数进行初始化时,也给对象成员 name 赋上了初值。
    这里需要注意的是:在定义类 girl 的构造函数时, 必须缀上其对象成员的名字 name, 而不能缀上类名,若写成:
    girl( char * st , int ag) :string(st)
    是不允许的,因为在类 girl 中是类 string 的对象 name 作为成员, 而不是类 string 作为其成 员。
  • 6
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值