# C 编程,第 2 部分:文本输入和输出 > 原文: ## 打印到流 ### 如何将字符串,整数,字符打印到标准输出流? 使用`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); ```