C++ 练气期之一文看懂字符串
C++ 练气期之细聊字符串
1. 概念
程序不仅仅用于数字计算,现代企业级项目中更多流转着充满了烟火气的人间话语。这些话语,在计算机语言称为字符串。
从字面上理解字符串,类似于用一根竹签串起了很多字符,让人很容易想起冰糖葫芦。
字符串的基本组成元素是字符,可以认为字符串就是字符类型的数组。
量变总会引起质变,字符串是由字符的量变演化出的新类型, 2 者在数据含义和存储结构都有着本质上区别。
1.1 数据含义
C++把字符类型当成整型数据类型看待。如下代码,当把A赋值给myChar时, 编译器先获取A的底层 ASCII 编码,然后再把编码值赋值给myChar。
int myChar='A';
cout<<myChar;
//输出:65
如下代码,编译器先找到97对应的字符,然后再赋值给myChar,字符类型和整型类型语法层面有差异,在底层,C++一视同仁。
char myChar=97;
cout<<myChar;
//输出:a
所以,用于整型数据类型的运算符都可以用于char类型。
char myChar='B';
char myChar_='A';
int res=myChar+myChar_;
cout<<"加操作:"<<res<<endl;
res=myChar-myChar_;
cout<<"减操作:"<<res<<endl;
res=myChar*myChar_;
cout<<"乘操作:"<<res<<endl;
res=myChar/myChar_;
cout<<"除操作:"<<res<<endl;
bool is=myChar>myChar_;
cout<<"关系操作:"<<is<<endl;
输出结果:
加操作:131
减操作:1
乘操作:4290
除操作:1
关系操作:1
虽然,字符串可看成是字符组成的数组,但是,应该把字符串当成一个独立的整体,其数据含义更贴近现实意义:
- 因
字符是单一词,所能表达的语义非常有限。 字符串则是由许多字符组成的语句,可用来表达丰富的语义。如:可以是姓名、可以是问候、可以情感表达、可以是提示……根据使用的上下文环境,字符串有其自己特定的现实意义。
1.2 存储结构
字符常量必须用单引号包起来,字符直接存储在变量中。
char myChar='A';
字符串的存储方案比字符复杂很多,C++支持两种字符串的存储方案:
C语言风格的存储。C++语言的对象存储。
下面深入了解这 2 种存储方案的区别。
2. C 风格的字符串
C++可以直接延用C语言中的2种字符串存储方案:
2.1 数组
数组存储能较好地诠释字符串是由字符所组成的概念。
使用数组存储时,并不能简单如下代码所示。对于开发者而言,可能想表达的是输出一句HTLLO问候语。但在实际执行时,输出时可能不仅只是HELLO。
char myStr[5]= {'H','E','L','L','O'};
cout<<myStr<<endl;
为什么会输出更多信息?
因为cout底层逻辑在输出字符数组时,会以一个特定标识符\0作为结束标志。cout在输出 myStr字符数组的数据时,如果没有遇到开发者提供的\0结束符号,则会在数组的存储范围之外寻找\0符号。
上述代码虽然能得到HELLO,那是因为在未使用的存储空间中,\0符号很常见。
显然,不能总是去碰运气。所以,在使用字符数组时描述字符串时,则需要在适当位置添加字符串结束标识符\0。
因结束符占用了一个存储位,HELLO需要5个存储位,在声明数组时,需要注意数组的实际长度为 6。
char myStr[6]= {'H','E','L','L','O','\0'};
cout<<myStr<<endl;
//输出结果:HELLO
执行下面的代码,查看输出结果,想想为什么输出结果是HEL?
char myStr[6]= {'H','E','L','\0','O','\0'};
cout<<myStr<<endl;
//输出结果:HEL
原因很简单,cout在遇到第一个 \0时,就认定字符串到此结束了。
这里有一个问题,如果实际的字符个数大于数组声明的长度,会出现什么情况?
char myStr[3]= {'H','E','L','L','O','\0'};
cout<<myStr<<endl;
如果出现上述代码,说明,你的数组没有学太好。C++规定在使用{}进行字面值初始化数组时,{}内的实际数据个数不能大于数组声明的长度。

