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的值。

相关推荐
-self-disciplinese43 分钟前
从零开始学Java,学习笔记Day22
java·笔记·后端·学习
胡说八道的Dr. Zhu44 分钟前
深度学习中的数学基础【学习笔记】——第六章:随机变量
笔记·学习
雨中奔跑的小孩7 小时前
爬虫学习案例3
爬虫·python·学习
冷环渊7 小时前
React基础学习
前端·学习·react.js
今天我又学废了9 小时前
学习记录,隐式对象,隐式类
学习
#HakunaMatata9 小时前
Java 中 List 接口的学习笔记
java·学习·list
小雄abc9 小时前
决定系数R2 浅谈三 : 决定系数R2与相关系数r的关系、决定系数R2是否等于相关系数r的平方
经验分享·笔记·深度学习·算法·机器学习·学习方法·论文笔记
Magnetic_h9 小时前
【iOS】OC高级编程 iOS多线程与内存管理阅读笔记——自动引用计数(三)
笔记·学习·ios·objective-c·cocoa
是小李呀~10 小时前
C盘扩容(亲测有效)
笔记
cwtlw11 小时前
CSS学习记录11
前端·css·笔记·学习·其他