再上一篇:8.4 指针与函数
上一篇:8.5 new与delete
主页
下一篇:8.7 简单链表及其应用
再下一篇:8.8 类型定义
文章列表

8.6 引用和其它类型的指针

《VC++程序设计基础》,讲述C++语言的语法和标准库,以及Visual C++ 函数库和MFC类库的使用,并附相关代码示例。

本节介绍引用,void类型的指针和const类型的指针。

8.6.1 引用类型变量的说明及使用

C++语言中提供了一个与指针密切相关的特性——引用,引用是一种特殊的数据类型。定义引用类型的变量,其本质是给一个已定义的变量起一个别名,系统不为引用类型的变量分配内存空间,只是使得引用类型的变量与其相关联的变量使用同一个内存空间。引用主要用于函数之间传递数据。

定义引用类型变量的一般格式为:

<类型> &<引用变量名> = <变量名>;

其中变量名必须是一个已定义过的变量。如:

int count ;

int &refcount=count;

这里定义了一个引用类型的变量refcount,它是变量count的别名。并称count为refcount引用的变量或关联的变量。

例8.27 引用类型变量的使用。

#include

void main(void)

{

int i, &refi=i;

i=100; refi += 100;

cout <<"refi=" << refi << '\n';

refi *=2;

cout << "i=" << i <<'\n';

}

执行程序后的输出为:

refi=200

i=400

从这程序的输出可以看出,变量refi和i使用相同的内存空间。对refi的访问就是对i的访问。

对引用类型的变量,说明以下几点:

1、定义引用变量时,必须将它初始化。为它初始化的变量类型必须与引用类型变量的类型相同。如:

float x;

int &px=x;

由于px和x的类型不同,因此是错误的。

2、引用类型变量的初始化值不能是一个常数。如:

int &ref1 = 5; // 是错误的。

因5不是一个变量,所以是错误的。但是,如下的说明是正确的:

const int &ref2 = 5;

这种情况告诉系统,ref2是常数5的引用。有关const定义的常量特性的说明,在本节的后面介绍。

3、能说明为引用类型变量的引用,不能说明为引用类型的数组。但引用数组中的某一个元素是可以的。如:

int i, &refi1 =i;

int &ref5=refi1; //是可以的

int &ref6=ref5; //是可以的

int &refi2 = &refi1; // 不允许的

int && re =&refi1; // 不允许的

int &ref=i; //是可以的

int a[10];

int &refa = a; // 不允许的

int &refaa[10] =a; // 不允许的

int &*refa3= a; // 不允许的

int &refa2 = a[2]; //是可以的

4、可以用动态分配的内存空间来初始化一个引用变量。如:

float &reff = * new float ;

reff= 200;

cout << reff ;

delete &reff;

用这种方法定义的引用类型变量reff是可以的。new运算符的运算结果是一个指针,所以在其前面必须加一个*,这是因为对引用变量进行初始化的值是一个变量,而不是一个指针。另外,运算符delete 的操作数必须是一个指针,而不是变量名,所以用delete释放动态内存空间时,要在引用变量前要加一个取地地址运算符“&”。

5、综合本书前面已介绍的内容,可以看到,C++中“&”有三种含义:第一种的用法是作为按位与的运算符“&”,它是一个双目运算符;第二种是作为取地址运算符“&”,它是一个单目运算符;第三种是作为引用运算符,用于定义引用变量。

8.6.2 引用与函数

在C++语言中引入引用类型的主要目的是为了在函数的参数传递时提供方便。引用类型主要用作函数的参数或用作函数的返回值类型。

8.6.2.1 引用类型变量作为函数的参数

当把函数的参数说明为引用类型时,把引用类型的实参称为引用传递。引用传递与地址传递类同,可作为函数的输入参数,也可作为函数的输出参数。使用引用类型的参数比使用指针类型的参数更能增加程序的可读性和编程的方便性。

例8.28 输入二个整数,交换后输出。

#include

void swap1( int *p1, int *p2)

{

int t;

t = *p1; *p1 =*p2; *p2 = t;

}

void swap2( int &p1, int &p2)

{

int t;

t = p1; p1 =p2; p2 = t;

}

void main(void )

{

int x,y;

int a,b;

cout << "Input values of x and y:";

cin >> x >> y;

swap1(&x, &y) ;

cout << "Input values of a and b:";

cin >> a >> b;

swap2(a, b) ;

cout << "x=" << x <<" y= " << y <<'\n';

cout << "a=" << a <<" a= " << b <<'\n';

}

程序执行时,若输入x和y的值分别为300和400,a和b的值分别为100和200,程序输出为:

x=400 y= 300

