Linux中C/C++的输入输出小结

本文详细介绍了Linux下的I/O概念,包括文件描述符及其与标准输入输出的关系,以及I/O重定向的方法。同时,深入探讨了C语言中的输入输出函数scanf和printf的用法,并对比了C++中的输入输出方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

参考
http://www.cplusplus.com/
http://www.learnlinux.org.za/courses/build/shell-scripting/ch01s04.html
http://www.cnblogs.com/weidagang2046/p/io-redirection.html
http://my.oschina.net/qihh/blog/55308

目录

  • Linux I/O
  • I/O重定向
  • C的输入输出
  • C++的输入输出

Linux I/O

Linux中,每个打开的文件都是通过文件描述符(File Descriptor)来标识的,内核为每个进程维护了一个文件描述符表(FDT),这个表以FD为索引,再进一步指向文件的详细信息。在进程创建时,内核为进程默认创建了0、1、2三个特殊的FD,这就是STDIN、STDOUT和STDERR:

这里写图片描述

首先看一个测试程序:

#include<stdio.h>
int main()
{
        extern FILE *stdin;
        extern FILE *stdout;
        extern FILE *stderr;
        printf("stdin's fd is : %d\n", stdin->_fileno);
        printf("stdout's fd is : %d\n", stdout->_fileno);
        printf("stderr's fd is : %d\n", stderr->_fileno);
        return 0;
}

其中直接获得结构体成员_fileno即是fd,也可以调用函数fileno获得fd,fileno其实就是一个宏定义,也是访问结构体成员_fileno达到目的。输出结果:

stdin's fd is : 0
stdout's fd is : 1
stderr's fd is : 2

我们可以在相关头文件看到声明,在stdio.h中:

extern struct _IO_FILE *stdin;   /* Standard input stream. */
extern struct _IO_FILE *stdout;   /* Standard output stream. */
extern struct _IO_FILE *stderr;   /* Standard error output stream. */
typedef struct _IO_FILE FILE;

可以看出stdin,stdout,stderr就是我们熟知的FILE结构体指针。在glibc的代码中,以stdin为例:

_IO_FILE *stdin = (FILE *) &_IO_2_1_stdin_;
DEF_STDFILE(_IO_2_1_stdin_, 0, 0, _IO_NO_WRITES);
#define DEF_STDFILE(NAME, FD, CHAIN, FLAGS)

FILE结构体中成员_fileno即fd,故stdin指向的FILE结构体fd为0。
再看一个测试程序:

#include <stdio.h>
 int main()
 {
        printf("%s\n", "hello world!");
        fprintf(stdout, "%s\n", "hello world!");
        return 0;
 }

输出结果:

hello world!
hello world!

可以看出,两个语句效果相同。其实,我们在glibc的代码中找到printf的实现:

 __nldbl_vfprintf (stdout, fmt, arg);

和fprintf的实现:

__nldbl_vfprintf (stream, fmt, arg);

可以看出printf就是将stdout作为默认参数的fprintf。

小结

Linux中程序的输入输出都是对文件操作的。每个程序开始运行时都会初始化3个FILE指针stdin,stdout,stderr,他们的文件描述符为0,1,2。printf函数的实质就是一个默认参数为stdout的fprintf。
另外,值得一提的是,stdin和stdout都是行缓冲的,而stderr是无缓冲的,磁盘文件I/O是全缓冲的,参考这里

I/O重定向

我们可以在shell重定向I/O,也可以在程序的代码中重定向I/O。在I/O重定向的过程中,不变的是FD 0/1/2代表STDIN/STDOUT/STDERR,变化的是文件描述符表中FD 0/1/2对应的具体文件。
shell中有一下几个重定向符:

< 或 0<
open a file for reading and associate with STDIN
用法:command < filename

<< 或 0<<
use the current input stream as STDIN for the program until token is seen. i.e. "here document".
用法:command << endflag
     input for command
     endflag

> 或 1>
open a file for writing and truncate it and associate it with STDOUT.
用法:command > filename

>> 或 1>>
open a file for writing and seek to the end and associate it with STDOUT. This is how you append to a file using a redirect.
用法:command >> filename

2>
用法同1>,输出的是错误信息
用法:command 2> filename

2>>
用法同1>>,输出的是错误信息
用法:command 2>> filename