当不确定字符串的长度时,可以采用省略[]中数字的方案。
char myStr[]= {'H','E','L','L','O','\0'};
cout<<myStr<<endl;
数组存储方案同样具有数组所描述的操作能力,最典型的就是使用下标遍历数组。
char myStr[6]= {'H','E','L','L','O','\0'};
for(int i=0;i<6;i++){
cout<<myStr[i]<<endl;
}
输出结果:
H
E
L
L
O
在使用上述代码时,有 2 个地方需要注意:
- 当下标定位到
\0数据位时,并不能识别\0是字符串结束符,它只是纯粹当成一个一个字符输出,不具有字符串语义。
char myStr[8]= {'H','E','L','L','O','\0','M','Y'};
for(int i=0;i<8;i++){
cout<<myStr[i]<<endl;
}
输出结果:
H
E
L
L
O
M
Y
- 因是静态数组声明方案,可以动态计算数组的长度。
char myStr[8]= {'H','E','L','L','O','\0','M','Y'};
cout<<"数组的长度:"<<sizeof(myStr)<<endl;
for(int i=0;i<sizeof(myStr);i++){
cout<<myStr[i]<<endl;
}
输出结果:
数组的长度:8
H
E
L
L
O
M
Y
使用
sizeof(myStr)计算出来的是创建数组时指定的物理存储长度。
所以,这里要注意:
- 通过
结束符描述字符串是编译器层面上的约定。 - 遍历时,实质是底层指针移动,这时,编译层面的字符串概念在这里不复存在。也就是说不存在遇到
\0,就认为输出结束。
2.2 字符串常量
上述字符串的描述方式,略显繁琐,因需要时时注意添加\0。C当然也会想到这一点,可以使用字符串常量简化字符串数组的创建过程。
char myStr[8]="HELLO";
cout<<myStr<<endl;
//输出结果:HELLO
字符串常量需要使用双引号括起来。
当执行如下代码时,会出现错误。

