再战指针!(C Primer Plus 第六版基础整合)(二)

简介: 再战指针!(C Primer Plus 第六版基础整合)(二)

五、使用指针形参



函数要处理数组必须知道何时开始、何时结束。sum()函数使用一个指针形参标识数组的开始,用一个整数形参表明待处理数组的元素个数(指针形参也表明了数组中的数据类型)。但是这并不是给函数传递必备信息的唯一方法。 还有一种方法是传递两个指针,第1个指针指明数组的开始处(与前面用法相同), 第2个指针指明数组的结束处。同时该程序也表明了指针形参是变量,这意味着可以用索引表明访问数组中的哪一个 元素。


实例

#include <stdio.h>
#define SIZE 10
int sump(int * start,int * end);
int main(void)
{
    int marbles[SIZE] = {20,10,5,39,4,16,19,26,31,20};
    long answer;
    answer = sump(marbles,marbles + SIZE);
    printf("The total number of marbles is %ld .\n",answer);
    return 0;
}
/* 使用指针算法 */
int sump(int * start ,int * end)
{
    int total = 0;
    while (start<end)
    {
        total += *start;    //把数组元素的值加起来
        start++;    //让指针指向下一个元素
    }
    return total;
}
输出结果为:
The total number of marbles is 190 .


程序解析


指针start开始指向marbles数组的首元素,所以赋值表达式total += start把首元素(20)加给total。然后,表达式start++递增指针变量start,使其指向数组的下一个元素。因为start是指向int的指针,start 递增1相当于其值递增int类型的大小。


注意,sump ()函数用另一种方法结束加法循环。sum()函数把元素的个数作为第2个参数,并把该参数作为循环测试的一部分:


for(i=0;i<n;i++)


而sump()函数则使用第2个指针来结束循环:


while (start < end)


因为while循环的测试条件是-一个不相等的关系,所以循环最后处理的一个元素是end所指向位置的前一个元素。这意味着end指向的位置实际上在数组最后一个元素的后面。C保证在给数组分配空间时,指向数组后面第一个位置的指针仍是有效的指针。这使得while 循环的测试条件是有效的,因为start在循环中最后的值是end。注意,使用这种“越界”指针的函数调用更为简洁:


answer = sump (marbles,marbles + SIZE) ;


因为下标从0开始,所以marbles + SIZE 指向数组末尾的下一个位置。如果end指向数组的最后一个元素而不是数组末尾的下一个位置,则必须使用下面的代码:


这种不推荐


还可以把循环体压缩成一行代码


total += *start++;


一元运 算符和+ +的优先级相同,但结合律是从右往左,**所以start++先求值,然后才是start。也就是说,指针start先递增后指向。**使用后缀形式(即start++而不是++start)意味着先把指针指向位置上的值加到total上,然后再递增指针。


**使用++start,顺序则反过来,先递增指针,再使用指针指向位置上的值。如果使用(start)++,则先使用start指向的值,再递增该值,而不是递增指针。**这样,指针将一直指向同一个位置,但是该位置上的值发生了变化。虽然*start++的写法比较常用,但是


*(start++)这样写更清楚。


指针的自增


#include <stdio.h>
int data[2] = {100,200};
int moredata[2] = {300,400};
int main(void)
{
    int *p1,*p2,*p3;//定义三个指针
    p1 = p2 = data; //指针p1等于指针p2等于数组data首元素
    p3 = moredata; //指针p3等于数组moredata首元素
    printf("*p1 = %d, *p2 = %d,*p3 = %d\n",*p1,*p2,*p3);//打印三个指针的值
    printf("*p1++ = %d, *++p2 = %d, (*p3)++ = %d\n",*p1++,*++p2,(*p3)++);
    printf("*p1 = %d , *p2 = %d, *p3 = %d\n",*p1,*p2,*p3);
    return 0;
}



运算结果为:


*p1 = 100, *p2 = 100,*p3 = 300
*p1++ = 100, *++p2 = 200, (*p3)++ = 300
*p1 = 200 , *p2 = 200, *p3 = 301


解析


只有(*p3)++ 改变了元素的值


其他两个操作分别把p1和p2指向数组的下一个元素


我的小理解:


和a++,++a意思一样,(a)++先使用a的值,再递增该值,而不是递增指针(相当于变成值++了),a++和++a都是对指针进行递增,指向数组下一个元素


六、指针表示法和数组表示