n>&m
redirect FD n to the same places as FD m. Eg, 2>&1 means send STDERR to the same place that STDOUT is going to.
n>&-
关闭文件描述符n
用法:n如果为0,1都是可以省略的,m前不能有空格。

也可以使用exec命令进行重定向,用法举例:

#!/bin/bash

exec 3<&0 0<input.txt
read line1
read line2
exec 0<&3
echo $line1
echo $line2

其中exec 3<&0 0<input.txt把标准输入重定向到文件描述符3(0表示标准输入),然后把文件input.txt内容重定向到文件描述符0,实际上就是把文件name.txt中的内容重定向到文件描述符3。然后通过exec打开文件描述符3;然后,通过read命令读取name.txt的第一行内容line1,第二行内容line2,通过Exec 0<&3关闭文件描述符3;最后,用echo命令输出line1和line2。

在程序中也可以重定向,C语言中有freopen函数:

#include<stdio.h>
int main()
{
        freopen("input","r",stdin);
        freopen("output","w",stdout);
        char str[10];
        scanf("%s", str);
        printf("%s\n", str);
        freopen("/dev/pts/1","r",stdin);
        freopen("/dev/pts/1","w",stdout);
        //将stdin和stdout恢复
        scanf("%s", str);
        printf("%s\n", str);
        return 0;
}

scanf会从文件input读入字符串,而第一个printf会将读入的字符串输出到文件output;第二个freopen则将stdout再次重定向到输出设备,这里的输出设备是从xshell连接的伪终端,可以在shell中用w命令或ps命令查看都有哪些登录的用户以及他们的终端类型。这里简单说下tty和pts的区别,tty是硬链接的终端,如在物理机中ctrl+alt+F1进入的界面即是tty1,GUI中点开的terminal,或是xshell的连接,都是pts伪终端,这些信息都可以在w命令中看到,pts设备文件的路径为/dev/pts/num,tty设备文件路径为/dev/ttynum。

C的输入输出

本节详细介绍scanf和printf的用法。使用这两个函数是基本可以满足输入输出需求的。以下内容来源于http://www.cplusplus.com/reference/cstdio/scanf/

scanf

定义:int scanf ( const char * format, … );
Read formatted data from stdin and stores them according to the parameter format into the locations pointed by the additional arguments.The additional arguments should point to already allocated objects of the type specified by their corresponding format specifier within the format string.

参数const char * format
常量字符串format控制着流中的字符如何被处理,关于format:
1. scanf会读入并丢弃format开头的所有空白符(空白符包括空格换行制表)直到遇到非空白符,而format中的一个空白符可以匹配流中任意个空白符,包括0个;
2. scanf对于format中空白符和格式区分符(以字符%开头)以外的字符,将与流中的字符严格匹配,如果匹配成功,将忽略这个字符,继续看format下一个字符。如果匹配失败,scanf将fail并返回,流中仍有未读的字符。(经测试流中匹配失败的那个字符仍在缓冲区);
3. 格式区分符:以%开头,用来说明流中将被读取数据的类型和格式。接下来重点介绍格式区分符。

格式区分符
%[*][width][length]specifier
需要说明的是,参数列表中的变量个数应该不少于格式区分符的个数,多出的变量参数会被忽略。specifier有以下几种,粗体为对于specifier能匹配的字符:

specifierDescriptionCharacters extracted
iIntegerAny number of digits, optionally preceded by a sign (+ or -).Decimal digits assumed by default (0-9), but a 0 prefix introduces octal digits (0-7), and 0x hexadecimal digits (0-f).Signed argument.
d or uDecimal integerAny number of decimal digits (0-9), optionally preceded by a sign (+ or -).d is for a signed argument, and u for an unsigned.
oOctal integerAny number of octal digits (0-7), optionally preceded by a sign (+ or -).Unsigned argument.
xHexadecimal integerAny number of hexadecimal digits (0-9, a-f, A-F), optionally preceded by 0x or 0X, and all optionally preceded by a sign (+ or -).Unsigned argument.
f, e, g, aFloating point numberA series of decimal digits, optionally containing a decimal point, optionally preceeded by a sign (+ or -) and optionally followed by the e or E character and a decimal integer (or some of the other sequences supported by strtod).Implementations complying with C99 also support hexadecimal floating-point format when preceded by 0x or 0X.
cCharacterThe next character. If a width other than 1 is specified, the function reads exactly width characters and stores them in the successive locations of the array passed as argument. No null character is appended at the end.(注:%c会读入空白符)
sString of charactersAny number of non-whitespace characters, stopping at the first whitespace character found. A terminating null character is automatically added at the end of the stored sequence.
pPointer addressA sequence of characters representing a pointer. The particular format used depends on the system and library implementation, but it is the same as the one used to format %p in fprintf.
[characters]ScansetAny number of the characters specified between the brackets.A dash (-) that is not the first character may produce non-portable behavior in some library implementations.(注:与%[length]c不同的是,这个会在接收变量最后加\0)
[^characters]Negated scansetAny number of characters none of them specified as characters between the brackets.(注:同上,会在接收变量最后加\0)
nCountNo input is consumed.The number of characters read so far from stdin is stored in the pointed location.
%%A % followed by another % matches a single %.

