指针,C语言的精华,它在C语言中,表现得最优秀也最危险。
1.1.1 指针概念
1. 指针概述
内存中每个字节有一个编号,即地址。
变量是对数据存储内存空间的抽象,一般变量(如int等)是对变量的直接访问,而指针变量是对变量的间接访问。
指针变量说明此量为一变量,变量需要占用空间,同时指针即内存地址,结合起来就是内存地址变量,即专门用来存放内存地址的变量,该变量存放另一变量的内存地址。指针变量类型指的是指针变量指向目标变量的类型。
指针变量本身没有类型,所有指针变量都占用四个字节的空间,这个四个字节空间里存放的是内存地址。如int *p1、char *p2、double *p3中的指针变量p1、p2、p3都占用四个字节的内存空间。
理解指针变量指向的变量,int *p1中p1为指针变量,*p1为指针变量指向的变量。指针变量只能指向定义时所规定类型的变量,此时p1只能指向int型变量,即给p1赋值时只能赋int型变量的地址。指针变量定义后,变量值不确定,应用前必须先赋值。
&与*运算符互为逆运算,*表示取指针变量所指向变量的内容,&表示取变量的地址。假设i_pointer为指针变量,它的内容是地址量,*i_pointe为指针变量指向的变量,它的内容是数据,&i_pointer为指针变量本身内存的地址。
存放变量地址的变量是指针变量。int *p1说明p1是指针变量,该指针变量名为p1,p1的值为内存地址。*p1是p1所指向的变量,int说明指针变量指向变量的类型,即*p1的类型,而不是p1的类型。
数组名是表示数组首地址,是地址常量。
数组名作函数参数,是地址传递,数组名和指针变量作为函数传递参数时可以通用,数组名作为形参时被调用函数将数组名当做指针变量处理。
2. 指针变量与其所指向的变量之间的关系
图6-3画出了指针变量与其所指向变量之间的关系图,ó符号表示等价,其中变量i的地址为2000,i_pointer为指针变量,*i_pointer为指针变量所指向的变量。
图6-3 指针变量与其所指向变量之间的关系图
3. 指针变量定义
图6-4画出了指针变量定义图,说明了定义时各字段的含义。
图6-4 指针变量定义图
指针变量定义说明:
int *p1;
float *p2;
对上述指针变量,具体解释如下:
指针变量名是p1、p2,不是*p1、*p2。
指针变量只能指向定义时所规定类型的变量。p1只能指向int变量的地址,p2只能指向float变量的地址。
指针变量定义后,变量值不确定,使用前必须先赋值。
4. 指针的引用
指针变量的赋值只能赋予地址,决不能赋予任何其他数据,否则将引起错误。在C语言中,变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。与指针有如下两个有关的运算符:
① &:取地址运算符。
② *:指针运算符(或称“间接访问” 运算符)。
C语言中提供了地址运算符&来表示变量的地址,其一般形式为:&变量名;如&a表示变量a的地址,&b表示变量b的地址,变量本身必须预先进行定义。
设有指向整型变量的指针变量p,如要把整型变量a的地址赋予p可以有以下两种方式:
(1) 指针变量初始化的方法如下
int a;
int *p=&a;
(2) 对指针变量赋值的方法如下
int a;
int *p;
p=&a;
5. 指针引用图解
指针变量和一般变量一样,存放在它们之中的值是可以改变的,也就是说可以改变它们的指向,假设
char i,j,*p1,*p2;
i='a';
j='b';
p1=&i;
p2=&j;
上述语句建立了如下图6-5所示的联系:
图6-5 指针引用图1
在这里-100为i的内存地址(即&i),-101为j的内存地址(即&j)。
赋值p2=p1,就使p2与p1指向同一对象i,此时*p2就等于i,而不是j。如下图6-6所示:
图6-6 指针引用图2
如果执行如下表达式 *p2=*p1,则表示把p1指向的内容赋给p2所指的内存区域, 此时就变成下图6-7所示:
图6-7 指针引用图3
表6-5列出了上述变量在栈空间上的内存布局,当p2=p1,就是把p2的值改为-100,而*(-100)代表变量i。当*p2=*p1,即*(-101)=*(-100),是对两个内存地址指向的空间进行赋值,相当于j=i,而p2本身的值并没有改变。*p1是地址-100内存空间的抽象,*p2是地址-101内存空间的抽象,而p1是地址-104~-107内存空间的抽象,p2是地址-108~-111内存空间的抽象。
表6-5 上述变量在栈空间上的内存布局表
假设变量的堆栈起始地址为-100,()中代表变量的值 | ||
内存地址 | 内存 | 说明 |
-100 | i(a) |
|
-101 | j(b) |
|
-102 |
| 由于整型变量需要对齐,地址必须被4整除,指针本身编译器把它当做整型数据处理,所以空两个字节 |
-103 |
| |
-104 | p1 (-100) | 此时p1等于-100,而-100是变量i的内存地址,*(-100)代表指向内存地址-100的变量,即变量i
|
-105 | ||
-106 | ||
-107 | ||
-108 | p2 (-101) |
|
-109 | ||
-110 | ||
-111 |
6. 理解指针变量
假设
int a=3, *p1;
char *p2, aa=’X’;
p1=&a ;
p2=&aa ;
表6-6列出了上述变量在内存中的布局并给出其详细说明。
表6-6 指针变量内存布局表
假设变量的堆栈起始地址为3000,()中为变量的值 | ||
内存地址 | 内存 | 说明 |
3000 | a (3) | 变量是相应地址内存空间的抽象,变量a是内存地址3000~2997这片内存空间的抽象,其值初始化等于3,int型说明此变量占用空间大小为4个字节。&a等于变量a的内存地址,即3000 |
2999 | ||
2998 | ||
2997 | ||
2996 | p1 (3000) | p1是一个指针变量(即地址变量),保存的值为3000。*p1是指针变量p1所指向的变量,此时*p1等于*3000,即地址3000所指向的变量(即内存地址3000所存的变量值,也就是变量a),*p1此时可以与变量a划等号,值为3 |
2995 | ||
2994 | ||
2993 | ||
2992 | p2 (2888) | char *p2说明指针变量指向的变量类型为char,p2等于2888,是变量aa的内存地址,*p2等于*2888,即变量aa,其值为X |
2991 | ||
2990 | ||
2889 | ||
2888 | aa(X) | aa是一个char类型的变量,占用一个字节的内存空间 |
对上述指针变量的解释如下:
指针变量是一个特殊的变量,它里面存储的数值是内存里的一个地址。不管指针变量前面的类型如何,32位机器中指针变量都一律占用内存空间4个字节,这4个字节存放的就是其他变量的内存地址。所以指针变量可以理解成内存地址变量,*可理解为指向。
理解指针变量和指针变量指向的变量,上述p1、p2是指针变量,*p1、*p2是指针变量指向的变量。
理解指针变量的值和指针变量指向变量的值,指针变量的值为p1等于3000,p2等于2888。指针变量指向变量的值即内存地址指向变量的值,*p1即*3000,*3000等于变量a,即3;*p2即*2888,*2888等于变量aa,即字符X。
理解指针变量类型,通常所说指针变量类型是指针变量所指向变量的类型。*p1代表p1指向的类型为int型,占用4个字节,*p2代表p2指向的类型为char型,占用1个字节的内存空间。
理解指针变量本身类型,所有指针变量本身占用4个字节,存放内存地址。本身类型在编程中毫无意义,可以理解成无类型,也可以理解成是4个字节的整型数据。
int a说明a是整型变量,其a有两重限定含义,其一为变量,其二类型为整型,两者合在一起即为整型变量;char aa说明aa是字符变量。int *p中的*说明p是指针变量,指针是变量的限定词,说明此变量的类型是指针(内存地址),int说明此指针变量所指向变量的类型为int。
通过指针变量是对目标变量的间接操作。例如我们找小强,可以通过直接操作找小强;也可以通过间接操作,先找到小强的爸,通过小强爸的指引找到小强。上述a=3是的直接操作,而p1=&a、*p1=3则是间接操作,虽然两者结果相同,但过程不同。直接操作是直接在相应内存地址上读写数据,而间接操作则需两次或多次读写内存,间接操作是CPU首先从内存中读到变量地址,再根据变量地址从内存中读写数据。
1.1.2 sizeof、void、const说明
1. sizeof运算符
sizeof运算符是C语言的关键字,用于求一个对象所占用的字节数。使用sizeof运算符计算对象大小是一个良好的编程习惯。
【例6-6】利用sizeof更深刻的理解指针变量。
sizeof.c源代码如下:
#include <stdio.h>
int main()
{
int *p_int ;
char *p_char ;
float *p_float ;
double *p_double ;
int var_int ;
char var_char ;
float var_float ;
double var_double ;
struct stu
{
int num;
char name[8];
char sex;
float score;
} ;
struct stu *p_str, var_str ;
p_int=&var_int ;
p_char=&var_char ;
p_float=&var_float ;
printf("p_int=%d, *p_int=%d\n", sizeof(p_int), sizeof(*p_int) ) ;
printf("p_char=%d, *p_char=%d\n", sizeof(p_char), sizeof(*p_char) ) ;
printf("p_float=%d, *p_float=%d\n", sizeof(p_float), sizeof(*p_float) ) ;
printf("p_double=%d, *p_double=%d\n", sizeof(p_double), sizeof(*p_double) ) ;
printf("p_str=%d, *p_str=%d\n", sizeof(p_str), sizeof(*p_str) ) ;
printf("int length=%d\n", sizeof(int) ) ;
printf("char length=%d\n", sizeof(char) ) ;
printf("float length=%d\n", sizeof(float) ) ;
printf("double length=%d\n", sizeof(double) ) ;
printf("struct stu length=%d\n", sizeof(struct stu) ) ;
return 0 ;
}
编译 gcc sizeof.c -o sizeof。
执行 ./sizeof,执行结果如下:
p_int=4, *p_int=4
p_char=4, *p_char=1
p_float=4, *p_float=4
p_double=4, *p_double=8
p_str=4, *p_str=20
int length=4
char length=1
float length=4
double length=8
struct stu length=20
从上面运行结果可以看出,指针变量无论类型是什么,都只占用四个字节的内存空间,而指针变量指向的变量就是变量类型定义的大小。
2. void类型指针
void表示无类型,通常用来修饰指针变量,使用时需要强制转换。
void *p ;
p=(int *)malloc(sizeof(int)*100) ;
3. const关键字说明
类型声明中const用来修饰一个常量。修饰时有以下情况,请读者理解掌握。
const int nValue; //nValue是const
const char *pContent; //*pContent是const, pContent可变
const (char *) pContent;//pContent是const,*pContent可变
char* const pContent; //pContent是const,*pContent可变
const char* const pContent; //pContent和*pContent都是const
1.1.3 指针变量作为函数参数
【例6-7】指针变量作函数参数的运行机理
swap_p.c源代码如下:
#include <stdio.h>
int swap(int *p1,int *p2)
{
int temp;
temp=*p1;
*p1=*p2;
*p2=temp;
return 0 ;
}
int main()
{
int a,b;
int *pointer_1,*pointer_2;
scanf("%d,%d",&a,&b);
pointer_1=&a;pointer_2=&b;
if(a<b) swap(pointer_1,pointer_2);
printf("%d,%d\n",a,b);
return 0 ;
}
编译 gcc swap_p.c -o swap_p。
执行 ./swap_p,执行结果如下:
80,90
90,80
对上述程序的说明:
变量的赋值只不过是把内存一个地址上的值复制到另一个地址而已。
形参pointer_1传给形参p1后,由于pointer_1的值为&a,所以在swap函数中对*p1的操作,其实是对*(&a)的操作,即对a的操作。
一个程序中指针变量可以指向该执行空间堆栈段任何内存地址,所以即使在swap函数中,也能达到对该程序中任何变量进行操作,只要形参传入该变量地址即可完成对该地址上变量的操作。如*p1操作的是main函数中的a变量,因为p1的值等于a变量的地址。
指针变量可以指向执行程序(进程)整个堆栈空间,指针变量的值(内存地址)不正确或越界会造成程序执行错误或coredump,指针变量的值不正确或越界是常见的编程错误。
表6-7列出了swap_p程序运行内存堆栈空间表,此表可以更好的理解指针作函数形参时的调用原理,假设栈段起始值为30000,下表圆括号()中的值是变量的值。
表6-7 swap_p程序运行时内存堆栈表
数据段说明 | 内存地址 | 说明 |
栈段 | 30000 | main函数返回值存放空间 |
29996 | a(80) 此时a等于80,&a等于29996,*(&a)等于a | |
29992 | b(90) | |
29988 | pointer1(29996) | |
29984 | pointer2(29992) | |
29980 | swap函数返回值存放空间(0) | |
29976 | p1 (29996) | |
29972 | p2 (29992) | |
29968 | temp | |
堆段 | ...... | ...... |
bss段 | ...... | ...... |
静态数据段 | ...... | ...... |
代码段 |
| 1. swap函数地址入口 2. 在栈顶分配整型变量temp空间 3. 执行temp=*p1,即temp=*29996==a==80 4. 执行*p1=*p2,即*29996=*29992,即*(&a)=*(&b),即a=b==90,即a=90 5. 执行*p2=temp,即*29992=80, 即*(&b)=80,即b=80 6. 返回0给函数返回值空间,即内存地址29980~29977处 7. 函数结束返回。
8. main函数地址入口 9. 在栈顶分配main函数返回值、a、b、pointer1、pointer2变量空间 10. 从屏幕上读取两个数字分别存放到a、b变量的内存空间里,此时&a等于内存地址29996,而a代表内存地址29996~29993内存空间抽象,读入变量a的值存放到内存地址29996~29993的内存空间里。变量b同理 11. 执行pointer_1=&a,即pointer_1=29996 12. 执行pointer_2=&b, 即pointer_2=29992 13. 如果a<b,调用swap(pointer_1,pointer_2)函数,在栈顶分配swap返回值空间、p1、p2变量空间,并将实参pointer_1的值复制给p1,实参pointer_2的值复制给p2。然后即跳转到1(swap函数入口)的地方去执行 14. 打印a、b的值到屏幕上 15. 返回值0写入到main返回值存放空间,即内存地址30000~29997内存空间处 16. 程序执行完成,释放所有内存空间 |
1.1.4 指针的运算
1. 加减算术运算
对于指向数组的指针变量,可以加上或减去一个整数n。设pa是指向数组a的指针变量,则pa+n、pa-n、pa++、++pa、pa--、--pa运算都是合法的。
指针变量的加减运算只能对数组指针变量进行,对指向其他类型变量的指针变量作加减运算是毫无意义的。
指针变量加减偏移量与其前面定义类型密切相关,int类型指针变量加1地址偏移4位,char类型指针变量加1偏移1位。
【例6-8】指针变量加减运算地址的偏移量
p_airth.c源代码如下:
#include <stdio.h>
int main()
{
int x[10], *px ;
char y[10], *py ;
px=x ;
py=&y[0] ;
printf("px=%d, py=%d\n", px, py) ;
px++ ;
py++ ;
printf("px1=%d, py1=%d\n", px, py) ;
return 0;
}
编译 gcc p_airth.c -o p_airth。
执行 ./p_airth,执行结果如下:
px=-1074644316, py=-1074644266
px1=-1074644312, py1=-1074644265
2. 两个指针变量之间的运算
只有指向同一数组的两个指针变量之间才能进行运算,否则运算毫无意义。两个指针变量之间的运算有下面两种情况。
① 两指针变量相减:两指针变量相减所得之差除以指针变量指向变量的类型长度是两个指针所指数组元素之间相差的元素个数。
int a[10],*p1, *p2 ;
p1=&a[0];
p2=&a[7];
此时(p2-p1)/4等于7,因为int占用4个字节,数组在内存中是顺序存储的。
② 两指针变量进行关系运算:指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。
例如:
pf1==pf2 表示pf1和pf2指向同一数组元素。
pf1>pf2 表示pf1处于高地址位置。
pf1<pf2 表示pf2处于低地址位置。
指针变量还可以与0比较,设p为指针变量,则p==0表明p是空指针,它不指向任何变量,p!=0表示p不是空指针,空指针是对指针变量赋予0值而得到的。
例如:
#define NULL 0
int *p=NULL;
对指针变量赋0值和不赋值是不同的。指针变量未赋值时,可以是任意值,是不能当右值使用的,否则将造成意外错误。而指针变量赋0值后,则是安全的,它不指向任何具体的变量,指针变量不使用时应赋0值。
3. 指针的赋值运算
指针的赋值运算有下面6种情形。
① 指针变量初始化赋值,前面已作介绍。
② 把一个变量的地址赋予指向相同数据类型的指针变量。
例如:int a,*pa;
pa=&a; /*把整型变量a的地址赋予整型指针变量pa*/
③ 把一个指针变量的值赋予指向相同类型变量的另一个指针变量。
如: int a,*pa=&a,*pb;
pb=pa; /*把a的地址赋予指针变量pb*/
由于pa、pb均为指向整型变量的指针变量,因此可以相互赋值。
④ 把数组的首地址赋予指向数组的指针变量。
例如:int a[5],*pa;
pa=a; /*数组名表示数组的首地址,故可赋予指向数组的指针变量pa*/
也可写为:pa=&a[0]; /*数组第一个元素的地址也是整个数组的首地址,也可赋予pa*/
当然也可采取初始化赋值的方法:
int a[5],*pa=a;
⑤ 把字符串的首地址赋予指向字符类型的指针变量。
例如: char *pc;
pc="C Language"; /*编译程序先开辟静态数据区存放字符串,然后指针变量pc指向它*/
或用初始化赋值的方法写为:
char *pc="C Language";
此时相当于 static char st[]={"C Language"}, *pc=&st ;
⑥ 把函数的入口地址赋予指向函数的指针变量。
例如:
int (*pf)();
pf=f; /*f为函数名*/
其实函数也有其入口地址,指针变量指向函数地址入口,就代表对此函数的执行。
1.1.5指向数组的指针变量
1.数组与指针的关系
在许多程序员眼里,数组与指针有说不清、道不明的差别,剪不断、理还乱的关系。它们俩什么时候是等价,什么时候又不等价,为什么许多时候可以等价使用,又有一些时候判若两人呢?为了揭开它们俩差别的神秘面纱,还得从CPU这个关键人物说起,CPU看待变量,只不过是一段内存空间的抽象而已。数组变量也好,指针变量也罢,最终都会转化对内存地址的操作,对于数组和指针,是编译器让这两位兄弟在右值时等价是编译器让数组与指针让这两位兄弟在右值时等价。又因为数组名是地址常量(数组名代表数组首地址,不能改变,编译时确定),而指针变量是地址变量(有4个字节的内存空间,是变量理所当然执行时可以不断改变),所以在左值时只能有指针变量可以使用,而数组名是地址常量,不占内存空间,当然就不能被其他变量或常量对其赋值。
一个变量有一个地址,一个数组包含若干元素,每个数组元素都在内存中占用一定的存储单元,它们都有相应的内存地址,而且数组元素是按顺序占用连续的内存空间。所谓数组的指针是指数组的起始地址,数组元素的指针是指某个数组元素的地址。
2.一级指针变量与一维数组的关系
假设定义:int *p与int q[10],下面详细说明两者的联系与区别,“ó”表示两者等价。
数组名是指针(地址)常量。
当p=q时,p+i是q[i]的地址。
数组元素的表示方法有下标法和指针法两种,若p=q,则p[i]óq[i] ó *(p+i) ó*(q+i) 。
形参是数组时实质上是形参变量是指针变量,即int q[]óint *q,调用时该形参变量占用4个字节,存放数组的首地址。
在定义指针变量(不是形参)时,不能把int *p写成int p[]。
系统只给p分配4字节的内存区(32位机器),而给q分配4*10字节的内存区。
3.指针变量与数组名的等价使用
如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素。引入指针变量后,就可以用下标法和指针法两种方法来访问数组元素了。
int a[10], p=&a[0];
此时p的初值为&a[0],则:
1) p+i和a+i就是a[i]的地址,或者说它们指向a数组的第i个元素。
图6-8画出了指针变量与数组名等价使用图。
图6-8 指针变量与数组名等价使用图
2) *(p+i)或*(a+i)就是p+i或a+i所指向的数组元素,即a[i]。
例如,*(p+5)或*(a+5)就是a[5]。
3) 指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价。
根据以上叙述,引用一个数组元素可以有如下两种方法:
① 下标法:用a[i]形式访问数组元素。
② 指针法:若p=a,可采用*(a+i)或*(p+i)形式访问数组元素,其中a是数组名, p是指向数组的指针变量。
注意指针变量可以实现本身值的改变,如p++是合法的,而a++是错误的。因为a是数组名,是地址常量,不能进行自加减运算。
【例6-9】指针变量与数组名的等价使用
p_array.c源代码如下:
#include <stdio.h>
int main()
{
int a[10]={0,1,2,3,4,5,6,7,8,9} ;
int *pa ;
pa=a ;
printf("a[9]=%d\n", a[9]) ;
printf("pa[9]=%d\n", pa[9]) ;
printf("*(a+9)=%d\n", *(a+9)) ;
printf("*(pa+9)=%d\n", *(pa+9)) ;
return 0 ;
}
编译 gcc p_array.c -o p_array。
执行 ./p_array, 执行结果如下:
a[9]=9
pa[9]=9
*(a+9)=9
*(pa+9)=9
从上面的执行结果可以看出,这四种表示法在右值时是等价的。假设a代表地址3000,这四种表示法经编译过后都是*(3000+9),都代表a[9]。
1.1.6 数组名作函数参数
1.形参是数组的本质特征
形参是数组时实质上形参变量是指针变量,即int q[]óint *q,调用时该形参变量q占用4个字节,存放数组的首地址。
f(int x[],int n)等同于f(int *x,int n)
2.形参是数组名时与指针可等价使用
fun_array.c源代码如下:
#include <stdio.h>
// void inv(int *x, int n)
void inv(int x[],int n) /*形参x是数组名*/
{
int temp,i,j,m=(n-1)/2;
for(i=0;i<=m;i++)
{j=n-1-i;
temp=x[i];x[i]=x[j];x[j]=temp;}
// temp=*(x+i); *(x+i)=*(x+j); *(x+j)=temp; }
return;
}
int main()
{int i,a[10]={3,7,9,11,0,6,7,5,4,2};
printf("The original array:\n");
for(i=0;i<10;i++)
printf("%d,",a[i]);
printf("\n");
inv(a,10);
printf("The array has benn inverted:\n");
for(i=0;i<10;i++)
printf("%d,",a[i]);
printf("\n");
return 0 ;
}
编译 gcc fun_array.c -o fun_array。
执行 ./fun_array, 执行结果如下:
The original array:
3,7,9,11,0,6,7,5,4,2,
The array has benn inverted:
2,4,5,7,6,0,11,9,7,3,
可以看出,数组名作形参时可以与指针变量互换。
此时void inv(int *x, int n)与void inv(int x[],int n)等价
temp=x[i];x[i]=x[j];x[j]=temp与temp=*(x+i); *(x+i)=*(x+j); *(x+j)=temp等价
上面两两组合可构成四种情形。
3.多维数组数组名含义
若int a[3][4],图6-9画出了二维数组a的地址,每个方格中是多维数组元素的地址,此时a、a[0]、a[1]、a[2]都是地址常量,编译时确定,其值说明如下。
a==a[0]==&a[0][0]==1000
a+1==a[1]==&a[1][0]==1008
a+2==a[2]==&a[2][0]==1016
图6-9 多维数组地址图
1.1.7 函数指针变量
在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使该指针变量指向该函数,然后通过指针变量就可以找到并调用这个函数。这种指向函数的指针变量称为“函数指针变量”。
函数指针变量定义的一般形式如下:
类型说明符 (*指针变量名)();
其中“类型说明符”表示被指函数的返回值类型。“(* 指针变量名)”表示定义的是指针变量,最后的空圆括号表示指针变量所指的是一个函数。
例如:
int (*pf)();
pf是一个指向函数入口的指针变量,该函数的返回值(函数值)是整型。
【例6-10】函数指针变量举例
p_fun.c源代码如下:
#include <stdio.h>
int max(int a,int b)
{
if(a>b)return a;
else return b;
}
int main()
{
int max(int a,int b);
int(*pmax)();
int x,y,z;
pmax=max;
printf("input two numbers:\n");
scanf("%d%d",&x,&y);
z=(*pmax)(x,y);
printf("maxmum=%d\n",z);
return 0 ;
}
编译 gcc p_fun.c -o p_fun。
执行 ./p_fun,执行结果如下:
input two numbers:
2000 3000
maxmum=3000
1.1.8 返回指针类型函数
所谓函数类型是指函数返回值的类型。在C语言中允许一个函数的返回值是一个指针(即地址),这种返回指针值的函数称为指针型函数。
定义指针型函数的一般形式如下:
类型说明符 *函数名(形参表)
{
…… /*函数体*/
}
其中函数名之前加了“*”号表明这是一个指针型函数,即返回值是一个指针,类型说明符表示返回的指针变量所指向的数据类型。
【例6-11】本程序是通过指针函数,输入一个1~7之间的整数,输出对应的星期名。
p_week.c源代码如下:
#include <stdio.h>
#include <stdlib.h>
int main()
{
int i;
char *day_name(int n);
printf("input Day No:\n");
scanf("%d",&i);
if(i<0) exit(1);
printf("Day No:%2d-->%s\n",i,day_name(i));
return 0 ;
}
char *day_name(int n){
static char *name[]={ "Illegal day",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"};
return((n<1||n>7) ? name[0] : name[n]);
}
编译 gcc p_week.c -o p_week。
执行 ./p_week, 执行结果如下:
input Day No:
3
Day No: 3-->Wednesday
1.1.9 指向指针的指针
如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。
通过指针访问变量称为间接访问。由于指针变量直接指向变量,所以称为“单级间址”,而如果通过指向指针的指针变量来访问变量则构成“二级间址”,图6-10画出二维指针变量实现图。
图6-10 二维指针变量实现图
【例6-12】一个指针数组元素指向数据的简单例子。
p2_array.c源代码如下:
#include <stdio.h>
int main()
{
static int a[5]={1,3,5,7,9};
int *num[5]={&a[0],&a[1],&a[2],&a[3],&a[4]};
int **p,i;
p=num;
for(i=0;i<5;i++)
{
printf("%d\t",**p);
p++;
}
printf("\n") ;
return 0 ;
}
编译 gcc p2_array.c -o p2_array。
执行 ./p2_array, 执行结果如下:
1 3 5 7 9
1.1.10 结构指针
1.结构指针变量定义
一个指针变量用来指向一个结构变量时,称之为结构指针变量。结构指针变量中的值是所指向的结构变量首地址,通过结构指针即可访问该结构变量,这与数组指针和函数指针的情况是相同的。
结构指针变量定义语法形式如下:
struct 结构名 *结构指针变量名
2.结构作函数形参特点
用结构变量的成员作形参或结构变量作形参都为值传递,效率低,用结构指针变量作参数为地址传递。
结构指针变量也是地址变量,占用4个字节的内存空间,存放的是结构变量的内存地址。结构变量代表一片内存空间的抽象,与int等直接变量类似,所以结构变量作形参是值传递,结构指针变量作形参是地址传递。
3.指针变量指向结构变量
struct stu
{
int num;
char name[30];
};
struct stu girl;
struct stu *pstu;
pstu=&girl;
有了结构指针变量,就能更方便地访问结构变量的各个成员。
结构指针变量访问结构成员语法形式如下:
(*结构指针变量).成员名
或为: 结构指针变量->成员名
此时(*pstu).num等价于pstu->num。
4. 结构指针变量作形参
p_struct.c源代码如下:
#include <stdio.h>
#include <string.h>
struct student
{
char name[20];
int age;
int sex;
int height;
};
void showstu(struct student *p)
{
printf("A student:\n");
printf(" Name : %s \n",p->name);
printf(" Age : %d \n",p->age);
printf(" Sex : %d \n",p->sex);
printf(" Height: %d \n\n",p->height);
}
void main()
{
struct student stu1;
struct student *p1;
p1=&stu1;
stu1.age=17;
stu1.sex=1;
stu1.height=176;
strcpy(stu1.name,"Jim");
showstu(p1);
}
编译 gcc p_struct.c -o p_struct。
执行 ./p_struct, 执行结果如下:
A student:
Name : Jim
Age : 17
Sex : 1
Height: 176
1.1.11 动态存储分配
变量空间是在栈上分配,动态存储分配是在堆上分配。动态内存分配需要用free函数释放空间,没有用free释放会造成内存泄露。
1.动态存储函数原型
申请内存空间函数有malloc、calloc、realloc三个函数,这里主要介绍malloc函数,另外两个函数将在Linux进程编程章节加以介绍,这三个函数申请的内存空间需要用free函数来释放。
malloc函数和free函数的函数原型如下:
malloc(动态申请内存空间) |
所需头文件 | #include <stdlib.h> |
函数说明 | 动态申请内存空间 |
函数原型 | void *malloc(size_t size) |
函数传入值 | size:申请内存空间的大小 |
函数返回值 | 成功:返回申请内存空间的首地址 |
失败:NULL |
free(释放原先申请内存空间) |
所需头文件 | #include <stdlib.h> |
函数说明 | 参数ptr为指向先前由malloc()、calloc()或realloc()所返回的内存指针,调用free()后ptr所指的内存空间便会被收回 |
函数原型 | void free(void *ptr) |
函数传入值 | ptr:申请内存空间的首地址 |
2.动态存储函数举例
malloc.c源代码如下:
#include <stdio.h>
#include <stdlib.h>
int main()
{
struct stu
{
int num;
char *name;
char sex;
float score;
} *ps;
ps=(struct stu*)malloc(sizeof(struct stu));
ps->num=102;
ps->name="Zhang ping";
ps->sex='M';
ps->score=62.5;
printf("Number=%d\nName=%s\n",ps->num,ps->name);
printf("Sex=%c\nScore=%.2f\n",ps->sex,ps->score);
free(ps);
return 0 ;
}
编译 gcc malloc.c -o malloc。
执行 ./malloc, 执行结果如下:
Number=102
Name=Zhang ping
Sex=M
Score=62.50
1.1.12 指针链表
链表是通过系统不断申请内存,然后通过结构体中的指针变量将所申请的空间一级一级链接起来。
以学生链表为例,下面是链表的结构体定义。
struct stu
{
int num;
int score;
struct stu *next;
}
图6-11为一简单链表的示意图,通过指针变量把各节点链在一起。
图6-11 指针链表图
图6-11中,第0个结点称为头结点,它存放第一个结点的首地址,它没有数据,只是一个指针变量。以下的每个结点都分为两个域,一个是数据域,存放各种实际的数据,如成绩score等;另一个域为指针域,存放下一结点的首地址。链表中的每一个结点都是同一种结构类型。
对链表的主要操作有以下几种:
① 建立链表;
② 结构体的查找与输出;
③ 插入一个结点;
④ 删除一个结点。
【例6-13】建立一个三个结点的链表,存放学生数据,假定学生数据结构中只有学号和年龄两项,可编写一个建立链表的函数creat。
creat.c源代码如下:
#define NULL 0
#define TYPE struct stu
#define LEN sizeof (struct stu)
struct stu
{
int num;
int age;
struct stu *next;
};
TYPE *creat(int n)
{
struct stu *head,*pf,*pb;
int i;
for(i=0;i<n;i++)
{
pb=(TYPE*) malloc(LEN);
printf("input Number and Age\n");
scanf("%d%d",&pb->num,&pb->age);
if(i==0)
pf=head=pb;
else pf->next=pb;
pb->next=NULL;
pf=pb;
}
return(head);
}
1.1.13指针数据类型小结
表6-8列出了指针数据类型,并对其含义进行了说明。
表6-8 指针数据类型小结表
定义 | 含 义 |
int i | 定义整型变量i |
int *p | p为指向整型数据的指针变量 |
int a[n] | 定义整型数组a,它有n个元素 |
int *p[n] | 定义指针数组p,它由n个指向整型数据的指针元素组成 |
int (*p)[n] | p为指向含n个元素的一维数组的指针变量 |
int f() | f为带回整型函数值的函数 |
int *p() | p为带回一个指针的函数,该指针指向整型数据 |
int (*p)() | p为指向函数的指针,该函数返回一个整型值 |
int **p | p是一个指针变量,它指向一个指向整型数据的指针变量 |
摘录自《深入浅出Linux工具与编程》