Google C++编程气势气魄指南(四):智能指针和其他C++特性
当前位置:以往代写 > C/C++ 教程 >Google C++编程气势气魄指南(四):智能指针和其他C++特性
2019-06-13

Google C++编程气势气魄指南(四):智能指针和其他C++特性

Google C++编程气势气魄指南(四):智能指针和其他C++特性

副标题#e#

1.对付智能指针,安详第一、利便第二,尽大概局部化(scoped_ptr); 2.引用形介入上const,不然利用指针形参;3.函数重载的利用要清晰、易读;4.鉴于容易误用,克制利用缺省函数参数(值得商榷);5.克制利用变长数组;6.公道利用友元……

Google特有的风情

Google有许多本身实现的使C++代码越发结实的能力、成果,以及有异于别处的C++的利用方法。

1.智能指针(Smart Pointers)

假如确实需要利用智能指针的话,scoped_ptr完全可以胜任。在很是非凡的环境下,譬喻对STL容器中工具,你应该只利用std::tr1::shared_ptr,任何环境下都不要利用auto_ptr。

“ 智能”指针看上去是指针,其实是附加了语义的工具。以scoped_ptr为例,scoped_ptr被销毁时,删除了它所指向的工具。shared_ptr也是如此,并且,shared_ptr实现了引用计数(reference-counting),从而只有当它所指向的最后一个工具被销毁时,指针才会被删除。

一般来说,我们倾向于设计工具附属明晰的代码,最明晰的工具附属是基础不利用指针,直接将工具作为一个域(field)或局部变量利用。另一种极度是引用计数指针不属于任何工具,这样设计的问题是容易导致轮回引用或其他导致工具无法删除的诡异条件,并且在每一次拷贝或赋值时连原子操纵城市很慢。

固然不推荐这么做,但有些时候,引用计数指针是最简朴有效的办理方案。

译者注:看来,Google所谓的差异之处,在于只管制止利用智能指针:D,利用时也只管局部化,而且,安详第一。

其他C++特性

1.引用参数(Reference Arguments)

所以按引用通报的参数必需加上const。

界说:在C语言中,假如函数需要修改变量的值,形参(parameter)必需为指针,如int foo(int *pval)。在C++中,函数还可以声明引用形参:int foo(int &val)。

利益:界说形参为引用制止了像(*pval)++这样丑恶的代码,像拷贝结构函数这样的应用也是必须的,并且不像指针那样不接管空指针NULL。

缺点:容易引起误解,因为引用在语法上是值却拥有指针的语义。

结论:

函数形参表中,所有引用必需是const:

void Foo(const string &in, string *out);

事实上这是一个硬性约定:输入参数为值或常数引用,输出参数为指针;输入参数可以是常数指针,但不能利用很是数引用形参。

在强调参数不是拷贝而来,在工具生命期内必需一直存在时可以利用常数指针,最好将这些在注释中具体说明。bind2nd和mem_fun等STL适配器不接管引用形参,这种环境下也必需以指针形参声明函数。


#p#副标题#e#

2.函数重载(Function Overloading)

仅在输入参数范例差异、成果沟通时利用重载函数(含结构函数),不要利用函数重载仿照缺省函数参数。

界说:可以界说一个函数参数范例为const string&,并界说其重载函数范例为const char*。

class MyClass {
public:
void Analyze(const string &text);
void Analyze(const char *text, size_t textlen);
};

利益:通过重载差异参数的同名函数,令代码越发直观,模板化代码需要重载,同时为会见者带来便利。

缺点:限制利用重载的一个原因是在特定挪用处很难确定到底挪用的是哪个函数,另一个原因是当派生类只重载函数的部门变量会令许多人对担任语义发生狐疑。另外在阅读库的客户端代码时,因缺省函数参数造成不须要的费解。

结论:假如你想重载一个函数,思量让函数名包括参数信息,譬喻,利用AppendString()、AppendInt()而不是Append()。

3.缺省参数(Default Arguments)

克制利用缺省函数参数。

利益:常常用到一个函数带有大量缺省值,偶然会重写一下这些值,缺省参数为很少涉及的破例环境提供了少界说一些函数的利便。

缺点:各人常常会通过查察现有代码确定如何利用API,缺省参数使得复制粘贴以前的代码难以泛起所有参数,当缺省参数不合用于新代码时大概导致重大问题。

