1. 前言
C語言是學習程式設計基礎時非常重要的語言。其中,「字串輸入」是在接收使用者輸入資料時不可或缺的功能。本文將詳細解說C語言中的字串輸入方法,並介紹安全處理的技巧與注意事項。
特別是對初學者來說,在處理輸入的字串時,常常會遇到錯誤或安全性風險而感到困惑。因此,本文將從基本函式到進階程式碼範例廣泛涵蓋,目標是幫助讀者掌握實用技能。
只要正確理解並能安全地實作C語言的字串輸入,就能邁出撰寫更高階程式的第一步。那麼,接下來就進入具體內容吧。
2. 什麼是C語言的字串輸入?基本概念解析
什麼是字串?
在C語言中,字串以字元陣列的形式表示。字串的最後必須附加「 」這個終止符號,用來表示字串的結束。透過這個特性,C語言在處理字串時不需要明確指定長度。
字串與陣列的關係
在C語言中,字串實際上就是字元型別(char
型)的陣列。例如,可以這樣宣告字串:
char str[10]; // 準備一個可容納10個字元的字串緩衝區
在這個例子中,會分配一塊記憶體來存放最多10個字元的字串。但其中有1個字元要保留給終止符號「 」,因此實際可存放的字元數為9個。
字串常值的範例
字串常值是用雙引號(”)括起來的字串。以下是使用字串常值的範例:
char greeting[] = "Hello";
在這種情況下,greeting
會自動被視為大小為6(”Hello”+終止符號 )的陣列。
為什麼需要字串輸入?
在程式中,經常需要接收使用者輸入的資料。例如姓名或地址的登錄、搜尋關鍵字的輸入等,字串輸入是應用程式介面中不可缺少的功能。因此,理解如何安全且有效率地處理字串非常重要。
3. 字串輸入的基本函式與使用範例
3-1. scanf函式
scanf函式的基本用法
scanf
函式用於從標準輸入(鍵盤)讀取資料。在輸入字串時,會使用%s
這個格式指定子。
程式碼範例:
#include <stdio.h>
int main() {
char str[50]; // 準備一個最多可存放50字元的緩衝區
printf("請輸入字串: ");
scanf("%s", str); // 從標準輸入讀取字串
printf("輸入的字串: %s\n", str);
return 0;
}
這個程式會接收使用者輸入的字串,並將其輸出到螢幕上。
scanf函式的注意事項
- 無法處理空白字元:
scanf
會將空格、Tab、換行等視為分隔符號,因此輸入含有空白的字串時會被截斷。
範例:
輸入:
Hello World
輸出:
Hello
- 緩衝區溢位風險:
如果輸入字串超過緩衝區大小,可能會破壞記憶體區域,導致程式當掉或產生安全漏洞。
對策:
建議使用更安全的函式(如後面介紹的fgets
)。
3-2. fgets函式
fgets函式的基本用法
fgets
函式可以安全地輸入指定大小的字串,並包含換行字元,因此不會發生緩衝區溢位問題。
程式碼範例:
#include <stdio.h>
int main() {
char str[50]; // 準備一個最多可存放50字元的緩衝區
printf("請輸入字串: ");
fgets(str, sizeof(str), stdin); // 安全輸入字串
printf("輸入的字串: %s", str);
return 0;
}
這個程式會安全地接收最多50字元的輸入並輸出。
fgets函式的優點
- 避免緩衝區溢位: 可透過指定大小避免溢位。
- 可處理含空白的字串: 輸入的字串中可包含空格與Tab。
fgets函式的注意事項
- 換行字元處理: 輸入的最後會包含換行字元,可能導致輸出時多出一行。
刪除換行字元範例:
str[strcspn(str, "\n")] = '\0';
- 標準輸入緩衝殘留: 使用
fgets
後再進行其他輸入時,緩衝中可能殘留多餘資料。可透過fflush(stdin)
或getchar()
清除。
3-3. 該使用哪一個?
函式名稱 | 用途 | 注意事項 |
---|---|---|
scanf | 簡單字串輸入(不含空白的短字串) | 需注意空白處理與緩衝區溢位。 |
fgets | 安全,適合含空白的字串輸入 | 需處理換行字元與緩衝清理。 |
在初學者或實用程式中,建議優先使用fgets
以確保安全性。
4. 為了安全字串輸入的實用技巧
4-1. 緩衝區溢位對策
什麼是緩衝區溢位?
緩衝區溢位是指輸入的資料超過預先分配的記憶體空間(緩衝區),導致資料寫入其他記憶體區域的問題。這可能造成程式當機,甚至產生安全漏洞。
範例: 以下的程式碼是不安全的使用方式。
char str[10];
scanf("%s", str); // 沒有限制輸入大小
若輸入超過10個字元,就會發生緩衝區溢位。
對策1: 使用fgets
fgets
函式可以限制輸入大小,因此更安全。
安全範例:
#include <stdio.h>
#include <string.h>
int main() {
char str[10];
printf("請輸入字串: ");
fgets(str, sizeof(str), stdin);
str[strcspn(str, "\n")] = '\0'; // 移除換行符號
printf("輸入的字串: %s\n", str);
return 0;
}
這段程式會限制輸入不超過10個字元,並適當處理換行字元。
對策2: 驗證輸入長度
當使用者輸入過長時,可以顯示警告並中止處理。
範例:
#include <stdio.h>
#include <string.h>
int main() {
char str[10];
printf("請輸入字串 (最多9個字元): ");
fgets(str, sizeof(str), stdin);
if (strlen(str) >= sizeof(str) - 1 && str[strlen(str) - 1] != '\n') {
printf("輸入過長。\n");
return 1; // 錯誤結束
}
str[strcspn(str, "\n")] = '\0'; // 移除換行符號
printf("輸入的字串: %s\n", str);
return 0;
}
這個範例會檢查輸入長度,若過長就會提示錯誤並結束程式。
4-2. 錯誤處理的實作
錯誤處理的重要性
錯誤處理對於提升程式的穩定性至關重要。特別是在處理使用者輸入時,必須能應對無效或意料之外的輸入。
對策1: 重試輸入
當使用者輸入無效時,要求重新輸入。
範例:
#include <stdio.h>
#include <string.h>
int main() {
char str[10];
int valid = 0;
while (!valid) {
printf("請輸入字串 (最多9個字元): ");
fgets(str, sizeof(str), stdin);
// 驗證輸入長度
if (strlen(str) >= sizeof(str) - 1 && str[strlen(str) - 1] != '\n') {
printf("輸入過長,請再試一次。\n");
while (getchar() != '\n'); // 清除多餘輸入
} else {
str[strcspn(str, "\n")] = '\0'; // 移除換行符號
valid = 1; // 有效輸入
}
}
printf("輸入的字串: %s\n", str);
return 0;
}
這個程式會在輸入錯誤時提示使用者重新輸入。
對策2: 輸入過濾
可以設定條件(如僅允許英數字),過濾不合法輸入。
範例:
#include <stdio.h>
#include <ctype.h>
#include <string.h>
int isValidInput(const char *str) {
for (int i = 0; str[i] != '\0'; i++) {
if (!isalnum(str[i])) { // 僅允許英數字
return 0;
}
}
return 1;
}
int main() {
char str[50];
printf("請輸入僅包含英數字的字串: ");
fgets(str, sizeof(str), stdin);
str[strcspn(str, "\n")] = '\0';
if (isValidInput(str)) {
printf("輸入的字串: %s\n", str);
} else {
printf("輸入無效。\n");
}
return 0;
}
這個程式會檢查輸入是否僅包含英數字,若有其他字元則視為無效。
5. 非建議使用的函式與替代方案
5-1. gets函式的危險性
什麼是gets函式?
gets
函式用於從使用者輸入讀取字串。基本用法如下:
程式碼範例:
char str[50];
gets(str); // 從標準輸入讀取字串
這段程式看似簡單,但其實存在致命問題。
gets函式的問題點
- 緩衝區溢位風險:
gets
沒有提供限制輸入長度的機制,如果輸入超過陣列大小,就會破壞記憶體,導致程式錯誤甚至安全漏洞。
範例:
char str[10];
gets(str); // 沒有輸入大小限制
如果輸入20個字元,程式可能當掉或行為異常。
- 安全風險:
攻擊者可能利用緩衝區溢位進行惡意攻擊(如Buffer Overflow Attack),造成系統被入侵。 - 標準規格廢止:
在C99標準中已經標註為「不建議使用」,C11更是完全刪除了。現代編譯器通常會對gets
顯示警告或錯誤。
5-2. 安全的替代函式
使用fgets函式
取代gets
最常見的方法是使用fgets
,它能避免緩衝區溢位。
安全程式碼範例:
#include <stdio.h>
#include <string.h>
int main() {
char str[50];
printf("請輸入字串: ");
fgets(str, sizeof(str), stdin); // 安全輸入
str[strcspn(str, "\n")] = '\0'; // 移除換行符號
printf("輸入的字串: %s\n", str);
return 0;
}
與scanf函式比較
scanf
也能用來輸入字串,但如前所述,它無法處理空白字元,且有緩衝區溢位風險。因此,如果需要處理複雜字串或更高安全性,建議使用fgets
。
使用getline函式
在支援POSIX標準的系統中,也可以使用getline
。它會動態配置記憶體,因此不必擔心緩衝區大小不足的問題。
範例:
#include <stdio.h>
#include <stdlib.h>
int main() {
char *line = NULL;
size_t len = 0;
ssize_t read;
printf("請輸入字串: ");
read = getline(&line, &len, stdin); // 動態分配記憶體接收輸入
if (read != -1) {
printf("輸入的字串: %s", line);
}
free(line); // 釋放記憶體
return 0;
}
避免使用不安全函式的重要性
在C語言中,部分舊有函式因安全性問題已經被棄用。特別是涉及外部輸入時,應盡量避免使用像gets
這類危險函式,並改用安全的替代方案。
不建議使用的函式 | 替代函式 | 用途與優點 |
---|---|---|
gets | fgets | 安全、支援固定大小的字串輸入。 |
gets | getline | 支援動態記憶體,適合長字串輸入。 |
scanf("%s") | fgets | 可安全處理含空白的字串。 |
6. 實踐範例與應用篇|多行字串輸入與處理
6-1. 接收多行字串輸入
多行輸入的概要
在程式中,有時需要讓使用者輸入多行字串並一次性處理。例如,像記事本這類應用程式,多行輸入是必須的。
範例1: 接收3行輸入的程式
#include <stdio.h>
#include <string.h>
#define MAX_LINES 3 // 輸入行數
#define MAX_LENGTH 100 // 每行的最大字元數
int main() {
char lines[MAX_LINES][MAX_LENGTH]; // 用於儲存字串的二維陣列
printf("請輸入 %d 行字串:\n", MAX_LINES);
for (int i = 0; i < MAX_LINES; i++) {
printf("第 %d 行: ", i + 1);
fgets(lines[i], sizeof(lines[i]), stdin);
lines[i][strcspn(lines[i], "\n")] = '\0'; // 移除換行字元
}
printf("\n您輸入的字串:\n");
for (int i = 0; i < MAX_LINES; i++) {
printf("第 %d 行: %s\n", i + 1, lines[i]);
}
return 0;
}
重點解析:
- 使用二維陣列: 每行輸入分別儲存在獨立的陣列中。
- 利用fgets安全輸入: 限制最大字元數並刪除換行符號。
- 透過for迴圈處理: 使用迴圈來簡化多行輸入的處理。
執行範例:
請輸入 3 行字串:
第 1 行: Hello
第 2 行: World
第 3 行: C Language
您輸入的字串:
第 1 行: Hello
第 2 行: World
第 3 行: C Language
6-2. 處理包含空白或特殊字元的字串
包含空白的輸入範例
當需要安全地處理包含空白的字串時,應使用fgets
。
範例2: 處理含有空格的輸入
#include <stdio.h>
#include <string.h>
int main() {
char str[100]; // 準備一個最多可存放100字元的緩衝區
printf("請輸入一段文字: ");
fgets(str, sizeof(str), stdin);
str[strcspn(str, "\n")] = '\0'; // 移除換行字元
printf("您輸入的文字: %s\n", str);
return 0;
}
重點:
- 能安全接收包含空格或Tab的輸入。
- 刪除換行符號可避免多餘的換行輸出。
執行範例:
請輸入一段文字: Hello World with spaces
您輸入的文字: Hello World with spaces
6-3. 特殊字元與跳脫字元處理
特殊字元輸入範例
在C語言中,也需要處理包含跳脫字元或符號的輸入。
範例3: 偵測特殊字元的程式
#include <stdio.h>
#include <string.h>
#include <ctype.h>
int main() {
char str[100];
int specialCharCount = 0;
printf("請輸入字串: ");
fgets(str, sizeof(str), stdin);
str[strcspn(str, "\n")] = '\0'; // 移除換行符號
for (int i = 0; str[i] != '\0'; i++) {
if (!isalnum(str[i]) && !isspace(str[i])) { // 非英數字且非空白
specialCharCount++;
}
}
printf("特殊字元數量: %d\n", specialCharCount);
return 0;
}
重點:
- 使用
isalnum
判斷是否為英數字。 - 使用
isspace
判斷是否為空白。 - 其餘字元皆視為「特殊字元」。
執行範例:
請輸入字串: Hello, World! 123
特殊字元數量: 2
6-4. 應用範例:簡易記事本程式
最後,我們來看一個結合多行輸入與檔案輸出的簡易記事本程式。
範例4: 記事本程式
#include <stdio.h>
#include <string.h>
#define MAX_LINES 5
#define MAX_LENGTH 100
int main() {
char lines[MAX_LINES][MAX_LENGTH];
printf("您最多可以輸入 %d 行備忘錄。\n", MAX_LINES);
for (int i = 0; i < MAX_LINES; i++) {
printf("第 %d 行: ", i + 1);
fgets(lines[i], sizeof(lines[i]), stdin);
lines[i][strcspn(lines[i], "\n")] = '\0';
}
FILE *file = fopen("memo.txt", "w");
if (file == NULL) {
printf("無法開啟檔案。\n");
return 1;
}
for (int i = 0; i < MAX_LINES; i++) {
fprintf(file, "%s\n", lines[i]);
}
fclose(file);
printf("備忘錄已儲存。\n");
return 0;
}
重點:
- 將使用者輸入儲存到檔案中。
- 能學習檔案操作的基礎應用。
7. 常見問題(Q&A形式)
Q1: 為什麼不建議使用gets函式?
A:
gets
函式讀取輸入時不會限制字串長度,因此存在緩衝區溢位的風險。這種漏洞會造成嚴重的安全問題,因此在C11之後已被標準移除。建議改用更安全的fgets
。
範例(安全寫法):
char str[50];
fgets(str, sizeof(str), stdin);
str[strcspn(str, "\n")] = '\0'; // 移除換行符號
Q2: 為什麼scanf無法處理包含空白的輸入?
A:
因為scanf
預設會將空白字元(空格、Tab、換行)視為分隔符號,所以輸入含有空格的字串會被截斷。
範例:
輸入:
Hello World
輸出:
Hello
解決方法:
若要處理含空格的字串,請使用fgets
。
char str[100];
fgets(str, sizeof(str), stdin);
str[strcspn(str, "\n")] = '\0';
printf("輸入的字串: %s\n", str);
Q3: 如果輸入超過fgets指定大小會怎麼辦?
A:
fgets
只會讀取到指定大小,過長的輸入會被截斷。
解決方法:
- 當輸入過長時,顯示錯誤訊息或要求重新輸入。
- 在POSIX環境中可以使用
getline
動態分配記憶體,處理更長的字串。
範例(使用getline):
#include <stdio.h>
#include <stdlib.h>
int main() {
char *line = NULL;
size_t len = 0;
printf("請輸入字串: ");
getline(&line, &len, stdin); // 動態配置記憶體
printf("輸入的字串: %s", line);
free(line); // 釋放記憶體
return 0;
}
Q4: fgets會保留換行字元,該如何處理?
A:
因為fgets
會將換行字元一併讀入,輸出時可能會多出一行。
解決方法:
手動刪除換行字元。
char str[100];
fgets(str, sizeof(str), stdin);
str[strcspn(str, "\n")] = '\0'; // 移除換行符號
printf("輸入的字串: %s\n", str);
Q5: 如果fgets後輸入緩衝區還有殘留資料怎麼辦?
A:
當輸入過長時,未讀取的資料會殘留在緩衝區。這會影響下一次輸入。
解決方法:
使用getchar()
清除緩衝區。
char str[10];
fgets(str, sizeof(str), stdin);
// 如果沒有讀到換行字元,清空緩衝區
if (strchr(str, '\n') == NULL) {
int c;
while ((c = getchar()) != '\n' && c != EOF);
}
printf("輸入的字串: %s\n", str);
Q6: 想只允許英數字輸入,該怎麼做?
A:
可以在程式中加入檢查,過濾掉非英數字的輸入。
範例:
#include <stdio.h>
#include <ctype.h>
#include <string.h>
int isValidInput(const char *str) {
for (int i = 0; str[i] != '\0'; i++) {
if (!isalnum(str[i])) { // 若不是英數字則回傳0
return 0;
}
}
return 1;
}
int main() {
char str[50];
printf("請輸入僅包含英數字的字串: ");
fgets(str, sizeof(str), stdin);
str[strcspn(str, "\n")] = '\0';
if (isValidInput(str)) {
printf("有效輸入: %s\n", str);
} else {
printf("輸入無效。\n");
}
return 0;
}
Q7: 如果要處理超過緩衝區大小的長字串怎麼辦?
A:
fgets
可以分段讀取,或者在POSIX環境中使用getline
,動態分配記憶體以處理長字串。
範例:
使用getline
能應對大量輸入,前面已有示範。
8. 總結
本文從C語言中「字串輸入」的基礎到進階應用進行了系統性的解說。讀者應該已經學會如何透過適當的函式與技巧,來安全且有效率地處理字串輸入。以下整理重點:
1. 理解字串輸入的基礎
在C語言中,字串是以字元陣列表示,並由終止符號\0
結束。理解這個基本概念能幫助正確進行字串操作。
2. 輸入函式的特性與選擇
scanf
函式:
無法處理空白,且存在緩衝區溢位風險,需小心使用。fgets
函式:
能指定輸入大小,安全性高,但需注意換行字元處理。getline
函式:
可動態配置記憶體,適合大量字串輸入(POSIX環境限定)。
3. 實踐安全的字串輸入
為了提升安全性,本文介紹了以下要點:
- 緩衝區大小管理: 避免過長輸入破壞記憶體。
- 換行符號處理: 刪除不必要的換行符,保持輸入乾淨。
- 輸入驗證: 過濾不合法字元,並實作錯誤處理或重試機制。
4. 透過應用範例強化實力
透過多行輸入與檔案輸出的應用範例,學會如何將基礎技巧延伸至更實用的程式設計。例如,使用二維陣列與動態記憶體管理,可應用於更進階的專案。
5. 學會解決常見疑問
Q&A部分針對初學者常遇到的困難提供具體解答,例如:
gets
函式的危險性,以及fgets
/getline
的替代方案。- 處理空白與特殊字元的方法。
- 面對過長輸入或換行符號問題的對策。
6. 下一步的學習方向
掌握字串輸入的基礎後,可以進一步挑戰以下主題:
- 字串處理函式庫的活用:
如strlen
,strcmp
,strcpy
等標準函式。 - 動態記憶體管理:
利用malloc
與realloc
彈性處理字串。 - 檔案輸入輸出:
設計能處理大量資料的程式。 - 資料結構與演算法:
例如字串搜尋、排序演算法等。
7. 實踐練習題
為了鞏固學習效果,可以挑戰以下練習:
- 練習1: 名單管理系統
利用多行輸入建立姓名、年齡等資料的管理程式。 - 練習2: 關鍵字搜尋程式
建立搜尋字串的程式,判斷是否包含特定關鍵字。 - 練習3: 檔案紀錄系統
將輸入資料儲存到檔案中,並可事後讀取。
總結中的總結
本文全面涵蓋了C語言字串輸入的基礎與應用。
最重要的重點:
- 安全性: 使用
fgets
或getline
等安全函式。 - 緩衝區與錯誤處理: 防止溢位並處理異常狀況。
- 實務應用: 結合多行輸入與檔案操作,開發更進階的程式。
只要理解並實踐這些重點,就能大幅提升C語言字串處理的能力,為後續更高階的程式設計奠定基礎。