# 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);
```