结论:所有参数必需明晰指定,强制措施员思量API和传入的各参数值,制止利用大概不为措施员所知的缺省参数。

4.变长数组和alloca(Variable-Length Arrays and alloca())

克制利用变长数组和alloca()。

利益:变长数组具有浑然天成的语法,变长数组和alloca()也都很高效。

缺点:变长数组和alloca()不是尺度C++的构成部门,更重要的是,它们在仓库(stack)上按照数据分派巨细大概导致难以发明的内存泄漏:“在我的呆板上运行的好好的,到了产物中却莫名其妙的挂掉了”。

结论:

利用安详的分派器(allocator),如scoped_ptr/scoped_array。

5.友元(Friends)

答允公道利用友元类及友元函数。

#p#分页标题#e#

凡是将友元界说在同一文件下,制止读者跑到其他文件中查找其对某个类私有成员的利用。常常用到友元的一个处所是将FooBuilder声明为Foo 的友元,FooBuilder以便可以正确结构Foo的内部状态,而无需将该状态袒暴露来。某些环境下,将一个单位测试用类声明为待测类的友元会很利便。

友元延伸了(但没有冲破)类的封装界限,当你但愿只答允另一个类会见某个成员时,利用友元凡是比将其声明为public要好得多。虽然,大大都类应该只提供民众成员与其交互。

#p#副标题#e#

6.异常(Exceptions)

不要利用C++异常。

利益:

1) 异常答允上层应用抉择如那里理惩罚在底层嵌套函数中产生的“不行能产生”的失败,不像堕落代码的记录那么恍惚费解;

2) 应用于其他许多现代语言中,引入异常使得C++与Python、Java及其他与C++临近的语言越发兼容;

3) 很多C++第三方库利用异常,封锁异常将导致难以与之团结;

4) 异常是办理结构函数失败的独一方案,固然可以通过工场函数(factory function)或Init()要领模仿异常,但他们别离需要堆分派或新的“犯科”状态;

5) 在测试框架(testing framework)中,异常确实很好用。

缺点:

1) 在现有函数中添加throw语句时,必需查抄所有挪用处,纵然它们至少具有根基的异常安详掩护,可能措施正常竣事,永远不行能捕捉该异常。譬喻:if f() calls g() calls h(),h抛出被f捕捉的异常,g就要当心了,制止没有完全清理;

2) 通俗一点说,异常会导致措施节制流(control flow)通过查察代码无法确定:函数有大概在不确定的处所返回,从而导致代码打点和调试坚苦,虽然,你可以通过划定何时何地如何利用异常来最小化的低落开销,却给开拓人员带来把握这些划定的承担;

3) 异常安详需要RAII和差异编码实践。轻松、正确编写异常安详代码需要大量支撑。答允利用异常;

4) 插手异常使二进制执行代码体积变大,增加了编译时长(或者影响不大),还大概增加地点空间压力;

