C语言细节 - 指针

文章只列出了使用指针需要注意的一些细节,属个人见解,如有错误,还望指正,谢谢!!!

指针的初始化

声明指针变量后,必须初始化才能使用,否则其指向是不确定的,除非不进行解引用,若解引用指针会造成内存错误;你至少应该初始化为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个步骤 (优先级:后缀自增 > 解引用符)

  1. 后缀++操作符产生p的一份拷贝
  2. 后缀++操作符增加p的值
  3. 对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;
}

结果为:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

|清风|

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值