C++<第三十六篇>:输入输出流

数据的输入和输出像水流一样将数据从一个地方流到另一个地方,这个过程称为“流”。

(1)C 和 C++ 的 输入输出策略

输入流的数据来源可能是键盘,也有可能是文件。

在 C 中的IO策略如下:

使用 scanf()、gets() 等函数从键盘读取数据,使用 printf()、puts() 等函数向屏幕上输出数据;
使用 fscanf()、fgets() 等函数读取文件中的数据,使用 fprintf()、fputs() 等函数向文件中写入数据;

在 C++ 中的IO策略如下:

C的IO解决方案在C++中也可以使用;
使用 cin 等函数从键盘读取数据,使用 cout 等函数向屏幕上输出数据;
C++ 的文件操作,需要使用流类,使用 ifstream 类读取文件中的数据,使用 ofstream 类向文件中写入数据;
fstream 类既可以读取文件中的数据,也可以将数据写入文件。

输入输出流的派生体系如下:

image.png

这些流类各自的功能分别为:

istream:常用于接收从键盘输入的数据;
ostream:常用于将数据输出到屏幕上;
ifstream:用于读取文件中的数据;
ofstream:用于向文件中写入数据;
iostream:继承自 istream 和 ostream 类,因为该类的功能兼两者于一身,既能用于输入,也能用于输出;
fstream:兼 ifstream 和 ofstream 类功能于一身,既能读取文件中的数据,又能向文件中写入数据。

C++ 常用的两个对象 cincout,它们定义在<iostream>库中,属于 C++ 的内置对象。
它们常用的用法是:

int n;
cout << "请输入数字:";
cin >> n;
cout << "输入数字为:" << n << endl;

由于 cincout 不是关键字,而是对象,所以可以调用对象的成员函数。

cin 的成员函数有:

成员方法名 功能
getline(str,n,ch) 从输入流中接收 n-1 个字符给 str 变量,当遇到指定 ch 字符时会停止读取,默认情况下 ch 为 '\0'。
get() 从输入流中读取一个字符,同时该字符会从输入流中消失。
gcount() 返回上次从输入流提取出的字符个数,该函数常和 get()、getline()、ignore()、peek()、read()、readsome()、putback() 和 unget() 联用。
peek() 返回输入流中的第一个字符,但并不是提取该字符。
putback(c) 将字符 c 置入输入流(缓冲区)。
ignore(n,ch) 从输入流中逐个提取字符,但提取出的字符被忽略,不被使用,直至提取出 n 个字符,或者当前读取的字符为 ch。
operator>> 重载 >> 运算符,用于读取指定类型的数据,并返回输入流对象本身。

cout 的成员函数有:

成员方法名 功能
put() 输出单个字符。
write() 输出指定的字符串。
tellp() 用于获取当前输出流指针的位置。
seekp() 设置输出流指针的位置。
flush() 刷新输出流缓冲区。
operator<< 重载 << 运算符,使其用于输出其后指定类型的数据。

另外,cerrclog,这两个对象也是 C++ 的内置对象,且也是输出对象,和 cout 的用法一致,包括成员函数也一致。
它们和 cout 的区别是:

(1)cout 除了可以将数据输出到屏幕上,通过重定向,还可以实现将数据输出到指定文件中;
     而 cerr 和 clog 都不支持重定向,它们只能将数据输出到屏幕上;
(2)cout 和 clog 都有缓冲区,即它们在输出数据时,会先将要数据放到缓冲区,等缓冲区满或者手动换行(使用换行符 '\n' 或者 endl)时,才会将数据全部显示到屏幕上;
     而 cerr 则没有缓冲区,它会直接将数据输出到屏幕上。
(2)输出字符串

使用 cout 对象可以输出字符串, cout 是 ostream 对象,调用 put 函数可以输出单个字符,代码如下:

cout.put('A');

put 函数的返回值是 ostream,所以如果想要输出多个字母,可以这样写:

cout.put('A').put('B').put('C');

那么,如何输出一个字符串呢?

调用 write 函数即可实现,代码如下:

cout.write(str, 1);

write 有两个参数,第一个参数是将要输出的字符串,第二个参数是输出字符串前多少个字符。

write 函数的返回值是 ostream,所以可以连续调用:

cout.write(str, 1).write(str, 2);
(3)输出当前输出流指针位置

调用输出流 tellp 方法可以获取当前输出流的指针位置,一般用于文件操作:

