ClickHouse C++代码书写规范 如何书写C++

如何编写代码++的C 

概述

本文仅供参考。

如果您正在编辑代码,那么编写有章法的代码是很有意义的。

您的书写风格是需要与现有代码保持一致的。

更简单的理解代码(更方便)需要代码具有统一性。而且,统一的代码使搜索更加容易。

本文之中诸多规则非也合理,往往是依例而为。

格式化

您写的代码将被自动执行clang-format。

缩进 - 4个空格。配置开发环境,以便该选项卡添加四个空格。

新的一行上书写大括号。(括号结尾亦如此)

inline void readBoolText (bool &x ,ReadBuffer &buf ){ 

    char tmp = '0' ;

    readChar (tmp ,buf ); 

    x = tmp != '0' ; 

}

但是,如果函数的全部足够短(一个语句) - 如果需要,它可以完全放在一行上。在这种情况下,在花括号的周围放置空白(除了行尾的空格)。

inline size_t mask ()                            const {   return buf_size ()- 1 ; } 

inline size_t place (HashValue x )       const {    return x &mask ();   }

对于功能来说,圆括号不会有空格。

void reinsert (const Value &x )

memcpy (&buf [ place_value ],&x ,sizeof (x ));


当使用if,for,while,...(而不是函数调用)时,在左括号之前放置一个空格。

for (为size_t 我= 0 ; 我< 行; 我+ = 存储。index_granularity )


围绕二元运算符(+, - ,*,/,%,...)以及三元运算符?:空间被放置。