a=200 b=100

函数swap1( )与swap2( )的功能完全相同,都是实现二个数的交换。对于引用类型的形参,其实参直接使用变量名;而对于指针类型的实参,必须是变量的地址。这二者是不一样的。另外,在swap1() 中要用到运算符*,来取指针所指向的数据;而在swap2( )中,直接使用引用类型的变量名。这二者也是不一样的。比较这二个函数,可看出,使用引用类型的参数比使用指针类型的参数更加方便和直观。

8.6.2.2 函数的返回值为引用类型

当把函数的返回值定义为引用类型时,根据引用类型的定义,它所返回的值一定是某一个变量的别名。因此,它相当于返回了一个变量,所以可对其返回值进行赋值操作。这一点类同于函数的返回值为指针类型。

例8.29 函数返回值为引用类型的特性。

#include

int &f1(void)

{

static int count;

return ++count;

}

int index;

int &f2(void)

{

return index;

}

void main(void )

{

f1() = 100; //A

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

cout << f1() << " "; //B

cout << '\n';

int n;

f2()=100; //C

n=f2(); //D

cout << "n=" << n << '\n';

f2() = 200;

cout << "index = " << index << '\n';

}

执行程序时的输出为:

101 102 103 104 105

n=100

index=200

函数f1( )返回静态类型变量count的引用,而函数f2( ) 返回全局变量index的引用。A行中的“f1( ) = 100”,因赋值运算符的优先级低于函数调用,为此先执行对函数f1( )的调用。函数的返回值为count的引用,等同于count的一个别名,然后执行赋值运算,实际完成将100赋给count。B行中调用函数f1( ),先使count的值加1,然后返回count的引用,即取出count的值输出。同理,C行等同于把100赋给变量index,D行等同于将index的值100赋给变量n。

应该注意二点,第一点是,在主函数中,不能直接使用局部变量count的值,但通过函数f1( )可以对其赋值或使用其值。第二点是,对于自动存储类型或寄存器类型的局部变量,函数是不能返回这种变量的引用;因这种类型的变量,在函数执行结束时,变量就不存在了,所以对它的引用是无效的。

8.6.3 void型指针

在C++语言中,当指定函数的类型为void时,表示其没有返回值,或者说返回的值无效。当把指针变量定义为void类型时,表示可以指向任意类型的数据。void型指针也称为无类型指针,可以把任意类型的指针值赋给它。但若将void型的指针值赋给其它类型的指针变量时,必须进行强制类型转换。例如:

int *ip,*ip1;

float *fp, *fp1;

void *p1, *p2;

ip= new int ;

fp = new float ;

p1 = ip; //正确

p2=p1; //正确

ip1 = p1; //不正确,应写成:ip1=(int *) p1;

fp1 = (float * ) p2; //正确

void类型指针的主要用途是编写通用的函数。我们通过一个例子来说明void类型指针的应用。

例8.30 用void类型的指针编写一个通用的排序程序,可以分别对整型数,实型数,字符串等进行排序。

#include

int ComInt(void *a,void *b) //比较二个整数

{

return * (int *) a - * (int * )b;

}

int ComFloat(void *a,void *b) //比较二个实数

{

return *(float *) a - *(float * )b > 0?1:-1;

}

void Sort(void *v,int n,int size,int (*Com)(void *,void *)) //排序

{

int i,j,k;

char *p,*q,t;

for (i=0;i< n-1;i++){

p=(char *)v + i*size; //求出第i个元素的指针值

for(j=i+1;j

q=(char *)v+ j*size; //求出第j个元素的指针值

if( Com(p,q)>0 )

for(k=0;k

t=p[k];p[k]=q[k];

q[k]=t;

}

}

}

}

void main(void)