//文件输出流对象
ofstream outfile;
outfile.open("test.txt"); // 打开文件,如果文件不存在,则新建
const char* str = "zhangsan";
for (int i = 0; i < strlen(str); i++) {
    outfile.put(str[i]); // 输出一个字符到文件中
    long pos = outfile.tellp(); // 获取当前输出流指针位置
    cout << pos << " ";
}
cout << endl;
outfile.close(); // 关闭流

输出结果是:

1 2 3 4 5 6 7 8
(4)输出流指针位置的跳跃

一般情况下,输出1的字符,输出流指针位置自动+1,输出n个字符,输出流指针位置自动+n。

使用输出流的 seekp 函数可以实现输出流指针的跳跃。

seekp 函数用于指定下一个进入输出缓冲区的字符所在的位置,演示代码如下:

//文件输出流对象
ofstream outfile;
outfile.open("test.txt"); // 打开文件,如果文件不存在,则新建
const char* str = "zhangsan";
for (int i = 0; i < strlen(str); i++) {
    outfile.put(str[i]); // 输出一个字符到文件中
    if (i == 2) 
    {
        outfile.seekp(10);
    }
}

test.txt文件中的内容是:

image.png

可以配合 tellp 函数打印当前指针位置,代码如下:

//文件输出流对象
ofstream outfile;
outfile.open("test.txt"); // 打开文件,如果文件不存在,则新建
const char* str = "zhangsan";
for (int i = 0; i < strlen(str); i++) {
    outfile.put(str[i]); // 输出一个字符到文件中
}
cout << "当前输出流指针位置:" << outfile.tellp() << endl;

outfile.seekp(4); // 将输出流指针位置改成4,下次输出从4开始

cout << "当前输出流指针位置:" << outfile.tellp() << endl;

outfile.seekp(40); // 将输出流指针位置改成4

cout << "当前输出流指针位置:" << outfile.tellp() << endl;

outfile.close(); // 关闭流

输出结果是:

当前输出流指针位置:8
当前输出流指针位置:4
当前输出流指针位置:40

seekp 还有一个带有两个形参的函数,用法如下:

//文件输出流对象
ofstream outfile;
outfile.open("test.txt"); // 打开文件,如果文件不存在,则新建
const char* str = "zhangsanlisi";

for (int i = 0; i < strlen(str); i++) {
    outfile.put(str[i]); // 输出一个字符到文件中
}

cout << "当前输出流指针位置:" << outfile.tellp() << endl;

outfile.seekp(4, ios::cur); // 从当前位置向正方向偏移4个单位

cout << "当前输出流指针位置:" << outfile.tellp() << endl;

outfile.seekp(-4, ios::end); // 从结尾位置向负方向偏移4个单位

cout << "当前输出流指针位置:" << outfile.tellp() << endl;

outfile.seekp(4, ios::beg); // 从起始位置向正方向偏移4个单位

cout << "当前输出流指针位置:" << outfile.tellp() << endl;

outfile.close(); // 关闭流

seekp 的第一个参数是偏移量,可以是正整数,也可以是负整数,正负表示偏移的方向。
第二个参数是从什么位置偏移,可以接受的值有三个:

ios::beg   从起始位置偏移
ios::cur   从当前位置偏移
ios::end   从结尾开始偏移

以上代码最终输出结果是:

当前输出流指针位置:12
当前输出流指针位置:16
当前输出流指针位置:8
当前输出流指针位置:4
(5)cout格式化输出

ostream 类可实现格式化输出,ostream 格式化相关的成员函数有:

成员函数 说明
flags(fmtfl) 当前格式状态全部替换为 fmtfl。注意,fmtfl 可以表示一种格式,也可以表示多种格式。
precision(n) 设置输出浮点数的精度为 n。
width(w) 指定输出宽度为 w 个字符。
fill(c) 在指定输出宽度的情况下,输出的宽度不足时用字符 c 填充(默认情况是用空格填充)。
setf(fmtfl, mask) 在当前格式的基础上,追加 fmtfl 格式,并删除 mask 格式。其中,mask 参数可以省略。
unsetf(mask) 在当前格式的基础上,删除 mask 格式。

其中 fmtfl 和 mask 的可选值有:

