C++ 流
在 C++ 标准库中,stream(流)是处理输入输出(I/O)的核心抽象,分为输入流(istream
)、输出流(ostream
)、输入输出流(iostream
)等多种类型,且支持面向不同设备(文件、字符串、控制台等)的具体流类型。
Stream 类型
类别 | 主要类型及说明 | 头文件 |
---|---|---|
输入流 | std::istream :通用输入流基类 |
<istream> |
std::ifstream :文件输入流 |
<fstream> |
|
std::istringstream :字符串输入流 |
<sstream> |
|
输出流 | std::ostream :通用输出流基类 |
<ostream> |
std::ofstream :文件输出流 |
<fstream> |
|
std::ostringstream :字符串输出流 |
<sstream> |
|
输入输出流 | std::iostream :通用输入输出流基类 |
<iostream> |
std::fstream :文件输入输出流 |
<fstream> |
|
std::stringstream :字符串输入输出流 |
<sstream> |
|
标准流对象 | std::cin :标准输入流(全局对象) |
<iostream> |
std::cout :标准输出流(全局对象) |
<iostream> |
|
std::cerr :标准错误输出流(无缓冲) |
<iostream> |
|
std::clog :标准错误输出流(带缓冲) |
<iostream> |
继承关系简图(简化版)
std::ios_base
└── std::ios
├── std::istream
│ ├── std::ifstream
│ └── std::istringstream
├── std::ostream
│ ├── std::ofstream
│ └── std::ostringstream
└── std::iostream
├── std::fstream
└── std::stringstream
一、标准流
C++ 标准库定义了四个主要的标准流对象:
对象名 | 类型 | 功能描述 | 缓冲机制 | 用途 |
---|---|---|---|---|
std::cin |
std::istream |
标准输入流,通常关联键盘输入 | 带缓冲 | 从键盘或重定向输入读取数据 |
std::cout |
std::ostream |
标准输出流,通常关联控制台显示 | 带缓冲 | 向控制台或重定向输出写数据 |
std::cerr |
std::ostream |
标准错误输出流,关联控制台显示 | 无缓冲 | 用于错误或即时输出提示 |
std::clog |
std::ostream |
标准日志输出流,关联控制台显示 | 带缓冲 | 用于程序日志信息输出 |
1. std::cin
- 标准输入流
- 类型:
std::istream
- 默认关联设备:键盘(终端输入)
- 用法:
int x;
std::cin >> x; // 从标准输入读取一个整数
- 缓冲:带缓冲。输入时会先缓冲一部分数据,通常是整行读取后交给程序处理。
- 支持格式化输入:
operator>>
会自动跳过空白、换行,进行类型转换。 - 支持重定向:例如从文件读取时,命令行重定向输入
program < input.txt
。
2. std::cout
- 标准输出流
- 类型:
std::ostream
- 默认关联设备:控制台(终端显示)
- 用法:
std::cout << "Hello, world!" << std::endl;
- 缓冲:带缓冲。缓冲区满或者遇到
std::endl
会刷新缓冲区写到终端。 - 支持各种类型的格式化输出(整型、浮点、字符串、自定义类型重载
operator<<
)。 - 支持重定向:例如把输出写入文件
program > output.txt
。
3. std::cerr
- 标准错误输出流
- 类型:
std::ostream
- 默认关联设备:控制台(终端显示)
- 无缓冲输出:写入时立即发送到终端,适合打印错误或警告信息,确保及时可见。
- 用法:
std::cerr << "Error: File not found!" << std::endl;
- 作用是和
cout
分开输出,方便终端或脚本捕获错误信息。 - 不受缓冲影响,即使程序异常退出也更容易保证错误信息打印出来。
4. std::clog
- 标准日志输出流
- 类型:
std::ostream
- 默认关联设备:控制台(终端显示)
- 带缓冲输出,适合大量日志信息,效率高。
- 用法:
std::clog << "Log: Starting the program..." << std::endl;
- 用于日志信息输出,不同于
cerr
主要是“错误”,clog
主要是“日志”。 - 和
cerr
区别在于缓冲方式和输出语义。
标准流对象常用操作和特性
操作 | 示例 | 说明 |
---|---|---|
读取输入 | std::cin >> x; |
从键盘读取格式化输入 |
输出文本 | std::cout << "Hello\n"; |
向终端打印文本 |
输出错误信息 | std::cerr << "Error!\n"; |
立即打印错误信息 |
输出日志 | std::clog << "Log message\n"; |
缓冲写入日志信息 |
刷新缓冲区 | std::cout.flush(); 或 std::cout << std::flush; |
立即将缓冲内容写入设备 |
操纵符换行+刷新 | std::cout << std::endl; |
输出换行并刷新缓冲 |
判断流状态 | if (!std::cin) { /* 读取失败 */ } |
判断流是否处于错误状态 |
重定向流 | 命令行重定向 program < input.txt > output.txt |
改变流的输入输出设备 |
标准流对象的缓冲策略
流对象 | 缓冲类型 | 说明 |
---|---|---|
std::cin |
带缓冲 | 读取时先缓冲,优化输入效率 |
std::cout |
带缓冲 | 输出时缓存,遇换行或缓冲满时刷新 |
std::cerr |
无缓冲 | 错误信息立即输出,不等待缓冲刷新 |
std::clog |
带缓冲 | 日志输出缓冲,提高效率 |
二、输入输出流
在C++中,输入输出流(Input/Output Stream)是对数据流动方向的抽象,用于从某个设备(如键盘、文件、内存)读取数据或向其写入数据。输入流负责读取,输出流负责写入。输入输出流即同时支持输入和输出的流,能既从中读取数据,也向其中写入数据。
基本流层级和具体实现类
std::iostream
- 继承自
std::istream
和std::ostream
。支持标准输入输出流的所有操作。既可以用作输入流,也可以用作输出流。 - 底层依赖于缓冲区(buffer)管理,避免频繁系统调用,提升效率。
iostream
对象拥有一个streambuf
缓冲区指针,通过它实现具体读写。iostream
继承了格式化输入输出的能力,比如支持operator>>
、operator<<
对各种类型的重载。
类名 | 功能描述 | 头文件 |
---|---|---|
std::ios_base |
I/O 库的基础类,管理格式化和状态 | <ios> |
std::ios |
继承自 ios_base ,增加缓冲等支持 |
<ios> |
std::istream |
输入流基类 | <istream> |
std::ostream |
输出流基类 | <ostream> |
std::iostream |
输入输出流基类(继承自 istream 和 ostream ) |
<iostream> |
iostream
继承了输入流和输出流的功能,因此可同时读写。
具体实现类:
类名 | 用途 | 头文件 |
---|---|---|
std::fstream |
文件输入输出流 | <fstream> |
std::stringstream |
内存字符串输入输出流 | <sstream> |
1. 文件读写(使用 std::fstream
)
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::fstream file("example.txt", std::ios::in | std::ios::out | std::ios::trunc);
if (!file) {
std::cerr << "文件打开失败\n";
return 1;
}
// 写入数据
file << "Hello, iostream!\n";
// 重置文件读取位置到开头
file.seekg(0);
// 读取数据
std::string line;
std::getline(file, line);
std::cout << "文件内容: " << line << std::endl;
file.close();
return 0;
}
2. 内存字符串读写(使用 std::stringstream
)
#include <sstream>
#include <iostream>
int main() {
std::stringstream ss;
// 写入
ss << "123 456";
int a, b;
// 读取
ss >> a >> b;
std::cout << "读取到的数字:" << a << ", " << b << std::endl;
}
//输出: 读取到的数字:123, 456
常用成员函数(部分)
函数 | 说明 |
---|---|
operator>> |
格式化输入(读取) |
operator<< |
格式化输出(写入) |
read(char*, size_t) |
读取原始字节流 |
write(char*, size_t) |
写入原始字节流 |
get() |
读取一个字符 |
put(char) |
写入一个字符 |
seekg(pos) |
设置输入位置 |
seekp(pos) |
设置输出位置 |
tellg() |
获取当前输入位置 |
tellp() |
获取当前输出位置 |
good() , fail() , eof() |
检查流状态 |
flush() |
刷新缓冲区 |
流状态及错误处理
在 C++ 中,输入输出流(iostream
)提供了状态标志和错误处理机制,用于检测和处理 I/O 操作中的异常或错误情况。理解这些机制对于编写健壮的 I/O 代码非常重要。
流的状态标志(Stream State Flags)
C++ 的流对象(如 std::ifstream
, std::ofstream
, std::cin
, std::cout
等)都继承自 std::ios_base
类,并维护一组状态标志来表示流当前的状态。这些状态标志是通过成员函数 rdstate()
获取的,其类型为 std::ios_base::iostate
。
常见的状态标志包括:
标志 | 含义 |
---|---|
goodbit |
没有发生错误(一切正常) |
badbit |
发生了不可恢复的读写错误(例如底层设备出错) |
failbit |
输入/输出操作失败(但流仍可用,例如类型不匹配) |
eofbit |
到达文件末尾(EOF) |
示例:
#include <iostream>
#include <fstream>
int main() {
std::ifstream file("example.txt");
if (!file) {
std::cerr << "Failed to open file.\n";
}
int value;
file >> value;
if (file.fail()) {
std::cerr << "Input failed: type mismatch or bad input.\n";
}
if (file.bad()) {
std::cerr << "Serious I/O error occurred.\n";
}
if (file.eof()) {
std::cerr << "End of file reached.\n";
}
if (file.good()) {
std::cout << "Everything is fine.\n";
}
return 0;
}
状态检查函数
除了直接使用 rdstate()
来获取状态位外,还可以使用以下成员函数进行状态判断:
函数名 | 返回值说明 |
---|---|
good() |
是否处于良好状态(没有设置任何错误标志) |
bad() |
是否发生了不可恢复的错误 |
fail() |
是否发生了可恢复的错误(包括 eofbit ) |
eof() |
是否到达文件末尾 |
operator!() |
是否处于失败状态(即 fail() 为真) |
operator bool() |
是否处于非失败状态(即 !fail() 为真) |
清除状态标志
如果你希望继续使用一个已经出错的流对象,可以使用 clear()
或 clear(iostate)
函数来清除状态标志。
示例:
#include <iostream>
int main() {
int num;
std::cin >> num;
if (std::cin.fail()) {
std::cin.clear(); // 清除 failbit
std::cin.ignore(10000, '\n'); // 忽略缓冲区中的非法输入
std::cout << "Invalid input. Please enter an integer: ";
std::cin >> num;
}
std::cout << "You entered: " << num << std::endl;
return 0;
}
错误处理与异常机制(Exception Handling)
默认情况下,C++ 流不会抛出异常,而是通过状态标志来报告错误。但你可以通过 exceptions()
函数启用异常处理。
示例:启用异常
#include <iostream>
#include <fstream>
int main() {
std::ifstream file("nonexistent.txt");
// 启用异常:当流状态变为 badbit 或 failbit 时抛出异常
file.exceptions(std::ifstream::failbit | std::ifstream::badbit);
try {
int value;
file >> value; // 如果文件不存在或无法读取,将抛出异常
} catch (const std::ios_base::failure& e) {
std::cerr << "Caught an exception: " << e.what() << '\n';
}
return 0;
}
⚠️ 注意:启用异常后,某些操作(如构造失败)可能不会触发异常。建议结合
if (!file)
进行检查。
三、输入流
在 C++ 中,输入流(Input Stream) 是一种用于从数据源读取数据的机制。C++ 的标准库提供了强大的 I/O 流类库,其中与输入相关的类主要包括:
std::istream
:基本输入流类。std::ifstream
:用于从文件中读取数据。std::istringstream
:用于从字符串中读取数据。std::cin
:标准输入流对象(通常来自键盘)。
所有输入流都继承自 std::istream
类。它的核心功能是提供读取操作符 >>
和成员函数如 get()
, getline()
, read()
等。
常用输入流对象:
对象/类 | 描述 |
---|---|
std::cin |
标准输入流,默认从控制台读取 |
std::ifstream |
文件输入流 |
std::istringstream |
字符串输入流 |
常用输入方法
1. 使用 operator >>
(提取运算符)
这是最常用的输入方式,用于按类型读取数据。
#include <iostream>
int main() {
int age;
std::cout << "Enter your age: ";
std::cin >> age;
std::cout << "You are " << age << " years old.\n";
return 0;
}
注意:
- 它会自动跳过前导空白字符(空格、换行、制表符等)。
- 如果输入类型不匹配(比如输入字母而非数字),将设置
failbit
标志。
2. 使用 get()
函数
用于逐个或批量读取字符(包括空白字符)。
单字符读取:
#include <iostream>
int main() {
char ch;
while (std::cin.get(ch)) {
std::cout << ch;
}
return 0;
}
多字符读取(带缓冲区):
#include <iostream>
int main() {
char buffer[100];
std::cin.get(buffer, 100);
std::cout << "You entered: " << buffer << std::endl;
return 0;
}
get()
不会跳过空白字符,并且不会自动添加\0
结尾。
3. 使用 getline()
函数
用于读取整行输入,直到遇到换行符。
#include <iostream>
int main() {
char buffer[100];
std::cin.get(buffer, 100);
std::cout << "You entered: " << buffer << std::endl;
return 0;
}
或者使用 std::string
版本:
#include <iostream>
#include <string>
int main() {
std::string line;
std::getline(std::cin, line);
std::cout << "You entered: " << line << std::endl;
return 0;
}
getline()
更适合读取包含空格的完整句子。
4. 使用 read()
函数(二进制模式)
用于一次性读取指定数量的字节,常用于二进制文件处理。
char buffer[100];
std::cin.read(buffer, sizeof(buffer));
std::streamsize count = std::cin.gcount();
std::cout.write(buffer, count); // 输出已读取的内容
文件输入流ifstream
std::ifstream
(input file stream)是文件输入流,用于从文件中读取数据,就像 std::cin
用于从标准输入读取一样。
它是 C++ 的 IO 库中的一部分,定义在头文件 <fstream>
中。
#include <fstream>
继承关系如下:
std::ios_base
↑
std::ios
↑
std::istream
↑
std::ifstream
常用构造函数
构造函数形式 | 说明 |
---|---|
ifstream() |
创建未打开任何文件的流对象 |
ifstream(const char* filename) |
打开一个文件用于读取,使用默认模式(文本模式) |
ifstream(const char* filename, ios::openmode mode) |
指定模式打开文件 |
ifstream(const std::string& filename, ios::openmode mode) |
使用 std::string 作为文件名 |
常见打开模式(std::ios::openmode
)
你可以传给 ifstream
构造函数或者 open()
方法的模式:
模式 | 含义 |
---|---|
ios::in |
以读模式打开(默认) |
ios::binary |
以二进制模式打开 |
ios::ate |
打开文件并定位到文件尾 |
ios::app |
追加模式(ifstream 很少用) |
ios::trunc |
清空文件(仅用于 ofstream) |
⚠️ 注意:ifstream
默认自带 ios::in
,所以你不用显式加 unless 和其他模式组合。
常见用法示例
🔸 1. 基本读取一整行
#include <iostream>
#include <fstream>
#include <string>
int main() {
std::ifstream infile("example.txt");
if (!infile.is_open()) {
std::cerr << "Failed to open file.\n";
return 1;
}
std::string line;
while (std::getline(infile, line)) {
std::cout << "Line: " << line << '\n';
}
infile.close();
return 0;
}
🔸 2. 按单词读取
#include <iostream>
#include <fstream>
#include <string>
int main() {
std::ifstream infile("example.txt");
if (!infile.is_open()) {
std::cerr << "Failed to open file.\n";
}
std::string word;
while (infile >> word) {
std::cout << "Word: " << word << '\n';
}
infile.close();
return 0;
}
🔸 3. 按数字读取
#include <iostream>
#include <fstream>
#include <string>
int main() {
try {
std::ifstream infile("/home/fxq/untitled3/example.txt");
if (!infile.is_open()) {
std::cerr << "Failed to open file.\n";
}
int num;
while (infile >> num) {
std::cout << "Read number: " << num << '\n';
}
infile.close();
} catch (const std::runtime_error& e) {
std::cerr << "发生异常: " << e.what() << std::endl;
}
return 0;
}
🔸 4. 用 .open()
延迟打开
std::ifstream infile;
infile.open("data.txt");
if (!infile) {
std::cerr << "Failed to open.\n";
}
缓冲区
C++ 的 std::ifstream
默认是带缓冲的,其底层使用了一个缓冲区(buffer)*来优化文件读取效率。但你可以*自定义缓冲区大小和内存,甚至设置成无缓冲。下面是详细讲解。
默认情况下:ifstream
自带缓冲
ifstream
会使用系统默认的缓冲区(通常是 4KB 或 8KB)。- 当你调用
read()
、getline()
、operator>>
等函数时,数据先进入缓冲区,再从中解析或返回给用户程序。 - 好处是:减少系统调用次数,提高读取性能。
自定义缓冲区:rdbuf()->pubsetbuf()
你可以设置自己的缓冲区或取消缓冲区:
std::ifstream file("example.txt");
char buffer[4096]; // 自定义缓冲区
file.rdbuf()->pubsetbuf(buffer, sizeof(buffer));
pubsetbuf(buffer, size)
:设置缓冲区起始地址和大小- 只能在
open()
之前调用(对已打开文件无效)
设置无缓冲模式(强烈不推荐除非特殊需求)
file.rdbuf()->pubsetbuf(nullptr, 0); // 禁用缓冲
⚠️ 这会让每一次 read()
或 getline()
都变成系统调用,性能会大幅下降,只在需要极低延迟的数据交互中才使用(如串口通信等)。
完整示例:自定义缓冲区读取大文件
#include <iostream>
#include <fstream>
int main() {
// 有问题试一下全路径
std::ifstream file("example.txt");
// 自定义缓冲区
char custom_buffer[8192]; // 8KB
file.rdbuf()->pubsetbuf(custom_buffer, sizeof(custom_buffer));
if (!file) {
std::cerr << "打开文件失败\n";
return 1;
}
std::string line;
size_t count = 0;
while (std::getline(file, line)) {
std::cout << line << std::endl;
++count;
}
std::cout << "共读取行数:" << count << "\n";
file.close();
return 0;
}
推荐场景
场景 | 缓冲设置建议 |
---|---|
大文件(几百 MB 以上) | 自定义大缓冲区(如 64KB) |
小文件 / 一次性读取 | 默认缓冲即可 |
实时读取(如串口) | pubsetbuf(nullptr, 0) |
多线程读取(少见) | 每线程独立缓冲区 |
字符串输入流(istringstream)
用于从字符串中解析数据,常用于字符串拆分和格式转换。
#include <sstream>
#include <iostream>
int main() {
std::string data = "John 25 85.5";
std::istringstream iss(data);
std::string name;
int age;
double score;
iss >> name >> age >> score;
std::cout << name << ", " << age << ", " << score << std::endl;
return 0;
}
总结
方法 | 用途 |
---|---|
>> |
提取数据,自动跳过空白,类型安全 |
get() |
读取字符(含空白) |
getline() |
读取一行(推荐用于整行输入) |
read() |
读取二进制数据 |
gcount() |
获取最后一次读取的字符数 |
clear() |
清除错误状态 |
ignore() |
忽略缓冲区中的无效字符 |
四、输出流
输出流是 C++ I/O 库中的一个核心概念,用于向外部目标输出数据,目标可以是:
- 控制台(标准输出
std::cout
) - 文件(
std::ofstream
) - 字符串(
std::ostringstream
) - 自定义设备(如网络、GUI 等)
std::ios_base
└── std::ios
└── std::ostream // 输出流基类
├── std::ofstream // 文件输出流
├── std::ostringstream // 字符串输出流
└── std::iostream // 同时支持输入输出
常见输出流类型
类型 | 功能 | 头文件 |
---|---|---|
std::ostream |
所有输出流的基类 | <ostream> |
std::cout |
标准输出流(控制台) | <iostream> |
std::cerr |
错误输出流(无缓冲) | <iostream> |
std::clog |
日志输出流(有缓冲) | <iostream> |
std::ofstream |
文件输出流 | <fstream> |
std::ostringstream |
字符串输出流 | <sstream> |
常见输出函数
函数名 | 功能说明 | 示例 |
---|---|---|
operator<< |
格式化输出 | os << x |
put(char) |
输出一个字符 | os.put('A') |
write(char*, size_t) |
输出字节流(通常用于二进制) | os.write(buf, n) |
flush() |
刷新缓冲区,强制输出 | os.flush() |
格式化控制(使用 std::iomanip
)
#include <iostream>
#include <iomanip>
int main() {
double pi = 3.14159265;
std::cout << std::fixed << std::setprecision(2) << pi << std::endl; // 输出 3.14
}
控制符 | 功能 |
---|---|
std::setw(n) |
设置字段宽度 |
std::setfill(c) |
设置填充字符 |
std::setprecision(n) |
设置浮点数精度 |
std::fixed |
固定小数位格式 |
std::scientific |
科学计数法格式 |
std::hex /std::dec /std::oct |
设置整数进制 |
std::left / std::right / std::internal |
对齐方式 |
文件输出流(std::ofstream
)示例
#include <fstream>
int main() {
std::ofstream ofs("output.txt");
if (!ofs) {
std::cerr << "无法打开文件!" << std::endl;
return 1;
}
ofs << "写入文件的第一行" << std::endl;
ofs.close();
}
字符串输出流(std::ostringstream
)
#include <sstream>
#include <iostream>
int main() {
std::ostringstream oss;
oss << "Name: " << "Alice" << ", Age: " << 30;
std::string result = oss.str(); // 获取写入的字符串
std::cout << "字符串流结果: " << result << std::endl;
}
输出流的缓冲刷新机制
- 会自动刷新缓冲区的情况:
std::endl
:输出换行并刷新flush()
手动刷新- 缓冲区满时自动刷新
- 程序正常退出时自动刷新
std::cerr
是无缓冲的
五、重定向流
C++ 中流的重定向(redirect)是指改变标准输入输出流(如 std::cout
、std::cin
)的目标,使它们指向其他设备(如文件、字符串、网络等),而不是默认的控制台(终端)。这对于日志保存、测试、捕获输出都非常有用。
重定向 std::cout
、std::cerr
到文件
方法:修改流缓冲区(streambuf
)
std::cout
等流对象内部都有一个缓冲区指针 rdbuf()
,通过替换它,我们可以让输出写到别处。
代码示例:把 std::cout
重定向到文件
#include <iostream>
#include <fstream>
int main() {
std::ofstream file("example.txt");
if (!file) {
std::cerr << "打开文件失败!" << std::endl;
return 1;
}
// 备份原 cout 缓冲区指针
std::streambuf* old_cout_buf = std::cout.rdbuf();
// 将 cout 缓冲区指向文件流的缓冲区
std::cout.rdbuf(file.rdbuf());
// 现在所有 cout 输出写入文件
std::cout << "这行写入了 example.txt 文件" << std::endl;
// 恢复原来的缓冲区(输出回终端)
std::cout.rdbuf(old_cout_buf);
std::cout << "这行又输出到终端了" << std::endl;
return 0;
}
重定向 std::cin
从文件读取
#include <iostream>
#include <fstream>
int main() {
std::ifstream file("/home/fxq/untitled3/example.txt");
if (!file) {
std::cerr << "打开文件失败!" << std::endl;
return 1;
}
// 备份原 cin 缓冲区指针
std::streambuf* old_cin_buf = std::cin.rdbuf();
// 将 cin 缓冲区指向文件流
std::cin.rdbuf(file.rdbuf());
std::string line;
std::getline(std::cin, line);
std::cout << "从文件读取的内容: " << line << std::endl;
// 恢复原缓冲区
std::cin.rdbuf(old_cin_buf);
return 0;
}
重定向流到字符串(内存)
可以用 std::stringstream
:
#include <iostream>
#include <sstream>
int main() {
std::stringstream ss;
// 重定向 cout 到 stringstream
std::streambuf* old_buf = std::cout.rdbuf(ss.rdbuf());
std::cout << "Hello, 内存流!" << std::endl;
// 恢复 cout
std::cout.rdbuf(old_buf);
// 获取写入的字符串
std::string captured = ss.str();
std::cout << "捕获的字符串内容是: " << captured << std::endl;
return 0;
}
总结重定向关键点
操作步骤 | 说明 |
---|---|
保存旧缓冲区指针 | std::streambuf* old_buf = stream.rdbuf(); |
替换缓冲区指针 | stream.rdbuf(new_stream.rdbuf()); |
使用新的流进行输入输出 | 输出会重定向到新流 |
恢复原缓冲区指针 | stream.rdbuf(old_buf); |
补充:命令行重定向
操作系统层面也支持重定向,不用代码实现,例如:
./program > output.txt # 重定向 stdout
./program < input.txt # 重定向 stdin
./program 2> error.log # 重定向 stderr
未经作者同意请勿转载
本文来自博客园作者:aixueforever,原文链接:https://www.cnblogs.com/aslanvon/p/18959951