最近的项目中有不少c的程序,在与项目新成员的交流中发现,普遍对于char *s1 和 char s2[] 认识有误区(认为无区别),导致有时出现“难以理解”的错误。一时也不能说得很明白,网上也搜了一下相关文章发现一些写的比较好的,综合了一下当教育资料备用。
char *s1 = "hello";
char s2[] = "hello";
【区别所在】
char *s1 的s1,而指针是指向一块内存区域,它指向的内存区域的大小可以随时改变,而且当指针指向常量字符串时,它的内容是不可以被修改的,否则在运行时会报错。
char s2[]的s2 是数组对应着一块内存区域,其地址和容量在生命期里不会改变,只有数组的内容可以改变
【内存模型】
+-----+ +---+---+---+---+---+---+
s1: | *======> | h | e | l | l | o |\0 |
+-----+ +---+---+---+---+---+---+
+---+---+---+---+---+---+
s2: | h | e | l | l | o |\0 |
+---+---+---+---+---+---+
场景一)
char *s1 = "hello";
char s2[] = "hello";
s2=s1; //编译ERROR
s1=s2; //OK
分析:s2其地址和容量在生命期里不能改变
场景二)
char s2[] = "hello";
char *s1 = s2; //编译器做了隐式的转换 实际为&s2
或
char *s1 = &s2;
分析:以上两个指针复值完全等价,由于编译器会做这个隐式转换也容易导致初学者误认为 char *s 与char s[]是一回事。
另用第二种在一些编译器甚至会报警告信息。
场景三)
char *s1 = "hello";
char s2[] = "hello";
s1[0]='a'; //×运行ERROR( 这一句好像在一些的编译器不会出错,原因待查)
s2[0]='a'; //OK
分析:运行时会报错,原因在于企图改变s1的内容,由于s1指向的是常量字符串,其内容是不可修改的,因此在运行时不会通过。而s2指向的是变量区字符串,可以修改。
场景四)
让我们来给一个指针的指针赋值,在使用某些含char**参数的函数时会用到,场景二的增强版。
char *s1="hello";
char s2[]="hello";
char *s3=s2; //★注意这句必须要★
char **s4=&s3; //s2(char[])要用两步才能完成赋值
char **s5=&s1; //s1(char*) 只需一步
printf("s4=[%s]\n",*s4);//打印结果:s4=[hello]
printf("s5=[%s]\n",*s5);//打印结果:s5=[hello]
分析:这个例子应当说最能反映出char *与char []的差异,但是由于使用场合不多,新人尤其需要注意。
下面是一些char *s1 和 char s2[]相同的地方(同样编译器对char[]做了隐式变化):
1)作为形参完全相同
如:
void function(char *s1);
void function(char s1[]);
2)只读取不修改的时候
如:
char *s1="hello";
char s2[]="hello";
printf("s1[1]=[%c]\n",s1[1]); //s1[1]=[e]
printf("s2[1]=[%c]\n",s2[1]); //s2[1]=[e]
printf("s1=[%s]\n",s1); //s1=[hello]
printf("s2=[%s]\n",s2); //s2=[hello]
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------
7.2 可是我听说 char a[ ] 和 char *a 是一样的。
并非如此。(你所听说的应该跟函数的形式参数有关;参见问题 6.4) 数组不是指针。 数组定义 char a[6] 请求预留 6 个字符的位置, 并用名称 ``a" 表示。也就是说, 有一个称为 ``a" 的位置, 可以放入 6 个字符。 而指针申明 char *p, 请求一个位置放置一个指针, 用名称 ``p" 表示。 这个指针几乎可以指向任何位置: 任何字符和任何连续的字符, 或者哪里也不指(参见问题 5.1 和 1.10)。
一个图形胜过千言万语。声明
char a[] = "hello";
char *p = "world";
将会初始化下图所示的数据结果:
+---+---+---+---+---+---+
a: | h | e | l | l | o |\0 |
+---+---+---+---+---+---+
+-----+ +---+---+---+---+---+---+
p: | *======> | w | o | r | l | d |\0 |
+-----+ +---+---+---+---+---+---+
根据 x 是数组还是指针, 类似 x[3] 这样的引用会生成不同的代码。认识到这一点大有裨益。以上面的声明为例, 当编译器看到表达式 a[3] 的时候, 它生成代码从 a 的位置开始跳过 3 个, 然后取出那个字符. 如果它看到 p[3], 它生成代码找到 ``p" 的位置, 取出其中的指针值, 在指针上加 3 然后取出指向的字符。换言之, a[3] 是 名为 a 的对象 (的起始位置) 之后 3 个位置的值, 而 p[3] 是 p 指向的对象的 3 个位置之后的值. 在上例中, a[3] 和 p[3] 碰巧都是 'l' , 但是编译器到达那里的途径不尽相同。本质的区别在于类似 a 的数组和类似 p 的指针一旦在表达式中出现就会按照不同的方法计算, 不论它们是否有下标。下一问题继续深入解释。 参见问题 1.13。
参考资料: [K&R2, Sec. 5.5 p. 104]; [CT&P, Sec. 4.5 pp. 64-5]。
7.3 那么, 在 C 语言中 ``指针和数组等价" 到底是什么意思 ?
在 C 语言中对数组和指针的困惑多数都来自这句话。说数组和指针 ``等价" 不表示它们相同, 甚至也不能互换。它的意思是说数组和指针的算法定义可以用指针方便的访问数组或者模拟数组。
特别地, 等价的基础来自这个关键定义:
一个 T 的数组类型的左值如果出现在表达式中会蜕变为一个指向数组第一个成员的指针(除了三种例外情况); 结果指针的类型是 T 的指针。
这就是说, 一旦数组出现在表达式中, 编译器会隐式地生成一个指向数组第一个成员地指针, 就像程序员写出了 &a[0] 一样。例外的情况是, 数组为 sizeof 或 & 操作符的操作数, 或者为字符数组的字符串初始值。
作为这个这个定义的后果, 编译器并那么不严格区分数组下标操作符和指针。在形如 a[i] 的表达式中, 根据上边的规则, 数组蜕化为指针然后按照指针变量的方式如 p[i] 那样寻址, 如问题 6.2 所述, 尽管最终的内存访问并不一样。 如果你把数组地址赋给指针:
p = a;
那么 p[3] 和 a[3] 将会访问同样的成员。
参见问题 6.6 和 6.11。
------------------------------------------------------------------------------------------
char *s1 = "hello";
char s2[6] = "hello";
类型指针与类型数组名在很多场合中可等价使用。容易给人造成的印象是两者是等价。
这话不尽然。首先我们要明白这是两个不同的东西。
s1的类型char *,而s2的类型是array of char。
s1初始化为一个指针值,指向一个内存区域,该处有6个字符的数据,
即'h', 'e', 'l', 'l', 'o', '\0'。 在运行过程中,s1的值可改变,指向其他任何允许的地址。
但上面的数据("hello")不会在程序退出之前销毁[注:这是另外一个比较迷惑人的细节],
即使s1变量生命周期结束。
s2初始化为6个字符的数组,也是'h', 'e', 'l', 'l', 'o', '\0'。在运行过程中,s2的内容可改变,
也就是存储在s2中的hello也就"消失"了。
但为什么容易给人造成类型指针与类型数组名可等价的疑惑呢?虽然类型不同,但C规定(为了
追求简洁与灵活性,C假设使用者知道自己代码会有什么结果。)在很多场合下,认为数组名
与类型指针类型兼容。记忆中只有2中情况下,数组名不可等同视为数组指针,&与sizeof操作符。
void foo1(const char *str) {...};
void foo2(int size) {return size};
...
char *s1 = "hello";
char s2[6] = "hello";
foo1(s1); // ok
foo1(s2); // ok
foo1(&s2); // incompatible
foo2(&s2[0]); // ok
s1[0] = 0; // error
s2[0] = 0; // ok
s1 = s2; // ok
s2 = s1; // error
// 下面假设在ia32平台上运行
foo2(sizeof(s1)); // return 4, pointer size
foo2(sizeof(s2)); // return 6, array size
只记得上面的这些内容,不知道对错,与大家共同提高。