system("Pause") return O; 在作者的计算机上的输出是 0023FF740023FF78 这个结果可能因为运行环境(编译器及计算机)的改变而有所不同。但有一点是确定的,那就是 输出的"8i+1"的值在数值上比"&i”的值大"sizeof(int)”。这表明一个数据指针加1的含义是得 到另一个同样类型的指针,这个指针刚好指向内存中后一个同类型的量。 对更一般的数据类型T,指向T类型的指针加1的含义是,得到指向内存中紧邻的后一个T类 型量的指针,在数值上相当于加了sizeof(T。如图9-9所示。 T*p: f(T) 图9-9数据指针+1的含义 加1的含义清楚了之后,加上其他整数的含义不难推之,减1的含义也就是得到指向内存中紧 邻的前一个同类型量的指针。然而道理上虽然可以这样理解,但实际上C语言对指针加上或诚 去一个整数是有严格限制的。比如对于 int is &+1"是有意义的运算,因为&1+1"恰好指向后面第一个“nt"类型的数据,但&1+2” 是没有意义的,除非确信&i+2"确实指向了一个it类型数据。只有在数组内部才可能确信 如此。此外,尽管8i+1"是有意义的运算,但是*(&1+1)“并没有意义。 同理,除非是在数组内部,在确认一个指针减1确实指向某个数据对象的前提下,否则指针减1 的运算是没有意义的。 这里,存在着指针加减法”不对称”的现象。对于一个数据对象(如前面的”),“&+1“是有意 义的,而”8-1”是没有定义的。也戴是说,除非通过运算得到的指针的值为0或者指向一个确 实的数据对象,或者指向紧邻某个数据对象之后的一个“虚拟“的同类型的数据对象,否则这个指
system("Pause"); return 0; } 在作者的计算机上的输出是 0023FF74 0023FF78 这个结果可能因为运行环境(编译器及计算机)的改变而有所不同。但有一点是确定的,那就是 输出的“&i+1”的值在数值上比“&i”的值大“sizeof(int)”。这表明一个数据指针加 1 的含义是得 到另一个同样类型的指针,这个指针刚好指向内存中后一个同类型的量。 对更一般的数据类型 T,指向 T 类型的指针加 1 的含义是,得到指向内存中紧邻的后一个 T 类 型量的指针,在数值上相当于加了 sizeof(T)。如图 9-9 所示。 图 9-9 数据指针+1 的含义 加 1 的含义清楚了之后,加上其他整数的含义不难推之,减 1 的含义也就是得到指向内存中紧 邻的前一个同类型量的指针。然而道理上虽然可以这样理解,但实际上 C 语言对指针加上或减 去一个整数是有严格限制的。比如对于 int i; “& i+ 1”是有意义的运算,因为“& i + 1”恰好指向“i”后面第一个“int”类型的数据,但“& i + 2” 是没有意义的,除非确信“& i + 2”确实指向了一个“int”类型数据。只有在数组内部才可能确信 如此。此外,尽管“& i + 1”是有意义的运算,但是“*(& i + 1)”并没有意义。 同理,除非是在数组内部,在确认一个指针减 1 确实指向某个数据对象的前提下,否则指针减 1 的运算是没有意义的。 这里,存在着指针加减法“不对称”的现象。对于一个数据对象(如前面的“i”),“&i+1”是有意 义的,而“&i-1”是没有定义的。也就是说,除非通过运算得到的指针的值为 0 或者指向一个确 实的数据对象,或者指向紧邻某个数据对象之后的一个“虚拟”的同类型的数据对象,否则这个指
针是没有意义的,其行为是未定义的。 例题:编写函数,求一个一维"it"数组中元素的最大值。 假设这个数组的数组名为a”,共"n”个元素,那么显然"&[0]“是指向这个数组起始元素的指针 而且&a[0]+1”、"&a[0]+2”.显然依次指向a[1]、a[2]。这样只要把"&a[0]和"n作 为实参传递给函数,函数就可以完成对数组的遍历。"&a[0]“和"n”的类型分别为"it*“和 “unsigned”,求得的最大值为函数返回值,因此函数原型为 int qiuzd (int*,unsigned ) 完整的代码如下。 程序代码9-11 #include<stdio.h> #include <stdlib.h> int qiuzd int *unsigned ) int main(void inta[3]={5,9,7;/测试数据 printf%d小n",qiuzd(&a[o],sizeof a/sizeof*a):/测试 system("PAUSE"); return O; int qiuzd (int*p,unsigned n) int i,zd =p; for(i=0 ;i<n;i++) f(*(p+i)>2d】 zd=*(p+i): return zd;
针是没有意义的,其行为是未定义的。 例题:编写函数,求一个一维“int”数组中元素的最大值。 假设这个数组的数组名为“a”,共“n”个元素。那么显然“&a[0]”是指向这个数组起始元素的指针, 而且“&a[0]+1”、“&a[0]+2”.显然依次指向 a[1]、a[2].。这样只要把“&a[0]”和“n”作 为实参传递给函数,函数就可以完成对数组的遍历。“&a[0]”和“n”的类型分别为“int *”和 “unsigned”,求得的最大值为函数返回值,因此函数原型为 int qiuzd ( int * , unsigned ) ; 完整的代码如下。 程序代码 9-11 #include <stdio.h> #include <stdlib.h> int qiuzd ( int * , unsigned ) ; int main( void ) { int a[3]={ 5 , 9 , 7 }; //测试数据 printf("%d\n", qiuzd ( &a[0] , sizeof a / sizeof *a) ); //测试 system("PAUSE"); return 0; } int qiuzd ( int *p , unsigned n) { int i , zd = * p; for ( i = 0 ; i < n ; i ++ ) { if( * ( p + i ) > zd ) { zd = * ( p + i ) ; } } return zd ; }
练习1.编写函数,求一个一维"it"数组中各元素之和。 2.如果把3.3分桔子问题中每人的桔子数目1sm,l2sm,l3sm,l4sm,15sm,l6sm用 一个数组表示,那么无论是”求最后每个人的桔子数"“还是”逐步前推"的过程都可以用循环描述, 代码将更为简洁。请自行完成之。 4.2数据指针的减法 两个同类型的数据指针可以做减法,而且它们应该是指向同一个数组的数组元素,或者是指向 这个数组最后一个元素的下一个同类型的量。这个运算是指针与整数加减法的逆运算。所得到的 结果是两个指针之间有几个这样类型的量,也就是它们所指向的数组元素的下标的差,结果的正 负号表示两个指针的前后关系。 请说出下面程序的运行结果,然后再自己运行程序验证一下。 程序代码9-12 #include <stdio.h> #include <stdlib.h> int main(void) char c[10] printf("%d%d",&c[2]小-&c[9],&c[10]-&c[7]): system("Pause"); return 0; } 注意,这里出现了一个c[10]子表达式,但由于代码中并不涉及对c[10]的读写,只是求出指向 这个char的指针,这个指针恰恰是c数组之后第一个指向char的指针,这在C代码中没有任 何问题,不属于越界访问。 4.3数据指针的关系运算 两个指针做”<“、”<=”、>”、“>="这些关系运算的前提,与两个指针做减法的前提类似。最 后的结果要么是0、要么是1,含义是两个指针在内存中哪个在前、哪个在后,或者是哪个不在 另一个之前、哪个不在另一个之后。 两个不同类型的指针的比较及其规则或潜规则,基本上是个钻牛角尖的问题。如果有这个爱好及 精力,请独立钻研C89/C99标准关于兼容性(Compatible Type)方面的阐述。事实上,在 真正写代码的时候,正如记不清楚运算优先级可以加括号避开优先级问愿、不同的类型之间的赋 值可以通过类型转换避开转换规则一样,如果一定要在不同类型的指针之间进行关系运算,也完
练习 1.编写函数,求一个一维“int”数组中各元素之和。 2.如果把 3.3 分桔子问题中每人的桔子数目 l1sm,l2sm,l3sm,l4sm,l5sm,l6sm 用 一个数组表示,那么无论是“求最后每个人的桔子数”还是“逐步前推”的过程都可以用循环描述, 代码将更为简洁。请自行完成之。 4.2 数据指针的减法 两个同类型的数据指针可以做减法 ,而且它们应该 是指向同一个数组的数组元素,或者是指向 这个数组最后一个元素的下一个同类型的量。这个运算是指针与整数加减法的逆运算。所得到的 结果是两个指针之间有几个这样类型的量,也就是它们所指向的数组元素的下标的差,结果的正 负号表示两个指针的前后关系。 请说出下面程序的运行结果,然后再自己运行程序验证一下。 程序代码 9-12 #include <stdio.h> #include <stdlib.h> int main(void) { char c[10]; printf("%d %d",&c[2]-&c[9],&c[10]-&c[7]); system("Pause"); return 0; } 注意,这里出现了一个 c[10]子表达式,但由于代码中并不涉及对 c[10]的读写,只是求出指向 这个 char 的指针,这个指针恰恰是 c 数组之后第一个指向 char 的指针,这在 C 代码中没有任 何问题,不属于越界访问。 4.3 数据指针的关系运算 两个指针做“<”、“<=”、“>”、“>=”这些关系运算的前提,与两个指针做减法的前提类似。最 后的结果要么是 0、要么是 1,含义是两个指针在内存中哪个在前、哪个在后,或者是哪个不在 另一个之前、哪个不在另一个之后。 两个不同类型的指针的比较及其规则或潜规则,基本上是个钻牛角尖的问题。如果有这个爱好及 精力,请独立钻研 C89/C99 标准关于兼容性(Compatible Type)方面的阐述。事实上,在 真正写代码的时候,正如记不清楚运算优先级可以加括号避开优先级问题、不同的类型之间的赋 值可以通过类型转换避开转换规则一样,如果一定要在不同类型的指针之间进行关系运算,也完
全可以通过类型转换避开令人烦恼的兼容性问题。毕竞,程序要解决的问题才是最重要的问题。 4.4数据指针的判等运算 两个相同类型的数据指针做”==“或”="”这两个等式运算的含义十分明显,无非是它们所指向的 数据是否为同一个。 两个指针可以进行”==”、“!=“”运算对操作数所要求的前提条件比做关系运算对操作数所要求的 前提条件更为宽泛,具体的规则在后面将详细介绍。 4.5"[门"运算 和多数运算符不同,下标运算(Subscripting Operator)"[门"的含义实际上是由另一个运算定 义的。C语言规定下面两个表达式 表达式1[表达式2]与(*((表达式1)+(表达式2))) 是完全等价的。 这可能多少令人出乎意科,但事实的确如此。进一步想下去的推论可能更加令人惊奇:比如,由 于+具有可交换性,如果 表达式1[表达式2】与 (*((表达式1)+(表达式2)))完全等价,那么是否可以说"Ex1[Ex2]”与Ex2[Ex1] 也完全等价呢? 的确如此。请看一下下面的代码。 程序代码9-13 #include <stdio.h> #include<stdlib.h≥ int main(void) t inti[1]={(7: printf("i[o]=%d \no[i]=%d\n",i [o],O[]) system("Pause") return O; } 它运行的结果会输出 i[0=7 00=7 请按任意键继续。·
全可以通过类型转换避开令人烦恼的兼容性问题。毕竟,程序要解决的问题才是最重要的问题。 4.4 数据指针的判等运算 两个相同类型的数据指针做“==”或“!=”这两个等式运算的含义十分明显,无非是它们所指向的 数据是否为同一个。 两个指针可以进行“==”、“!=”运算对操作数所要求的前提条件比做关系运算对操作数所要求的 前提条件更为宽泛,具体的规则在后面将详细介绍。 4.5 “[]”运算 和多数运算符不同,下标运算(Subscripting Operator)“[]”的含义实际上是由另一个运算定 义的。C 语言规定下面两个表达式 表达式 1[表达式 2] 与 ( * ( (表达式 1) +(表达式 2 ) ) ) 是完全等价的。 这可能多少令人出乎意料,但事实的确如此。进一步想下去的推论可能更加令人惊奇:比如,由 于+具有可交换性,如果 表达式 1[表达式 2] 与 ( * ((表达式 1)+(表达式 2 )))完全等价,那么是否可以说“Ex1[Ex2]”与“Ex2[Ex1]” 也完全等价呢? 的确如此。请看一下下面的代码。 程序代码 9-13 #include <stdio.h> #include <stdlib.h> int main(void) { int i[1]={7}; printf("i[0]=%d \n0[i]= %d\n", i [0] , 0[i] ); system("Pause"); return 0; } 它运行的结果会输出: i[0]=7 0[i]= 7 请按任意键继续. .
而且没有任何语法问题,你相信吗?如果你不相信,自己运行一下程序好了。 结论是,"[0]”与0[叮“这两个表达式是完全等价的,它们都等价于”(*()+(O)》严,也就是 “*(+0)“。如果理解这一点没有什么问题,说明你对数据指针的理解已经很有深度了。 测验:以上面的代码为背景,表达式“(+1)[-1】*(~1)[+1]”的值是多少?请在一分钟之内给 出答案并上机验证。 此外我要郑重声明,“(+1)[-1]+(~1)[+1刂”这种显得有几分诡异的表达式,只是为了测验你 对指针概念的掌握和理解,在源程序中如果没有特别正当的理由,还是写堂堂正正、平易近人的 代码为好。 如果你顺利地阅读到了这里,表明你对数据指针的概念非常清晰。指针这个令很多人感到头疼的 东西,对你来说只会感到轻松愉快。甚至,下一小节的内容,你可能现在已经懂了。 4.6数组名是指针 任意定义一个一维数组,比如: double d[6]=(); 从C语言数组的理论中可以知道,"[O]“是这个数组的第一个元素,面且这个元素的类型是 double类型。 从上一小节中可以得知,"d[0]这个表达式等价于”(*()+(0)》”,也就是等价于"*d”.而* 作为一元运算符时,它的运算对象是指针。那么数组名”"除了是指针还能是什么呢? 显然,“d”是一个"d0uble*”类型的指针,而且是指向这个数组起始元素的指针。这个结论非常 重要,理解了这一点,指针部分就几乎不存在什么难点了。当然,这里所谓的”理解”是要能够自 然而然地根据指针的概念自己得到这个结论,而不是死记硬背。如果理解这一点很吃力,请暂时 不要继续读后面的内容,重读几遍前面的内容。 既然d”是doub1e*"类型的指针,那么显然可以把它的值赋给一个同类型的指针变量。假设有: double*p; 那么显然可以: p=d; 而且既然"p"与"d"类型相同,值也相同,而d[O]"或"*d”是这个数组的起始元素,那么"p[O]” 或”*p”显然也是同一个数据对象。 那么“p"与"d"的区别何在呢?答案是:"d"是个常量。这从"d"的意义就可以推知。 由于“"是指向"d"数组的起始元素的指针,而”d"数组的存储空间是编译器而不是代码编写者负 责安排的,那么这意味着代码书写者也不可能通过代码确定或改变起始元素在内存中的位置。这 样,对于代码书写者来说,“d”就是一个不可以改变的量,也就是“常量”。 而p”的值是可以改变的,它可以被赋值为”,可以被赋值为其他的值,也可以进行”++”、”一
而且没有任何语法问题,你相信吗?如果你不相信,自己运行一下程序好了。 结论是,“i[0]”与“0[i]”这两个表达式是完全等价的,它们都等价于“(*((i)+(0)))”,也就是 “*(i+0)”。如果理解这一点没有什么问题,说明你对数据指针的理解已经很有深度了。 测验:以上面的代码为背景,表达式“(i+1)[-1] * (-1)[i+1]”的值是多少?请在一分钟之内给 出答案并上机验证。 此外我要郑重声明,“(i+1)[-1] + (-1)[i+1]”这种显得有几分诡异的表达式,只是为了测验你 对指针概念的掌握和理解,在源程序中如果没有特别正当的理由,还是写堂堂正正、平易近人的 代码为好。 如果你顺利地阅读到了这里,表明你对数据指针的概念非常清晰。指针这个令很多人感到头疼的 东西,对你来说只会感到轻松愉快。甚至,下一小节的内容,你可能现在已经懂了。 4.6 数组名是指针 任意定义一个一维数组,比如: double d[6]={2}; 从 C 语言数组的理论中可以知道,“d[0]”是这个数组的第一个元素,而且这个元素的类型是 double 类型。 从上一小节中可以得知,“d[0]”这个表达式等价于“(*((d)+(0)))”,也就是等价于“*d”。而“*” 作为一元运算符时,它的运算对象是指针。那么数组名“d”除了是指针还能是什么呢? 显然,“d”是一个“double *”类型的指针,而且是指向这个数组起始元素的指针。这个结论非常 重要,理解了这一点,指针部分就几乎不存在什么难点了。当然,这里所谓的“理解”是要能够自 然而然地根据指针的概念自己得到这个结论,而不是死记硬背。如果理解这一点很吃力,请暂时 不要继续读后面的内容,重读几遍前面的内容。 既然“d”是“double *”类型的指针,那么显然可以把它的值赋给一个同类型的指针变量。假设有: double *p; 那么显然可以: p = d ; 而且既然“p”与“d”类型相同,值也相同,而“d[0]”或“*d”是这个数组的起始元素,那么“p[0]” 或“*p”显然也是同一个数据对象。 那么“p”与“d”的区别何在呢?答案是:“d”是个常量。这从“d”的意义就可以推知。 由于“d”是指向“d”数组的起始元素的指针,而“d”数组的存储空间是编译器而不是代码编写者负 责安排的,那么这意味着代码书写者也不可能通过代码确定或改变起始元素在内存中的位置。这 样,对于代码书写者来说,“d”就是一个不可以改变的量,也就是“常量”。 而“p”的值是可以改变的,它可以被赋值为“d”,可以被赋值为其他的值,也可以进行“++”、“−