char *str 与char str[]的区别与联系
常用以下两种方式定义字符串:一种是字符数组,另一种是指向字符串的指针。
文章目录
- char *str 与char str[]的区别与联系
- 一、基本概念区别
- 二、名称含义区别
- 三、底层属性区别
- 1.char *str = "abcdefgh"
- 2.char str[] = "stuvwxyz"
- 四、作为函数参数
- 五、作函数返回值
- 六、其他区别
一、基本概念区别
char *str:声明的是一个字符型指针,这个指针可指向任何字符串常量或某个字符变量。
char str[] :声明的是一个字符型数组,数组的内容可以是任何内容,末尾加上’\0’ 后就是是字符串。
二、名称含义区别
char *str:str是一个字符型指针变量,这个指针变量用来保存某个字符变量或常量的地址(定义字符串时,保存首字符地址)
char str[] :str是数组名,数组名的值是一个指针常量,也就是该数组第一个元素的地址。数组名用于表达式中时,编译器将它视为一个指针常量。
对于数组名的理解,可参见C语言的经典书籍《Pointers On C》(Kenneth.A Reek) 【US】中文版,译名:《C和指针》,其中对数组名的理解有这样一段描述:指针常量指向的是内存中数组的起始位置,如果要修改这个指针常量,唯一可行的操作就是把数组移动到内存其他位置,但是,当程序完成链接之后,内存中数组的位置是固定的,所以程序运行时,再想要移动数组为时已晚了,因此数组名的值是一个指针常量。
三、底层属性区别
数组和指针都可以在定义时使用字符串常量对其进行初始化:
char *str = “abcdefgh”;
char str[] = “stuvwxyz”;
两者看似一样,但底层机制有很大区别。
1.char *str = “abcdefgh”
char *str = “abcdefgh”,定义了一个指向字符串常量的指针,编译过后,是不运行再通过指针修改字符串的值,但是str变量却可以改变指向。见下代码:字符串"abcdefg"作为常量保存在静态存储区,其位置在编译后已经固定,str1和str2指向同一个字符串常量首字符的地址,所以打印的值和地址都是一样的。此时改变str指向,使其指向另一个字符串常量"ABCDEFG",值和地址都改变。
void main()
{
char *str1 = "abcdefg";
char *str2 = "abcdefg";
printf("*str1 = %s \t &str1[0] = %p \n",str1,&str1[0]);
printf("*str2 = %s \t &str2[0] = %p \n",str2,&str2[0]);
str2 = "ABCDEFG";
printf("*str2 = %s \t &str2[0] = %p \n",str2,&str2[0]);
}
运行结果:
因为在ANSIC中,初始化指针时所创建的字符串常量被定义为只读,如果试图通过指针修改字符串的值,会出现未定义错误,所以如果写成:str2[1] = ‘B’,则会出现编译没问题,运行出现段错误。
因此在这种定义字符串的方式中,我们可以将str看作常量指针,因为常量指针定义完成后不可修改指向地址中的值,但可修改其指向的地址。
值得一提的是,在实际项目中,为了防止这个指针变量乱指方向,一般在定义时会加上const修饰这个变量:
char *const str = “abcdefgh”;如此一来,其指向也就不能变了,用该变量特指该字符串常量。
2.char str[] = “stuvwxyz”
char str[] = “stuvwxyz”:声明的是一个字符型数组,数组的内容可以是任何内容,末尾加上’\0’ 后就是是字符串。这种定义方式,数据保存全局数据区或堆栈区,可以看到虽然str1和str2的内容字符串都一样,但是地址却不一样,所以大胆推测其内容应该是可以修改的,验证代码如下:
void main()
{
char str1[] = "stuvwxyz";
char str2[] = "stuvwxyz";
printf("*str1 = %s \t &str1[0] = %p \n",str1,&str1[0]);
printf("*str2 = %s \t &str2[0] = %p \n",str2,&str2[0]);
str2[1] = 'T';
printf("*str2 = %s \t &str2[0] = %p \n",str2,&str2[0]);
}
运行结果:
可以看到str2的第二个字符已经变成大写’T’,但是如果写成:str2 = ‘STUVWXYZ’,则会出现编译问题。因此关于数组名str,我们可看为一个指针常量,因为指针常量定义完成后可修改指向地址中的值但不可修改其指向。
四、作为函数参数
二者作为函数参数时的特征几乎无区别,这主要体现在以下两方面:
1:数组名作为参数传递给函数时,实际上传递给函数的是指向数组第一个字符的指针。因此都可以看做是指针作为参数传递,对指针参数执行间接访问操作是允许函数修改原先的数组元素的,但是函数所接收到的参数是原参数的一份拷贝,所以函数对参数进行操作而不会影响实际的参数,说明白点就是函数内无法改变指针参数的指向,但可以改变指针参数所指向的值。
2:数组形参即可声明为数组形式,也可申明为指针形式,这两种申明形式只有当它们作为函数形参时才是相等的。
#include<stdio.h>
#include<stdlib.h>
void fun(char *s1,char s2[]) //两种声明形式都可以
{
s2[1] = s1[1];
s1 = "Hello world!";
}
void main()
{
char str2[] = "stuvwxyz";
char *str1 = "ABCDEFG";
fun(str1,str2);
printf("str1 = %s \t str2 = %s \n",str1,str2);
}
结果:s1指向没该变,s2第二个元素已改变
提示:如果要在函数中改变传入的指针实参的值需要解引用或使用二级指针:
五、作函数返回值
都可以通过名称作为实参传入函数内部,但其基本属性保持不变,但作为函数返回值返回时候,直接在函数内部定义字符串数组char str[],然后返回str是不行的,因为此时定义的字符串数组是在栈区,函数结束后该函数栈区被释放,str指向的区域为空,返回值是无效的,如下,函数fun返回str1是无效的,返回str3是有效的。
#include <stdio.h>
#include <stdlib.h>
char *fun()
{
char str1[] = "stuvwxyz";
char *str3 = "abcdefghijk";
return str1;
}
int main(int argc, char **argv)
{
char *str = fun(2);
printf("str = %s \n",str);
return 0;
}
其他情况下要在函数内使用数组或指针,需要用new或者malloc函数申请堆空间,然后在外部使用之后释放,
六、其他区别
一般情况下数组名和指针可以通用,但是除了上述区别以外还有两个例外:
1.sizeof(数组名字)得到的是数组大小所占字节空间;sizeof(指针变量名)得到的是指针本身所占的字节空间。
2.&数组名:得到指向整个数组的指针,&数组名+1会跳跃整个数组;&指针变量名得到指向指针变量的地址。