5) 异常的实用性大概会刺激开拓人员在不得当的时候抛出异常,可能在不安详的处所从异常中规复,譬喻,犯科用户输入大概导致抛出异常。假如答允利用异常会使得这样一篇编程气势气魄指南长出许多(译者注,这个来由有点牵强:-()!

结论:

从外貌上看,利用异常利大于弊,尤其是在新项目中,然而,对付现有代码,引入异常会连累到所有依赖代码。假如答允异常在新项目中利用,在跟以前没有利用异常的代码整适时也是一个贫苦。因为Google现有的大大都C++代码都没有异常处理惩罚,引入带有异常处理惩罚的新代码相当坚苦。

鉴于Google现有代码不接管异常,在现有代码中利用异常比在新项目中利用的价钱几多要大一点,迁移进程会较量慢,也容易堕落。我们也不相信异常的有效替代方案,如错误代码、断言等,都是严重承担。

我们并不是基于哲学或道德层面阻挡利用异常,而是在实践的基本上。因为我们但愿利用Google上的开源项目,但项目中利用异常会为此带来未便,因为我们也发起不要在Google上的开源项目中利用异常,假如我们需要把这些项目推倒重来显然不太现实。

对付Windows代码来说,这一点有个破例(比及最后一篇吧:D)。

译者注:对付异常处理惩罚,显然不是短短几句话可以或许说清楚的,以结构函数为例,许多C++书籍上都提到当结构失败时只有异常可以处理惩罚,Google克制利用异常这一点,仅仅是为了自身的利便,说大了,无非是基于软件打点本钱上,实际利用中照旧本身抉择。

7.运行时范例识别(Run-Time Type Information, RTTI)

我们克制利用RTTI。

界说:RTTI答允措施员在运行时识别C++类工具的范例。

利益:

RTTI在某些单位测试中很是有用,如在举办工场类测试时用于检讨一个新建工具是否为期望的动态范例。

除测试外,极罕用到。

缺点:运行时识别范例意味著设计自己有问题,假如你需要在运行期间确定一个工具的范例,这凡是说明你需要从头思量你的类的设计。

结论:

除单位测试外,不要利用RTTI,假如你发明需要所写代码因工具范例差异而行动各异的话,思量换一种方法识别工具范例。

虚函数可以实现随子类范例差异而执行差异代码,事情都是交给工具自己去完成。

假如事情在工具之外的代码中完成,思量双重分发方案,如Visitor模式,可以利便的在工具自己之外确定类的范例。

#p#分页标题#e#

假如你认为上面的要领你把握不了,可以利用RTTI,但务必请三思,不要去手工实现一个貌似RTTI的方案(RTTI-like workaround),我们阻挡利用RTTI,同样阻挡贴上范例标签的貌似类担任的替代方案(译者注,利用就利用吧,不利用也不要造轮子:D)。

#p#副标题#e#

8.范例转换(Casting)

利用static_cast<>()等C++的范例转换,不要利用int y = (int)x或int y = int(x);。

界说:C++引入了有别于C的差异范例的范例转换操纵。

利益:C语言的范例转换问题在于操纵较量暗昧:有时是在做强制转换(如(int)3.5),有时是在做范例转换(如(int)"hello")。别的,C++的范例转换查找更容易、更精明。

缺点:语法较量恶心(nasty)。

结论:利用C++气势气魄而不要利用C气势气魄范例转换。

1) static_cast:和C气势气魄转换相似可做值的强制转换,或指针的父类到子类的明晰的向上转换;

2) const_cast:移除const属性;

3) reinterpret_cast:指针范例和整型或其他指针间不安详的彼此转换,仅在你对所做一切了然于心时利用;

4) dynamic_cast:除测试外不要利用,除单位测试外,假如你需要在运行时确定范例信息,说明设计有缺陷(参考RTTI)。

9.流(Streams)

只在记录日志时利用流。

界说:流是printf()和scanf()的替代。

利益:有了流,在输出时不需要体贴工具的范例,不消担忧名目化字符串与参数列表不匹配(固然在gcc中利用printf也不存在这个问题),打开、封锁对应文件时,流可以自动结构、析构。

缺点:流使得pread()等成果函数很难执行,假如不利用printf之类的函数而是利用流很难对名目举办操纵(尤其是常用的名目字符串%.*s),流不支持字符串操纵符从头定序(%1s),而这一点对国际化很有用。

结论:

不要利用流,除非是日志接口需要,利用printf之类的取代。

利用流尚有许多利弊,代码一致性胜过一切,不要在代码中利用流。

拓展接头:

对这一条法则存在一些争论,这儿给出深条理原因。回想独一性原则(Only One Way):我们但愿在任何时候都只利用一种确定的I/O范例,使代码在所有I/O处保持一致。因此,我们不但愿用户来抉择是利用流照旧printf + read/write,我们应该抉择到底用哪一种方法。把日志作为破例是因为流很是适合这么做,也有必然的汗青原因。

流的支持者们主张流是不二之选,但概念并不是那么清晰有力,他们所指出流的所有优势也正是其劣势地址。流最大的优势是在输出时不需要体贴输出工具的范例,这是一个亮点,也是一个不敷:很容易用错范例,而编译器不会报警。利用流时容易造成的一类错误是:

cout << this; // Prints the address

cout << *this; // Prints the contents

编译器不会报错,因为<<被重载,就因为这一点我们阻挡利用操纵符重载。

有人说printf的名目化丑恶不堪、易读性差,但流也好不到哪儿去。看看下面两段代码吧,哪个越发易读?