标 志 作 用
ios::boolapha 把 true 和 false 输出为字符串
ios::left 输出数据在本域宽范围内向左对齐
ios::right 输出数据在本域宽范围内向右对齐
ios::internal 数值的符号位在域宽内左对齐,数值右对齐,中间由填充字符填充
ios::dec 设置整数的基数为 10
ios::oct 设置整数的基数为 8
ios::hex 设置整数的基数为 16
ios::showbase 强制输出整数的基数(八进制数以 0 开头,十六进制数以 0x 打头)
ios::showpoint 强制输出浮点数的小点和尾数 0
ios::uppercase 在以科学记数法格式 E 和以十六进制输出字母时以大写表示
ios::showpos 对正数显示“+”号
ios::scientific 浮点数以科学记数法格式输出
ios::fixed 浮点数以定点格式(小数形式)输出
ios::unitbuf 每次输出之后刷新所有的流

下面是一些举例说明:

【flags(fmtfl)】 当前格式状态全部替换为 fmtfl

bool a = true;
cout << a << endl;

bool类型本质上是一个整数,当a=true时,打印显示为1,当a=false时,打印显示为0。

如果想要打印显示为:true 或者 false,那么就需要输出格式化。

bool a = true;
cout.flags(ios::boolalpha);
cout << a << endl;

cout 对象的 flags 函数传入的输出格式为:ios::boolalpha(把 true 和 false 输出为字符串)。

【precision(n)】 设置输出浮点数的精度为 n

float a = 1.1f;
double b = a / 3;
cout << b << endl;

以上代码的打印结果是:

0.366667

如果要求只保留两位小数,那么需要将输出结果格式化:

float a = 1.1f;
double b = a / 3;
cout.precision(2);
cout << b << endl;

输出结果是:

0.37

【width(w)】 指定输出宽度为 w 个字符

cout.width(20);
cout << "zhangsan" << endl;

以上代码的输出结果是:

        zhangsan

输出宽度为20,默认右对齐。

对齐方式分为:左对齐和右对齐,可以设置flag保证其对其方式:

左对齐:

cout.flags(ios::left);

右对齐:

cout.flags(ios::right);

【正数符号的输出】

有关负数和正数的输出,负数的输出都是带有 - 号的,正数的输出默认不带 + 号,如果想要输出正数的符号,就需要对正数的输出进行格式化:

cout.flags(ios::showpos);
cout << 100 << endl;

输出结果是:

+100

结合 ios::internal 使用,将数值的符号位在域宽内左对齐,数值右对齐,中间由填充字符填充:

cout.width(20);
cout.flags(ios::showpos | ios::internal);
cout << 100 << endl;

输出结果是:

+                100

【fill(c)】 在指定输出宽度的情况下,输出的宽度不足时用字符 c 填充(默认情况是用空格填充)

cout.width(20);
cout << "zhangsan" << endl;

以上代码的输出结果是:

        zhangsan

输出结果前面默认用空格填充,相当于:

cout.width(20);
cout.fill();
cout << "zhangsan" << endl;

但是,如果想要将左边的空格用其它字符填充,需要调用 fill 函数填充字符:

cout.width(20);
cout.fill('#');
cout << "zhangsan" << endl;

输出结果是:

############zhangsan

【进制输出】

cout 有关进制的输出格式有:

ios::dec:设置整数的基数为 10
ios::oct:设置整数的基数为 8
ios::hex:设置整数的基数为 16

代码如下:

int a = 177;

cout.flags(ios::dec); // 10进制输出
cout << a << endl;

cout.flags(ios::oct); // 8进制输出
cout << a << endl;

cout.flags(ios::hex); // 16进制输出
cout << a << endl;

以上代码的输出结果为:

177
261
b1

其中,8进制和16进制的输出结果很奇怪,它们并不是很标准化(8进制应该以0开头,16进制应该以0x开头),使用输出格式:ios::showbase 可以让进制数显示的更加标准,代码如下:

int a = 177;

cout.flags(ios::dec); // 10进制输出
cout << a << endl;

cout.flags(ios::oct | ios::showbase); // 8进制输出
cout << a << endl;

cout.flags(ios::hex | ios::showbase); // 16进制输出
cout << a << endl;

输出结果是:

177
0261
0xb1

【unsetf(mask)】 在当前格式的基础上,删除 mask 格式

cout.width(20);
cout.flags(ios::showpos | ios::internal);
cout << 100 << endl;

以上代码的输出结果是:

+                100

ios::showpos:表示正数的符号输出
ios::internal:表示符号左对齐,数值右对齐

如果想要去掉 ios::internal,就需要调用 unsetf 函数:

cout.width(20);
cout.flags(ios::showpos | ios::internal);
cout.unsetf(ios::internal);
cout << 100 << endl;

