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];
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;
void *p2 = p1;
(4)int num = 0;
void *p = &num;
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为空”。(此题即库函数strstr())
算法分析:子串的定位操作是各种字符串处理系统中最重要的操作之一。这里给出一种最简单的(但不是最好的)求子串的算法,其基本思想是:在串S1中取从第i(i的初值为S1的首字符位置)个字符起、长度和串S2相等的子串,将其与串S2比较,若相等,则i即为所求;否则i增1如此继续比较,直至串S1中不存在和串S2相等的字串为止。下面是该算法思想的一个实现方案。
#include <stdio.h>
#include <string.h>
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 < S1len && j < S2len)
{
if(S1[i]==S2[j])
{
++i; ++j;
}
else{
i = i-j+1;
j = 0;
}
}
if(j>=S2len)
printf("The S2 first character appears in S1
the position is %d\n ",i-S2len);
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 <stdio.h>
#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<SIZE; i++)
printf("%4d",a[i]);
for(i = 0; i<SIZE; i++)
p[i] = &a[i]; /* p[i]指向a[i] */
for(i = 1; i<SIZE; i++) /* 开始排序 */
{ /* 选择a[k] = max{a[0],a[1],…,a[SIZE-1]} */
k = 0;
for(j = 1; j<=SIZE-i; j++)
if(*p[j] > *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");
}