{

int vi[]={23,44,32,66,15,25};

float vf[]={ 15.4,34.789,55.4,5.6,18.3,99.8,67.34};

cout<<"排序前的整数为:\n";

for(int i=0;i

cout << vi[i] <<'\t';

cout <<'\n';

Sort(vi,sizeof(vi)/sizeof(int),sizeof(int),ComInt);

cout<<"排序后的整数为:\n";

for(i=0;i

cout << vi[i] <<'\t';

cout <<'\n';

cout<<"排序前的实数为:\n";

for( i=0;i

cout << vf[i] <<'\t';

cout <<'\n';

Sort(vf,sizeof(vf)/sizeof(float),sizeof(float),ComFloat);

cout<<"排序后的实数为:\n";

for( i=0;i

cout << vf[i] <<'\t';

cout <<'\n';

}

执行程序后的输出为:

排序前的整数为:

23 44 32 66 15 25

排序后的整数为:

15 23 25 32 44 66

排序前的实数为:

15.4 34.789 55.4 5.6 18.3 99.8 67.34

排序后的实数为:

5.6 15.4 18.3 34.789 55.4 67.34 99.8

函数ComInt()比较二个整型数,其返值是要比较的二个数之差,即a所指向的整数大于b所指向的整数时,返回值大于0;当二个数相等时,返回值为0;否则返回值小于0。这里把二个参数定义为void类型的指针,是考虑到程序的通用性。因为在有的计算机中,一个整数用二个字节来表示,而有的计算机中,一个整数用四个字节来表示。本函数可比较任意长度的整型数。

函数ComFloat()比较二个实数,因二实数相减的结果转换成整数时有误差,所以不能直接返回二个实数之差。如25.6 - 25.4的值为0.2,转换成整型时,其值为0。这时不能表示25.6大于25.4。所以,只能使用条件表达式。

函数Sort( )实现数据的排序。第一个参数指向要排序的数组,第二个参数为该数组中元素个素,第三个参数指明数组中每一个元素占用的字节数,而第四个参数是用来比较数组中二个元素大小的函数指针。因该函数是一个通用的排序函数,计算数组中第i个元素的起始地址必须使用通用公式来计算:

p=(char *)v + i*size;

其中v是数组的起始地址,size为每一个元素占用的字节数,则p为数组v中第i个元素的起始地址。A行,要将p和q所指向的数组元素值进行交换,由于Sort( )是一个通用的排序函数,并不知道数组元素的类型,只能认为一个元素由若干个字节组成,并逐个字节进行交换。

8.6.4 const 类型变量

当用const限制说明标识符时,表示所说明的数据类型为常量类型。可分为const型常量和const型指针。

8.6.4.1 定义const型常量

可用const限制定义标识符量,如:

const int MaxLine =1000;

const float Pi=3.1415926;

用const定义的标识符常量时,一定要对其初始化。在说明时进行初始化是对这种常量置值的唯一方法,不能用赋值运算符对这种常量进行赋值。如:

MaxLine =35; //这是错误的

从使用的效果上来说,用const定义的const型常量与用编译预处理指令define定义的符号常量是相同的。二者的区别在于:首先,用define指令定义的符号常量是在编译之前,由编译预处理程序来处理的;而定义的const型常量是由编译程序进行处理的。因此,在调试程序的过程中,可用调试工具来查看const型常量,但不能查看用define定义的符号常量。其次,作用域不一样,用const定义的常量的作用域,与一般变量的作用域相同,也分为局部和全局量。用define指令定义的符号常量,其作用域是从其定义开始,到整个文件结束之前均有效。

8.6.4.2 const型指针

可有三种不同的方法来说明const型指针,其作用和含义都是不同的。我们分别举例说明之。第一种形式是将const放在指针变量的类型之前。如:

const int *Pint;

flaot x,y;

const float *Pf= &x;

这种形式定义的指针变量,表示指针变量所指向的数据是一个常量。因此,不能改变指针变量所指向的数据值,但可以改变指针变量的值。如:

*Pf = 25; //不允许

x= 200; //可以

Pf= &y; // 可以

用这种形式定义的指针变量,在定义时可以赋初值,也可以不赋初值。第二种形式是将const放在指针变量的“*”之后。如:

int n, i;

int *const pn=&n;

这种形式定义的指针变量,表示指针变量的值是一个常量。因此,不能改变这种指针变量的值,但可以改变指针变量所指向的数据值。如:

*pn= 25; //允许

pn= &i; // 不可以

用这种形式定义的指针变量,在定义时必须赋初值。第三种形式是把一个const放在指针变量的类型之前,再把另一个const放在指针变量的“*”之后。如:

int j, k;

const int *const pp=&j;

这种形式定义的指针变量,表示指针变量的值是一个常量,指针变量所指向的数据也是一个常量。因此,不能改变指针变量的值,也不能改变指针变量所指向的数据值。如:

*pp= 25; //不允许

pp= &k; // 不可以

用这种形式定义的指针变量,在定义时必须赋初值。

因引用类型的变量类同于指针类型的变量,所以这三种定义形式完全适用于引用类型的变量。

定义const类型指针的主要目的是提高程序的安全性。前面已经讲到,指针的正确使用是编写程序者的责任,因指针的应用非常灵活,编译程序无法检查指针类型变量是否完全正确使用。说明为const型指针,当使用不当时,编译程序就可以作相应的语法检查了。

const类型的指针主要用作函数的参数,以限制在函数体内不能修改指针变量的值,或不能修改指针所指向的数据值。在后面章节的例子中可以看到这方面的应用。