《C++程序设计基础教程》——刘厚泉,李政伟,二零一三年九月版,学习笔记
文章目录
更多有趣的代码示例,可参考【Programming】
1、流的概念
流就是流动
C++ 中流(Stream)是指信息从外部输入设备(如键盘、磁盘)向计算机内部(即内存)输入和从内存向外部输出设备(如显示器和磁盘)输出的过程,这种输入输出过程被形象地比喻为流
C++ 中的流是一种抽象的数据处理机制,用于统一处理输入(Input)和输出(Output)操作。它通过标准库中的 iostream
库实现,提供类型安全、可扩展的接口,支持从/向多种数据源(如控制台、文件、字符串等)读写数据。
+-----------------+
| ios_base | // 所有流类的基类,定义基础功能(如错误处理、格式化标志)
+-----------------+
|
| 继承
+-----------------+
| ios | // 中间层,扩展 ios_base,提供公共接口
+-----------------+
/ \
/ \
/ \
/ \
+----------------+ +----------------+ +-----------------+
| istream | | ostream | | iostream | // 双向流基类(继承自 istream 和 ostream)
| (输入流基类) | | (输出流基类) | | |
+----------------+ +----------------+ +-----------------+
| | |
| 继承 | 继承 | 继承
| | |
+----------------+ +----------------+ +-----------------+
| ifstream | | ofstream | | fstream | // 文件双向流
| (文件输入流) | | (文件输出流) | | |
+----------------+ +----------------+ +-----------------+
| | |
| 继承 | 继承 | 继承
| | |
+----------------+ +----------------+ +-----------------+
| istringstream | | ostringstream | | stringstream | // 字符串双向流
| (字符串输入流) | | (字符串输出流) | | |
+----------------+ +----------------+ +-----------------+
|
| 实例化
+-----------------+
| 标准流对象 |
| (cin, cout, cerr, clog) |
+-----------------+
ios_base 是所有流类的基类,定义底层操作(如错误码、格式化标志)。
ios 作为中间层,扩展 ios_base 并提供公共接口。
istream(输入流)和 ostream(输出流)分别继承自 ios。
iostream 通过多重继承同时继承 istream 和 ostream,实现双向流。
所有流类均为模板类,默认使用 char 类型(如 basic_istream<char>
)。
流的类型
(1)标准流
cin
:标准输入流(从键盘读取)。cout
:标准输出流(输出到屏幕)。cerr
:标准错误流(无缓冲,直接输出错误信息)。clog
:标准日志流(缓冲输出日志信息)。
默认关联:cin 与键盘关联,cout/cerr/clog 与屏幕关联。
(2)文件流
ifstream
:从文件读取数据。ofstream
:向文件写入数据。fstream
:同时读写文件。
(3)字符串流
istringstream
:从字符串读取数据。ostringstream
:向字符串写入数据。stringstream
:同时读写字符串。
流的状态
- good():流状态正常。
- eof():到达流末尾。
- fail():操作失败(如类型不匹配)。
- bad():流损坏(如文件无法打开)。
- clear():重置流状态。
2、标准输入输出流
流(Stream)的核心作用
- 提供统一的接口(如 operator<< 和 operator>>)让用户以类型安全的方式读写数据,隐藏底层设备差异。
流与缓冲区(Buffer)的关联
- 流本身不直接操作设备,而是通过关联的缓冲区对象(streambuf)完成实际的数据传输。
缓冲区的定义:
- 缓冲区是内存中的一块临时存储区域,用于暂存待传输的数据。
缓冲区的核心作用:
- 减少直接 I/O 操作:批量读写数据,提升性能(如写入文件时,先存到缓冲区,再一次性写入磁盘)。
- 支持格式化操作:如 cout 的 endl 会触发缓冲区刷新,同时插入换行符。
- 提供中间层:允许流对输入/输出进行预处理(如转换大小写、过滤特定字符)。
流通过缓冲区操作数据:
当用户执行 cout << “Hello”; 时:
- 数据 “Hello” 先写入 cout 关联的缓冲区(ostream 的 streambuf 对象)。
- 缓冲区暂存数据,直到满足刷新条件(如缓冲区满、遇到 endl、程序结束等)。
- 缓冲区将数据同步到目标设备(如控制台)。
缓冲区的类型:
- 全缓冲(Full Buffering):缓冲区满或显式刷新时写入(如文件流)。
- 行缓冲(Line Buffering):遇到换行符 \n 时刷新(如 cout 关联的标准输出)。
- 无缓冲(No Buffering):直接写入设备(如 cerr,用于紧急输出)。
总结
- 流是数据通道,提供统一的 I/O 接口。
- 缓冲区是流背后的数据暂存区,通过批量操作优化性能。
- 流通过缓冲区实现数据的高效传输,但两者是协作关系而非等同关系。理解这一机制有助于优化 I/O 性能(如手动控制缓冲区刷新)或扩展流的功能(如自定义缓冲区)。
流(stream)是抽象的数据通道,缓冲区(Buffer)是流实现高效数据传输的关键机制,流通过缓冲区管理数据的流动。
2.1、标准输入流
在 C++ 中的 istream
类库为标准输入流
只有在输入完数据再按回车键后,该行数据才被送入键盘缓冲区,形成输入流,提取运算符 “>>” 才能从中提取数据
eg 13-1 通过测试 cin 的真值,判断流对象是否处于正常状态
#include <iostream>
using namespace std;
int main()
{
float grade;
cout << "enter grade:";
while(cin>>grade) //从 cin 流读取数据
{
if(grade>=85)
cout << grade << "GOOD!" << endl;
if(grade<60)
cout << grade << "FAIL!" << endl;
cout << "enter grade:";
}
cout << "The End!" << endl;
return 0;
}
output
enter grade:67
enter grade:89
89GOOD!
enter grade:56
56FAIL!
enter grade:100
100GOOD!
enter grade:^D
The End!
^D
是文件结束符
除了可以用 cin
输入标准类型的数据外,还可以用 istream
类流对象的一些成员函数,实现字符的输入
eg:
std::getline(std::cin, line); // 读取整行
eg:
while (std::cin.get(ch) && ch != '\n') {
// 读取直到换行符
std::cout << ch; // 逐个输出字符
}
(1)用 get
函数读入一个字符
cin >>
会跳过空白符(如空格、换行符),直接读取有效数据。
cin.get
会读取所有字符,包括空白符。
cout <<
通用输出操作符,可处理多种数据类型(如 int, string 等)。
cout.put
专用于输出单个字符,性能略高(但通常可忽略)。
eg 不带参数的 get
函数
#include <iostream>
using namespace std;
int main()
{
int c;
cout << "enter a sentence:" << endl;
while ((c = cin.get()) != '\n' && c != EOF)
cout.put(c);
return 0;
}
回车键触发 \n
结束,或者 ctrl + z
+ 回车触发 EOF
结束
output
enter a sentence:
I study C++ very hard.
I study C++ very hard.
eg
#include <iostream>
using namespace std;
int main()
{
int c;
cout << "enter a sentence:" << endl;
while ((c = cin.get()) != EOF)
cout.put(c);
return 0;
}
仅遇到 EOF
退出
enter a sentence:
I study C++ very hard.
I study C++ very hard.
^Z
EOF,end of file,文件结束符
cout << EOF << endl;
的值为 -1
eg 带一个参数的 get
函数
调用形式为 cin.get(ch)
,如果读取成功为真,否则为假
#include <iostream>
using namespace std;
int main()
{
char c;
cout << "enter a sentence:" << endl;
while (cin.get(c)) // 读取一个字符给字符变量 c, 如果读取成功, cin.get(c) 为真
cout.put(c) << endl;
cout << "endl" << endl;
return 0;
}
output
enter a sentence:
I study C++ very hard.
I
s
t
u
d
y
C
+
+
v
e
r
y
h
a
r
d
.
^Z
endl
eg 有多个参数的 get 函数
调用形式为
cin.get(字符数组, 字符个数n, 终止字符)
或
cin.get(字符指针, 字符个数n, 终止字符)
其作用是从输入流中读取 n-1 个字符,赋给指定的字符数组(或字符指针指向的数组),如果在读取 n-1 个字符之前遇到指定的终止符,则提前结束读取。读取成功返回真,如果失败返回假。
#include <iostream>
using namespace std;
int main()
{
char ch[20];
cout << "enter a sentence:" << endl;
cin.get(ch, 10, '\n'); // 指定换行符为终止字符
cout << ch << endl;
return 0;
}
out
enter a sentence:
I study C++ very hard
I study C
get 函数总参数可以省略,此时默认为 \n
也即 cin.get(ch, 10, '\n');
等价于 cin.get(ch, 10);
终止符也可以用其它字符,如 cin.get(ch, 10, 'h');
#include <iostream>
using namespace std;
int main()
{
char ch[30];
cout << "enter a sentence:" << endl;
cin.get(ch, 20, 'a'); // 指定换行符为终止字符
cout << ch << endl;
return 0;
}
output
enter a sentence:
I study c++ very hard.
I study c++ very h
(2)用成员函数 getline
函数读入一行字符
getline 函数的作用是从输入流中读取一行字符,其用法与带参数的 get 函数类似
cin.getline(字符数组(或字符指针), 字符个数 n, 终止标志字符)
eg
#include <iostream>
using namespace std;
int main()
{
char ch[20];
cout << "enter a sentence:" << endl;
cin >> ch;
cout << "The string read with cin is:" << ch << endl;
cin.getline(ch, 20, '/'); // 指定换行符为终止字符, '\n' 默认
cout << "The second part is:" << ch << endl;
cin.getline(ch, 20);
cout << "The third part is:" << ch << endl;
return 0;
}
cin 的局限性,只能读取到第一个空格前的字符串,剩余的内容会存放到缓存区
The second part 遇到终止符 '/'
停止
The third part 输入19 个字符满了
output
enter a sentence:
I like C++./I study C++./I am happy.
The string read with cin is:I
The second part is: like C++.
The third part is:I study C++./I am h
eg
#include <iostream>
using namespace std;
int main()
{
char ch[20];
cout << "enter a sentence:" << endl;
cin >> ch;
cout << "The string read with cin is:" << ch << endl;
cin.getline(ch, 20, '/'); // 指定换行符为终止字符, '\n' 不是默认
cout << "The second part is:" << ch << endl;
cin.getline(ch, 20);
cout << "The third part is:" << ch << endl;
return 0;
}
output
enter a sentence:
He likes C++./He studys C++./He is happy.
The string read with cin is:He
The second part is: likes C++.
The third part is:He studys C++./He i
读取字符串推荐做法
用 cin.getline
#include <iostream>
using namespace std;
int main()
{
char buffer[100];
cout << "Enter text (max 99 chars): ";
cin.getline(buffer, 100, '\n'); // 以 '/' 为分隔符
cout << "You entered: " << buffer << endl;
return 0;
}
output
Enter text (max 99 chars): I love you.
You entered: I love you.
或者 getline
#include <iostream>
#include <string>
using namespace std;
int main()
{
string input;
getline(cin, input); // 安全读取整行
cout << input << endl;
return 0;
}
output
I love you.
I love you.
或者 cin.get
#include <iostream>
using namespace std;
int main()
{
char buffer[100];
cout << "Enter text (max 99 chars): ";
cin.get(buffer, 100); // 读取整行,包括空格
cout << "You entered: " << buffer << endl;
return 0;
}
output
Enter text (max 99 chars): I love you.
You entered: I love you.
(3) eof 函数
eof 是 end of file 的缩写,表示 “文件结束”,如果达到文件末尾为 true,否则为 false
window 中文件结束是 Ctrl+z
+ Enter
#include <iostream>
using namespace std;
int main()
{
char num[20];
while (!cin.eof()) // 循环直到输入结束(如按 Ctrl+Z + Enter)
{
cin.getline(num, 19, '\n');
cout << "Read: " << num << endl;
}
return 0;
}
output
I love you!
Read: I love you!
I love C++!
Read: I love C++!
hello wordl!
Read: hello wordl!
^Z
Read:
(4)peek 函数
peek 是观察的意思,作用是观测下一个字符,调用形式为
c=cin.peek()
或
cin.peek()
返回的是指针指向的当前字符,但是只是观测,指针仍停留在当前位置,并不后移。
如果要访问字符是文件结束符,则函数值是 EOF(-1)
核心性质总结
- 非破坏性读取:peek() 仅查看字符,不移动流指针。
- 条件检查:常用于判断输入类型(如数字、分隔符)。
- 预处理输入:可跳过无效字符或提前终止读取。
- 避免冗余读取:与 cin.get() 或 cin.ignore() 配合使用更高效。
eg,检查下一个字符是否为特定字符
#include <iostream>
using namespace std;
int main()
{
char c;
cout << "Enter a character: ";
c = cin.peek(); // 查看下一个字符,不消耗
cout << "Next character is: " << c << endl;
cin.get(); // 实际读取字符
return 0;
}
output
Enter a character: S
Next character is: S
解释:peek() 仅查看字符 ‘A’,未将其从流中移除,后续 cin.get() 仍能读取到它。
eg:跳过所有前导指定字符
#include <iostream>
#include <string>
using namespace std;
int main()
{
string s;
cout << "Enter text with spaces:";
while (cin.peek() == ' ')
{
// 跳过所有前导空格
cin.ignore();
}
getline(cin, s);
cout << "Result:" << s << endl;
return 0;
}
output
Enter text with spaces: I love you!
Result:I love you!
注意是前导,也即输入的第一个非空格才会开始进入 buffer
解释:peek() 检查字符是否为空格,若是则用 ignore() 跳过,直到遇到非空格字符。
eg
#include <iostream>
using namespace std;
int main() {
int num = 0;
cout << "Enter digits (e.g., 123a): ";
while (isdigit(cin.peek())) // 检查下一个字符是否为数字
{
num = num * 10 + (cin.get() - '0');
}
cout << "Parsed number: " << num << endl;
return 0;
}
output
Enter digits (e.g., 123a): 123x
Parsed number: 123
解释:peek() 确保只读取数字字符,遇到非数字时停止。
(5)putback 函数
调用形式为
cin.putback(ch)
作用是将前面 get 或 getline 函数从输入流中读取的 ch 返回到输入流,插入到当前指针位置,以供后面读取
eg 13-5 peek 函数和 putback 函数的用法
#include <iostream>
using namespace std;
int main()
{
char c[20];
int ch;
cout << "please enter a sentence:" << endl;
cin.getline(c, 15,'/');
cout << "The first part is:" << c << endl;
ch = cin.peek();
cout << "The next character(ASCII) is:" << ch << endl;
cin.putback(c[0]);
cin.getline(c, 15,'/');
cout << "The Second part is:" << c << endl;
return 0;
}
output
please enter a sentence:
I am a boy./ am a student./
The first part is:I am a boy.
The next character(ASCII) is:32
The Second part is:I am a student
eg:预读并回退字符
#include <iostream>
using namespace std;
int main()
{
char c;
cout << "Enter a character: ";
c = cin.get(); // 读取字符
cin.putback(c); // 将字符放回
c = cin.get();
cout << "Read again: " << c << endl; // 重新读取
return 0;
}
output
Enter a character: a
Read again: a
eg:跳过非数字字符
#include <iostream>
using namespace std;
int main()
{
char c;
cout << "Enter digits (e.g., a123): ";
c = cin.get(); // 预读第一个字符
if (!isdigit(c)) {
cin.putback(c); // 非数字则放回
cout << "Skipping non-digit: " << c << endl;
cin.ignore(); // 跳过该字符(实际已放回,需处理)
}
else
{
cin.putback(c); // 数字则放回供后续读取
}
int num;
cin >> num;
cout << "Parsed number: " << num << endl;
return 0;
}
output
Enter digits (e.g., a123): x456
Skipping non-digit: x
Parsed number: 456
核心性质总结
- 回退单个字符:putback 允许将最近读取的一个字符放回输入流。
- 预读与条件处理:常用于先检查字符类型(如数字、分隔符),再决定是否回退。
- 多字符回退限制:需循环调用 putback,且只能放回有限次数(依赖实现)。
- 错误处理:若尝试放回未读取的字符,可能导致未定义行为。
(6)ignore 函数
调用形式
cin.ignore(n, 终止字符)
函数作用是跳过输入流中 n 个字符,或在遇到指定的终止字符时提前结束(此时跳过包括终止符在内的若干字符)
ignore() // 默认值为 1,终止字符默认为 EOF
相当于
ignore(1, EOF)
eg 13-6 不用 ignore 函数的情况
#include <iostream>
using namespace std;
int main()
{
char ch[20];
cin.get(ch, 20,'/');
cout << "The first part is:" << ch << endl;
cin.get(ch, 20,'/');
cout << "The Second part is:" << ch << endl;
return 0;
}
output
I like C++./I study C++./I am happy./
The first part is:I like C++.
The Second part is:
可以看到第二次 get 不到信息了,因为遇到了 '/'
可以 ignore 掉
#include <iostream>
using namespace std;
int main()
{
char ch[20];
cin.get(ch, 20,'/');
cout << "The first part is:" << ch << endl;
cin.ignore();
cin.get(ch, 20,'/');
cout << "The Second part is:" << ch << endl;
return 0;
}
output
I like C++./I study C++./I am happy./
The first part is:I like C++.
The Second part is:I study C++.
2.2、标准输出流
ostrem
类定义了输出流对象
(1)cout 流对象
cout 是 console output 的缩写,控制台(终端显示器)的输出
cout 是 ostream 流类的对象,在 io stream 中定义
(2)cerr 流对象
cerr 是标准错误流
作用是向标准错误设备(standard error device)输出有关出错信息。
用法和 cout 差不多,区别是 cout 流通常是传送到显示器输出,也可以被重定向输出到磁盘文件
cerr 流中的信息只能在显示器上及时输出
eg
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
float a,b,c,disc;
cout << "please input a,b,c:";
cin >> a >> b >> c;
if(a==0)
cerr << "a is equal to zero, error!" << endl;
else
{
if((disc=b*b-4*a*c)<0)
cerr << "disc=b*b-4*a*c<0!" << endl;
else
{
cout << "x1=" << (-b + sqrt(disc)) / (2*a) << endl;
cout << "x2=" << (-b - sqrt(disc)) / (2*a) << endl;
}
}
return 0;
}
output
please input a,b,c:0 2 3
a is equal to zero, error!
output
please input a,b,c:5 2 3
disc=b*b-4*a*c<0!
output
please input a,b,c:1 2.5 1.5
x1=-1
x2=-1.5
(3)clog 流对象
clog 也是标准错误流,console log 的缩写
作用与 cerr 相同,都是在终端显示器上显示出错误信息
区别:cerr 是不经过缓冲区,直接向显示器上输出有关信息,而 clog 中的信息存放在缓冲区中,缓冲区满后或遇 endl 时向显示器输出。
缓冲机制:
- cout:默认使用行缓冲,遇到换行符(\n)或缓冲区满时自动刷新。
- cerr:无缓冲(单位缓冲),每个字符直接输出,确保错误信息实时显示。
- clog:通常使用行缓冲,行为与 cout 类似,但默认关联到日志文件。
总结:
- cout:高效缓冲,适合常规输出。
- cerr:无缓冲,确保错误信息实时性。
- clog:缓冲日志输出,适合记录程序行为。
数据按指定的格式输出,有如下两种方法
(1)使用控制符控制输出格式
iomanip 是 “I/O Manipulators” 的缩写,全称为 Input/Output Manipulators(输入输出操作符库)。
setiosflags
- set:表示“设置”(Set)。
- ios:是 “Input/Output Stream”(输入/输出流)的缩写。
- flags:表示“标志”(Flags)。
setprecision(4) 默认4位有效数字,包含整数部分和小数部分,四舍五入
-
fixed 固定小数点后几位
-
scientific 科学计数法
eg 13-8 用控制符控制输出格式
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
int a;
cout << "input a:";
cin >> a;
cout << "dec:" << dec << a << endl; //十进制输出
cout << "dec:" << setbase(10) << a << endl;
cout << "hex:" << hex << a << endl; //十六进制输出
cout << "hex" << setbase(16) << a << endl;
cout << "oct:" << setbase(8) << a << endl; // 八进制输出
cout << "oct:" << oct << a << endl;
char *pt = "China";
cout << setw(10) << pt << endl; // 指定域宽
cout << setfill('*') << setw(10) << pt << endl; // 指定域宽,空白处填 *
cout << setfill('*') << setw(6) << pt << endl; // 指定域宽,空白处填 *
double pi=22.0/7.0; // 计算 pi
cout << "pi=" << setprecision(4) << pi << endl; // 4 位有效数字
cout << "pi=" << setiosflags(ios::fixed) << setprecision(4) << pi << endl;
cout << resetiosflags(ios::fixed); // 注意前面的 ios 设置,不然后面会乱
cout << "pi=" << setiosflags(ios::scientific) << setprecision(4) << pi << endl; // 科学计数法,4位小数
return 0;
}
output
input a:34
dec:34
dec:34
hex:22
hex22
oct:42
oct:42
China
*****China
*China
pi=3.143
pi=3.1429
pi=3.1429e+00
注意显式重置格式标志,resetiosflags
,不重置可能导致输出的结果和预期不一样
(2)用流对象成员函数控制输出格式
cout.setf()
是 C++ 中用于控制输出格式的关键函数,属于 <ios>
头文件。它通过修改流(如 cout)的格式标志(Format Flags)来调整输出行为。
总结
- cout.setf() 是控制输出格式的核心工具。
- 通过组合标志和掩码,可以灵活调整输出行为(进制、对齐、浮点数格式等)。
- 注意标志的粘性和清除操作,避免意外影响后续输出。
eg 13-9 用流控制成员函数输出数据
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
int a = 21;
cout.setf(ios::showbase); // 显示基数符号,十六进制 0x 和 八进制 0
cout << "dec:" << a << endl;
cout.unsetf(ios::dec); // 终止十进制的格式设置
cout.setf(ios::hex); // 设置十六进制的输出状态
cout <<"hex:" << a << endl;
cout.unsetf(ios::hex); // 终止十六进制的格式设置
cout.setf(ios::oct); // 设置八进制的输出状态
cout <<"oct:" << a << endl;
cout.unsetf(ios::oct); // 终止八进制的格式设置
char *pt = "China";
cout.width(10); // 指定域宽
cout << pt << endl;
cout.width(10);
cout.fill('*'); // 填充
cout << pt << endl;
double pi=22.0/7.0;
cout.setf(ios::scientific); // 指定科学计数法
cout <<"pi=";
cout.width(14);
cout << pi << endl;
cout.unsetf(ios::scientific); // 终止科学计数法状态
cout.setf(ios::fixed); // 指定定点形式
cout.width(12);
cout.setf(ios::showpos); // 正数输出 +
cout.setf(ios::internal); // 数符+出现在左侧
cout.precision(6); // 保留小数
cout << pi << endl;
return 0;
}
output
dec:21
hex:0x15
oct:025
China
*****China
pi=**3.142857e+00
+***3.142857
代码解释
注释掉 cout.setf(ios::internal);
的话,输出从 +***3.142857
变成了 ***+3.142857
cout.setf(ios::showbase);
是 C++ 中用于控制输出格式的标志操作,属于 <ios>
头文件。它的作用是强制显示数值的进制前缀(如十六进制 0x、八进制 0),但需配合进制标志(如 ios::hex
、ios::oct
)使用。
默认行为:
- 十进制(
ios::dec
)默认不显示前缀。 - 十六进制(
ios::hex
)默认不显示 0x,启用 showbase 后会显示。 - 八进制(
ios::oct
)默认不显示 0,启用 showbase 后会显示。
清除标志
- 使用
cout.unsetf(ios::showbase);
可关闭前缀显示。
临时生效:
showbase
是“粘性”标志,一旦启用,会持续影响后续输出,直到被显式关闭。
3、文件流
文件流分类
ifstream
:输入文件流(读取文件)。ofstream
:输出文件流(写入文件)。fstream
:同时支持读写操作。
3.1、文件流的概念
cin 和 cout 也是流对象,由 iostream 事先定义,无须用户自己定义
在用磁盘文件时,由于情况各异,无法事先统一定义,所以文件流对象必须由用户自己定义
eg
#include<fstream.h>
ofstream outfile;
3.2、文件流的操作
文件流操作可以分为以下步骤:
-
建立文件流对象
-
打开或建立文件
-
进行文件读写操作
-
关闭文件
成员函数 open 的一般形式为(显示调用)
文件流对象.open(文件名, 模式)
eg
ifstream inFile;
inFile.open("a.txt", iso::in);
还可以利用构造函数直接 open
ifstream inFile("a.txt", iso::in);
区别如下:
模式如下
如果文件需要用两种或多种方式打开,则用 |
来分隔组合在一起
关闭文件
流对象.close()
流类库中的 IO 操作 <<
、>>
、put、get、getline、read 和 write 都可以用于文件的输入输出
详细介绍下 read 和 write 函数常用格式
文件流对象.read(char *buf, int len);
文件流对象.write(const char *buf, int len);
eg 13-10 写入文本文件的例子
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ofstream file("file.txt", ios::out | ios::ate); // 打开文件
if(!file)
{
cout << "不可以打开文件。" << endl;
exit(1);
}
// 写文件
file << "hello c++!\n";
char ch;
while(cin.get(ch))
{
if(ch=='\n')
break;
file.put(ch);
}
file.close();
return 0;
}
输入
234234
output
生成 file.txt
eg 13-11 读文件 file.txt
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ifstream file("file.txt", ios::in); // 打开文件
if(!file)
{
cout << "不可以打开文件。" << endl;
exit(1);
}
// 读文件
char str[100];
file.getline(str, 100, '\n');
cout << str << endl;
char rch;
while(file.get(rch)) // '\n' 终止
{
cout.put(rch);
}
cout << endl;
file.close();
return 0;
}
output
hello c++!
234234
代码中 ios::in
和 ios::out
可省略,因为 ifstream 和 ofstream 中分别默认了相应的模式
eg 13-12,编写一个程序,从一个文本文件 source.txt 中读入若干整数,将升序排序后的结果写入另一个文本文件 target.txt 中
#include <iostream>
#include <fstream>
#include <stdlib.h>
using namespace std;
void sort(int *a, int n)
{
for(int i=0; i<n-1; i++)
{
for(int j=i+1; j<n; j++)
{
if(a[i]>a[j])
{
int tmp;
tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
}
}
}
int main()
{
int a[100], i, n;
fstream in, out;
in.open("source.txt", ios::in); // 打开文件
if(!in)
{
cout << "不可以打开文件 source.txt。" << endl;
exit(1);
}
out.open("target.txt", ios::out); // 打开文件
if(!out)
{
cout << "不可以打开文件 target.txt." << endl;
exit(2);
}
i =0;
while(in>>a[i])
{
i++;
}
sort(a, i);
n = i;
for(i=0; i<n; i++)
out << a[i] << endl;
in.close();
out.close();
return 0;
}
准备好 source.txt,内容如下
output
注意,这个例子中写用的是 <<
eg 13-13 读入文件 data.txt 中的数据,写入二进制文件 data.bin 中
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
fstream in, out;
in.open("data.txt", ios::in); // 打开文件
if(!in)
{
cout << "不可以打开文件 data.txt。" << endl;
exit(1);
}
int a[100], n;
int i=0;
while(in>>a[i])
{
i++;
}
in.close();
n = i;
for(i=0; i<n; i++)
cout << a[i] << endl;
out.open("data.bin", ios::out | ios::binary); // 打开文件
if(!out)
{
cout << "不可以打开文件 data.dat." << endl;
exit(2);
}
out.write((char *)&n, sizeof(n));
out.write((char *)a, n*sizeof(int));
out.close();
return 0;
}
输入的 data.txt 内容如下
output
2
3
10
45
33
8
9
20
45
67
888
3
7
2
2
-2
0
-1
生成的二进制文件 data.bin
该例子中写用的是 文件流对象.write
eg 13-14 编写一个程序对二进制文件进行读写。本程序的功能是,从键盘输入若干学生的信息,写入二进制文件,再从二进制文件中读出学生的信息,输出到屏幕
#include <iostream>
#include <fstream>
#include <string.h>
using namespace std;
struct student // 学生结构体
{
char name[10]; // 姓名
char id[10]; // 学号
int score; // 分数
};
#define LEN sizeof(struct student) // 结构体内存大小
int main()
{
student st;
fstream file;
file.open("stud.dat", ios::out | ios::binary); // 打开文件
if(!file)
{
cout << "不可以打开文件 stud.dat" << endl;
exit(1);
}
cin >> st.name; // 输入姓名
while(strcmp(st.name, "#")!=0) // 输入姓名以 # 结束
{
cin>>st.id>>st.score; // 输入学号和成绩
file.write((char *)&st, LEN); // 写入文件中
cin>>st.name;
}
file.close();
student sts[100]; // 结构体数组,存储多位学生的信息
int i=0, j;
file.open("stud.dat", ios::in | ios::binary); // 打开文件,复用 file 流对象
if(!file)
{
cout << "不可以打开文件 stu.dat" << endl;
exit(2);
}
while(file.read((char *)(sts+i), LEN)) // 一次性读入 LEN 字节的数据,存入内存指定地址
{
i++;
}
file.close();
for(j=0; j<i; j++) // 循环向屏幕输出学生信息
cout << sts[j].name << '\t' << sts[j].id << '\t' << sts[j].score << endl;
return 0;
}
注意,输入 #
表示结束,读和写 open 的时候记得都要用二进制模式 ios::binary
这个例子中写入文件用的是 对象流.write
output
Zhangsan
001 90
Lisi
002 98
Wangwu
003 78
#
Zhangsan 001 90
Lisi 002 98
Wangwu 003 78
3.3、文件类型
C++ 程序中使用的保存数据的文件按存储格式分为两种类型
-
字符文件,ASCII 码文件,文本文件(Text File)
-
字节文件,二进制文件(Binary File)
文本文件特点
- 以字符序列存储,每个字符对应一个字节(ASCII 或 Unicode)。
- 可被文本编辑器直接打开和编辑。
- 换行符在不同系统中可能不同(如 Windows 用 \r\n,Linux 用 \n)。
适用场景
- 存储字符串、数字等可读数据。
- 需要与其他文本工具(如 Excel、记事本)交互的场景。
eg
#include <iostream>
#include <fstream>
using namespace std;
int main() {
// 写入文本文件
ofstream outFile("text.txt");
outFile << "Hello World\n";
outFile << 42 << "\n";
outFile.close();
// 读取文本文件
ifstream inFile("text.txt");
string line;
while (getline(inFile, line))
{
cout << line << endl;
}
inFile.close();
return 0;
}
output
Hello World
42
二进制文件特点
- 直接存储内存中的二进制数据(字节流)。
- 不可被文本编辑器直接阅读,但读写效率更高。
- 换行符和特殊字符(如 \0)按原样存储。
适用场景
- 存储非文本数据(如图片、音频、视频)。
- 高效读写结构体、数组等复杂数据类型。
eg
#include <iostream>
#include <fstream>
using namespace std;
struct Data {
int id;
double value;
};
int main() {
// 写入二进制文件
ofstream outFile("data.bin", ios::binary);
Data d = {
1, 3.14};
outFile.write(reinterpret_cast<char*>(&d), sizeof(Data));
outFile.close();
// 读取二进制文件
ifstream inFile("data.bin", ios::binary);
Data readData;
inFile.read(reinterpret_cast<char*>(&readData), sizeof(Data));
cout << "ID: " << readData.id
<< ", Value: " << readData.value << endl;
inFile.close();
return 0;
}
output
ID: 1, Value: 3.14
对比
注意,当向字符文件输出一个换行符 ‘\n’ 时,则被看作为输出了回车 ‘\r’ 和换行 ‘\n’ 两个字符
相反,当从字符文件中读取回车和换行两个连续字符时,也被看作为一个换行符读取。
选择建议
- 文本文件:需要人类可读性或与其他工具交互时。
- 二进制文件:追求性能或处理非文本数据时(如游戏存档、图像)。
eg 13-15 txt 文件读取和写入
#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;
int main()
{
char buffer[256];
ifstream inFile;
inFile.open("a.txt");
ofstream outFile;
outFile.open("b.txt");
if(!inFile.is_open())
{
cout << "不可以打开文件 a.txt" << endl;
exit(1);
}
if(!outFile.is_open())
{
cout << "不可以打开文件 b.txt" << endl;
exit(2);
}
int a,b;
int i=0;
int data[6][2];
while(!inFile.eof())
{
inFile.getline(buffer, sizeof(buffer), '\n');
sscanf(buffer, "%d %d", &a, &b);
cout << "read:" << a << " " << b << endl;
data[i][0] = a;
data[i][1] = b;
i ++;
}
inFile.close();
for(int k=0; k<i; k++)
{
outFile << data[k][0] << " " << data[k][1] << endl;
cout << "write:" << data[k][0] << " " << data[k][1] << endl;
}
outFile.close();
return 0;
}
输入文件
程序打印
read:2 3
read:33 8
read:45 67
read:7 2
read:0 -1
write:2 3
write:33 8
write:45 67
write:7 2
write:0 -1
输出结果
代码解析:
注意 scanf
和 sscanf
的区别
scanf:
- 从标准输入(通常是键盘)读取格式化的数据。
- 适用于从用户输入或重定向的输入流中获取数据。
sscanf:
- 从字符串中读取格式化的数据。
- 适用于从内存中的字符串解析数据。
改的更规范一些的话如下
#include <iostream>
#include <fstream>
#include <iomanip>
using namespace std;
int main()
{
char buffer[256];
ifstream inFile;
inFile.open("a.txt");
ofstream outFile;
outFile.open("b.txt");
if(!inFile.is_open())
{
cout << "不可以打开文件 a.txt" << endl;
exit(1);
}
if(!outFile.is_open())
{
cout << "不可以打开文件 b.txt" << endl;
exit(2);
}
int a,b;
int i=0;
int data[6][2];
while(!inFile.eof())
{
inFile.getline(buffer, sizeof(buffer), '\n');
if (sscanf(buffer, "%d %d", &a, &b) == 2) // Parse integers from the buffer
{
if (i >= 6) // Prevent array overflow
{
cout << "数据超出存储限制,忽略后续行" << endl;
break;
}
cout << "read:" << a << " " << b << endl;
data[i][0] = a;
data[i][1] = b;
i++;
}
else
{
cout << "无法解析行: " << buffer << endl; // Handle invalid lines
}
}
inFile.close();
for(int k=0; k<i; k++)
{
outFile << data[k][0] << " " << data[k][1] << endl;
cout << "write:" << data[k][0] << " " << data[k][1] << endl;
}
outFile.close();
return 0;
}
修改点
- 增加了对数组越界的检查,防止 i 超过 data 的大小。
- 增加了对无法解析的行的处理,避免程序崩溃。
- 增加了对空行或格式错误行的提示。
eg 13-16 从键盘读入一行字符,把其中的字母依次放在磁盘文件 fat1.dat 中,再把它从磁盘文件中读入程序,其中的小写字母改成大写字母,再存入磁盘文件 fat2.dat
#include<fstream>
#include<iostream>
#include<cmath>
using namespace std;
void read_save()
{
char c[80];
ofstream outfile("fat1.dat", ios::out);
if(!outfile)
{
cerr << "open error!" << endl;
exit(3);
}
cout << "please enter a string:";
cin.getline(c, 80);
cout << "write to fat1.dat:";
for(int i=0; c[i]!=0; i++) // 遇到 /0 为止
{
if((c[i]>=65 && c[i]<=90) || (c[i]>=97)&&c[i]<=122)
{
outfile.put(c[i]);
cout << c[i];
}
}
cout << endl;
outfile.close();
}
void create_data()
{
char ch;
ifstream infile("fat1.dat", ios::in);
if(!infile)
{
cerr << "open error!" << endl;
exit(1);
}
ofstream outfile("fat2.dat", ios::out);
if(!outfile)
{
cerr << "open error!" << endl;
exit(2);
}
cout<<"write to fat2.dat:";
while(infile.get(ch))
{
if(ch<=122&&ch>=97)
ch-=32;
outfile.put(ch);
cout << ch;
}
cout << endl;
infile.close();
outfile.close();
}
int main()
{
read_save();
create_data();
return 0;
}
output
please enter a string:SaedaeiuASDF
write to fat1.dat:SaedaeiuASDF
write to fat2.dat:SAEDAEIUASDF
又比如
please enter a string:S1p2Q3d4aa5
write to fat1.dat:SpQdaa
write to fat2.dat:SPQDAA
代码解析
ASCII 65 到 90 是大写 A 到 Z,97 到 122 是小写字母 a 到 z
【附录】
获取当前工作路径 windows
#include <iostream>
#include <unistd.h> // 包含 getcwd
int main() {
char buffer[PATH_MAX];
if (getcwd(buffer, sizeof(buffer)) != nullptr) {
std::cout << "Current working directory: " << buffer << std::endl;
} else {
std::cerr << "Error getting current directory." << std::endl;
}
return 0;
}
output
Current working directory: C:\window64\bin
更多有趣的代码示例,可参考【Programming】