当前位置: 首页 > article >正文

Cherno C++学习笔记 P33 字符串的字面量

在这篇文章当中我们介绍一下有关于字符串更加深入的知识,也就是字符串的字面量。

首先什么是字面量?其实也很简单,就是双引号里面的那一坨,其实就是字面量,我们举一个最简单的例子:

#include<iostream>
#include<string>

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

这个例子当中,"Cherno"就是字符串字面量。如果我们把鼠标放到上面去,会看到提示变量类型为const char[7],因为实际上它还自带了一个终止字符,所以我们会看到总长度是7。

那么这样就带来了一个神奇的问题,如果我们把终止字符写在了字面量的中间,会发生什么?如下所示,我们这样修改我们的代码:

const char* str = "Che\0rno";
std::cout << str << std::endl;
std::cout << strlen(str) << std::endl;

这个时候就会看到,输出的结果分别是"Che"和3。因为当我们在字面量中间混杂了一个终止字符的时候,我们已经破坏了这个字符串。然后我们可以在调试模式种看看内存里面发生了什么:

我们可以在右侧的ASCII码翻译处看到,在"Cherno"中间出现了一个".",而且在左侧中间位置有一个“00”,这个就是我们添加进去的终止符,编译器在遇到了它之后自动认为这里是这个字符串的终止位置,所以输出了Che。

接下来需要注意这样一个写法:

char* name = "Cherno";
name[2] = 'a';

至少在VS2022当中,它已经不再是合法的写法了,可能在其他的编译器当中还有允许这样去写的,但是现在相当多编译器已经不再允许我们这样去写了。这种写法被称为是“未定义行为”,这是很危险的,所以不要去尝试。这些编译器当中,字面量已经被严格的限定为const char*了。

那么为什么这种行为危险且有可能不合法?这是因为字符串字面量是非常特殊的。我们用指针指向了字面量,但是字面量只存在于内存当中的只读部分。我们可以来仔细地看一下。接下来我们可以使用之前用过的方法,让我们的编译器为我们输出一下汇编语言。

我们可以看到,这段汇编语言中,我们刚才输入的Cherno被标记为了const segment,也就是常量。接着我们使用以下HxD来打开一下我们生成的exe file再看看在可执行文件当中,这个Cherno被放在了哪里。

可以看到, Cherno被直接内嵌进了我们的exe file,也就是它是定死的了,就连我们的可执行文件里面,都已经把它给定死了。那么如果我们还用一个指针指向它,且尝试修改它的值,那么这个行为肯定是很危险的。

如果我们使用的是VS2017,那么在release模式下,是可以运行的,但是会发现给出的结果是没有被修改的,就是“Cherno”,当然在VS2022里面,就连release模式下也是报错不误了。就不要用一个不是const的指针指向字面量啦,因为字面量是被钉死在可执行文件里面的。

所以对于字面量,我们一般都用const char*,如果真的想要对其进行修改,那么需要使用数组而不是指针,这个时候我们相当于是把这个字面量复制了过来,复制到了我们的数组中,那么当然就可以修改了。如果一定要用非const指针,那么只能进行强制类型转换了。当然实际测试下来,强制类型转换只是不报错,但是也有问题,所以最好还是用数组或者是const char*。

那么除了普通的char类型,我们还有哪些其他类型选项?就像上一篇文章提到的,如果只有1个字节的char,那么很多字符没有办法表示。所以C++还给我们提供了包括宽字符w_char,16位字符char_16t以及32位字符char_32t等其它字符。当然,我们平时用的char其实也可以前面加上u8,如下所示:

const wchar_t* name = L"Cherno";
const char16_t* name = u"Cherno";
const char32_t* name = U"Cherno";
const char* name = u8"Cherno";

其中,char16_t占位2个字节,char32_t占位是4个字节,wchar_t在各个平台上都有区别, 在Windows上是2个字节,在Linux和iOS上是4个字节。

那么在上一篇当中,我们说到了没有办法将两个字面量相加,除非对第一个字面量进行强制类型转换成string。那么如果我们非得相加要怎么办?有这样一个办法,如下所示:

using namespace std::string_literals;
std::string name = "Cherno"s + "CPP";

这样就可以正常使用了。其中的s其实是一个函数。

当然string也有32位的情况,如下所示:

std::u32string name = U"Cherno";

 根据上面的例子,我们知道了L关键字可以将字符串转变为宽字符,U可以转变为32位字符,那么还有另外一个关键字R,可以完全依照字面来输出字符串,如下所示:
 

	std::string name = R"(aaaa\nbbbb\ncccc\
ddddeeefff)";

输出的结果如图所示:

当然如果不用R关键字,其实还是有一些诡异写法:

const char* ex = "Line1\n"
	"Line2\n"
	"Line3\n";

这样也是可以正常输出的。

最后我们来看看如果使用数组和指针来处理字面量,分别会发生什么。首先来看数组:

char name[] = "Cherno";

我们打开了汇编语言界面看看什么情况:

我们可以看到,分别移动了目标的字符串和常量到对应的寄存器,然后通过mov指令分别进行复制,最后我们设定操作次数为7次,并使用rep movsb命令,将rsi当中的内容一个字节一个字节的搬运到了rdi当中,这样通过复制,我们完成了用字面量给数组赋值的操作。这样因为是复制过来的,所以当然可以修改。

然后我们再来看看如果使用常指针的情况:

const char* name = "Cherno";

然后我们再来查看汇编语言:

可以看到我们首先还是移动了常量到rax寄存器,但是随后我们直接使用了name指针指向了这个寄存器,也就是我们把rax的地址直接放到了name当中,所以并没有复制这样一个过程,自然也就是无法修改的了。


http://www.kler.cn/a/429601.html

相关文章:

  • Java版-图论-拓扑排序与有向无环图
  • spring boot 同一个redis 操作不同的库
  • 数据类型转换在自然语言处理中的应用
  • 计算机组成原理(一):计算机指令
  • SparkSQL编程实践
  • ollama-webui - Ollama的ChatGPT 风格的 Web 界面
  • 从零开始的使用SpringBoot和WebSocket打造实时共享文本应用
  • Rust 内置数据结构——BTreeMap应用教程
  • 【教学类-82-01】20241209涂色手表制作1.0(表盘、表带)
  • 基于STM32的手势电视机遥控器设计
  • 使用pyspark完成wordcount案例
  • Flutter 图片编辑板(二) 拖动位置和对齐线应用
  • 封闭式论文写作--全面掌握ChatGPT-4o的写作技能,掌握提示词使用技巧、文献检索与分析方法,帮助您选定研究方向,提炼学术论文题目
  • 软件漏洞印象
  • 网络安全 - Cross-site scripting
  • 刷leetcodehot100-7动态规划
  • 【RBF SBN READ】hadoop社区基于RBF的SBN READ请求流转
  • 产品经理的财会知识课:资产的减值测试
  • X推出新AI图像生成器Aurora:更接近真实的创作效果
  • Facebook与Web3的结合:去中心化社交的可能性