cerr << "Error connecting to '" << foo->bar()->hostname.first
<< ":" << foo->bar()->hostname.second << ": " << strerror(errno);
fprintf(stderr, "Error connecting to '%s:%u: %s",
foo->bar()->hostname.first, foo->bar()->hostname.second,
strerror(errno));

你大概会说,“把流封装一下就会较量好了”,这儿可以,其他处所呢?并且不要忘了,我们的方针是使语言尽大概小,而不是添加一些别人需要进修的新的内容。

每一种方法都是各有利弊,“没有最好,只有更好”,简朴化的教条申饬我们必需从中选择其一,最后的大都抉择是printf + read/write。

#p#副标题#e#

10.前置自增和自减(Preincrement and Predecrement)

对付迭代器和其他模板工具利用前缀形式(++i)的自增、自减运算符。

界说:对付变量在自增(++i或

un">i++)或自减(–i或i–)后表达式的值又没有没用到的环境下,需要确定到底是利用前置照旧后置的自增自减。

利益:不思量返回值的话,前置自增(++i)凡是要比后置自增(–i)效率更高,因为后置的自增自减需要对表达式的值i举办一次拷贝,假如i是迭代器或其他非数值范例,拷贝的价钱是较量大的。既然两种自增方法行动一样(译者注,不思量表达式的值,相信你知道我在说什么),为什么不直接利用前置自增呢?

#p#分页标题#e#

缺点:C语言中,当表达式的值没有利用时,传统的做法是利用后置自增,出格是在for轮回中,有些人以为后置自增越发易懂,因为这很像自然语言,主语(i)在谓语动词(++)前。

结论:对简朴数值(非工具)来说,两种都无所谓,对迭代器和模板范例来说,要利用前置自增(自减)。

11.const的利用(Use of const)

我们强烈发起你在任何可以利用的环境下都要利用const。

界说:在声明的变量或参数前加上要害字const用于指明变量值不行修改(如const int foo),为类中的函数加上const限定表白该函数不会修改类成员变量的状态(如class Foo { int Bar(char c) const; };)。

利益:人们更容易领略变量是如何利用的,编辑器可以更好地举办范例检测、更好地生成代码。人们对编写正确的代码越发自信,因为他们知道所挪用的函数被限定了能或不能修改变量值。纵然是在无锁的多线程编程中,人们也知道什么样的函数是安详的。

缺点:假如你向一个函数传入const变量,函数原型中也必需是const的(不然变量需要const_cast范例转换),在挪用库函数时这尤其是个贫苦。

结论:const变量、数据成员、函数和参数为编译时范例检测增加了一层保障,更好的尽早发明错误。因此,我们强烈发起在任何可以利用的环境下利用const:

1) 假如函数不会修改传入的引用或指针范例的参数,这样的参数应该为const;

2) 尽大概将函数声明为const,会见函数应该老是const,其他函数假如不会修改任何数据成员也应该是const,不要挪用非const函数,不要返回对数据成员的非const指针或引用;

3) 假如数据成员在工具结构之后不再改变,可将其界说为const。

然而,也不要对const太过利用,像const int * const * const x;就有些过了,即便这样写准确描写了x,其实写成const int** x就可以了。

要害字mutable可以利用,可是在多线程中是不安详的,利用时首先要思量线程安详。

const位置:

有人喜欢int const *foo形式不喜欢const int* foo,他们认为前者越发一致因此可读性更好:遵循了const总位于其描写的工具(int)之后的原则。可是,一致性原则不合用于此,“不要太过利用”的权威抵消了一致性利用。将const放在前面才更易读,因为在自然语言中形容词(const)是在名词(int)之前的。

这是说,我们倡导const在前,并不是要求,但要分身代码的一致性!

12.整型(Integer Types)

C++内建整型中,独一用到的是int,假如措施中需要差异巨细的变量,可以利用<stdint.h>中的准确宽度(precise-width)的整型,如int16_t。

界说:C++没有指定整型的巨细,通凡人们认为short是16位,int是32位,long是32位,long long是64位。

利益:保持声明统一。

缺点:C++中整型巨细因编译器和体系布局的差异而差异。

结论:

<stdint.h>界说了int16_t、uint32_t、int64_t等整型,在需要确定巨细的整型时可以利用它们取代short、unsigned long long等,在C整型中,只利用int。适当环境下,推荐利用尺度范譬喻size_t和ptrdiff_t。

