目录

第一性问题:计算机如何表示文字?

ASCII:最早的字符编码标准(美国人写的)

Unicode:解决全球语言的编码方案

字符(Character)

​编辑 为什么字符常量必须加上单引号 ' '?

如果我想表示一个“汉字”怎么办?

字符数组(character array)

 字符串(Strings)

 声明字符串的方式

 为什么一定要有 '\0'?

⚠️ 注意事项


第一性问题:计算机如何表示文字?

计算机的本质是一个只能理解 “0”和“1” 的电子设备,它不懂汉字、字母,也不懂你说的“苹果”、“Hello”。所以我们有个根本的问题:

问题:如何让计算机识别和存储“文字”?

答案就是:把文字 → 转换成数字(编码) → 再转换成 0/1(二进制)

这就是 字符编码(Character Encoding) 的起源!

ASCII:最早的字符编码标准(美国人写的)

1. ASCII 是什么?

ASCII(American Standard Code for Information Interchange)是1963年美国制定的最早的字符编码标准。

  • 每个字符被映射成一个 7位二进制数(后来扩展成8位)。

  • 一共能表示 128 个字符(2⁷ = 128)

  • 后来为兼容计算机的存储单元,扩展为 8 位(即 1 字节)因此,在 ASCII 及其兼容的单字节编码中,一个字符通常占用 1 字节内存

ASCII的结构逻辑:

类别范围例子
控制字符0–31回车(13)、换行(10)
可打印字符32–126空格(32)、A(65)、z(122)
字符ASCII十进制ASCII二进制
A6501000001
B6601000010
a9701100001
04800110000
空格3200100000

 2. 特点:

  • 优点:简单,早期英文系统够用。

  • 缺点:只能表示英文字符、数字和常用符号,不支持汉字、西班牙语、阿拉伯语等多语言。

Unicode:解决全球语言的编码方案

1. 为什么需要 Unicode?

ASCII 只能表示128个字符,不适合全球。于是各国开始“自己发明编码”:

  • 中国:GB2312、GBK、GB18030

  • 日本:Shift-JIS

  • 韩国:EUC-KR

 问题出现了:一个文件在中文电脑上正常,放到英文电脑就乱码。同样的二进制,在不同系统下解释为不同字符。

于是——我们能不能制定一个“全世界统一的字符编码”?

 Unicode(Unified Code,统一码)诞生于1991年,目标:为所有文字分配统一的编号(码点)!

2. Unicode 的核心思想

  • 给世界上所有字符(汉字、泰文、emoji等)分配唯一的“码点”(code point),例如:

    • A:U+0041

    • 中:U+4E2D

    • 😃:U+1F603

 注意:Unicode 只是“编号表”,它没有规定用多少个字节去存储这些字符。于是出现了不同的“实

现方式”,即只定义 “字符 ↔ 码点”映射,但没有规定如何把码点存储在内存里!

Unicode的存储实现:UTF-8、UTF-16、UTF-32

UTF全称:Unicode Transformation Format(Unicode 转换格式)

我们来解决另一个第一性问题:

“U+4E2D”这个码点怎么放进内存?需要几个字节?怎么转成二进制?

这就诞生了多种“Unicode编码实现方式”

编码方式

特点

字节数

优点

缺点

UTF-8

可变长编码

1~4字节

英文节省空间,兼容 ASCII

汉字需要3字节

UTF-16

可变长编码

2或4字节

常用于Windows

英文占2字节,不节省

UTF-32

定长编码

4字节

编码简单

空间浪费

UTF-8 是目前最常用的编码方式(尤其在网页、Python中)

举例:同一个字如何编码

字符Unicode码点UTF-8编码(二进制)
AU+004101000001(1字节)
U+4E2D11100100 10111000 10101101(3字节)
😃U+1F60311110000 10011111 10011000 10000011(4字节)

现在把之前讲的字符编码知识,落地到 C/C++ 语言中,理解字符变量的本质和如何使用。

