以下为个人学习笔记和习题整理
课程:计算机程序设计(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

# 输入输出流类库结构

输入输出流类库结构

  1. 定义了基本的格式控制符、状态设定函数等等
  2. 定义了输入流类、输出流类
  3. 定义了标准输入、输出流对象。自动包含 <ios><ostream>
  4. 定义文件操作流类

# 标准输入流

从标准输入设备(键盘)流向程序的数据。
一般使用 cin 流对象进行输入。

例子
int a, b;
cin >> a >> b; // 从键盘输入 2 个整数

>> 的特点:

  1. 符号重载
  2. 以空格、回车做分隔符

cinistream 类的对象,除了 >> 符号外,常用函数如下:

函数功能
read无格式输入指定字节数
get从流中提取字符,包括空格
getline从流中提取一行字符
ignore提取并丢弃流中指定字符
peek返回流中下一个字符,但不从流中删除
gcount统计最后输入的字符个数
seekg移动输入流指针
tellg返回输入流中指定位置的指针值

# istream 类的 get 函数

有3种形式
// 无参数,直接返回数据
int get();
// 参数是引用类型的变量,读取一个字符,不跳过空白字符
istream& get( char& rch );
istream& get( char* pch, int nCount, char delim = '\n' );
// 参数 1:char 类型的指针,读取多个字符,放到 pch 中
// 参数 2:读取字符数量上限
// 参数 3:读多个字符至 '\n' 结束,不提取 '\n'
利用无参数get函数读入数据
#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;
}
利用多个参数的get函数读入数据
#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>, <终止字符>)
  • 相同之处:

    1. 从输入流提取 n-1 个字符放入数组,因为最后一个位置要放字符数组的结束标准。
    2. 读取成功,则函数返回值是非 0 值。
    3. 若遇到文件结束符,则返回值为 0。
  • 不同之处:
    当读到终止字符时,
    cin.getline() —— 将指针移到终止字符之后
    cin.get() —— 将指针移到终止字符处
    则下次继续读取时的位置就不同。

# 标准输出流

流向标准输出设备(显示器)的数据。
一般使用 cout 流对象进行输出操作。
例如,用流插入运算符 << 输出数据。

coutostream 类的对象,除了 << 符号外,常用函数如下

函数功能
put无格式插入一个字节
write无格式插入一字节序列
flush刷新输出流
seekp移动输出流指针
tellp返回输出流中指定位置的指针值

# 用流对象的成员函数控制输出格式

  1. 设置状态标志流成员函数 setf

    cout.unsetf(ios::状态标志);
  2. 清除状态标志流成员函数 unsetf

    cout.unsetf(ios::状态标志);
  3. 设置域宽流成员函数 width
    设置显示数据占多少个字节宽

    cout.width(n);

    只对下一次流输出有效,输出完成后该函数的作用就消失。

  4. 设置实数的精度流成员函数 precision

    cout.precision(n);

    参数 n 在十进制小数形式输出时,代表有效数字。
    在以 fixed 形式和 scientific 形式输出时,代表小数位数。

  5. 填充字符流成员函数 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)清除状态标志,多个用 | 分隔

setiosflagsresetiosflags 的常用状态标志

状态标志功能
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>

  • 文件操作的基本步骤
    1. 定义一个文件流对象
    2. 打开文件
    3. 读 / 写文件
    4. 关闭文件

# 文件分类

  • 文本文件:
    是一种由若干行字符构成的计算机文件。其编码可以是 ASCII 码、UNICODE 码、GBK 编码等等。可以用文本编辑器编辑。

  • 二进制文件:
    除了上述以字符构成的文本文件,其他文件均称为二进制文件。典型的二进制文件有声音、动画、图像、视频等。

C++ 语言对这两种文件都可以进行创建、读写等操作。

# 定义一个文件流对象

ifstream 对象只进行读操作
ofstream 对象只进行写操作
fstream 对象既可以读文件,也可以写文件

# 打开文件

  1. 文件流类的成员函数 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二进制方式打开文件

每打开一个文件都有一个文件指针,
指针的开始位置由打开方式指定,
每次读写都从文件指针的当前位置开始。

  1. 还可以用文件流类的构造函数来打开文件,其参数和默认值与 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() 关闭文件后,该流对象可以重用。

# 文本文件的读写

下面以 ifstreamofstream 为例说明。

