写在前面:
- 本系列专栏主要介绍C++的相关知识,思路以下面的参考链接教程为主,大部分笔记也出自该教程,笔者的原创部分主要在示例代码的注释部分。
- 除了参考下面的链接教程以外,笔者还参考了其它的一些C++教材(比如计算机二级教材和C语言教材),笔者认为重要的部分大多都会用粗体标注(未被标注出的部分可能全是重点,可根据相关部分的示例代码量和注释量判断,或者根据实际经验判断)。
- 由于C++基本继承了C语言的所有内容,建议读者先阅读C语言系列的专栏,有一些重点是仅在C语言系列专栏中有介绍的(比如二级指针、预处理等)。
- 如有错漏欢迎指出。
参考教程:黑马程序员匠心之作|C++教程从0到1入门编程,学习编程不再难_哔哩哔哩_bilibili
一、C++流的概念
1、C++流的体系结构
(1)C++为实现数据的输入输出定义了一系列的流类,这些类之间的派生、继承关系如下图所示,它们之中一部分是用模板实现的,图中用细线框表示,另外图中的虚线表示模板类与模板实例的关系。
(2)要利用C++流,必须在程序中包含有关的头文件,以便获得相关流类的声明。与C++流有关的头文件有:
①iostream:使用cin、cout等预定义流对象针对标准设备进行I/O操作。
②fstream:使用文件流对象针对磁盘文件进行I/O操作。
③ststream:使用字符串流对象针对内存字符串空间进行I/O操作。
④iomanip:使用setw、fixed等操作符进行格式控制。
2、预定义流对象
C++流有4个预定义流对象,它们的名称及与之联系的I/O设备如下,其中cin为istream类对象,其余3个为ostream类对象。
cin | 标准输入 |
cout | 标准输出 |
cerr | 标准出错信息输出 |
clog | 带缓冲的标准出错信息输出 |
3、提取运算符“>>”和插入运算符“<<”
(1)输入流类istream重载了运算符“>>”用于数据输入,其原型为
istream& operator>>(istream&, <类型>&);
重载的“>>”的功能是从输入流中提取数据赋值给一个变量,因此称之为提取运算符。当系统执行cin>>x操作时,将根据实参x的类型调用相应的提取运算符重载函数,把x的引用传送给对应的形参,接着从键盘的输入缓冲区读入一个值并赋给x后,返回istream流,以便继续使用提取运算符为下一个变量输入数据。
(2)输出流类ostream重载了运算符“<<”用于数据输出,其原型为
ostream& operator<<(ostream&, const <类型>&);
重载的“<<”的功能是把表达式的值插入到输出流中,因此称之为插入运算符。当系统执行cout<<x操作时,首先根据x的类型调用相应的插入运算符重载函数,把x的值传送给对应的形参,接着执行函数体,把x的值输出到显示器屏幕上,在当前屏幕光标位置显示,然后返回ostream流,以便继续使用插入运算符输出下一个表达式的值。
(3)除了可以将“>>”和“<<”运算符作用于C++中固有的数据类型外,还可以将其作为非成员函数重载作用于用户自定义类型,详见第十章——运算符重载,这里不再赘述。
4、有格式输入输出和无格式输入输出
利用C++流既可以进行有格式输入输出,也可以进行无格式输入输出。
(1)有格式输入输出针对的是键盘、显示器、打印机等字符设备以及磁盘中的文本文件,cin、cout等预定义流对象只能用于有格式输入输出,提取运算符和插入运算符也只能用于有格式输入输出。
(2)无格式输入输出只针对磁盘文件,需调用流对象中专门的成员函数实现。
二、输入输出的格式控制
1、默认的输入输出格式
(1)C++流所识别的输入数据类型及其默认的输入格式如下所示。
short、int、long(signed、unsigned) | 与整型常量同 |
float、double、long double | 与浮点数常量同 |
char(signed、unsigned) | 第一个非空白字符 |
char * (signed、unsigned) | 从第一个非空白字符开始到下一个空白字符结束 |
void * | 无前缀的16进制数 |
bool | 把true或1识别成true,其它均识别为false(VC6.0中把0识别为false,其它均识别为true) |
(2)C++所识别的输出数据的类型及其默认的输出格式如下所示。
short、int、long(signed、unsigned) | 一般整数形式,负数前有负号 |
float、double、long double | 浮点格式或指数形式(科学计数法),较短者优先 |
char(signed、unsigned) | 单个字符(无引号) |
char * (signed、unsigned) | 字符序列(无引号) |
void * | 无前缀的16进制数 |
bool | 1或0 |
2、格式标志与格式控制
(1)在作为流库根类的ios_base中,有一个作为数据成员的格式控制变量专门用来记录格式标志,通过设置标志可以有意识地对有格式输入输出的效果加以控制。
(2)各种格式标志被定义为一组符号常量,如下表所示。
格式控制标志 | 含义 |
skipws | 输入时跳过空白字符 |
left | 输出数据在指定宽度内左对齐 |
right | 输出数据在指定宽度内右对齐 |
internal | 输出数据在指定宽度内内部对齐,即符号在最左端,数值数据右对齐 |
dec | 整数按十进制输出 |
oct | 整数按八进制输出 |
hex | 整数按十六进制输出 |
showbase | 显示数制标志 |
showpoint | 浮点数即使小数部分为0也显示小数点,如114.0输出为114.000 |
uppercase | 用大写字符输出数制标志 |
showpos | 正数也显示符号 |
scientific | 按指数格式(科学计数法)显示浮点数 |
fixed | 按定点格式显示浮点数 |
unitbuf | 每次输出操作后立即写缓 |
boolalpha | 把逻辑值输出为true和false |
3、输入输出宽度的控制
(1)宽度设置用于输入时只对字符串有效,宽度设置用于输出时指最小输出宽度。
(2)当实际数据宽度小于指定的宽度时,多余的位置用填充字符(通常是空格)填满;当实际数据的宽度大于设置的宽度时,仍按实际的宽度输出;初始宽度值为0,其含义是所有数据都按实际宽度输出。
(3)与宽度设置有关的操作符是“setw(int n)”,等价函数调用为“io.width(n)”,其中n为一个表示宽度的表达式,如果用于字符串输入,实际输入的字符串的最大长度为n-1,也就是说宽度n连字符串结束符也包含在内。函数width返回此前设置的宽度。
(4)宽度设置的效果只对一次输入或输出有效,在完成了一个数据的输入或输出后,宽度设置自动恢复为0。
4、浮点数输出方式的控制
(1)在初始状态下,浮点数按浮点格式输出,输出精度是指有效的个数,小数点的相对位置随数据的不同而浮动;将浮点数改变为按定点格式或指数格式(科学计数法)输出时,输出精度是指小数位数,小数点的相对位置固定不变,必要时进行舍入处理或添加无效0。
(2)浮点数输出方式的设置一直有效,直到再次设置浮点数输出方式时为止。
(3)与浮点数输出方式控制有关的操作符如下。
操作符 | 等价函数调用 | 输出方式 |
restiosflags(ios_base::floatfield) | o.unsetf(ios_base::floatfield) | 浮点数按浮点格式输出(默认设置) |
fixed | o.setf(ios_base::fixed, ios_base::floatfield) | 浮点数按定点格式输出 |
scientific | o.setf(ios_base::scientific, ios_base::floatfield) | 浮点数按指数格式输出 |
5、输出精度的控制
(1)输入输出精度是针对浮点数设置的,其实际含义与浮点数输出方式有关:
①如果采用浮点格式,精度的函数是有效位数。
②如果采用定点格式或指数格式,精度的含义是小数位数。
(2)精度的设置用于输出,默认值为6,可以通过设置改变精度,将精度值设置为0意味着回到默认精度6。
(3)精度值的设置一直有效,直到再次设置精度时为止。
(4)与精度设置有关的操作符为“setprecision(int n)”,等价函数调用为“io.precision(n)”,其中n为表明精度值的表达式。函数precision返回此前设置的精度。
6、对齐方式的控制
(1)初始的对齐方式为右对齐,可以改变这一设置,使得输出采用左对齐方式或内部对齐方式。
(2)对齐方式的设置一直有效,直到再次设置对齐方式为止。
(3)只有在设置了宽度的情况下,对齐操作才有意义。
(4)与对齐方式控制有关的操作符如下。
操作符 | 等价函数调用 | 输出方式 |
left | o.setf(ios_base::left, ios_base::adjustfield) | 在设定的宽度内左对齐输出,右端使用设定的填充字符填满 |
right | o.setf(ios_base::right, ios_base::adjustfield) | 在设定的宽度内右对齐输出,左端使用设定的填充字符填满(默认设置) |
internal | o.setf(ios_base::internal, ios_base::adjustfield) | 在设定的宽度内右对齐输出,但若有符号(-或+),则符号置于最左端 |
7、小数点处理方式的控制
(1)此设置只影响采用浮点格式输出的浮点数据。
(2)在初始状态下,若一浮点数的小数部分为0,则不输出小数点及小数点后的无效0,可以改变这一设置,使得在任何情况下都输出小数点及其后面的无效0。
(3)小数点处理方式的设置一直有效,直到再次设置小数点处理方式为止。
(4)与小数点处理方式控制有关的操作符如下。
操作符 | 等价函数调用 | 输出方式 |
showpoint | o.setf(ios_base::showpoint) | 即使小数部分为0,也输出小数点及其后的无效0 |
noshowpoint | o.unsetf(ios_base::showpoint) | 小数部分为0时不输出小数点(默认设置) |
8、填充字符的控制
(1)在输出数据时,如果数据宽度小于设置的宽度,则空闲位置要用填充字符填满。
(2)初始填充字符为空格,可以将别的字符设置为填充字符。
(3)填充字符的设置一直有效,直到再次设置填充字符时为止。
(4)只有在设置了宽度的情况下,字符填充操作才有意义。
(5)与填充字符控制有关的操作符为“setfill(char c)”,等价函数调用为“io.fill(c)”,其中c是用于填充的字符。函数fill返回此前设置的填充字符。
9、插入换行符
(1)“endl”的作用是插入换行符,即插入’\n’,并强制写缓冲区,实现输出的回车换行。
(2)“endl”的等价函数调用为“o.put(o.widen(‘\n’)); o.flush()”。
10、输入输出数制状态的控制
(1)初始状态为十进制,可以将之设置为八进制或十六进制。
(2)数制状态的设置一直有效,直到下一次设置为止。
(3)与输入输出数制状态控制有关的操作符如下。
操作符 | 等价函数调用 | 输出方式 |
dec | io.setf(ios_base::dec, ios_base::basefield) | 将整数按十进制输入输出(默认设置) |
oct | io.setf(ios_base::oct, ios_base::basefield) | 将整数按八进制输入输出 |
hex | io.setf(ios_base::hex, ios_base::basefield) | 将整数按十六进制输入输出 |
setbase(int base) | io.setf(ios_base::oct, ios_base::basefield)等 | 设置数制(8、10、16) |
showbase | o.setf(ios_base::showbase) | 为输出的整数生成一个前缀,用以表示该数的数制(对于八进制数,前面输出一个无效0;对于十六进制数,前面输出“0x”) |
noshowbase | o.unsetf(ios_base::showbase) | 取消上述设置(默认设置) |
uppercase | o.setf(ios_base::uppercase) | 将数制标志中的字母以大写形式输出 |
nouppercase | o.unsetf(ios_base::uppercase) | 数制标志中的字母恢复为小写形式(默认设置) |
三、文件流
1、概述
(1)程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放,通过文件可以将数据持久化。
(2)C++中对文件操作需要包含头文件 < fstream >。
(3)文件类型分为两种:
①文本文件——文件以文本的ASCII码形式存储在计算机中。
②二进制文件——文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们。
(4)文件流以磁盘文件以及其它可按文件方式进行管理的外部设备作为输入输出对象,ifstream是文件输入流类(负责文件的读操作),ofstream是文件输出流类(负责文件的写操作),fstream是文件输入输出流类(负责文件的读写操作)。
(5)文件的打开方式:
利用|操作符可以实现文件打开方式配合使用,例如用二进制方式写文件:
ios::binary | ios:: out
2、写文件(文本文件)
(1)写文件步骤如下:
①包含头文件:
#include <fstream>
②创建流对象:
ofstream ofs;
③打开文件:
ofs.open("<文件路径>", <打开方式>);
④写数据:
ofs << "<写入的数据>";
⑤关闭文件:
ofs.close();
(2)举例:
#include<iostream>
using namespace std;
#include<fstream> //包含头文件
void test01()
{
ofstream ofs; //创建流对象
ofs.open("test.txt",ios::out); //指定打开方式
ofs << "姓名:张三" << endl; //写内容
ofs << "性别:男" << endl;
ofs << "年龄:18" << endl;
ofs.close(); //关闭文件
}
int main() {
test01();
system("pause");
return 0;
}
3、读文件(文本文件)
(1)读文件步骤如下:
①包含头文件:
#include <fstream>
②创建流对象:
ifstream ifs;
③打开文件并判断文件是否打开成功:
ifs.open("<文件路径>", <打开方式>);
④读数据:
//共有四种方式读取数据,具体见下例
⑤关闭文件
ifs.close();
(2)举例:
#include<iostream>
using namespace std;
#include<fstream> //包含头文件
#include<string>
void test01()
{
ifstream ifs; //创建流对象
ifs.open("test.txt",ios::in); //指定打开方式
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
char buf[1024] = { 0 }; //读取文件的第一种方式
while (ifs >> buf)
{
cout << buf << endl;
}
/*char buf[1024] = { 0 }; //读取文件的第二种方式
while (ifs.getline(buf, sizeof(buf)))
{
cout << buf << endl;
}*/
/*string buf; //读取文件的第三种方式
while (getline(ifs, buf))
{
cout << buf << endl;
}*/
/*char c; //读取文件的第四种方式(不推荐)
while ( (c = ifs.get()) != EOF )
{
cout << buf << endl;
}*/
ifs.close(); //关闭文件
}
int main() {
test01();
system("pause");
return 0;
}
4、写文件(二进制文件)
(1)以二进制方式写文件主要利用流对象调用成员函数write,文件输出流对象可以通过write函数,以二进制方式写数据。
(2)write函数的原型:
ostream& write(const char * buffer, int len);
//字符指针buffer指向内存中一段存储空间,len是读写的字节数
(3)举例:
#include<iostream>
using namespace std;
#include<fstream> //包含头文件
class Person
{
public:
char m_Name[64];
int m_Age;
};
void test01()
{
ofstream ofs; //创建流对象
ofs.open("person.txt", ios::out | ios::binary); //指定打开方式
//ofstream ofs("person.txt", ios::out | ios::binary); 上两句可以合并
Person p = { "张三",18 };
ofs.write((const char *)&p,sizeof(Person)); //写文件
ofs.close(); //关闭文件
}
int main() {
test01();
system("pause");
return 0;
}
5、读文件(二进制文件)
(1)二进制方式读文件主要利用流对象调用成员函数read,文件输入流对象可以通过read函数,以二进制方式读数据。
(2)read函数的原型:
istream& read(char *buffer,int len);
//字符指针buffer指向内存中一段存储空间,len是读写的字节数
(3)举例:
#include<iostream>
using namespace std;
#include<fstream> //包含头文件
class Person
{
public:
char m_Name[64];
int m_Age;
};
void test01()
{
ifstream ifs; //创建流对象
ifs.open("person.txt", ios::in | ios::binary); //指定打开方式
if (!ifs.is_open())
{
cout << "文件打开失败" << endl;
return;
}
Person p; //读文件
ifs.read((char *)&p, sizeof(Person));
cout << "姓名:" << p.m_Name << " 年龄:" << p.m_Age << endl;
ifs.close(); //关闭文件
}
int main() {
test01();
system("pause");
return 0;
}