字符(Character)

核心第一性问题:

在 C/C++ 中,我们如何存储一个字符?它在内存里是什么样子?和 ASCII / Unicode 有什么关系?

char 类型(C语言的基础字符类型)

char c = 'A';
  • char 本质上是一个 1字节(8位)整数。

  • 它存的是字符的 ASCII码的整数值。

举个例子

char ch = 'A';   // 实际上等价于 char ch = 65;
printf("%c\n", ch);   // 输出字符A
printf("%d\n", ch);   // 输出65(对应ASCII)

也就是说:

  • 'A' 是 字符常量,它的 ASCII 值是 65。

  • C语言内部:字符就是整数,只不过默认以字符形式解释。

 为什么字符常量必须加上单引号 ' '

在 C/C++ 中:

字符是一个整数,本质上就是它的 ASCII 值(或 Unicode 码点)

但我们不希望写程序时天天记这些数字,所以语言提供了一个“简写”方式:

char ch = 'A';    // 实际等价于:char ch = 65;

所以:

  • 'A' 是字符常量(character literal)

  • 单引号告诉编译器:你要存的是字符的整数值,不是变量或字符串

单引号的作用 = 区分不同类型的常量

在 C/C++ 中,有很多类型的常量,你要用不同方式告诉编译器它是什么类型:

代码形式含义类型
'A'单个字符 → 65char(字符常量)
"A"字符串常量(含 \0char[2](字符串数组)
A错误(变量名或未定义)-
65数值常量 → 65int(整型常量)

所以可以理解为:'A' 是一种语法糖,是给你写代码时的“语义提示”,它会转成整数。 

总结:为什么要用 ' ' 来写字符?

理由解释
1️⃣ 区分字符和变量'A' 是字符常量,A 是变量名
2️⃣ 区分字符和字符串'A' 是一个字符,"A" 是一个字符串
3️⃣ 让编译器知道你是想用字符的 ASCII 值'B' → 66
4️⃣ 和字符串数组不同(char[] vs char单引号用于单个字符

如果我想表示一个“汉字”怎么办?

汉字在 Unicode 中的码点远大于 127(ASCII之外),所以不能用 char 存。

❌ 错误做法:

char h = '中';   // 错误,汉字需要多个字节,char 只存1字节

✅ 正确做法:使用 多字节编码(如 UTF-8)+ 字符串处理

UTF-8 示例(多字节汉字):

char* s = "中";  // UTF-8编码:0xE4 0xB8 0xAD
  • 然声明的是 char*,但实际上:

    • s[0] = 0xE4

    • s[1] = 0xB8

    • s[2] = 0xAD

    • s[3] = '\0'

打印每个字节:

for (int i = 0; s[i] != '\0'; ++i) {printf("%02x ", (unsigned char)s[i]);
}
// 输出:e4 b8 ad

字符数组(character array)

一个 char 类型的数组,用来存储一串字符(文本)。

开辟一段长度为 5个字节 的连续内存空间,每个元素是 char 类型(1字节),但不一定表示字符串!

重点来了:⚠️ 字符数组 ≠ 字符串,除非你手动加 \0

示例1:

char arr[5] = {'H', 'e', 'l', 'l', 'o'};
  • 初始化时指定了5个字符,数组大小为 5。

  • 并没有加 \0,所以这只是一个 字符数组,不是C字符串。

  • 如果你执行 printf("%s", arr); →  可能输出乱码,因为没有终止符。

 示例2:

char arr[] = {'H', 'e', 'l', 'l', 'o'};
  • 数组长度由编译器自动推导为 5。

  • 同样没有加 \0,仍然不是字符串。

  • 用于数据处理完全OK,for (int i = 0; i < 5; ++i) 这样访问是安全的。

示例3:

char arr[5] = {72,101,108,108,111};
  • 97 和 98 是 ASCII:分别对应 'a''b'

  • 所以 arr[0] = 'a'arr[1] = 'b'

 示例4:

char arr[5] = {'H', 'e'};
  • 数组长度是固定的 5,但只初始化了前两个元素。

  • 后面三个元素会默认补0('\0')(这是C初始化规则)

  • 实际上这是一种 手动初始化前两项,后面自动置0 的数组。

  • 这个数组其实可以当字符串用了,因为正好加上了 null terminator(arr[2] = 0),所以 printf("%s", arr); 是安全的,会输出 "He"


 字符串(Strings)

前面我们说过:

char arr[5] = {'H', 'e', 'l', 'l', 'o'};  // 只是字符数组,不是字符串

为什么不是字符串呢?

因为它没有以 '\0' 结尾!

C语言中的字符串 = 一个以 null 结尾(即 '\0')的字符数组

也就是说:

char str[] = {'H', 'e', 'l', 'l', 'o', '\0'};  // 这是字符串

或者用简写:

char str[] = "Hello";  // 编译器会自动补 '\0'

字符串的内存结构

char str[] = "Hi";

等价于:

char str[3] = {'H', 'i', '\0'};

 声明字符串的方式

char name[10] = {'J','o','h','n','\0'};
  • 显式指定数组长度为 10

  • 只初始化前5项,其余自动填 0(C语言的初始化规则)

  • 多出空间可用于后续拼接等操作

char name[] = {'J','o','h','n','\0'};
  • 编译器自动推断数组长度为5

  • 和上一种不同,没有额外空间(刚好放下这5个字符)

  • 没有多余空间(不能 strcat 附加)

char name[] = "John";
  • 这是最常见、推荐的字符串声明方式

  • 字符串字面量会自动转换为字符数组并添加 '\0'

  • 编译器推断数组长度为 5(4个字符 + 1个 \0

char* n = "John";
  • "John" 是一个字符串常量,存在只读常量区(Read-only memory segment)

  • n 是一个指针,指向这块常量内存

📦 内存示意: 

[Stack]           [Read-Only Data]
+--------+        +--------+--------+--------+--------+--------+
|  n --> | -----> |  'J'   |  'o'   |  'h'   |  'n'   | '\0'   |
+--------+        +--------+--------+--------+--------+--------+栈变量              只读常量区,不能修改

⚠️ 特点:

  • 内容不可修改(例如 n[0] = 'M'; 是未定义行为,可能崩溃)

  • 节省空间(不用复制字面量)

为什么不能修改内容? 

因为字符串字面量是 只读的!它们放在 .rodata(read-only data section) 

n[0] = 'M';   // 非法访问只读内存,行为未定义(Undefined Behavior)

正确做法(可改内容):

char s[] = "abc";  // 数组副本,可修改
s[0] = 'z';        // OK,现在 s = "zbc"

 为什么不用复制字面量?

因为 "John" 本身已经是存储在内存中的一段文字,编译器在编译期就把它放在了常量段,它有了

地址,我们只要“拿来用”就行了。

所以不会把 "John" 的内容复制到栈,而是让 n 直接指向编译器分配的静态内存

好处:

  • 节省空间(不重复复制)

  • 快(不需要运行时构造)

代价:

  • 不能修改内容(是只读区域)


 为什么一定要有 '\0'

因为 C语言中字符串函数(如 printf, strlen, strcat 等)全靠 '\0' 判断字符串结束位置。

它没有 string.length() 这样的成员变量。只能一位一位读,直到遇到:

00000000   // 即 '\0',ASCII = 0

printf("%s", str) 的工作原理

❓如何打印一个字符串?

char name[] = "John";
printf("%s", name);

实际发生的事情(模拟代码逻辑):

void my_print_string(char* s) {while (*s != '\0') {putchar(*s);  // 打印一个字符s++;          // 移动到下一个字符}
}
  • %s 会触发 printf 调用字符串打印逻辑

  • 从传入的地址开始,一个字符一个字符地读,直到遇到 \0 停止

如果没有 \0 会怎样? 

没有 '\0'printf 会一直往后读,直到遇到某个随机内存中的 0 → 结果:乱码或程序崩溃 

scanf("%s", str) 的工作原理

❓如何从键盘读入一个字符串?

char name[100];
scanf("%s", name);

 实际发生的事情(伪代码):

void my_scan_string(char* s) {char ch;while (ch = getchar()) {if (ch == ' ' || ch == '\n') break;*s++ = ch;}*s = '\0';  // 手动添加结尾符!
}
  • 自动以空格、回车为结束

  • 自动加 '\0' 到末尾(你才可以继续用 printf("%s", name)

  • 所以你传入的数组必须足够大(要容得下 \0

如果你忘了预留空间给 \0

char str[4];
scanf("%s", str);  // 如果输入 "John",会写入 5 字节,越界!

 正确做法:总长度 = 最大字符数 + 1(for \0

为什么 \0 是必须的终止符?

原因说明
字符数组不存长度C语言的数组不记录“当前长度”
函数需要知道结束位置printf, strlen, strcpy 都必须知道“何时停止”
\0 = ASCII 值 0在内存中表示 “结束”,不会与正常字符冲突
所有字符串函数都依赖它没有它你什么都做不了

 

⚠️ 注意事项

1. 不能用空格分隔字符串!

char str = 'H' 'i';  // 错误语法,不能写两个字符连在一起

2. scanf("%s", str) 遇到空格会提前终止!

char name[100];
scanf("%s", name);   // 输入 "John Smith" → 只读到 "John"

📌 scanf 的工作机制

执行后:

  1. scanf 会从标准输入缓冲区(stdin)读取字符

  2. 它遇到的第一个非空白字符 → 开始填入 str

  3. 一直读,直到遇到空白字符(空格 ' '、制表符 '\t'、换行 '\n'

  4. 添加 \0,停止写入字符串

  5. 空白字符保留在缓冲区中,用于下一个 scanf

设计目的:

  • %s 处理的是“一个单词” → 所以它默认把空格视为词与词的分隔符

  • 它并不是专门为“读取整行”设计的!

所以 scanf("%s") 读取到第一个空格就会停止!

这就是为什么 C语言早期提供了 gets() 函数!

gets() 的目标是:一次读一整行,连空格都读进来! 

char line[100];
gets(line);  // 输入:John Smith → 全部读入
字符索引
'J'0
'o'1
'h'2
'n'3
' '4
'S'5
......
'\0'N
  • 它会一直读取,直到遇到换行符 \n

  • 然后把换行符“吃掉”,用 \0 结尾

  • 空格、制表符都能保留

📛 gets() 被淘汰了,为什么?

因为 gets 无法限制输入长度 → 极度不安全!

char buf[10];
gets(buf);  // 用户输入超过10字节,就会溢出

后果:

  • 内存溢出(buffer overflow)

  • 栈破坏(stack smashing)

  • 造成安全漏洞(攻击者可利用)

C11 标准中,gets() 被正式移除。 

✅ 现代安全替代品:fgets()

char line[100];
fgets(line, sizeof(line), stdin);
  • 它会读取整行,包括空格

  • 最多读取 sizeof(line) - 1 个字符,自动加 \0

  • 如果缓冲区不够大,会保留未读内容

⚠️ 注意:fgets() 会保留换行符 \n,如果你不想要它,要手动去掉:

line[strcspn(line, "\n")] = '\0';

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/89718.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/89718.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/89718.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【vue-5】Vue 3 中的 v-model:双向数据绑定的全面指南

在 Vue 开发中&#xff0c;v-model 是实现表单输入和应用状态之间双向绑定的关键指令。Vue 3 对 v-model 进行了重大改进&#xff0c;使其更加灵活和强大。本文将深入探讨 Vue 3 中 v-model 的工作原理、新特性以及最佳实践。 1. v-model 基础 1.1 什么是 v-model v-model 是 V…

结合自身,制定一套明确的 Web3 学习路线和技术栈建议

目录 ✅ 一、结合自身&#xff0c;明确方向和目的 ✅ 二、技术路线和建议 &#x1f9ed; 技术路线图&#xff08;按阶段划分&#xff09; 第一阶段&#xff1a;巩固 Web3 基础&#xff08;1-2 周&#xff09; 第二阶段&#xff1a;NFT 平台开发实战&#xff08;4-6 周&…

SPARKLE:深度剖析强化学习如何提升语言模型推理能力

摘要&#xff1a;强化学习&#xff08;Reinforcement Learning&#xff0c;RL&#xff09;已经成为赋予语言模型高级推理能力的主导范式。尽管基于 RL 的训练方法&#xff08;例如 GRPO&#xff09;已经展示了显著的经验性收益&#xff0c;但对其优势的细致理解仍然不足。为了填…

【Linux服务器】-MySQL数据库参数调优

一、基础配置 [mysqld] # 声明以下配置属于MySQL服务器&#xff08;mysqld&#xff09;[mysqld]&#xff1a;配置文件的模块标识&#xff0c;表示这是 MySQL 服务器的配置段。 二、路径与基础设置 datadir/var/lib/mysql socket/var/lib/mysql/mysql.sock pid-file/var/run/mys…

sqli-labs靶场通关笔记:第32-33关 宽字节注入

第32关 宽字节注入查看一下本关的源代码&#xff1a;function check_addslashes($string) // 定义一个用于过滤特殊字符的函数&#xff0c;目的是转义可能用于注入的特殊符号 {$string preg_replace(/. preg_quote(\\) ./, "\\\\\\", $string); // 转义…

基于Eureka和restTemple的负载均衡

在微服务架构中&#xff0c;基于 Eureka&#xff08;服务注册中心&#xff09;和 RestTemplate&#xff08;HTTP 客户端&#xff09;实现负载均衡是常见的方案&#xff0c;核心是通过 Eureka 获取服务实例列表&#xff0c;再结合负载均衡策略选择具体服务实例进行调用。以下是详…

子线程不能直接 new Handler(),而主线程可以

在 Android 中&#xff0c;子线程不能直接 new Handler()&#xff0c;而主线程可以&#xff0c;原因在于 Looper 机制。下面详细解释&#xff1a;1. 为什么主线程可以直接 new Handler()&#xff1f; 主线程&#xff08;UI 线程&#xff09;在启动时&#xff0c;系统会自动调用…

Android无需授权直接访问Android/data目录漏洞

从android11开始&#xff0c;访问/sdcard/Android/data目录需要URI授权&#xff0c;而从更高的版本开始甚至URI权限也被收回&#xff0c;返回“无法使用此文件夹”的提示&#xff0c;这里提供一种方法&#xff0c;可以越权强制访问data目录&#xff0c;当然也包括obb、media等目…

本地部署 Kimi K2 全指南(llama.cpp、vLLM、Docker 三法)

Kimi K2 是 Moonshot AI 于2025年7月11日发布的高性能多专家语言模型&#xff08;MoE&#xff09;&#xff0c;支持最大 128K 上下文&#xff0c;激活参数规模为 32B&#xff0c;具备极强的推理、代码生成与多轮对话能力。自从其权重以多种格式开源以来&#xff0c;许多开发者希…

使用python的pillow模块将图片转化为灰度图和相关的操作

使用python的pillow模块可以将图片转化为灰度图&#xff0c; 可以获取灰度图的特定点值&#xff0c;区域值&#xff0c; 修改值并保存到图片 图片转换为灰度图 from PIL import Image# 打开图片 image Image.open("d://python//2//1.jpg")gray_image image.convert…

【网络安全】大型语言模型(LLMs)及其应用的红队演练指南

未经许可,不得转载。 文章目录 什么是红队演练? 为什么 RAI 红队演练是一项重要实践? 如何开展和规划 LLM 的红队演练 1.测试前的准备 规划:由谁负责测试 规划:测试内容 规划:测试方式 规划:数据记录方式 2.测试过程中 3.每轮测试后 报告数据 区分“识别”与“测量” 本…

ROS2安装ros-humble-usb-cam 404错误导致失败的解决方法

ROS2安装ros-humble-usb-cam遇到404错误导致安装失败&#xff0c;如图&#xff1a;解决方法&#xff1a; 备份 sources.list sudo cp /etc/apt/sources.list.d/ros2.list /etc/apt/sources.list.d/ros2.list.bak替换为清华源 sudo sed -i s|http://packages.ros.org/ros2/ubunt…

OllyDbg技巧学习

1 尝试在反汇编代码中找到一个函数的二进制代码 有的时候需要一个函数的二进制代码&#xff0c;注入到另外的一些地方&#xff1b;以此程序为示例&#xff0c; 八叉树的C实现与原理解析-CSDN博客 Ollydbg打开可执行文件&#xff0c;我想先找到此函数的二进制代码体&#xff0…

数据分析智能体:让AI成为你的数据科学家

数据分析智能体&#xff1a;让AI成为你的数据科学家 &#x1f31f; 嗨&#xff0c;我是IRpickstars&#xff01; &#x1f30c; 总有一行代码&#xff0c;能点亮万千星辰。 &#x1f50d; 在技术的宇宙中&#xff0c;我愿做永不停歇的探索者。 ✨ 用代码丈量世界&#xff0c…

K8s与Helm实战:从入门到精通

Kubernetes 简介 Kubernetes(简称 K8s)是一个开源的容器编排平台,用于自动化部署、扩展和管理容器化应用。最初由 Google 设计并捐赠给云原生计算基金会(CNCF),现已成为容器编排领域的事实标准。 核心功能 自动化容器部署:支持声明式配置和自动化部署,减少人工干预。…

根据ARM手册,分析ARM架构中,原子操作的软硬件实现的底层原理

目录 1.问题背景&#xff1a; 2.原子操作 2.1 硬件操作 2.1.1 LDREX/LDXR指令 2.1.2 STREX/STXR指令 2.2 软件操作 2.3 软件硬件操作的各性能对比 3.总结 1.问题背景&#xff1a; 我们知道&#xff0c;RTOS的任务调度算法是抢占式优先级调度算法。 既然是抢占了&…

iOS 抓包工具选择与配置指南 从零基础到高效调试的完整流程

iOS 抓包&#xff1a;复杂网络调试的必要技能 随着移动端应用越来越依赖网络交互&#xff0c;iOS 抓包作为核心调试工具之一&#xff0c;变得尤为重要。无论是调试 App 与后端的接口通信、排查 HTTPS 请求加密问题&#xff0c;还是定位网络连接超时、请求异常&#xff0c;抓包都…

Java使用FastExcel实现Excel文件导入

依赖配置 (Maven pom.xml)<dependencies><!-- FastExcel 核心库 --><dependency><groupId>cn.idev.excel</groupId><artifactId>fastexcel</artifactId><version>1.0.0</version></dependency><!-- Apache POI…

【60】MFC入门到精通——运行后 button按键上不显示 按键名, 控件上的文字不显示

文章目录运行后&#xff0c;button按键上不显示 “Test”原因是属性&#xff0c;图标–>True&#xff0c;改为False就好了。

抖音回应:没有自建外卖,就是在团购的基础上增加的配送功能

今年以来&#xff0c;外卖行业竞争愈加激烈&#xff0c;市场格局风云变幻。在这一背景下&#xff0c;外卖行业动向备受关注。近日&#xff0c;针对抖音上线团购版外卖的消息引发公众关注。为此&#xff0c;大公科技以商家身份咨询了抖店客服&#xff0c;对方回应称&#xff0c;…