C++17完整导引-组件之std::byte

std::byte

通过std::byteC++17引入了一个类型来代表内存的最小单位:字节。std::byte本质上代表一个字节的值,但并不能进行数字或字符的操作,也不对每一位进行解释。对于不需要数字计算和字符序列的场景,这样会更加类型安全。
然而,注意std::byte实现的大小和unsigned char一样,这意味着它并不保证是8位,可能会更多。

std::byte:它代表了寻址单位,但没有作为整数类型的功能。std::byte 可以进行位运算,但不能够像char那样进行普通代数运算。在提高了类型安全性的同时,也有助于提高程序的可读性。

摘自C++17: std::byte

You might wonder what the difference is with just using the existing uint8_t instead of std::byte. Well, std::byte is really just a bunch of un-interpreted bits. If you use uint8_t, you are actually interpreting the bits as an 8-bit unsigned numerical value, which might convey the wrong semantics. Also, std::byte will not allow accidental arithmetic on it, while uint8_t does.
std::byte是一组未解释的位,而uint8_t则将这些位解释为8位无符号数值。使用std::byte可以避免错误的语义,并防止意外进行算术运算。

摘自cppreference.com

std::byte 是一种独立类型,它实现指定于 C++ 语言定义中的字节的概念。
char unsigned char ,它能用于访问其他对象所占据的生内存(对象表示),但不同于这些类型,它不是字符类型且非算术类型。 byte 只是位的汇集,而且只对它定义逐位运算符。

摘自A byte type definition

程序中许多需要对内存进行字节级别的访问。目前,这些程序必须使用charsigned charunsigned char类型来实现。然而,这些类型执行“三重任务”。它们不仅用于字节寻址,还用作算术类型和字符类型。这种角色的多样性为程序员错误(例如在应将其视为字节值的内存上意外执行算术运算)和混淆打开了大门。
byte类型的引入提高了类型安全性,通过区分基于字节的内存访问和将内存作为字符或整数值进行访问。它提高了可读性,并使代码意图更清晰地传达给读者(以及用于理解和转换程序的工具)。它通过消除程序员意图表达中的歧义,增加了表达式准确度,从而提高了类型安全性并增加了分析工具的精度。

使用std::byte

std::byte是一种独立的类型,不属于整数或字符类型。它用于访问构成对象存储的位,以提高程序安全性。
下面的代码展示了std::byte的核心能力:

#include <cstddef>  // for std::byte
#include <iostream>