此时的输出结果是:

                +100

【setf(fmtfl, mask)】 在当前格式的基础上,追加 fmtfl 格式,并删除 mask 格式。其中,mask 参数可以省略

如果想要添加某输出格式,可以使用 setf 函数:

cout.width(20);
cout.flags(ios::showpos);
cout.setf(ios::internal);
cout << 100 << endl;

以上代码 setf 只有一个参数,它可以带有两个参数,但是目前的测试结果是有问题的。

【强制输出浮点数的小点和尾数 0】

使用输出格式 ios::showpoint 可以强制输出浮点数的小点和尾数 0:

float a = 1;
cout.flags(ios::showpoint);
cout << a << endl;

输出结果是:

1.00000

【科学计数法格式输出】

使用输出格式:ios::scientific 可以将浮点数以科学记数法格式输出,代码如下:

float a = 1.11f;
cout.flags(ios::scientific);
cout << a << endl;

输出结果是:

1.110000e+00

如果想恢复成小数输出,可以重新执行小数输出格式:ios::fixed,代码如下:

float a = 1.11f;
cout.flags(ios::scientific);
cout << a << endl;
cout.flags(ios::fixed);
cout << a << endl;

输出格式为:

1.110000e+00
1.110000

【大写格式输出】

使用输出格式:ios::uppercase 可以实现大写格式输出,但是只适用于科学计数法和16进制,演示代码如下:

float a = 1.11f;
cout.flags(ios::scientific | ios::uppercase); // 科学计数法小写字母转大写字母
cout << a << endl;

int b = 177;
cout.flags(ios::hex | ios::showbase | ios::uppercase); // 16进制的小写字母转大写字母
cout << b << endl;

输出结果是:

1.110000E+00
0XB1
(6)使用流操纵算子格式化输出

<iomanip> 头文件中定义的一些常用的格式控制符,它们都可用于格式化输出:

流操纵算子 作 用 是否常用
dec(默认) 以十进制形式输出整数 常用
hex 以十六进制形式输出整数 常用
oct 以八进制形式输出整数 常用
fixed 以普通小数形式输出浮点数 常用
scientific 以科学计数法形式输出浮点数 常用
left 左对齐,即在宽度不足时将填充字符添加到右边 常用
right(默认) 右对齐,即在宽度不足时将填充字符添加到左边 常用
setbase(b) 设置输出整数时的进制,b=8、10 或 16 常用
setw(w) 指定输出宽度为 w 个字符,或输入字符串时读入 w 个字符。注意,该函数所起的作用是一次性的,即只影响下一次 cout 输出。 常用
setfill(c) 在指定输出宽度的情况下,输出的宽度不足时用字符 c 填充(默认情况是用空格填充) 常用
setprecision(n) 设置输出浮点数的精度为 n。
在使用非 fixed 且非 scientific 方式输出的情况下,n 即为有效数字最多的位数,如果有效数字位数超过 n,则小数部分四舍五人,或自动变为科学计 数法输出并保留一共 n 位有效数字。
在使用 fixed 方式和 scientific 方式输出的情况下,n 是小数点后面应保留的位数。
常用
setiosflags(mask) 在当前格式状态下,追加 mask 格式,mask 参数可选择表 2 中的所有值。 常用
resetiosflags(mask) 在当前格式状态下,删除 mask 格式,mask 参数可选择表 2 中的所有值。 常用
boolapha 把 true 和 false 输出为字符串 不常用
noboolalpha(默认) 把 true 和 false 输出为 0、1 不常用
showbase 输出表示数值的进制的前缀 不常用
noshowbase (默认) 不输出表示数值的进制.的前缀 不常用
showpoint 总是输出小数点 不常用
noshowpoint(默认) 只有当小数部分存在时才显示小数点 不常用
showpos 在非负数值中显示 + 不常用
noshowpos(默认) 在非负数值中不显示 + 不常用
uppercase 十六进制数中使用 A~E。若输出前缀,则前缀输出 0X,科学计数法中输出 E 不常用
nouppercase (默认) 十六进制数中使用 a~e。若输出前缀,则前缀输出 0x,科学计数法中输出 e。 不常用
internal 数值的符号(正负号)在指定宽度内左对齐,数值右对 齐,中间由填充字符填充。 不常用

其用法可以举例说明:

int a = 177;
cout << hex << a << endl; // 16进制格式输出
cout << setbase(8) << a << endl; // 8进制格式输出

