第 6章 指针和引用
6.1 指针
6.1.1 指针的概念一台计算机中必定配置有大量的存储单元,每一个存储单元都用一个唯一的“地址”所标识。指针变量(简称指针)就是用来存放该“地址号码”的变量。
由于不同的数据类型其所占的存储单元个数相异,因此指针所存放的是数据的 首地址 。
2000
2002
25
36
6.1.2 指针的说明
<storage> type *pointer;
其中,pointer 为所说明的指针变量名;星号( *)在这里是一个指针变量说明符,它指出这里所说明的是一个指针变量;
type 为指针类型,也就是该指针变量将存放其地址之数据的类型。例如:
char *pc;
int i,*pi = &i;
这里的符号,&”叫做“取地址”运算符,它是一个一元运算符,
其运算结果为其操作数(必须是一个变量)的地址。
6.1.3 对指针的访问对指针的访问与对普通变量的访问不同。除了可以像普通变量那样对指针进行读、写访问以及进行算术运算(统统属于对指针 值 的访问)外,还存在一种对指针 内容 的访问,实际上就是访问指针所指地址中数据的值。例如:
int i,*ip,*iq;
ip = &i; // 为指针赋一地址
iq = ip; // 指针间的赋值
*ip = 5; // 对指针的内容进行写访问,等效于 i = 5;
cout << *ip; // 对指针的内容进行读访问
6.1.4 指针的运算
6.1.4.1 指针的赋值运算
int *ip,*iq,i;
float *fp;
void *vp;
ip = &i; // 将一个整型变量的地址赋给整型指针
iq = ip; // 将一个整型指针的值赋给整型指针
fp = (float*)ip; // 将一个整型指针的值赋给实型指针
vp = iq; // 将一个整型指针的值赋给无值型指针
iq = 0x80; // 将一个常量赋给指针
6.1.4.2 指针的关系运算
ip > iq // ip 的值大于 iq 时为真在实用中,若某个指针的值为 0,则称该指针为 空指针 。例:
if(ip == 0) … // 若 ip 为空指针则,..。
C++ 语言提供了一个预定义的宏:
#define NULL 0
所以,上述语句可以改为:
if(ip == NULL),..
应当强调说明的是:向 未初始化 的指针所指的内容进行写操作是一种危险的操作。所谓未初始化的指针是指尚无明确值的指针。
6.1.4.3 指针的算术运算在实用中,仅使用指针的加减运算。应当说明的是,指针的算术运算是以指针所属数据类型所占内存的大小为单位进行的。即,设指针 p 的类型为 type,则有:
p? n = p? n? sizeof(type)
例如,设 ip 为一 int 型指针,其值为 2000,则执行语句:
ip += 2;
后,ip 的值将变为 2004,而不是 2002。
6.1.5 多级指针由于指针是一个变量,它自身也需要占据一定的内存单元,所以可以说明一个指针来存放指针变量的地址。这样的指针的数据类型自然应当是某一种指针类型。例:
int i = 5,*ip = &i;
int **ipp = &ip;
这里,ipp 就是一个类型为 int*(整型指针类型)型的指针,
称为指向指针的指针。对于这样的指针,当用一个星号来访问其内容时,访问的将是一个地址;用两个星号来访问它时,访问的才是一个数据。例:
cout << ipp; // 输出(假设)为 0x2000
cout << *ipp; // 输出 (假设) 为 0x2100
cout << ip; // 同上
cout << **ipp; // 输出为 5
6.2 指针与数组
6.2.1 用指针访问数组元素在 C++ 语言中,数组名实际是就是一个指针,它标识出数组元素所占内存的首地址。然而,数组名相当一个常指针,即这种指针的值是一个常量。因此,不得对数组名进行赋值和算术运算。
利用数组名是一个指针这一事实,可以将数组名赋给一个指针,
然后通过对指针的算术运算来逐一地访问数组的各个元素,以提高程序的执行效率和改善程序的灵活性。这实际上是指针最普遍的用法。
6.2.1.1 指向数组元素的指针由于数组元素是变量(下标变量),所以将指针指向数组元素与将指针指向普通变量的用法没有什么区别。例:
int iArr[10],*ip;
ip = &iArr[3];
*ip = 5; // 等效于 iArr[3] = 5
6.2.1.2 通过指针访问数组元素将指针指向数组的首地址,可以很方便地访问所有的数组元素。
当对数组进行顺序访问时,其效率大大高于使用下标变量。例:
int iArr[10],*ip = iArr;
for(int i = 10; i < 20; i ++) {
*ip = i * i;
ip ++;
}
#include <iostream.h>
void main()
{
int ia[10],*ip = ia,i;
for(i = 1; i <= 10; i ++)
*ip ++ = i * i;
ip = ia;
for(i = 0; i < 10; i ++)
cout << *ip ++ << '\t';
cout << endl;
}
说明:
1,程序中的 *ip ++ = i * i; 等效于:
*ip = i * i;
ip ++;
若欲将 ip 所指的内容加 1,
应使用 *(ip) ++;
或 ++ (*ip)
2,当第一个循环结束后,指针 ip 已经指向了数组 ia
中并不存在的第 11 个元素。因此,第二个循环开始前对指针重新赋值是十分必要的。
6.2.2 指针与字符串由于字符串是一维数组,因此上述指针与数组的关系完全可以用于字符串。例:
void StrCopy(char Targ[],const char Src[])
{
char *p1 = Targ,*p2 = Src;
while((*p1 ++ = *p2 ++) != 0);
}
注意函数中的 while 循环,它等效于:
do {
*p1= *p2;
if(*p1 == 0)
break;
p1 ++;
p2 ++;
} while(1);
6.2.3 指针数组和数组指针指针是变量,因此可以说明一个数组来存放多个类型相同的指针。这样的数组就叫做指针数组。说明指针数组的一般形式为:
<storage> type *pt_arr[c1][c2]…;
例:
int arr[3][5];
int *pa[3] = {arr[0],arr[1],arr[2]};
#include <iostream.h>
void main()
{
char *Wds[] = {"","Monday","Tuesday","Wednesday",
"Thursday","Friday","Saturday","Sunday"};
int num;
while(1) {
cin << num;
if(num < 1 || num > 7)
break;
cout << num << "-->" << Wds[num] << endl;
}
}
C++ 语言允许说明这样的指针:它存放的不是一个普通变量的首地址,而是一个数组的首地址。这样的指针叫做 数组指针。说明数组指针的一般形式为:
<storage> type ( *pt_arr) [c1][c2]…;
例:
int (*ap)[10];
这里,ap 是 一个 指向可以存放 10 个元素的整型数组之指针。
访问该指针的内容时,得到的不是一个数据,而是某个数组的首地址。
#include <iostream.h>
void main()
{
int arr[3][2] = {{1,2},{3,4},{5,6}},(*ap)[2] = arr,*p;
for(int i = 0; i < 3; i ++,ap ++) {
for(int j = 0,p = *ap; j < 2; j ++,p ++)
cout << "arr[" << i << "][" << j << "] = "
<< *p << '\t';
cout << endl;
}
}
6.3 指针与函数
6.3.1 指针作为函数的参数函数参数的数据类型可以是指针类型。与整个数组作为函数的参数一样,指针参数实参与形参的结合方式也叫做地址调用。
实际上,在实用中极少使用数组参数,当需要将整个数组传递组函数时,使用指针参数。例如,在 5.1.4.2 中所给出的例子函数
void Squares(int ia[],int n);
在实用中常写成以下的形式:
void Squares(int *ia,int n);
利用地址调用可以改变实参值变一特点,就能编写出一些具有特定功能的函数。
void Swap(int *a,int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int x = 3,y = 5;
Swap(&x,&y); // 调用后,x 的值变为 5,y 的值变为 3
6.3.2 返回指针的函数指针不仅可以用作函数的参数,函数的返回值也可以是一个指针。利用指针,函数就可以返回一个像数组那样存放多个数据的内存块的首地址。从而使得程序能够通过函数所返回的指针来访问其所指内存中的多个数据。
char* StrCopy(char* Targ,const char* Src)
{
char* temp = Targ;
while(*Src != 0)
*Targ ++ = *Src ++;
*Targ = 0;
return temp;
}
这里将函数写成返回目标串的形式,将使得该函数可以用在连续复制的场合:
StrCopy(s3,StrCopy(s2,s1));
double* Quadratic(const double* factor)
{
static double result[2];
double delta;
delta = factor[1] * factor[1] - 4 * factor[0] * factor[2];
if(delta < 0)
return 0;
delta = sqrt(delta);
result[0] = (-factor[1] + delta) / (2 * factor[0]);
result[1] = (-factor[1] - delta) / (2 * factor[0]);
return result;
}
函数中将作为返回数据的数组 result
说明成静态的是极为重要的!否则,
虽然可以返回该数组的首地址,但该地址所标识之内存中的数据可以已经面目全非了。
当函数返回的指针指向的是函数内部所说明的局部数据时,该数据必须说明成静态的 。
6.3.3 C++ 语言中的 main( ) 函数使用 DOS 命令时常常可以带有一些参数,比如:
dir dir *.* dir myfile.txt
如何使利用 C++ 语言所编写的程序也可以带有参数呢?答案是使用带参数的 main() 函数。
C++ 语言预定义的 main( ) 函数的原型为:
int main(int argc,char* argv[],char* env[]);

int main(int argc,char** argv,char** env);
其中:整型参数 argc 用来记录实参的个数;字符型数组参数
argv 用来存放实参的内容;字符型数组参数 env 用来存放环境变量的值(仅用于 DOS)。
#include <iostream.h>
#include <conio.h>
int main(int argc,char* argv[],char* env[])
{
if(argc == 1) {
cout << "USAGE,test some strings<CR>" << endl;
getch();
return -1;
}
int i;
cout << "Parameters:" << endl;
for(i = 0; i < argc; i ++)
cout << "Argv[" << i << "]," << argv[i] << endl;
cout << "Environment variables:" << endl;
for(i = 0; env[i] != 0; i ++)
cout << "Env[" << i << "]," << env[i] << endl;
return 1;
}
设运行该程序时的输入为:
C:\>test command-line argument<CR>
则程序的输出为:
Parameters:
Argv[0],C:\MYDIR\TEST.EXE
Argv[1],command-line
Argv[2],argument
Environment variables:
Env[0],// 视不同的设置而异
…...
6.3.4 指向函数的指针
6.3.4.1 说明函数指针说明函数指针的一般形式为:
<storage> type (*pointer)(<arg_list>);
例:
int (*funcp)(int n);
注意,以下说明的是一个返回值为整型指针的函数,而不是一个函数指针:
int *funcp(int n);
6.3.4.2 函数指针的运算函数指针仅存在对指针值的赋值运算,且赋值时必须保证数据类型严格匹配。例:
int (*fp)(int,int);
int f1(int,int);
char f2(int,int);
int f3(int,char);
fp = f1; // 正确
fp = f2; // 错误
fp = f3; // 错误
6.3.4.3 使用函数指针调用函数一但将一个函数的首地址赋给了一个函数指针,就可以利用该指针来调用相应的函数。利用函数指针调用函数除使用的是指针名而不是函数名外,其它方面与利用函数名调用函数没有任何区别。例:
void (*fup)(int*,int*);
fup = Swap;
fup(&x,&y);
#include <iostream.h>
#include <stdlib.h>
int Add(int,int);
int Sub(int,int);
int Mult(int,int);
int Div(int,int);
int main(int argc,char* argv[])
{
int (*funp)(int,int);
switch(argv[2][0]) {
case '+':
funp = Add;
break;
case '-':
funp = Sub;
break;
case '*':
funp = Mult;
break;
case '/':
funp = Div;
break;
default:
cout << "Operator you enter is illegal.\n";
return -1;
}
cout << funp(atoi(argv[1]),atoi(argv[3])) << endl;
return 0;
}
int Add(int a,int b)
{
return a + b;
}
int Sub(int a,int b)
{
return a - b;
}
//...
习题:
15,16