using namespace std;
int main() {
    byte b1{0x3F};
    byte b2{0b1111'0000};
    cout << to_integer<int>(b1) << '\n'; // 输出: 63
    byte b3[4]{b1, b2, std::byte{1}};  // 4个字节(最后一个是0)
    if (b1 == b3[0]) {
        b1 <<= 1;
    }
    cout << to_integer<int>(b1) << '\n';  // 输出:126
    return 0;
}

预处理代码如下:

  std::byte b1 = {63};
  std::byte b2 = {240};
  std::operator<<(std::cout.operator<<(std::to_integer<int>(b1)), '\n');
  std::byte b3[4] = {b1, b2, std::byte{1}, 0};
  if(b1 == b3[0]) {
    std::operator<<=(b1, 1);
  } 
  
  std::operator<<(std::cout.operator<<(std::to_integer<int>(b1)), '\n');

这里,我们定义了两个初始值不同的字节。b2的初始化使用了两个C++14引入的特性:

  • 前缀0b允许定义二进制字面量
  • 数字分隔符 '可以增强数字字面量的可读性(它可以被放置在数字字面量中任意两个数字之间)。
    注意列表初始化(使用花括号初始化)是唯一可以直接初始化std::byte对象的方法。所有其他的形式都不能编译:
std::byte b1{42};       // OK(因为自从C++17起所有枚举都有固定的底层类型)
std::byte b2(42);       // ERROR
std::byte b3 = 42;      // ERROR
std::byte b4 = {42};    // ERROR

这是将std::byte实现为枚举类型的一个直接后果花括号初始化使用了新的用整数值初始化有作用域的枚举特性
也没有隐式类型转换,这意味着你必须显式对整数值进行转换才能初始化字节数组

std::byte b5[] {1};             // ERROR
std::byte b6[] {std::byte{1}};  // OK

如果没有初始化,std::byte对象的值将是未定义的,因为它存储在栈上:

std::byte b;    // 值未定义

像通常一样(除了原子类型),你可以使用花括号强制初始化为每一位为0:

std::byte b{};  // 等价于b{0}

std::to_integer<>允许你将std::byte对象转换为整数值(包括boolchar类型)。如果没有转换,将不能使用输出运算符。注意因为这个转换函数是模板,所以你需要使用带有std::的完整名称:

std::cout << b1;    // ERROR
std::cout << to_integer<int>(b1);       // ERROR(ADL在这里不起作用)
std::cout << std::to_integer<int>(b1);  // OK

也可以使用using声明(但请只在局部作用域中这么做):

using std::to_integer;
...
std::cout << to_integer<int>(b1);       // OK

如果要将std::byte用作bool值也需要这样的转换。例如:

if (b2) ...                         // ERROR
if (b2 != std::byte{0}) ...         // OK
if (to_integer<bool>(b2)) ...       // ERROR(ADL在这里不起作用)
if (std::to_integer<bool>(b2)) ...  // OK

因为std::byte被实现为底层类型是unsigned char的枚举类型,所以它的大小总是1:

std::cout << sizeof(b);             // 总是1

它的位数依赖于底层类型unsigned char的位数,你可以通过标准数字限制来获取位数:

std::cout << std::numeric_limits<unsigned char>::digits; // std::byte的位数

这等价于:

std::cout << std::numeric_limits<std::underlying_type_t<std::byte>>::digits;

大多数时候结果是8,但在有些平台上可能不是。

std::byte类型和操作

std::byte类型

在头文件<cstddef>中,C++标准库以如下方式定义了std::byte

namespace std {
    enum class byte : unsigned char {
    };
}

也就是说,std::byte不是别的,只是一个带有一些位运算符操作的有作用域的枚举类型

namespace std {
    ...
    template<typename IntType>
    constexpr byte  operator<<  (byte  b, IntType shift) noexcept;
    template<typename IntType>
    constexpr byte& operator<<= (byte& b, IntType shift) noexcept;
    template<typename IntType>
    constexpr byte  operator>>  (byte  b, IntType shift) noexcept;
    template<typename IntType>
    constexpr byte& operator>>= (byte& b, IntType shift) noexcept;

    constexpr byte& operator|= (byte& l, byte r) noexcept;
    constexpr byte  operator|  (byte  l, byte r) noexcept;
    constexpr byte& operator&= (byte& l, byte r) noexcept;
    constexpr byte  operator&  (byte  l, byte r) noexcept;
    constexpr byte& operator^= (byte& l, byte r) noexcept;
    constexpr byte  operator^  (byte  l, byte r) noexcept;
    constexpr byte  operator~  (byte b) noexcept;

    template<typename IntType>
    constexpr IntType to_integer (byte b) noexcept;
}

std::byte操作

std::byte的操作列出了std::byte的所有操作。

操作效果
构造函数创建一个字节对象(调用默认构造函数时值未定义)
析构函数销毁一个字节对象(什么也不做)
=赋予新值
==、!=、<、<=、>、>=比较字节对象
<<、>>、|、&、^、~二元位运算符
<<=、>>=、 |=、 &=、^=修改自身的位运算符
to_integer<T>()把字节对象转换为整数类型T
sizeof()返回1
#include <bitset>
#include <cstddef>
#include <iostream>

std::ostream& operator<<(std::ostream& os, std::byte b) {
    return os << std::bitset<8>(std::to_integer<int>(b));
}

int main() {
    std::byte b{42};
    std::cout << "1. " << b << '\n';

    // b *= 2 compilation error
    b <<= 1;
    std::cout << "2. " << b << '\n';

    b >>= 1;
    std::cout << "3. " << b << '\n';

    std::cout << "4. " << (b << 1) << '\n';
    std::cout << "5. " << (b >> 1) << '\n';

    b |= std::byte{0b11110000};
    std::cout << "6. " << b << '\n';

    b &= std::byte{0b11110000};
    std::cout << "7. " << b << '\n';

    b ^= std::byte{0b11111111};
    std::cout << "8. " << b << '\n';
}

运行结果如下:

1. 00101010
2. 01010100
3. 00101010
4. 01010100
5. 00010101
6. 11111010
7. 11110000
8. 00001111
转换为整数类型

to_integer<>()可以把std::byte转换为任意基本整数类型(bool、字符类型或者整数类型)。这也是必须的,例如为了将std::byte和整数值比较或者将它用作条件:

if (b2) ...                         // ERROR
if (b2 != std::byte{0}) ...         // OK
if (to_integer<bool>(b2)) ...       // ERROR(ADL在这里不生效)
if (std::to_integer<bool>(b2)) ...  // OK

另一个使用它的例子是std::byte I/O。to_integer<>()使用static_cast来把unsigned char转换为目标类型。例如:

std::byte ff{0xFF};
std::cout << std::to_integer<unsigned int>(ff); // 255
std::cout << std::to_integer<int>(ff);          // 也是255(没有负值)
std::cout << static_cast<int>(std::to_integer<signed char>(ff)); // -1
std::byte的I/O

std::byte没有定义输入和输出运算符,因此不得不把它转换为整数类型再进行I/O:

std::byte b;
...
std::cout << std::to_integer<int>(b);   // 以十进制值打印出值
std::cout << std::hex << std::to_integer<int>(b); // 以十六进制打印出值

通过使用std::bitset<>,你可以以二进制输出值(一串位序列):

#include <cstddef>  // for std::byte
#include <bitset>   // for std::bitset
#include <limits>   // for std::numeric_limits

std::byte b1{42};
using ByteBitset = std::bitset<std::numeric_limits<unsigned char>::digits>;
std::cout << ByteBitset{std::to_integer<unsigned>(b1)};

上例中using声明定义了一个位数和std::byte相同的bitset类型,之后把字节对象转换为整数来初始化一个这种类型的对象,最后输出了该对象。最后值42会以如下方式输出(假设一个char是8位):

00101010

另外,你可以使用std::underlying_type_t<std::byte>代替unsigned char,这样using声明的目的将更明显。

你也可以使用这种方法把std::byte的二进制表示写入一个字符串:

std::string s = ByteBitset{std::to_integer<unsigned>(b1)}.to_string();

如果你已经有了一个字符序列,你也可以像下面这样使用byte到位序列

#include <bitset>
#include <charconv>
#include <cstddef>
#include <iostream>

int main() {
    std::byte b1{42};
    char str[100];
    std::to_chars_result res =
        std::to_chars(str, str + 99, std::to_integer<int>(b1), 2);
    *res.ptr = '\0';  // 确保最后有一个空字符结尾

    std::cout<< std::string(str); // 101010
}

注意这种形式将不会写入前导0,这意味着对于值42,最后的结果是(假设一个char有8位):

101010

可以使用相似的方式进行输入:以整数、字符串或bitset类型读入并进行转换。例如,你可以像下面这样实现读入字节对象的二进制表示的输入运算符:

std::istream& operator>> (std::istream& strm, std::byte& b)
{
    // 读入一个bitset:
    std::bitset<std::numeric_limits<unsigned char>::digits> bs;
    strm >> bs;
    // 如果没有失败就转换为std::byte:
    if (!std::cin.fail()) {
        b = static_cast<std::byte>(bs.to_ulong());  // OK
    }
    return strm;
}

注意我们必须使用static_cast<>()来把bitset转换成的unsigned long转换为std::byte列表初始化将不能工作,因为会发生窄化

b = std::byte{bs.to_ulong()};   // ERROR:发生窄化

并且我们也没有其他的初始化方法了。

另外,你也可以使用std::from_chars()来从给定的字符序列读取:

#include <charconv>

const char* str = "101001";
int value;
std::from_chars_result res = std::from_chars(str, str+6, // 要读取的字符范围
                                             value,      // 读取后存入的对象
                                             2);         // 2进制
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 在 Objective-C 中,使用 `std::string` 会报错的原因是 Objective-C 和 C++ 是两种不同的编程语言,它们的语法和特性不完全兼容。 Objective-C 是一种面向对象的编程语言,它是在 C 语言的基础上发展而来的,并且主要用于开发 macOS 和 iOS 平台的应用程序。Objective-C 的字符串类型是 `NSString`,它是 Foundation 框架提供的一个类,具有很多方便的字符串操作方法。 而 `std::string` 是 C++ 标准库提供的一个类,用于处理字符串。C++ 是一种支持面向对象编程的通用编程语言,与 C 语言相比,具有更多的高级特性和功能。 在 Objective-C 中,如果想要使用 `std::string`,需要进行一些额外的工作。首先,需要在文件的开头添加 `#import <string>` 语句来引入 C++ 的标准库。然后,需要使用 `[[NSString stringWithCString:stdString.c_str() encoding:NSUTF8StringEncoding]]` 这样的方法将 `std::string` 转换成 `NSString` 对象。 如果没有正确导入 C++ 的标准库或者没有进行正确的类型转换,使用 `std::string` 就会报错。所以,在 Objective-C 中使用 `std::string` 之前,需要确保已经正确导入标准库并进行了适当的类型转换。 总之,Objective-C 和 C++ 是两种不同的编程语言,对应于不同的字符串类型。在 Objective-C 中使用 `std::string` 会报错,需要进行额外的导入和类型转换才能正确使用。 ### 回答2: 在Objective-C中,无法直接使用std::string,因为std::string是C++中的标准库类型,并不是Objective-C的一部分。 如果在Objective-C代码中使用std::string,会导致编译错误,因为Objective-C编译器无法找到std::string的定义。 解决这个问题的一种方法是将std::string转换为NSString。可以使用NSString的initWithUTF8String方法将std::string转换为NSString对象。具体代码如下: ``` std::string cppString = "Hello, World!"; NSString* objcString = [[NSString alloc] initWithUTF8String:cppString.c_str()]; ``` 这样就可以使用objcString来表示相应的字符串了。 另外,如果需要在Objective-C中频繁使用C++的字符串操作,可以考虑将整个文件的后缀名改为.mm,这样文件就会被当做Objective-C++文件处理,这样就可以使用std::string了。 总之,Objective-C中无法直接使用std::string,但可以通过将其转换为NSString来使用。 ### 回答3: 在 Objective-C 中使用 std::string 可能会导致编译错误。这是因为 std::string 是 C++ 标准库中的类,而 Objective-C 是基于 C 的编程语言。虽然 Objective-C 允许使用 C++ 的一些特性,但不支持完全的 C++语法。 如果你在 Objective-C 中使用 std::string,并且遇到了编译错误,可能是因为编译器无法识别 std::string 这个类型。你可以尝试进行以下解决办法: 1. 使用 NSString 类型代替 std::string 类型。NSString 是 Objective-C 中用于处理字符串的类,可以满足大部分的字符串操作需求。 2. 将 std::string 转换为 NSString。可以使用 NSString 的 initWithCString:encoding: 方法将 std::string 转换为 NSString 对象,然后在 Objective-C 中进行操作。 3. 在 Objective-C++ 文件中使用 std::string。如果你的文件后缀为 .mm,那么这个文件实际上是 Objective-C++ 文件,支持 C++ 语法。在这种文件中,可以直接使用 std::string,而不会报错。 需要注意的是,在使用 std::string 时,还要考虑到 Objective-C 的内存管理机制。如果使用了 std::string,可能需要手动管理内存的释放和分配,以避免内存泄漏等问题。 总而言之,当在 Objective-C 中出现 std::string 报错时,可以尝试使用 NSString 类型代替,转换为 NSString 对象,或者将文件后缀改为 .mm 来解决问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

-西门吹雪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值