标题:
C基础教程 第十章 指 针(上)
[打印本页]
作者:
苹果也疯狂
时间:
2011-10-21 12:18
标题:
C基础教程 第十章 指 针(上)
指针是C语言中的重要概念,也是C语言的重要特色。使用指针,可以使程序更加简洁、紧凑、高效。
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
所指向的变量
*/
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
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
的地址,如图
9-6
所示。
(
2
)
*(pointer+i)
和
*(array+i)
就是数组元素
array
。
(
3
)指向数组的指针变量,也可将其看作是数组名,因而可按下标法来使用。例如,
pointer
等价于
*(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
[0]
的值。
用
array
作指针访问数组元素
array
[j]
的格式:
*(*(array+i)
+
j)
注意:行指针是一个2级指针,如图
9-7
所示。
(
2
)从
1
维数组角度看,数组名
array
和第
1
维下标的每一个值,
共同构成一组新的
1
维数组名
array[0]
、
array[1]
、
array[2]
,它们均由
4
个元素组成。
C语言规定:数组名代表数组的地址,所以
array
是第
i
行
1
维数组的地址,
它指向该行的第
0
列元素,是一个以数组元素为单位进行控制的列指针:
·
array
+j
:(列)指针值,指向数组元素
array
[j]
。
·
*(array
+j)
:数组元素
array
[j]
的值。
如果有“
intarray[3][4],*p=array[0]
;”,则
p+1
指向下一个元素,如图
9-8
所示。
用
p
作指针访问数组元素
array
[j]
的格式:
*(p+(
i
*
每行列数
+j) )
2.
行指针变量──指向由
n
个元素组成的一维数组的指针变量
(1)
定义格式
数据类型
(*
指针变量
)[n];
注意:“
*
指针变量”外的括号不能缺,否则成了指针数组
--
数组的每个元素都是一个指针──指针数组(本章第
6
节介绍)。
(2)
赋值
行指针变量
=
2
维数组名
|
行指针变量
;
[
案例
9.6]
使用行指针和列指针两种方式输出
2
维数组的任一元素。
(
1
)
使用行指针
/*
案例代码文件名:
AL9_6_1.C*/
/*
程序功能:使用行指针输出
2
维数组的任一元素
*/
main()
{
intarray[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
);
/*
输出刚输入的
num
个数据
*/
printf(“%d
elements
are: ”, num);
for(i=0; i<num; i++) printf(“%d,”, array
);
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
);
语句和
printf(
“
%d,
”
, array
);
语句
将指向数组的指针变量当作数组名使用,所以就必须按引用数组元素的语法规则来使用。
(
3
)
printf(
“
\b
”
);
语句
“
\b
”
在该语句中的作用是,使光标定位到最后一个数据后的分隔符“,”上,然后再输出一个空格,以达到删除之目的。
(
4
)
free(array);
语句──库函数
free()
·用法:
void
free(void
*ptr)
·功能:释放由
ptr
指向的内存块(
ptr
是调用
malloc()
函数的返回值)。
·返回值:无。
·函数原型:
stdlib.h
,
alloc.h
。
原则上,使用
malloc()
函数申请的内存块,操作结束后,应及时使用
free()
函数予以释放。尤其是循环使用
malloc()
函数时,如果不及时释放不再使用的内存块,很可能很快就耗尽系统的内存资源,从而导致程序无法继续运行。
欢迎光临 电子技术论坛_中国专业的电子工程师学习交流社区-中电网技术论坛 (http://bbs.eccn.com/)
Powered by Discuz! 7.0.0