错误提示,数组长度不够存储给定的数据。可能要问!
数组长度是5,实际数据HELLO的长度也是5,不是刚刚好吗。
别忘记了,完整的字符串是包括结束符\0的。在使用字符常量赋值时,编译器会在字符串常量的尾部添加\0,再存储到数组中,所以数组的长度至少是:字符串常量的长度+1。
如下的代码方能正确编译运行:
char myStr[6]="HELLO";
cout<<myStr<<endl;
//输出:HELLO
字符串常量只是上述{}赋值的语法简法版,其它的操作都是相同的,如循环遍历。
char myStr[6]="HELLO";
for(int i=0;i<sizeof(myStr);i++){
cout<<myStr[i]<<endl;
}
注意,如下的代码是错误的。
char myStr[6]="HELLO";
myStr[0]="S";
"S"表示一个字符串,至少包括了'S'和'\0' 2 个字符,更重要的是 "S"返回的是内存地址。
2.3 字符串操作
C语言风格的字符串提供了cstring库,此库提供大量函数用来操作字符串,常见函数如下:
strcat:字符串拼接。strcpy:字符串复制。strcmp:字符串比较。strstr:字符串查找。- ……
下面介绍几个字符串的常见操作。
2.3.1 复制操作
C++中数组之间是不能直接赋值的,如下是错误的:
char myStr[6]="HELLO";
char myStr_[6];
//错误
myStr_=myStr;
可以使用cstring库中的 strcpy 函数:
#include <iostream>
#include <cstring>
using namespace std;
int main(int argc, char** argv) {
char myStr[6]="HELLO";
char myStr_[6];
strcpy(myStr_,myStr);
cout<<myStr_<<endl;
return 0;
}
strcpy需要 2 个参数:
- 目标字符串指针。
- 源字符串指针。
其作用是,把源字符串复制给目标字符串。
2.3.2 长度操作
使用 strlen函数计算字符串的长度。
char myStr[10]="HELLO";
cout<<strlen(myStr)<<endl;
//输出结果:5
和sizeof计算出来的长度区别:
sizeof创建数组时,分配到的实际物理空/间的长度。
char myStr[10]="HEL\0LO";
cout<<sizeof(myStr)<<endl;
cout<<strlen(myStr)<<endl;
输出结果:
10
3
strlen计算出的是字符数组中字符串的实际长度,即遇到\0结束符前所有字符的长度。如下代码:
char myStr[10]="HEL\0LO";
cout<<strlen(myStr)<<endl;
输出结果是:3。\0结束前的字符串是HEL。
2.3.3 拼接操作
字符串常量之间可以使用空白(空格、换行符、制表符)字符自动完成拼接。
cout<<"this is a test" "hello world";
//输出:this is a testhello world
需要注意的地方是,第一个字符串常量和第二个字符串常量的拼接处直接连接,中间不保留空白符。
使用strcat进行拼接。
#include <iostream>
#include <cstring>
using namespace std;
int main(int argc, char** argv) {
char names[10]="Hello";
char address[10]="changsha";
strcat(names,address);
cout<<names;
return 0;
}
//输出:Hellochangsha
strcat是把第二字符串连接到第一个字符串后尾部。
2.3.4 字符串比较
字符能够直接比较,字符串则不能。如果相互之间有比较的需求时,可以使用 strcmp 函数。
#include <iostream>
#include <cstring>
using namespace std;
int main(int argc, char** argv) {
char names[10]="zs";
char names_[10]="ls";
cout<<strcmp(names,names_);
return 0;
}
//输出结果:1
返回值的语义:
- 如果返回值为小于
0,则names小于address。 - 如果返回值为 等于
0,则names等于address。 - 如果返回值大于
0,则names大于address。
2.3.5 子字符串查找
在原子符串中查找给定的子字符串出现的位置,返回此位置的指针地址。
#include <iostream>
#include <cstring>
using namespace std;
int main(int argc, char** argv) {
char srcStr[15]="Hello World";
char subStr[5]="llo";
cout<<strstr(srcStr,subStr);
return 0;
}
//输出:llo World
如果没有查找到,则返回null。
cstring库提供了大量处理字符串的函数,如大小写转换函数tolower和toupper等。本文仅介绍几个常用函数,需要时,可查阅文档,其使用并不是很复杂。
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<<str<<endl;
//输出:中国人
除了支持
char、还支持wchar_t、char16_t、char32_t数据类型。
在string类中封装了很多处理字符串的相关函数(方法),在cstring库中可以找到对应的函数。因得益于类设计的优秀特性,string类中封装的功能体相比较cstring库,更丰富、更全面。
下面介绍几个常用的功能,其它可以查阅文档。
获取字符串的常规信息:如长度、是否为空……
string str="Hello World";
cout<<str.size()<<endl;
cout<<str.length()<<endl;
//是否为空
cout<<str.empty()<<endl;
//能存储的最大长度
cout<<str.max_size()<<endl;
//容量
cout<<str.capacity()<<endl;
输出结果:
11
11
0
4611686018427387897
11
数据维护(增、删除、改、查)方法:
clear:清除所有内容。
string str="Hello World";
str.clear();
cout<<str<<endl;
//没有任何内容输出
insert:插入字符。
string str="Hello World";
string str_="Hi";
//第一个参数指定插入位置,第二参数指定需要插入的字符串
str.insert(3,str_);
cout<<str<<endl;
//输出结果:HelHilo World
erase:删除指定范围内的所有字符。
string str="Hello World";
//第一个参数:指定删除的起始位置,第二个参数:指定删除的结束位置
string str_= str.erase(1,3);
cout<<str_<<endl;
//输出:Ho World
push_back、append追加字符和字符串。
string str="Hello World";
//只能追加字符串,不能追加字符
str.append("OK");
cout<<str<<endl;
//只能以字符为单位追加
str.push_back('O');
cout<<str<<endl;
//输出结果:
//Hello WorldOK
//Hello WorldOKO
pop_back:删除最后一个字符。
string str="Hello World";
str.pop_back();
cout<<str<<endl;
//输出结果:Hello Worl
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 输入字符串
如果需要使用交互输入方式获取用户输入的数据,可以直接使用 cin。
string str;
char bar[7];
cin>>str;
cin>>bar;
cout<<str<<endl;
cout<<bar<<endl;
如上代码,如果用户输入this is,因字符串有空白字符。则会出现获取到错误数据的问题。

