// 常用的类型实际上是类模板实例化的结果
#include <iostream>
// 表示形式的变化:使用格式化 / 解析在数据的内部表示与字符序列间转换
int main()
{
int x = 100; // 00..00 01100100
// 将二进制的值格式化成 三个字符: 1 0 0 ; 输出流
std::cout << x << std::endl;
std::cin
}
////////////////////////////////////////////////////
int main()
{
union // 联合体
{ // 使用一块内存既表示 x , 又表示 y
// int 和 float 都占了四个字节
// 这四个字节可以同时用来表示 x 和 y
int x;
float y;
};
x = 100;
std::cout << x << std::endl; // -> 100
std::cout << y << std::endl; // -> 1.4013e-43
// 用不同的方式解释同一块内存, 相应的格式化处理方式不同,
// 类型不同输出流转换出来的字符序列不同
}
////////////////////////////////////////////////////
/*
template<
class CharT,
class Traits = std::char_traits<CharT>
> class basic_ifstream : public std::basic_istream<CharT, Traits>
*/
#include <fstream> // 对文件进行输入输出操作
int main()
{
std::ifstream x;
std::basic_ifstream<char,std::char_traits<char>> x; //同上面等价
// ifstream -> basic_ifstream<char>
//char 是一个模板的实参
// wifstream -> basic_ifstream<wchar_t>
// -->
std::ifstream x = std::basic_ifstream<char>();
}
输入与输出
非格式化 I/O
#include <iostream>
int main()
{
// 格式化
int x;
std::cin >> x; // 格式化操作,读入字符解析成二进制表示,并保存在int中
std::cout << x << std::endl; // 把 x 中二进制表示进行格式化,输出
// 非格式化输入
// basic_istream& read( char_type* s, std::streamsize count );
std::cin.read(reinterpret_cast<char*>(&x), sizeof(x));
std::cout << x << std::endl;
// 输入 100 输出 170930225
// 原因:希望读入四个字符,100和回车;每个字符都对应一个ASCII码,
// 每个ASCII码是一串二进制的数值,read会把每个字符对应的ASCII码读出来
// 把这个数值不作任何解析操作和变换,直接放到 x 的某个字节当中
// 会把四个字符对应的ASCII码原封不动放到 x 的字符中
// 放完后再尝试打印 x ,使用格式化的输出系统尝试把这样二进制的序列进行格式化解析
// 二进制 -> int 转换; 因此转换完之后并不与 100 对应
// 输入 1 ; 还在等待输入; 在输入 2 ; 输出 171051569
// 因为采用非格式化输入,需要输入四个字符;1 + 回车 + 2 + 回车
}
// 非格式化优势
int main()
{
float y = 3.14; // 类型确定,对象所占的尺寸基本确定
// 但字符个数不同,在寄存器内部所占宽度不同:3 是一个字符; 3.14是四个字符
std::cout << y << std::endl; // 对人友好
// 在一堆 float 数里找某一个字符输出,长度不可控
// 非格式化:每个类型占的字符个数确定,可以定位到具体的字符,对计算机友好,人不可读
}
格式化 I/O
#include <iostream>
// C++ 通过操作符重载以支持内建数据类型的格式化 I/O
// 触发函数重载
int main()
{
char x = '0';
// 重载了移位操作符进行输入输出
std::cout << x << std::endl; // -> 0
// std::cout << x 触发重载
int y = static_cast<int>(x);
std::cout << y << std::endl; // -> 48
// 根据传入的类型选择适当的调用以格式化
}
// 格式控制
// 可接收位掩码类型( showpos )
// C++ 基本数据单位是字节,一个字节占 8 位,
// 在一些特殊情况下,仅需要对 1 位或几位数据进行操作
// showpos 用来修改某一个字节的某一位
int main()
{
char x = '0';
// setf -> set flag flag 一位
std::cout.setf(std::ios_base::showpos); // positive +
// 需要显示正号时
// 相当于改变了格式化的行为
std::cout << x << std::endl; // -> 0
// 此处打印字符,并不是一个整数,不存在正负,所以没有正号
int y = static_cast<int>(x);
std::cout << y << std::endl; // -> +48
}
// 取值相对随意( width )
// 字符类型( fill )
int main()
{
char x = '0';
std::cout.setf(std::ios_base::showpos);
std::cout.width(10); // 要让整个输出占 10 个字符
std::cout.fill('.'); // 用 . 填充字符
std::cout << x << std::endl; // -> " 0"
int y = static_cast<int>(x);
// 注意 width 方法的特殊性:触发后被重置
std::cout << y << std::endl; // -> +48
}
操纵符
#include <iostream>
#include <string>
#include <iomanip>
int main()
{
char x = '0';
int y = static_cast<int>(x);
std::cout.setf(std::ios_base::showpos);
std::cout.width(10);
std::cout.fill('.');
std::cout << x << std::endl;
std::cout.width(10);
std::cout << y << std::endl;
std::cout << x << '\n' << y << '\n';
}
#include <iomanip>
// 操纵符
int main()
{
char x = '0';
int y = static_cast<int>(x);
std::cout << std::showpos
<< std::setw(10) << std::setfill('.') << x << '\n'
<< std::setw(10) << y << std::endl;
}
// 提取会放松对格式的限制, 部分数据类型有效
int x;
char x; // 行为不一样,只取第一个字符
std::string x;
std::cin >> x; // +10; 10 -> 10
//提取 C 风格字符串时要小心内存越界
int main()
{
char x[5]; // abcdefg \0
std::cin >> x;
std::cout << x << std::endl;
std::cin >> std::setw(5) >> x; // abcdefg \0
std::cout << x << std::endl; // abcd
}
文件与内存操作
文件操作
// basic_ifstream / basic_ofstream / basic_fstream
#include <iostream>
#include <fstream>
#include <string>
int main()
{
std::ofstream outFile("my_file");
std::cout << outFile.is_open() << '\n'; // 判断文件流是否打开
outFile << "Hello\n"; // 输出到文件里,cout 输出到终端
std::ofstream outFile; // 缺省构造,此时outFile没有和指定文件相关联
std::cout << outFile.is_open() << '\n'; // 此时关闭,无关联
outFile.open("my_file"); // 使 outFile 与文件关联
std::cout << outFile.is_open() << '\n'; // 此时打开
outFile.close(); // 解除关联
std::cout << outFile.is_open() << '\n'; // 关闭
// 读取文件
std::ifstream inFile("my_file");
std::string x;
inFile >> x;
std::cout << x << '\n'; // -> Hello
}
// 文件流可以处于打开 / 关闭两种状态
// 处于打开状态时无法再次打开,只有打开时才能 I/O
int main()
{
// 类模板:1.构造函数,构造对象 2.析构函数,销毁对象
// 销毁对象时会调用析构函数,此处的析构函数包含了关闭数据流的逻辑
// 因此没有显示包含outFile.close()也能把缓存中的内容放入文件
std::ofstream outFile; // ofstream 抽象数据函数,使用类模板实现
outFile.open("my_file");
outFile << "Hello\n"; // 写到缓存
outFile.close(); // 把缓存中的内容放到文件里,再关闭
// 确保缓存中的内容全部放到文件中
// 如果想在某一行之前执行完销毁
// 构造域 即可
{
std::ofstream outFile("my_file");
outFile << "Hello\n";
}
// 此时 outFile 已经被关闭
}
文件流的打开模式
// 每种文件流都有缺省的打开方式
#include <iostream>
#include <fstream>
int main()
{
std::ofstream outFile("my_file", std::ios_base::out);
outFile << "Hello\n";
std::ifstream inFile("my_file", std::ios_base::in);
std::ios_base::in; // 0010
std::ios_base::ate; // 0001
std::ios_base::in | std::ios_base::ate // 按位或 不是逻辑或
// 按位或 0011 -> 表示既能实现 in 的功能又能实现 ate 的功能
// 如果文件存在,会把原始文件清了,再写入
std::ofstream outFile("my_file", std::ios_base::out | std::ios_base::trunc);
outFile << "World\n";
// 附加
std::ofstream outFile("my_file", std::ios_base::out | std::ios_base::app);
}
内存流
// basic_istringstream
// basic_ostringstream
// basic_stringstream
#include <iostream>
#include <sstream>
// 输出流
int main()
{
std::ostringstream obj1; // 构造输出的内存流对象
obj1 << 1234; // 格式化方式输出,注意 这里输入是 1234 整数--> 字符串
// obj1 << true; // -> 1 最终是把数据放到 res 内;不是终端不是文件
// 把 1234 放到一块内存中
// 需要找到对应的内存读取相应的数据
// 用 str 获取底层所对应的内存
// 返回 std::basic_string<CharT,Traits,Allocator> 其实就是string
auto res = obj1.str();
// 可以直接写成
std::string res = obj1.str(); // obj1 内部保存的对应的信息
std::cout << res << std::endl; // -> 1234 字符串
}
#include <iomanip>
int main()
{
std::ostringstream obj1;
obj1 << std::setw(10) << std::setfill('.') << 10; // -> ........10
std::string res = obj1.str();
std::cout << res << std::endl;
}
// 输入流
int main()
{
std::ostringstream obj1;
obj1 << 10;
std::string res = obj1.str();
std::istringstream obj2(res); // 构造输入流时需把读取的内存给出
int x;
obj2 >> x; // std::cin >> x
std::cout << x << std::endl;
}
// 也会受打开模式: in / out / ate / app 的影响
int main()
{
// 输出流在 append 模式 (C++11)
std::ostringstream buf2("test", std::ios_base::ate);
buf2 << '1'; // 位于结尾处 + 1 // 可以自动扩大内存
std::cout << buf2.str() << '\n';
std::ostringstream buf2("test");
buf2 << '1'; // 输出 1est ; 会把 t 替换掉
std::cout << buf2.str() << '\n';
}
// 使用 str() 方法获取底层所对应的字符串
// 小心 避免使用 str().c_str() 的形式获取 C 风格字符串
int main()
{
std::ostringstream buf2("test", std::ios_base::ate);
buf2 << '1';
std::string res = buf2.str();
auto c_res = res.c_str(); // C 风格字符串
// c_res 是一个指针,指向 字符串 开头地址
std:: cout << c_res << std::endl; // 合法
// 这样也行,但要 小心销毁内存 导致 程序未定义 的情况发生
std:: cout << buf2.str().c_str() << std::endl;
// 不能这么写,程序未定义
auto c_res = buf2.str().c_str();
// buf2.str()返回的是右值 字符串,c_str()指向了这块字符串内部的首地址,此处还没问题
// 因为执行了右值,此处会被释放掉
std:: cout << c_res << std::endl;
// 可能会指向一块已经被释放掉的内存,导致行为未定义
}
// 基于字符串流的字符串拼接优化操作
int main()
{
std::string x; // string
x += "Hello"; // 插入字符串,先看string 是否有足够的缓冲区 保存
x += "World"; // 插入新的字符,基本上要建立新的缓冲区
x += "Hello"; // 把原始缓冲区的内容拷到新的缓冲区中
x += "World"; // 再把插入的字符添加到新的缓冲区中,再把原始的缓冲区释放
// 涉及到缓冲区开辟,内存拷贝,缓冲区释放过程
std::cout << x << std::endl;
// 这样性能较差
// 行为一样,但较上述性能要好
// 基于字符串流的字符串拼接优化操作
// 原理:数据先放到缓冲区而不是直接输出到设备;
// 此处缓冲区较大,且在缓冲区满了的时候一次性把内容输出到对应设备
// 此时才会设计到内存开辟、拷贝、释放; 减少了内存的消耗成本。因此性能更好
std::ostringstream obj;
obj << "Hello";
obj << "World";
obj << "Hello";
obj << "World";
std::cout << obj.str() << std::endl;
// obj.str()触发缓冲区刷新,把内容输出到目标内存
}
流的状态
// iostate
typedef /*implementation defined*/ iostate;
static constexpr iostate goodbit = 0;
static constexpr iostate badbit = /*implementation defined 实现定义*/
static constexpr iostate failbit = /*implementation defined*/
static constexpr iostate eofbit = /*implementation defined*/
// 编译器定义的
/*
常量 解释
goodbit 无错误
badbit 不可恢复的流错误
failbit 输入/输出操作失败(格式化或提取错误)
eofbit 关联的输出序列已抵达文件尾*/
#include <iostream>
int main()
{
//指定流状态标志。它是位掩码类型 (BitmaskType)
iostate -- char
badbit -- 0000,0001 // badbit | failbit = 0000,0011
failbit -- 0000,0010
eofbit -- 0000,0100
}
#include <iostream>
#include <fstream>
// badbit 不可恢复
int main()
{
std::ofstream outFile; // 构造outFile
outFile << 10; // 没有对应文件,把outFile由一个正常状态变成异常状态
// 异常状态 -> badbit 不可恢复
}
// failbit 可恢复
int main()
{
// 输入流
int x;
std::cin >> x;
// 输入 Hello 因为不是 int型 因此会 failbit
// 输出流
std::ofstream outFile;// 当前状态即是关闭状态
outFile.close();// 再次进行关闭 failbit
}
// eofbit : end of file 读到文件尾没得读了会产生eofbit
// 对文件流、内存流以及终端流都有效
// 只对输入序列有关
int main()
{
int x;
std::cin >> x;
// Ubuntu下 ctrl + D --> eofbit
}
// 检测流的状态
// good() / fail() / bad() / eof()
int main()
{
int x;
std::cin >> x;
std::cout << std::cin.good()
<< std::cin.fail()
<< std::cin.bad()
<< std::cin.eof() // 检测异常状态eofbit
<< static_cast<bool>(std::cin) << std::endl;
}
//注意区分 fail 与 eof
// char 时 eof 和 fail 会被同时设置
int main()
{
char x;
// 先输入 a
// Ubuntu下按两次 ctrl + D
std::cin >> x;
std::cout << std::cin.fail() << ' ' << std::cin.eof() << std::endl;
// 读完 a 已经读取完了系统不会再往下读取,因此 eof 输出为 0
std::cin >> x;
// 因为已经读到了结尾,eof 输出为 1,fail 输出 1
std::cout << std::cin.fail() << ' ' << std::cin.eof() << std::endl;
// 输出 0 0; 1 1
}
// int 时 eof 和 fail 不会被同时设置
int main()
{
int x;
std::cin >> x;
// 输入 10 ; 第一个字符 1 第二个字符 0,读到输入结尾
// 输出 0 1
std::cout << std::cin.fail() << ' ' << std::cin.eof() << std::endl;
}
// 通常来说,只要流处于某种错误状态时,插入 / 提取操作就不会生效
// 复位流状态
// clear :设置流的状态为具体的值(缺省为 goodbit )
void clear( std::ios_base::iostate state = std::ios_base::goodbit );
int main()
{
int x;
std::cin >> x;
std::cout << std::cin.fail() << ' ' << std::cin.eof() << std::endl;
std::cin.clear(); // 传入了 goodbit
// 相当于把所有错误的状态给清空了,复位了流的状态
// 也可以传入 failbit、badbit、eofbit,设置流的状态为具体的值
}
// setstate :将某个状态附加到现有的流状态上
// 通常不会调用上述方法,一般不太需要对流的状态进行处理
// 捕获流异常:exceptions方法
// 程序处于非正常状态时 抛出异常,使程序跳转到异常处理
#include <iostream>
#include <fstream>
int main()
{
int ivalue;
try {
std::ifstream in("in.txt");
in.exceptions(std::ifstream::failbit);
in >> ivalue;
} catch (std::ios_base::failure &fail) {
// 此处处理异常
}
}
流的定位
// 获取流位置
#include <iostream>
// tellg() / tellp() 可以用于获取输入 / 输出流位置 (pos_type 类型 )
// gap 拿 put 放
// 若 fail()==true ,则返回 pos_type(-1) 流错误则不能使用
#include <iostream>
#include <sstream>
int main()
{
std::ostringstream s; // 输出流
std::cout << s.tellp() << '\n'; // 返回的是 当前 可以写入的位置
s << 'h';
std::cout << s.tellp() << '\n';
s << "ello, world ";
std::cout << s.tellp() << '\n';
s << 3.14 << '\n';
std::cout << s.tellp() << '\n' << s.str();
}
// 输出
0 // 当前可以写入的位置是 0
1 // 写入位置往后挪一位
13
18
hello, world 3.14
// tellg()
#include <iostream>
#include <string>
#include <sstream>
int main()
{
std::string str = "Hello, world";
std::istringstream in(str); // 输入流
std::string word;
in >> word; // 用 in 读取字符串并保存至 word
std::cout << "After reading the word \"" << word
<< "\" tellg() returns " << in.tellg() << '\n';
// 标准输入处理字符串时,按顺序依次处理,直到遇到分隔符
// tellg 打印出 接下来 要读取或写入的字符
}
// 输出
After reading the word "Hello," tellg() returns 6
// 设置流位置
// seekg() 用于设置输入流的位置
basic_istream& seekg( pos_type pos );
basic_istream& seekg( off_type off, std::ios_base::seekdir dir);
// off_type 偏移量
pos - 设置输入位置指示器到的绝对位置。
off - 设置输入位置指示器到的相对位置。
dir - 定义应用相对偏移到的基位置。它能为下列常量之一:
常量 解释
beg 流的开始
end 流的结尾
cur 流位置指示器的当前位置
#include <iostream>
#include <string>
#include <sstream>
int main()
{
std::string str = "Hello, world";
std::istringstream in(str); // 定义输入流
std::string word1, word2;
in >> word1;
in.seekg(0); // 回溯 //调用了第一个方法
// 流的当前位置是6,回溯到了0
in >> word2;
std::cout << "word1 = " << word1 << '\n'
<< "word2 = " << word2 << '\n';
}
// 输出:
word1 = Hello,
word2 = Hello,
// seekp() 用于设置输出流的位置
basic_ostream& seekp( pos_type pos );
basic_ostream& seekp( off_type off, std::ios_base::seekdir dir );
#include <sstream>
#include <iostream>
int main()
{
std::ostringstream os("hello, world");
os.seekp(7);
os << 'W'; // 小 w 换成 W
os.seekp(0, std::ios_base::end);// 挪到最后一个位置
os << '!';
os.seekp(0);// 挪到开头
os << 'H';
std::cout << os.str() << '\n';
}
// 输出
Hello, World!
流的同步
#include <sstream>
#include <iostream>
// 输出流的同步
int main()
{
std::cout << "What's your name\n";
// 缓冲区满了在一次性输出
// 若是基于缓冲区的用法,这句话还在缓冲区中,而系统还在等待输入
// 不能采用缺省行为(缓冲区满输出),需要主动刷新缓冲区——流的同步
// 方法1
std::cout.flush();
// 方法2
std::cout << "What's your name\n" << std::flush;
// 方法3
std::cout << "What's your name" << std::endl;
// endl 回车和刷新放在一起
std::string name;
std::cin >> name;
// cin 绑定到了 cout 上
// cin 在做任何操作之前会对 cout 的缓冲区进行刷新
// 因此此处 cout 的内容一定能打印出来
}
// 输入流的同步
// sync() 用于输入流同步,其实现逻辑是编译器所定义的
// 输出流可以通过设置 unitbuf 来保证每次输出后自动同步
// std::cout << std::unitbuf; // 启用自动冲入
// 好处:信息立即显示
// 坏处:影响程序性能(与外部设备交互次数变多)
int main()
{
// 区别
std::cout << "What's your name" << std::endl;
// 标准输出
// cout 一般会设置缓冲区
std::cerr << "What's your name" << std::endl;
// 标准错误输出
// 缺省情况会被设置成unitbuf,
// 即cerr中的内容会被直接显示出来
}
// 基于绑定 (tie) 的同步
// 流可以绑定到一个输出流上,这样在每次输入 / 输出前可以刷新输出流的缓冲区
A --> C // 将输出流 A 绑定到 C 上,即 A 记录了 C 中的内容
D // 若再将 A 绑定到 D 上, A 不再绑定 C
B --> C
// A、B 可以同时绑定到一个输出流
// 绑定后, A 无论是输入还是输出流,A 在做输入输出操作之前一定会刷新 C 的缓冲区
// 缺省情况下, C++ 的输入输出操作会与 C 的输入输出函数同步
#include <iostream>
#include <cstdio>
int main()
{
// 可以通过 sync_with_stdio 关闭该同步
std::ios::sync_with_stdio(false); // 解除同步
// 除非特别注重性能,一般不用关闭此同步
std::cout << "a\n";
std::printf("b\n");
std::cout << "c\n";
}
// Possible output:
b
a
c