什么是非打印字符

1.什么是非打印字符

非打印字符(Non-printable characters)指的是那些无法在终端、控制台或文本编辑器中直接显示为可见符号的字符。它们通常用于控制设备行为或传递特殊信息,而不是作为可见的文本字符。非打印字符包括像空格(\n 换行符、\t 制表符)以及控制字符(如 NULL 字符 \0ESC 键的字符)。

1.1 分类

  • 控制字符:这类字符不代表实际的符号,而是用于控制显示、输入/输出设备的行为。它们位于 ASCII 表的前 32 个位置(值 0-31),比如:

    • \0:空字符,表示字符串的结束。
    • \n:换行符,移动光标到下一行的开始位置。
    • \r:回车符,移动光标到当前行的开头。
    • \t:水平制表符,插入一个制表位。
    • ESC:转义字符,通常用于终端命令序列。
  • 不可见符号:一些字符虽然具有实际的符号含义,但在某些上下文中被认为是非打印的。例如,某些 Unicode 字符或者特殊的格式控制字符在大多数情况下不可见。

  • 二进制数据中的任意字节:当处理二进制数据时,这些字节可能不符合任何字符编码规范,因此无法直接映射到可见字符。它们的含义取决于数据的结构和解释方式。

2.二进制数据

二进制数据并不是文本,它每一个字节可以是任何 8 位数值(0 到 255),并不是所有这些值都能对应到可打印的字符。例如,字节值 0x010xFF 这样的二进制值在字符编码中没有对应的可打印字符,因此它们是不可打印的。

二进制数据不是用于直接显示的,而是用于在内存、磁盘、网络等介质中存储和传输数据。这些数据可以是加密信息、压缩文件、协议消息等,不需要对应可视化的符号,而是需要设备或程序对其进行解码和处理。

2.1 二进制数据 vs 可打印字符

  • 可打印字符:可打印字符通常指在某种字符编码(如 ASCII 或 UTF-8)中对应于显示符号的字符。它们可以在控制台或屏幕上显示,例如字母、数字、标点符号等。

    • 例如,0x41 对应的是可打印字符 'A'
  • 非打印字符和二进制数据:二进制数据中的任意字节(例如 0x01, 0xFF, 0x00 等)通常是非打印字符,因为它们在字符集(如 ASCII)中没有对应的可见符号

std::string binary_data;
binary_data.push_back(0x01);  // 非打印字符,二进制值 0x01。
binary_data.push_back(0xFF);  // 非打印字符,二进制值 0xFF。

// 打印出这些字节,将无法显示可见字符。
std::cout << binary_data << std::endl;  // 输出会是乱码或不可见内容

————————————————————
char printable_char = 'A';  // 可打印字符 'A',对应的 ASCII 值是 0x41。
std::cout << printable_char << std::endl;  // 输出: A

2.2 为什么不自动打印为01串

因为计算机内部存储的数据是以 字节 为单位的,每个字节由 8 位组成,存储了一个二进制数值(0 到 255 的整数),但这些数字在打印时会根据不同的上下文字符十六进制整数其他格式 表示,而不会直接以二进制形式显示。

当你使用 std::cout 这样的函数打印数据时,C++ 会根据数据类型来决定如何显示。例如:

  • 对于 char 类型的数据,std::cout 会假定这是一个 可打印字符,并尝试显示对应的 ASCII 字符。
  • 对于 int 类型的数据,它会显示其对应的 十进制 数值。
  • 对于 std::string,它会打印字符串的内容,但不会把字符串内容转换为其底层的二进制位(即 01)。

二进制数据不直接打印为 01 串的原因

  • 效率问题:直接打印 01 的二进制表示非常低效,因为每个字节需要 8 个字符来表示。例如,字节 65 对应的二进制是 01000001,而用 std::cout 打印只需显示字符 A 或整数 65,远比打印 01000001 这样的二进制串高效得多。
  • 可读性问题:人类更习惯查看十进制数字或字符,二进制串对于人类来说不易阅读和理解。
  • 字符编码的应用:当打印一个 char 类型时,系统会尝试将该字节解释为一个 ASCII 或其他字符编码中的字符,而不是显示该字节的二进制位。
char c = 65;  // 65 是 'A' 的 ASCII 值
std::cout << c << std::endl;  // 输出: A

int num = 5;
std::cout << num << std::endl;  // 输出: 5

// 打印二进制数据
std::string binary_data;
binary_data.push_back(65);  // 65 是 'A' 的 ASCII 编码
std::cout << binary_data << std::endl;  // 输出: A

3.string为什么可以存储非打印字符

std::string 的底层实现只是一个字节数组,它不对存储的内容进行任何格式上的限制。char 只是一个字节(8 位),可以存储任意的 8 位数据,即二进制数据。

std::string 维护一个长度值来记录实际存储的数据长度,而不是依赖像 C 语言中的 \0(空字符)来标识字符串的结束。因此,即使二进制数据中包含 \0 字节,它也不会被视为字符串的终止符。

尽管 std::string 名字中有 "string"(字符串),但它本质上是一个字节容器,可以存储任意的字节序列,包括二进制数据。

int main(){
    // 创建包含二进制数据的字符串,包含非打印字符(0x00, 0x01, 0x02)
    std::string binary_data;
    binary_data.push_back(0x00);  // 添加一个字节 0x00
    binary_data.push_back(0x01);  // 添加一个字节 0x01
    binary_data.push_back(0x02);  // 添加一个字节 0x02

    // 打印字符串的大小
    std::cout << "Binary data size: " << binary_data.size() << std::endl;

    // 遍历并以十六进制形式打印每个字节
    for (unsigned char c : binary_data) {
        printf("%02x ", c);
    }
    std::cout << std::endl;

    cout<<endl;
    return 0;
}

// 打印结果
// Binary data size: 3
// 00 01 02 

4.string类型如何存储汉字

std::string 并不直接理解多字节字符的编码。汉字等非 ASCII 字符在 std::string 中存储时,通常使用 UTF-8 编码。

UTF-8 编码

  • UTF-8 是一种变长的字符编码方式,使用 1 到 4 个字节来表示一个 Unicode 字符。
  • ASCII 字符(如字母、数字等)使用 1 个字节存储,编码与 ASCII 完全兼容。
  • 汉字等非 ASCII 字符 使用 3 个字节(大多数汉字)或 4 个字节来表示。常见的汉字在 UTF-8 中通常占用 3 个字节。
  • 每个字节仍然是 char 类型,即 8 位的整数。虽然汉字的表示需要多个字节,但 std::string 可以按顺序存储这些字节。

std::string 的字节存储

  • std::string 底层是一个 char 数组,而 char 代表 8 位的数据。因此,std::string 可以存储任意字节序列,不关心数据是否是汉字或其他特殊符号。
  • 当存储汉字时,每个汉字的 UTF-8 编码会拆分成多个字节,并按顺序存储在 std::string 中。
int main(){
    std::string utf8_str = "汉";  // UTF-8 编码的汉字
    cout<<utf8_str<<", "<<utf8_str.size()<<endl;
    for (unsigned char c : utf8_str) {
    printf("%02x ", c);  // 逐个字节输出其十六进制表示
    }
    cout<<endl;
    return 0;
}

// 运行结果
// 汉, 3
// e6 b1 89 

打印时是根据终端的编码类型来编码输出的,使用locale命令查看终端编码类型是UTF-8所以可以正确输出为汉字。

猜你喜欢

转载自blog.csdn.net/huanting74/article/details/143085937