The format specifier can also contain sub-specifiers: asterisk (*), width and length (in that order), which are optional:

sub-specifierdescription
*An optional starting asterisk indicates that the data is to be read from the stream but ignored (i.e. it is not stored in the location pointed by an argument).
widthSpecifies the maximum number of characters to be read in the current reading operation (optional).
lengthOne of hh, h, l, ll, j, z, t, L (optional).This alters the expected type of the storage pointed by the corresponding argument.

scanf返回值

成功时返回参数列表中得到值的变量个数(可以是0。匹配失败且此时还未读入变量的情况算成功),这个个数可能由于匹配失败,读错误,到达EOF而小于总的变量个数。
如果在未成功读入任何数据前发生读错误,或是到达EOF,则返回EOF(-1)。

scanf总结

scanf对于数字类型,字符串类型的格式区分符匹配,会先把流中开始的空白符(whitespace,换行空格制表)读入并丢弃,直到遇见非空白符;匹配失败时,失败的那个字符仍在输入缓冲区里。

接下来看几个应用场景的测试例:

1, 在已知输入格式时读入一行

input.txt(每行开头,间隔,还是末尾的空格数量随意):

     xjy     18      USTC 
   cc     19      MIT   
     nami             20 DKU   

读入并输出

#include<stdio.h>
int main()
{
        freopen("input.txt", "r", stdin);
        char name[10], school[10];
        int age;
        while (scanf("%s%d%s", name, &age, school) != EOF) {
                printf("name: %s, age: %d, school: %s\n", name, age, school);
        }   
        return 0;
}

再次强调,scanf在匹配数字类型%i, %d, %u, %o, %x, %f以及字符串类型%s时,会在成功匹配前把流中的空白符都读入并忽略。如在读入第一行最后一个字符串USTC后,为了匹配format中下一个格式区分符%s,则把USTC之后的数个空格和换行以及第二行开头的数个空格都读入并丢弃,然后把cc读入name。

2, 未知输入格式时读入一行到一个字符数组,input.txt同上。

#include<stdio.h>
int main()
{
        char s[50], *fmt = "%[^\n]%*c";
        freopen("input.txt", "r", stdin);
        while (scanf(fmt, s) != EOF) {
                printf("%s\n", s); 
        }   
        return 0;
}

这个测试例中,%[^\n]即匹配任意不是\n的字符,而每行末尾的换行却不能被处理掉,所以用%*c来吸收掉换行,从而完成读取一行含空格的字符串的目的。

3, %[characters]和%[^characters]的使用

#include<stdio.h>
int main()
{
        char s1[10] = {'y', 'y', 'y', 'y', 'y', 'y', 'y', 'y'};
        char s2[10] = {'y', 'y', 'y', 'y', 'y', 'y', 'y', 'y'};
        char s3[10] = {'y', 'y', 'y', 'y', 'y', 'y', 'y', 'y'};
        scanf("%5c", s1);
        //读入5个字符给s1,并不补\0
        printf("s1:%s\n", s1);
        scanf("%[^x]", s2);
        //遇到字符'x'前都读入到s2,如果上来就是x,则scanf返回EOF
        printf("s2:%s\n", s2);
        scanf("%[a-x]", s3);
        //范围在a-x内的字符读入到s3
        printf("s3:%s\n", s3);
        return 0;
}

测试:

输入:

abcdexxx

输出:

s1:abcdeyyy
s2:yyyyyyyy
s3:xxx

分析:
第一个scanf读入5个字符并写入s1,且不补\0,所以s1并未截断;第二个scanf要求读入不是x的字符,然后此时输入缓冲区中还有xxx和\n,故匹配失败,scanf直接返回0;第三个scanf则要求读入a-x的字符,则把xxx读入,且自动补\0。