最常利用的是,对整数来说,凡是不会用到太大,如轮回计数等,可以利用普通的int。你可以认为int至少为32位,但不要认为它会多于32位,需要64位整型的话,可以利用int64_t或uint64_t。

对付大整数,利用int64_t。

不要利用uint32_t等无标记整型,除非你是在暗示一个位组(bit pattern)而不是一个数值。纵然数值不会为负值也不要利用无标记范例,利用断言(assertion,译者注,这一点很有原理,计较机只会按照变量、返回值等有无标记确定命值正负,仍然无法确定对错)来掩护数据。

无标记整型:

有些人,包罗一些教科书作者,推荐利用无标记范例暗示非负数,范例表白了数值取值形式。可是,在C语言中,这一利益被由其导致的bugs所沉没。看看:

for (unsigned int i = foo.Length()-1; i >= 0; –i) …

上述代码永远不会终止!有时gcc会发明该bug并报警,但凡是不会。雷同的bug还会呈此刻较量有切合变量和无标记变量时,主要是C的范例晋升机制(type-promotion scheme,C语言中各类内建范例之间的晋升转换干系)会致使无标记范例的行为出乎你的料想。

因此,利用断言声明变量为非负数,不要利用无标记型。

#p#副标题#e#

13.64位下的可移植性(64-bit Portability)

代码在64位和32位的系统中,原则上应该都较量友好,尤其对付输出、较量、布局对齐(structure alignment)来说:

#p#分页标题#e#

1) printf()指定的一些范例在32位和64位系统上可移植性不是很好,C99尺度界说了一些可移植的名目。不幸的是,MSVC 7.1并非全部支持,并且尺度中也有所漏掉。所以有时我们就不得不本身界说丑恶的版本(利用尺度气势气魄要包括文件inttypes.h):

// printf macros for size_t, in the style of inttypes.h
#ifdef _LP64
#define __PRIS_PREFIX "z"
#else
#define __PRIS_PREFIX
#endif
// Use these macros after a % in a printf format string
// to get correct 32/64 bit behavior, like this:
// size_t size = records.size();
// printf("%"PRIuS"\n", size);
#define PRIdS __PRIS_PREFIX "d"
#define PRIxS __PRIS_PREFIX "x"
#define PRIuS __PRIS_PREFIX "u"
#define PRIXS __PRIS_PREFIX "X"
#define PRIoS __PRIS_PREFIX "o"

范例 不要利用 利用 备注
void *(或其他指针范例) %lx %p  
int64_t %qd, %lld %"PRId64"  
uint64_t %qu, %llu, %llx %"PRIu64", %"PRIx64"  
size_t %u %"PRIuS", %"PRIxS" C99指定%zu
ptrdiff_t %d %"PRIdS" C99指定%zd

留意宏PRI*会被编译器扩展为独立字符串,因此假如利用很是量的名目化字符串,需要将宏的值而不是宏名插入名目中,在利用宏PRI*时同样可以在%后指定长度等信息。譬喻,printf("x = %30"PRIuS"\n", x)在32位Linux大将被扩展为printf("x = %30" "u" "\n", x),编译器会处理惩罚为printf("x = %30u\n", x)。

2) 记着sizeof(void *) != sizeof(int),假如需要一个指针巨细的整数要利用intptr_t。

3) 需要对布局对齐加以把稳,尤其是对付存储在磁盘上的布局体。在64位系统中,任何拥有int64_t/uint64_t成员的类/布局体将默认被处理惩罚为8字节对齐。假如32位和64位代码共用磁盘上的布局体,需要确保两种体系布局下的布局体的对齐一致。大大都编译器提供了调解布局体对齐的方案。gcc中可利用__attribute__((packed)),MSVC提供了#pragma pack()和__declspec(align())(译者注,办理方案的项目属性里也可以直接配置)。

4) 建设64位常量时利用LL或ULL作为后缀,如:

int64_t my_value = 0x123456789LL;

uint64_t my_mask = 3ULL << 48;

5) 假如你确实需要32位和64位系统具有差异代码,可以在代码变量前利用。(只管不要这么做,利用时只管使修改局部化)。

#p#副标题#e#

14.预处理惩罚宏(Preprocessor Macros)

利用宏时要审慎,只管以内联函数、列举和常量取代之。

宏意味着你和编译器看到的代码是差异的,因此大概导致异常行为,尤其是当宏存在于全局浸染域中。

