# 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
```
来源: ![this StackOverflow post](https://stackoverflow.com/questions/12746885/why-use-asprintfand) 和 手册页。
### 如果我真的希望`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 编程:复习题
- 多线程编程:复习题
- 同步概念:复习题
- 记忆:复习题
- 管道:复习题
- 文件系统:复习题
- 网络:复习题
- 信号:复习题
- 系统编程笑话