float b = 1.11f;
cout << scientific << b << endl; // 科学计数法格式输出

float c = 1.110000e+00;
cout << fixed << c << endl; // 小数格式输出

string e = "zhangsan";
cout << setw(20) << left << setfill('C') << e << endl; // 宽度为20,左对齐,用字符C填充

cout << setprecision(2) << 1.1111 << endl; // 设置浮点数的精度,保留2位小数

float f = 1.11f;
cout << setiosflags(ios::scientific) << f << endl; // 在当前格式状态下,追加 mask 格式
cout << resetiosflags(ios::scientific) << f << endl; // 在当前格式状态下,删除 mask 格式

输出结果是:

b1
261
1.110000e+00
1.110000
zhangsanCCCCCCCCCCCC
1.11
0x1.1c28f60000000p+0
1.11
(7)输入输出重定向

在默认情况下,cin 只能接收从键盘输入的数据,cout 也只能将数据输出到屏幕上。但通过重定向,cin 可以将指定文件作为输入源,即接收文件中早已准备好的数据,同样 cout 可以将原本要输出到屏幕上的数据转而写到指定文件中。

C++ 中,实现重定向的常用方式有 3 种,分别是:

(1)freopen()函数实现重定向

    FILE* file = new FILE();
    string lineStr;

    freopen_s(&file, "in.txt", "r", stdin); // 将标准输入流重定向到 in.txt 文件
    freopen_s(&file, "out.txt", "w", stdout); // 将标准输出重定向到 out.txt文件
    while (cin >> lineStr) 
    {
        cout << lineStr << endl;
    }
    fclose(stdout);
    fclose(stdin);

此时,cin 和 cout 被重定向为文件的输入输出,那么怎么恢复到控制台输出呢?

恢复到控制台输出:
    freopen_s(&file, "CON", "w", stdout); 
恢复到控制台输入(目测无效):
    freopen_s(&file, "CON", "r", stdin);

(2)rdbuf()函数实现重定向

    ifstream fin("in.txt"); //打开 in.txt 文件,等待读取
    ofstream fout("out.txt"); // 打开 out.txt 文件,等待写入
    streambuf* oldcin;
    streambuf* oldcout;
    string linStr;
    oldcin = cin.rdbuf(fin.rdbuf()); // 用 rdbuf() 重新定向
    oldcout = cout.rdbuf(fout.rdbuf()); //用 rdbuf() 重新定向
    while (cin >> linStr) // // 从input.txt文件读入,并写入 out.txt
    {
        cout << linStr << endl;
    }
    cin.rdbuf(oldcin); // 恢复键盘输入
    cout.rdbuf(oldcout); //恢复屏幕输出

    fin.close();
    fout.close();

(3)通过控制台实现重定向

cmd打开控制台,输入:xxx.exe,执行应用的可执行文件"xxx.exe"可以直接运行代码,但是这样无法做到 cin 和 cout 的重定向;
如果输入:xxx.exe <in.txt >out.txt 的话,可以做到重定向;

<in.txt:将 cin 从控制台输入重定向到文件(in.txt)输入,当前目录要确保存在文件 in.txt;
>out.txt:将 cout 从控制台输出重定向到文件(out.txt)输出。

编辑代码:

string lineStr;
while(cin >> lineStr)
    cout << lineStr << endl;

在控制台输入:xxx.exe <in.txt >out.txt 

此时发现,in.txt 中的文本已经写到out.txt文件中。
(8)如何忽视输入的指定字符
int n;
cin.ignore();
cin >> n;
cout << n;

cin 的 ignore 函数可以实现忽视输入的字符,以上代码中 ignore() 忽视一个字符,假如输入:

12345

那么输出结果是:

2345

ignore 函数还可以含有一个形式参数

istream & ignore(int n =1);

表示跳过几个字符。

ignore 函数还可以含有两个形式参数:

istream & ignore(int n =1, int delim = EOF);

此函数的作用是跳过输入流中的 n 个字符,或跳过 delim 及其之前的所有字符,哪个条件先满足就按哪个执行。
两个参数都有默认值,因此 cin.ignore() 就等效于 cin.ignore(1, EOF), 即跳过一个字符。

两个参数的举例代码:

int n;
cin.ignore(5, '1');
cin >> n;
cout << n;

输入和输出结果是:

ABC123
23
(9)查看输入流下一个字符

cin 的 peek() 函数可以查看输入流下一个字符。

[本章完...]

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容