原因解析:
cin接受用户输入时,以用户输入的换行符作为结束标识。用户输入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<<str<<endl;
//输入: this is 输出:this is
如下代码,能实现相同的效果。
char str[20];
cin.getline(str,10);
cout<<str<<endl;
两者也有区别,cin.get()不会丢弃用户输入字符串时的结束符。在连续使用 cin.get有可能出现问题,如下代码:
char str[20];
char str_[20];
//第一次输入
cin.get(str,10);
cout<<str<<endl;
//第二次输入
cin.get(str_,10);
cout<<str_<<endl;
执行效果:

第二次接受用户输入的过程根本没出现。
原因是第一次接受用户输入后,cin.get缓存了用户输入的换行符。在第二次接受用户输入时,cin会首先检查缓存器中是否有数据,发现有换行符,直接结束输入。
解决方案,手动清除缓存器的数据。
char str[20];
char str_[20];
cin.get(str,10);
cout<<str<<endl;
//不带参数的 get 方法可以清除数据
cin.get();
cin.get(str_,10);
cout<<str_<<endl;
cin.getline在接受用户输入后,不会保留换行符,所以可以用于连续输入。如下代码:
char str[20];
char str_[20];
//第一次输入
cin.getline(str,10);
cout<<"str:"<<str<<endl;
//第二次输入
cin.getline(str_,10);
cout<<"str_:"<<str_<<endl;

