2020年7月15日 周三 天气晴 【不悲叹过去,不荒废现在,不惧怕未来】
关于C++里的字符串和字符数组以及字符指针,一直都搞不太明白,今天在这里做个总结,希望能彻底弄懂它们。
一、C++中两种风格的字符串:
- C-风格字符串
- C++引入的string类
1. C-风格字符串
C-风格字符串起源于 C 语言,并在 C++ 中继续得到支持。C-风格字符串实际上是使用 null 字符 ‘\0’ 终止的一维字符数组。下面是C-风格字符串的两种写法:
char a[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
char a[] = "Hello";
这两种写法是等价的,若使用第二种写法,C++ 编译器在初始化数组时会自动把 ‘\0’ 放在字符串的末尾。
2. C++中的string类
C++ 标准库提供了 string 类类型,定义字符串的方法如下:
string a = "Hello";
string类有很多功能,这里就不详细叙述了。
二、C++在定义字符串时,可能会出现以下几种形式:
- string
- char[]C
- const char[]
- char*
- const char*
为啥会有这么多种呢?还不是指针和const搞的鬼~(指针、引用、const真是折磨啊)
我们先来说一说string容易搞错的地方,然后再详细叙述后四种的关系和区别。
1. string
string是一个C++类库中的一个类,其本质是字符数组(char类型的数组)。它包含了对字符串的各种常用操作,它较char*的优势是内容可以动态拓展,以及对字符串操作的方便快捷,用 “+” 号进行字符串的连接是最常用的操作。有下面一段代码:
string a = "hello";
cout << &a << endl;
cout << &a[0] << endl;
cout << *&a[0] << endl;
输出是:
001DFCA8
hello
h
这里或许就有疑问了,a[0] 表示 a 的第一个字符,对第一个字符取地址,为什么得到的不是首字符,而是整个字符串呢?
这是因为,&a[0] 是 char* 类型, cout 会把 char* 当做C-风格字符串处理一直输出直到"\0",而对 &a[0] 解引用 *&a[0] 得到的才是首字符。
2. char[]、const char[]、char*、const char*
在讲它们之前,先看下面这道很经典的题:
char str1[] = "abc";
char str2[] = "abc";
const char str3[] = "abc";
const char str4[] = "abc";
const char *str5 = "abc";
const char *str6 = "abc";
char *str7 = "abc";
char *str8 = "abc";
cout << (str1 == str2) << endl;
cout << (str3 == str4) << endl;
cout << (str5 == str6) << endl;
cout << (str7 == str8) << endl;
要解决这道题,首先要清楚定义字符串时,数据是如何分布的:
(1) char str1[] = “abc”
- 这里的 “abc” 是一个常量,首先会在常量存储区里存储 “abc” 这个常量;
- 然后,因为 “abc” 被赋值给str1[],所以在栈中开辟一段内存,内存大小为4个节点(char数组后会自动加一个’\0’),因此又有一个"abc"被保存在栈中。
同理,str2[]中的"abc"也是保存在栈中,地址不同。
到此,有三个"abc"被保存起来,一个在常量存储区,另外两个在栈中。
插一句,c++内存被分为5个区,分别是栈、堆、静态/全局存储区、常量区、和代码区(详见我的另一篇博客),如下图所示:
(2) const char str3[] = “abc”
对于这种被 const 修饰起来的变量,一般也是被保存在常量存储区,但是,但是对于const 数组来讲,系统不确定符号表是否有足够的空间来存放 const 数组,所以还是为const 数组分配内存的。因此,str3指向的是栈上的"abc"。
同理,str4[] 也是保存在栈中,地址不同。
(3) const char *str5 = “abc”
因为"abc"在常量存储区中保存有一份(即使没保存,这样的操作也会新建一份),这里 str5 定义的时候,就可以开心的直接指向 “abc” 所在的常量区的地址。
同理str6,str7 和 str8 与 const 没有任何关系,const 只是使得 str5 和 str6 无法指向新的字符串常量(也就是新的地址)。
搞清楚以上这些,答案也就不言而喻了:
0
0
1
1
三、数组名char[] 和 数组指针char *
关于数组名和数组指针,我相信很多人都会有疑问,数组名究竟是不是指针?如果不是为什么它表现的行为又和指针那么像呢?现在就来一一解答。
总的来说,char[]与char*与许多相同点,char[] 代表字符数组,可以对应一个字符串,例如:
char *a="string1";
char b[]="string2";
1. 数组名和数组指针的显著不同点:
(1) 数组名 b 可以看作指针常量(只能看作,并不是指针常量,具体代表什么后面会说),对应着数组的首地址,其值不能改变;b 对应的内存区域总是可写。
(2) 数组指针 a 是变量,值可以改变;a 指向的区域有时可写,有时只读。
比如:
char *a="string1"; //编译器会报警告
char b[]="string2";
gets(a); //试图将读入的字符串保存到a指向的区域,运行崩溃!
gets(b) //正确
解释:a指向的是一个字符串常量,即指向的内存区域只读,一旦尝试通过a改变字符串常量的值就会使程序崩溃! 因此,char * a=“string1”; 这句代码虽然不是错的,但是编译器会报警告,规范的写法是在前面加上 const;b始终指向他所代表的数组(保存在栈区)在内存中的位置,始终可写。
但是如果加上一句代码 a=b; 结果又是如何呢?
char * a="string1";
char b[]="string2";
a=b; //a,b指向同一个区域
gets(a); //正确
printf("%s",b); //会出现gets(a)时输入的结果
解释:a的值变成了是字符数组首地址,即&b[0],根据之前的叙述我们可以知道,字符数组b被保存在栈区,该区域可读可写,因此 gets(a) 不会再报错。
2. 数组名和数组指针的其它不同点
(1) 数组名指代一种数据结构:数组
有以下代码:
char str[10];
cout << sizeof(str);
10
打印结果为整个数组的大小10,这是因为数组名 str 的内涵为一种数据结构,即一个长度为 10 的 char 型数组,所以 sizeof(str) 的结果为这个数据结构占据的内存大小:10字节。如果C/C++程序可以这样写:
char[10] str;
cout << sizeof(str);
这就比较清晰了,str 被定义为 char[10] 这种数据结构的一个实例。
另外, cout 打印字符数组名会得到整个字符串(仅字符数组):
char str[] = "I Love U";
int intArray[5] = {1,2,3,4,5};
cout << str << endl;
cout << intArray << endl;
I Love U
0x7ec2fc616c50
这是因为, cout 对于 char[] 有重载,这是一个特例,只有字符串数组才会输出整个数组,如果是 int 或其它类型的数组,只会输出数组的首地址。
(2) 数组名作为函数形参时,在函数体内,其失去了本身的内涵,仅仅只是一个指针。在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。
举个栗子:
void fun1 ( char *p1, char p2[]){
printf("%s %d %d\n",p1,p1,&p1);
printf("%s %d %d\n",p2,p2,&p2);
p2="asdf"; //通过! 说明p2不是常量!
printf("%s %d %d\n",p2,p2,&p2);
}
void main(){
char a[]="Hello";
fun1(a,a);
}
因此,在作为函数的形参时,char [] 被当做 char * 来处理,两种写法是完全等效的。
参考文献
https://blog.csdn.net/ksws0292756/article/details/79432329
https://blog.csdn.net/y519476132/article/details/9866001
https://blog.csdn.net/u013654125/article/details/79758286#commentBox
文章到此基本也就告一段落了,如果有错误的地方还请大家帮忙指出呀,欢迎大家在评论区互相交流,共同进步~