C++ 练气期之一文看懂字符串

C++ 练气期之细聊字符串

1. 概念

程序不仅仅用于数字计算,现代企业级项目中更多流转着充满了烟火气的人间 话语。这些 话语,在计算机语言称为 字符串

从字面上理解 字符串,类似于用一根竹签串起了很多 字符,让人很容易想起 冰糖葫芦

字符串的基本组成元素是字符,可以认为字符串就是字符类型的数组。

量变总会引起质变, 字符串是由 字符的量变演化出的 新类型2 者在 数据含义存储结构都有着本质上区别。

1.1 数据含义

C++字符类型当成 整型数据类型看待。如下代码,当把 A赋值给 myChar时, 编译器先获取 A的底层 ASCII 编码,然后再把编码值赋值给 myChar

int myChar='A';
cout<

如下代码,编译器先找到 97对应的字符,然后再赋值给 myChar&#x5B57;&#x7B26;&#x7C7B;&#x578B;&#x6574;&#x578B;&#x7C7B;&#x578B;语法层面有差异,在底层, C++一视同仁。

char myChar=97;
cout<

所以,用于整型数据类型的运算符都可以用于 char类型。

char myChar='B';
char myChar_='A';
int res=myChar+myChar_;
cout<myChar_;
cout<

输出结果:

加操作:131
减操作:1
乘操作:4290
除操作:1
关系操作:1

虽然,字符串可看成是字符组成的数组,但是,应该把字符串当成一个独立的整体,其数据含义更贴近现实意义:

  • &#x5B57;&#x7B26;是单一词,所能表达的语义非常有限。
  • &#x5B57;&#x7B26;&#x4E32;则是由许多字符组成的语句,可用来表达丰富的语义。如:可以是姓名、可以是问候、可以情感表达、可以是提示……根据使用的上下文环境,字符串有其自己特定的现实意义。

1.2 存储结构

&#x5B57;&#x7B26;常量必须用 &#x5355;&#x5F15;&#x53F7;包起来, &#x5B57;&#x7B26;直接存储在变量中。

char myChar='A';

&#x5B57;&#x7B26;&#x4E32;的存储方案比 &#x5B57;&#x7B26;复杂很多, C++支持两种 &#x5B57;&#x7B26;&#x4E32;的存储方案:

  • C语言风格的存储。
  • C++语言的对象存储。

下面深入了解这 2 种存储方案的区别。

2. C 风格的字符串

C++可以直接延用 C语言中的 2种字符串存储方案:

2.1 数组

&#x6570;&#x7EC4;存储能较好地 &#x8BE0;&#x91CA;字符串是由字符所组成的概念。

使用数组存储时,并不能简单如下代码所示。对于开发者而言,可能想表达的是输出一句 HTLLO问候语。但在实际执行时,输出时可能不仅只是 HELLO

char myStr[5]= {'H','E','L','L','O'};
cout<

为什么会输出更多信息?

因为 cout底层逻辑在输出字符数组时,会以一个特定标识符 \0作为结束标志。 cout在输出 myStr字符数组的数据时,如果没有遇到开发者提供的 \0结束符号,则会在数组的存储范围之外寻找 \0符号。

上述代码虽然能得到 HELLO,那是因为在未使用的存储空间中, \0符号很常见。

显然,不能总是去碰运气。所以,在使用字符数组时描述字符串时,则需要在适当位置添加字符串结束标识符 \0

因结束符占用了一个存储位, HELLO需要 5个存储位,在声明数组时,需要注意数组的实际长度为 6

char myStr[6]= {'H','E','L','L','O','\0'};
cout<

执行下面的代码,查看输出结果,想想为什么输出结果是 HEL

char myStr[6]= {'H','E','L','\0','O','\0'};
cout<

原因很简单, cout在遇到第一个 \0时,就认定字符串到此结束了。

这里有一个问题,如果实际的字符个数大于数组声明的长度,会出现什么情况?

char myStr[3]= {'H','E','L','L','O','\0'};
cout<

如果出现上述代码,说明,你的数组没有学太好。 C++规定在使用 {}进行字面值初始化数组时, {}内的实际数据个数不能大于数组声明的长度。

C++ 练气期之一文看懂字符串

当不确定字符串的长度时,可以采用省略 []中数字的方案。

char myStr[]= {'H','E','L','L','O','\0'};
cout<

数组存储方案同样具有数组所描述的操作能力,最典型的就是使用下标遍历数组。

