char *p = "123"和char a[] = "123"根本就不同

本文详细解析了C语言中字符串与指针的基本概念及其使用技巧,对比了字符串常量与字符数组的区别,并介绍了如何通过指针或数组下标访问字符串元素。

    示例代码如下: 

#include <stdio.h>

int main(void)
{
    char *p = "123"; //等价于char *p; p = "123";
    char a[] = "123"; //等价于char a[] = {'1', '2', '3', '\0' };

    printf("address a = %p, p = %p, &p = %p\n", a, p, &p);

    a[2] = '4';
//    p[2] = '5';

    printf("a[2] = %c, p[2] = %c\n", a[2], p[2]); //a[2]等价于*(a+2),p[2]等价于*(p+2)
    
    printf("\"abcd\"[1] = %c, \"abcd\" = %p, &\"abcd\"[2] = %p\n", 
	    "abcd"[1], "abcd", &"abcd"[2]);

    return 0;
}

    1、两者的差别

    代码中第5行的意思是声明一个字符指针变量p,然后给它赋值为字符串常量“123”的首地址(如示例中的0x8048590)。在C语言中,字符串都是以字符数组的形式存储的,而这里的字符串常量就是一个无名字符数组,由于没有数组名,所以一般通过指针变量获得它的首地址来间接地进行数据访问,如示例中,先通过指针变量p获得无名字符数组的首地址,然后通过第13行的p[2]来访问这个无名字符数组中的第3个元素(即字符’3’)。

    注意:指针变量的存储空间(在32位机中为4个字节)只够存储一个地址值(在32位机中是一个4字节的无符号整数),这里不能错误地认为是把字符串常量“123”赋值给p或者*p。

    字符串常量一般存储在可执行文件的只读数据段(section .rodata),不能对其中的元素进行修改,所以,把第11行的语句加入到程序中,执行时会产生段错误,尽管用编译器GCC可成功编译。

    第6行的意思是声明一个字符数组并给它初始化为字符串“123”(字符串以空字符为结尾)。

    注意:数组只有在初始化阶段才能同时为其中的多个元素赋值。

    因此,5、6两行中的字符串“123”的含义是完全不同的:第5行中的“123”代表着它(无名字符数组)自身的首地址,而第6行的“123”仅仅是字符数组a的初始值

    通过下面的图示更能直观地理解两者的差异(小端模式): 

 

    2、比较酷的用法

    除了可以通过指针变量间接地访问无名字符数组的元素以外,也可以通过数组下标或指针的形式直接访问其中的元素,如”abcd”[1]或者*(“abcd” +1),它们获得的值都是字符’b’。

    因为字符串常量代表它自身的首地址(如0x80485fa),而访问数组元素都是通过类似于“ *( 数组名(即首地址)+ sizeof(元素数据类型)  x  n(第n+1个元素) ) ”这样的形式进行访问的,所以”abcd”[1](等价于 *(“abcd” + 1) )的意思是从无名字符数组首地址加1的地址(即0x80485fb)中获得第2个元素的值(即字符’b’)。

    例子结果输出: 

address a = 0xbfdd0f78, p = 0x8048590, &p = 0xbfdd0f74
a[2] = 4, p[2] = 3
"abcd"[1] = b, "abcd" = 0x80485fa, &"abcd"[2] = 0x80485fc

    尽管指针变量的值是一个32位的无符号整数,但在printf函数中,还是特别使用了另外的格式符%p来输出其中的值,说明指针变量是有它特有的数据类型的。虽然使用%x等格式符能正确地输出指针变量的值,但一般会警告数据类型不匹配。

在C语言里,直接传递一级指针传递二级指针存在显著差异,这在给定的代码中能清晰体现。 ### 直接传递一级指针 给定代码如下: ```c #include <stdio.h> int func(char* P) { char *q = "123"; *P = q; // 这里代码有误,应该是 P = q; 但即便修改也无法改变调用者的指针 return 0; } int main() { char* P = NULL; func(P); // 这里P仍然是NULL,因为func内部无法改变P本身 if (P == NULL) { printf("P is still NULL.\n"); } return 0; } ``` 直接传递一级指针时,函数接收的是指针的副本。也就是函数内部对指针的修改不会影响到调用者的原始指针。在这个代码里,`func`函数接收`P`的副本,即便在函数内部尝试修改这个副本,调用者的`P`依然是`NULL`。因为函数内的指针调用者的指针是不同的变量,只是值(地址)相同。 ### 传递二级指针 若要在函数内部修改调用者的指针,就需要传递二级指针。示例代码如下: ```c #include <stdio.h> int func(char** P) { char *q = "123"; *P = q; // 修改调用者的指针 return 0; } int main() { char* P = NULL; func(&P); // 这里P已经指向了"123" if (P != NULL) { printf("P now points to: %s\n", P); } return 0; } ``` 传递二级指针时,函数接收的是指向指针的指针。这意味着函数能够通过解引用二级指针来修改调用者的原始指针。在`func`函数中,通过`*P = q;`,能够把调用者的`P`指针指向字符串`"123"`。 ### 总结 直接传递一级指针时,函数无法修改调用者的原始指针,只能修改指针所指向的内容;而传递二级指针时,函数可以修改调用者的原始指针,使其指向其他内存地址。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

tanglinux

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

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

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

打赏作者

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

抵扣说明:

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

余额充值