C语言char, char*, char**, char*[]类型及用法详细测试解析

背景:初学C语言时,由于对于char家族一系列类型掌握不佳,遇到char*, char**, char*[]时总是晕头转向。今特地编写用例,详细辨析其中的区别

环境:Win10 64位

语言:C

编译器:gcc (x86_64-posix-sjlj-rev0, Built by MinGW-W64 project) 8.1.0

源码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    // part 1
    char c = 'a';
    printf("c = %c, c: 0x%p;\n", c, &c);
    // part 2
    char *s1 = "hello world";
    printf("s1 = %s, s1: 0x%p, s1[0] = %c, sizeof(s1) = %d, strlen(s1) = %d;\n", s1, s1, s1[0], sizeof(s1), strlen(s1));
    char s2[10] = "thank you";
    printf("s2 = %s, s2: 0x%p, s2[0] = %c, sizeof(s2) = %d, strlen(s2) = %d;\n", s2, s2, s2[0], sizeof(s2), strlen(s2));
    s1 = "world hello";
    printf("s1' = %s;\n", s1);
    s1 = s2;
    printf("s1'' = %s;\n", s1);
    // error: s2 = "you thank";
    // error :s2 = s1;
    // part 3
    char *s3[] = {"apple", "orange", "banana"};
    printf("s3 = %s, s3: 0x%p;\n", s3, s3);
    printf("s3[0] = %s, s3[0]: 0x%p, s3[0][0] = %c, s3[0][0]: 0x%p, sizeof(s3[0]) = %d, strlen(s3[0]) = %d;\n", s3[0], s3[0], s3[0][0], &s3[0][0], sizeof(s3[0]), strlen(s3[0]));
    printf("s3[1] = %s, s3[1]: 0x%p, s3[1][0] = %c, s3[1][0]: 0x%p, sizeof(s3[1]) = %d, strlen(s3[1]) = %d;\n", s3[1], s3[1], s3[1][0], &s3[1][0], sizeof(s3[1]), strlen(s3[1]));
    printf("s3[2] = %s, s3[2]: 0x%p, s3[2][0] = %c, s3[2][0]: 0x%p, sizeof(s3[2]) = %d, strlen(s3[2]) = %d;\n", s3[2], s3[2], s3[2][0], &s3[2][0], sizeof(s3[2]), strlen(s3[2]));
    printf("sizeof(s3) = %d, sizeof(*s3) = %d, sizeof(**s3) = %d, strlen(*s3) = %d\n", sizeof(s3), sizeof(*s3), sizeof(**s3), strlen(*s3));
    printf("&s3[0]: 0x%p, &s3[1]: 0x%p, &s3[2]: 0x%p\n", &s3[0], &s3[1], &s3[2]);
    printf("*s3 = %s, *(s3 + 1) = %s, *(s3 + 2) = %s\n", *s3, *(s3 + 1), *(s3 + 2));
    char **s4 = s3;
    printf("s4 = %s, s4: 0x%p\n", s4, s4);
    printf("*s4 = %s, *s4: 0x%p\n", *s4, *s4);
    printf("**s4: 0x%p\n", **s4);
    printf("sizeof(s4) = %d, sizeof(*s4) = %d, sizeof(**s4) = %d, strlen(*s4) = %d\n", sizeof(s4), sizeof(*s4), sizeof(**s4), strlen(*s4));
    printf("&s4[0]: 0x%p, &s4[1]: 0x%p, &s4[2]: 0x%p\n", &s4[0], &s4[1], &s4[2]);
    printf("*s4 = %s, *(s4 + 1) = %s, *(s4 + 2) = %s\n", *s4, *(s4 + 1), *(s4 + 2));
    // part 4
    char **s5 = (char **)malloc(sizeof(char **));
    *s5 = "hello world";
    printf("s5: 0x%p, *s5: 0x%p, *s5 = %s\n", s5, *s5, *s5);
    *s5 = "thank you";
    printf("s5': 0x%p, *s5': 0x%p, *s5' = %s\n", s5, *s5, *s5);
    // part 5
    char *s6;
    char **s7 = &s6;
    *s7 = "hello world";
    printf("s6 = %s, s6: 0x%p\n", s6, s6);
    s6 = "thank you";
    printf("s6 = %s, s6: 0x%p\n", s6, s6);
    while(getchar() != '\n') {}
    return 0;
}

输出:

// part 1
c = a, c: 0x000000000061FDFF;
// part 2
s1 = hello world, s1: 0x0000000000405012, s1[0] = h, sizeof(s1) = 8, strlen(s1) = 11;
s2 = thank you, s2: 0x000000000061FDF5, s2[0] = t, sizeof(s2) = 10, strlen(s2) = 9;
s1' = world hello;
s1'' = thank you;
// part 3
s3 = 蚉@, s3: 0x000000000061FDD0;
s3[0] = apple, s3[0]: 0x00000000004050CD, s3[0][0] = a, s3[0][0]: 0x00000000004050CD, sizeof(s3[0]) = 8, strlen(s3[0]) = 5;
s3[1] = orange, s3[1]: 0x00000000004050D3, s3[1][0] = o, s3[1][0]: 0x00000000004050D3, sizeof(s3[1]) = 8, strlen(s3[1]) = 6;
s3[2] = banana, s3[2]: 0x00000000004050DA, s3[2][0] = b, s3[2][0]: 0x00000000004050DA, sizeof(s3[2]) = 8, strlen(s3[2]) = 6;
sizeof(s3) = 24, sizeof(*s3) = 8, sizeof(**s3) = 1, strlen(*s3) = 5
&s3[0]: 0x000000000061FDD0, &s3[1]: 0x000000000061FDD8, &s3[2]: 0x000000000061FDE0
*s3 = apple, *(s3 + 1) = orange, *(s3 + 2) = banana
s4 = 蚉@, s4: 0x000000000061FDD0
*s4 = apple, *s4: 0x00000000004050CD
**s4: 0x0000000000000061
sizeof(s4) = 8, sizeof(*s4) = 8, sizeof(**s4) = 1, strlen(*s4) = 5
&s4[0]: 0x000000000061FDD0, &s4[1]: 0x000000000061FDD8, &s4[2]: 0x000000000061FDE0
*s4 = apple, *(s4 + 1) = orange, *(s4 + 2) = banana
// part 4
s5: 0x0000000000976EF0, *s5: 0x0000000000405012, *s5 = hello world
s5': 0x0000000000976EF0, *s5': 0x00000000004053CF, *s5' = thank you
// part 5
s6 = hello world, s6: 0x0000000000405012
s6 = thank you, s6: 0x00000000004053CF
\n
\n