从以上分析可知,处理数组的函数实际上用指针作为参数,但是在编写这样的函数时,可以选择是使用数组表示法还是指针表示法。


至于C语言,ar [ i]和* (ar+1)这两个表达式都是等价的。无论 ar 是数组名还是指针变量,这两个表达式都没问题。但是,只有当ar是指针变量时,才能使用ar++这样的表达式。


指针表示法(尤其与递增运算符一起使用时)更接近机器语言,因此一些编译器在编译时能生成效率更高的代码。然而,许多程序员认为他们的主要任务是确保代码正确、逻辑清晰,而代码优化应该留给编译器去做。


哈哈哈,这本书写的还挺有意思呢!


七、指针操作



总结一下指针的常见操作


实例


#include <stdio.h>
int main(void)
{
    int urn[5] = {100, 200, 300, 400, 500}; //定义一个数组并赋值
    int *ptr1, *ptr2, *ptr3;                //定义三个指针
    ptr1 = urn;     //把数组首元素的地址赋给指针
    ptr2 = &urn[2]; //把数组第2个元素地址赋给指针
    printf("pointer value,dereferenced pointer,pointer address:\n");
    printf("ptr1 = %p,*ptr1 = %d,&ptr1 = %p\n", ptr1, *ptr1, &ptr1); //两个地址,一个值
    //指针的加法
    ptr3 = ptr1 + 4; //urn数组中第4个元素的地址
    printf("\nadding an int to a pointer:\n");
    printf("ptr1 + 4 = %p, *(ptr + 4) = %d\n", ptr1 + 4, *(ptr1 + 4));
    ptr1++; //递增指针
    printf("\nvalues after ptr1++:\n");
    printf("ptr1 = %p,*ptr1 = %d,&ptr1 = %p\n", ptr1, *ptr1, &ptr1);
    ptr2--; //递减指针
    printf("\nvalues after ptr2--:\n");
    printf("ptr2 = %p,*ptr2 = %d,&ptr2 = %p\n", ptr2, *ptr2, &ptr2);
    --ptr1; //恢复为初始值
    ++ptr2; //恢复为初始值
    printf("\nPointers reset to original values:\n");
    printf("ptr1 = %p,ptr2 = %p\n", ptr1, ptr2);
    //一个指针减去另一个指针
    printf("\nsubtracting one pointer from another:\n");
    printf("ptr2 = %p,ptr1 = %p,ptr2 - ptr1 = %td\n", ptr2, ptr1, ptr2 - ptr1);
    //一个指针减去一个整数
    printf("\nsubtracting an int from a poniter:\n");
    printf("ptr3 = %p,ptr3 - 2 = %p\n", ptr3, ptr3 - 2);
    return 0;
}
输出的结果为:
PS D:\Code\C\指针> cd "d:\Code\C\指针\" ; if ($?) { gcc 指针Demo05.c -o 指针Demo05 } ; if ($?) { .\指针Demo05 }
pointer value,dereferenced pointer,pointer address:
ptr1 = 000000000061FE00,*ptr1 = 100,&ptr1 = 000000000061FDF8
adding an int to a pointer:
ptr1 + 4 = 000000000061FE10, *(ptr + 4) = 500
values after ptr1++:
ptr1 = 000000000061FE04,*ptr1 = 200,&ptr1 = 000000000061FDF8
values after ptr2--:
ptr2 = 000000000061FE04,*ptr2 = 200,&ptr2 = 000000000061FDF0
Pointers reset to original values:
ptr1 = 000000000061FE00,ptr2 = 000000000061FE08
subtracting one pointer from another:
ptr2 = 000000000061FE08,ptr1 = 000000000061FE00,ptr2 - ptr1 = 2
subtracting an int from a poniter:
ptr3 = 000000000061FE10,ptr3 - 2 = 000000000061FE08


解析


赋值:可以把地址赋给指针。例如,用数组名、带地址运算符(&)的变量名、另一个指针进行赋值。在该例中,把urn数组的首地址赋给了ptr1,该地址的编号恰好是000000000061FE00。变量ptr2获得数组urn 的第3个元素(urn[2])的地址。注意,地址应该和指针类型兼容。也就是说,不能把 double类型的地址赋给指向int的指针,至少要避免不明智的类型转换。C99/C11已经强制不允许这样做。


解引用: 运算符给出指针指向地址上储存的值。因此,ptr1的初值是100,该值储存在编号为0x7fff5fbff8d0的地址上。