char myStr[6]= {'H','E','L','L','O','\0'};
for(int i=0;i

输出结果:

H
E
L
L
O

在使用上述代码时,有 2 个地方需要注意:

  • 当下标定位到 \0数据位时,并不能识别 \0是字符串结束符,它只是纯粹当成一个一个 &#x5B57;&#x7B26;输出,不具有字符串语义。
char myStr[8]= {'H','E','L','L','O','\0','M','Y'};
for(int i=0;i

输出结果:

H
E
L
L
O

M
Y
  • 因是静态数组声明方案,可以动态计算数组的长度。
char myStr[8]= {'H','E','L','L','O','\0','M','Y'};
cout<

输出结果:

数组的长度:8
H
E
L
L
O

M
Y

使用 sizeof(myStr)计算出来的是创建数组时指定的物理存储长度。

所以,这里要注意:

  • 通过 &#x7ED3;&#x675F;&#x7B26;描述 &#x5B57;&#x7B26;&#x4E32;是编译器层面上的约定。
  • 遍历时,实质是底层指针移动,这时,编译层面的字符串概念在这里不复存在。也就是说不存在遇到 \0,就认为输出结束。

2.2 字符串常量

上述字符串的描述方式,略显繁琐,因需要时时注意添加 \0C当然也会想到这一点,可以使用字符串常量简化字符串数组的创建过程。

char myStr[8]="HELLO";
cout<

字符串常量需要使用 &#x53CC;&#x5F15;&#x53F7;括起来。

当执行如下代码时,会出现错误。

C++ 练气期之一文看懂字符串

错误提示,数组长度不够存储给定的数据。可能要问!

数组长度是 5,实际数据 HELLO的长度也是 5,不是刚刚好吗。

别忘记了,完整的字符串是包括结束符 \0的。在使用字符常量赋值时,编译器会在字符串常量的尾部添加 \0,再存储到数组中,所以数组的长度至少是: &#x5B57;&#x7B26;&#x4E32;&#x5E38;&#x91CF;&#x7684;&#x957F;&#x5EA6;+1

如下的代码方能正确编译运行:

char myStr[6]="HELLO";
cout<

&#x5B57;&#x7B26;&#x4E32;&#x5E38;&#x91CF;只是上述 {}赋值的语法简法版,其它的操作都是相同的,如循环遍历。

char myStr[6]="HELLO";
for(int i=0;i

注意,如下的代码是错误的。

char myStr[6]="HELLO";
myStr[0]="S";

"S"表示一个字符串,至少包括了 'S''\0' 2 个字符,更重要的是 "S"返回的是内存地址。

2.3 字符串操作

C语言风格的字符串提供了 cstring库,此库提供大量函数用来操作字符串,常见函数如下:

  • strcat:字符串拼接。
  • strcpy:字符串复制。
  • strcmp:字符串比较。
  • strstr:字符串查找。
  • ……

下面介绍几个 &#x5B57;&#x7B26;&#x4E32;的常见操作。

2.3.1 复制操作

C++中数组之间是不能直接赋值的,如下是错误的:

char myStr[6]="HELLO";
char myStr_[6];
//错误
myStr_=myStr;

可以使用 cstring库中的 strcpy 函数:

#include
#include
using namespace std;
int main(int argc, char** argv) {
    char myStr[6]="HELLO";
    char myStr_[6];
    strcpy(myStr_,myStr);
    cout<

strcpy需要 2 个参数:

  • 目标字符串指针。
  • 源字符串指针。

其作用是,把源字符串复制给目标字符串。

2.3.2 长度操作

使用 strlen函数计算字符串的长度。

char myStr[10]="HELLO";
cout<

sizeof计算出来的长度区别:

  • sizeof创建数组时,分配到的实际物理空/间的长度。
char myStr[10]="HEL\0LO";
cout<

输出结果:

10
3
  • strlen计算出的是 &#x5B57;&#x7B26;&#x6570;&#x7EC4;中字符串的实际长度,即遇到 \0结束符前所有字符的长度。如下代码:
char myStr[10]="HEL\0LO";
cout<

输出结果是: 3\0结束前的字符串是 HEL

2.3.3 拼接操作

字符串常量之间可以使用空白(空格、换行符、制表符)字符自动完成拼接。

cout<

需要注意的地方是,第一个字符串常量和第二个字符串常量的拼接处直接连接,中间不保留空白符。

使用 strcat进行拼接。

#include
#include
using namespace std;
int main(int argc, char** argv) {
    char names[10]="Hello";
    char address[10]="changsha";
    strcat(names,address);
    cout<

strcat是把第二字符串连接到第一个字符串后尾部。

2.3.4 字符串比较

&#x5B57;&#x7B26;能够直接比较, &#x5B57;&#x7B26;&#x4E32;则不能。如果相互之间有比较的需求时,可以使用 strcmp 函数。

#include
#include

using namespace std;
int main(int argc, char** argv) {
    char names[10]="zs";
    char names_[10]="ls";
    cout<

返回值的语义:

  • 如果返回值为小于 0,则 names 小于 address
  • 如果返回值为 等于 0,则 names 等于 address
  • 如果返回值大于 0,则 names 大于 address

2.3.5 子字符串查找

在原子符串中查找给定的子字符串出现的位置,返回此位置的指针地址。

#include
#include
using namespace std;
int main(int argc, char** argv) {
    char srcStr[15]="Hello World";
    char subStr[5]="llo";
    cout<

如果没有查找到,则返回 null

cstring库提供了大量处理字符串的函数,如大小写转换函数 tolowertoupper等。本文仅介绍几个常用函数,需要时,可查阅文档,其使用并不是很复杂。

3. C++字符串对象

C++除了支持 C风格的字符串,因其面向对象编程的特性,内置有 string类,可以使用此类创建字符串对象。

string类定义在 string头文件中。

如下代码可以初始化字符串对象:

//空字符串
string str1;
//字符串常量直接赋值
string str2="Hello";
string str3 {"this"};
string str4("Hi");

string为了支持 uncode字符编码,底层为每一个字符提供了 1~4个字节的存储空间。

所以,可以用来存储中文:

string str="中国人";
cout<

除了支持 char、还支持 wchar_tchar16_tchar32_t数据类型。

string类中封装了很多处理字符串的相关函数(方法),在 cstring库中可以找到对应的函数。因得益于 &#x7C7B;设计的优秀特性, string类中封装的功能体相比较 cstring库,更丰富、更全面。

下面介绍几个常用的功能,其它可以查阅文档。

获取字符串的常规信息:如长度、是否为空……

string str="Hello World";
cout<

输出结果:

11
11
0
4611686018427387897
11

数据维护(增、删除、改、查)方法:

  • clear:清除所有内容。
string str="Hello World";
str.clear();
cout<
  • insert:插入字符。
string str="Hello World";
string str_="Hi";
//第一个参数指定插入位置,第二参数指定需要插入的字符串
str.insert(3,str_);
cout<
  • erase:删除指定范围内的所有字符。
string str="Hello World";
//第一个参数:指定删除的起始位置,第二个参数:指定删除的结束位置
string str_= str.erase(1,3);
cout<
  • push_backappend追加字符和字符串。
string str="Hello World";
//只能追加字符串,不能追加字符
str.append("OK");
cout<
  • pop_back:删除最后一个字符。
string str="Hello World";
str.pop_back();
cout<
  • compare:比较两个字符串。
string str="Hello World";
string str_="Hello";
int res= str.compare(str_);
//返回值的语义和 strcmp一样。
  • copy:字符串的拷贝。
//源字符串
string foo("quuuux");
//目标字符串,数组形式
char bar[7];
//第一个参数,目标字符串,第二参数,向目标字符串复制多少
foo.copy(bar, sizeof bar);
bar[6] = '\0';
cout << bar << '\n';
//输出:quuuux

总结下来,字符串的存储方案有 2 种:

  • 数组形式。
  • 字符串对象。

4. cin 输入字符串

如果需要使用 &#x4EA4;&#x4E92;&#x8F93;&#x5165;方式获取用户输入的数据,可以直接使用 cin

string str;
char bar[7];
cin>>str;
cin>>bar;
cout<

如上代码,如果用户输入 this is,因字符串有 &#x7A7A;&#x767D;&#x5B57;&#x7B26;。则会出现获取到错误数据的问题。

C++ 练气期之一文看懂字符串

原因解析:

cin接受用户输入时,以用户输入的 &#x6362;&#x884C;&#x7B26;作为结束标识。用户输入 this is时,遇到字符串的中间空白字符(空格、制表符、换行符)时,就认定输入结束,仅把 this存储到 str中,并不是 this is

cin内置有缓存器,会把 is缓存起来,也就是说 cin是以单词为单位进行输入的。

当再次使用 cin接受用户输入时, cin会检查到缓存器中已经有数据,会直接把 is赋值给 bar变量。

如果需要以行为单位进行输入时,可以使用:

  • cin.get()方法。
  • cin.getline()方法。

上述 2 个方法主要用于字符串数组的赋值。

两者在使用时,都可以接受 2 个参数:

  • 目标字符串。
  • 用来限制输入的大小。
char str[20];
cin.get(str,10);
cout<

如下代码,能实现相同的效果。

char str[20];
cin.getline(str,10);
cout<

两者也有区别, cin.get()不会丢弃用户输入字符串时的结束符。在连续使用 cin.get有可能出现问题,如下代码:

char str[20];
char str_[20];
//第一次输入
cin.get(str,10);
cout<

执行效果:

C++ 练气期之一文看懂字符串

第二次接受用户输入的过程根本没出现。

原因是第一次接受用户输入后, cin.get缓存了用户输入的换行符。在第二次接受用户输入时, cin会首先检查缓存器中是否有数据,发现有 &#x6362;&#x884C;&#x7B26;,直接结束输入。

解决方案,手动清除缓存器的数据。

char str[20];
char str_[20];
cin.get(str,10);
cout<

cin.getline在接受用户输入后,不会保留换行符,所以可以用于连续输入。如下代码:

char str[20];
char str_[20];
//第一次输入
cin.getline(str,10);
cout<

C++ 练气期之一文看懂字符串

如果要使用 cin输入一行字符串,并赋值给字符串对象,则需要使用全局 getline函数。

//字符串对象
string str;
//第一个参数:cin对象  第二个参数:字符串对象
getline(cin,str);
cout<

5. 总结

本文主要讲解了 C++字符串的 2种存储方案,一个是 C语言风格的数组存储方案,一个是 C++对象存储方案。

因存储方案不同,其操作函数的提供方式也不相同。

Original: https://www.cnblogs.com/guo-ke/p/16426548.html
Author: 一枚大果壳
Title: C++ 练气期之一文看懂字符串

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/583919/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球