C++场景实现
C++字符串分割
在C++中,我们可以使用多种方法来分割字符串。以下是一些常见的方法:
方法一:使用
std::istringstream
和 std::getline
这种方法不依赖于任何第三方库,只需要标准库即可。
方法二:使用
std::strtok
尽管
std::strtok
是一个C库函数,但是它也可以在C++中使用。这个函数会修改原始字符串,所以如果你不希望原始字符串被修改,你需要复制一份。注意:
std::strtok
不是线程安全的。方法三:使用 Boost 库的
boost::split
如果你的项目已经使用了 Boost 库,你可以使用
boost::split
函数来分割字符串。以上三种方法都可以用来分割字符串,你可以根据你的项目需求和你的编程环境来选择最适合的一种。
C++计算结构体成员偏移量
构建一个宏函数,提供结构体类型和成员变量名称,计算该变量在结构体中的偏移量。
在C++中,你可以使用
offsetof
宏来获取结构体中成员的偏移量。这是一个预处理器宏,它接受两个参数:一个类型和该类型的成员名称,然后返回该成员在该类型对象中的字节偏移量。下面是如何使用
offsetof
宏的一个例子:在这个例子中,
offsetof(MyStruct, a)
返回的是a
成员在MyStruct
中的偏移量,这通常是0,因为a
是结构体的第一个成员。offsetof(MyStruct, b)
和offsetof(MyStruct, c)
分别返回b
和c
成员的偏移量。注意,
offsetof
宏只能用于POD(Plain Old Data)类型。如果你尝试使用它来获取非POD类型的成员偏移量,那么结果是未定义的。在C++中,POD类型是简单的、可以通过复制内存来复制其值的类型,例如内置类型、数组和结构。offsetof
宏的具体实现可能因编译器的不同而有所不同,但通常,它基于一个事实,那就是在C++中,一个结构体或类的实例的地址就是它的首个数据成员的地址。offsetof
宏通常定义如下:这个宏首先将
0
强制转换为type
指针,这样得到的就是一个指向地址0
的type
指针,然后它通过指针访问成员member
的地址。由于type
的实例在地址0
,所以访问的地址就是member
的偏移量。注意,虽然这个宏在许多情况下可以正常工作,但是它实际上是未定义的行为,因为它试图访问空指针的成员。然而,由于
offsetof
宏只是计算地址而不是真正访问成员,所以在实际中,这通常不会造成问题。至于POD(Plain Old Data)类型,它包括了C++中一些简单的类型,这些类型可以通过复制内存来复制其值,例如:
- 所有基本数据类型,例如
int
、char
、double
等。
- 指针类型。
- POD类型的数组。
- 不包含构造函数、析构函数、或虚函数的结构体或类,其所有非静态成员都是POD类型,且没有任何基类或私有或保护成员。
简单来说,POD类型是那些可以用简单的内存复制来完全复制其值的类型,这使得它们可以兼容C语言的数据类型和结构。
编译
编译表示
编译器在将源代码转换为目标代码的过程中,常常会生成一种或多种中间表示(Intermediate Representation, IR)。这些中间表示有多种形式,包括但不限于以下几种:
- 抽象语法树(Abstract Syntax Tree, AST):这是源代码的高级表示,它基本上保留了源代码的结构信息。这种形式的中间表示在语法分析阶段生成。
- 三地址代码(Three-Address Code, TAC):这是一种低级表示,每条指令最多包含三个操作数。逻辑上,每个操作数可以是一个变量、一个常量或者一个间接引用。三地址代码是一种更接近于目标代码的中间表示,通常在优化阶段使用。
- 四元式(Quadruples):四元式是一种特殊的三地址代码,它明确地将每个操作符和它的操作数分开。因此,四元式的每条指令都包含四个部分:操作符、两个源操作数和一个目标操作数。
- 逆波兰表示(Reverse Polish Notation, RPN):逆波兰表示是一种没有括号的算术表示方法,它将操作符放在操作数的后面。这种表示方法的优点是它消除了运算优先级和括号的需要。
- N元表示(N-Tuples):N元表示是一种更一般的方式,它可以表示包含任意数量操作数的操作。这种表示方法通常用于更复杂的编程语言和机器语言。
这些中间表示从不同的角度抽象了源代码的信息,可以帮助编译器进行诸如优化这样的操作。它们的选择和设计取决于编译器的目标和源语言的特性。
一些特殊概念
RAII
RAII是Resource Acquisition Is Initialization的缩写,意为“资源获取即初始化”,是一种C++编程技巧,用于管理资源的生命周期。在RAII中,资源的获取和释放是与对象的生命周期绑定在一起的,即资源的获取在对象构造时进行,资源的释放在对象析构时进行,从而确保资源的正确获取和释放,避免资源泄漏和使用错误。
RAII的核心思想是将资源的管理和对象的生命周期绑定在一起,利用对象的构造和析构函数管理资源,从而保证资源的正确获取和释放。在C++中,可以使用智能指针、容器、锁等RAII封装类来管理资源。
例如,我们可以使用
std::unique_ptr
来管理动态分配的内存,如下所示:在上述示例中,我们使用
std::unique_ptr
来管理动态分配的内存。当ptr
离开作用域时,会自动调用析构函数,释放它所管理的内存,从而避免了内存泄漏的问题。另外,RAII还可以用于管理其他资源,如文件句柄、网络连接、锁等,例如使用
std::lock_guard
来管理std::mutex
的锁,如下所示:在上述示例中,我们使用
std::lock_guard
来管理std::mutex
的锁,它会在构造函数中获取锁,在析构函数中释放锁,从而避免了忘记释放锁的问题。总之,RAII是一种重要的C++编程技巧,可以有效地管理资源的生命周期,避免资源泄漏和使用错误,提高代码的可靠性和安全性。
内存布局
输出:
成员函数:
成员函数(或者称为方法)不像数据成员那样存储在对象的内存布局中。这是因为无论你创建多少个对象,每个对象都会有自己的数据成员的副本,但它们共享同一个成员函数的代码。
成员函数的代码通常存储在程序的文本段(也称为代码段)。这是程序内存布局的一部分,主要用于存储程序的机器代码。每个成员函数只有一份代码,由所有对象共享。这意味着,无论你创建多少个对象,成员函数的代码只存储一次。
当你调用一个对象的成员函数时,这个函数知道它正在操作哪个对象,是通过在调用时隐式地传递一个指向对象的
this
指针实现的。这就是为什么你可以在成员函数中访问调用它的对象的数据成员,即使这个函数的代码是由所有对象共享的。因此,你在代码中看到的
sizeof(MyStruct)
只计算了数据成员的大小,而没有计算成员函数的大小,因为成员函数并不是对象的一部分。同时,offsetof
也只能用于数据成员,不能用于成员函数,因为成员函数并不占据对象的存储空间。内存对齐
内存对齐是一种优化内存访问的技术。一些特定的硬件平台只能在特定地址边界上访问特定类型的数据。例如,一个常见的情况是,一个
int
可能需要在4字节的边界上进行访问,而一个double
可能需要在8字节的边界上进行访问。这种限制是由硬件的内存访问机制决定的。对于你的示例:
在许多平台上,
MyStruct1
和MyStruct2
的内存布局将是不同的,因为编译器会插入填充字节以满足对齐要求。对于
MyStruct1
,int a
后面可能会插入4个填充字节,以确保double b
在8字节边界上。然后,char c
后面可能会插入7个填充字节,以确保整个结构体的大小是最大对齐要求的倍数(这里是8)。所以,sizeof(MyStruct1)
可能是24。对于
MyStruct2
,int a
后面不需要插入填充字节,因为char c
可以放在紧随int a
后面的字节中。但是,char c
后面可能会插入7个填充字节,以确保double b
在8字节边界上。所以,sizeof(MyStruct2)
可能是16。这只是一个可能的结果,实际的结果取决于具体的编译器和硬件平台。你可以使用
sizeof()
函数和offsetof()
宏来查看实际的结果。请注意,一般来说,我们不应该依赖特定的内存布局,除非有特殊的需要,例如与硬件或网络协议进行交互。对于这种情况,可以使用字符数组或
std::byte
数组,并手动进行序列化和反序列化。- 作者:Olimi
- 链接:https://olimi.icu/article/aac09f63-217a-4a04-8868-a2a5c7f59832
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。