说起C语言的难点,我想很多人的第一念头就是指针,各种不同类型的指针指来指去,左偏移一下右偏移一下,经常搞得人头昏脑胀,不知所措。然而C语言的灵活性也恰恰体现在指针上,只要你能够不被指针迷惑,不被吓倒,能够清楚地知道自己在做怎样的处理,那么指针的运用,会让你更加得心应手,随心所欲,指哪打哪。从本质上讲指针与数组是同一种类型,数组可以做的操作,指针基本上也都可以。
我们先来了解一下使用操纵内存时容易出错的地方,这样在使用指针时才能够尽量避免出错。
1:未初始化
这也是使用数组是容易出现的问题,在声明一个数组后或者是malloc分配一块空间后,没有及时地进行初始化,造成使用数组或者指针时取得不可预知的数据。
2:内存读写越界覆盖
每个数组或者指针,都有其大小,在编码时要时刻注意不能操作到其范围外的地址空间。因为那样会有可能影响到其他的变量,造成不可预知的错误。C语言不像Java等编程语言,在数组越界时会提示错误,C语言的灵活性就在于开发人员可以自由操纵内存,因此即使你的数组或者指针越界了,也不会得到编译器的人和回应。这种越界有可能不会造成任何问题,但也许会在任意时刻造成致命的错误。
3:空指针
这个是相对较容易发现的问题,也是出现问题较多的地方。我们需要做的就是,在使用任意指针做操作之前(当然初始化操作或者内存释放操作除外),都需要保证该指针指向非空(!=NULL)。
以上就是操纵内存时容易出现问题的地方,也是我们在学习指针时需要重点关注的地方。
关于指针的操作,重点在于偏移。偏移的形式有多种,类似于数组的下标偏移方式比较简单,而用++或者——进行多次偏移时则要复杂的多。关于指针的难点,那就是多重指针了。基础是单一指针,我们只有把多重指针分解成一个个的单一指针,才能更好地理解和应用多重指针。
下面针对偏移和多重指针举例进行说明。
#include <stdio.h>
#include <string.h>
int main()
{
int i = 0;
int numlist[4] = {0x123456,0x2111,0x78,0x23765432};
int *pnlist = NULL;
short *pilist = NULL;
char *pclist = NULL;
pnlist = numlist;
for(i = 0; i < sizeof(numlist)/sizeof(*pnlist); i++) {
printf("pnlist[%d]: %x/n", i, *(pnlist++));
}
//out of numlist
printf("pnlist[%d]: %x/n", i, *(pnlist++));
pilist = (short *)numlist;
for(i = 0; i < sizeof(numlist)/sizeof(*pilist); i++) {
printf("pilist[%d]: %x/n", i, *(pilist++));
}
pclist = (char *)numlist;
for(i = 0; i < sizeof(numlist)/sizeof(*pclist); i++) {
printf("pclist[%d]: %x/n", i, *(pclist++));
}
return 0;
}
运行结果为:
pnlist[0]: 123456
pnlist[1]: 2111
pnlist[2]: 78
pnlist[3]: 23765432
pnlist[4]: 4
pilist[0]: 12
pilist[1]: 3456
pilist[2]: 0
pilist[3]: 2111
pilist[4]: 0
pilist[5]: 78
pilist[6]: 2376
pilist[7]: 5432
pclist[0]: 0
pclist[1]: 12
pclist[2]: 34
pclist[3]: 56
pclist[4]: 0
pclist[5]: 0
pclist[6]: 21
pclist[7]: 11
pclist[8]: 0
pclist[9]: 0
pclist[10]: 0
pclist[11]: 78
pclist[12]: 23
pclist[13]: 76
pclist[14]: 54
pclist[15]: 32
其中pnlist[4] 为越界后取得的值,程序不会提示出错。从结果可以看出来,指针的偏移是以本身类型为单位偏移量的,int指针++后,偏移了一个int(4byte),short指针++后,偏移了一个short(2byte),char指针++后偏移了一个char(1byte)。还有就是,指针本身没有明确它所指定的范围,但是它所指向的空间有着明确的大小范围。
#include <stdio.h>
int main()
{
char *pletter[] = {"abc", "de", "fghi"};
//first string
printf("string1: %s/n", *pletter);
//first string'first letter
printf("letter1: %c/n", **pletter);
return 0;
}
运行结果为:
string1: abc
letter1: a
其中*pletter 等效于pletter[0],**pletter等效于pletter[0][0]或者*pletter[0]。
以上例子只是用来帮助理解指针的操作,复杂度上可能无法与实际应用相比。但是如果理解了这些,那么即使再复杂的指针也可以分解成简单的指针操作,也就不难理解了。
关于函数指针,实际应用中比较少见,比较容易见到的场合可能就是在创建线程时,不是太复杂,这里仅做简单说明。
int thr_create(void *stack_base, size_t stack_size, void
*(*start_func) (void*), void *arg, long flags, thread_t *new_thread_ID)
其中第三个参数指明了创建线程所需函数的特征:void*(*start_func) (void*)
第一个void*表明所需函数的返回值为void,括号内的*表明所需函数要声明成指针类型,后面的void*表明参数类型为void*,根据要求我们可以定义下面的函数:void *threadtest(void *argv)或者void *threadtest()。