Part 1:

该部分从简单字符型变量入手,定义一个字符型变量c并初始化,打印其值和地址,需要注意的是,此时的c是栈上分配的内存空间;

Part 2:

分别定义字符指针s1和字符数组s2,对于s2的大小可以隐式声明为char s2[],且虽然s1被定义为字符指针,但仍可以用数组下标的方式访问字符串中的每个字符;

重点一:观察两个sizeof的结果可以发现,s1的大小为8,而s2的大小为10。这是因为s1本质为指针,64位系统的指针大小为 64 / 8 = 8 字节,故指针大小恒定为8字节,与其地址中内容是什么无关;而s2本质为数组,在定义时就已经确定其大小为10,且每个char型变量的大小为1字节,故s2大小为 1 * 10 = 10 字节。而strlen函数的功能为求字符串的长度,也即字符串的字符个数,从给定地址开始计数,直到遇到字符串结束符’\0’时停止计数,不包含结束符;对于隐式声明的字符数组的大小和长度,请各位自行尝试;

重点二:一句话概括:字符指针可以改变其指向,但字符数组不能重复赋值,仅能在初始化时指定其内容。这是因为字符数组的数组名是常量,常量是不能被赋值的。故s1可以指向其他任意字符串或字符数组s2,而s2不能被重新赋值或指向s1,这将会导致编译失败;

Part 3:

该部分为本篇重中之重,分别定义char *[]型变量s3和char **型变量s4,并且使s4指向s3来完成对指针数组元素的访问;

重点三:对于s3,字符串"apple"的地址为0x00000000004050CD,这个地址也是字符串中第一个字符’a’的地址,而这个地址被保存在地址0x000000000061FDD0中;那这个"s3 = 蚉@“是怎么来的呢?查询"蚉"与”@"的编码,分别为0xCD50与0x40,即我们将s3中保存的地址当作一个字符串来打印了,这样做其实并没有意义,只是印证字符串"apple"的地址0x00000000004050CD被当作一个数保存在地址0x000000000061FDD0中;对于**s4的地址为什么是0x0000000000000061,想必你已经有答案了(提示:查询字符’a’的十六进制编码);

重点四:char *[]为指针数组,注意与char (*)[]的区别,后者为数组指针;前者[]优先级比*高,故本质为数组,后者从左向右先遇到()内*,故本质为指针。教大家一个实用口诀来记忆这两者:指针数组即存放指针的数组,数组指针即指向数组的指针;这样一来作用和本质便一目了然;而char**为二级指针,即指向指针的指针,可以理解为保存某一地址的地址;

重点五:由重点四可知,s3本质为数组,s4本质为指针,故两者大小有别;s3中存放3个字符串的地址,故sizeof(s3)为 8 * 3 = 24 字节,s4恒定为8字节;而sizeof(**s3)与sizeof(**s4)皆为1是因为在两次解引用后,**s3和**s4的类型已不是指针,而是字符类型char,故只占1个字节,这也就解答了为什么**s4的地址为什么是0x0000000000000061而不是字符串"apple"中每个十六进制编码的排列集合;注意,系统给s3中三个字符串分配的内存空间是连续,且存放这三个字符串地址的地址也是连续的。通过计算s3[1]的地址减去s3[0]的地址可得结果为6,即字符串"apple"的长度加1个结束符,同理可计算s3[2]的地址减去s3[1]的地址结果为7,即字符串"orange"的长度加1个结束符;但在有些系统中,给s3中三个字符串分配的内存空间并不是连续的(详见:https://blog.csdn.net/daiyutage/article/details/8604720),具体原因猜想与编译器或操作系统对内存分配的实现有关,有待考证;

重点六:对于s3中每个字符串的访问,有两种方式,例如访问字符串"orange",可以用s3[1]或*(s3 + 1);而对于字符串中字符"r"的访问,可以用s3[1][1],也可用*(s3 + 1)[1],也可用*(*(s3 + 1) + 1);

Part 4:

这一部分说明了如何用二级指针改变一级指针的指向。从两行输出结果可以看出,s5两次的地址都没有变化,都是首次申请空间时系统在堆上分配的内存;而*s5变了,也即s5中保存的值变了;

有一个有趣的现象:使*s5第一次指向字符串"hello world"时,*s5的地址为0x0000000000405012,这个地址是不是似曾相识?往前看,发现s1的地址也是这个,这也就表明,他俩在内存中指向的是同一块地址空间;但第二次指向字符串"thank you"时,得到的地址却和s2的地址不同。请大家思考一下这是为什么?

Part 5:

这一部分与上一部分类似,分别用二级指针s7和一级指针s6来改变s6本身的指向,不再赘述。需要注意的是,s6定义时未初始化,指向一个随机无效内存地址,访问可能会引起程序崩溃。

有错误之处或疑问欢迎私信或评论区留言:)

  • 6
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值