取址:和所有变量一样,指针变量也有自己的地址和值。对指针而言,s运算符给出指针本身的地址。本例中,ptr1储存在内存编号为000000000061FDF8 的地址上,该存储单元储存的内容是000000000061FE00,即urn的地址。因此&ptr1是指向ptrl的指针,而ptr1是指向utn[0]的指针。


指针与整数相加:可以使用+运算符把指针与整数相加,或整数与指针相加。无论哪种情况,整数都会和指针所指向类型的大小(以字节为单位)相乘,然后把结果与初始地址相加。因此ptrl +4与&urn [ 4]等价。如果相加的结果超出了初始指针指向的数组范围,计算结果则是未定义的。除非正好超过数组末尾第一个位置,C保证该指针有效。


递增指针:递增指向数组元素的指针可以让该指针移动至数组的下一个元素。因此,ptr1++相当于把ptr1的值加上4(我们的系统中int为4字节),ptr1指向urn[1]。现在ptr1的值是Ox7fff5fbff8d4(数组的下一个元素的地址),*ptr的值为200(即urn[ 1]的值)。注意,ptr1本身的地址仍是0x7fff5fbff8c8。毕竟,变量不会因为值发生变化就移动位置。


**指针减去一个整数:**可以使用-运算符从一个指针中减去一个整数。指针必须是第Ⅰ个运算对象,整数是第⒉个运算对象。该整数将乘以指针指向类型的大小(以字节为单位),然后用初始地址减去乘积。所以ptr3 - 2与&urn [2]等价,因为ptr3指向的是&arn[4]。如果相减的结果超出了初始指针所指向数组的范围,计算结果则是未定义的。除非正好超过数组末尾第一个位置,C保证该指针有效。


**递减指针:**当然,除了递增指针还可以递减指针。在本例中,递减ptr3使其指向数组的第2个元素而不是第3个元素。前缀或后缀的递增和递减运算符都可以使用。注意,在重置ptr1和ptr2前,它们都指向相同的元素urn [ 1]。


**指针求差:**可以计算两个指针的差值。通常,求差的两个指针分别指向同一个数组的不同元素,通过计算求出两元素之间的距离。差值的单位与数组类型的单位相同。例如,**ptr2 - ptr1得2,意思是这两个指针所指向的两个元素相隔两个int,而不是2字节。**只要两个指针都指向相同的数组(或者其中一个指针指向数组后面的第1个地址),C都能保证相减运算有效。如果指向两个不同数组的指针进行求差运算可能会得出一个值,或者导致运行时错误。


**比较:**使用关系运算符可以比较两个指针的值,前提是两个指针都指向相同类型的对象。


小知识点

解引用未初始化的指针

说到注意事项,一定要牢记一点:千万不要解引用未初始化的指针。例如,考虑下面的例子:


int * pt;  //未初始化的指针
*pt = 5;  //严重的错误


为何不行?第2行的意思是把5储存在pt 指向的位置。但是pt未被初始化,其值是一个随机值,所以不知道5将储存在何处。这可能不会出什么错,也可能会擦写数据或代码,或者导致程序崩溃.切记:创建一个指针时,系统只分配了储存指针本身的内存,并未分配储存数据的内存。因此,在使用指针之前,必须先用已分配的地址初始化它。例如,可以用一个现有变量的地址初始化该指针(使用带指针形参的函数时,就属于这种情况)。或者还可以使用第12章将介绍的malloc()函数先分配内存。无论如何,使用指针时一定要注意,不要解引用未初始化的指针!


相关文章
|
4月前
|
编译器 vr&ar C语言
C primer plus 学习笔记 第10章 数组和指针
C primer plus 学习笔记 第10章 数组和指针
|
5月前
|
C++
来自C++ Primer 5的函数指针的定义,调用等
来自C++ Primer 5的函数指针的定义,调用等
30 0
指向结构指针(C Primer Plus 第六版)
指向结构指针(C Primer Plus 第六版)
69 0
指针和多维数组的关系(C Primer Plus第六版)
指针和多维数组的关系(C Primer Plus第六版)
57 0
|
C语言
指针常用的操作(C Primer Plus第六版)
指针常用的操作(C Primer Plus第六版)
95 0
|
索引
指针如何使用形参?(C Primer Plus第六版)
指针如何使用形参?(C Primer Plus第六版)
72 0
|
4月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)
|
26天前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
|
2月前
|
C语言
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
|
2月前
|
C语言
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
下一篇
无影云桌面