一、输入输出基础
- 标准输入输出流
cin与标准输入
cin
是C++标准库中用于处理标准输入(通常是键盘输入)的预定义对象,属于istream
类。它通常与提取运算符>>
配合使用,从输入流中读取数据。
基本特性
- 缓冲输入:
cin
会缓冲输入,用户按回车键后数据才会被处理。 - 类型安全:
>>
运算符会根据变量类型自动转换输入数据。 - 空白符处理:默认情况下会跳过前导空白符(空格、制表符、换行符)。
基本用法
int num;
cin >> num; // 从键盘读取一个整数
常见问题
-
输入失败:如果输入类型不匹配(如要求整数但输入字母),
cin
会进入错误状态,后续输入操作会被跳过。if (!(cin >> num)) { cout << "输入错误"; cin.clear(); // 清除错误状态 cin.ignore(100, '\n'); // 忽略错误输入 }
-
混合输入:当混合使用
>>
和getline()
时,需要注意残留的换行符问题。int age; string name; cin >> age; cin.ignore(); // 忽略换行符 getline(cin, name);
-
连续输入:可以链式使用
>>
运算符:int x, y; cin >> x >> y; // 连续读取两个整数
cin
是C++中最基础的输入方式,适合简单的交互场景。对于复杂输入(如包含空格的字符串),通常需要结合getline()
或其他方法处理。
cout与标准输出
cout
是 C++ 标准库中用于标准输出的对象,属于 <iostream>
头文件的一部分。它是 ostream
类的一个实例,通常用于向控制台(终端)输出数据。
基本用法
cout
通常与插入运算符 <<
结合使用,用于输出数据到标准输出设备(通常是屏幕)。例如:
#include <iostream>
using namespace std;
int main() {
cout << "Hello, World!" << endl;
return 0;
}
输出结果:
Hello, World!
特点
- 链式输出:可以连续使用
<<
运算符输出多个数据。cout << "Value: " << 42 << endl;
- 自动类型转换:
cout
能够自动识别基本数据类型(如int
、float
、char
、string
等)并正确输出。 - 缓冲区:
cout
通常是有缓冲的,数据可能不会立即输出到屏幕,直到缓冲区满或遇到endl
、flush
等操作符。
常用控制符
endl
:输出换行并刷新缓冲区。flush
:仅刷新缓冲区,不换行。setw(n)
:设置输出宽度(需包含<iomanip>
)。cout << setw(10) << "Hello" << endl; // 输出占10个字符宽度
注意事项
- 性能:频繁使用
endl
会频繁刷新缓冲区,可能影响性能。在需要高效输出时,可以用'\n'
代替。 - 国际化:
cout
会根据本地化设置输出数据(如数字格式),但默认是C本地化。
cout
是C++中最常用的输出工具,适合大多数控制台输出场景。
cerr与clog错误输出
cerr(标准错误流)
- 定义:
cerr
是C++标准库中预定义的标准错误输出流对象,属于ostream
类实例。 - 特点:
- 无缓冲:输出直接显示(不经过缓冲区),确保错误信息即时可见。
- 默认关联:通常输出到控制台(如终端),与
cout
相同,但独立于标准输出。 - 典型用途:用于输出紧急错误信息(如程序崩溃前的提示)。
- 示例:
#include <iostream> using namespace std; int main() { cerr << "Error: File not found!" << endl; // 立即显示错误 return 0; }
clog(缓冲错误流)
- 定义:
clog
是C++标准库中预定义的缓冲错误输出流对象,同样属于ostream
类实例。 - 特点:
- 有缓冲:输出可能暂存缓冲区,直到缓冲区满或手动刷新(如
endl
)。 - 默认关联:与
cerr
相同,通常输出到控制台。 - 典型用途:记录非紧急日志信息(如程序运行状态)。
- 有缓冲:输出可能暂存缓冲区,直到缓冲区满或手动刷新(如
- 示例:
#include <iostream> using namespace std; int main() { clog << "Warning: Low memory." << endl; // 可能缓冲后输出 return 0; }
关键区别
特性 | cerr | clog |
---|---|---|
缓冲 | 无缓冲(即时输出) | 有缓冲(可能延迟) |
适用场景 | 紧急错误 | 常规日志 |
性能 | 较低(频繁I/O) | 较高(减少I/O次数) |
注意事项
- 两者均需包含
<iostream>
头文件。 - 可通过重定向(如
./a.out 2> error.log
)将错误流导出到文件。
- 输入输出格式控制
操纵符(Manipulators)
操纵符是C++中用于控制输入输出格式的特殊函数或对象,通常与<<
和>>
运算符一起使用。它们可以直接嵌入到输入输出流中,以修改流的格式状态或执行特定操作。
常用操纵符分类
-
无参操纵符(不需要参数):
endl
:插入换行符并刷新输出缓冲区。ends
:插入空字符(\0
),常用于字符串处理。flush
:强制刷新输出缓冲区。
-
带参操纵符(需要参数,需包含
<iomanip>
头文件):setw(int n)
:设置下一个输出字段的宽度为n
个字符。setprecision(int n)
:设置浮点数输出的精度(小数位数或有效数字)。setfill(char c)
:设置填充字符(默认是空格)。setiosflags(ios::fmtflags flags)
:设置指定格式标志(如左对齐、十六进制等)。resetiosflags(ios::fmtflags flags)
:清除指定格式标志。
示例代码
#include <iostream>
#include <iomanip> // 必须包含此头文件以使用带参操纵符
int main() {
double pi = 3.1415926535;
// 使用setw和setfill
std::cout << std::setw(10) << std::setfill('*') << 42 << std::endl; // 输出:********42
// 使用setprecision
std::cout << std::setprecision(4) << pi << std::endl; // 输出:3.142
// 使用endl和flush
std::cout << "Hello" << std::endl; // 换行并刷新缓冲区
std::cout << "World" << std::flush; // 仅刷新缓冲区,不换行
return 0;
}
注意事项
- 头文件依赖:无参操纵符(如
endl
)在<iostream>
中定义,带参操纵符(如setw
)需包含<iomanip>
。 - 作用范围:操纵符的效果通常是临时的,仅影响紧随其后的输出操作。
- 性能影响:频繁使用
endl
(尤其是循环中)可能导致性能下降,因其会强制刷新缓冲区。
自定义格式设置(如setf、unsetf、width等)
在C++中,<iomanip>
和<ios>
头文件提供了一系列用于控制输入输出格式的函数和操纵符。以下是常用的自定义格式设置方法:
1. setf
和 unsetf
setf
:用于设置格式标志,控制输出的显示方式(如进制、对齐等)。unsetf
:用于清除之前设置的格式标志。
常用格式标志:
ios::hex
:以十六进制输出。ios::oct
:以八进制输出。ios::dec
:以十进制输出(默认)。ios::showbase
:显示进制前缀(如0x
表示十六进制)。ios::uppercase
:十六进制字母大写。ios::showpos
:正数显示+
号。ios::fixed
:以固定小数位数输出浮点数。ios::scientific
:以科学计数法输出浮点数。ios::left
/ios::right
:左对齐或右对齐。
示例:
#include <iostream>
using namespace std;
int main() {
int num = 255;
cout.setf(ios::hex | ios::showbase | ios::uppercase); // 设置十六进制、显示前缀、大写字母
cout << num << endl; // 输出: 0XFF
cout.unsetf(ios::hex | ios::uppercase); // 清除十六进制和大写标志
cout << num << endl; // 输出: 255
return 0;
}
2. width
width
:设置下一次输出的字段宽度(仅对下一次输出有效)。- 若输出内容宽度不足,默认用空格填充;若超过设置宽度,按实际内容输出。
示例:
#include <iostream>
using namespace std;
int main() {
cout.width(10); // 设置宽度为10
cout << "Hello" << endl; // 输出: " Hello"(右对齐,5个空格)
cout.width(10); // 重新设置宽度
cout << "HelloWorldLongString" << endl; // 输出: "HelloWorldLongString"(超过宽度,忽略设置)
return 0;
}
3. fill
fill
:设置填充字符(默认是空格),与width
配合使用。
示例:
#include <iostream>
using namespace std;
int main() {
cout.width(10);
cout.fill('*'); // 设置填充字符为'*'
cout << "Hi" << endl; // 输出: "********Hi"
return 0;
}
4. precision
precision
:设置浮点数输出的精度(小数位数或有效数字)。- 与
ios::fixed
或ios::scientific
配合使用时,表示小数位数;否则表示有效数字总数。
示例:
#include <iostream>
using namespace std;
int main() {
double pi = 3.1415926535;
cout.precision(5); // 设置精度为5
cout << pi << endl; // 输出: 3.1416(四舍五入)
cout.setf(ios::fixed); // 固定小数位数
cout.precision(2); // 设置小数点后2位
cout << pi << endl; // 输出: 3.14
return 0;
}
5. 操纵符(简化版)
- 使用
<iomanip>
中的操纵符可以简化格式设置:hex
/oct
/dec
:设置进制。setw(n)
:设置字段宽度(等价于width(n)
)。setfill(c)
:设置填充字符(等价于fill(c)
)。setprecision(n)
:设置精度(等价于precision(n)
)。
示例:
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
cout << hex << showbase << 255 << endl; // 输出: 0xff
cout << setw(10) << setfill('-') << "Hi" << endl; // 输出: "--------Hi"
cout << fixed << setprecision(2) << 3.1415 << endl; // 输出: 3.14
return 0;
}
- 字符串输入输出
getline函数的使用
getline
是C++标准库中用于从输入流读取一行字符串的函数,主要定义在<string>
头文件中。它有两种常见形式:
1. 基本形式(从标准输入读取)
istream& getline(istream& is, string& str);
- 参数:
is
:输入流对象(如cin
)str
:存储读取内容的字符串对象
- 特点:
- 读取直到遇到换行符
\n
- 换行符会被读取但不会存入字符串
- 返回流对象的引用(支持链式调用)
- 读取直到遇到换行符
示例:
#include <iostream>
#include <string>
using namespace std;
int main() {
string line;
getline(cin, line); // 读取整行
cout << "You entered: " << line;
return 0;
}
2. 带分隔符的形式
istream& getline(istream& is, string& str, char delim);
- 额外参数:
delim
:自定义分隔符(默认是\n
)
- 特点:
- 读取直到遇到指定的分隔符
- 分隔符会被读取但不会存入字符串
示例(读取以逗号分隔的内容):
string data;
getline(cin, data, ','); // 读取直到第一个逗号
重要注意事项
-
与
cin >>
的区别:cin >>
会忽略前导空白符并在空白处停止getline
会读取所有字符(包括前导空格)直到遇到分隔符
-
混合使用时的问题:
int num;
string name;
cin >> num; // 读取后留下换行符在缓冲区
getline(cin, name); // 会立即读取到空行
解决方法:
cin >> num;
cin.ignore(); // 清除缓冲区中的换行符
getline(cin, name);
- 文件流同样适用:
ifstream file("data.txt");
string content;
while(getline(file, content)) {
// 逐行处理文件内容
}
字符串流(stringstream)的应用
字符串流是C++标准库中的一个类,定义在<sstream>
头文件中。它允许将字符串当作流来处理,类似于cin
和cout
,但操作的对象是内存中的字符串而不是控制台或文件。
1. 基本功能
- 输入和输出:
stringstream
可以像cin
和cout
一样使用>>
和<<
运算符进行输入和输出操作。 - 类型转换:常用于将字符串与其他数据类型(如整数、浮点数)相互转换。
- 字符串拼接:可以方便地将多个不同类型的数据拼接成一个字符串。
2. 常用操作
- 创建字符串流:
#include <sstream> std::stringstream ss;
- 写入数据:
ss << "Hello, " << 42 << " world!";
- 读取数据:
std::string str; int num; ss >> str >> num; // 从流中提取数据
- 获取字符串内容:
std::string content = ss.str();
- 清空流:
ss.str(""); // 清空内容 ss.clear(); // 清除错误状态
3. 典型应用场景
- 字符串分割:通过
getline
和stringstream
结合,可以方便地分割字符串。std::string input = "apple,orange,banana"; std::stringstream ss(input); std::string item; while (std::getline(ss, item, ',')) { std::cout << item << std::endl; }
- 数据类型转换:将字符串转换为数值类型,或反之。
std::string numStr = "123"; int num; std::stringstream ss(numStr); ss >> num; // 字符串转整数
double pi = 3.14159; std::stringstream ss; ss << pi; std::string piStr = ss.str(); // 浮点数转字符串
4. 注意事项
- 错误处理:如果转换失败(如字符串不是有效的数字),流会进入错误状态,需要调用
clear()
重置状态。 - 性能:频繁创建和销毁
stringstream
对象可能影响性能,可以复用对象以提高效率。
字符串流是一个非常灵活的工具,特别适合需要混合处理字符串和其他数据类型的场景。
二、文件操作基础
- 文件流类概述
ifstream(输入文件流)
ifstream
是 C++ 标准库中的一个类,用于从文件中读取数据。它是 basic_ifstream
模板类的特化版本,通常用于处理字符文件输入。ifstream
继承自 istream
,因此可以使用所有 istream
提供的输入操作。
基本用法
-
包含头文件
使用ifstream
需要包含<fstream>
头文件:#include <fstream>
-
创建对象并打开文件
可以通过构造函数直接打开文件,或先创建对象再调用open()
方法:std::ifstream file("example.txt"); // 直接打开 // 或 std::ifstream file; file.open("example.txt");
-
检查文件是否成功打开
使用is_open()
方法检查文件是否成功打开:if (file.is_open()) { // 文件操作 } else { std::cerr << "无法打开文件!" << std::endl; }
-
读取文件内容
可以使用>>
运算符、getline()
或read()
方法读取文件内容:std::string line; while (std::getline(file, line)) { std::cout << line << std::endl; }
-
关闭文件
文件使用完毕后应调用close()
方法关闭:file.close();
常用成员函数
open(filename, mode)
:打开文件,mode
是打开模式(如std::ios::in
)。is_open()
:检查文件是否成功打开。close()
:关闭文件。eof()
:检查是否到达文件末尾。fail()
:检查是否发生了错误。
示例代码
#include <iostream>
#include <fstream>
#include <string>
int main() {
std::ifstream file("example.txt");
if (file.is_open()) {
std::string line;
while (std::getline(file, line)) {
std::cout << line << std::endl;
}
file.close();
} else {
std::cerr << "无法打开文件!" << std::endl;
}
return 0;
}
注意事项
- 文件路径可以是相对路径或绝对路径。
- 如果文件不存在或无法访问,
is_open()
会返回false
。 - 读取文件时应注意处理可能的错误(如
fail()
或bad()
)。
ofstream(输出文件流)
ofstream
是 C++ 标准库中用于文件输出的类,属于 <fstream>
头文件的一部分。它是 ostream
类的派生类,专门用于将数据写入文件。
基本特性
- 用途:主要用于创建、写入或追加文件内容。
- 继承关系:继承自
ostream
,因此支持所有ostream
的操作(如<<
运算符)。 - 打开模式:通过构造函数或
open()
函数指定文件打开模式(如覆盖、追加等)。
常用成员函数
-
构造函数
ofstream(); // 默认构造,未关联文件 ofstream(const char* filename, ios_base::openmode mode = ios_base::out); // 打开指定文件
filename
:文件路径。mode
:打开模式(默认为ios::out
,覆盖写入)。
-
open()
void open(const char* filename, ios_base::openmode mode = ios_base::out);
- 功能:关联文件并设置打开模式。若文件已关联,需先调用
close()
。
- 功能:关联文件并设置打开模式。若文件已关联,需先调用
-
is_open()
bool is_open() const;
- 功能:检查文件是否成功打开。
-
close()
void close();
- 功能:关闭文件,释放资源。
文件打开模式
模式标志 | 说明 |
---|---|
ios::out | 默认模式,覆盖写入(若文件存在则清空内容)。 |
ios::app | 追加模式,所有写入操作在文件末尾进行。 |
ios::binary | 以二进制模式打开文件(避免字符转换)。 |
ios::trunc | 若文件存在,先清空内容(通常与 ios::out 联合使用)。 |
示例代码
#include <fstream>
using namespace std;
int main() {
ofstream file1("test.txt"); // 默认覆盖写入
if (file1.is_open()) {
file1 << "Hello, World!\n";
file1.close();
}
ofstream file2;
file2.open("data.txt", ios::app); // 追加模式
if (file2) { // 等价于 is_open()
file2 << "Appended line.\n";
file2.close();
}
return 0;
}
注意事项
- 错误检查:始终通过
is_open()
或直接布尔检查确认文件是否成功打开。 - 资源释放:操作完成后调用
close()
,尽管析构函数会自动调用。 - 路径处理:文件路径可以是相对路径或绝对路径,需确保程序有写入权限。
fstream(读写文件流)
fstream
是 C++ 标准库中用于文件输入输出(I/O)操作的类,它继承自 iostream
,并提供了同时读写文件的能力。fstream
结合了 ifstream
(输入文件流)和 ofstream
(输出文件流)的功能,允许对文件进行双向操作。
1. 头文件
使用 fstream
需要包含 <fstream>
头文件:
#include <fstream>
2. 常用成员函数
open()
:打开文件,可以指定打开模式(如读、写、追加等)。close()
:关闭文件。is_open()
:检查文件是否成功打开。<<
和>>
:重载的流插入和提取运算符,用于读写数据。get()
、getline()
、read()
、write()
:用于更灵活的文件读写操作。
3. 文件打开模式
fstream
支持以下打开模式(可通过 |
组合使用):
ios::in
:以读取方式打开文件。ios::out
:以写入方式打开文件(默认会清空文件内容)。ios::app
:以追加方式写入,保留原有内容。ios::binary
:以二进制模式打开文件。ios::trunc
:打开文件时清空内容(默认与ios::out
一起使用)。
4. 基本用法示例
#include <fstream>
#include <iostream>
using namespace std;
int main() {
fstream file;
// 打开文件(读写模式)
file.open("example.txt", ios::in | ios::out | ios::trunc);
if (!file.is_open()) {
cout << "文件打开失败!" << endl;
return 1;
}
// 写入数据
file << "Hello, World!" << endl;
file << 123 << endl;
// 重置文件指针到开头
file.seekg(0, ios::beg);
// 读取数据
string line;
while (getline(file, line)) {
cout << line << endl;
}
// 关闭文件
file.close();
return 0;
}
5. 注意事项
- 文件操作完成后必须调用
close()
关闭文件,否则可能导致资源泄漏。 - 读写操作需注意文件指针的位置,可以使用
seekg()
(输入指针)和seekp()
(输出指针)调整位置。 - 二进制模式下读写时,需使用
read()
和write()
直接操作字节数据。
- 文件的打开与关闭
打开模式(ios::in、ios::out、ios::app等)
在C++中,文件流的打开模式用于指定文件如何被打开和使用。这些模式是ios
类的静态成员,可以通过位或运算符|
组合使用。以下是常见的打开模式及其含义:
-
ios::in
- 以读取方式打开文件。
- 如果文件不存在,打开失败。
- 通常与
ifstream
(输入文件流)一起使用。
-
ios::out
- 以写入方式打开文件。
- 如果文件不存在,会创建新文件;如果文件已存在,默认会清空文件内容。
- 通常与
ofstream
(输出文件流)一起使用。
-
ios::app
(append)- 以追加方式打开文件,所有写入操作都在文件末尾进行。
- 文件内容不会被清空,新数据会追加到文件尾部。
- 即使调用了
seekp()
移动写指针,写入仍会在文件末尾进行。
-
ios::ate
(at end)- 打开文件后,立即将读写指针定位到文件末尾。
- 与
ios::app
不同,允许通过seekp()
或seekg()
移动指针到其他位置进行读写。
-
ios::trunc
(truncate)- 如果文件已存在,清空其内容(截断为0字节)。
- 通常与
ios::out
一起使用(默认行为)。
-
ios::binary
- 以二进制模式打开文件,避免字符转换(如换行符处理)。
- 默认是文本模式(
ios::text
),但C++中通常显式使用ios::binary
处理二进制文件。
常见组合示例
ios::in | ios::out
以读写方式打开文件,文件必须存在。ios::out | ios::trunc
以写入方式打开并清空文件(ofstream
的默认行为)。ios::out | ios::app
以追加方式写入,保留原有内容。ios::in | ios::out | ios::binary
以二进制模式读写文件。
示例代码
#include <fstream>
using namespace std;
int main() {
// 写入文件(清空原有内容)
ofstream outFile("test.txt", ios::out | ios::trunc);
outFile << "Hello, World!" << endl;
outFile.close();
// 追加内容
ofstream appendFile("test.txt", ios::app);
appendFile << "Appended text." << endl;
appendFile.close();
// 读取文件
ifstream inFile("test.txt", ios::in);
string line;
while (getline(inFile, line)) {
cout << line << endl;
}
inFile.close();
return 0;
}
注意事项
- 打开模式是位掩码,可以通过
|
组合多个模式。 - 某些模式是互斥的(如
ios::trunc
和ios::app
不能同时使用)。 - 文件流对象的默认模式:
ifstream
:ios::in
ofstream
:ios::out | ios::trunc
fstream
:ios::in | ios::out
open()函数与close()函数的使用
open()函数
open()
函数用于打开一个文件,以便进行读写操作。它是文件流对象(如ifstream
, ofstream
, fstream
)的成员函数。
基本语法:
void open(const char* filename, ios_base::openmode mode = ios_base::in | ios_base::out);
参数说明:
filename
:要打开的文件的名称(包含路径,如果需要)。mode
:打开文件的模式,可以是以下值的组合(通过|
连接):ios::in
:以读取方式打开文件(ifstream
默认)。ios::out
:以写入方式打开文件(ofstream
默认)。ios::binary
:以二进制模式打开文件。ios::app
:追加模式,所有写入都追加到文件末尾。ios::trunc
:如果文件已存在,先清空文件内容。ios::ate
:打开文件后,定位到文件末尾。
示例:
#include <fstream>
using namespace std;
int main() {
ofstream outFile;
outFile.open("example.txt", ios::out | ios::trunc); // 以写入方式打开,清空原有内容
if (outFile.is_open()) {
outFile << "Hello, World!" << endl;
}
return 0;
}
close()函数
close()
函数用于关闭已打开的文件。它是文件流对象的成员函数,通常在文件操作完成后调用,以释放资源。
基本语法:
void close();
注意事项:
- 调用
close()
后,文件流对象不再与文件关联。 - 如果文件流对象的析构函数被调用(如对象离开作用域),且文件未关闭,析构函数会自动调用
close()
。 - 关闭文件后,可以再次调用
open()
打开其他文件。
示例:
#include <fstream>
using namespace std;
int main() {
ofstream outFile;
outFile.open("example.txt");
if (outFile.is_open()) {
outFile << "Hello, World!" << endl;
outFile.close(); // 显式关闭文件
}
return 0;
}
常见错误处理
- 检查文件是否成功打开:使用
is_open()
成员函数。 - 如果
open()
失败,后续的读写操作会无效。 - 重复调用
open()
前需先调用close()
,否则可能导致未定义行为。
示例:
ifstream inFile;
inFile.open("data.txt");
if (!inFile.is_open()) {
cerr << "Failed to open file!" << endl;
return 1;
}
// 文件操作...
inFile.close();
- 文件读写操作
文本文件读写
插入符<<与提取符>>
在C++中,<<
和>>
运算符分别用于输出和输入操作,通常与iostream
库中的cin
和cout
对象一起使用。在文件操作中,它们也可以与文件流对象(如ifstream
和ofstream
)一起使用。
-
插入符
<<
用于将数据写入文件。例如:#include <fstream> using namespace std; int main() { ofstream outFile("example.txt"); // 打开文件用于写入 outFile << "Hello, World!" << endl; // 使用<<写入数据 outFile.close(); // 关闭文件 return 0; }
这里,
<<
将字符串"Hello, World!"
和换行符endl
写入文件example.txt
。 -
提取符
>>
用于从文件中读取数据。例如:#include <fstream> #include <iostream> using namespace std; int main() { ifstream inFile("example.txt"); // 打开文件用于读取 string word; inFile >> word; // 使用>>读取数据(以空格为分隔符) cout << word << endl; // 输出读取的内容 inFile.close(); return 0; }
>>
会从文件中读取数据,直到遇到空格或换行符为止。
getline函数
getline
函数用于从文件或输入流中读取一行文本(包括空格),直到遇到换行符或指定的分隔符。
-
基本用法
#include <fstream> #include <iostream> using namespace std; int main() { ifstream inFile("example.txt"); string line; getline(inFile, line); // 读取一行到字符串line cout << line << endl; // 输出整行内容 inFile.close(); return 0; }
getline
会读取整行内容(包括空格),直到遇到换行符为止。 -
指定分隔符
getline
还可以指定自定义的分隔符:getline(inFile, line, ','); // 读取直到遇到逗号
此时,
getline
会读取内容直到遇到逗号或文件结束。
注意事项
- 文件打开检查:在使用文件流对象前,应检查文件是否成功打开:
if (!inFile.is_open()) { cerr << "Failed to open file!" << endl; return 1; }
- 逐行读取:
getline
常用于逐行处理文本文件:while (getline(inFile, line)) { cout << line << endl; }
- 混合使用
>>
和getline
:
当>>
和getline
混合使用时,>>
可能会留下换行符在输入缓冲区中,导致getline
读取空行。此时可以用cin.ignore()
清除缓冲区。
通过<<
、>>
和getline
,可以灵活地实现文本文件的读写操作。
二进制文件读写(read()与write()函数)
基本概念
二进制文件读写是指以二进制模式(非文本模式)对文件进行读取和写入操作。与文本模式相比,二进制模式不会对数据进行任何转换(如换行符转换),直接以字节形式处理数据。
相关函数
-
write()
函数- 原型:
ostream& write(const char* buffer, streamsize size)
- 功能:将内存中的一块数据(以字节为单位)写入文件。
- 参数:
buffer
:指向要写入数据的缓冲区的指针(通常需要类型转换)。size
:要写入的字节数。
- 示例:
ofstream outfile("data.bin", ios::binary); int num = 12345; outfile.write(reinterpret_cast<char*>(&num), sizeof(num));
- 原型:
-
read()
函数- 原型:
istream& read(char* buffer, streamsize size)
- 功能:从文件中读取指定字节数的数据到内存。
- 参数:
buffer
:指向存储读取数据的缓冲区的指针。size
:要读取的字节数。
- 示例:
ifstream infile("data.bin", ios::binary); int num; infile.read(reinterpret_cast<char*>(&num), sizeof(num));
- 原型:
注意事项
- 文件打开模式:必须使用
ios::binary
标志打开文件,否则可能因平台差异导致数据错误。 - 指针类型转换:通常需要用
reinterpret_cast<char*>
将其他类型的指针转换为char*
。 - 数据对齐:读写时应确保数据类型和大小一致,否则可能导致数据损坏。
- 错误检查:操作后应检查文件流状态(如
fail()
或eof()
)。
典型用途
- 存储/加载结构体或类对象。
- 保存原始内存数据(如图像、音频等)。
- 跨平台数据交换(避免文本格式的兼容性问题)。
示例代码
#include <fstream>
#include <iostream>
struct Person {
char name[50];
int age;
double height;
};
int main() {
// 写入二进制文件
Person p1 = {"Alice", 30, 1.65};
std::ofstream out("person.bin", std::ios::binary);
out.write(reinterpret_cast<char*>(&p1), sizeof(Person));
out.close();
// 读取二进制文件
Person p2;
std::ifstream in("person.bin", std::ios::binary);
in.read(reinterpret_cast<char*>(&p2), sizeof(Person));
std::cout << p2.name << ", " << p2.age << ", " << p2.height;
return 0;
}
三、文件操作进阶
- 文件定位
流指针(get pointer与put pointer)
在C++的文件输入输出中,流指针用于跟踪文件中的当前位置。流指针分为两种类型:
-
get指针(get pointer)
- 用于输入操作(读取文件)
- 指向文件中下一个将被读取的字节位置
- 可以通过
tellg()
成员函数获取当前位置 - 可以通过
seekg()
成员函数移动指针位置
-
put指针(put pointer)
- 用于输出操作(写入文件)
- 指向文件中下一个将被写入的字节位置
- 可以通过
tellp()
成员函数获取当前位置 - 可以通过
seekp()
成员函数移动指针位置
常用函数
-
tellg()
/tellp()
- 返回当前get/put指针的位置(类型为
streampos
) - 示例:
streampos pos = file.tellg();
- 返回当前get/put指针的位置(类型为
-
seekg()
/seekp()
- 设置get/put指针的位置
- 有两种重载形式:
- 绝对定位:
seekg(pos)
或seekp(pos)
- 相对定位:
seekg(offset, direction)
或seekp(offset, direction)
- 绝对定位:
- 方向参数(
direction
)可以是:ios::beg
:从文件开头开始ios::cur
:从当前位置开始ios::end
:从文件末尾开始
- 示例:
file.seekg(10, ios::beg); // 移动到第10个字节 file.seekp(-5, ios::end); // 移动到倒数第5个字节
注意事项
- 对于同一个文件流,get指针和put指针是独立的
- 以不同模式(如
ios::in
或ios::out
)打开文件时,指针的初始位置可能不同 - 二进制模式下,指针位置以字节为单位;文本模式下,行为可能因平台而异
seekg()与seekp()函数的使用
1. seekg()函数
seekg()
是C++中用于移动输入文件指针(get pointer)的成员函数,属于istream
类。主要用于在输入文件流中重新定位读取位置。
函数原型:
istream& seekg(streampos pos);
istream& seekg(streamoff offset, ios_base::seekdir dir);
参数说明:
- 第一种形式:直接定位到绝对位置
pos
- 第二种形式:
offset
:偏移量(可正可负)dir
:基准位置,可以是:ios::beg
(文件开头)ios::cur
(当前位置)ios::end
(文件末尾)
示例:
ifstream file("data.txt");
file.seekg(10); // 移动到第10字节处
file.seekg(-5, ios::end); // 移动到文件末尾前5字节
2. seekp()函数
seekp()
是C++中用于移动输出文件指针(put pointer)的成员函数,属于ostream
类。主要用于在输出文件流中重新定位写入位置。
函数原型:
ostream& seekp(streampos pos);
ostream& seekp(streamoff offset, ios_base::seekdir dir);
参数说明:
- 参数形式与
seekg()
完全相同
示例:
ofstream file("data.txt");
file.seekp(20); // 移动到第20字节处
file.seekp(10, ios::cur); // 从当前位置向后移动10字节
3. 共同特点
- 都用于随机访问文件
- 位置参数以字节为单位
- 可以与
tellg()
/tellp()
配合使用 - 对文本文件和二进制文件都有效(但在文本文件中行为可能受系统影响)
4. 注意事项
- 使用前应检查文件是否成功打开
- 移动位置不应超出文件范围
- 对于文本文件,某些系统可能不支持负偏移
- 这两个函数通常用于
fstream
对象(同时支持输入输出的文件流)
tellg()与tellp()函数的使用
tellg()函数
tellg()
是C++中用于输入文件流(ifstream
)的成员函数,用于获取当前文件读取指针的位置。它返回一个streampos
类型的值,表示指针距离文件开头的偏移量(以字节为单位)。
语法:
streampos tellg();
示例:
#include <fstream>
#include <iostream>
int main() {
std::ifstream file("example.txt");
if (file.is_open()) {
std::streampos pos = file.tellg();
std::cout << "Current position: " << pos << std::endl;
file.close();
}
return 0;
}
tellp()函数
tellp()
是C++中用于输出文件流(ofstream
)的成员函数,用于获取当前文件写入指针的位置。它同样返回一个streampos
类型的值,表示指针距离文件开头的偏移量(以字节为单位)。
语法:
streampos tellp();
示例:
#include <fstream>
#include <iostream>
int main() {
std::ofstream file("example.txt");
if (file.is_open()) {
std::streampos pos = file.tellp();
std::cout << "Current position: " << pos << std::endl;
file.close();
}
return 0;
}
区别
-
用途不同:
tellg()
用于输入流(读取文件)。tellp()
用于输出流(写入文件)。
-
适用流类型:
tellg()
适用于ifstream
或fstream
(以输入模式打开)。tellp()
适用于ofstream
或fstream
(以输出模式打开)。
注意事项
- 如果文件未打开或操作失败,返回值可能为
-1
(具体实现可能不同)。 - 返回值类型
streampos
通常可以隐式转换为整数类型(如long
),但具体行为取决于实现。
- 文件状态检测
eof()函数
eof()
是C++中用于检测输入流是否到达文件末尾的函数。当读取操作尝试越过文件末尾时,eof()
会返回true
。
- 通常与文件输入操作一起使用
- 在读取失败后才会设置eof标志
- 典型用法:
ifstream file("example.txt");
while(!file.eof()) {
// 读取文件内容
}
fail()函数
fail()
函数用于检测流上的失败操作,当输入/输出操作失败但未到达文件末尾时返回true
。
- 检测格式错误(如尝试读取数字时遇到字母)
- 比
bad()
更轻微的错误 - 示例:
int num;
cin >> num;
if(cin.fail()) {
// 处理输入失败
}
bad()函数
bad()
函数检测流是否发生了严重错误(物理性错误),如磁盘读取错误或内存不足等。
- 表示流已损坏
- 通常无法恢复
- 示例:
if(file.bad()) {
// 处理严重错误
}
good()函数
good()
函数是一个综合状态检查函数,当流处于良好状态时返回true
。
- 等价于
!eof() && !fail() && !bad()
- 通常用于检查流是否准备好进行I/O操作
- 示例:
if(cin.good()) {
// 可以进行输入操作
}
这些函数通常一起使用来全面检查流的状态。例如:
ifstream file("data.txt");
if(!file.good()) {
if(file.eof()) { /* 处理EOF */ }
else if(file.fail()) { /* 处理失败 */ }
else if(file.bad()) { /* 处理严重错误 */ }
}
错误处理机制(异常处理与条件判断)
在C++中,错误处理主要通过两种方式实现:条件判断和异常处理。两者各有适用场景,以下分别介绍:
条件判断
条件判断是最基础的错误处理方式,通过检查函数返回值或变量状态来检测错误。
特点:
- 简单直接,适用于预期内的错误(如文件打开失败、参数无效等)。
- 通常结合
if-else
或switch
语句实现。
示例:
#include <iostream>
#include <fstream>
int main() {
std::ifstream file("example.txt");
if (!file.is_open()) { // 条件判断文件是否打开成功
std::cerr << "Error: Failed to open file." << std::endl;
return 1; // 返回非零值表示错误
}
// 正常处理文件
file.close();
return 0;
}
异常处理
异常处理用于处理程序运行时的意外错误(如内存不足、除零错误等)。
核心关键字:
try
:定义可能抛出异常的代码块。catch
:捕获并处理特定类型的异常。throw
:主动抛出异常对象。
示例:
#include <iostream>
#include <stdexcept>
double divide(int a, int b) {
if (b == 0) {
throw std::runtime_error("Division by zero!"); // 抛出异常
}
return static_cast<double>(a) / b;
}
int main() {
try {
double result = divide(10, 0); // 可能抛出异常的调用
std::cout << "Result: " << result << std::endl;
} catch (const std::runtime_error& e) { // 捕获特定异常
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
对比与选择
方式 | 适用场景 | 优点 | 缺点 |
---|---|---|---|
条件判断 | 预期内的、可恢复的错误 | 性能开销小,逻辑清晰 | 嵌套过多时代码冗长 |
异常处理 | 意外错误或复杂逻辑中的错误传递 | 分离正常逻辑和错误处理,灵活性高 | 性能开销较大 |
注意:
- 异常处理应避免滥用,仅在真正“异常”时使用。
- C++标准库异常类型(如
std::runtime_error
)需包含头文件<stdexcept>
。
- 文件操作综合案例
文本文件的复制
文本文件复制是指将一个文本文件的内容完整地复制到另一个文件中。在C++中,可以通过文件流操作来实现:
-
基本步骤:
- 打开源文件(用于读取)
- 打开目标文件(用于写入)
- 逐行或逐字符读取源文件内容
- 将读取的内容写入目标文件
- 关闭两个文件
-
示例代码:
#include <fstream>
#include <string>
void copyTextFile(const std::string& source, const std::string& destination) {
std::ifstream in(source);
std::ofstream out(destination);
if (in && out) {
std::string line;
while (getline(in, line)) {
out << line << '\n';
}
}
in.close();
out.close();
}
- 注意事项:
- 需要检查文件是否成功打开
- 可以选择逐字符复制或逐行复制
- 复制时应保留原始文件的格式(如换行符)
文本文件的合并
文本文件合并是指将多个文本文件的内容按顺序连接成一个文件。在C++中实现方式与复制类似,但需要处理多个输入文件:
-
基本步骤:
- 打开目标文件(用于写入)
- 依次打开每个源文件
- 将每个源文件的内容追加到目标文件
- 关闭所有文件
-
示例代码:
#include <fstream>
#include <string>
#include <vector>
void mergeTextFiles(const std::vector<std::string>& sources, const std::string& destination) {
std::ofstream out(destination, std::ios::app); // 追加模式
for (const auto& file : sources) {
std::ifstream in(file);
if (in && out) {
out << in.rdbuf(); // 使用缓冲区快速复制
}
in.close();
}
out.close();
}
-
注意事项:
- 可以使用追加模式(std::ios::app)打开目标文件
- 合并时可以添加分隔标记区分不同源文件内容
- 大文件处理时考虑使用缓冲区提高效率
- 注意处理可能存在的同名文件问题
-
合并选项:
- 简单连接:直接拼接文件内容
- 带分隔符:在文件之间添加分隔行
- 选择性合并:根据条件选择部分内容合并
二进制文件的序列化与反序列化
序列化
序列化是将数据结构或对象转换为二进制格式的过程,以便可以将其存储到文件中或在网络上传输。在C++中,序列化通常涉及将对象的成员变量按一定的顺序写入二进制文件。
特点:
- 直接以二进制形式存储数据,不进行任何格式化
- 相比文本文件更节省空间
- 读写速度更快
- 但人类不可直接阅读
基本步骤:
- 打开文件为二进制模式(
ios::binary
) - 使用
write()
方法写入数据 - 通常需要将指针转换为
char*
类型
示例代码:
struct Person {
char name[50];
int age;
double salary;
};
// 序列化函数
void serializeToFile(const Person& p, const string& filename) {
ofstream outfile(filename, ios::binary);
if (outfile) {
outfile.write(reinterpret_cast<const char*>(&p), sizeof(Person));
}
}
反序列化
反序列化是从二进制文件中读取数据并重建原始数据结构或对象的过程。
基本步骤:
- 打开二进制文件进行读取
- 使用
read()
方法读取数据 - 将读取的数据转换为适当的数据类型
示例代码:
Person deserializeFromFile(const string& filename) {
Person p;
ifstream infile(filename, ios::binary);
if (infile) {
infile.read(reinterpret_cast<char*>(&p), sizeof(Person));
}
return p;
}
注意事项
- 平台兼容性:二进制文件在不同平台间可能不兼容(如大小端问题)
- 指针问题:不能直接序列化包含指针的对象,需要特殊处理
- 版本控制:数据结构改变后,旧版本的二进制文件可能无法正确读取
- 安全风险:二进制文件可能包含恶意构造的数据
高级用法
对于复杂对象,可以考虑:
- 使用Boost.Serialization库
- 使用Protocol Buffers等跨语言序列化方案
- 实现自定义的序列化/反序列化方法
自定义序列化示例:
class MyClass {
public:
void serialize(ostream& os) const {
os.write(reinterpret_cast<const char*>(&data1), sizeof(data1));
os.write(reinterpret_cast<const char*>(&data2), sizeof(data2));
}
void deserialize(istream& is) {
is.read(reinterpret_cast<char*>(&data1), sizeof(data1));
is.read(reinterpret_cast<char*>(&data2), sizeof(data2));
}
private:
int data1;
double data2;
};
性能考虑
- 二进制I/O通常比文本I/O快
- 大块数据读写比小块多次读写效率高
- 考虑使用内存映射文件处理大型二进制文件
数据统计与分析(如学生成绩管理系统)
1. 基本概念
数据统计与分析是指通过收集、整理、分析数据,提取有用信息并得出结论的过程。在学生成绩管理系统中,数据统计与分析通常包括对学生成绩的汇总、计算平均分、最高分、最低分、及格率等指标,以便教师或管理者了解学生的学习情况。
2. 常见操作
- 数据输入:通过键盘或文件输入学生的成绩数据。
- 数据存储:使用数组、结构体或文件存储学生的成绩信息。
- 数据计算:计算平均分、总分、排名等统计指标。
- 数据输出:将统计结果输出到屏幕或文件。
3. C++实现示例
以下是一个简单的学生成绩统计与分析程序框架:
#include <iostream>
#include <vector>
#include <algorithm>
#include <fstream>
using namespace std;
struct Student {
string name;
float score;
};
int main() {
vector<Student> students;
ifstream inputFile("scores.txt");
ofstream outputFile("results.txt");
// 从文件读取数据
Student temp;
while (inputFile >> temp.name >> temp.score) {
students.push_back(temp);
}
// 计算平均分
float sum = 0;
for (const auto& s : students) {
sum += s.score;
}
float average = sum / students.size();
// 输出结果
outputFile << "Average Score: " << average << endl;
inputFile.close();
outputFile.close();
return 0;
}
4. 常用统计方法
- 求和:计算所有成绩的总和。
- 平均值:总成绩除以学生人数。
- 最大值/最小值:通过遍历或排序找到最高分和最低分。
- 及格率:统计及格人数占总人数的比例。
5. 注意事项
- 数据验证:确保输入的成绩数据是有效的(如分数在0-100之间)。
- 文件处理:检查文件是否成功打开,避免程序崩溃。
- 内存管理:对于大量数据,使用动态数据结构(如
vector
)避免栈溢出。
通过以上步骤,可以实现一个简单的学生成绩统计与分析系统。