如果要使用cin输入一行字符串,并赋值给字符串对象,则需要使用全局 getline函数。
//字符串对象
string str;
//第一个参数:cin对象 第二个参数:字符串对象
getline(cin,str);
cout<<str<<endl;
5. 总结
本文主要讲解了C++字符串的2种存储方案,一个是C语言风格的数组存储方案,一个是C++对象存储方案。
因存储方案不同,其操作函数的提供方式也不相同。
C++ 练气期之一文看懂字符串的更多相关文章
- [转帖] 一文看懂:"边缘计算"究竟是什么?为何潜力无限?
一文看懂:"边缘计算"究竟是什么?为何潜力无限? 转载cnbeta 云计算 雾计算 边缘计算... 知名创投调研机构CB Insights撰文详述了边缘计算的发展和应用前景 ...
- Nature 为引,一文看懂个体化肿瘤疫苗前世今生
进入2017年,当红辣子鸡PD-1疗法,一路横扫多个适应症.而CAR-T治疗的“小车”在获得FDA专委会推荐后也已经走上高速路,成为免疫治疗又一里程碑事件.PD-1.CAR-T之后,下一个免疫治疗产品 ...
- 一文看懂web服务器、应用服务器、web容器、反向代理服务器区别与联系
我们知道,不同肤色的人外貌差别很大,而双胞胎的辨识很难.有意思的是Web服务器/Web容器/Web应用程序服务器/反向代理有点像四胞胎,在网络上经常一起出现.本文将带读者对这四个相似概念如何区分. 1 ...
- 一文看懂https如何保证数据传输的安全性的【转载、收藏】
一文看懂https如何保证数据传输的安全性的 一文看懂https如何保证数据传输的安全性的 大家都知道,在客户端与服务器数据传输的过程中,http协议的传输是不安全的,也就是一般情况下http是明 ...
- [转帖]一文看懂web服务器、应用服务器、web容器、反向代理服务器区别与联系
一文看懂web服务器.应用服务器.web容器.反向代理服务器区别与联系 https://www.cnblogs.com/vipyoumay/p/7455431.html 我们知道,不同肤色的人外貌差别 ...
- 一文看懂Stacking!(含Python代码)
一文看懂Stacking!(含Python代码) https://mp.weixin.qq.com/s/faQNTGgBZdZyyZscdhjwUQ
- 一文看懂大数据的技术生态圈,Hadoop,hive,spark都有了
一文看懂大数据的技术生态圈,Hadoop,hive,spark都有了 转载: 大数据本身是个很宽泛的概念,Hadoop生态圈(或者泛生态圈)基本上都是为了处理超过单机尺度的数据处理而诞生的.你可以把它 ...
- 转载来自朱小厮博客的 一文看懂Kafka消息格式的演变
转载来自朱小厮博客的 一文看懂Kafka消息格式的演变 ✎摘要 对于一个成熟的消息中间件而言,消息格式不仅关系到功能维度的扩展,还牵涉到性能维度的优化.随着Kafka的迅猛发展,其消息格式也在 ...
- 【转帖】一文看懂docker容器技术架构及其中的各个模块
一文看懂docker容器技术架构及其中的各个模块 原创 波波说运维 2019-09-29 00:01:00 https://www.toutiao.com/a6740234030798602763/ ...
随机推荐
- Sliding Window - 题解【单调队列】
题面: An array of size n ≤ 106 is given to you. There is a sliding window of size k which is moving fr ...
- GO语言学习——运算符
运算符 Go 语言内置的运算符有: 算术运算符 关系运算符 逻辑运算符 位运算符 赋值运算符 算术运算符 运算符 描述 + 相加 - 相减 * 相乘 / 相除 % 求余 注意: ++(自增)和--(自 ...
- Oracle中查找某个点半径范围内的所有经纬度(优化)
需求: 已知一个点的经纬度,需要从表中找出以这个点为中心,半径M米范围内的所有经纬度数据. 假设现有表 TAB_LONG_LAT_DATA,字段如下: ID INTE ...
- Halo 开源项目学习(七):缓存机制
基本介绍 我们知道,频繁操作数据库会降低服务器的系统性能,因此通常需要将频繁访问.更新的数据存入到缓存.Halo 项目也引入了缓存机制,且设置了多种实现方式,如自定义缓存.Redis.LevelDB ...
- python写一个能变身电光耗子的贪吃蛇
python写一个不同的贪吃蛇 写这篇文章是因为最近课太多,没有精力去挖洞,记录一下学习中的收获,python那么好玩就写一个大一没有完成的贪吃蛇(主要还是跟课程有关o(╥﹏╥)o,课太多好烦) 第一 ...
- 如何定制.NET6.0的日志记录
在本章中,也就是整个系列的第一部分将介绍如何定制日志记录.默认日志记录仅写入控制台或调试窗口,这在大多数情况下都很好,但有时需要写入到文件或数据库,或者,您可能希望扩展日志记录的其他信息.在这些情况下 ...
- 189. Rotate Array - LeetCode
Question 189. Rotate Array Solution 题目大意:数组中最后一个元素移到第一个,称动k次 思路:用笨方法,再复制一个数组 Java实现: public void rot ...
- 基于Proxmox平台搭建3D云教室
背景 本文介绍了在 Proxmox VE 虚拟化平台上使用NVIDIA A16 GPU,开启vGPU特性,利用DoraCloud 搭建3D云教室的方案. Proxmox virtualization ...
- 做一个能对标阿里云的前端APM工具(下)
上篇请访问这里做一个能对标阿里云的前端APM工具(上) 样本多样性问题 上一小节中的实施方案是微观的,即单次性的.具体的.但是从宏观上看,我需要保证性能测试是公允的,符合大众预期的.为了达到这种效果, ...
- SPPNet(特征金字塔池化)学习笔记
SPPNet paper:Spatial pyramid pooling in deep convolutional networks for visual recognition code 首先介绍 ...