由于 ifstreamofstream 类继承自流类 istreamostream ,因此也可以使用常见的 IO 操作。
比如 >>getgetline 常用于文本文件输入,而操作 <<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 类提供成员函数 ,控制读写位置指针的移动,实现文件的随机读写。

文件内容
文件刚打开时:读写位置指针 ➡️
读写数据后自动移动
读写位置指针 ➡️
...
  1. 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 个字节
  1. 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;
}

# 课堂讨论

  1. 使用 cin>>cin.get()cin.getline() 读取字符串有什么区别? getline 函数必须读取一行数据吗?
  1. cin>> 不能获取含有空格的字符串;
  2. cin.getline() 可以获取含有空格的字符串,但是不能获取换行的字符串;
  3. cin.get() 以上两种的功能均可实现,并且可以获取换行的字符串;
  4. cin.getline() 的指针停止在中止字符之后,即把中止字符从输入流中拿掉;
  5. cin.get() 的指针停止在终止字符位置,即不会把中止字符从输入流中拿掉。
    getline 函数不是必须读取一行数据, getline 也可以读取单个字符
  1. 请总结读写文本文件的步骤。
// 包含头文件
#include<fstream>
// 创建对象
ifstream in;
// 打开文件
in.open("file.txt");
// 读或写
in>>a;
// 关闭文件
in.close();
  1. 读取文本文件时,一定要知道文件的格式吗?

  2. 文本文件读写方法可以用于二进制文件读写吗?

不可以。二进制数据解释成文本,有些在 ASCII 表中不存在,有些是不可视的字符,会出现乱码。

# 随堂练习

  1. 下列说法错误的是

    • 进入输入流的数据是有序的数据集合。
    • cinistream 类的对象。
    • cin 是输入流类。
    • cin 是输入函数。
  2. 有一段程序如下:

    char  str[200];
    cin.get( str,  100,  '*' );

    利用上面代码从标准输入流获取一段文本,下列说法错误的是:

    • 获取的文本放到 str 数组中
    • 遇到 * 时,停止读入文本,同时 * 被从流中取出丢掉
    • 系统在 str 数组的最后一个字符后面自动添加 \0
    • 空格字符可以正常读取
  3. 为了舍去流中的一些字符,应该使用的输入流的成员函数是

    • ignore
    • peek
    • get
    • seekg
  4. 用语句 cin.getline(ch,70,'*'); 从标准输入流获取一段字符,遇到 * 时,停止读入文本,同时 * 被从流中取出丢掉。

  5. 设置输出格式时,利用 setf 成员函数不能设置的显示属性是

    • 输出数据在本域宽范围内左对齐
    • 输出数据在本域宽范围内右对齐
    • 设置整数的按 8 进制显示
    • 设置实数显示的小数点位数
  6. 使用流操纵符(流格式控制符)控制输出格式,是将控制符号作为数据的一部分插入到输出流中。

  7. 只用于读文件的流类是

    • ifstream
    • ofstream
    • iostream
    • fstream
  8. 不论 ifstreamofstream 对象,打开文件都可以使用构造函数或 open 函数,这两种方式所使用的函数的参数可以完全一致。

  9. 假定 inifstream 类的对象,用 in 打开一个文件后,下面的代码用于读取文件内容:

    while(in) // 读取文件内容
    {
    	char c=in.get();
    	if(in) {
    		cout<<c;
    	}
    }

    请问上面程序段划线的语句中使用 if 判别语句的原因是:

    • 确保循环能终止
    • 是输入流操作中的语法要求
    • 确保不重复输出最后读取的数据
    • if 判别是多余的,可以不用
  10. 在标准输入流中常使用符号 >>get 函数、 getline 函数输入数据,这些方式也可用于从文件流向应用程序输入数据,且使用方式不变。

  11. 在标准输出流中常使用符号 <<put 函数向屏幕输出数据,这些方式也可用于向文件流写入数据,且使用方式不变。

  12. 不论何种方式打开一个二进制文件,在文件打开方式(即第二个参数)中必须有

    • ios::in
    • ios::out
    • ios::binary
    • ios::app
  13. 二进制文件使用的读写成员函数是

    • get
    • put
    • read
    • write
阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Ruri Shimotsuki 微信支付

微信支付

Ruri Shimotsuki 支付宝

支付宝

Ruri Shimotsuki 贝宝

贝宝