7.1间接访问NULL指针会发生什么情况? 答:标准未定义。某些编译器可能访问内存0地址,其结果不可预测,某些机器可能引 发一个错误,并终止程序 7.2说明下面两个“*”运算符的区别 int *p=&a n 答:前者将p定义为一个整型指针;后者访问p所指向的对象 7.3下面的说法是否正确?为什么? (1)如果x=y则&x=&y (2)如果x=y则*x==*y 答:(1)不正确。不同的变量其值可以相同,但必须有不同的地址。 (2)显然,如果结论成立,则x和y应为指针 7.4何谓“空悬指针”?对空悬指针间接访问会带来什么后果?如何避免这种后果? 答:(1)空悬指针就是一个未初始化的指针,它可能指向未初始化的空间或不可访问的 空间 (2)对空悬指针间接访问,要么更新不可知变量的值,要么导致程序崩溃 (3)初始化它 7.5为什么说指针的算术运算只有作用于数组中其结果才是可预测的? 答:比如说指针p指向数组的第i个元素,则一般说来,p++,p,p+n,p-n,仍指向 数组中的某个元素,因而是可预测的。但如果p指向任何其它目标,则p++等运算都无法预 测p指向了谁。所以说,对任何非指向数组元素的指针执行算术运算是毫无意义的(编译器 不一定会捕获这个错误)。 7.6下面的代码段是否有问题?如果有的话,问题在哪里?可能会产生什么后果? int array [SIZE] int *ptr for (nPtr &array [0]: nPtr<&array [SIze] **+tnPtr =0 答:有两个错误。其一,如果该循环语句的目的是将数组所有元素清0的话,那么数组 的第一个元素并没有清零。第二个错误比较复杂,分两种情况 如果指针nPtr所在空间与数组空间不连续,指针越过数组右边界后仍进行间接访问, 它将把紧邻数组右边界的长度为 sizeof(int)的内存空间的内容清零。其后果不可预知 如果编译器恰好把nPr分配在紧邻数组右边界的内存位置,结果将是灾难性的。因为 当指针移到数组后面的那个内存位置时,那个最后被清零的内存位置就是指针本身所在的位 置。可见此时指针的值为零,条件nPr<& arrayISIZE]仍然满足,所以循环将继续,指针 在内存中欢快地前行,破坏它所遇见的一切。当它再一次到达这个数组的位置时,恶梦重现, 从而导致一个微妙的无限循环。 7.7下面的代码段是否有问题?为什么? float array [10] float *fPtr &array [o] --fPtr *fPtr=3.14; 答: ANSIC标准规定,一个指向数组的指针经运算后指向了数组第一个元素之前,那么 它是非法的。一fPtr的结果正是如此,所以它是非法的。尽管某些系统在这种情况下仍然 能运行,但 ANSIC认为这种行为是未定义,因此其结果也是不可知的,更何况上述代码已经
1 7.1 间接访问 NULL 指针会发生什么情况? 答:标准未定义。某些编译器可能访问内存 O 地址,其结果不可预测,某些机器可能引 发一个错误,并终止程序。 7.2 说明下面两个“*”运算符的区别。 int *p = &a; n = *p; 答:前者将 p 定义为一个整型指针;后者访问 p 所指向的对象。 7.3 下面的说法是否正确?为什么? (1)如果 x == y 则&x == &y (2)如果 x == y 则*x == *y 答:(1)不正确。不同的变量其值可以相同,但必须有不同的地址。 (2)显然,如果结论成立,则 x 和 y 应为指针。 7.4 何谓“空悬指针”?对空悬指针间接访问会带来什么后果?如何避免这种后果? 答:(1)空悬指针就是一个未初始化的指针,它可能指向未初始化的空间或不可访问的 空间。 (2)对空悬指针间接访问,要么更新不可知变量的值,要么导致程序崩溃。 (3)初始化它。 7.5 为什么说指针的算术运算只有作用于数组中其结果才是可预测的? 答:比如说指针 p 指向数组的第 i 个元素,则一般说来,p++,p--,p+n,p-n,仍指向 数组中的某个元素,因而是可预测的。但如果 p 指向任何其它目标,则 p++等运算都无法预 测 p 指向了谁。所以说,对任何非指向数组元素的指针执行算术运算是毫无意义的(编译器 不一定会捕获这个错误)。 7.6 下面的代码段是否有问题?如果有的话,问题在哪里?可能会产生什么后果? int array[SIZE]; int *Ptr; for(nPtr = &array[0];nPtr<&array[SIZE];) *++nPtr = 0; 答:有两个错误。其一,如果该循环语句的目的是将数组所有元素清 0 的话,那么数组 的第一个元素并没有清零。第二个错误比较复杂,分两种情况: 如果指针 nPtr 所在空间与数组空间不连续,指针越过数组右边界后仍进行间接访问, 它将把紧邻数组右边界的长度为 sizeof(int)的内存空间的内容清零。其后果不可预知。 如果编译器恰好把 nPtr 分配在紧邻数组右边界的内存位置,结果将是灾难性的。因为 当指针移到数组后面的那个内存位置时,那个最后被清零的内存位置就是指针本身所在的位 置。可见此时指针的值为零,条件 nPtr < &array[SIZE] 仍然满足,所以循环将继续,指针 在内存中欢快地前行,破坏它所遇见的一切。当它再一次到达这个数组的位置时,恶梦重现, 从而导致一个微妙的无限循环。 7.7 下面的代码段是否有问题?为什么? float array[10]; float *fPtr = &array[0]; --fPtr; *fPtr = 3.14; 答:ANSIC 标准规定,一个指向数组的指针经运算后指向了数组第一个元素之前,那么 它是非法的。--fPtr 的结果正是如此,所以它是非法的。尽管某些系统在这种情况下仍然 能运行,但 ANSIC 认为这种行为是未定义,因此其结果也是不可知的,更何况上述代码已经
更新了指针所指向的未知空间的内容,即使程序能得到结果,该结果也是不可信赖的。 7.8下面的代码段有什么错误? int i, a[100] (i=0;i<100;i++) 答:数组名是一个指针常量,不能施加“+”运算 7.9下面的代码有什么错误? float x = 3.14 float *fp = &x: short k 36 short *sp 答:错误出在最后一个赋值表达式语句:不同类型的指针不能赋值,即fp不能指向 short 型对象。 7.10对于数组a和int型变量i,下面的条件是否成立?为什么? (a+i)=i[a] alil=i[al 答:条件成立。首先我们来分析表达式a[i]。表达式a[i]的值是通过下标运算符返 回的。a[]作为左值表达式时,下标运算计算表达式a+i,返回一个地址值(即数组第i个 元素的地址):a[i]作为右值表达式时,下标运算计算表达式a+i得到一个地址值,然后返 回该地址空间的内容的一个副本。设数组a的元素类型为T,则无论a[i]是作为左值还是右 值,下标运算都是将a+i转化为a+i* sizeof(T)。 至于表达式*(a+i),按指针运算法则,*(a+i)即*(a+i* sizeof(T),显然,它无论是作 为左值还是右值,与a[i]都是等价的 表达式i[a]转换为等价的间接访问表达式是*(i+[a]),显然,里面的下标运算符是冗 余的,于是有*(i+a)。 7.11下面表达式合法吗?为什么? (int*)200=10 答:由于强制类型转换把值200从“int”型转换为“指向int型的指针”,再对它间接 访问当然是合法的。但阁下什么时候需要这样做呢? 7.12如果pl和p2都是指向int型的指针,n为int型数,则下列表达式中哪些是非法 的? (1)pl+p2 (2)pl-p2 (3) pI+n (4)p1-n (5)n+p1l (6)n-p2 答:pl+p2和np2是非法的 7.13下列语句中,哪些是非法的?哪些是合法的?为什么? (2) void *p (3) int num =0
2 更新了指针所指向的未知空间的内容,即使程序能得到结果,该结果也是不可信赖的。 7.8 下面的代码段有什么错误? int i,a[100]; for(i = 0;i<100;i++) *a++ = 0; 答:数组名是一个指针常量,不能施加“++”运算。 7.9 下面的代码有什么错误? float x = 3.14; float *fp = &x; short k = 36; short *sp = &k; fp = sp; 答:错误出在最后一个赋值表达式语句:不同类型的指针不能赋值,即 fp 不能指向 short 型对象。 7.10 对于数组 a 和 int 型变量 i,下面的条件是否成立?为什么? a[i] == *(a+i); *(a+i) == i[a]; a[i] == i[a]; 答:条件成立。首先我们来分析表达式 a[i]。表达式 a[i]的值是通过下标运算符[]返 回的。a[i]作为左值表达式时,下标运算计算表达式 a+i,返回一个地址值(即数组第 i 个 元素的地址);a[i]作为右值表达式时,下标运算计算表达式 a+ i 得到一个地址值,然后返 回该地址空间的内容的一个副本。设数组 a 的元素类型为 T,则无论 a[i]是作为左值还是右 值,下标运算都是将 a+i 转化为 a+i*sizeof(T)。 至于表达式*(a+i),按指针运算法则,*(a+i)即*(a+i*sizeof(T)),显然,它无论是作 为左值还是右值,与 a[i]都是等价的。 表达式 i[a]转换为等价的间接访问表达式是*(i+[a]),显然,里面的下标运算符是冗 余的,于是有*(i+a)。 7.11 下面表达式合法吗?为什么? *(int*)200 = 10; 答:由于强制类型转换把值 200 从“int”型转换为“指向 int 型的指针”,再对它间接 访问当然是合法的。但阁下什么时候需要这样做呢? 7.12 如果 p1 和 p2 都是指向 int 型的指针,n 为 int 型数,则下列表达式中哪些是非法 的? (1)p1+p2 (2)p1-p2 (3)p1+n (4)p1-n (5)n+p1 (6)n-p2 答:p1+p2 和 n-p2 是非法的。 7.13 下列语句中,哪些是非法的?哪些是合法的?为什么? (1)void a; (2)void *p; (3)int num = 0,*p1 = 0;
d * p2= pl (4) int num =0: id p=&num print("%d\n", *((int*)p)) 答:(1)非法。任何非指针量不可以具有void类型。 (2)合法。一个指针可以具有void类型 (3)合法。void类型指针可以指向任何类型 (4)合法。不能直接访问void类型指针所指的目标,但可以通过强制类型转换来 实现访问。 7.14分别解释下列语句的语义: (1) const int (2) int const (3)int *const p (4) const int *const p 答:(1)p是一个指向int型常量的指针,p可以指向不同的对象,但p所指向的对象 即*p的值不可改变。 (2)与a等价 (3)p是一个指向it型的常量指针,p的值不可改变(不能指向另一个目标),但 p所指向的对象即*p的值可改变 (4)指针p和p指向的对象都不可变。 7.15解释下列语句的含义: typedef char Word [80] 答:Word是有80个元素的字符数组的类型名;psz是指向80个字符元素数组的指针。 7.16动态内存分配中的常见错误有哪些? 答:最常见的错误是忘记检查所申请的内存是否分配成功:第二个错误就是访问内存时 超越了被分配内存的边界,从而破坏了其它信息:第三个错误是只要求分配而忘记释放,导 致“内存泄漏”的严重错误。所谓内存泄漏是指因持续分配而导致最终耗尽可用内存的一种 现象 7.17分析下列代码段,并给出你的分析结论: float *p[2] p[o]=(float*)malloc(sizeof (3. 14159)) printf(%f\n", *p[O]) 答:由于常数3.14159隐含为 doubl,因此 sizeof(3.14159)等价于 sizeof( double) 这意味着函数 malloc分配了 sizeof( double)个字节空间。设 float和 double型的长度分 别为4和8个字节,则被分配的8个字节中的前4个字节作为 float型指针p[0]所指向的 目标空间。由于动态分配的空间是未初始化的,所以 printf所输出的目标空间的内容是不 可预知的,编译器不会捕获这个错误。 7.18下列代码是否存在错误?为什么? nt*p,a[]={1,2,3,4,5} free(p1)
3 void *p2 = p1; (4)int num = 0; void *p = # print("%d\n",*((int*)p)); 答:(1)非法。任何非指针量不可以具有 void 类型。 (2)合法。一个指针可以具有 void 类型。 (3)合法。void 类型指针可以指向任何类型。 (4)合法。不能直接访问 void 类型指针所指的目标,但可以通过强制类型转换来 实现访问。 7.14 分别解释下列语句的语义: (1)const int *p; (2)int const *p; (3)int *const p; (4)const int *const p; 答:(1)p 是一个指向 int 型常量的指针,p 可以指向不同的对象,但 p 所指向的对象 即*p 的值不可改变。 (2)与 a 等价。 (3)p 是一个指向 int 型的常量指针,p 的值不可改变(不能指向另一个目标),但 p 所指向的对象即*p 的值可改变。 (4)指针 p 和 p 指向的对象都不可变。 7.15 解释下列语句的含义: typedef char Word[80]; Word *psz; 答:Word 是有 80 个元素的字符数组的类型名;psz 是指向 80 个字符元素数组的指针。 7.16 动态内存分配中的常见错误有哪些? 答:最常见的错误是忘记检查所申请的内存是否分配成功;第二个错误就是访问内存时 超越了被分配内存的边界,从而破坏了其它信息;第三个错误是只要求分配而忘记释放,导 致“内存泄漏”的严重错误。所谓内存泄漏是指因持续分配而导致最终耗尽可用内存的一种 现象。 7.17 分析下列代码段,并给出你的分析结论: float *p[2]; p[0] = (float*)malloc(sizeof(3.14159)); printf("%f\n",*p[0]); 答:由于常数 3.14159 隐含为 doubl,因此 sizeof(3.14159)等价于 sizeof(double), 这意味着函数 malloc 分配了 sizeof(double)个字节空间。设 float 和 double 型的长度分 别为 4 和 8 个字节,则被分配的 8 个字节中的前 4 个字节作为 float 型指针 p[0]所指向的 目标空间。由于动态分配的空间是未初始化的,所以 printf 所输出的目标空间的内容是不 可预知的,编译器不会捕获这个错误。 7.18 下列代码是否存在错误?为什么? int *p,a[] = {1,2,3,4,5}; int i,*p1 = a; for(i = 0;i < 10;i++) p[i] = i; free(p1);
free(p+5) 答:最后两个语句是错误的。 ANSIC不允许free函数释放一块非动态分配的内存,也 不允许它释放一次动态分配的内存的一部分。而这两个语句分别犯了其中的一个错误。尽管 一些系统(如TC2.0)并不捕获这个错误,似乎已经释放了空间,但事实上并没有释放。这 种隐藏错误的行为是最糟糕的。 7.19设有两个串S1和S2,编程:如果串S2包含在串S1中(不含S2的串结束符),则 输出S2的第一个字符出现在串S1中的位置;否则如果没有该串出现则输出“S2不在S1中 如果S2是一个空串(长度为0的串),则输出“S2为空”。(此题即库函数 strstr0) 算法分析:子串的定位操作是各种字符串处理系统中最重要的操作之一。这里给出一种 最简单的(但不是最好的)求子串的算法,其基本思想是:在串S1中取从第i(i的初值为 S1的首字符位置)个字符起、长度和串S2相等的子串,将其与串S2比较,若相等,则 即为所求;否则i增1如此继续比较,直至串S1中不存在和串S相等的字串为止。下面是 该算法思想的一个实现方案。 #include int maino char s1[81],S2[81]={"0"}; int Sllen, Salen, i,j: printf(" Input string S1 if(*S2=='\0) printf( The string S2 is NULL. \n") else i=0;j=0; while(i< Silen &&j< S2len if(slli]==S2[j]) printf(" the S2 first character appears in SI
4 free(p+5); 答:最后两个语句是错误的。ANSIC 不允许 free 函数释放一块非动态分配的内存,也 不允许它释放一次动态分配的内存的一部分。而这两个语句分别犯了其中的一个错误。尽管 一些系统(如 TC2.0)并不捕获这个错误,似乎已经释放了空间,但事实上并没有释放。这 种隐藏错误的行为是最糟糕的。 7.19 设有两个串 S1 和 S2,编程:如果串 S2 包含在串 S1 中(不含 S2 的串结束符),则 输出 S2 的第一个字符出现在串 S1 中的位置;否则如果没有该串出现则输出“S2 不在 S1 中”。 如果 S2 是一个空串(长度为 0 的串),则输出“S2 为空”。(此题即库函数 strstr()) 算法分析:子串的定位操作是各种字符串处理系统中最重要的操作之一。这里给出一种 最简单的(但不是最好的)求子串的算法,其基本思想是:在串 S1 中取从第 i(i 的初值为 S1 的首字符位置)个字符起、长度和串 S2 相等的子串,将其与串 S2 比较,若相等,则 i 即为所求;否则 i 增 1 如此继续比较,直至串 S1 中不存在和串 S2 相等的字串为止。下面是 该算法思想的一个实现方案。 #include #include int main() { char S1[81],S2[81] = {"\0"}; int S1len,S2len,i,j; printf("Input string S1:"); scanf("%s",S1); printf("Input string S2: "); scanf("%s",S2); if(*S2 =='\0') printf("The string S2 is NULL.\n "); else { i = 0; j = 0; S1len = strlen(S1); S2len = strlen(S2); while(i =S2len) printf("The S2 first character appears in S1 the position is %d\n ",i-S2len);
lse printf(" s2 not in S1 7.20使用数组指针实现间接选择排序 算法分析:选择排序的基本思想是,对有n个元素的数组a排序,使用n-1次循环,每 次循环选择下一个最大的元素a[k],并将它与其应在的正确位置上的元素交换位置。所以 第一次循环在所有的元素中选出最大的元素并与a[n-1]互换位置,第二次循环在剩余的未 排序序列a[0],a[1 [n-2]中选出最大的元素并与a[n-2]互换位置,余类推 所谓间接选择排序,就是在选择排序过程中,通过将数组下标排序,而不是通过移动数 组元素来实现数组有序。 #include #define size 10 int main inta[SIZE]={70,35,52,25,59,60,88,01,66,21} int i, j, k, *temp, *p [SIZe] /*定义指针数组* printf(" Data items in orginal order: \n") for(i =0: i*plkl)k=j pk]= plsize-i] P[SIZE-i]= temp printf( " \n\nData items in ascending order: \n") for(i =0: i<SIZE: i++) printf("%4d" *p[il) rinf( "\n")
5 else printf("S2 not in S1!"); } } 7.20 使用数组指针实现间接选择排序。 算法分析:选择排序的基本思想是,对有 n 个元素的数组 a 排序,使用 n-1 次循环,每 次循环选择下一个最大的元素 a[k],并将它与其应在的正确位置上的元素交换位置。所以 第一次循环在所有的元素中选出最大的元素并与 a[n-1]互换位置,第二次循环在剩余的未 排序序列 a[0],a[1],…,a[n-2]中选出最大的元素并与 a[n-2] 互换位置,余类推。 所谓间接选择排序,就是在选择排序过程中,通过将数组下标排序,而不是通过移动数 组元素来实现数组有序。 #include #define SIZE 10 int main() { int a[SIZE] = {70,35,52,25,59,60,88,01,66,21}; int i, j, k, *temp, *p[SIZE]; /* 定义指针数组 */ printf("Data items in oraginal order:\n"); for(i = 0; i *p[k]) k = j; temp = p[k]; p[k] = p[SIZE-i]; p[SIZE-i] = temp; } printf("\n\nData items in ascending order:\n"); for(i = 0; i<SIZE; i++) printf("%4d",*p[i]); printf("\n"); }