Cherno C++学习笔记 P32 字符串

这篇文章我们来讲字符串。字符串可以说是最重要的变量类型了,因为对字符串的读写极大地影响到我们的程序和用户之间的交互。甚至很多很庞大的程序就只是在处理字符串。

对于字符串,我们同时需要有关于数组和指针的关系,字符串的实现与数组是紧密相连的。字符串本质是group of characters,是一堆字符的合集。而一堆字符可以组成各种各样我们所需的文本,这些其实都是字符串,所以字符串是C++处理文本的方式。主要目的是对文本进行操作并将其展现出来给用户互动。

那么在了解字符串之前,我们首先需要了解字符character。我们常见的所有的字母,符号,都是字符。在C++当中,我们用数据类型char来表示字符。而C++默认的字符编码方式是ASCII,一个字符用一个字节来表示,那么就总共有 种不同的选择。但是我们知道,如果只有256种选择,那么当我们想表示中文或者日文的时候,这个选择数量是远远不够的。所以为了能够表示更多的语言,我们有很多其他的编码方式来进行处理,比如utf_16,允许我们用两个字节,16位来表示一个字符,那么就可以有 种不同的选择。这样可以表示的语言就更多了。当然在这里的话我们并不会深入编码的相关知识,只是给大家做个了解即可。

回到默认的char,因为char只有一个字节,所以可以用来按字节处理内存或者分配缓存等。在C++当中,我们通常会用单引号来表示一个字符。

文本和语言其实是非常非常复杂的东西,所以这里我们掌握基本的知识差不多就够用了。

那么有了字符之后,什么是字符串?字符串其实就是字符数组。我们可以举一个简单的例子。

cpp 复制代码
#include<iostream>

int main() {
	const char* name = "Cherno";
	std::cout << name << std::endl;
	std::cin.get();
}

这样我们就写下了一段有C风格的字符串定义代码。可以看到我们定义了一个const char类型的指针,指向了一个字符串。而在C++当中,字符串需要用双引号括起来。但是我们可以看到,这里我们是不需要写一个new的,也就是我们其实还是在栈上分配的内存。

接下来我们进入到内存里面去看看长什么样子:

可以在右侧看到我们数据的ASCII码,确实就是我们输入的Cherno。如果我们增加一句话,输出一下这个字符串的大小,看看是多少。

不过需要注意的是,如果我们使用上面的代码直接输出sizeof(name),那么它返回的其实是指针的大小,我们无法真的得到这个字符串的大小,所以我们这样写(VS2022下的结果):

cpp 复制代码
#include<iostream>

int main() {
	const char name[] = "Cherno";
	std::cout << name << std::endl;
	std::cout << sizeof(name) << std::endl;
	std::cin.get();
}

这样我们可以看到,输出的长度是7而不是我们看到的6,这是因为我们在最后还有一位空字符,这个被称为空终止符,是编译器自动加上来的。这个是因为name本身是指针的情况下,编译器依然应该知道在哪里停止下来,所以我们才能够直接输出。

需要注意的一点是,如果我们定义好了一个字符串,就意味着我们没有办法再改变它的长度了,如果想要更长的字符串,我们只能删除掉重新写一个。当然如果添加了const关键字,那就什么都改变不了了。

但是如果我们做这样的定义:

cpp 复制代码
char name[6] = { 'C', 'h', 'e', 'r', 'n', 'o' };
std::cout << name << std::endl;

那么我们就会得到下面这个经典的输出:

也就是所谓的"口中直喊烫烫烫",这个是因为什么,我们也可以进入内存里一探究竟。

因为未初始化的内存自动填充了cc,而0xcccc在GB2312当中刚好对应烫字,所以我们就会看到一堆烫烫烫了。这个成为stack guard,在debug模式下会出现的问题。

为了防止出现这么多烫烫烫,我们需要在最后面手动添加上空字符'\0'或者直接是0

