此专栏为移动机器人知识体系下的编程语言中的 C {\rm C} C++从入门到深入的专栏,参考书籍:《深入浅出 C {\rm C} C++》(马晓锐)和《从 C {\rm C} C到 C {\rm C} C++精通面向对象编程》(曾凡锋等)。
12.C++输入/输出
12.1 C++流类
-
计算机的输入和输出是数据传送的过程, C {\rm C} C++将数据的传送过程称为流 ( s t r e a m ) ({\rm stream}) (stream);
-
C {\rm C} C++的 I / O {\rm I/O} I/O流是由若干字节组成的字节序列,称为字节流,字节流中的数据可以是多种形式的,包括:二进制数据、 A S C I I {\rm ASCII} ASCII字符、数字化视频和音频、数字图像信息等;
-
在进行数据传输时,数据会被从一个对象传送到另一个对象中,这些对象包括:内存、输出设备、输入设备,当进行输入操作时,字节流是从输入设备流向内存,当进行输出操作时,字节流是从内存输出到输出设备;
-
内存缓冲区:是系统为数据流开辟的一个内存空间,用于存放流中的数据;如果利用输入/输出操作对流进行传送,系统先将数据存入缓冲区中,当缓冲区满或遇到特定的输出指令时,才将这些数据进行传送;
-
用 c o u t {\rm cout} cout向屏幕输出时,系统将输出的内容先保存在为其开辟的缓冲区内,当此缓冲区满或遇到 e n d l {\rm endl} endl时,才将数据传送到屏幕上显示;利用 c i n {\rm cin} cin进行输入操作时,数据先保存在键盘输入缓冲区,遇到回车时将数据传送到程序输入缓冲区,当程序遇到 < < {\rm <<} <<操作符时,输入缓冲区的数据会被赋予相关的变量;
-
系统提供缓冲区机制的目的:为了减少数据传送频度,以提高 I / O {\rm I/O} I/O效率;
-
C {\rm C} C++中,输入/输出流被定义为一系列的类,这些类称为流类 ( s t r e a m c l a s s ) ({\rm stream\ class}) (stream class),这些类被包含在 I / O {\rm I/O} I/O类库中,其输入输出包括如下方面的内容:
- 标准 I / O {\rm I/O} I/O:对系统指定的标准设备,如:显示器、打印机等的输入和输出;
- 文件 I / O {\rm I/O} I/O:对外存磁盘或外存储设备文件进行输入和输出;
- 内存 I / O {\rm I/O} I/O(串 I / O {\rm I/O} I/O):对内存中的空间进行输入和输出;
-
常用的 I / O {\rm I/O} I/O操作类如下表:
类名 类名 类名 作用 作用 作用 头文件 头文件 头文件 i o s {\rm ios} ios 输入 / 输出抽象基类 输入/输出抽象基类 输入/输出抽象基类 i o s t r e a m {\rm iostream} iostream i s t r e a m {\rm istream} istream 通用输入流和其他输入流基类 通用输入流和其他输入流基类 通用输入流和其他输入流基类 i o s t r e a m {\rm iostream} iostream o s t r e a m {\rm ostream} ostream 通用输出流和其他输出流基类 通用输出流和其他输出流基类 通用输出流和其他输出流基类 i o s t r e a m {\rm iostream} iostream i o s t r e a m {\rm iostream} iostream 通用输入 / 输出流和其他输入 / 输出流基类 通用输入/输出流和其他输入/输出流基类 通用输入/输出流和其他输入/输出流基类 i o s t r e a m {\rm iostream} iostream i f s t r e a m {\rm ifstream} ifstream 输入文件流类 输入文件流类 输入文件流类 f s t r e a m {\rm fstream} fstream o f s t r e a m {\rm ofstream} ofstream 输出文件流类 输出文件流类 输出文件流类 f s t r e a m {\rm fstream} fstream f s t r e a m {\rm fstream} fstream 输入 / 输出文件流类 输入/输出文件流类 输入/输出文件流类 f s t r e a m {\rm fstream} fstream i s t r s t r e a m {\rm istrstream} istrstream 输入字符串流 输入字符串流 输入字符串流 s t r s t r e a m {\rm strstream} strstream o s t r s t r e a m {\rm ostrstream} ostrstream 输出字符串流 输出字符串流 输出字符串流 s t r s t r e a m {\rm strstream} strstream s t r s t r e a m {\rm strstream} strstream 输入 / 输出字符串流 输入/输出字符串流 输入/输出字符串流 s t r s t r e a m {\rm strstream} strstream -
常用的 I / O {\rm I/O} I/O操作流类之间的关系图:
-
几个常用的流类:
- i o s t r e a m {\rm iostream} iostream:基本输入/输出流操作;
- s t r e a m {\rm stream} stream:用于文件的 I / O {\rm I/O} I/O操作;
- s t r s t r e a m {\rm strstream} strstream:用于字符串流输入/输出;
- i o m a n i p {\rm iomanip} iomanip:用于对流中的数据进行格式化输入/输出;
12.2 流对象和格式化输出
-
在 i o s t r e a m {\rm iostream} iostream头文件中,定义了许多相关的类,如: i o s 、 i s t r e a m 、 o s t r e a m 、 i o s t r e a m {\rm ios、istream、ostream、iostream} ios、istream、ostream、iostream等,还定义了 4 4 4种常用的流对象,如下表所示:
对象 对象 对象 含义 含义 含义 对应设备 对应设备 对应设备 对应的类 对应的类 对应的类 c i n {\rm cin} cin 标准输入流 标准输入流 标准输入流 键盘 键盘 键盘 i s t r e a m _ w i t h a s s i g n {\rm istream\_withassign} istream_withassign c o u t {\rm cout} cout 标准输出流 标准输出流 标准输出流 显示器 显示器 显示器 o s t r e a m _ w i t h a s s i g n {\rm ostream\_withassign} ostream_withassign c e r r {\rm cerr} cerr 标准错误流 标准错误流 标准错误流 显示器 显示器 显示器 o s t r e a m _ w i t h a s s i g n {\rm ostream\_withassign} ostream_withassign c l o g {\rm clog} clog 标准输出流 标准输出流 标准输出流 显示器 显示器 显示器 o s t r e a m _ w i t h a s s i g n {\rm ostream\_withassign} ostream_withassign - c i n {\rm cin} cin:标准输入流,是类 i s t r e a m _ w i t h a s s i g n {\rm istream\_withassign} istream_withassign的对象,作用为从系统指定的标准输入设备输入数据到程序变量中;
- c o u t {\rm cout} cout:标准输出流,是类 o s t r e a m _ w i t h a s s i g n {\rm ostream\_withassign} ostream_withassign的对象,作用是将内存数据输出到系统指定的输出设备上;
- c e r r {\rm cerr} cerr:标准错误流,是类 o s t r e a m {\rm ostream} ostream的对象,作用为向系统指定的标准输出设备输出错误信息;
- c l o g {\rm clog} clog:标准输出流,是类 o s t r e a m {\rm ostream} ostream的对象,作用为向系统指定的标准输出设备输出日志信息, c e r r {\rm cerr} cerr没有缓冲区, c l o g {\rm clog} clog具有缓冲区,即 c e r r {\rm cerr} cerr结果会立即被显示到输出设备上, c l o g {\rm clog} clog在缓冲区满或遇到 e n d l {\rm endl} endl时才显示;
-
对输出数据进行格式化称为流格式化输出,如果需要控制流输出的格式,一是使用流格式控制符,二是利用流类的相关成员函数进行控制;
-
流格式控制符在头文件 i o m a n i p {\rm iomanip} iomanip中定义,使用控制符时需要包含头文件 i o m a n i p {\rm iomanip} iomanip,常用的输入/输出流控制符如下表:
控制符 控制符 控制符 控制符作用 控制符作用 控制符作用 d e c {\rm dec} dec 置基数为 10 , 相当 于 ′ % d ′ 置基数为10,相当于'\%{\rm d}' 置基数为10,相当于′%d′ h e x {\rm hex} hex 置基数为 16 , 相当 于 ′ % X ′ 置基数为16,相当于'\%{\rm X}' 置基数为16,相当于′%X′ o c t {\rm oct} oct 置基数为 8 , 相当 于 ′ % o ′ 置基数为8,相当于'\%{\rm o}' 置基数为8,相当于′%o′ s e t b a s e ( n ) {\rm setbase(n)} setbase(n) 置基数为 n 置基数为{\rm n} 置基数为n s e t f i l l ( c ) {\rm setfill(c)} setfill(c) 设置填充字符为 c 设置填充字符为{\rm c} 设置填充字符为c s e t p r e c i s i o n ( n ) {\rm setprecision(n)} setprecision(n) 设置显示小数精度为 n 位 设置显示小数精度为{\rm n}位 设置显示小数精度为n位 s e t w ( n ) {\rm setw(n)} setw(n) 设置域宽为 n 个字符 设置域宽为{\rm n}个字符 设置域宽为n个字符 s e t i o s f l a g s ( i o s : : f i x e d ) {\rm setiosflags(ios::fixed)} setiosflags(ios::fixed) 固定的浮点显示 固定的浮点显示 固定的浮点显示 s e t i o s f l a g s ( i o s : : s c i e n t i f i c ) {\rm setiosflags(ios::scientific)} setiosflags(ios::scientific) 指数表示 指数表示 指数表示 s e t i o s f l a g s ( i o s : : l e f t ) {\rm setiosflags(ios::left)} setiosflags(ios::left) 左对齐 左对齐 左对齐 s e t i o s f l a g s ( i o s : : r i g h t ) {\rm setiosflags(ios::right)} setiosflags(ios::right) 右对齐 右对齐 右对齐 s e t i o s f l a g s ( i o s : : s k i p w s ) {\rm setiosflags(ios::skipws)} setiosflags(ios::skipws) 忽略前导空白 忽略前导空白 忽略前导空白 s e t i o s f l a g s ( i o s : : u p p e r c a s e ) {\rm setiosflags(ios::uppercase)} setiosflags(ios::uppercase) 十六进制大写输出 十六进制大写输出 十六进制大写输出 s e t i o s f l a g s ( i o s : : l o w e r c a s e ) {\rm setiosflags(ios::lowercase)} setiosflags(ios::lowercase) 十六进制小写输出 十六进制小写输出 十六进制小写输出 s e t i o s f l a g s ( i o s : : s h o w p o i n t ) {\rm setiosflags(ios::showpoint)} setiosflags(ios::showpoint) 强制显示小数点 强制显示小数点 强制显示小数点 s e t i o s f l a g s ( i o s : : s h o w p o s ) {\rm setiosflags(ios::showpos)} setiosflags(ios::showpos) 强制显示符号 强制显示符号 强制显示符号 -
控制符实例:用控制符控制数据输出格式 ( e x a m p l e 12 _ 1. c p p ) ({\rm example12\_1.cpp}) (example12_1.cpp)。
c++/** * 作者:罗思维 * 时间:2024/03/31 * 描述:用控制符控制数据输出格式。 */ #include <iostream> #include <iomanip> using namespace std; int main() { int iNumber; cout << "请输入一个数字:"; cin >> iNumber; cout << "iNumber十进制:" << dec << iNumber << endl; cout << "iNumber十六进制:" << hex << iNumber << endl; cout << "iNumber八进制:" << setbase(8) << iNumber << endl; char *pStr = (char*)"Welcome"; // pStr指向字符串"Welcome"; cout << setw(20) << pStr << endl; // 设置域宽为20,输出字符串; cout << setfill('-') << setw(20) << pStr << endl; // 设置域宽为20,输出字符串,空白处用-填充; double dNumber = 5.845201314; cout << setiosflags(ios::scientific) << setprecision(8); cout << "dNumber = " << dNumber << endl; cout << "dNumber = " << setprecision(4) << dNumber << endl; cout << "dNumber = " << setiosflags(ios::fixed) << dNumber << endl; // 小数形式输出; return 0; }
-
流对象 c o u t {\rm cout} cout常用的控制输出格式的成员函数如下:
成员函数 成员函数 成员函数 对应的控制符 对应的控制符 对应的控制符 作用 作用 作用 p r e c i s i o n ( n ) {\rm precision(n)} precision(n) s e t p r e c i s i o n ( n ) {\rm setprecision(n)} setprecision(n) 设置显示小数精度为 n 位 设置显示小数精度为{\rm n}位 设置显示小数精度为n位 w i d t h ( n ) {\rm width(n)} width(n) s e t w ( n ) {\rm setw(n)} setw(n) 设置输出宽度为 n 位 设置输出宽度为{\rm n}位 设置输出宽度为n位 f i l l ( c ) {\rm fill(c)} fill(c) s e t f i l l ( c ) {\rm setfill(c)} setfill(c) 当输出长度不足输出长度时 , 设置填充字符 c 当输出长度不足输出长度时,设置填充字符{\rm c} 当输出长度不足输出长度时,设置填充字符c s e t f ( f l a g ) {\rm setf(flag)} setf(flag) s e t i o s f l a g s ( f l a g ) {\rm setiosflags(flag)} setiosflags(flag) 设置输出格式状况 设置输出格式状况 设置输出格式状况 u n s e t f ( ) {\rm unsetf()} unsetf() r e s e t i o s f l a g s ( ) {\rm resetiosflags()} resetiosflags() 终止已设置的输出格式状态 终止已设置的输出格式状态 终止已设置的输出格式状态 设置格式状态的格式标志:
- 对齐方式:
- i o s : : l e f t {\rm ios::left} ios::left:输出数据左对齐;
- i o s : : r i g h t {\rm ios::right} ios::right:输出数据右对齐;
- i o s : : i n t e r n a l {\rm ios::internal} ios::internal:数值的符号位左对齐,数值右对齐,中间由填充字符填充,如:输出的数值为 − 10 -10 −10,输出长度为 5 5 5,填充字符位为 ∗ * ∗,则: − ∗ ∗ 10 -**10 −∗∗10;
- 数值基数设定:
- i o s : : d e c {\rm ios::dec} ios::dec:设置整数的基数为 10 10 10;
- i o s : : o c t {\rm ios::oct} ios::oct:设置整数的基数为 8 8 8;
- i o s : : h e x {\rm ios::hex} ios::hex:设置整数的基数为 16 16 16;
- 整数格式控制:
- i o s : : s h o w b a s e {\rm ios::showbase} ios::showbase:强制输出整数的基数;
- i o s : : s h o w p o i n t {\rm ios::showpoint} ios::showpoint:强制输出浮点数的小数点和尾数 0 0 0;
- i o s : : u p p e r c a s e {\rm ios::uppercase} ios::uppercase:以科学记数法格式 E {\rm E} E和以十六进制输出字母时以大写表示;
- i o s : : s h o w p o s {\rm ios::showpos} ios::showpos:对整数显示 + + +号;
- 浮点数格式控制:
- i o s : : s c i e n t i f i c {\rm ios::scientific} ios::scientific:浮点数以科学记数法格式输出;
- i o s : : f i x e d {\rm ios::fixed} ios::fixed:浮点数以定点格式(小数形式)输出;
- 流控制:
- i o s : : u n i t b u f {\rm ios::unitbuf} ios::unitbuf:每次输出后刷新所有的流;
- i o s : : s t d i o {\rm ios::stdio} ios::stdio:每次输出后清除 s t d o u t 、 s t d e r r {\rm stdout、stderr} stdout、stderr;
- 对齐方式:
-
流控制成员函数实例:用流控制成员函数控制数据输出格式 ( e x a m p l e 12 _ 2. c p p ) ({\rm example12\_2.cpp}) (example12_2.cpp)。
C++/** * 作者:罗思维 * 时间:2024/03/31 * 描述:用流控制成员函数控制数据输出格式。 */ #include <iostream> using namespace std; int main() { int nNumber = 80; // 进制的设置与取消设置; cout.setf(ios::showbase); cout << "nNumber十进制:" << nNumber << endl; cout.unsetf(ios::dec); cout.setf(ios::hex); cout << "nNumber十六进制:" << nNumber << endl; cout.unsetf(ios::hex); cout.setf(ios::oct); cout << "nNumber八进制:" << nNumber << endl; cout.unsetf(ios::oct); // 域宽与填充的设置与取消设置; char *pStr = (char*)"Welcome"; cout.width(20); cout << pStr << endl; cout.width(20); cout.fill('-'); cout << pStr << endl; // 小数输出形式设置; double dNumber = 5.845201314; cout.setf(ios::scientific); cout << "dNumber = "; cout.width(15); cout << dNumber << endl; cout.unsetf(ios::scientific); cout.setf(ios::fixed); cout.width(14); cout.setf(ios::showpos); cout.setf(ios::internal); cout.precision(5); cout << dNumber << endl; return 0; }
12.3 重载流运算符
-
运算符重载的两种形式:重载为成员函数或重载为友元函数,重载插入运算符和提取运算符时,左边的参数是流,右边的参数是类的对象,因此,只能用友元函数的方式去重载插入运算符和提取运算符;
-
插入运算符重载的语法格式:
c++ostream& operator<<(ostream& os,const T& t) { ...; // 函数体; return os; }
- 第一个参数:是对 o s t r e a m {\rm ostream} ostream对象的引用,函数返回值必须是这个引用对象;
- 第二个参数:为输出的对象, T {\rm T} T是用户自定义的类, t {\rm t} t为该类的对象名;
-
插入运算符重载实例:复数类的输出 ( C o m p l e x 1 ) ({\rm Complex1}) (Complex1)。
-
C C o m p l e x {\rm CComplex} CComplex类定义头文件 ( C C o m p l e x . h ) ({\rm CComplex.h}) (CComplex.h):
c++/** * 作者:罗思维 * 时间:2024/03/31 * 描述:复数类定义头文件。 */ #include <iostream> using namespace std; class CComplex { protected: double real; // 复数的实部; double imag; // 复数的虚部; public: CComplex(double pr = 0.0, double pi = 0.0) { real = pr; imag = pi; }; virtual ~CComplex(); // 重载插入运算符; friend ostream& operator<<(ostream&, const CComplex&); };
-
C C o m p l e x {\rm CComplex} CComplex类实现文件 ( C C o m p l e x . c p p ) ({\rm CComplex.cpp}) (CComplex.cpp):
c++/** * 作者:罗思维 * 时间:2024/03/31 * 描述:复数类实现文件。 */ #include "CComplex.h" CComplex::~CComplex() { } // 重载插入运算符实现; ostream& operator<<(ostream& os, const CComplex& c) { os << "(" << c.real << "," << c.imag << "i" << ")" << endl; return os; }
-
程序主文件 ( m a i n . c p p ) ({\rm main.cpp}) (main.cpp):
c++/** * 作者:罗思维 * 时间:2024/03/31 * 描述:程序主文件。 */ #include "CComplex.h" int main() { CComplex a(1, 2); cout << a; return 0; }
-
-
提取运算符重载的语法格式:
c++istream& operator>>(istream& is,const T& t) { ...; // 函数体; return is; }
- 第一个参数:为输入流 i s t r e a m {\rm istream} istream对象的引用,函数返回值必须是这个引用对象;
- 第二个参数:为提取的对象, T {\rm T} T是用户自定义的类, t {\rm t} t为该类的对象名;
-
提取运算符重载实例:复数类的输入 ( C o m p l e x 2 ) ({\rm Complex2}) (Complex2)。
-
C C o m p l e x {\rm CComplex} CComplex类定义头文件 ( C C o m p l e x . h ) ({\rm CComplex.h}) (CComplex.h):
c++/** * 作者:罗思维 * 时间:2024/03/31 * 描述:复数类定义头文件。 */ #include <iostream> using namespace std; class CComplex { protected: double real; // 复数的实部; double imag; // 复数的虚部; public: CComplex(double pr = 0.0, double pi = 0.0) { real = pr; imag = pi; }; virtual ~CComplex(); // 重载插入运算符; friend ostream& operator<<(ostream&, const CComplex&); // 重载提取运算符; friend istream& operator>>(istream&, CComplex&); };
-
C C o m p l e x {\rm CComplex} CComplex类实现文件 ( C C o m p l e x . c p p ) ({\rm CComplex.cpp}) (CComplex.cpp):
c++/** * 作者:罗思维 * 时间:2024/03/31 * 描述:复数类实现文件。 */ #include "CComplex.h" CComplex::~CComplex() { } // 重载插入运算符实现; ostream& operator<<(ostream& os, const CComplex& c) { os << "(" << c.real << "," << c.imag << "i" << ")" << endl; return os; } // 重载提取运算符实现; istream& operator>>(istream& is, CComplex& c) { cout << "请输入一个复数:"; is >> c.real >> c.imag; return is; }
-
程序主文件 ( m a i n . c p p ) ({\rm main.cpp}) (main.cpp):
c++/** * 作者:罗思维 * 时间:2024/03/31 * 描述:程序主文件。 */ #include "CComplex.h" int main() { CComplex a; cin >> a; cout << a; return 0; }
-
12.4 文件操作
-
C {\rm C} C++中对文件的读/写是通过流来操作的,操作文件的流称为文件流;
-
文件是一组由有限且相关的数据组成的有序集合,"有限"指文件中的数据有最大限度,"相关"指这些数据有关联性;
-
文件通常存储在外部存储设备上,当程序需要使用文件时,才将其中的数据读入内存;
-
文件可以分为:普通文件和设备文件;
- 普通文件:指存储在外部设备上的文件;
- 设备文件:指与计算机主机相关联的各种外部设备,如:显示器、打印机、鼠标、键盘等;
-
按照文件存储时的编码方式分为:文本文件( A S C I I {\rm ASCII} ASCII码文件)和二进制文件。
- 文本文件:此类文件在存储时,以 A S C I I {\rm ASCII} ASCII码作为内容来存储,每一个字符对应文件中的一个字节,这些字节按照顺序被存储在磁盘上;
- 二进制文件:将其内容按照二进制编码的方式来存放的文件;
-
文本文件和二进制文件存储举例:
-
在对文本文件进行 I / O {\rm I/O} I/O操作时,需要进行格式转换,二进制文件不需要进行转换;
-
对文件进行 I / O {\rm I/O} I/O操作的步骤:
- 打开文件:在读写文件前,需要定义一个文件流类的对象,并用该对象打开文件,得到文件的一个句柄;
- 读写文件:打开文件后,文件对象中会有一个指向文件中当前位置的指针,该指针的初始位置取决于打开文件时开发者设置的参数,默认情况为 0 0 0;每次从文件读出数据或向文件写入信息后,文件指针自动向后移动相应的字节,对于文本文件,文件指针移动一个字节,对于二进制文件,由数据类型决定;读文件时,如果文件指针移到文件的末尾,则读出的内容是文件结束符;
- 关闭文件:完成文件的所有操作,需要关闭文件;
-
常用的文件流对象有: i f s t r e a m 、 o f s t r e a m 、 f s t r e a m {\rm ifstream、ofstream、fstream} ifstream、ofstream、fstream;
- i f s t r e a m {\rm ifstream} ifstream:输入文件流对象;
- o f s t r e a m {\rm ofstream} ofstream:输出文件流对象;
- f s t r e a m {\rm fstream} fstream:输入/输出文件流对象;
-
打开文件前,先根据需要进行的操作声明流对象:
c++ifstream file_in; // 建立输入文件流对象; ofstream file_out; // 建立输出文件流对象; fstream file_io; // 建立输入/输出文件流对象;
-
打开文件的两种方式:一是通过文件流的构造函数,二是利用成员函数 o p e n ( ) {\rm open()} open();
c++// 1.利用文件流对象构造函数打开文件; ofstream file_out("C:\\a_out.dat",ios::out|ios::binary); // 以二进制方式打开输出文件; ifstream file_in("C:\\a_in.dat",ios::in|ios::binary); // 以二进制方式打开输入文件; // 2.常用文件流对象构造函数: ifstream::ifstream(const char*,int=ios::in,int=filebuf::openprot); ofstream::ofstream(const char*,int=ios::out,int=filebuf::openprot); fstream::fstream(const char*,int,int=filebuf::openprot); // 3.文件流类的成员函数open()原型: void open(const char *,int); // 4.三个常用的文件流的默认成员函数原型: void ifstream::open(const char*,int=ios::in); void ofstream::open(const char*,int=ios::out); void fstream::open(const char*,int); // 4.1 参数说明: // 参数1:代表文件名的字符串,一般需要完整的路径名,若文件在与exe相同的目录,则可以使用相对路径; // 参数2:文件的打开方式;
文件的打开方式:
常用 常用 常用 作用 作用 作用 i o s : : i n {\rm ios::in} ios::in 打开文件用于数据输入 , 即从文件中读数据 打开文件用于数据输入,即从文件中读数据 打开文件用于数据输入,即从文件中读数据 i o s : : o u t {\rm ios::out} ios::out 打开文件用于数据输出 , 即向文件中写数据 打开文件用于数据输出,即向文件中写数据 打开文件用于数据输出,即向文件中写数据 i o s : : a t e {\rm ios::ate} ios::ate 打开文件后将文件指针置在文件尾部 打开文件后将文件指针置在文件尾部 打开文件后将文件指针置在文件尾部 i o s : : a p p {\rm ios::app} ios::app 打开文件用于追加数据 , 文件指针始终指向文件尾部 打开文件用于追加数据,文件指针始终指向文件尾部 打开文件用于追加数据,文件指针始终指向文件尾部 i o s : : t r u n c {\rm ios::trunc} ios::trunc 当打开文件已存在时 , 则清除其内容 , 即擦除以前所有数据 , 使之成为空文件 当打开文件已存在时,则清除其内容,即擦除以前所有数据,使之成为空文件 当打开文件已存在时,则清除其内容,即擦除以前所有数据,使之成为空文件 i o s : : n o c r e a t e {\rm ios::nocreate} ios::nocreate 如果打开文件不存在 , 则不建立任何文件 , 返回打开失败信息 如果打开文件不存在,则不建立任何文件,返回打开失败信息 如果打开文件不存在,则不建立任何文件,返回打开失败信息 i o s : : n o r e p l a c e {\rm ios::noreplace} ios::noreplace 如果打开文件已存在 , 不进行覆盖 , 当文件存在时 , 返回错误信息 如果打开文件已存在,不进行覆盖,当文件存在时,返回错误信息 如果打开文件已存在,不进行覆盖,当文件存在时,返回错误信息 i o s : : b i n a r y {\rm ios::binary} ios::binary 以二进制方式打开文件 , 在不指定的情况下默认以文本文件方式打开文件 以二进制方式打开文件,在不指定的情况下默认以文本文件方式打开文件 以二进制方式打开文件,在不指定的情况下默认以文本文件方式打开文件 使用符号"|"将常量组合,控制打开文件的多种属性,如下:
c++ios::in|ios::nocreate; // 文件打开用于数据输入,若打开文件不存在,则返回打开失败信息; ios::out|ios::noreplace; // 文件打开用于数据输出,若打开文件存在,则返回打开失败信息; ios::in|ios::out; // 文件打开既用于数据输入,又用于数据输出; ios::app|ios::nocreate; // 文件打开后指针在文件尾,用于追加数据,若打开文件不存在,返回打开失败信息; ios::in|ios::out|ios::binary; // 文件以二进制方式打开,既用于数据输入,又用于输出输出;
-
检查文件是否打开成功,使用 f a i l ( ) {\rm fail()} fail()成员函数判定,原型如下:
c++bool fail(); // 打开失败,返回true;打开成功,返回false;
-
文件操作实例:打开一个文件并向其中写入文本数据,如果文件不存在,则新建一个文件,如果文件存在,则以追加方式写入数据 ( e x a m p l e 12 _ 3. c p p ) ({\rm example12\_3.cpp}) (example12_3.cpp)。
c++/** * 作者:罗思维 * 时间:2024/04/01 * 描述:打开一个文件并向其中写入文本数据。 */ #include <iostream> #include <fstream> using namespace std; int main() { ofstream file_out; file_out.open("./data.txt", ios::out | ios::app); if (file_out.fail()) { cerr << "file data.txt open fail!" << endl; return 1; } file_out.close(); // 关闭文件; return 0; }
-
向文本文件输出数据的两种方法:一是使用插入操作符"<<",二是调用成员函数 ( p u t ) ({\rm put}) (put);
-
文本文件数据输出实例 ( e x a m p l e 12 _ 4. c p p ) ({\rm example12\_4.cpp}) (example12_4.cpp):
c++/** * 作者:罗思维 * 时间:2024/04/01 * 描述:文本文件的输出。 */ #include <iostream> #include <fstream> using namespace std; int main() { ofstream file_out("./data2.txt"); // 利用插入操作符进行数据输出; file_out << "A B C D E F G" << endl; file_out << "H I J K L M N" << endl; file_out << "O P Q R S T" << endl; file_out << "U V W X Y Z" << endl; // 利用put()成员函数进行数据输出。 for (int i = 65; i <= 90; i++) { file_out.put(i); // 输出单个字母; file_out.put(32); // 输出一个空格; switch (i) { case 71: file_out.put(10); // 输出换行; break; case 78: file_out.put(10); break; case 84: file_out.put(10); break; case 90: file_out.put(10); break; default: break; } } file_out.close(); return 0; }
-
将文本文件中的数据读入内存的三种方法:一是利用提取操作符">>",二是调用成员函数 g e t ( ) {\rm get()} get(),三是调用成员函数 g e t l i n e ( ) {\rm getline()} getline();
c++// 1.利用提取操作符">>",原型如下: istream& operator >>(C++标准类型 &); // 2.调用成员函数get(); int get(); // 读取字符; istream& get(char& c); // 读取一个字符并存储到c中; // 3.调用成员函数getline(); istream& getline(char * buffer,int len,char="\n"); // 参数说明: // getline()用于读入文本文件中的一块文本; // 参数1:读入数据转存到的字符串,即将读入的字符存储到buffer; // 参数2:读入字符块的长度; // 参数3:默认为换行,当遇到换行时,则停止本次读取;
-
利用提取操作符读取文本数据 ( e x a m p l e 12 _ 5. c p p ) ({\rm example12\_5.cpp}) (example12_5.cpp):
c++/** * 作者:罗思维 * 时间:2024/04/01 * 描述:利用提取操作符读取文本数据。 */ #include <iostream> #include <fstream> using namespace std; int main() { ifstream file_in("./data3.txt", ios::in); // 打开文件; if (file_in.fail()) { // 判断文件打开是否成功; cerr << "File data3.txt open fail!" << endl; } char cRead; while (file_in >> cRead) { // 读取单个字符; cout << cRead << " "; // 输出单个字符; } file_in.close(); // 关闭文件; return 0; }
-
利用 g e t ( ) {\rm get()} get()成员函数读取文本数据 ( e x a m p l e 12 _ 6. c p p ) ({\rm example12\_6.cpp}) (example12_6.cpp):
c++/** * 作者:罗思维 * 时间:2024/04/01 * 描述:利用get()成员函数读取文本数据。 */ #include <iostream> #include <fstream> using namespace std; int main() { ifstream file_in("./data3.txt", ios::in); // 打开文件; if (file_in.fail()) { // 判断文件是否成功打开; cerr << "File data3.txt open fail!" << endl; } char cRead; while (!file_in.eof()) { // 判断文件内容是否结束,读取单个字符并输出到屏幕上; cRead = file_in.get(); cout << cRead; } file_in.close(); // 关闭文件; return 0; }
-
利用提取操作符读取数据时,忽略空格、换行符,利用 g e t ( ) {\rm get()} get()成员函数读取数据时,会将空格、换行符全部读出;
-
利用 g e t ( c h a r & c ) {\rm get(char\&\ c)} get(char& c)成员函数读取文本数据 ( e x a m p l e 12 _ 7. c p p ) ({\rm example12\_7.cpp}) (example12_7.cpp):
c++/** * 作者:罗思维 * 时间:2024/04/01 * 描述:利用get(char& c)成员函数读取文本数据。 */ #include <iostream> #include <fstream> using namespace std; int main() { ifstream file_in("./data3.txt", ios::in); // 打开文件; if (file_in.fail()) { // 判断文件打开是否成功; cerr << "File data3.txt open fail!" << endl; } char c; while (!file_in.eof()) { // 当没有读到文件尾部时,读取单个字符并输出到屏幕上; file_in.get(c); cout << c; } file_in.close(); // 关闭文件; return 0; }
-
利用 g e t l i n e ( ) {\rm getline()} getline()成员函数读取文本数据 ( e x a m p l e 12 _ 8. c p p ) ({\rm example12\_8.cpp}) (example12_8.cpp):
c++/** * 作者:罗思维 * 时间:2024/04/01 * 描述:利用getline()成员函数读取文本数据。 */ #include <iostream> #include <fstream> #include <string.h> using namespace std; int main() { ifstream file_in("./data3.txt", ios::in); // 打开文件; if (file_in.fail()) { // 判断文件打开是否成功; cerr << "File data3.txt open fail!" << endl; } char* buf = new char[128]; memset(buf, 0, 128); // 清空buf内容; while (!file_in.eof()) { file_in.getline(buf, 128); // 逐行读取数据到buf中; cout << buf << endl; memset(buf, 0x00, 128); // 清空buf内容; } delete[] buf; // 释放动态内存; file_in.close(); // 关闭文件; return 0; }
-
向二进制文件写入数据是利用流类 o s t r e a m {\rm ostream} ostream的 w r i t e ( ) {\rm write()} write()函数实现,原型如下:
c++ostream& write(const char * buffer,int len); // 参数说明: // 参数1:buffer用于存储需要写入文件的内容; // 参数2:len是设定写入数据的长度;
-
向二进制文件写入数据实例 ( e x a m p l e 12 _ 9. c p p ) ({\rm example12\_9.cpp}) (example12_9.cpp):
c++/** * 作者:罗思维 * 时间:2024/04/02 * 描述:向二进制写入数据。 * 数据:1 2 3 4 5 6 7 8 9 * A B C D E F G */ #include <iostream> #include <fstream> using namespace std; int main() { // 打开二进制文件; ofstream file_out("./data.dat", ios::out | ios::binary | ios::trunc); // 如果文件打开失败,则显示错误信息; if (!file_out) { cerr << "File data.dat open fail!" << endl; exit(1); } // 写入1,2,3,4,5,6,7,8,9; for (int i = 49; i <= 57; i++) { file_out.write((char*)&i, sizeof(int)); } // 写入A,B,C,D,E,F,G for (int i = 65; i <= 70; i++) { file_out.write((char*)&i, sizeof(int)); } file_out.close(); return 0; }
-
将二进制文件作为输入文件,则读取其数据时使用流类 i s t r e a m {\rm istream} istream的成员函数 r e a d ( ) {\rm read()} read(),函数原型为:
c++istream& read(char * buffer,int len); // 参数说明: // 参数1:buffer用于存储需要读取的内容; // 参数2:len是设定读取数据的长度;
-
读取二进制文件数据实例,每次只读一个数值 ( e x a m p l e 12 _ 10. c p p ) ({\rm example12\_10.cpp}) (example12_10.cpp):
c++/** * 作者:罗思维 * 时间:2024/04/02 * 描述:从二进制读入数据。 * 数据:1 2 3 4 5 6 7 8 9 * A B C D E F G */ #include <iostream> #include <fstream> using namespace std; int main() { ifstream file_in("./data.dat", ios::in | ios::binary); if (!file_in) { cerr << "File data.dat open fail!" << endl; exit(1); } int n; while (!file_in.eof()) { file_in.read((char*)&n, sizeof(n)); cout << (char)n << " "; } file_in.close(); return 0; }
-
读取二进制文件数据实例,一次性从文件中读出多个字节 ( e x a m p l e 12 _ 11. c p p ) ({\rm example12\_11.cpp}) (example12_11.cpp):
c++/** * 作者:罗思维 * 时间:2024/04/02 * 描述:从二进制读入数据。 * 数据:1 2 3 4 5 6 7 8 9 * A B C D E F */ #include <iostream> #include <fstream> #include <string.h> using namespace std; // 定义函数,输出字符串; void print(char* &str, int len) { for (int i = 0; i < len; i++) { cout << str[i]; } } int main() { ifstream file_in("./data.dat", ios::in | ios::binary); if (!file_in) { cerr << "File data.dat open fail!" << endl; exit(1); } char* buf = new char[11]; // 开辟空间; memset(buf, 0x00, 11); // 清空空间内容; while (!file_in.eof()) { file_in.read(buf, 10); // 一次性读取10个字节; print(buf, 10); memset(buf, 0x00, 10); // 清空空间内容; } delete[] buf; // 释放内存; file_in.close(); // 关闭文件; return 0; }
-
与文件相关联的指针:读取指针和写入指针。
- 读取指针:指向文件当前要读取的数据的位置;
- 写入指针:指向现在正要写入数据的位置;
-
控制文件指针的两个函数:
-
流类 i s t r e a m {\rm istream} istream的成员函数 s e e k g ( ) {\rm seekg()} seekg()用于把读文件指针移动到指定位置,函数原型:
c++istream& seekg(long dis,seek_dir ref=ios::beg);
-
流类 o s t r e a m {\rm ostream} ostream的成员函数 s e e k p ( ) {\rm seekp()} seekp()用于把写文件指针移动到指定位置,函数原型:
c++ostream& seekp(long dis,seek_dir ref=ios::beg);
-
参数 d i s {\rm dis} dis:文件指针需要移动的字节数,当其为正数时,表示向后移,向文件末尾,当其为负数时,表示向前移,向文件开头;
-
参数 s e e k _ d i r {\rm seek\_dir} seek_dir: i o s {\rm ios} ios根基类中定义的枚举类型;
c++enum seek_dir {beg = 0,cur = 1,end =2}; // ios::beg:文件的开始位置; // ios::cur:当前位置; // ios::end:结束位置;
-
-
通过文件指针移动把二进制文件数据显示到屏幕上 ( e x a m p l e 12 _ 12. c p p ) ({\rm example12\_12.cpp}) (example12_12.cpp):
c++/** * 作者:罗思维 * 时间:2024/04/02 * 描述:从二进制读入数据。 * 数据:1 2 3 4 5 6 7 8 9 * A B C D E F */ #include <iostream> #include <fstream> using namespace std; int main() { // 打开二进制文件; ifstream file_in("./data.dat", ios::in | ios::binary); // 判断文件是否打开成功; if (!file_in) { cerr << "File data.dat open fail!" << endl; exit(1); } int n; // 移动指针,9x4个字节; file_in.seekg(9 * 4, ios::beg); while (!file_in.eof()) { file_in.read((char*)&n, sizeof(n)); // 读取二进制文件数据; cout << (char)n << " "; } file_in.close(); // 关闭文件; return 0; }
12.5 实战
项目需求 :编写一个学生信息管理系统,要求将学生数据保存在文件中,提供查询、修改、删除学生信息的功能。
代码实现 ( S t u M a n a g e I n f o S y s t e m ) {\rm (StuManageInfoSystem)} (StuManageInfoSystem):
-
S t u d e n t . h {\rm Student.h} Student.h
c++/** * 作者:罗思维 * 时间:2024/04/03 * 描述:定义存储学生信息结构体。 */ #pragma once #include <iostream> #include <iomanip> #include <fstream> #include <vector> using namespace std; #define NULL 0 int const MAX_NUM = 20; #define LEN sizeof(struct student) // 定义一个学生考试信息的结构体; /* 参数说明: * name: 姓名; * sex: 性别; * id: 准考证号; * score: 分数; * total: 总分数; */ struct student { char name[MAX_NUM]; char sex[MAX_NUM]; long int id; int score[4]; int total; struct student *next; };
-
I n f o r m a t i o n . h {\rm Information.h} Information.h
c++/** * 作者:罗思维 * 时间:2024/04/03 * 描述:学生信息类定义头文件。 */ #pragma once #include "Student.h" static int n = 0; class CInformation { private: student *p1, *p2, *p3, *head, st; public: CInformation(); virtual ~CInformation(); student* create(); // 建立链表函数; void output(student* head); // 输出链表; int count(student* head); // 函数count统计考生总数; student* insert(student* head); // 指针函数insert添加考生信息; student* cancel(student* head, long int num); // 指针函数cancel删除考生信息; student* find(student* head, long int num); // 指针函数find查找考生信息; void sort(student* head); // 考生总分从大到小排列输出; void average(student* head); // 考生成绩平均分; void save(student* head); // 保存信息函数; student* Read(); // 读取信息函数; };
-
I n f o r m a t i o n . c p p {\rm Information.cpp} Information.cpp
c++/** * 作者:罗思维 * 时间:2024/04/03 * 描述:学生信息各种接口函数实现。 */ #include "Information.h" #include "Student.h" #include <string.h> CInformation::CInformation() { } CInformation::~CInformation() { } // 新建学生信息成员函数; student *CInformation::create(void) { char ch[MAX_NUM]; n = 0; p1 = p2 = (student *) malloc(LEN); cout << "------<<请建立学生考试信息表>>------" << endl; cout << "姓名:"; cin >> ch; head = NULL; while (strcmp(ch, "!") != 0) { p1 = (student *)malloc(LEN); strcpy(p1->name, ch); cout << "性别:"; cin >> p1->sex; cout << "准考证号(8位):"; cin >> p1->id; cout << "数学成绩:"; cin >> p1->score[0]; cout << "物理成绩:"; cin >> p1->score[1]; cout << "英语成绩:"; cin >> p1->score[2]; cout << "C语言成绩:"; cin >> p1->score[3]; p1->total = p1->score[0] + p1->score[1] + p1->score[2] + p1->score[3]; if (n == 0) { head = p1; } else { p2->next = p1; } p2 = p1; n++; cout << "姓名:"; cin >> ch; } p2->next = NULL; return (head); } // 输出学生信息成员函数; void CInformation::output(student *head) { if (head == NULL) { cout << "这是一个空表,请先输入考生成绩.\n"; } else { cout << "-------------------------\n"; cout << " *学生考试成绩信息表*\n"; cout << "-------------------------\n"; cout << "准考证号 姓名 性别 数学 物理 英语 C语言 平均分 总分\n"; p1 = head; do { cout << setw(8) << p1->id << setw(9) << p1->name << setw(8) << p1->sex << setw(8) << p1->score[0] << setw(9) << p1->score[1] << setw(9) << p1->score[2] << setw(9) << p1->score[3] << setw(9) << p1->total / 4.0 << setw(9) << p1->total << endl; cout << "-------------------------\n"; p1 = p1->next; } while (p1 != NULL); } } // 获得存储的学生数成员函数; int CInformation::count(struct student *head) { if (head == NULL) { return (0); } else { return (1 + count(head->next)); } } // 插入存储的学生信息成员函数; student *CInformation::insert(student *head) { cout << "\t---------------<<请输入新增学生成绩信息>>---------------\n" << endl; p1 = (student *)malloc(LEN); cout << "准考证号(8位):"; cin >> p1->id; cout << "姓名:"; cin >> p1->name; cout << "性别:"; cin >> p1->sex; cout << "数学成绩:"; cin >> p1->score[0]; cout << "物理成绩:"; cin >> p1->score[1]; cout << "英语成绩:"; cin >> p1->score[2]; cout << "C语言成绩:"; cin >> p1->score[3]; p1->total = p1->score[0] + p1->score[1] + p1->score[2] + p1->score[3]; p2 = head; if (head == NULL) { head = p1; p1->next = NULL; } else { while ((p1->id > p2->id) && (p2->next != NULL)) { p3 = p2; p2 = p2->next; } if (p1->id <= p2->id) { if (head == p2) { p1->next = head; head = p1; } else { p3->next = p1; p1->next = p2; } } else { p2->next = p1; p1->next = NULL; } } n++; cout << "\t---------------<<你输入的学生信息已经成功插入>>---------------\n" << endl; return head; } // 删除某学生信息成员函数; student *CInformation::cancel(student *head, long int num) { if (head == NULL) { return (head); } else { p1 = head; while (num != p1->id && p1->next != NULL) { p2 = p1; p1 = p1->next; } if (num == p1->id) { if (p1 == head) { head = p1->next; } else { p2->next = p1->next; } cout << "删除准考证号为" << num << "的学生\n"; n--; } return (head); } } // 查找某学生信息成员函数; student *CInformation::find(student *head, long int num) { if (head == NULL) { cout << "这是一个空表,请先输入考生成绩.\n"; return (head); } else { p1 = head; while (num != p1->id && p1->next != NULL) { p1 = p1->next; } if (num == p1->id) { cout << setw(8) << p1->id << setw(9) << p1->name << setw(8) << p1->sex << setw(8) << p1->score[0] << setw(9) << p1->score[1] << setw(9) << p1->score[2] << setw(9) << p1->score[3] << setw(9) << p1->total / 4.0 << setw(9) << p1->total << endl; cout << "-------------------------\n"; } else { cout << "没找到准考证号为" << num << "的学生.\n"; } return (head); } } // 信息排序成员函数; void CInformation::sort(student *head) { int i, k, m = 0, j; student *p[MAX_NUM]; if (head != NULL) { m = count(head); cout << "-------------------------\n"; cout << "*学生考试成绩统计表*\n"; cout << "-------------------------\n"; cout << "准考证号 姓名 性别 数学 物理 英语 C语言 平均分 总分 名次\n"; cout << "-------------------------\n"; p1 = head; for (k = 0; k < m; k++) { p[k] = p1; p1 = p1->next; } for (k = 0; k < m - 1; k++) { for (j = k + 1; j < m; j++) { if (p[k]->total < p[j]->total) { p2 = p[k]; p[k] = p[j]; p[j] = p2; } } } for (i = 0; i < m; i++) { cout << setw(8) << p[i]->id << setw(9) << p[i]->name << setw(6) << p[i]->sex << setw(7) << p[i]->score[0] << setw(7) << p[i]->score[1] << setw(7) << p[i]->score[2] << setw(7) << p[i]->score[3] << setw(8) << p[i]->total / 4.0 << setw(7) << p[i]->total << setw(9) << i + 1 << endl; cout << "-------------------------\n"; } } } // 计算学生各科平均成绩成员函数; void CInformation::average(student *head) { int k, m; float arg1 = 0, arg2 = 0, arg3 = 0, arg4 = 0; if (head == NULL) { cout << "这是一个空表,请先输入考生成绩.\n"; } else { m = count(head); p1 = head; for (k = 0; k < m; k++) { arg1 += p1->score[0]; arg2 += p1->score[1]; arg3 += p1->score[2]; arg4 += p1->score[3]; p1 = p1->next; } arg1 /= m; arg2 /= m; arg3 /= m; arg4 /= m; cout << "*全班单科成绩平均分*\n"; cout << "-------------------------\n"; cout << "数学平均分:" << setw(7) << arg1 << "物理平均分:" << setw(7) << arg2 << "英语平均分:" << setw(7) << arg3 << "C语言平均分:" << setw(7) << arg4 << endl; cout << "-------------------------\n"; } } // 信息保存成员函数; void CInformation::save(student *head) { ofstream out("data.txt", ios::out); out << count(head) << endl; while (head != NULL) { out << head->name << "\t" << head->id << "\t" << "\t" << head->sex << "\t" << head->score[0] << "\t" << head->score[1] << "\t" << head->score[2] << "\t" << head->score[3] << "\t" << head->total << endl; head = head->next; } } // 学生信息读取成员函数; student *CInformation::Read() { int i = 0; p1 = p2 = (student *) malloc(LEN); head = NULL; ifstream in("data.txt", ios::out); in >> i; if (i == 0) { cout << "data.txt文件中的数据为空,请先输入数据." << endl; return 0; } else { cout << "-------------------------\n"; for (; i > 0; i--) { p1 = (student *)malloc(LEN); in >> st.name >> st.id >> st.sex >> st.score[0] >> st.score[1] >> st.score[2] >> st.score[3] >> st.total; strcpy(p1->name, st.name); p1->id = st.id; strcpy(p1->sex, st.sex); p1->score[0] = st.score[0]; p1->score[1] = st.score[1]; p1->score[2] = st.score[2]; p1->score[3] = st.score[3]; p1->total = st.total; if (n == 0) { head = p1; } else { p2->next = p1; } p2 = p1; n++; cout << " " << p1->name << "\t" << p1->id << "\t" << "\t" << p1->sex << "\t" << p1->score[0] << "\t" << p1->score[1] << "\t" << p1->score[2] << "\t" << p1->score[3] << "\t" << p1->total << endl; cout << "-------------------------\n"; } cout << "数据已经成功读取完毕." << endl; p2->next = NULL; return (head); } }
-
m a i n . c p p {\rm main.cpp} main.cpp
c++/** * 作者:罗思维 * 时间:2024/04/03 * 描述:程序主文件。 */ #include "Student.h" #include "Information.h" vector<student> stu; int main() { CInformation person; student *head = NULL; int choice; long int i; do { cout << " 1.输入学生成绩" << endl; cout << " 2.显示学生成绩" << endl; cout << " 3.排序统计成绩" << endl; cout << " 4.查找学生成绩" << endl; cout << " 5.增加学生成绩" << endl; cout << " 6.删除学生成绩" << endl; cout << " 7.安全退出系统" << endl; cout << " 请输入您的选择(0-7):"; cin >> choice; switch (choice) { case 0: head = person.Read(); break; case 1: head = person.create(); break; case 2: person.output(head); break; case 3: person.sort(head); person.average(head); cout << "参加考试的学生人数为:" << person.count(head) << "人\n"; break; case 4: cout << "请输入要查找的准考证号(8位):"; cin >> i; person.find(head, i); break; case 5: head = person.insert(head); person.output(head); break; case 6: cout << "请输入要删除的准考证号(8位):"; cin >> i; head = person.cancel(head, i); person.output(head); break; case 7: person.save(head); break; default: cout << "输入有误,请重新输入.\n"; break; } } while (choice != 7); }