# C 編程,第 2 部分:文本輸入和輸出
> 原文:[Processes, Part 1: Introduction](https://github.com/angrave/SystemProgramming/wiki/Processes%2C-Part-1%3A-Introduction)
> 校驗:[_stund](https://github.com/hqiwen)
> 自豪地采用[谷歌翻譯](https://translate.google.cn/)
## 打印到流
### 如何將字符串,整數,字符打印到標準輸出流?
使用`printf`。第一個參數是格式字符串,其中包含要打印的數據的占位符。通用格式說明符是`%s`將參數視為 c 字符串指針,保持打印所有字符,直到達到 NULL 字符; `%d`將參數打印為整數; `%p`將參數打印為內存地址。
一個簡單的例子如下所示:
```c
char *name = ... ; int score = ...;
printf("Hello %s, your result is %d\n", name, score);
printf("Debug: The string and int are stored at: %p and %p\n", name, &score );
// name已經是一個字符串指針,指向字符串的開頭第一個字節的地址。
// 我們需要用“&”操作符來獲取整數變量score的地址。
```
默認情況下,為了提高性能,`printf`實際上不會寫出任何內容(通過調用 write),直到其緩沖區已滿或打印出換行符。
### 我怎么能打印字符串和單個字符?
使用`puts( name );`和`putchar( c )`,其中 name 是指向 C 字符串的指針,c 只是`char`
### 如何打印到其他文件流?
使用`fprintf( _file_ , "Hello %s, score: %d", name, score);`其中 _file_ 是預定義的'stdout''stderr'或`fopen`或`fdopen`返回的 FILE 指針
### 我可以使用文件描述符嗎?
是!只需使用`dprintf(int fd, char* format_string, ...);`只記得可以緩沖流,因此您需要確保將數據寫入文件描述符。
### 如何將數據打印到 C 字符串中?
使用`sprintf`或更好`snprintf`。
```c
char result[200];
int len = snprintf(result, sizeof(result), "%s:%d", name, score);
```
~~snprintf 返回寫入的字符數,不包括終止字節。在上面的例子中,這最多為 199。~~snprintf 返回有足夠的空間寫入字符串的長度,不包括末尾NULL字節。
```c
char x[5];
int size = snprintf(x, 5, "%s%s%s", "12", "34", "56"); // writes "1234" + null
printf("%d\n", size); // output 6
```
來源:  和 手冊頁。
### 如果我真的希望`printf`在沒有換行符的情況下調用`write`怎么辦?
使用`fflush( FILE* inp )`。將寫入文件的內容。如果我想寫沒有換行的“Hello World”,我可以像這樣寫。
```c
int main(){
fprintf(stdout, "Hello World");
fflush(stdout);
return 0;
}
```
### `perror`如何幫助?
假設您有一個失敗的函數調用(因為您檢查了手冊頁,它是一個失敗的返回碼)。 `perror(const char* message)`會將錯誤的英文版本打印到 stderr
```c
int main(){
int ret = open("IDoNotExist.txt", O_RDONLY);
if(ret < 0){
perror("Opening IDoNotExist:");
}
//...
return 0;
}
```
## 解析輸入
### 如何從字符串中解析數字?
使用`long int strtol(const char *nptr, char **endptr, int base);`或`long long int strtoll(const char *nptr, char **endptr, int base);`。
這些函數的作用是將指針指向您的字符串`*nptr`和`base`(即二進制,八進制,十進制,十六進制等)和可選指針`endptr`,并返回一個解析的 int。
```c
int main(){
const char *num = "1A2436";
char* endptr;
long int parsed = strtol(num, &endptr, 16);
return 0;
}
```
但要小心!錯誤處理有點棘手,因為該函數不會返回錯誤代碼。如果你傳入一個會返回0的字符串而不是一個數字,這意味著你不能區別出一個合格的“0”和一個不合格的字符串。查看參考手冊頁去獲得更多的關于strtol的不合法行為和超出邊界的值。一個安全的替代是使用`sscanf`(并且檢查返回值)。
```c
int main(){
const char *input = "0"; // or "!##@" or ""
char* endptr;
long int parsed = strtol(input, &endptr, 10);
if(parsed == 0){
// 不管輸入的字符串是一個合格的10進制數還是真的是0
}
return 0;
}
```
### 如何使用`scanf`將輸入解析為參數?
使用`scanf`(或`fscanf`或`sscanf`)分別從默認輸入流,任意文件流或 C 字符串獲取輸入。檢查返回值以查看解析了多少項是個好主意。 `scanf`函數需要有效的指針。傳遞不正確的指針值是常見的錯誤來源。例如,
```c
int *data = (int *) malloc(sizeof(int));
char *line = "v 10";
char type;
// 好習慣:確保scanf解析了line并讀取了兩個值
int ok = 2 == sscanf(line, "%c %d", &type, &data); // pointer error
```
我們想將字符值寫入 c,將整數值寫入 malloc 內存。但是我們傳遞了數據指針的地址,而不是指針指向的地址!所以`sscanf`會改變指針本身。即,指針現在將指向地址 10,因此該代碼稍后將失敗,例如當調用 free(數據)時。
### 如何阻止 scanf 導致緩沖區溢出?
以下代碼假定 scanf 不會將超過 10 個字符(包括終止字節)讀入緩沖區。
```c
char buffer[10];
scanf("%s",buffer);
```
您可以包含一個可選的整數來指定排除終止字節的字符數:
```c
char buffer[10];
scanf("%9s", buffer); // 讀取至多9個字符從輸入(第十個字節是留給終止字節的)
```
### 為什么`gets`很危險?我應該用什么呢?
以下代碼容易受到緩沖區溢出的影響。它假定或信任輸入行不超過 10 個字符,包括終止字節。
```c
char buf[10];
gets(buf); // 請記住數組名代表著數組的第一個字節
```
`gets`在 C99 標準中已棄用,已從最新的 C 標準(C11)中刪除。程序應使用`fgets`或`getline`代替。
每個都分別具有以下結構:
```c
char *fgets (char *str, int num, FILE *stream);
ssize_t getline(char **lineptr, size_t *n, FILE *stream);
```
這是一種簡單,安全的讀取單行的方法。超過 9 個字符的行將被截斷:
```c
char buffer[10];
char *result = fgets(buffer, sizeof(buffer), stdin);
```
如果出現錯誤或文件結束,則結果為 NULL。注意,與`gets`不同,`fgets`將換行符復制到緩沖區中,您可能要將其丟棄 -
```c
if (!result) { return; /* no data - don't read the buffer contents */}
int i = strlen(buffer) - 1;
if (buffer[i] == '\n')
buffer[i] = '\0';
```
### 我該如何使用`getline`?
`getline`的一個優點是將在足夠大小的堆上自動(重新)分配緩沖區。
```c
// ssize_t getline(char **lineptr, size_t *n, FILE *stream);
/* 初始化buffer和size的值,它們會被getline所改變 */
char *buffer = NULL;
size_t size = 0;
ssize_t chars = getline(&buffer, &size, stdin);
// 如果有換行符,就丟棄換行符
if (chars > 0 && buffer[chars-1] == '\n')
buffer[chars-1] = '\0';
// 讀取另外一行
// 存在的緩沖區會被重復使用,如果有必要它會被釋放并分配一個更大的緩沖區
chars = getline(&buffer, &size, stdin);
// 最后,不要忘了釋放緩沖區內存
free(buffer);
```
- UIUC CS241 系統編程中文講義
- 0. 簡介
- #Informal 詞匯表
- #Piazza:何時以及如何尋求幫助
- 編程技巧,第 1 部分
- 系統編程短篇小說和歌曲
- 1.學習 C
- C 編程,第 1 部分:簡介
- C 編程,第 2 部分:文本輸入和輸出
- C 編程,第 3 部分:常見問題
- C 編程,第 4 部分:字符串和結構
- C 編程,第 5 部分:調試
- C 編程,復習題
- 2.進程
- 進程,第 1 部分:簡介
- 分叉,第 1 部分:簡介
- 分叉,第 2 部分:Fork,Exec,等等
- 進程控制,第 1 部分:使用信號等待宏
- 進程復習題
- 3.內存和分配器
- 內存,第 1 部分:堆內存簡介
- 內存,第 2 部分:實現內存分配器
- 內存,第 3 部分:粉碎堆棧示例
- 內存復習題
- 4.介紹 Pthreads
- Pthreads,第 1 部分:簡介
- Pthreads,第 2 部分:實踐中的用法
- Pthreads,第 3 部分:并行問題(獎金)
- Pthread 復習題
- 5.同步
- 同步,第 1 部分:互斥鎖
- 同步,第 2 部分:計算信號量
- 同步,第 3 部分:使用互斥鎖和信號量
- 同步,第 4 部分:臨界區問題
- 同步,第 5 部分:條件變量
- 同步,第 6 部分:實現障礙
- 同步,第 7 部分:讀者編寫器問題
- 同步,第 8 部分:環形緩沖區示例
- 同步復習題
- 6.死鎖
- 死鎖,第 1 部分:資源分配圖
- 死鎖,第 2 部分:死鎖條件
- 死鎖,第 3 部分:餐飲哲學家
- 死鎖復習題
- 7.進程間通信&amp;調度
- 虛擬內存,第 1 部分:虛擬內存簡介
- 管道,第 1 部分:管道介紹
- 管道,第 2 部分:管道編程秘密
- 文件,第 1 部分:使用文件
- 調度,第 1 部分:調度過程
- 調度,第 2 部分:調度過程:算法
- IPC 復習題
- 8.網絡
- POSIX,第 1 部分:錯誤處理
- 網絡,第 1 部分:簡介
- 網絡,第 2 部分:使用 getaddrinfo
- 網絡,第 3 部分:構建一個簡單的 TCP 客戶端
- 網絡,第 4 部分:構建一個簡單的 TCP 服務器
- 網絡,第 5 部分:關閉端口,重用端口和其他技巧
- 網絡,第 6 部分:創建 UDP 服務器
- 網絡,第 7 部分:非阻塞 I O,select()和 epoll
- RPC,第 1 部分:遠程過程調用簡介
- 網絡復習題
- 9.文件系統
- 文件系統,第 1 部分:簡介
- 文件系統,第 2 部分:文件是 inode(其他一切只是數據...)
- 文件系統,第 3 部分:權限
- 文件系統,第 4 部分:使用目錄
- 文件系統,第 5 部分:虛擬文件系統
- 文件系統,第 6 部分:內存映射文件和共享內存
- 文件系統,第 7 部分:可擴展且可靠的文件系統
- 文件系統,第 8 部分:從 Android 設備中刪除預裝的惡意軟件
- 文件系統,第 9 部分:磁盤塊示例
- 文件系統復習題
- 10.信號
- 過程控制,第 1 部分:使用信號等待宏
- 信號,第 2 部分:待處理的信號和信號掩碼
- 信號,第 3 部分:提高信號
- 信號,第 4 部分:信號
- 信號復習題
- 考試練習題
- 考試主題
- C 編程:復習題
- 多線程編程:復習題
- 同步概念:復習題
- 記憶:復習題
- 管道:復習題
- 文件系統:復習題
- 網絡:復習題
- 信號:復習題
- 系統編程笑話