UINT16 年= (小号[ 0 ] - '0' )* 1000 + (小号[ 1 ] - '0' )* 100 + (小号[ 2 ] - '0' )* 10 + (小号[ 3 ] - “0 ' ); UInt8 月= (s [ 5 ] - '0' )* 10 + (s [ 6 ] - '0' ); UInt8 day = (s [ 8 ] - '0' )* 10 + (s [ 9 ] - '0' );


如果您张贴一行,则该语句将写入一个新行,并且缩进之前递增。

中频(elapsed_ns )消息<< “(” << rows_read_on_server * 10亿/ elapsed_ns << “行/秒,。” << bytes_read_on_server * 1000.0 / elapsed_ns << “MB立即下载/秒)。” ;


在行内,如果需要,可以用空格对齐。

dst 。ClickLogID = 点击。LogID ; dst 。ClickEventID = 点击。EventID ; dst 。ClickGoodEvent = 点击。GoodEvent ;


在Carrier周围  .,->没有空格。

如有必要,可以将Carrier转移到新行。在这种情况下,空格在它之前增加。


一元运算符(,...)不能与空间分隔。--, ++, *, &

在逗号之后放置一个空格,之前 - 不存在。for语句中的分号类似。

操作员[]不能被空格分开。

在该表达式中,间,和前面有一个空格; 之后和之前-未分配。template <...>template<<>

template < typename TKey ,typename TValue > struct AggregatedStatElement {}


在类和结构中,公共的,私有的,被保护的被写在与类/结构相同的层次上,而其他所有的内部都是更深的。

template < typename T > class MultiVersion { 

public :///使用对象的版本。

    shared_ptr 管理版本的生命周期。

    using Version = std :: shared_ptr < const T > ;

     ... 

}

如果整个文件上有一个命名空间,除此之外没有任何必要,那么名称空间内的缩进就不需要了。

如果表达式if,for,while ...的块由一个语句组成,则花括号不需要写入。相反,把语句放在一个单独的行上。这个语句也可以嵌套,如果for,while ...但是如果inner语句包含大括号或者其他的,那么外部的块应该被写在大括号中。

///完成写入。for (auto &stream :streams )流。第二个- > finalize ();

行尾应该没有空格。

以UTF-8编码的源代码。

在字符串文字中,您可以使用非ASCII。

<< “” << (计时器。消逝()/ chunks_stats 。点击数)<< “微秒/命中”。;

不要在一行中写入多个表达式。

在函数内部,对代码段进行分组,使用不超过一个空行分隔它们。

功能,类别等至少由一个,至多两个空行相互隔开。

const(引用该值)被写入类型名称。

// 正确的const char * pos const std :: string &s // 不正确的char const * pos

当你声明一个指针或引用时,*和&字符被两边的空格分开。

// 正确的const char * pos // 不正确的const char * pos const char * pos

当使用模板类型时,写using(除了最简单的情况)。

也就是说,模板参数仅在using代码中指定,然后在代码中不重复。

using 可以在本地声明,例如在一个函数内。

// 正确使用FileStreams = std :: map < std :: string ,std :: shared_ptr < Stream >> ; FileStreams 流; // 不正确的std :: map < std :: string ,std :: shared_ptr < Stream > 流;

你不能在同一个声明中声明多个不同类型的变量。

// true int x ,* y ;

C风格演员没有使用。

//不正确的std :: cerr << (int )c << ; std :: endl ; //正确的std :: cerr << static_cast < int > (c )<< std :: endl ;

在课程和结构中,分别在每个范围内分别分组和分组。

对于不是很大的类/结构,您不能将方法声明与实现分开。

类似于任何类/结构中的小方法。

对于模板类/结构,最好不要将方法声明与实现分开(否则它们必须在同一个翻译单元中定义)。

代码宽度不能超过80个字符。有可能在140。

如果不需要postfix,请始终使用前缀递增/递减。

对于(姓名:: 为const_iterator IT = COLUMN_NAMES 。在开始(); IT != COLUMN_NAMES 。结束(); ++ IT )

评论(注释)

有必要在所有需要解释的地方写评论。

这非常重要。我们不应当为他人创造阅读代码的多余工作,而应当简便的介绍代码的内容

/ **一部分内存,可以使用。  * 例如,如果internal_buffer为1MB ,并且有唯一的图10中,加载到缓冲“从文件中的字节进行读取,  *当时working_buffer将具有仅为10字节大小床  *(working_buffer.end()点位置将可用的那些10个字节之后的阅读)。  * /

注释可以尽可能详细。

注释被写入适当的代码。在极少数情况下 - 可以放置于同一行之后。

/** Parses and executes the query.*/

void executeQuery( ReadBuffer & istr,  /// Where to read the query from (and data for INSERT, if                                     applicable)WriteBuffer & ostr,/// Where to write the result

                               Context  & context,/// DB, tables, data types, engines, functions, aggregate                                      functions...BlockInputStreamPtr & query_plan,///executedQueryProcessingStage::Enum

                               stage=QueryProcessingStage::Complete/// Up to which stage process the SELECT query)

注释只能用英文书写。

编写一个库时,请在最重要的头文件中发布一个关于它是什么的详细注释。

你不能写评论,不提供额外的信息。特别是,你不能写这样的空的评论:

/ * *过程如何名称:*原程序名称:*作者:*创造条件的日期:*修改的日期:*修改作者:*原始文件名:*用途:*的意图:*的名称:*在类中使用:*常量:*局部变量:*参数:*创建日期:*目的:* /

(实施例从资源采取http://home.tamk.fi/~jaalto/course/coding-style/doc/unmaintainable-code/

您不能在每个文件的开头写入垃圾评论(作者,创建日期...)。

单行注释以三个斜杠开始:///多行与/**。这样的评论被认为是“记录”。

注意:这些注释可用于使用Doxygen生成文档。但是,实际上,Doxygen并没有被使用,因为使用IDE功能导航代码要方便得多。

在多行注释的开始和结尾,不应该有空行(除了多行注释关闭的行外)。

对于注释的代码块,通常不使用“记录”注释。

在提交之前删除注释的代码块。

不要在评论或代码中写粗话。

不要用大写字母写。不要使用不必要的标点符号。

///WHAT A FALIURE

不要在评论中撰写分隔线。

/// *********************************************** *******

在评注中写下对话是没有必要的(最好口头说)。

///你为什么要做这个东西?

不要在块的末尾写下关于这个块的评论

/// for

名称

变量和类成员的名称是带有下划线的小写字母。

size_t max_block_size ;

函数(方法)的名字是用小写字母camelCase。

std :: string getName ()const override { return “Memory” ; }

类(结构)的名称是大写字母的CamelCase。不使用接口以外的前缀。

类StorageMemory :公共IStorage

使用的名称以及类,或者您可以在最后添加_t。

类型名称 - 模板参数:在简单情况下 - T; T,U; T1,T2。

在更复杂的情况下 - 无论是类名,还是可以将字母T添加到开头。

template < typename TKey ,typename TValue > struct AggregatedStatElement

常量的名称 - 模板的参数:或者变量的名称,或者简单情况下的N。

template < bool without_www > struct ExtractDomain

对于抽象类(接口),可以将字母I添加到名称的开头。

类IBlockInputStream

如果变量在本地使用,那么可以使用简称。

在其他情况下 - 使用描述含义的相当详细的名称。

bool info_successfully_loaded = false ;

定义-s - 带下划线的ALL_CAPS。全局常量 - 也是。

#define MAX_SRC_TABLE_NAMES_TO_STORE 1000

具有代码的文件的名称根据其中的内容根据样式命名。

如果在文件中有一个类 - 在CamelCase中将该文件命名为一个类。

如果文件中有一个函数 - 将该文件命名为函数 - 在camelCase中。

如果名称包含缩写,则:

对于变量名,所有的缩写都是用小写字母mysql_connection(不mySQL_connection)。

对于类和函数的名字,大的字母保留在缩写MySQLConnection(not MySqlConnection)中。

应立即使用用于初始化类的相应成员的构造函数的参数以及类的成员,并在末尾添加下划线。

FileQueueProcessor (const的在std :: :字符串和path_ ,常量在std :: :字符串和prefix_ ,在std :: shared_ptr的< 文件处理器> handler_ ):路径(path_ ),前缀(prefix_ ),处理器(handler_ ),日志(&记录器:: 的get (“FileQueueProcessor” )){ }

您也可以像构造函数的成员一样调用构造函数的参数(不要添加下划线),但只有在构造函数的主体中不使用此参数时才可以。

命名局部变量和类成员没有区别(不需要前缀)。

定时器(不是m_timer )

enum-e中的常量 - 使用大写字母的CamelCase。ALL_CAPS也是可以接受的。如果枚举不在本地,则使用枚举类。

枚举类CompressionMethod { QuickLZ = 0 ,LZ4 = 1 ,};

所有的名字都是英文的。俄语字符、汉语字符、日语字符等不得使用。

不要使用Stroka 

名称中的缩写(来自不同单词的几个字母)只有在被普遍接受的情况下才可以使用(如果缩短可以在英文维基百科中进行解码或进行搜索查询): AST,SQL。

No NVDH

只有在广泛使用这种缩减的情况下,才能使用缩略语的缩略语。

但是,如果缩写的代码全称就在注释附近出现过,也可以使用缩写。

C ++源文件名只能有.cpp扩展名。头文件 - 只有.h。

如何编写代码

内存管理。

手动删除内存(删除)只能在库代码中使用。

而在库代码中,删除操作符只能在析构函数中使用。

在应用程序代码中,应该完成内存被拥有它的某个对象释放。

例子:

将对象放在堆栈上,或者使其成为另一个类的成员是最简单的。

对于大量的小物件,使用容器。

要自动释放堆中分配的少量对象,请使用shared_ptr / unique_ptr。

资源管理。

使用RAII并参见上面的段落。

错误处理。

使用例外。在大多数情况下,您只需要抛出异常,而不需要捕捉(因为RAII)。

在离线数据处理程序中,通常你不能捕捉异常。

在处理自定义请求的服务器中,通常在连接处理程序的顶部捕获异常就足够了。

在线程函数中,你应该捕获并记住所有异常,在加入之后将它们抛入主线程中。

///如果还没有计算,如果(!Started ){ calculate (); 开始= 真; } else ///如果计算已经在运行,请等待池的结果。wait (); 如果(例外)异常- > rethrow ();

不要“不分青红皂白地”吞食“例外。在任何情况下,都不要将所有例外不加区分地转换成日志中的消息。

不要。catch (...) {}

如果您需要忽略一些例外情况,那么只需要忽略特定的例外情况,其余部分则会退回。

捕捉(const的DB :: 异常&ë ){ 如果(Ë 。代码()== ErrorCode的:: UNKNOWN_AGGREGATE_FUNCTION )返回nullptr ; 否则扔; }

当使用使用返回码或errno的函数时,检查结果并抛出异常。

if (0 != close (fd ))throwFromErrno (“Can not close file” + file_name ,ErrorCodes :: CANNOT_CLOSE_FILE );

断言不被使用。

异常类型。

在应用程序代码中,您不需要使用复杂的异常层次结构。希望系统管理员能够理解例外文本。

从析构函数中出现的异常。不建议使用,但可以接受。

使用以下选项:

做一个函数(done()或finalize()),它可以让你事先执行所有的工作,在这个过程中可能会发生异常。如果这个函数被调用,那么在析构函数中不应该有任何异常。

过于复杂的工作(例如通过网络发送数据)在析构函数中根本无法完成,期望用户提前调用该方法来完成工作。

如果在析构函数中发生异常,建议不要“吞食”它,而是将信息输出到日志(如果在此处有日志记录器)。

在简单的程序,如果相关的异常没有被捕获,并导致与在日志中记录信息的工作完成后,你可以不用担心从析构函数发出的异常,因为调用的std ::终止(在默认noexcept在C ++ 11的情况下)是处理异常的可接受的方式。

独立的代码块。

在一个函数内部,可以创建一个单独的代码块,以便在其中创建一些局部变量,并在退出块时调用相应的析构函数。

块块= 数据。in - > read (); { std :: lock_guard < std :: mutex > lock (mutex ); 数据。ready = true ; 数据。block = block ; } Ready_any 。set ();

多线程。

在离线数据处理程序中:

首先,在单个处理器内核上实现或多或少的最大性能,然后您可以并行化代码,但只在必要的时候使用。

在服务器程序中:

使用线程池来处理请求。目前,我们没有任何需要使用用户空间上下文切换的任务。

叉不用于并行化。

流的同步。

通常,你可以单独的流数据写入到不同的存储位置(更好 - 在不同的缓存行),并且不使用流同步(除joinAll)。

如果需要同步,在大多数情况下,使用lock_guard下的互斥锁就足够了。

在其他情况下,使用系统同步原语。不要用忙等待。

原子操作只能用于最简单的情况。

除非你是专家,否则你不需要自己写一个无锁的数据结构。

参考和指标。

在大多数情况下,更喜欢链接。

常量。

可以使用常量引用,常量指针,const_iterator,常量方法。

考虑到const是“缺省”写入的变体,只有必要时才需要const的缺失。

对于通过值传递的变量,const通常是没有意义的。

无符号。

如果需要,使用无符号。

数字类型。

使用类型UInt8,UInt16,UInt32,UInt64,Int8,Int16,Int32,Int64以及size_t,ssize_t,ptrdiff_t。

不要使用signed / unsigned long,long long,short; signed char,unsigned char和char。

传递参数。

复合值是通过引用传递的(包括std :: string)。

如果该函数捕获在堆上创建的对象的所有权,则使参数类型为shared_ptr或unique_ptr。

返回值。

在大多数情况下,只需返回值。不要写[return std :: move(res)] {。Strike}。

如果在函数内部在堆上创建一个对象并发出,则返回shared_ptr或unique_ptr。

在少数情况下,可能需要通过函数参数返回一个值。在这种情况下,参数是一个链接。

使用AggregateFunctionPtr = std :: shared_ptr < IAggregateFunction > ; / **允许您通过名称创建一个聚合函数。  * / class AggregateFunctionFactory { public :AggregateFunctionFactory (); AggregateFunctionPtr get (const String &name ,const DataTypes &argument_types )const ;

命名空间。

对于应用程序代码,您不需要使用单独的名称空间。

对于小型图书馆 - 不需要。

对于不是很小的图书馆 - 把所有的东西放在名字空间里

在.h文件库中,可以使用名称空间详细信息来获取应用程序代码不需要的实现细节。

在.cpp文件中,可以使用静态或匿名命名空间来隐藏字符。

此外,命名空间可以用于枚举,以便相应的名称不会进入外部名称空间(但最好使用枚举类)。

延迟初始化。

通常,如果您需要参数来初始化,请不要为了逻辑而编写构造函数。

如果那么你需要延迟初始化,那么你可以添加一个默认的构造函数(这将创建一个不正确的状态的对象)。或者,对于少数对象,可以使用shared_ptr / unique_ptr。

Loader (DB :: Connection * connection_ ,const std :: string &query ,size_t max_block_size_ ); ///用于延迟初始化Loader (){}

虚拟功能。

如果这个类不是为多态使用而设计的,那么你就不需要使这个函数虚拟化。这也适用于析构函数。

编码。

UTF-8被广泛使用。使用std::string,。未使用,。char *std::wstringwchar_t

日志记录。

查看代码中的所有示例。

在提交之前,删除所有无意义和调试日志,以及其他类型的调试输出。

内部循环的每个迭代都不应该有日志记录,即使是跟踪级别也是如此。

在任何级别的日志记录中,日志都应该可以读取。

日志记录应该主要用在应用程序代码中。

日志中的信息应该用英文书写。

系统管理员可以理解日志。

不需要在日志中写下脏话。

日志使用UTF-8编码。偶尔,日志中可以使用非ASCII字符。

I / O.

在内部循环(在程序的关键部分),你不能使用iostreams(包括,绝不使用stringstream)。

而是使用DB / IO库。

日期和时间。

查看DateLUT库。

Include

头文件仅用于,包括guard-s不需要写。#pragma once

Using

不使用名称空间。

使用一些具体的东西 - 这是可能的。在本地更好 - 在课堂上或功能上。

如果没有必要,则不需要使用函数的尾随返回类型。

[auto f() ->  void;] {.strike}

你不需要像这样声明和初始化变量:

auto s = std :: string { “Hello” };

这是必要的:

std :: string s = “Hello” ; std :: string s { “Hello” };

对于虚拟函数,在基类中写入虚拟,在继承类中写入覆盖,不写虚拟。

未使用的语言特性++的C 

虚拟继承不使用。

不使用C ++ 03的异常限定符。

函数try块不被使用,除了测试中的主函数。

平台

我们编写一个非跨平台的代码(针对特定的平台)。

尽管其他条件相同,但是更多或更少的跨平台或便携代码是优选的。

语言 - C ++ 17。

编译器是gcc。目前(2017年12月),代码将版本7.2。(也可以用clang 5编译代码)

使用标准库(实现libstdc ++或libc ++)。

操作系统 - Linux Ubuntu,不比Precise大。

该代码是为体系结构x86_64的处理器编写的。

一套指令是我们的服务器支持的最低要求。现在是SSE4.2。

编译标志被使用。-Wall -Wextra -Werror

使用带有除了那些困难的所有库静态链接到静态连接(见。结论LDD命令)。

代码是用程序集的发布参数开发和调试的。

工具

KDevelop是一个很好的开发环境。

对于调试,使用gdb,valgrind(memcheck),strace,-fsanitize = ...,tcmalloc_minimal_debug。

性能分析使用Linux Perf,valgrind(callgrind),strace -cf。

来源于Git。

用CMake构建。

程序使用deb包进行布局。

Master承诺不应该破坏项目的组装。

所收集的程序的工作能力仅保证个人审计。

尽可能经常提交,包括非工作代码。

要做到这一点,使用 branch。

如果您的主代码还没有准备好,那么在推送它之前 - 从程序集中排除它,您将不得不修改它或在几天内删除它。

对于不重要的更改,使用branch。应该将分支上传到服务器。

不必要的代码从source中删除。

使用标准的C ++库14(允许使用 Experimental 扩展)以及 Poco 框架。

如有必要,您可以使用软件包中操作系统中可用的任何已知库。

如果有一个好的现成的解决方案,那就使用它,即使你需要为此安装一个库。

(但要做好准备,因为有时候你必须从代码中剔除不好的库。

如果软件包中没有库,或者版本足够旧,或者没有以正确的方式编译,则可以使用未从软件包安装的库。

如果库足够小,并且没有自己的构建系统,则应将其文件包含在项目的contrib目录中

总是优先考虑已经在使用的library库。

一般

写尽可能少的代码。

尝试最简单的解决方案。

如果您不知道程序将执行什么操作,以及内部循环如何工作,则无需编写代码。

在最简单的情况下,使用 using 而不是class/struct。

如果可能的话,不要写复制构造函数赋值运算符,析构函数(除非虚拟类至少包含一个虚函数),移动构造函数和移动赋值。也就是说,编译器生成的相应函数正常工作。你可以使用默认。

简化和减少代码量是值得欢迎的。

此外

显式的std ::用于stddef.h中的类型。

建议不要指定。也就是说,建议编写size_t而不是std :: size_t - 因为它更加精简

但是,如果你想,你仍然可以分配std :: - 这个选项也是可以接受的。

基本的std ::用于标准C库中的函数。

不推荐。也就是说,写入memcpy而不是std :: memcpy。

原因 - 有类似的非标准功能,例如memmem。我们可以使用和偶尔使用这些功能。这些函数在命名空间std中找不到。

如果你在任何地方都写std :: memcpy而不是memcpy,那么在不用std ::的情况下查看memmem将会很不方便。

但是,指定std ::也是可以接受的,如果这更像。

如果标准C ++库中有类似物,则使用C中的函数。

如果这种使用更有效,这是允许的。

例如,要复制长块的内存,请使用memcpy而不是std :: copy。

传递长函数参数。

可以使用任何传输方式,类似于下面列出的传输方式:

功能(T1 x1 ,T2 x2 )

函数(size_t left ,size_t right ,const &RangesInDataParts 范围,size_t 限制)

函数(size_t left ,size_t right ,const &RangesInDataParts 范围,size_t 限制)

函数(size_t left ,size_t right ,const &RangesInDataParts 范围,size_t 限制)

函数(size_t left ,size_t right ,const &RangesInDataParts 范围,size_t 限制)

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

推荐阅读更多精彩内容