值得名誉的是,C++中,宏不像C中那么须要。宏内联效率要害代码(performance-critical code)可以内联函数替代;宏存储常量可以const变量替代;宏“缩写”长变量名可以引用替代;利用宏举办条件编译,这个……,最好不要这么做,会令测试越发疾苦(#define防备头文件重包括虽然是个破例)。

宏可以做一些其他技能无法实现的工作,在一些代码库(尤其是底层库中)可以看到宏的某些特性(如字符串化(stringifying,译者注,利用#)、毗连(concatenation,译者注,利用##)等等)。但在利用前,仔细思量一下能不能不利用宏实现同样结果。

译者注:关于宏的高级应用,可以参考《C语言宏的高级应用》。

下面给出的用法模式可以制止一些利用宏的问题,供利用宏时参考:

1) 不要在.h文件中界说宏;

2) 利用前正确#define,利用后正确#undef;

3) 不要只是对已经存在的宏利用#undef,选择一个不会斗嘴的名称;

4) 不利用会导致不不变的C++结构(unbalanced C++ constructs,译者注)的宏,至少文档说明其行为。

15.0和NULL(0 and NULL)

整数用0,实数用0.0,指针用NULL,字符(串)用’\0’。

整数用0,实数用0.0,这一点是毫无争议的。

#p#分页标题#e#

对付指针(地点值),到底是用0照旧NULL,Bjarne Stroustrup发起利用最原始的0,我们发起利用看上去像是指针的NULL,事实上一些C++编译器(如gcc 4.1.0)专门提供了NULL的界说,可以给出有用的告诫,尤其是sizeof(NULL)和sizeof(0)不相等的环境。

字符(串)用’\0’,不只范例正确并且可读性好。

16. sizeof(sizeof)

尽大概用sizeof(varname)取代sizeof(type)。

利用sizeof(varname)是因为当变量范例改变时代码自动同步,有些环境下sizeof(type)或者有意义,照旧要只管制止,假如变量范例改变的话不能同步。

Struct data;
memset(&data, 0, sizeof(data));
memset(&data, 0, sizeof(Struct));

#p#副标题#e#

17. Boost库(Boost)

只利用Boost中被承认的库。

界说:Boost库集是一个很是受接待的、同级评议的(peer-reviewed)、免费的、开源的C++库。

利益:Boost代码质量普遍较高、可移植性好,填补了C++尺度库许多空缺,如型别特性(type traits)、更完善的绑定(binders)、更好的智能指针,同时还提供了TR1(尺度库的扩展)的实现。

缺点:某些Boost库倡导的编程实践可读性差,像元措施(metaprogramming)和其他高级模板技能,以及太过“函数化”("functional")的编程气势气魄。

结论:为了向阅读和维护代码的人员提供更好的可读性,我们只答允利用Boost特性的一个成熟子集,当前,这些库包罗:

1) Compressed Pair:boost/compressed_pair.hpp;

2) Pointer Container:boost/ptr_container不包罗ptr_array.hpp和序列化(serialization)。

我们会努力思量添加可以的Boost特性,所以不必拘泥于该法则。
______________________________________

译者:关于C++特性的留意事项,总结一下:

1. 对付智能指针,安详第一、利便第二,尽大概局部化(scoped_ptr);

2. 引用形介入上const,不然利用指针形参;

3. 函数重载的利用要清晰、易读;

4. 鉴于容易误用,克制利用缺省函数参数(值得商榷);

5. 克制利用变长数组;

6. 公道利用友元;

7. 为了利便代码打点,克制利用异常(值得商榷);

8. 克制利用RTTI,不然从头设计代码吧;

9. 利用C++气势气魄的范例转换,除单位测试外不要利用dynamic_cast;

10. 利用流还printf + read/write,it is a problem;

11. 能用前置自增/减不消后置自增/减;

12. const能用则用,倡导const在前;

13. 利用确定巨细的整型,除位组外不要利用无标记型;

14. 名目化输出及布局对齐时,留意32位和64位的系统差别;

15. 除字符串化、毗连外只管制止利用宏;

16. 整数用0,实数用0.0,指针用NULL,字符(串)用’\0’;

17. 用sizeof(varname)取代sizeof(type);

18. 只利用Boost中被承认的库。

    关键字:

在线提交作业