4, 挨个读取字符,可以用getchar。相关函数getch、getche、fgetc、getc、getchar、fgets、gets可参考这里

#include<stdio.h>
int main()
{
        freopen("input.txt", "r", stdin);
        char c;
        while ((c = getchar()) != EOF)
                printf("%d\n", c); 
        return 0;
}

值的一提的是,vim会自动在文本最后加入换行(\n,值是10)。

printf

这个貌似没什么梗~=.=~参考手册http://www.cplusplus.com/reference/cstdio/printf/

C++的输入输出

Google C++ 编码规范中提到,建议不要使用流,除非是日志接口需要,使用printf 之类的代替。

cin的确比scanf效率低,虽然可以使用ios_base::sync_with_stdio(false);关闭C++与C输入输出流的同步来提升性能,不过也有相关问题。所以建议C++也使用printf和scanf。

不过这里还是总结下C++输入输出的相关类。

C++_stream_class

啊。C++的相关梗太多。。坑之后再填。。。

<think>首先,用户的问题是:“我想在C++环境中进行输入输出操作 请问C++ 输入输出操作 方法”。这是一个关于C++输入输出操作的查询。 根据系统级指令: - 所有行内数学表达式必须使用$...$格式。 - 独立公式必须使用$$...$$格式并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识,例如:[^1] 用户可见层指令: - 回答结构清晰,帮助用户逐步解决问题。 - 尽量保证回答真实可靠。 参考站内引用提供了相关信息: - 引用[1]:关于C/C++输入输出的详解,包括cin等。 - 引用[2]:C++输入输出的三种类型:标准I/O、文件I/O、串I/O。 - 引用[3]:C++输入输出的重要性,包括cout函数。 - 引用[4]:C++输入输出方式小结,包括各种输入输出方法及其用法。 - 引用[5]:C++输入输出流和文件操作总结,包括cin.get和cin.getline的区别。 我需要基于这些引用构建一个结构清晰的回答。回答应该覆盖C++输入输出的基本方法,包括标准输入输出、文件输入输出和字符串输入输出。 大纲回答: 1. **引言**:简要介绍C++输入输出的重要性和基本概念。 2. **标准输入输出**:使用cin和cout进行键盘输入和屏幕输出。 - 包括基本用法、格式化输出等。 3. **文件输入输出**:使用fstream进行文件操作。 - 包括读取文件、写入文件。 4. **字符串输入输出**:使用stringstream进行字符串操作。 5. **其他输入输出方法**:如scanf/printf、getchar/putchar等。 6. **常见问题与注意事项**:如输入缓冲问题、错误处理。 7. **总结**:简要概括。 8. **相关问题**:根据指令生成相关问题。 确保使用中文回答。在适当的地方添加引用标识。例如,当引用特定内容时,在句子末尾添加[^数字]。 生成相关问题: - 基于用户查询,生成3-5个相关问题。例如: 1. C++中如何读取文件? 2. 如何处理输入错误? 3. C++输入输出性能优化? 4. 文件I/O的常见错误? 5. 字符串流的使用场景? 最后,确保回答真实可靠,基于提供的引用。</think>### C++ 输入输出操作方法详解 在C++编程中,输入输出(I/O)操作是核心功能之一,用于与用户交互、处理文件和数据流。C++标准库提供了一套强大的流类(如`iostream`、`fstream`、`sstream`),支持三种主要类型:**标准I/O**(键盘输入、屏幕输出)、**文件I/O**(磁盘文件读写)和**字符串I/O**(内存字符串处理)。下面我将逐步介绍这些方法,确保内容真实可靠,基于C++标准库文档和最佳实践。 --- #### 1. **标准输入输出(标准I/O)** 标准I/O用于从键盘读取输入和向屏幕输出结果,主要通过`<iostream>`库实现。常用对象包括`cin`(标准输入)、`cout`(标准输出)、`cerr`(错误输出)和`clog`(日志输出)。 - **基本输入方法**: - **`cin >>`**:用于读取基本数据类型(如整数、浮点数、字符)。它会跳过空白字符(空格、制表符、换行)。 ```cpp #include <iostream> using namespace std; int main() { int num; cout << "输入一个整数: "; cin >> num; // 读取整数 cout << "您输入的是: " << num << endl; return 0; } ``` 返回值:`istream&`,可链式调用(如`cin >> a >> b;`)。但需注意,如果输入类型不匹配,会设置错误状态位[^4][^5]。 - **`cin.get()`**:读取单个字符(包括空白字符),常用于处理换行符。 ```cpp char ch; ch = cin.get(); // 读取一个字符 if (ch != EOF) { /* 处理字符 */ } ``` 返回值:`int`(读取的字符或EOF)[^4][^5]。 - **`cin.getline()`**:读取一行文本到字符数组,自动丢弃换行符。 ```cpp char str[100]; cin.getline(str, 100); // 读取最多99个字符 ``` 返回值:`istream&`,可判断是否成功[^4][^5]。 - **基本输出方法**: - **`cout <<`**:输出各种数据类型,支持链式操作。 ```cpp cout << "Hello, " << num << "!" << endl; // endl 表示换行 ``` 返回值:`ostream&`[^3][^4]。 - **格式化输出**:使用`<iomanip>`库设置精度、宽度等。 ```cpp #include <iomanip> double pi = 3.14159; cout << fixed << setprecision(2) << pi; // 输出 3.14 ``` **注意事项**:输入缓冲问题常见,例如`cin >>`后残留换行符可能导致后续`getline`失败。解决方法:在`cin >>`后添加`cin.ignore()`清除缓冲[^5]。 --- #### 2. **文件输入输出(文件I/O)** 文件I/O通过`<fstream>`库实现,用于读写磁盘文件。核心类包括`ifstream`(输入文件流)、`ofstream`(输出文件流)和`fstream`(双向文件流)。 - **文件读写步骤**: 1. 打开文件:指定文件名和模式(如`ios::in`、`ios::out`)。 2. 读写数据:类似标准I/O。 3. 关闭文件:释放资源。 - **示例代码**: ```cpp #include <fstream> using namespace std; int main() { // 写入文件 ofstream outFile("data.txt", ios::out); if (outFile.is_open()) { outFile << "Hello, File I/O!" << endl; outFile.close(); } // 读取文件 ifstream inFile("data.txt", ios::in); string line; if (inFile.is_open()) { while (getline(inFile, line)) { // 逐行读取 cout << line << endl; } inFile.close(); } return 0; } ``` 返回值:`getline`返回`istream&`,可判断文件结束(`eof()`)[^2][^5]。 **常见错误**:文件路径错误或权限不足会导致打开失败。务必检查`is_open()`状态[^2]。 --- #### 3. **字符串输入输出(字符串I/O)** 字符串I/O通过`<sstream>`库实现,用于在内存中处理字符串数据,常用于数据解析或格式化。 - **核心类**:`istringstream`(输入字符串流)、`ostringstream`(输出字符串流)。 - **示例代码**: ```cpp #include <sstream> #include <iostream> using namespace std; int main() { // 字符串到数据 string data = "10 3.14"; istringstream iss(data); int num; double val; iss >> num >> val; // 解析字符串 cout << "整数: " << num << ", 浮点数: " << val << endl; // 数据到字符串 ostringstream oss; oss << "结果: " << num * val; string result = oss.str(); // 获取字符串 cout << result << endl; return 0; } ``` 优势:避免直接I/O开销,适合高性能场景[^2][^4]。 --- #### 4. **其他输入输出方法** - **C风格函数**:兼容C语言的`scanf`/`printf`和`getchar`/`putchar`,需包含`<cstdio>`。 - `scanf("%d", &num)`:格式化输入,返回值是成功读取的项数[^4]。 - `putchar('A')`:输出单个字符,返回值是字符或EOF[^4]。 - **性能优化**:在大量数据读写时,使用`printf`/`scanf`可能比`cin`/`cout`更快,但牺牲类型安全[^1][^4]。 --- #### 5. **最佳实践与常见问题** - **错误处理**:检查流状态(如`cin.fail()`)处理无效输入。 - **性能考虑**:文件I/O时使用缓冲(如`setvbuf`)提升效率[^1]。 - **跨平台问题**:换行符在Windows(`\r\n`)和Linux(`\n`)不同,`getline`自动处理[^5]。 - **资源管理**:始终关闭文件流,避免资源泄漏。 **总结**:C++输入输出操作灵活高效,标准I/O适合交互式程序,文件I/O用于持久化数据,字符串I/O优化内存处理。根据场景选择合适方法[^3][^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值