Q. 指针的好处
A. 比如:
动态分配数组;
对多个相似变量的一般性访问;
(模拟)按引用传递函数参数;
各种动态分配的数据结构,尤其是树和链表;
遍历数组,例如解析字符串;
高效的按引用”复制“数组和结构,特别是作为函数参数的时候。
Q. 我想声明一个指针并为他分配一些空间,但却失败了。下面的代码有什么问题?
char* p;
*p = malloc(10);
A. 这样声明的指针是p,而不是*p, 当操作指针本身时(例如对其赋值,使之指向别处),只需要使用指针的名字即可。如:
p = malloc(10);
当操作指针所指向的内存时,才需要使用*作为间接操作符,如:
*p = ‘H’;
然而,如果像如下方式在局部变量的声明中使用malloc调用作为初始化:
char* p = malloc(10);
则很容易犯问题中所描述的错误。
在把一个初始化的指针声明分成一个声明和一个后续赋值的时候,要记得去掉*号。总之,在表达式中,p是指针,*p是他所指向的内容(在这里是一个char)。
Q. *p++自增p还是p所指向的变量?
A. 后缀++和--操作符本质上比前缀一元操作符的优先级高,因此*p++和*(p++)等价,他自增p并返回p自增之前所指向的值。要自增p指向的值,则使用(*p)++,如果副作用的顺序无关紧要也可以使用++*p。
Q. 指针操作int数组的问题。下面的代码的问题:
int array[5], i, *ip;
for( i=0; i<5 ; i++ ) array[i] = i;
ip = array;
printf( "%d/n", *(ip+3*sizeof(int)) );
打印出来的是乱码。
A. C中的指针算术总是自动的采纳他所指向的对象的大小。我们所需要的只是:
printf( "%d/n", *(p+3) );
这样就可以打印出数组的第三个元素。在类似的代码中,无需考虑按指针指向的元素大小进行计算。
Q. 一个char*类型的指针碰巧指向一些int类型的变量。为什么((int*)p)++不能跳过他们?
A. C语言类型转换操作符并不意味着”把这些二进制位看作另一种类型,并作相应的处理“。这是一个转换操作符,根据定义它只能生成一个右值。而右值既不能赋值,也不能用++自增。如果编译器接受这样的表达式,那要么是一个错误,要么是有意作出的非标准扩展。要达到跳过的目的可以用:
p = (char*)((int*)p+1);
或者,因为p是char*型,直接用
p += sizeof(int);
真正明白无误的用法是:
int *ip = (int*)p;
p = (char*)(ip+1);
但是可以的话,还是应该一开始就选择适当的指针类型,而不是一味的试图李代桃僵。
Q. 定义某个函数,他应该接受并初始化一个指针:
void f( int* ip )
{
static int dummy = 5;
ip = &dummy;
}
但是在如下方式调用时:
int* ip;
f(ip);
调用者的指针没有任何变化。
A. 切记,在C语言中,参数是通过值传递的。上述代码中被调用函数仅仅修改了传入的指针副本。为了达到期望的效果,需要传入指针的地址,让函数变成接受指向指针的指针,如:
void f(int** ipp)
{
static int dummy = 5;
*ipp = &dummy;
}
int *ip;
f(&ip);
这里实际上是在模拟通过引用传递参数。另一种办法是让函数返回指针:
int* f()
{
static int dummy = 5;
return &dummy;
}
int* ip = f();
Q. 能否用void**通用指针作为参数,使函数模拟按引用传递参数?例如:
void f(void**);
double *dp;
f((void**)&dp);
A. 不可移植。这样的代码可能有效,而且有时鼓励这样用。但是他依赖于一种假设——所有指针的内部表示都是一样的。
C语言中没有通用的指针类型。void*可以用作通用指针只是因为当他和其他类型相互赋值的时候,如果需要,他可以自动转换成其他类型。但是如果试图这样转换所指类型为void*之外的类型的void**指针时,就不会自动转换了。当使用void**指针的时候(例如使用*操作符访问void**指针所指的void*值的时候),编译器无从知道void*值是否是从其他类型的指针转换而来的。从而,编译器只能认为他仅仅是个void*指针,不能对他进行任何隐式的转换。
换言之,任何void**值必须的确是某个位置的void*值的地址。(void**)&dp这样的类型转换虽然可以让编译器接受,但却不能移植,而且也达不到目的。如果void**指针指向的值不是void*类型,而且他的大小或内部表示和void*也不相同,则编译器就不能正确的访问。
要使上述代码正确工作,需要使用一个中间变量void*,如下:
double* dp;
void*vp = dp;
f(&vp);
dp = vp;
对vp的赋值使编译器有机会在需要的时候进行适当的类型转换。
目前的讨论假设不同类型的指针可能具有不同的大小和内部实现。为了进一步弄清楚void**的问题,看如下类似的情况,比如类型int和double。他们可能大小不一样,而且肯定内部表示也不一样。假如有这样的函数:
void incme(double* p)
{
*p += 1;
}
可以这样做:
int i = 1;
double d = i;
incme(&d);
i = d;
很明显,i增加了1。这跟使用辅助变量vp的void**指针的正确代码类似。但是,如果:
int i = 1;
incme((double*)&i); // WRONG!
跟问题中的代码类似,这段代码肯定不会正确运行。
Q. 一个函数:
extern intf(int*);
他接受指向int型的指针。怎样用引用方式传入一个常量?调用f(&5);对吗?
A. 在C语言中,接受指针而不是值的函数可能往往希望修改指针指向的值。因此传入一个常数指针可能不是一个好办法。事实上,如果f被定义成接受int*,则向他传入const int的指针的时候需要诊断。如果函数能够保证不修改传入指针指向的值,则他可以定义成接受const int*参数。
在C99之前,必须要先定义一个临时变量,然后把它的地址传给函数:
int five = 5;
f(&five);
Q. C语言可以按引用传参吗?
A. 严格的讲,C语言总是按值传参。
Q. 用指针调用函数的不同语法形式。
A. 最初,函数指针必须使用*操作符和一对括号”转换为“一个真正的函数才能使用。如:
int r, (*fp)()
fp = func;
r = (*fp)();
最后一行的解释很明确:fp是一个函数的指针,因此*fp是一个函数。在括号内加上函数参数列表,再在*fp外加上一对括号用于使运算的优先级正确,就完成了一个完整的函数调用。
而函数总是通过指针进行调用的,所有真正的函数名在表达式和初始化中,总是隐式的退化为指针。这个推论也表明,无论fp是函数名还是函数指针,r=fp();都是合法的,且能正确工作。这种做法没有任何歧义,使用函数指针后跟参数列表的方法,除了调用它所指导函数外,别的什么都做不了。
Q. 通用指针类型是什么?当把函数指针赋向void*类型的时候,编译通不过。
A. 没有什么”通用指针类型“。void*指针只能保存对象指针,也就是数据。将函数指针转换为void*类型指针是不可移植的。在某些机器上,函数指针可能很大,比数据类型指针要大。
但是可以确保的是,所有的函数指针类型都可以相互转换,只要在调用之前转回了正确的类型即可。因此,可以使用任何函数类型(通常是int(*)()或者void(*)(),即未指明参数、返回int或void的函数)作为通用函数指针。如果需要一个既能容纳对象指针,又能容纳函数指针的地方,可移植到解决方案是使用包含void*指针和通用函数指针的联合。
Q. 怎么在整形和指针之间转换?能否暂时将整数放到指针变量中?或者相反?
A. 标准C不保证能将指针转换为整数。当需要同时保存两种类型数据的存储结构的时候,使用联合是一个比较好的办法。