C++中的string与char[]、char*详解

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”

  1. 这里的 “abc” 是一个常量,首先会在常量存储区里存储 “abc” 这个常量;
  2. 然后,因为 “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


文章到此基本也就告一段落了,如果有错误的地方还请大家帮忙指出呀,欢迎大家在评论区互相交流,共同进步~

  • 70
    点赞
  • 167
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
C++ string 是一个类,可以用来存储和操作字符串。使用 string 类型需要包含头文件 <string>。 string 类型的特点如下: 1. 可以动态改变字符串的长度。 2. 支持下标访问、迭代器操作和常用的字符串操作函数(如 find、substr 等)。 3. 可以与 C 语言字符串(即以 '\0' 结尾的字符数组)进行互相转换。 下面是一些常用的 string 类型操作: 1. 初始化字符串: ```cpp string str1 = "hello"; // 直接用字符串初始化 string str2("world"); // 用字符数组初始化 string str3(5, 'a'); // 用字符和长度初始化 ``` 2. 获取字符串长度: ```cpp int len = str.length(); // 获取字符串长度 ``` 3. 字符串拼接: ```cpp string str4 = str1 + str2; // 直接使用加号拼接字符串 str1 += str2; // 使用加等于号拼接字符串 ``` 4. 字符串查找: ```cpp int pos = str.find("world"); // 查找子串,返回第一次出现的位置 ``` 5. 字符串截取: ```cpp string substr = str.substr(pos, len); // 截取子串,从 pos 开始,长度为 len ``` 6. 字符串转换为字符数组: ```cpp const char* cstr = str.c_str(); // 获取指向字符数组指针 ``` 7. 字符数组转换为字符串: ```cpp string str5 = "hello"; const char* cstr2 = "world"; string str6 = str5 + cstr2; // 直接使用加号拼接字符串和字符数组 string str7(cstr2); // 使用字符数组初始化 ``` 以上是 string 类型的一些常用操作,具体用法还需要根据实际情况进行调整。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值