鉴于很多C语言初学者都觉得指针非常难,本文将初学者常常觉得迷惑的地方以问答的形式解答,希望能够对读者有所帮助。
小明定义了一个C语言函数 int f(int * ),为什么 f(&5) 不能正常工作呢?
如果希望传递 5 给函数 f(),在C99中,可以使用下面这种方法:
f( (int[]){5} );
上面这行C语言代码相当于定义了一个数组,并且数组只有一个元素 5,函数 f() 接收到的参数是该数组,只不过这一过程中的数组名没有“显示”。
抛开C99的这个特性,C语言调用 f(&5) 就不能这么写了,而是需要借助变量:
int five = 5;
f(&five);
在C语言中,接受某个值指针的函数是有可能通过该指针修改该值的(即使程序员无此打算,C语言还是会一直这么认为),因此只有变量才能胜任。在C语言中,常数是只读的,只能作为右值,& 运算符是不能处理常数 5 的,f(&five) 会引发编译错误。
表达式 * p++ 增加了 p 指向的数值,还是指针 p 本身?
C语言中的 ++ 和 -- 运算符的优先级高于 * 运算符,所以 *p++
其实就相当于 *(p++)
。显然,++ 运算符增加的是指针 p 本身的值,不过在指针 p 自增之前,*p++
会先返回 p 指向的值。如果希望 ++ 运算符增加 p 指向的值,应该使用括号运算符:(*p)++
。
小明想使用指针操作数组里的数值,下面这段C语言代码有什么问题?
小明预计程序会输出 3,但是程序却输出了“垃圾值”,他的C语言代码如下,请看:
int array[5], i, *ip;
for(i = 0; i < 5; i++)
array[i] = i;
ip = array;
printf("%d\n", *(ip + 3 * sizeof(int)));
为什么上述C语言程序没有按照小明的预期输出?
小明是“聪明反被聪明误”了,他发现数组 array 是 int 型的,所以在指针加数上乘上了 sizeof(int)。其实,C语言中的指针加法运算会自动考虑元素类型的,所以小明应该修改 printf() 语句的代码:
printf("%d\n", *(ip + 3));
这样他的C语言程序就会输出数组 array 的第 3 个元素了。事实上,使用指针 ip 访问数组 array 中的元素时,也可以以“数组的形式”进行,例如:
printf("%d\n", ip[3]);
这样也就清楚了,小明原本的写法相当于 ip[3*sizeof(int)]
,如果 int 类型占用 4 个字节,其实也就是访问了 array[12],这超出了数组范围,程序的输出自然就不可预知了。
小明使用 char * 指针指向 int 型数组,为什么下面这行C语言代码就不正常了呢?
小明的C语言代码是下面这样的,他发现这段代码甚至都不能编译通过,为什么呢?
int main()
{
int arr[] = {1, 2, 3, 4, 5};
char *p = (char *)arr;
((int*)p)++;
return 0;
}
这是因为在C语言中,强制类型转换运算并不意味着“忽略类型差异,强制按照指定类型执行”。按照C语言标准,强制类型转换将产生一个右值,而 ++ 运算符只能操作左值(lvalue),因此标准C语言编译器当然会报错了。例如使用gcc编译器编译上述C语言代码,将得到如下错误:
可能有部分C语言编译器可能做了非标准扩展,能够编译上述代码。
小明写出((int*)p)++;
的本意应该是希望产生下面这样的C语言代码:
p = (char *)((int *)p + 1);
或者以更简单的形式:
p += sizeof(int);
再或者,也可以多写一行C语言代码:
int *ip = (int *)p;
p = (char *)(ip + 1);
虽然有多种方法可以实现小明的想法,但是仍然应该尽量避免使用不恰当类型的指针操作数据。
小结
C语言的大多语法都是简单易懂的,这常常会给初学者一种“别人都说C语言难,我看也不过如此”的感觉。但是这种感觉常常会停止在初学者学到指针时,这是一些读者跟我说的。为此,本节讨论了几个比较典型的初学者会遇到的问题,并做了较为详细的解析。
本专栏是C语言基本功修炼秘籍,对指针的剖析自然是少不了的,接下来几节,将更加深入的探讨指针的用法,敬请关注。