文章只列出了使用指针需要注意的一些细节,属个人见解,如有错误,还望指正,谢谢!!!
指针的初始化
声明指针变量后,必须初始化才能使用,否则其指向是不确定的,除非不进行解引用,若解引用指针会造成内存错误;你至少应该初始化为NULL,因为内存位置0是没有存储任何数据的,虽然对其访问也是非法的,故同时你最好在使用前进行空指针检查,这样能节省调试时间
int main(void)
{
int *a;
*a = 12;
return 0;
}
这里a是局部变量,未被初始化,故a的指向是未知的,执行*a是非法的
关于指针的左值和右值
你需要记住一点,作为左值的前提是该值在内存内有明确的存储位置,如:
- 对一个整型变量取地址&a ,你无法知道这个地址值存在内存中的什么位置
- 指针解引用后的额外处理*a+1 , 你能知道 *a所代表的值存储在地址a中,但 *a+1,你无法知道
- 对指针使用前后缀自增自减++a ,a++ ,前者的结果是地址自增后的一份拷贝,后者是拷贝后自增,而这份拷贝的存储位置并未清晰定义
*p++的说明
这里涉及到3个步骤 (优先级:后缀自增 > 解引用符)
- 后缀++操作符产生p的一份拷贝
- 后缀++操作符增加p的值
- 对p的拷贝进行解引用(间接访问)
指针作为函数参数
指针作为参数传递,与普通变量作为参数一样,函数所使用的值,是参数的一份拷贝,函数内修改并不会改变原始值,而指针传参与普通变量传参的区别在于,指针传参后能够真正修改所指向的内容
void change(char *str)
{
while(*str != '\0')
*str++ = 'a';
*str = '\0';
}
int main(void)
{
char p[] = "abcd";
printf("%s,%p\n",p,p);
change(p);
printf("%s,%p\n",p,p);
return 0;
}
结果为:
可以看到change函数中有改变传入的地址值,但地址p却并未改变,说明函数改变的只是一份拷贝值
注:当你不希望改变地址参数所指向的内容时,最好加上const关键字;如void change(char const *str)
指针无法改变常量地址内容
void change(char *str)
{
while(*str != '\0')
*str++ = 'a';
*str = '\0';
}
int main(void)
{
char *p = "abcd";
printf("%s\n",p);
change(p);
printf("%s\n",p);
return 0;
}
结果为:
与上面的程序类似,只是将数组改为了指针,如结果所示,第二行未正常输出,该程序并未改变p指向的内容,而原因就在于指针p指向的是字符串常量,处在只读区域
指针运算
提示:指针运算不要超出内存范围!!!
1.指针 ± 整数(可交换位置)
当指针与整数进行算数运算时,整数会按照指针所指向类型的大小进行调整
struct
{
char *a;
char *b;
char *c;
}test[3] = {
{"12","ab","cd"},
{"34","ef","gh"},
{"56","hi","jk"}
};
int main(void)
{
printf( "%p , %d\n", test , sizeof(test) );
printf( "%p , %s\n", (char **)(test+2) , *(char **)(test+2) );
printf( "%p , %s\n", ((char **)test+2) , *((char **)test+2) );
return 0;
}
输出为:
分析:这里定义了一个包含3个结构体的结构体数组test[3],test为该数组的首地址,指向第一个结构体,地址值为00402000,数组总大小为:3×结构体大小( sizeof(test[0])=12 ) = 36
-
(char **)(test+2):
- (test+2):一个结构体所占大小为12Bytes,故test做指针运算步长为12,所以test+2地址值为:00402000 + 2×12 = 00402018,指向第三个结构体
- (char **):强制改变指针test+2的指向类型,结构体成员变量为指针类型,故其地址为指针的指针,使用char **进行强制转换,将使test+2指向第三个结构体第一个成员变量,指向类型为char * -
((char **)test+2):
- (char **)test:这里首先将test强制转换为指向char*的指针,指向第一个结构体的第一个成员变量char *a; 故在指针运算时,步长为sizeof(char *) = 4
- (char **)test+2:按照步长4进行计算,得到的地址为:00402000 + 2×4 = 00402008,指向第一个结构体中的第3个成员变量
2.指针 - 指针
- 这种情况只能发生在同一个数组中
- 如p1指向array[i],p2指向array[j] ,则p2-p1 = j - i,或 p1-p2 = i - j,若第一个为正数,那第二个就为负数
3.指针关系运算
- <,>,>=,<=,均只能发生在同一个数组中,表示哪个指针指向的元素更靠前或靠后
- ==,!=,可以发生在任意指针间,表示两指针指向地址是否相同
假设要初始化一个数组value[num]为0,num为数组中元素个数,观察下面两个初始化过程
//1
for( vp = &value[num]; vp > &value[0]; )
*--vp = 0;
//2
for( vp = &value[num-1]; vp >= &value[0]; vp-- )
*vp = 0;
可以看到:
第一个初始化使vp指针指向了数组最后一个元素的后面一个地址(最初)
第二个初始化使vp指针指向了数组的第一个元素的前面一个地址(最后)
第一个初始化仅仅指向了数组后一个地址,并未做任何操作
第二个初始化不仅指向了数组前一个地址,而且进行了比较(vp >= &value[0])
上面第一点已经说明做>=比较的前提是两个指针都指向同一个数组,很明显这里vp最后已经没有指向该数组了,故第二个是有问题的,这是一个值得注意的地方
字符串常量
这里涉及到 指针与数组
- 字符串给数组赋值:表示一个初始化序列;如
char str[] = "abc"
中字符串表示{‘a’,‘b’,‘c’,’\0’} - 字符串在其他表达式:表示指针常量,编译器将指定字符拷贝到内存的某个内存位置,该字符串即表示该内存的首地址
- 既然字符串用在表达式时表示指针常量,与数组相同,也可以进行下标引用(可以把字符串假设为一个数组名):
- “abc” + 1:类似于数组名+1,是指向"abc"中的b的地址
- *“abc”:类似于对数组名解引用,表示"abc"中第一个字符’a’
- “abc”[2]:表示’c’
- *(“abc”+4):相当于"abc"[4],显然已经超出范围,故这种表述是非法的
int main(void)
{
printf("%s\n", "abc" );
printf("%s\n", "abc"+1 );
printf("%c\n", "abc"[3]);
printf("%c\n", "abc"[2]);
printf("%c\n", *("abc"+1));
printf("%c\n", *"abc" );
return 0;
}
结果为: