數據結構:字符串(Strings)

目錄

第一性問題:計算機如何表示文字?

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;…