第 9章 指 针
指针是C语言中的重要概念,也是C语言的重要特色。
使用指针,可以使程序更加简洁、紧凑、高效。
9.1 指针和指针变量的概念
9.2 指针变量的定义与应用
9.3 数组的指针和指向数组的指针变量
9.4 字符串的指针和指向字符串的指针变量
9.5 返回指针值的函数
9.6 指针数组与主函数 main()的形参
9.7 函数的指针和指向函数的指针变量
[Return]
9.1 指针和指针变量的概念
1.内存地址 ──内存中存储单元的编号
( 1)计算机硬件系统的内存储器中,拥有大量的存
储单元(容量为1字节)。
为了方便管理,必须为每一个存储单元编号,这个编
号就是存储单元的“地址”。每个存储单元都有一个惟
一的地址。
( 2)在地址所标识的存储单元中存放数据。
注意:内存单元的地址与内存单元中的数据是两个完
全不同的概念。
2.变量地址 ──系统分配给变量的内存单元的起始地
址
假设有这样一个程序,
main()
{ int num;
scanf("%d",&num);
printf("num=%d\n",num);
}
C编译程序编译到该变量定义语句时, 将变量 num 登录
到, 符号表, 中 。 符号表的关键属性有两个:一是, 标识
符名 ( id),, 二是该标识符在内存空间中的, 地址
( addr), 。
为描述方便, 假设系统分配给变量 num的 2字节存储单元
为 3000 和 3001,则起始地址 3000就是变量 num在内存中的
地址 。
3.变量值的存取 ──通过变量在内存中的地址进行
系统执行, scanf(”%d“,&num);”和, printf(”num=%d\n“,
num);”时, 存取变量 num值的方式可以有两种,
(1)直接访问 ──直接利用变量的地址进行存取
1)上例中 scanf(“%d”,&num)的执行过程是这样的,
用变量名 num作为索引值, 检索符号表, 找到变量 num
的起始地址 3000;然后将键盘输入的值 ( 假设为3 ) 送到
内存单元 3000和 3001中 。 此时, 变量 num在内存中的地址
和值, 如图 9-1所示 。
2) printf("num=%d\n",num)的执行过程,与 scanf()很相
似,
首先找到变量 num的起始地址 3000,然后从 3000和 3001
中取出其值, 最后将它输出 。
( 2) 间接访问 ──通过另一变量访问该变量的值
C语言规定:在程序中可以定义一种特殊的变量 ( 称
为指针变量 ), 用来存放其它变量的地址 。
例如, 假设定义了这样一个指针变量 num_pointer,它被
分配到 4000, 4001 单元, 其值可通过赋值语句
,num_pointer= & num ;, 得到 。 此时, 指 针 变 量
num_pointer的值就是变量 num在内存中的起始地址 3000,如
图 9-1所示 。
通过指针变量 num_pointer存取变量 num值的过程如下,
首先找到指针变量 num_pointer的地址 ( 4000), 取出其
值 3000( 正好是变量 num 的起始地址 ) ; 然后从 3000,3001
中取出变量 num的值 ( 3) 。
( 3) 两种访问方式的比较
两种访问方式之间的关系, 可以用某人甲 ( 系统 ) 要找
某人乙 ( 变量 ) 来类比 。
一种情况是, 甲知道乙在何处, 直接去找就是 ( 即直接
访问 ) 。
另一种情况是, 甲不知道乙在哪, 但丙 ( 指针变量 ) 知
道, 此时甲可以这么做:先找丙, 从丙处获得乙的去向, 然
后再找乙 ( 即间接访问 ) 。
4.指针与指针变量
( 1) 指针 ──即地址
一个变量的地址称为该变量的指针 。 通过变量的指针能够找到该变量 。
( 2) 指针变量 ──专门用于存储其它变量地址的变量
指针变量 num_pointer的值就是变量 num的地址 。 指针与指针变量的区
别, 就是变量值与变量的区别 。
( 3) 为表示指针变量和它指向的变量之间的关系, 用指针运算符, *”
表示 。
例如, 指针变量 num_pointer与它所指向的变量 num的关系, 表示为,
*num_pointer,即 *num_pointer等价于变量 num。
因此, 下面两个语句的作用相同,
num=3; /*将 3直接赋给变量 num*/
num_pointer=# /*使 num_pointer指向 num */
*num_pointer=3; /*将 3赋给指针变量 num_pointer所指向的变量 */
[Return]
9.2 指针变量的定义与应用
9.2.1 指针变量的定义与相关运算
[案例 9.1] 指针变量的定义与相关运算示例。
/*案例代码文件名,AL9_1.C*/
main()
{ int num_int=12,*p_int; /*定义一个指向 int型数据的指针变量 p_int */
float num_f=3.14,*p_f; /*定义一个指向 float型数据的指针变量 p_f */
char num_ch=’p’,*p_ch; /*定义一个指向 char型数据的指针变量 p_ch */
p_int=&num_int; /*取变量 num_int的地址,赋值给 p_int */
p_f=&num_f; /*取变量 num_f的地址,赋值给 p_f */
p_ch=&num_ch; /*取变量 num_ch的地址,赋值给 p_ch */
printf(“num_int=%d,*p_int=%d\n”,num_int,*p_int);
printf(“num_f=%4.2f,*p_f=%4.2f\n”,num_f,*p_f);
printf(“num_ch=%c,*p_ch=%c\n”,num_ch,*p_ch);
} [程序演示 ]
程序运行结果,
num_int=12,*p_int=12
num_f=3.14,*p_f=3.14
num_ch=p,*p_ch=p
程序说明,
( 1) 头三行的变量定义语句 ──指针变量的定义
与一般变量的定义相比, 除变量名前多了一个星号, *”
( 指针变量的定义标识符 ) 外, 其余一样,
数据类型 *指针变量 [,*指针变量 2……];
注意,此时的指针变量 p_int,p_f,p_ch,并未指向某
个具体的变量 ( 称指针是悬空的 ) 。 使用悬空指针很容易破
坏系统, 导致系统瘫痪 。
(2)中间三行的赋值语句 ──取地址运算 (& )
取地址运算的格式,&变量
例如, &num_int,&num_f,&num_ch的结果, 分别为变量 num_int、
num_f,num_ch的地址 。
注意,指针变量只能存放指针 ( 地址 ), 且只能是相同类型变量的
地址 。
例如, 指针变量 p_int,p_f,p_ch,只能分别接收 int型, float型,
char型变量的地址, 否则出错 。
( 3) 后三行的输出语句 ──指针运算 ( *)
使用直接访问和间接访问两种方式, 分别输出变量 num_int,num_f、
num_ch的值 。
注意,这三行出现在指针变量前的星号, *” 是指针运算符, 访问
指针变量所指向的变量的值, 而非指针运算符 。
[案例 9.2] 使用指针变量求解:输入 2个整数, 按升序 ( 从小到大排序 )
输出 。
/*案例代码文件名,AL9_2.C*/
/*程序功能:使用指针变量求解 2个整数的升序输出 */
main()
{ int num1,num2;
int *num1_p=&num1,*num2_p=&num2,*pointer;
printf(“Input the first number:,); scanf(“%d”,num1_p);
printf(“Input the second number:,); scanf(“%d”,num2_p);
printf(“num1=%d,num2=%d\n”,num1,num2);
if( *num1_p > *num2_p ) /*如果 num1>num2,则交换指针 */
pointer= num1_p,num1_p= num2_p,num2_p=pointer;
printf(“min=%d,max=%d\n”,*num1_p,*num2_p);
}
[程序演示 ]
程序运行情况,
Input the first number:9←┘
Input the second number:6←┘
num1=9,num2=6
min=6,max=9
程序说明,
( 1) 第 5行的 if语句
如果 *num1_p>*num2_p ( 即 num1>num2), 则交换
指针, 使 num1_p指向变量 num2( 较小值 ), num2_p指向
变量 num1( 较大值 ) 。
( 2) printf(“min=%d,max=%d\n”,*num1_p,
*num2_p); 语句:通过指针变量, 间接访问变量
的值 。
本案例的 处理思路 是:交换指针变量 num1_p 和
num2_p的值, 而不是变量 num1和 num2的值 ( 变量 num1和
num2并未交换, 仍保持原值 ), 最后通过指针变量输出
处理结果 。
9.2.2 指针变量作函数参数
1.指针变量,既可以作为函数的形参,也可以作函数
的实参。
2.指针变量作实参时,与普通变量一样,也是“值传
递”,即将指针变量的值(一个地址)传递给被调用函数
的形参(必须是一个指针变量)。
注意,被调用函数不能改变实参指针变量的值,但
可以改变实参指针变量所指向的变量的值。
[案例 9.3] 使用函数调用方式改写 [案例 9.2],要求实参为指针变量。
/*案例代码文件名,AL9_3.C*/
/******************************************************/
/*exchange()功能:交换 2个形参指针变量所指向的变量的值 */
/*形参,2个,均为指向整型数据的指针变量 */
/*返回值:无 */
/******************************************************/
void exchange(int *pointer1,int *pointer2)
{ int temp;
temp=*pointer1,*pointer1=*pointer2,*pointer2=temp;
}
/*主函数 main()*/
main()
{ int num1,num2;
/*定义并初始化指针变量 num1_p和 num2_p */
int *num1_p=&num1,*num2_p=&num2;
printf(“Input the first number:,); scanf(“%d”,num1_p);
printf(“Input the second number:,);
scanf(“%d”,num2_p);
printf(“num1=%d,num2=%d\n”,num1,num2);
if( *num1_p > *num2_p ) /* 即 num1>num2)*/
exchange(num1_p,num2_p); /*指针变量作实参 */
/*输出排序后的 num1和 num2的值 */
printf(“min=%d,max=%d\n”,num1,num2);
} [程序演示 ]
程序运行情况,
Input the first number:9←┘
Input the second number:6←┘
num1=9,num2=6
min=6,max=9
调用函数 exchange()之前, 之时, 结束时和结束后的
情况, 如图 9-5所示 。
形参指针变量 pointer1( 指向变量 num1) 和 pointer2
( 指向变量 num2), 在函数调用开始时才分配存储空间,
函数调用结束后立即被释放 。
虽然被调用函数不能改变实参指针变量的值, 但可以
改变它们所指向的变量的值 。
总结,为了利用被调用函数改变的变量值, 应该使用
指针 ( 或指针变量 ) 作函数实参 。 其机制为:在执行被
调用函数时, 使形参指针变量所指向的变量的值发生变
化;函数调用结束后, 通过不变的实参指针 ( 或实参指
针变量 ) 将变化的值保留下来 。
[案例 9.4] 输入 3个整数,按降序(从大到小的顺序)输出。要求使
用变量的指针作函数调用的实参来实现。
/*案例代码文件名,AL9_4.C*/
/******************************************************/
/*exchange()功能:交换 2个形参指针变量所指向的变量的值 */
/*形参,2个,均为指向整型数据的指针变量 */
/*返回值:无 */
/******************************************************/
void exchange(int *pointer1,int *pointer2)
{ int temp;
temp=*pointer1,*pointer1=*pointer2,*pointer2=temp;
}
/*主函数 main()*/
main()
{ int num1,num2,num3;
/*从键盘上输入 3个整数 */
printf(“Input the first number:,); scanf(“%d”,&num1);
printf(“Input the second number:,); scanf(“%d”,&num2);
printf(“Input the third number:,); scanf(“%d”,&num3);
printf(“num1=%d,num2=%d,num3=%d\n”,num1,num2,num3);
/*排序 */
if( num1 < num2 ) /*num1<num2*/
exchange( &num1,&num2 );
if( num1 < num3 ) exchange( &num1,&num3 );
if( num2 < num3 ) exchange( &num2,&num3 );
/*输出排序结果 */
printf(“排序结果, %d,%d,%d\n”,num1,num2,num3);
}
[程序演示 ]
程序运行情况,
Input the first number:9←┘
Input the second number:6←┘
Input the third number:12←┘
num1=9,num2=6,num3=12
排序结果, 12,9,6
[Return]
9.3 数组的指针和指向数组的指针变量
9.3.1 概述
1.概念
数组的指针 ──数组在内存中的起始地址,数组元素
的指针 ──数组元素在内存中的起始地址。
2.指向数组的指针变量的定义
指向数组的指针变量的定义,与指向普通变量的指针
变量的定义方法一样。
例如,int array[10],*pointer=array(或 &array[0]);
或者,
int array[10],*pointer;
pointer= array;
注意,数组名代表数组在内存中的起始地址(与第 1
个元素的地址相同),所以可以用数组名给指针变量赋值。
3.数组元素的引用
数组元素的引用, 既可用下标法, 也可用指针法 。 使用下标法,
直观;而使用指针法, 能使目标程序占用内存少, 运行速度快 。
9.3.2 通过指针引用数组元素
如果有, int array[10],*pointer=array;”, 则,
( 1) pointer+i和 array+i都是数组元素 array[i]的地址, 如图 9-6所
示 。
( 2) *(pointer+i)和 *(array+i)就是数组元素 array[i]。
( 3) 指向数组的指针变量, 也可将其看作是数组名, 因而可按
下标法来使用 。 例如, pointer[i]等价于 *(pointer+i)。
注意, pointer+1指向数组的下一个元素, 而不是简单地使指针变
量 pointer的值 +1。 其实际变化为 pointer+1*size(size为一个元素占用的
字节数 ) 。
例如, 假设指针变量 pointer的当前值为 3000,则 pointer+1为
3000+1*2=3002,而不是 3001。
[案例 9.5] 使用指向数组的指针变量来引用数组元素 。
/*案例代码文件名,AL9_5.C*/
/*程序功能:使用指向数组的指针变量来引用数组元素 */
main()
{ int array[10],*pointer=array,i;
printf(“Input 10 numbers:,);
for(i=0; i<10; i++)
scanf(“%d”,pointer+i); /*使用指针变量来输入数组元素的值 */
printf(“array[10]:,);
for(i=0; i<10; i++)
printf(“%d,,*(pointer+i)); /*使用指向数组的指针变量输出数组 */
printf(“\n”);
} [程序演示 ]
程序运行情况,
Input 10 numbers,0 1 2 3 4 5 6 7 8 9←┘
array[10],0 1 2 3 4 5 6 7 8 9
程序说明,
程序中第 3行和第 6行的 2个 for语句, 等价于下面的程序段,
for(i=0; i<10; i++,pointer++)
scanf(“%d”,pointer);
printf(“array[10]:,);
pointer=array; /*使 pointer重新指向数组的第一个元素 */
for(i=0; i<10; i++,pointer++)
printf(“%d”,*pointer);
思考题,
( 1) 如果去掉, pointer=array;”行, 程序运行结果会如何?
请上机验证 。
( 2) 在本案例中, 也可以不使用 i来作循环控制变量,
程序怎么修改? 提示:指针可以参与关系运算 。
说明,
( 1) 指针变量的值是可以改变的, 所以必须注意其当前
值, 否则容易出错 。
( 2) 指向数组的指针变量, 可以指向数组以后的内存单
元, 虽然没有实际意义 。
( 3) 对指向数组的指针变量 ( px和 py) 进行算术运算和
关系运算的含义
1) 可以进行的算术运算, 只有以下几种,
px± n,px++/++px,px--/--px,px-py
·px± n:将指针从当前位置向前 ( +n) 或回退 ( -n) n
个数据单位, 而不是 n个字节 。 显然, px++/++px和 px--/--px
是 px± n的特例 ( n=1) 。
·px-py:两指针之间的数据个数, 而不是指针的地址之
差 。
2) 关系运算
表示两个指针所指地址之间, 位置的前后关系:前者为小, 后
者为大 。
例如, 如果指针 px所指地址在指针 py所指地址之前, 则 px〈 py的
值为 1。
9.3.3 再论数组作函数参数
数组名作形参时, 接收实参数组的起始地址;作实参时, 将数组
的起始地址传递给形参数组 。
引入指向数组的指针变量后, 数组及指向数组的指针变量作函
数参数时, 可有4种等价形式 ( 本质上是一种, 即指针数据作函数
参数 ),
( 1) 形参, 实参都用数组名
( 2) 形参, 实参都用指针变量
( 3) 形参用指针变量, 实参用数组名
( 4) 形参用数组名, 实参用指针变量
9.3.4 2维数组的指针及其指针变量
1,2维数组的指针
假设有如下数组定义语句,int array[3][4];
( 1)从 2维数组角度看,数组名 array代表数组的起始地
址,是一个以行为单位进行控制的行指针,
·array+i:行指针值,指向 2维数组的第 i行。
·*(array+i):(列)指针值,指向第 i行第0列(控制由行
转为列,但仍为指针)。
·*(*(array+i)):数组元素 array[i][0]的值。
用 array作指针访问数组元素 array[i][j]的格式,
*(*(array+i)+ j)
注意,行指针是一个2级指针,如图 9-7所示。
( 2)从 1维数组角度看,数组名 array和第 1维下标的每
一个值,共同构成一组新的 1维数组名 array[0],array[1]、
array[2],它们均由 4个元素组成。
C语言规定,数组名代表数组的地址, 所以 array[i]
是第 i行 1维数组的地址, 它指向该行的第 0列元素, 是一
个以数组元素为单位进行控制的列指针,
·array[i]+j,( 列 ) 指针值, 指向数组元素 array[i][j]。
·*(array[i]+j):数组元素 array[i][j]的值 。
如果有, int array[3][4],*p=array[0];,, 则 p+1指向
下一个元素, 如图 9-8所示 。
用 p作指针访问数组元素 array[i][j]的格式,
*(p+(i *每行列数 +j) )
2.行指针变量 ──指向由 n个元素组成的一维数组的指
针变量
(1)定义格式
数据类型 (*指针变量 )[n];
注意,, *指针变量, 外的括号不能缺, 否则成了指针数组 ——
数组的每个元素都是一个指针 ──指针数组 ( 本章第 6节介绍 ) 。
(2)赋值
行指针变量 = 2维数组名 | 行指针变量 ;
[案例 9.6] 使用行指针和列指针两种方式输出 2维数组的任一元素 。
( 1) 使用行指针
/*案例代码文件名,AL9_6_1.C*/
/*程序功能:使用行指针输出 2维数组的任一元素 */
main()
{ int array[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int (*pointer)[4],row,col;
pointer=array;
printf(“Input row =,); scanf(“%d”,&row);
printf(“Input col =,); scanf(“%d”,&col);
printf(“array[%1d][%1d] = %d\n”,row,col,*(*(pointer+row)+col));
} [程序演示 ]
程序运行情况,
Input row = 1←┘
Input col = 2←┘
array[1][2] = 7
思考题,本题也可以直接使用数组名 array作指针, 应如何修改?
( 2) 使用列指针
/*案例代码文件名,AL9_6_2.C*/
/*程序功能:使用列指针输出 2维数组的任一元素 */
main()
{ int array[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int *pointer,row,col; /*定义一个 (列 )指针变量 pointer*/
pointer=array[0]; /*给 (列 )指针变量 pointer赋值 */
printf(“Input row =,); scanf(“%d”,&row);
printf(“Input col =,); scanf(“%d”,&col);
printf(“array[%1d][%1d] = %d\n”,row,col,*(pointer+(row*4+col)));
} [程序演示 ]
3,2维数组指针作函数参数
2维数组的指针作函数实参时, 有列指针和行指针两种形式 。 相应
的, 用来接受实参数组指针的形参, 必须使用相应形式的指针变量,
如下所示,
实参,列指针 行指针
↓ ↓
形参,( 列 ) 指针变量 行指针变量
9.3.5 动态数组的实现
在程序运行过程中, 数组的大小是不能改变的 。 这种数组称为静
态数组 。 静态数组的缺点是:对于事先无法准确估计数据量的情况,
无法做到既满足处理需要, 又不浪费内存空间 。
所谓 动态数组是指, 在程序运行过程中, 根据实际需
要指定数组的大小 。
在 C语言中, 可利用内存的申请和释放库函数, 以及指
向数组的指针变量可当数组名使用的特点, 来实现动态数
组 。
动态数组的本质是, 一个指向数组的指针变量 。
[案例 9.7] 动态数组的实现 。
/*案例代码文件名,AL9_7.C*/
/*程序功能:实现动态数组 */
#include,alloc.h”
#include,stdlib.h”
main()
{ int *array=NULL,num,i;
printf(“Input the number of element:,); scanf(“%d”,&num);
/*申请动态数组使用的内存块 */
array=(int *)malloc( sizeof(int) * num );
if ( array==NULL ) /*内存申请失败:提示, 退出 */
{ printf(“out of memory,press any key to quit……,);
exit(0); /*exit():终止程序运行, 返回操作系统 */
}
/*提示输入 num个数据 */
printf(“Input %d elements:,,num);
for (i=0; i<num; i++) scanf(“%d”,&array[i]);
/*输出刚输入的 num个数据 */
printf(“%d elements are:,,num);
for (i=0; i<num; i++) printf(“%d,”,array[i]);
printf(“\b,); /*删除最后一个数据后的分隔符,,, */
free(array); /*释放由 malloc()函数申请的内存块 */
} [程序演示 ]
程序运行情况,
Input the number of element,3←┘
Input 3 elements,1 2 3←┘
3 elements are,1,2,3
程序说明,
( 1) array=(int *)malloc( sizeof(int) * num );语句 ──malloc()函数和
sizeof运算符
1) 库函数 malloc()
·用法,void *malloc(unsigned size)
·功能:在内存的动态存储区分配1个长度为 size的连续空间 。
·返回值:申请成功, 则返回新分配内存块的起始地址;否则, 返回
NULL。
·函数原型,alloc.h,stdlib.h。
malloc()函数的返回值是一个无类型指针, 其特点是可以指向任何类
型的数据 。 但在 实际使用 malloc()函数时, 必须将其返回值强制转换成
被赋值指针变量的数据类型, 以免出错 。
2) 运算符 sizeof
·格式,sizeof(变量名/类型名 )
·功能:求变量/类型占用的内存字节数 ( 正整数 ) 。 例如, 在
IBM-PC机上, sizeof(int)=2。
思考题,在该语句中, 使用 sizeof(int)求出 1个 int型数
据占用的内存字节数, 而不是使用常量, 2”,为什么?
( 2 ) scanf(“%d”,&array[i]); 语句和 printf(“%d,”,
array[i]);语句
将指向数组的指针变量当作数组名使用, 所以就必须
按引用数组元素的语法规则来使用 。
( 3) printf(“\b,);语句
, \b” 在该语句中的作用是, 使光标定位到最后一个
数据后的分隔符,,, 上, 然后再输出一个空格, 以达到
删除之目的 。
( 4) free(array);语句 ──库函数 free()
·用法,void free(void *ptr)
·功能:释放由 ptr指向的内存块 ( ptr是调用 malloc() 函
数的返回值 ) 。
·返回值:无 。
·函数原型,stdlib.h,alloc.h。
原则上, 使用 malloc()函数申请的内存块, 操作结束后,
应及时使用 free()函数予以释放 。 尤其是循环使用 malloc()函
数时, 如果不及时释放不再使用的内存块, 很可能很快就
耗尽系统的内存资源, 从而导致程序无法继续运行 。
[Return]
9.4 字符串的指针和指向字符串的指针变量
字符串在内存中的起始地址称为字符串的指针,可以
定义一个字符指针变量指向一个字符串。
9.4.1 字符串的表示与引用
在C语言中,既可以用字符数组表示字符串,也可用
字符指针变量来表示;引用时,既可以逐个字符引用,也
可以整体引用。
1.逐个引用
[案例 9.8] 使用字符指针变量表示和引用字符串 。
/*案例代码文件名,AL9_8.C*/
main()
{ char *string=”I love Beijing.”;
for(; *string!=’\0’; string++) printf(“%c”,*string);
printf(“\n”);
} [程序演示 ]
程序运行结果,
I love Beijing,
程序说明,char *string="I love Beijing.";语句
定义并初始化字符指针变量 string:用串常量, I love
Beijing.”的地址 ( 由系统自动开辟, 存储串常量的内存块的
首地址 ) 给 string赋初值 。
该语句也可分成如下所示的两条语句,
char *string;
string="I love Beijing.";
注意,字符指针变量 string中, 仅存储串常量的地址,
而串常量的内容 ( 即字符串本身 ), 是存储在由系统自动
开辟的内存块中, 并在串尾添加一个结束标志 ’ \0’。
2.整体引用
[案例 9.9] 采取整体引用的办法, 改写 [案例 9.8]。
/*案例代码文件名,AL9_9.C*/
/*程序功能:使用字符指针变量表示和引用字符串 */
main()
{ char *string=”I love Beijing.”;
printf(“%s\n”,string);
} [程序演示 ]
程序说明, printf("%s\n",string);语句
通过指向字符串的指针变量 string,整体引用它所指向的
字符串的原理:系统首先输出 string指向的第一个字符, 然
后使 string自动加1, 使之指向下一个字符;重复上述过程,
直至遇到字符串结束标志 。
注意,其它类型的数组, 是不能用数组名来一次性输出它
的全部元素的, 只能逐个元素输出 。
例如,
int array[10]={…… };
,....,
printf("%d\n",array); /*这种用法是非法的 */
,....,
3.字符指针变量与字符数组之比较
虽然用字符指针变量和字符数组都能实现字符串的存储和
处理, 但二者是有区别的, 不能混为一谈 。
( 1) 存储内容不同 。
字符指针变量中存储的是字符串的首地址, 而字符数组中
存储的是字符串本身 ( 数组的每个元素存放一个字符 ) 。
( 2) 赋值方式不同 。
对字符指针变量, 可采用下面的赋值语句赋值,
char *pointer;
pointer="This is a example.";
而字符数组, 虽然可以在定义时初始化, 但不能用赋
值语句整体赋值 。 下面的用法是非法的,
char char_array[20];
char_array="This is a example."; /*非法用法 */
( 3) 指针变量的值是可以改变的, 字符指针变量也不
例外;而数组名代表数组的起始地址, 是一个常量, 而常
量是不能被改变的 。
9.4.2 字符串指针作函数参数
[案例 9.10] 用函数调用方式,实现字符串的复制。
/*案例代码文件名,AL9_10.C*/
/**********************************************************/
/*string_copy()函数:复制一个字符串 */
/*形参:字符指针 str_from接收源串,字符指针 str_to存储目标串 */
/*返回值:无 */
/**********************************************************/
void string_copy(char *str_from,char *str_to)
{ int i=0;
for(; (*(str_to+i)=*(str_from+i))!=’\0’; i++) ; /*循环体为空语句 */
}
main()
{char array_str1[20]=”I am a teacher.”;
char array_str2[20];
string_copy(array_str1,array_str2); /*数组名作实参 */
printf(“array_str2=%s\n”,array_str2);
}
[程序演示 ]
程序运行结果,
I am a teacher,
程序说明, for(; (*(str_to+i)=*(str_from+i))!=’\0’; i++) ;
语句的执行过程为:首先将源串中的当前字符, 复制
到目标串中;然后判断该字符 ( 即赋值表达式的值 ) 是否
是结束标志 。 如果不是, 则相对位置变量 i的值增 1,以便
复制下一个字符;如果是结束标志, 则结束循环 。 其特点
是:先复制, 后判断, 循环结束前, 结束标志已经复制 。
在 C语言中, 用赋值运算符, 而不是赋值语句来实现
赋值操作, 能给某些处理带来很大的灵活性, 该语句 ( 实
现字符串的复制 ) 的用法就是最好的例证 。
[Return]
9.5 返回指针值的函数
一个函数可以返回一个 int型,float型,char型的数据,
也可以返回一个指针类型的数据。 返回指针值的函数(简称
指针函数)的定义格式如下,
函数类型 *函数名 ([形参表 ])
[案例 9.11] 某数理化三项竞赛训练组有 3个人,找出其中至少有一项
成绩不合格者。要求使用指针函数实现。
/*案例代码文件名,AL9_11.C*/
/*************************************************************/
/*seek()函数:判断是否有不合格成绩 */
/*形参:指向由 3个 int型元素组成的 1维数组的行指针变量 */
/*返回值,(1)有不合格成绩,则返回指向本行首列的一个 (列 )指针; */
/* (2)没有有不合格成绩,返回值为指向下一行的一个 (列 )指针 */
/*************************************************************/
int *seek( int (*pnt_row)[3] )
{ int i=0,*pnt_col; /*定义一个 (列 )指针变量 pnt_col */
pnt_col=*(pnt_row+1); /*使 pnt_col指向下一行之首 (作标志用 )*/
for(; i<3; i++)
if(*(*pnt_row+i)<60) /*某项成绩不合格 */
{ pnt_col=*pnt_row; /*使 pnt_col指向本行之首 */
break; /*退出循环 */
}
return(pnt_col);
}
/*主函数 main( ) */
main()
{ int grade[3][3]={{55,65,75},{65,75,85},{75,80,90}};
int i,j,*pointer; /*定义一个 (列 )指针变量 pointer */
for(i=0; i<3; i++) /*控制每个学生 */
{ pointer=seek(grade+i); /*用行指针作实参, 调用 seek()函数 */
if(pointer==*(grade+i)) /*该学生至少有一项成绩不合格 */
{ /*输出该学生的序号和各项成绩 */
printf(“No.%d grade list:,,i+1);
for(j=0; j<3; j++) printf(“%d,,*(pointer+j));
printf(“\n”);
}
}
} [程序演示 ]
程序运行结果,
No.1 grade list,55 65 75
程序说明,
( 1) 主函数中的 pointer=seek(grade+i);语句
调用 seek()函数时, 将实参 grade+i( 行指针 )的值, 复制到形参
pnt_row( 行指针变量 )中, 使形参 pnt_row指向 grade数组的第 i行 。
( 2) 在指针函数 seek()中,
1) pnt_col=*(pnt_row+1);语句
*(pnt_row+1)将行指针转换为列指针, 指向 grade数组的第 i+1行
第0列, 并赋值给 ( 列 ) 指针变量 pnt_col。
2) if(*(*pnt_row+i)<60)行
pnt_row是一个行指针, 指向数组 grade的第 i行; *pnt_row使指针
由行转换为列, 指向数组 grade的第 i行0列; *pnt_row+j的值还是一
个指针, 指向数组的第 i行第 j列; *(*pnt_row+ j)是一个数据 ( 数组元
素 grade[i][j]的值 ) 。
[Return]
9.6 指针数组与主函数 main()的形参
9.6.1 指针数组
1.概念
数组的每个元素都是一个指针数据。指针数组比较适
合用于指向多个字符串,使字符串处理更加方便、灵活。
2.定义格式
数据类型 *数组名 [元素个数 ]
注意,与行指针变量定义格式,<数据类型 >(*行指针
变量 )[<元素个数 >]”的差别。
[案例 9.12] 有若干计算机图书,请按字母顺序,从小
到大输出书名。解题要求:使用排序函数完成排序,在主
函数中进行输入输出。
/*案例代码文件名,AL9_12.C*/
/*程序功能:指针数组应用示例 */
/***********************************************/
/* sort()函数:对字符指针数组进行排序 */
/*形参,name——字符指针数组, count——元素个数 */
/*返回值:无 */
/***********************************************/
void sort(char *name[],int count)
{ char *temp_p;
int i,j,min;
/*使用选择法排序 */
for(i=0; i<count-1; i++) /*外循环:控制选择次数 */
{ min=i; /*预置本次最小串的位置 */
for(j=i+1; j<count; j++) /*内循环:选出本次的最小串 */
if(strcmp(name[min],name[j])>0) /*存在更小的串 */
min=j; /*保存之 */
if(min!=i) /*存在更小的串, 交换位置 */
temp_p=name[i],name[i]=name[min],name[min]=temp_p;
}
}
/*主函数 main()*/
main()
{ char *name[5]={“BASIC”,”FORTRAN”,”PASCAL”,”C”,”FoxBASE”};
int i=0;
sort(name,5); /*使用字符指针数组名作实参, 调用排序函数 sort()*/
/*输出排序结果 */
for(; i<5; i++) printf(“%s\n”,name[i]);
} [程序演示 ]
程序运行结果,
BASIC
C
FORTRAN
FoxBASE
PASCAL
程序说明,
( 1) 实参对形参的值传递,
sort( name,5 );
↓ ↓
void sort(char *name[],int count)
( 2) 字符串的比较只能使用 strcmp()函数 。 形参字符指针数组
name 的 每 个 元 素, 都 是 一 个 指 向 字 符 串 的 指 针, 所 以 有
strcmp(name[min],name[j])。
9.6.2 主函数 main()的形参
在以往的程序中, 主函数 main()都使用其无参形式 。 实际上, 主
函数 main()也是可以指定形参的 。
[案例 9.13] 用同一程序实现文件的加密和解密 。 约定:程序的可
执行文件名为 lock.exe,其用法为,lock +|- <被处理的文件名 >,其中
,+”为加密,, -”为解密 。
/*案例代码文件名,AL9_13.C*/
/*程序功能:带参主函数的应用示例 */
main(int argc,char *argv[])
{ char c;
if (argc != 3) printf("参数个数不对 ! \n");
else
{ c=*argv[1]; /*截取第二个实参字符串的第一个字符 */
switch(c)
{ case '+',/*执行加密 */
{ /*加密程序段 */
printf("执行加密程序段 。 \n");
}
break;
case '-',/*执行解密 */
{ /*解密程序段 */
printf("执行解密程序段 。 \n");
}
break;
default,printf("第二个参数错误 ! \n");
}
}
} [程序演示 ]
1.主函数 main()的有参形式
main(int argc,char *argv[])
{ … … }
2.实参的来源
运行带形参的主函数, 必须在操作系统状态下, 输入
主函数所在的可执行文件名, 以及所需的实参, 然后回
车即可 。
命令行的一般格式为,
[ 实参 2…… ]
例如, 本案例程序的用法,lock +|- <被处理的文件
名 >←┘
● 在 TC的集成环境下, 也可直接利用 Options |
Arguments 项, 输入主函数所需要的实参:只须输入各参
数 ( 相邻参数间用空格分开 ), 可执行文件名可省略 。
就本案例而言, 输入, +|- <被处理的文件名 >”即可 。
3.形参说明
( 1) 形参 argc是命令行中参数的个数 ( 可执行文件
名本身也算一个 ) 。
在本案例中, 形参 argc的值为 3( lock,+|-,文件
名 ) 。
( 2) 形参 argv是一个字符指针数组, 即形参 argv首
先是一个数组 ( 元素个数为形参 argc的值 ), 其元素值都
是指向实参字符串的指针 。
在本案例中, 元素 argv[0] 指向第 1个实参字符串
,lock”,元素 argv[1] 指向第 2个实参字符串, +|-”,元素
argv[2]指向第 3个实参字符串, 被处理的文件名, 。
9.6.3 指向指针的指针变量简介
在 [案例 9.12]的主函数 main()中, 数组 name是一个字符
指针数组, 即数组的每一个元素都是一个指向字符串的指
针 。
既然 name是一个数组, 则它的每一个元素也同样有相
应的地址, 因此可以设置一个指针变量 pointer,使其指向指
针数组的元素 ( 元素的值还是一个指针 ), 称 pointer为指向
指针的指针变量 。 显然, 指向指针的指针变量是一个两级
的指针变量 。
1.指向指针的指针变量的定义
数据类型 **指针变量 [,**指针变量 2…… ];
2.指向指针的指针变量的赋值
指针变量 = 指针数组名 + i
[Return]
9.7 函数的指针和指向函数的指针变量简介
1.函数指针的概念
一个函数在编译时,被分配了一个入口地址,这个地址就称为该
函数的指针。
可以用一个指针变量指向一个函数,然后通过该指针变量调用此
函数。
2.指向函数的指针变量
( 1)定义格式
函数类型 (*指针变量 )( );
注意,,*指针变量”外的括号不能缺,否则成了返回指针值的函
数。
例如,int (*fp)(); /* fp为指向 int函数的指针变量 */
( 2)赋值
函数名代表该函数的入口地址。因此,可用函数名给指向函数的
指针变量赋值。
指向函数的指针变量= [&]函数名 ;
注意,函数名后不能带括号和参数;函数名前的,&”符号是可选
的。
(3)调用格式
(*函数指针变量 )([实参表 ])
3.指向函数的指针变量作函数参数
指向函数的指针变量的常用用途之一, 就是将函数指
针作参数, 传递到其它函数 。
函数名作实参时, 因为要缺省括号和参数, 造成编译
器无法判断它是一个变量还是一个函数, 所以必须加以
说明 。 函数说明的格式, 与第 7章中介绍的一样 。
注意,对指向函数的指针变量, 诸如 p+i,p++/p--等
运算是没有意义的 。
[Return]
指针是C语言中的重要概念,也是C语言的重要特色。
使用指针,可以使程序更加简洁、紧凑、高效。
9.1 指针和指针变量的概念
9.2 指针变量的定义与应用
9.3 数组的指针和指向数组的指针变量
9.4 字符串的指针和指向字符串的指针变量
9.5 返回指针值的函数
9.6 指针数组与主函数 main()的形参
9.7 函数的指针和指向函数的指针变量
[Return]
9.1 指针和指针变量的概念
1.内存地址 ──内存中存储单元的编号
( 1)计算机硬件系统的内存储器中,拥有大量的存
储单元(容量为1字节)。
为了方便管理,必须为每一个存储单元编号,这个编
号就是存储单元的“地址”。每个存储单元都有一个惟
一的地址。
( 2)在地址所标识的存储单元中存放数据。
注意:内存单元的地址与内存单元中的数据是两个完
全不同的概念。
2.变量地址 ──系统分配给变量的内存单元的起始地
址
假设有这样一个程序,
main()
{ int num;
scanf("%d",&num);
printf("num=%d\n",num);
}
C编译程序编译到该变量定义语句时, 将变量 num 登录
到, 符号表, 中 。 符号表的关键属性有两个:一是, 标识
符名 ( id),, 二是该标识符在内存空间中的, 地址
( addr), 。
为描述方便, 假设系统分配给变量 num的 2字节存储单元
为 3000 和 3001,则起始地址 3000就是变量 num在内存中的
地址 。
3.变量值的存取 ──通过变量在内存中的地址进行
系统执行, scanf(”%d“,&num);”和, printf(”num=%d\n“,
num);”时, 存取变量 num值的方式可以有两种,
(1)直接访问 ──直接利用变量的地址进行存取
1)上例中 scanf(“%d”,&num)的执行过程是这样的,
用变量名 num作为索引值, 检索符号表, 找到变量 num
的起始地址 3000;然后将键盘输入的值 ( 假设为3 ) 送到
内存单元 3000和 3001中 。 此时, 变量 num在内存中的地址
和值, 如图 9-1所示 。
2) printf("num=%d\n",num)的执行过程,与 scanf()很相
似,
首先找到变量 num的起始地址 3000,然后从 3000和 3001
中取出其值, 最后将它输出 。
( 2) 间接访问 ──通过另一变量访问该变量的值
C语言规定:在程序中可以定义一种特殊的变量 ( 称
为指针变量 ), 用来存放其它变量的地址 。
例如, 假设定义了这样一个指针变量 num_pointer,它被
分配到 4000, 4001 单元, 其值可通过赋值语句
,num_pointer= & num ;, 得到 。 此时, 指 针 变 量
num_pointer的值就是变量 num在内存中的起始地址 3000,如
图 9-1所示 。
通过指针变量 num_pointer存取变量 num值的过程如下,
首先找到指针变量 num_pointer的地址 ( 4000), 取出其
值 3000( 正好是变量 num 的起始地址 ) ; 然后从 3000,3001
中取出变量 num的值 ( 3) 。
( 3) 两种访问方式的比较
两种访问方式之间的关系, 可以用某人甲 ( 系统 ) 要找
某人乙 ( 变量 ) 来类比 。
一种情况是, 甲知道乙在何处, 直接去找就是 ( 即直接
访问 ) 。
另一种情况是, 甲不知道乙在哪, 但丙 ( 指针变量 ) 知
道, 此时甲可以这么做:先找丙, 从丙处获得乙的去向, 然
后再找乙 ( 即间接访问 ) 。
4.指针与指针变量
( 1) 指针 ──即地址
一个变量的地址称为该变量的指针 。 通过变量的指针能够找到该变量 。
( 2) 指针变量 ──专门用于存储其它变量地址的变量
指针变量 num_pointer的值就是变量 num的地址 。 指针与指针变量的区
别, 就是变量值与变量的区别 。
( 3) 为表示指针变量和它指向的变量之间的关系, 用指针运算符, *”
表示 。
例如, 指针变量 num_pointer与它所指向的变量 num的关系, 表示为,
*num_pointer,即 *num_pointer等价于变量 num。
因此, 下面两个语句的作用相同,
num=3; /*将 3直接赋给变量 num*/
num_pointer=# /*使 num_pointer指向 num */
*num_pointer=3; /*将 3赋给指针变量 num_pointer所指向的变量 */
[Return]
9.2 指针变量的定义与应用
9.2.1 指针变量的定义与相关运算
[案例 9.1] 指针变量的定义与相关运算示例。
/*案例代码文件名,AL9_1.C*/
main()
{ int num_int=12,*p_int; /*定义一个指向 int型数据的指针变量 p_int */
float num_f=3.14,*p_f; /*定义一个指向 float型数据的指针变量 p_f */
char num_ch=’p’,*p_ch; /*定义一个指向 char型数据的指针变量 p_ch */
p_int=&num_int; /*取变量 num_int的地址,赋值给 p_int */
p_f=&num_f; /*取变量 num_f的地址,赋值给 p_f */
p_ch=&num_ch; /*取变量 num_ch的地址,赋值给 p_ch */
printf(“num_int=%d,*p_int=%d\n”,num_int,*p_int);
printf(“num_f=%4.2f,*p_f=%4.2f\n”,num_f,*p_f);
printf(“num_ch=%c,*p_ch=%c\n”,num_ch,*p_ch);
} [程序演示 ]
程序运行结果,
num_int=12,*p_int=12
num_f=3.14,*p_f=3.14
num_ch=p,*p_ch=p
程序说明,
( 1) 头三行的变量定义语句 ──指针变量的定义
与一般变量的定义相比, 除变量名前多了一个星号, *”
( 指针变量的定义标识符 ) 外, 其余一样,
数据类型 *指针变量 [,*指针变量 2……];
注意,此时的指针变量 p_int,p_f,p_ch,并未指向某
个具体的变量 ( 称指针是悬空的 ) 。 使用悬空指针很容易破
坏系统, 导致系统瘫痪 。
(2)中间三行的赋值语句 ──取地址运算 (& )
取地址运算的格式,&变量
例如, &num_int,&num_f,&num_ch的结果, 分别为变量 num_int、
num_f,num_ch的地址 。
注意,指针变量只能存放指针 ( 地址 ), 且只能是相同类型变量的
地址 。
例如, 指针变量 p_int,p_f,p_ch,只能分别接收 int型, float型,
char型变量的地址, 否则出错 。
( 3) 后三行的输出语句 ──指针运算 ( *)
使用直接访问和间接访问两种方式, 分别输出变量 num_int,num_f、
num_ch的值 。
注意,这三行出现在指针变量前的星号, *” 是指针运算符, 访问
指针变量所指向的变量的值, 而非指针运算符 。
[案例 9.2] 使用指针变量求解:输入 2个整数, 按升序 ( 从小到大排序 )
输出 。
/*案例代码文件名,AL9_2.C*/
/*程序功能:使用指针变量求解 2个整数的升序输出 */
main()
{ int num1,num2;
int *num1_p=&num1,*num2_p=&num2,*pointer;
printf(“Input the first number:,); scanf(“%d”,num1_p);
printf(“Input the second number:,); scanf(“%d”,num2_p);
printf(“num1=%d,num2=%d\n”,num1,num2);
if( *num1_p > *num2_p ) /*如果 num1>num2,则交换指针 */
pointer= num1_p,num1_p= num2_p,num2_p=pointer;
printf(“min=%d,max=%d\n”,*num1_p,*num2_p);
}
[程序演示 ]
程序运行情况,
Input the first number:9←┘
Input the second number:6←┘
num1=9,num2=6
min=6,max=9
程序说明,
( 1) 第 5行的 if语句
如果 *num1_p>*num2_p ( 即 num1>num2), 则交换
指针, 使 num1_p指向变量 num2( 较小值 ), num2_p指向
变量 num1( 较大值 ) 。
( 2) printf(“min=%d,max=%d\n”,*num1_p,
*num2_p); 语句:通过指针变量, 间接访问变量
的值 。
本案例的 处理思路 是:交换指针变量 num1_p 和
num2_p的值, 而不是变量 num1和 num2的值 ( 变量 num1和
num2并未交换, 仍保持原值 ), 最后通过指针变量输出
处理结果 。
9.2.2 指针变量作函数参数
1.指针变量,既可以作为函数的形参,也可以作函数
的实参。
2.指针变量作实参时,与普通变量一样,也是“值传
递”,即将指针变量的值(一个地址)传递给被调用函数
的形参(必须是一个指针变量)。
注意,被调用函数不能改变实参指针变量的值,但
可以改变实参指针变量所指向的变量的值。
[案例 9.3] 使用函数调用方式改写 [案例 9.2],要求实参为指针变量。
/*案例代码文件名,AL9_3.C*/
/******************************************************/
/*exchange()功能:交换 2个形参指针变量所指向的变量的值 */
/*形参,2个,均为指向整型数据的指针变量 */
/*返回值:无 */
/******************************************************/
void exchange(int *pointer1,int *pointer2)
{ int temp;
temp=*pointer1,*pointer1=*pointer2,*pointer2=temp;
}
/*主函数 main()*/
main()
{ int num1,num2;
/*定义并初始化指针变量 num1_p和 num2_p */
int *num1_p=&num1,*num2_p=&num2;
printf(“Input the first number:,); scanf(“%d”,num1_p);
printf(“Input the second number:,);
scanf(“%d”,num2_p);
printf(“num1=%d,num2=%d\n”,num1,num2);
if( *num1_p > *num2_p ) /* 即 num1>num2)*/
exchange(num1_p,num2_p); /*指针变量作实参 */
/*输出排序后的 num1和 num2的值 */
printf(“min=%d,max=%d\n”,num1,num2);
} [程序演示 ]
程序运行情况,
Input the first number:9←┘
Input the second number:6←┘
num1=9,num2=6
min=6,max=9
调用函数 exchange()之前, 之时, 结束时和结束后的
情况, 如图 9-5所示 。
形参指针变量 pointer1( 指向变量 num1) 和 pointer2
( 指向变量 num2), 在函数调用开始时才分配存储空间,
函数调用结束后立即被释放 。
虽然被调用函数不能改变实参指针变量的值, 但可以
改变它们所指向的变量的值 。
总结,为了利用被调用函数改变的变量值, 应该使用
指针 ( 或指针变量 ) 作函数实参 。 其机制为:在执行被
调用函数时, 使形参指针变量所指向的变量的值发生变
化;函数调用结束后, 通过不变的实参指针 ( 或实参指
针变量 ) 将变化的值保留下来 。
[案例 9.4] 输入 3个整数,按降序(从大到小的顺序)输出。要求使
用变量的指针作函数调用的实参来实现。
/*案例代码文件名,AL9_4.C*/
/******************************************************/
/*exchange()功能:交换 2个形参指针变量所指向的变量的值 */
/*形参,2个,均为指向整型数据的指针变量 */
/*返回值:无 */
/******************************************************/
void exchange(int *pointer1,int *pointer2)
{ int temp;
temp=*pointer1,*pointer1=*pointer2,*pointer2=temp;
}
/*主函数 main()*/
main()
{ int num1,num2,num3;
/*从键盘上输入 3个整数 */
printf(“Input the first number:,); scanf(“%d”,&num1);
printf(“Input the second number:,); scanf(“%d”,&num2);
printf(“Input the third number:,); scanf(“%d”,&num3);
printf(“num1=%d,num2=%d,num3=%d\n”,num1,num2,num3);
/*排序 */
if( num1 < num2 ) /*num1<num2*/
exchange( &num1,&num2 );
if( num1 < num3 ) exchange( &num1,&num3 );
if( num2 < num3 ) exchange( &num2,&num3 );
/*输出排序结果 */
printf(“排序结果, %d,%d,%d\n”,num1,num2,num3);
}
[程序演示 ]
程序运行情况,
Input the first number:9←┘
Input the second number:6←┘
Input the third number:12←┘
num1=9,num2=6,num3=12
排序结果, 12,9,6
[Return]
9.3 数组的指针和指向数组的指针变量
9.3.1 概述
1.概念
数组的指针 ──数组在内存中的起始地址,数组元素
的指针 ──数组元素在内存中的起始地址。
2.指向数组的指针变量的定义
指向数组的指针变量的定义,与指向普通变量的指针
变量的定义方法一样。
例如,int array[10],*pointer=array(或 &array[0]);
或者,
int array[10],*pointer;
pointer= array;
注意,数组名代表数组在内存中的起始地址(与第 1
个元素的地址相同),所以可以用数组名给指针变量赋值。
3.数组元素的引用
数组元素的引用, 既可用下标法, 也可用指针法 。 使用下标法,
直观;而使用指针法, 能使目标程序占用内存少, 运行速度快 。
9.3.2 通过指针引用数组元素
如果有, int array[10],*pointer=array;”, 则,
( 1) pointer+i和 array+i都是数组元素 array[i]的地址, 如图 9-6所
示 。
( 2) *(pointer+i)和 *(array+i)就是数组元素 array[i]。
( 3) 指向数组的指针变量, 也可将其看作是数组名, 因而可按
下标法来使用 。 例如, pointer[i]等价于 *(pointer+i)。
注意, pointer+1指向数组的下一个元素, 而不是简单地使指针变
量 pointer的值 +1。 其实际变化为 pointer+1*size(size为一个元素占用的
字节数 ) 。
例如, 假设指针变量 pointer的当前值为 3000,则 pointer+1为
3000+1*2=3002,而不是 3001。
[案例 9.5] 使用指向数组的指针变量来引用数组元素 。
/*案例代码文件名,AL9_5.C*/
/*程序功能:使用指向数组的指针变量来引用数组元素 */
main()
{ int array[10],*pointer=array,i;
printf(“Input 10 numbers:,);
for(i=0; i<10; i++)
scanf(“%d”,pointer+i); /*使用指针变量来输入数组元素的值 */
printf(“array[10]:,);
for(i=0; i<10; i++)
printf(“%d,,*(pointer+i)); /*使用指向数组的指针变量输出数组 */
printf(“\n”);
} [程序演示 ]
程序运行情况,
Input 10 numbers,0 1 2 3 4 5 6 7 8 9←┘
array[10],0 1 2 3 4 5 6 7 8 9
程序说明,
程序中第 3行和第 6行的 2个 for语句, 等价于下面的程序段,
for(i=0; i<10; i++,pointer++)
scanf(“%d”,pointer);
printf(“array[10]:,);
pointer=array; /*使 pointer重新指向数组的第一个元素 */
for(i=0; i<10; i++,pointer++)
printf(“%d”,*pointer);
思考题,
( 1) 如果去掉, pointer=array;”行, 程序运行结果会如何?
请上机验证 。
( 2) 在本案例中, 也可以不使用 i来作循环控制变量,
程序怎么修改? 提示:指针可以参与关系运算 。
说明,
( 1) 指针变量的值是可以改变的, 所以必须注意其当前
值, 否则容易出错 。
( 2) 指向数组的指针变量, 可以指向数组以后的内存单
元, 虽然没有实际意义 。
( 3) 对指向数组的指针变量 ( px和 py) 进行算术运算和
关系运算的含义
1) 可以进行的算术运算, 只有以下几种,
px± n,px++/++px,px--/--px,px-py
·px± n:将指针从当前位置向前 ( +n) 或回退 ( -n) n
个数据单位, 而不是 n个字节 。 显然, px++/++px和 px--/--px
是 px± n的特例 ( n=1) 。
·px-py:两指针之间的数据个数, 而不是指针的地址之
差 。
2) 关系运算
表示两个指针所指地址之间, 位置的前后关系:前者为小, 后
者为大 。
例如, 如果指针 px所指地址在指针 py所指地址之前, 则 px〈 py的
值为 1。
9.3.3 再论数组作函数参数
数组名作形参时, 接收实参数组的起始地址;作实参时, 将数组
的起始地址传递给形参数组 。
引入指向数组的指针变量后, 数组及指向数组的指针变量作函
数参数时, 可有4种等价形式 ( 本质上是一种, 即指针数据作函数
参数 ),
( 1) 形参, 实参都用数组名
( 2) 形参, 实参都用指针变量
( 3) 形参用指针变量, 实参用数组名
( 4) 形参用数组名, 实参用指针变量
9.3.4 2维数组的指针及其指针变量
1,2维数组的指针
假设有如下数组定义语句,int array[3][4];
( 1)从 2维数组角度看,数组名 array代表数组的起始地
址,是一个以行为单位进行控制的行指针,
·array+i:行指针值,指向 2维数组的第 i行。
·*(array+i):(列)指针值,指向第 i行第0列(控制由行
转为列,但仍为指针)。
·*(*(array+i)):数组元素 array[i][0]的值。
用 array作指针访问数组元素 array[i][j]的格式,
*(*(array+i)+ j)
注意,行指针是一个2级指针,如图 9-7所示。
( 2)从 1维数组角度看,数组名 array和第 1维下标的每
一个值,共同构成一组新的 1维数组名 array[0],array[1]、
array[2],它们均由 4个元素组成。
C语言规定,数组名代表数组的地址, 所以 array[i]
是第 i行 1维数组的地址, 它指向该行的第 0列元素, 是一
个以数组元素为单位进行控制的列指针,
·array[i]+j,( 列 ) 指针值, 指向数组元素 array[i][j]。
·*(array[i]+j):数组元素 array[i][j]的值 。
如果有, int array[3][4],*p=array[0];,, 则 p+1指向
下一个元素, 如图 9-8所示 。
用 p作指针访问数组元素 array[i][j]的格式,
*(p+(i *每行列数 +j) )
2.行指针变量 ──指向由 n个元素组成的一维数组的指
针变量
(1)定义格式
数据类型 (*指针变量 )[n];
注意,, *指针变量, 外的括号不能缺, 否则成了指针数组 ——
数组的每个元素都是一个指针 ──指针数组 ( 本章第 6节介绍 ) 。
(2)赋值
行指针变量 = 2维数组名 | 行指针变量 ;
[案例 9.6] 使用行指针和列指针两种方式输出 2维数组的任一元素 。
( 1) 使用行指针
/*案例代码文件名,AL9_6_1.C*/
/*程序功能:使用行指针输出 2维数组的任一元素 */
main()
{ int array[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int (*pointer)[4],row,col;
pointer=array;
printf(“Input row =,); scanf(“%d”,&row);
printf(“Input col =,); scanf(“%d”,&col);
printf(“array[%1d][%1d] = %d\n”,row,col,*(*(pointer+row)+col));
} [程序演示 ]
程序运行情况,
Input row = 1←┘
Input col = 2←┘
array[1][2] = 7
思考题,本题也可以直接使用数组名 array作指针, 应如何修改?
( 2) 使用列指针
/*案例代码文件名,AL9_6_2.C*/
/*程序功能:使用列指针输出 2维数组的任一元素 */
main()
{ int array[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
int *pointer,row,col; /*定义一个 (列 )指针变量 pointer*/
pointer=array[0]; /*给 (列 )指针变量 pointer赋值 */
printf(“Input row =,); scanf(“%d”,&row);
printf(“Input col =,); scanf(“%d”,&col);
printf(“array[%1d][%1d] = %d\n”,row,col,*(pointer+(row*4+col)));
} [程序演示 ]
3,2维数组指针作函数参数
2维数组的指针作函数实参时, 有列指针和行指针两种形式 。 相应
的, 用来接受实参数组指针的形参, 必须使用相应形式的指针变量,
如下所示,
实参,列指针 行指针
↓ ↓
形参,( 列 ) 指针变量 行指针变量
9.3.5 动态数组的实现
在程序运行过程中, 数组的大小是不能改变的 。 这种数组称为静
态数组 。 静态数组的缺点是:对于事先无法准确估计数据量的情况,
无法做到既满足处理需要, 又不浪费内存空间 。
所谓 动态数组是指, 在程序运行过程中, 根据实际需
要指定数组的大小 。
在 C语言中, 可利用内存的申请和释放库函数, 以及指
向数组的指针变量可当数组名使用的特点, 来实现动态数
组 。
动态数组的本质是, 一个指向数组的指针变量 。
[案例 9.7] 动态数组的实现 。
/*案例代码文件名,AL9_7.C*/
/*程序功能:实现动态数组 */
#include,alloc.h”
#include,stdlib.h”
main()
{ int *array=NULL,num,i;
printf(“Input the number of element:,); scanf(“%d”,&num);
/*申请动态数组使用的内存块 */
array=(int *)malloc( sizeof(int) * num );
if ( array==NULL ) /*内存申请失败:提示, 退出 */
{ printf(“out of memory,press any key to quit……,);
exit(0); /*exit():终止程序运行, 返回操作系统 */
}
/*提示输入 num个数据 */
printf(“Input %d elements:,,num);
for (i=0; i<num; i++) scanf(“%d”,&array[i]);
/*输出刚输入的 num个数据 */
printf(“%d elements are:,,num);
for (i=0; i<num; i++) printf(“%d,”,array[i]);
printf(“\b,); /*删除最后一个数据后的分隔符,,, */
free(array); /*释放由 malloc()函数申请的内存块 */
} [程序演示 ]
程序运行情况,
Input the number of element,3←┘
Input 3 elements,1 2 3←┘
3 elements are,1,2,3
程序说明,
( 1) array=(int *)malloc( sizeof(int) * num );语句 ──malloc()函数和
sizeof运算符
1) 库函数 malloc()
·用法,void *malloc(unsigned size)
·功能:在内存的动态存储区分配1个长度为 size的连续空间 。
·返回值:申请成功, 则返回新分配内存块的起始地址;否则, 返回
NULL。
·函数原型,alloc.h,stdlib.h。
malloc()函数的返回值是一个无类型指针, 其特点是可以指向任何类
型的数据 。 但在 实际使用 malloc()函数时, 必须将其返回值强制转换成
被赋值指针变量的数据类型, 以免出错 。
2) 运算符 sizeof
·格式,sizeof(变量名/类型名 )
·功能:求变量/类型占用的内存字节数 ( 正整数 ) 。 例如, 在
IBM-PC机上, sizeof(int)=2。
思考题,在该语句中, 使用 sizeof(int)求出 1个 int型数
据占用的内存字节数, 而不是使用常量, 2”,为什么?
( 2 ) scanf(“%d”,&array[i]); 语句和 printf(“%d,”,
array[i]);语句
将指向数组的指针变量当作数组名使用, 所以就必须
按引用数组元素的语法规则来使用 。
( 3) printf(“\b,);语句
, \b” 在该语句中的作用是, 使光标定位到最后一个
数据后的分隔符,,, 上, 然后再输出一个空格, 以达到
删除之目的 。
( 4) free(array);语句 ──库函数 free()
·用法,void free(void *ptr)
·功能:释放由 ptr指向的内存块 ( ptr是调用 malloc() 函
数的返回值 ) 。
·返回值:无 。
·函数原型,stdlib.h,alloc.h。
原则上, 使用 malloc()函数申请的内存块, 操作结束后,
应及时使用 free()函数予以释放 。 尤其是循环使用 malloc()函
数时, 如果不及时释放不再使用的内存块, 很可能很快就
耗尽系统的内存资源, 从而导致程序无法继续运行 。
[Return]
9.4 字符串的指针和指向字符串的指针变量
字符串在内存中的起始地址称为字符串的指针,可以
定义一个字符指针变量指向一个字符串。
9.4.1 字符串的表示与引用
在C语言中,既可以用字符数组表示字符串,也可用
字符指针变量来表示;引用时,既可以逐个字符引用,也
可以整体引用。
1.逐个引用
[案例 9.8] 使用字符指针变量表示和引用字符串 。
/*案例代码文件名,AL9_8.C*/
main()
{ char *string=”I love Beijing.”;
for(; *string!=’\0’; string++) printf(“%c”,*string);
printf(“\n”);
} [程序演示 ]
程序运行结果,
I love Beijing,
程序说明,char *string="I love Beijing.";语句
定义并初始化字符指针变量 string:用串常量, I love
Beijing.”的地址 ( 由系统自动开辟, 存储串常量的内存块的
首地址 ) 给 string赋初值 。
该语句也可分成如下所示的两条语句,
char *string;
string="I love Beijing.";
注意,字符指针变量 string中, 仅存储串常量的地址,
而串常量的内容 ( 即字符串本身 ), 是存储在由系统自动
开辟的内存块中, 并在串尾添加一个结束标志 ’ \0’。
2.整体引用
[案例 9.9] 采取整体引用的办法, 改写 [案例 9.8]。
/*案例代码文件名,AL9_9.C*/
/*程序功能:使用字符指针变量表示和引用字符串 */
main()
{ char *string=”I love Beijing.”;
printf(“%s\n”,string);
} [程序演示 ]
程序说明, printf("%s\n",string);语句
通过指向字符串的指针变量 string,整体引用它所指向的
字符串的原理:系统首先输出 string指向的第一个字符, 然
后使 string自动加1, 使之指向下一个字符;重复上述过程,
直至遇到字符串结束标志 。
注意,其它类型的数组, 是不能用数组名来一次性输出它
的全部元素的, 只能逐个元素输出 。
例如,
int array[10]={…… };
,....,
printf("%d\n",array); /*这种用法是非法的 */
,....,
3.字符指针变量与字符数组之比较
虽然用字符指针变量和字符数组都能实现字符串的存储和
处理, 但二者是有区别的, 不能混为一谈 。
( 1) 存储内容不同 。
字符指针变量中存储的是字符串的首地址, 而字符数组中
存储的是字符串本身 ( 数组的每个元素存放一个字符 ) 。
( 2) 赋值方式不同 。
对字符指针变量, 可采用下面的赋值语句赋值,
char *pointer;
pointer="This is a example.";
而字符数组, 虽然可以在定义时初始化, 但不能用赋
值语句整体赋值 。 下面的用法是非法的,
char char_array[20];
char_array="This is a example."; /*非法用法 */
( 3) 指针变量的值是可以改变的, 字符指针变量也不
例外;而数组名代表数组的起始地址, 是一个常量, 而常
量是不能被改变的 。
9.4.2 字符串指针作函数参数
[案例 9.10] 用函数调用方式,实现字符串的复制。
/*案例代码文件名,AL9_10.C*/
/**********************************************************/
/*string_copy()函数:复制一个字符串 */
/*形参:字符指针 str_from接收源串,字符指针 str_to存储目标串 */
/*返回值:无 */
/**********************************************************/
void string_copy(char *str_from,char *str_to)
{ int i=0;
for(; (*(str_to+i)=*(str_from+i))!=’\0’; i++) ; /*循环体为空语句 */
}
main()
{char array_str1[20]=”I am a teacher.”;
char array_str2[20];
string_copy(array_str1,array_str2); /*数组名作实参 */
printf(“array_str2=%s\n”,array_str2);
}
[程序演示 ]
程序运行结果,
I am a teacher,
程序说明, for(; (*(str_to+i)=*(str_from+i))!=’\0’; i++) ;
语句的执行过程为:首先将源串中的当前字符, 复制
到目标串中;然后判断该字符 ( 即赋值表达式的值 ) 是否
是结束标志 。 如果不是, 则相对位置变量 i的值增 1,以便
复制下一个字符;如果是结束标志, 则结束循环 。 其特点
是:先复制, 后判断, 循环结束前, 结束标志已经复制 。
在 C语言中, 用赋值运算符, 而不是赋值语句来实现
赋值操作, 能给某些处理带来很大的灵活性, 该语句 ( 实
现字符串的复制 ) 的用法就是最好的例证 。
[Return]
9.5 返回指针值的函数
一个函数可以返回一个 int型,float型,char型的数据,
也可以返回一个指针类型的数据。 返回指针值的函数(简称
指针函数)的定义格式如下,
函数类型 *函数名 ([形参表 ])
[案例 9.11] 某数理化三项竞赛训练组有 3个人,找出其中至少有一项
成绩不合格者。要求使用指针函数实现。
/*案例代码文件名,AL9_11.C*/
/*************************************************************/
/*seek()函数:判断是否有不合格成绩 */
/*形参:指向由 3个 int型元素组成的 1维数组的行指针变量 */
/*返回值,(1)有不合格成绩,则返回指向本行首列的一个 (列 )指针; */
/* (2)没有有不合格成绩,返回值为指向下一行的一个 (列 )指针 */
/*************************************************************/
int *seek( int (*pnt_row)[3] )
{ int i=0,*pnt_col; /*定义一个 (列 )指针变量 pnt_col */
pnt_col=*(pnt_row+1); /*使 pnt_col指向下一行之首 (作标志用 )*/
for(; i<3; i++)
if(*(*pnt_row+i)<60) /*某项成绩不合格 */
{ pnt_col=*pnt_row; /*使 pnt_col指向本行之首 */
break; /*退出循环 */
}
return(pnt_col);
}
/*主函数 main( ) */
main()
{ int grade[3][3]={{55,65,75},{65,75,85},{75,80,90}};
int i,j,*pointer; /*定义一个 (列 )指针变量 pointer */
for(i=0; i<3; i++) /*控制每个学生 */
{ pointer=seek(grade+i); /*用行指针作实参, 调用 seek()函数 */
if(pointer==*(grade+i)) /*该学生至少有一项成绩不合格 */
{ /*输出该学生的序号和各项成绩 */
printf(“No.%d grade list:,,i+1);
for(j=0; j<3; j++) printf(“%d,,*(pointer+j));
printf(“\n”);
}
}
} [程序演示 ]
程序运行结果,
No.1 grade list,55 65 75
程序说明,
( 1) 主函数中的 pointer=seek(grade+i);语句
调用 seek()函数时, 将实参 grade+i( 行指针 )的值, 复制到形参
pnt_row( 行指针变量 )中, 使形参 pnt_row指向 grade数组的第 i行 。
( 2) 在指针函数 seek()中,
1) pnt_col=*(pnt_row+1);语句
*(pnt_row+1)将行指针转换为列指针, 指向 grade数组的第 i+1行
第0列, 并赋值给 ( 列 ) 指针变量 pnt_col。
2) if(*(*pnt_row+i)<60)行
pnt_row是一个行指针, 指向数组 grade的第 i行; *pnt_row使指针
由行转换为列, 指向数组 grade的第 i行0列; *pnt_row+j的值还是一
个指针, 指向数组的第 i行第 j列; *(*pnt_row+ j)是一个数据 ( 数组元
素 grade[i][j]的值 ) 。
[Return]
9.6 指针数组与主函数 main()的形参
9.6.1 指针数组
1.概念
数组的每个元素都是一个指针数据。指针数组比较适
合用于指向多个字符串,使字符串处理更加方便、灵活。
2.定义格式
数据类型 *数组名 [元素个数 ]
注意,与行指针变量定义格式,<数据类型 >(*行指针
变量 )[<元素个数 >]”的差别。
[案例 9.12] 有若干计算机图书,请按字母顺序,从小
到大输出书名。解题要求:使用排序函数完成排序,在主
函数中进行输入输出。
/*案例代码文件名,AL9_12.C*/
/*程序功能:指针数组应用示例 */
/***********************************************/
/* sort()函数:对字符指针数组进行排序 */
/*形参,name——字符指针数组, count——元素个数 */
/*返回值:无 */
/***********************************************/
void sort(char *name[],int count)
{ char *temp_p;
int i,j,min;
/*使用选择法排序 */
for(i=0; i<count-1; i++) /*外循环:控制选择次数 */
{ min=i; /*预置本次最小串的位置 */
for(j=i+1; j<count; j++) /*内循环:选出本次的最小串 */
if(strcmp(name[min],name[j])>0) /*存在更小的串 */
min=j; /*保存之 */
if(min!=i) /*存在更小的串, 交换位置 */
temp_p=name[i],name[i]=name[min],name[min]=temp_p;
}
}
/*主函数 main()*/
main()
{ char *name[5]={“BASIC”,”FORTRAN”,”PASCAL”,”C”,”FoxBASE”};
int i=0;
sort(name,5); /*使用字符指针数组名作实参, 调用排序函数 sort()*/
/*输出排序结果 */
for(; i<5; i++) printf(“%s\n”,name[i]);
} [程序演示 ]
程序运行结果,
BASIC
C
FORTRAN
FoxBASE
PASCAL
程序说明,
( 1) 实参对形参的值传递,
sort( name,5 );
↓ ↓
void sort(char *name[],int count)
( 2) 字符串的比较只能使用 strcmp()函数 。 形参字符指针数组
name 的 每 个 元 素, 都 是 一 个 指 向 字 符 串 的 指 针, 所 以 有
strcmp(name[min],name[j])。
9.6.2 主函数 main()的形参
在以往的程序中, 主函数 main()都使用其无参形式 。 实际上, 主
函数 main()也是可以指定形参的 。
[案例 9.13] 用同一程序实现文件的加密和解密 。 约定:程序的可
执行文件名为 lock.exe,其用法为,lock +|- <被处理的文件名 >,其中
,+”为加密,, -”为解密 。
/*案例代码文件名,AL9_13.C*/
/*程序功能:带参主函数的应用示例 */
main(int argc,char *argv[])
{ char c;
if (argc != 3) printf("参数个数不对 ! \n");
else
{ c=*argv[1]; /*截取第二个实参字符串的第一个字符 */
switch(c)
{ case '+',/*执行加密 */
{ /*加密程序段 */
printf("执行加密程序段 。 \n");
}
break;
case '-',/*执行解密 */
{ /*解密程序段 */
printf("执行解密程序段 。 \n");
}
break;
default,printf("第二个参数错误 ! \n");
}
}
} [程序演示 ]
1.主函数 main()的有参形式
main(int argc,char *argv[])
{ … … }
2.实参的来源
运行带形参的主函数, 必须在操作系统状态下, 输入
主函数所在的可执行文件名, 以及所需的实参, 然后回
车即可 。
命令行的一般格式为,
[ 实参 2…… ]
例如, 本案例程序的用法,lock +|- <被处理的文件
名 >←┘
● 在 TC的集成环境下, 也可直接利用 Options |
Arguments 项, 输入主函数所需要的实参:只须输入各参
数 ( 相邻参数间用空格分开 ), 可执行文件名可省略 。
就本案例而言, 输入, +|- <被处理的文件名 >”即可 。
3.形参说明
( 1) 形参 argc是命令行中参数的个数 ( 可执行文件
名本身也算一个 ) 。
在本案例中, 形参 argc的值为 3( lock,+|-,文件
名 ) 。
( 2) 形参 argv是一个字符指针数组, 即形参 argv首
先是一个数组 ( 元素个数为形参 argc的值 ), 其元素值都
是指向实参字符串的指针 。
在本案例中, 元素 argv[0] 指向第 1个实参字符串
,lock”,元素 argv[1] 指向第 2个实参字符串, +|-”,元素
argv[2]指向第 3个实参字符串, 被处理的文件名, 。
9.6.3 指向指针的指针变量简介
在 [案例 9.12]的主函数 main()中, 数组 name是一个字符
指针数组, 即数组的每一个元素都是一个指向字符串的指
针 。
既然 name是一个数组, 则它的每一个元素也同样有相
应的地址, 因此可以设置一个指针变量 pointer,使其指向指
针数组的元素 ( 元素的值还是一个指针 ), 称 pointer为指向
指针的指针变量 。 显然, 指向指针的指针变量是一个两级
的指针变量 。
1.指向指针的指针变量的定义
数据类型 **指针变量 [,**指针变量 2…… ];
2.指向指针的指针变量的赋值
指针变量 = 指针数组名 + i
[Return]
9.7 函数的指针和指向函数的指针变量简介
1.函数指针的概念
一个函数在编译时,被分配了一个入口地址,这个地址就称为该
函数的指针。
可以用一个指针变量指向一个函数,然后通过该指针变量调用此
函数。
2.指向函数的指针变量
( 1)定义格式
函数类型 (*指针变量 )( );
注意,,*指针变量”外的括号不能缺,否则成了返回指针值的函
数。
例如,int (*fp)(); /* fp为指向 int函数的指针变量 */
( 2)赋值
函数名代表该函数的入口地址。因此,可用函数名给指向函数的
指针变量赋值。
指向函数的指针变量= [&]函数名 ;
注意,函数名后不能带括号和参数;函数名前的,&”符号是可选
的。
(3)调用格式
(*函数指针变量 )([实参表 ])
3.指向函数的指针变量作函数参数
指向函数的指针变量的常用用途之一, 就是将函数指
针作参数, 传递到其它函数 。
函数名作实参时, 因为要缺省括号和参数, 造成编译
器无法判断它是一个变量还是一个函数, 所以必须加以
说明 。 函数说明的格式, 与第 7章中介绍的一样 。
注意,对指向函数的指针变量, 诸如 p+i,p++/p--等
运算是没有意义的 。
[Return]