以下为个人学习笔记和习题整理
课程:计算机程序设计(C++)- 西安交通大学 @ 中国大学 MOOC
https://www.icourse163.org/course/XJTU-46006
# 课堂笔记
# 流与流库
# 流
流是指从一个位置向另一个位置传输的一连串数据的集合。
在输入输出过程中,会在内存中为每一个数据流开辟一个内存缓冲区,用来存数据。从而匹配不同工作效率的对象。
Error: Failed to launch the browser process! spawn /Applications/Google Chrome.app/Contents/MacOS/Google Chrome ENOENT TROUBLESHOOTING: https://github.com/puppeteer/puppeteer/blob/main/docs/troubleshooting.md
# 输入输出流类库结构
- 定义了基本的格式控制符、状态设定函数等等
- 定义了输入流类、输出流类
- 定义了标准输入、输出流对象。自动包含
<ios>
,<ostream>
等 - 定义文件操作流类
# 标准输入流
从标准输入设备(键盘)流向程序的数据。
一般使用 cin
流对象进行输入。
int a, b; | |
cin >> a >> b; // 从键盘输入 2 个整数 |
>>
的特点:
- 符号重载
- 以空格、回车做分隔符
cin
是 istream
类的对象,除了 >>
符号外,常用函数如下:
函数 | 功能 |
---|---|
read | 无格式输入指定字节数 |
get | 从流中提取字符,包括空格 |
getline | 从流中提取一行字符 |
ignore | 提取并丢弃流中指定字符 |
peek | 返回流中下一个字符,但不从流中删除 |
gcount | 统计最后输入的字符个数 |
seekg | 移动输入流指针 |
tellg | 返回输入流中指定位置的指针值 |
# istream
类的 get
函数
// 无参数,直接返回数据 | |
int get(); | |
// 参数是引用类型的变量,读取一个字符,不跳过空白字符 | |
istream& get( char& rch ); | |
istream& get( char* pch, int nCount, char delim = '\n' ); | |
// 参数 1:char 类型的指针,读取多个字符,放到 pch 中 | |
// 参数 2:读取字符数量上限 | |
// 参数 3:读多个字符至 '\n' 结束,不提取 '\n' |
#include<iostream> | |
using namespace std; | |
int main() | |
{ | |
char c; | |
cout<<"enter a sentence:"<<endl; | |
while(1) | |
{ | |
c=cin.get();// 可以用 cin.get (c) 替换 | |
if(c=="\n") | |
{ | |
break; | |
} | |
else | |
{ | |
cout<<c; | |
} | |
} | |
return 0; | |
} |
#include<iostream> | |
using namespace std; | |
int main() | |
{ | |
char ch[80]; | |
cout<<"enter a sentence:"<<endl; | |
// enter a sentence: | |
// Xi'an Jiaotong University|Shanghai Jiaotong University | |
cin.get(ch,70,'|'); | |
cout<<ch<<endl; | |
// Xi'an Jiaotong University | |
cin.ignore(1); // 扔掉 | | |
cin.get(ch,70); | |
cout<<ch<<endl; | |
// Shanghai Jiaotong University | |
return 0; | |
} |
# istream
类的 getline
函数
istream& getline(char* pch, int nCount, char delim = '\n') | |
// 参数 1:读取多个字符,放到 pch 中 | |
// 参数 2:读取字符数量上限 | |
// 参数 3:读取字符至 delim 结束,终止字符被舍弃 |
#include<iostream> | |
using namespace std; | |
int main() | |
{ | |
char ch[80]; | |
cout<<"enter a sentence:"<<endl; | |
// enter a sentence: | |
// Xi'an Jiaotong University|Shanghai Jiaotong University | |
cin.getline(ch,70,'|'); // 读 69 个字符或遇 '|' 结束 | |
cout<<ch<<endl; | |
// Xi'an Jiaotong University | |
// 不需要 cin.ignore (1); | |
cin.getline(ch,70); // 读 69 个字符或遇 '\n' 结束 | |
cout<<ch<<endl; | |
// Shanghai Jiaotong University | |
return 0; | |
} |
# 比较
cin.get(<字符数组>, <字符个数n>, <终止字符>) | |
cin.getline(<字符指针>, <字符个数n>, <终止字符>) |
相同之处:
- 从输入流提取
n-1
个字符放入数组,因为最后一个位置要放字符数组的结束标准。 - 读取成功,则函数返回值是非 0 值。
- 若遇到文件结束符,则返回值为 0。
- 从输入流提取
不同之处:
当读到终止字符时,cin.getline()
—— 将指针移到终止字符之后。cin.get()
—— 将指针移到终止字符处。
则下次继续读取时的位置就不同。
# 标准输出流
流向标准输出设备(显示器)的数据。
一般使用 cout
流对象进行输出操作。
例如,用流插入运算符 <<
输出数据。
cout
是 ostream
类的对象,除了 <<
符号外,常用函数如下
函数 | 功能 |
---|---|
put | 无格式插入一个字节 |
write | 无格式插入一字节序列 |
flush | 刷新输出流 |
seekp | 移动输出流指针 |
tellp | 返回输出流中指定位置的指针值 |
# 用流对象的成员函数控制输出格式
设置状态标志流成员函数
setf
cout.unsetf(ios::状态标志);
清除状态标志流成员函数
unsetf
cout.unsetf(ios::状态标志);
设置域宽流成员函数
width
设置显示数据占多少个字节宽cout.width(n);
只对下一次流输出有效,输出完成后该函数的作用就消失。
设置实数的精度流成员函数
precision
cout.precision(n);
参数
n
在十进制小数形式输出时,代表有效数字。
在以fixed
形式和scientific
形式输出时,代表小数位数。填充字符流成员函数
fill
cout.fill(ch);
用
ch
去填充无输出内容的位置。
在
cout.setf(ios::状态标志)
和cout.unsetf(ios::状态标志)
中常用的状态标志如下:
状态标志 | 功能 |
---|---|
left | 输出数据在本域宽范围内左对齐 |
right | 输出数据在本域宽范围内右对齐 |
dec | 设置整数的基数为 10 |
oct | 设置整数的基数为 8 |
hex | 设置整数的基数为 16 |
showpoint | 浮点数输出时,强制显示小数点 |
uppercase | 在以科学表示法格式 E 和以十六进制输出字母时用大写表示 |
scientific | 用科学表示法格式显示浮点数 |
fixed | 用定点格式(固定小数位数)显示浮点数 |
在引用这些值之前要加上
ios::
,如果有多项标志,中间则用|
分隔。
#include<iostream> | |
using namespace std; | |
int main() | |
{ | |
// 设左对齐,以一般实数方式显示 | |
cout.setf(ios::left|ios::showpoint); | |
// 设置除小数点外有效数字为 5 | |
cout.precision(5); | |
cout<<123.456789<<endl; | |
// 123.46 | |
// 设置显示区域宽 10 | |
cout.width(10); | |
// 在显示区域空白处用 * 填充 | |
cout.fill('*'); | |
// 清除状态左对齐 | |
cout.unsetf(ios::left); | |
// 设置右对齐 | |
cout.setf(ios::right); | |
cout<<123.456789<<endl; | |
// ****123.46 | |
// 设左对齐,以固定小数位数显示 | |
cout.setf(ios::left|ios::fixed); | |
// 设置实数显示 3 位小数 | |
cout.precision(3); | |
cout<<999.123456<<endl; | |
cout<<99.123456<<endl; | |
// 999.123 | |
// 99.123 | |
// 清除状态左对齐和定点格式 | |
cout.unsetf(ios::left|ios::fixed); | |
// 设置左对齐,以科学计数法显示 | |
cout.setf(ios::left|ios::scientific); | |
// 设置保留 3 位小数 | |
cout.precision(3); | |
cout<<123.45678<<endl; | |
// 1.235e+002 | |
return 0; | |
} |
# 用 C++
流格式控制符控制输出格式
多数 C++
流格式控制符与前面的成员函数有等价对应的关系,两者都可实现同样的功能。C++
流格式控制符一般与符号 <<
联用。
控制符 | 功能 |
---|---|
dec | 设置后面的整数按 10 进制方式显示 |
hex | 设置后面的整数按 16 进制方式显示 |
oct | 设置后面的整数按 8 进制方式显示 |
endl | 输出一个换行符并刷新输出流 |
setfill(c) | 设置填充符 (默认为空格) |
setprecision(n) | 设置实数精度 n, 原理和成员函数 precision 一样 |
setw(n) | 设置域宽 n |
setiosflags(flags) | 设置状态标志,多个用 | 分隔 |
resetiosflags(flags) | 清除状态标志,多个用 | 分隔 |
setiosflags
和resetiosflags
的常用状态标志
状态标志 | 功能 |
---|---|
ios::left | 按域宽左对齐输出 |
ios::right | 按域宽右对齐输出 |
ios::fixed | 固定小数位数输出 |
ios::showpos | 强制设置显示正号 |
ios::uppercase | 科学记数法或 16 进制输出数据时字母大写 |
ios::lowercase | 科学记数法或 16 进制输出数据时字母小写 |
#include <iostream> | |
#include <iomanip> | |
using namespace std; | |
int main() | |
{ | |
int a=128; | |
// 以 10 进制形式输出 | |
cout<<"dec:"<<dec<<a<<endl; | |
// dec:128 | |
// 以 16 进制形式输出 | |
cout<<"hex:"<<hex<<a<<endl; | |
// hex:80 | |
// 以 8 进制形式输出 | |
cout<<"oct:"<<oct<<a<<endl; | |
// oct:200 | |
char pt[]="xi'an"; | |
// 域宽为 10,输出字符串 | |
cout<<setw(10)<<pt<<endl; | |
// xi'an | |
// 指定域宽 10,输出字符串,空白处以 "*" 填充 | |
cout<<setfill('*')<<setw(10)<<pt<<endl; | |
// *****xi'an | |
double B=27.123456789; | |
// 按指数形式输出,8 位小数 | |
cout<<setiosflags(ios::scientific)<<setprecision(8); | |
// 输出 B 值 | |
cout<<"B="<<B<<endl; | |
// B=2.71234568e+001 | |
//4 位小数 | |
cout<<"B="<<setprecision(4)<<B<<endl; | |
// B=2.7123e+001 | |
// 清除格式设定 | |
cout<<resetiosflags(ios::scientific); | |
// 改为小数形式输出,小数点后 6 位 | |
cout<<"B="<<setiosflags(ios::fixed)<<setprecision(6)<<B; | |
// B=27.123457 | |
cout<<endl; | |
return 0; | |
} |
# 文件操作基础
文件流类是用于操作文件的标准 C++
类。
ifstream
从文件读取数据fstream
可读取插入ofstream
向文件插入数据
这三个类包含在头文件 <fstream>
中
- 文件操作的基本步骤
- 定义一个文件流对象
- 打开文件
- 读 / 写文件
- 关闭文件
# 文件分类
文本文件:
是一种由若干行字符构成的计算机文件。其编码可以是 ASCII 码、UNICODE 码、GBK 编码等等。可以用文本编辑器编辑。二进制文件:
除了上述以字符构成的文本文件,其他文件均称为二进制文件。典型的二进制文件有声音、动画、图像、视频等。
C++ 语言对这两种文件都可以进行创建、读写等操作。
# 定义一个文件流对象
ifstream
对象只进行读操作ofstream
对象只进行写操作fstream
对象既可以读文件,也可以写文件
# 打开文件
- 文件流类的成员函数
open
(打开文件)
void open (const char * filename, openmode mode) |
filename
是文件名(如e:\c++\file.txt
),若缺少路径,则默认为当前目录。mode
指文件打开方式,内容如下:
方式 | 功能 |
---|---|
ios::in | 为输入(读)而打开文件 |
ios::out | 为输出(写)而打开文件 |
ios::ate | 打开文件,初始位置在文件尾部 |
ios::app | 所有输出附加在文件末尾 |
ios::trunc | 如果文件已存在则先删除该文件全部数据 |
ios::binary | 二进制方式打开文件 |
每打开一个文件都有一个文件指针,
指针的开始位置由打开方式指定,
每次读写都从文件指针的当前位置开始。
- 还可以用文件流类的构造函数来打开文件,其参数和默认值与
open
函数完全相同。
// 在工程默认目录打开文件 grade.txt,只用于输入 | |
ifstream file1; | |
file1.open("grade.txt", ios::in); | |
//ifstream 就是用于输入的,故第二个参数 ios::in 可以省略 | |
// 打开文本文件 c:\msg.txt,只用于输出 | |
ofstream file2; | |
file2.open("c:\\msg.txt"); // 第二个参数 ios::out 省略 | |
// 以二进制输入方式打开文件 c:\abc.bmp | |
fstream file3; | |
file3.open("c:\\abc.bmp",ios::binary|ios::in); | |
// 二进制方式 | 只用于读入 | |
// 用构造函数来打开二进制文件,用于输出 | |
ofstream file("example.bin", ios::out|ios::binary); |
# 关闭文件
关闭文件操作包括:把缓冲区数据完整地写入文件,添加文件结束标志,切断流对象和外部文件的连接。
当一个流对象的生存期结束,系统也会自动关闭文件;
若流对象的生存期没有结束,用
close()
关闭文件后,该流对象可以重用。
# 文本文件的读写
下面以 ifstream
、 ofstream
为例说明。
由于 ifstream
、 ofstream
类继承自流类 istream
和 ostream
,因此也可以使用常见的 IO 操作。
比如 >>
、 get
、 getline
常用于文本文件输入,而操作 <<
、 put
常用于输出。
- 用符号
<<
和put
函数向文本文件写入一些文字
#include <iostream> | |
#include <fstream> // 必须包含的头文件 | |
using namespace std; | |
int main() | |
{ | |
// 打开文件 | |
ofstream out("file.txt"); | |
// 如果 file.txt 文件存在,则打开,否则自动创建一个 | |
if(!out) { | |
cout<<"打开文件失败!"<<endl; | |
return 1; | |
} | |
// 写文件 | |
out<<"Welcome to "; | |
char ch[]="Xi'an Jiaotong University."; | |
int i=0; | |
while(ch[i]!=0) // 使用 put 函数的示例 | |
{ | |
out.put(ch[i]); | |
i++; | |
} | |
// 真正编程时只要用一条语句 out<<ch 即可 | |
out.close(); // 关闭文件 | |
return 0; | |
} |
- 用符号
>>
和get
函数读取文本文件
#include <iostream> | |
#include <fstream> | |
using namespace std; | |
int main() | |
{ | |
// 打开文件 | |
ifstream in("file.txt"); | |
if(!in) | |
{ | |
cout<<"不可以打开文件"<<endl; | |
return 1; | |
} | |
// 读文件 | |
char ch[80]; | |
in>>ch; // 读取第一个单词 Welcome | |
cout<<ch; | |
in>>ch; // 读取第二个单词 to | |
cout<<ch; | |
while(in) // 剩余部分用 get 函数读出并显示 | |
{ | |
char c=in.get(); | |
if(in) { | |
// 之所以要有 if 判断, | |
// 是为了不输出最后一次读取的非正文数据 | |
cout<<c; | |
} | |
} | |
in.close(); // 关闭文件 | |
return 0; | |
} |
- 如何判断读到达文件尾部
当没有读到文件尾部时,ifstream
对象in
相当于一个true
值,甚至直到读取完所有有效数据后,in
仍然相当于true
。这时再读取一次文件后,in
才变成了相当于false
值的对象。
# 二进制文件的读写
要根据 文件的定义格式 对二进制文件进行读写
比如 BMP 位图文件,是典型的二进制文件。
其文件头部是格式固定的信息,其中
前 2 字节用来记录文件为 BMP 格式,
接下来的 8 个字节用来记录文件长度,
再接下来的 4 字节用来记录 BMP 文件头的长度,等等。
因此,BMP 文件的读取方法是依次读取 2 字节、8 字节、4 字节的数据,再转化为字符或整数。
对二进制文件进行操作时,打开文件要指定方式
ios::binary
从二进制文件输入数据,可调用
istream
流类提供的成员函数,
istream& read(char* buffer, int len) |
- 向二进制文件输出数据可调用
ostream
流类提供的成员函数,
ostream& write(const char* buffer,int len) |
两个函数格式上差不多,
第一个参数是一个字符指针,用于指向输入输出数据所放的内存空间的地址。
第二个参数是一个整数,表示要输入输出的数据的字节数。
#include<iostream> | |
#include<fstream> | |
using namespace std; | |
class Student // 定义类 | |
{ | |
// 建立学生信息类,包含姓名、班级、性别、年龄四个私有属性。 | |
char Name[10]; | |
char Class[10]; | |
char Sex; | |
int Age; | |
public: | |
// 构造函数 | |
Student() { } | |
Student( char *Name, char *Class, char sex, int age) | |
{ | |
strcpy(this->Name,Name); | |
strcpy(this->Class,Class); | |
Sex=sex; | |
Age=age; | |
} | |
// 输出自身信息 | |
void Showme() | |
{ | |
cout<<Name<<'\t'<<Class<<'\t'<<Sex<<'\t'<<Age<<endl; | |
} | |
}; | |
int main() { | |
// 程序运行后,先创建文件并写入信息,而后从文件读出信息 | |
Student stu[3]={ | |
Student("王二小","电气11",'m',27), | |
Student("刘大明","机械01",'f',24), | |
Student("李文化","生物12",'m',39) | |
}; | |
// 打开文件 | |
ofstream file1("file.dat",ios::binary); | |
if(!file1) { | |
cout<<"文件打开失败!"; | |
return 1; | |
} | |
// 写文件 | |
for(int i=0;i<3;i++) { | |
file1.write((char*)&stu[i],sizeof(stu[i])); | |
} | |
file1.close(); // 关闭文件 | |
/////// 以下为读文件并显示出来 ////////// | |
Student stu2; // 建立对象 | |
// 打开文件 | |
ifstream file2("file.dat", ios::binary); | |
if(!file2) { | |
cout<<"文件打开失败!"; | |
return 1; | |
} | |
// 读文件 | |
while(file2) | |
{ | |
file2.read((char*)&stu2,sizeof(stu2)); | |
if(file2) { | |
stu2.Showme(); | |
} | |
} | |
// 关闭文件 | |
file2.close(); | |
return 0; | |
} |
# 二进制文件的顺序读写、随机读写
istream
类和 ostream
类提供成员函数 ,控制读写位置指针的移动,实现文件的随机读写。
文件内容 | |
---|---|
文件刚打开时:读写位置指针 ➡️ | |
读写数据后自动移动 | |
读写位置指针 ➡️ | |
... | |
istream
类操作流读指针的成员函数
istream& istream::seekg( long pos); | |
// 读指针从流的起始位置向后移动由 pos 指定字节 | |
istream& istream::seekg( long off, ios::seek_dir ); | |
// 读指针从流的 seek_dir 位置移动,off 指定字节 |
ios::seek_dir 值 | 功能 |
---|---|
cur | 当前读指针所在的当前位置 |
beg | 文件流的开始位置 (文件头部) |
end | 文件流的结尾处 |
istream input; | |
input.seekg ( -10 , ios::cur ); | |
// 读指针以当前位置为基准,向前移动 10 个字节 | |
input.seekg ( 10 , ios::beg ); | |
// 读指针从流的开始位置,向后移动 10 个字节 | |
input.seekg ( -10 , ios::end ); | |
// 读指针从流的结尾,向前移动 10 个字节 |
ostream
类操作流写指针的成员函数
ostream& ostream::seekp ( long pos ) ; | |
// 写指针从流的起始位置向后移动由参数指定字节 | |
ostream& ostream::seekp ( long off , ios::seek_dir ); | |
// 写指针从流的 seek_dir 位置移动由 off 指定字节 | |
ostream& ostream::tellp () ; | |
// 返回写指针当前所指位置值 |
#include<iostream> | |
#include<fstream> | |
using namespace std; | |
class Student { ... }; | |
int main() | |
{ | |
Student stu; // 建立对象 | |
// 打开文件 | |
ifstream file("file.dat",ios::binary); | |
if(!file) { | |
cout<<"文件打开失败!"; | |
return 1; | |
} | |
// 定位文件指针到文件末尾 | |
file.seekg(0,ios::end); | |
// 得到文件指针位置 (文件大小) | |
int len=file.tellg(); | |
// 读文件 | |
for(int k=len/sizeof(stu)-1;k>=0;k--) | |
{ | |
file.seekg(k*sizeof(stu)); | |
file.read((char*)&stu,sizeof(stu)); | |
stu.Showme(); | |
} | |
file.close(); // 关闭文件 | |
return 0; | |
} |
# 课堂讨论
- 使用
cin>>
,cin.get()
,cin.getline()
读取字符串有什么区别?getline
函数必须读取一行数据吗?
cin>>
不能获取含有空格的字符串;cin.getline()
可以获取含有空格的字符串,但是不能获取换行的字符串;cin.get()
以上两种的功能均可实现,并且可以获取换行的字符串;cin.getline()
的指针停止在中止字符之后,即把中止字符从输入流中拿掉;cin.get()
的指针停止在终止字符位置,即不会把中止字符从输入流中拿掉。getline
函数不是必须读取一行数据,getline
也可以读取单个字符
- 请总结读写文本文件的步骤。
// 包含头文件 | |
#include<fstream> | |
// 创建对象 | |
ifstream in; | |
// 打开文件 | |
in.open("file.txt"); | |
// 读或写 | |
in>>a; | |
// 关闭文件 | |
in.close(); |
读取文本文件时,一定要知道文件的格式吗?
文本文件读写方法可以用于二进制文件读写吗?
不可以。二进制数据解释成文本,有些在 ASCII 表中不存在,有些是不可视的字符,会出现乱码。
# 随堂练习
下列说法错误的是 。
有一段程序如下:
char str[200];
cin.get( str, 100, '*' );
利用上面代码从标准输入流获取一段文本,下列说法错误的是:
为了舍去流中的一些字符,应该使用的输入流的成员函数是 。
用语句
cin.getline(ch,70,'*');
从标准输入流获取一段字符,遇到*
时,停止读入文本,同时*
被从流中取出丢掉。设置输出格式时,利用 setf 成员函数不能设置的显示属性是 。
使用流操纵符(流格式控制符)控制输出格式,是将控制符号作为数据的一部分插入到输出流中。
只用于读文件的流类是 。
不论
ifstream
或ofstream
对象,打开文件都可以使用构造函数或 open 函数,这两种方式所使用的函数的参数可以完全一致。假定
in
为ifstream
类的对象,用in
打开一个文件后,下面的代码用于读取文件内容:while(in) // 读取文件内容
{
char c=in.get();
if(in) {
cout<<c;
}
}
请问上面程序段划线的语句中使用
if
判别语句的原因是:在标准输入流中常使用符号
>>
、get
函数、getline
函数输入数据,这些方式也可用于从文件流向应用程序输入数据,且使用方式不变。在标准输出流中常使用符号
<<
、put
函数向屏幕输出数据,这些方式也可用于向文件流写入数据,且使用方式不变。不论何种方式打开一个二进制文件,在文件打开方式(即第二个参数)中必须有 。
二进制文件使用的读写成员函数是 。