cpp 复制代码
char name[7] = { 'C', 'h', 'e', 'r', 'n', 'o', '\0'};
cpp 复制代码
char name[7] = { 'C', 'h', 'e', 'r', 'n', 'o', 0};

这两种定义方式是等效的,都是可以正常使用的。

以上都是C风格的字符串,那么在C++当中,我们更多使用的是string,而string相对而言容易使用得多。string类是一个char以及一些用来操纵这个char的方法的集合。实际上string还有一个模板类叫做basic_string,而我们使用的string是对basic_string的template specialization。

cpp 复制代码
#include<iostream>
#include<string>

int main() {
	std::string name = "Cherno";
	std::cout << name << std::endl;
	std::cout << name.size() << std::endl;
	std::cin.get();
}

这样我们就可以直接获得name的长度为6,而且如果我们把鼠标放到"Cherno"上面,会发现其真实的类型是const char[],这同样也是name的真实类型。这里的size则是C++风格的语句了,如果是C风格,还需要strlen()、strcpy()等函数。

如果我们有两个string类型的变量,我们可以直接对其进行相加操作,因为"+"在string类当中进行了重载,使得我们可以这样操作。

cpp 复制代码
std::string name = "Cherno";
std::string language = "CPP";
std::string lesson = name + language;

但是需要注意的是,两个const char*是不能直接相加的,理由也很简单,两个指针类型怎么可能相加呢?但是因为"+"在string中被重载了,所以如果是一个string加一个const char*,这个是可以支持的。

cpp 复制代码
std::string name = "Cherno";
std::string lesson = name + "CPP";

这样写是合法的。

那么如果就想直接把两个const char*相加,应该怎么办?答案是强制类型转换。

cpp 复制代码
std::string lesson = (std::string)"Cherno" + "CPP";

这样就可以了。

string有很多方法,这里介绍另一个方法,叫做find,作用是寻找这个字符串内有没有对应的子串。但是因为string并没有contain方法来判断是否真的包含一个子串,所以需要我们自己写:

cpp 复制代码
bool contains = lesson.find("no") != lesson.npos;

其中npos表示的是这个类型下最大的值,一般在如果find没有找到对应子串的时候返回。

最后讲一下有关于将string传递到函数中的问题。如果我们直接传递字符串,如下所示:

cpp 复制代码
void PrintString(std::string string) {
	std::cout << string << std::endl;
}

那么会涉及一次字符串的拷贝,这个是会非常浪费时间的做法,因为拷贝字符串是很慢的,所以会导致性能的降低。

在我们不改变字符串的内容的情况下,可以只传入引用:

cpp 复制代码
void PrintString(const std::string& string) {
	std::cout << string << std::endl;
}

添加const来表示我们也不会修改string的值。

相关推荐
炸膛坦客12 分钟前
各类神经网络学习:(十)注意力机制(第2/4集),pytorch 中的多维注意力机制、自注意力机制、掩码自注意力机制、多头注意力机制
pytorch·神经网络·学习
东方芷兰23 分钟前
JavaWeb 课堂笔记 —— 03 Vue
java·前端·javascript·vue.js·笔记
帅弟15035 分钟前
Day7 FIFO与鼠标控制
学习
DXM05212 小时前
牟乃夏《ArcGIS Engine地理信息系统开发教程》学习笔记1
开发语言·经验分享·笔记·学习·arcgis·c#·arcgis engine
穷儒公羊2 小时前
第一部分——Docker篇 第三章 构建自定义镜像
java·运维·后端·学习·docker·云原生·容器
kfepiza2 小时前
硬盘分区格式方案之 MBR(Master Boot Record)主引导记录详解 笔记250407
linux·windows·笔记
超帅的好吧2 小时前
Scala
笔记
Elendill2 小时前
【算法笔记】并查集详解
笔记·python·算法
houliabc2 小时前
C语言个人笔记
c语言·数据结构·笔记·算法
枫叶20002 小时前
3DMAX笔记-UV知识点和烘焙步骤
笔记·3dsmax·贴图·uv