副标题#e#
下面来先容C语言成果最强大的特点,同时也是相对而言较量难把握的观念之一——指针。
一、指针的根基观念
如同其它根基范例的变量一样,指针也是一种变量,但它是一种把内存地点作为其值的变量。因为指针凡是包括的是一个拥有详细值的变量的地点,所以它可以间接地引用一个值。
二、指针变量的声明、初始化和运算符
声明语句
int *ptra, a;
声明白一个整型变量a与一个指向整数值的指针ptra,也就是说,在声明语句中利用*(称为“间接引用运算符”)即暗示被声明的变量是一个指针。指针可被声明为指向任何数据范例。需要强调的是,在此语句中变量a只被声明为一个整型变量,这是因为间接引用运算符*并不针对一条声明语句中的所有变量,所以每一个指针都必需在其名字前面用前缀*声明。指针应该用声明语句或赋值语句初始化,可以把指针初始化为0、NULL或某个地点,具有值0或NULL的指针不指向任何值,而要想把某个变量地点赋给指针,需利用单目运算符&(称为“地点运算符”)。
譬喻措施已用声明语句
int *ptra, a=3;
声明白整型变量a(值为3)与指向整数值的指针a,那么通过赋值语句
ptra=&a;
就可以把变量a的地点赋给指针变量ptra。需要留意的是不行将运算符&用于常量、表达式或存储种别被声明为register的变量上。被赋值后的指针可以通过运算符*得到它所指向的工具的值,这叫做“指针的复引用”,譬喻打印语句
printf("%d", *ptra);
就会打印出指针变量ptra所指向的工具的值(也就是a的值)3。假如被复引用的指针没有被正确的初始化或没有指向详细的内存单位城市导致致命的执行错误或意外地修改重要的数据。Printf的转换说明符%p以十六进制整数形式输出内存地点,譬喻在以上的赋值后,打印语句
printf("%p", &a);
printf("%p", ptra);
城市打印出变量a的地点。
三、指针表达式和算术运算以及数组、字符串和指针的干系
在算术表达式、赋值表达式和较量表达式中,指针是正当的操纵数,可是并非所有的运算符在与指针变量一起利用时都是正当的,可以对指针举办的有限的算术运算包罗自增运算(++)、自减运算(–)、加上一个整数(+、+=)、减去一个整数(-或-=)以及减去另一个指针。
数组的各元素在内存中是持续存放的,这是指针运算的基本。此刻假设在一台整数占4个字节的呆板上,指针ptr被初始化指向整型数组a(共有三个元素)的元素a[0],而a[0]的地点是40000,那么各个变量的地点就会如下表所示:
表达式 | ptra | &a[0] | &a[1] | &a[2] |
表达式的意义 | 指针ptra的值 | 元素a[0]的地点 | 元素a[1]的地点 | 元素a[2]的地点 |
表达式的值 | 40000 | 40000 | 40004 | 40008 |
必需留意,指针运算差异于通例的算术运算,一般地,40000+2的功效是40002,但当一个指针加上或减去一个整数时,指针并非简朴地加上或减去该整数值,而是加上该整数与指针引用工具巨细的乘积,而工具的巨细则和呆板与工具的数据范例有关。譬喻在上述环境下,语句
ptra+=2;
的功效是40000+4*2=40008, ptra也随之指向元素a[2],同理,诸如语句
ptra-=2;
ptra++;
++ptra;
ptra--;
ptra--;
等的运算道理也都与此沟通,至于指针与指针相减,则会获得在两个地点之间所包括的数组元素的个数,譬喻ptra1包括存储单位40008,ptra2包括存储单位40000,那么语句
x = ptra1 - ptra2;
获得的功效就是2(仍假设整数在内存中占4个字节)。因为除了数组元素外,我们不能认为两个沟通范例的变量是在内存中持续存储的,所以指针算数运算除了用于数组外没有什么意义。
假如两个指针范例沟通,那么可以把一个指针赋给另一个指针,不然必需用强制范例转换运算符把赋值运算符右边的指针的范例转换为赋值运算符左边指针的范例。譬喻ptr1是指向整数的指针,而ptr2是指向浮点数的指针,那么要把ptr2的值赋给ptr1, 则须用语句
ptr1 = (int *) ptr2;
#p#分页标题#e#
来实现。独一破例的是指向void范例的指针(即void *),因为它可以暗示任何范例的指针。任何范例的指针都可以赋给指向void范例的指针,指向void范例的指针也可以赋给任何范例的指针,这两种环境都不需要利用强制范例转换。
可是由于编译器不能按照范例确定void *范例的指针到底要引用几多个字节数,所以复引用void *指针是一种语法错误。
可以用相等测试运算符和干系运算符较量两个指针,可是除非他们指向同一个数组中的元素,不然这种较量一般没有意义。相等测试运算符则一般用来判定某个指针是否是NULL,这在本文后头提到内存操纵中有必然的用途。
C语言中的数组和指针有着密切的干系,他们险些可以交流,实际上数组名可以被认为是一个常量指针,假设a是有五个元素的整数数组,又已用赋值语句
ptra = a;
将第一个元素的地点赋给了指向整数的指针ptra,那么如下的一组表达式是等价的:
a[3] ptra[3] *(ptra+3) *(a+3)
它们都暗示数组中第四个元素的值。
又因为C语言中的字符串是用空字符(’\0′)竣事的字符数组,所以事实上,字符串就是指向其第一个字符的指针。可是照旧要提醒各人,数组名和字符串名都是常量指针,他们的值是不行被改变的,譬喻措施段
char s[]="this is a test.";
for (;*s='\0';s++)
printf("%c", *s);
是错误的,因为它试图在轮回中改变s的值,而s实际上是一个常量指针。
在本部门的最后,说一说指向函数的指针。指向函数的指针包括了该函数在内存中的地点,函数名实际上就是完成函数任务的代码在内存中的起始地点。函数指针常用在菜单驱动系统中。
#p#副标题#e#
四、指针的应用、内存操纵和简朴的数据布局
其实初学者在进修指针时,其坚苦之处往往不在于领略个中的根基观念,而在于不知道指针毕竟有何用处,什么时候应该用指针去办理实际问题。以下我就将指针的主要成果作一个简朴的先容。
A——挪用函数时能修改两个或两个以上的值并将其返回挪用函数
我们在没有进修指针之前便涉及了函数的利用,在函数中利用return语句可以把一个值从被挪用的函数返回给挪用函数(或从被挪用函数返回节制权而不返回一个值),可是在实际应用中经常需要可以或许修改挪用函数中的多个值,这就必需用到指针。
给函数通报参数的方法有两种,即传值和传引用,C语言中的所有函数挪用都是传值挪用,但可以用指针和间接引用运算符模仿传引用挪用。在挪用某个函数时,假如需要在被挪用的函数中修改参数值,应该给函数通报参数的地点。当把变量的地点通报给函数时,可以在函数顶用间接引用运算符*修改挪用函数中内存单位中的该变量的值。好比我们无法在只用传值挪用的环境下在函数中完成两个数的互换,因为它要求修改两个参数的值,但通过传地点模仿传引用挪用即可轻松地实现这一点,譬喻函数:
void swap(int *a, int *b)
{
int temp;
temp=*a;
*a=*b;
*b=temp;
}
就实现了两个整数的互换,在挪用函数swap时,需要用两个地点(或两个指向整数的指针)作为参数,好比
swap(&num1, &num2);
个中num1和num2是两个整型变量,这样他们的值通过挪用swap函数便获得了互换。需要指出的是,通报数组不需要利用运算符&, 因为数组名实际上是一个常量指针,然而通报数组元素就差异了,仍需要以元素的地点作为参数才气在函数中修改元素的值并返回挪用函数。
B——能越发利便地处理惩罚字符串与数组
如前文所述,数组名与字符串名都是常量指针,指针和数组名有时候是可以替代的。用指针编写数组下标标达式可节减编译时间,并且有时暗示相对偏移量会越发利便,这方面的能力本文就不多先容了。
C——能动态地分派空间并直接处理惩罚内存地点
不像BASIC等一些其他高级语言,C语言要求同一条理中声明语句不能被置于任何执行语句之后,这抉择了C中的数组是完全静态的,因为数组的长度必需在声明的时候确定,也就是说不能通过输入语句之类的要领姑且抉择命组的巨细,要想成立和维护动态的数据布局就需要实现动态的内存分派并运用指针举办操纵。
此刻将关于内存操纵的几个重要函数列表如下:
#p#分页标题#e#
函数:
void *calloc(size_t nmemb, size_t size); 浸染:
为含nmemb个工具的数组分派空间,每个工具的巨细为size.所分派的空间初始化玉成零。函数calloc返回空指针或一个指向所分派空间的指针。 函数:
void free(void *ptr); 浸染:
接纳ptr所指向的空间,纵然该空间可用于再分派。 函数:
void malloc(size_t size); 浸染:
分派由size指定巨细的工具空间。函数返回一个空指针或指向所分派空间的指针。 函数:
void *realloc(void *ptr, size_t size); 浸染:
将ptr所指工具的巨细改为size指定的巨细。在新旧空间的较小空间中的工具的内容不被改变。假如新空间大,该工具新分派部门的值是不确定的。若ptr是空指针,函数realloc的行为雷同指定空间的函数malloc。假如size为零且ptr不是空指针,其释放所指向的工具。函数realloc返回一个空指针或指向大概移动了的分派空间的指针。 注:这些函数的原型在通用东西头文件中。
运用这些函数我们可以实现动态数据布局的处理惩罚,举个简朴的例子,我们要模仿成立变长数组,只需利用如下语句:
int n, *ptr;
scanf("%d", &n);
ptr=malloc(n*sizeof(int));
这样,系统就分派了存放有n个元素的数组所需要的空间,ptr存储了分派内存的首地点,之后通过指针的复引用即可举办赋值等操纵。需要提醒的是,假如没有可用的内存,在挪用malloc函数时就返回NULL指针,所以这时一般应判定一下指针是否为NULL再举办下一步操纵。
由此可见,尽量指针凡是以变量的地点作为其值,但有时候它指向的地点基础就没有变量名,这时候只能通过复引用指针举办一系列的操纵。
D——能有效地暗示巨大的数据布局(本部门内容发起初学者在弄清一切根基观念之前可以先不看)
在初学者打仗令人费解的数据布局之前,必需先相识两个新的观念——“指向指针的指针”(也称二级指针)和“自引用布局”。
一级指针包括一个地点,该地点中存放详细的值,而二级指针包括的地点中存放的是另一个地点,因此也可以说二级指针是指向指针的指针。
声明一个二级指针要利用**,譬喻:
int **ptr;
声明白一个指向整数的二级指针。二级指针到底有什么用呢?其实就像通过给函数通报地点,我们就可以修改详细的值一样,给一个函数通报二级指针,就可以将修改地点并将其返回挪用函数,这在数据布局的打点上相当重要。
一般的布局包括若干成员,而自引用布局肯定包括一个指针成员,该指针指向与自身同一个范例的布局。譬喻,如下布局界说就界说了一个自引用布局:
struct node{
int data;
struct node *nextptr;
};
通过链节nextptr可以把一个struct node范例的布局与另一个同范例的布局链在一起。我们可以通过自引用布局成立有用的数据布局,如链表、行列、仓库和树。思量到篇幅问题与难度干系,笔者仅就较量根基的单向链表给出机器家产出书社《C措施设计教程》(第二版)中的源措施。我不推荐初学者过早打仗数据布局,因为个中涉及了二级指针和自引用布局的大量应用,在打牢基本之前根基不行能完全读懂措施就更不要说运用了。对数据布局有乐趣的同学可以再和我举办交换。
五、进修指针与内存的一点发起
许多人在刚打仗指针的时候大概会以为较量抽象,所以在操练的时候老是回避指针的应用,只管用原有的要领办理问题,越是这样就越难进步。我发起各人纵然面临不消指针也能办理的问题,也应该实验利用指针,尤其是模仿函数的传引用挪用等,只要多多操练,其实把握根基观念照旧很轻松的。再接下来就是要多思考什么时候用指针显得更利便和实用,逐渐把握怎么用指针的真谛。绝不浮夸地说指针是C的焦点,学好指针是驾御C语言的必由之路,各人加油干吧。