Creative Commons License
本Blog采用 知识共享署名-非商业性使用-禁止演绎 3.0 Unported许可协议 进行许可。 —— Fox <游戏人生>

游戏人生

游戏人生 != ( 人生 == 游戏 )
站点迁移至:http://www.yulefox.com。请订阅本博的朋友将RSS修改为http://feeds.feedburner.com/yulefox
posts - 62, comments - 508, trackbacks - 0, articles - 7

原文地址:

  • 注释

注释虽然写起来很痛苦,但对保证代码可读性至为重要,下面的规则描述了应该注释什么、注释在哪儿。当然也要记住,注释的确很重要,但最好的代码本身就是文档(self-documenting),类型和变量命名意义明确要比通过注释解释模糊的命名好得多。

注释是为别人(下一个需要理解你的代码的人)而写的,认真点吧,那下一个人可能就是你!

1. 注释风格(Comment Style)

使用///* */,统一就好。

///* */都可以,//只是用的更加广泛,在如何注释和注释风格上确保统一。

2. 文件注释(File Comments)

在每一个文件开头加入版权公告,然后是文件内容描述。

法律公告和作者信息

每一文件包含以下项,依次是:

1) 版权(copyright statement):如Copyright 2008 Google Inc.

2) 许可版本(license boilerplate):为项目选择合适的许可证版本,如Apache 2.0BSDLGPLGPL

3) 作者(author line):标识文件的原始作者。

如果你对其他人创建的文件做了重大修改,将你的信息添加到作者信息里,这样当其他人对该文件有疑问时可以知道该联系谁。

文件内容

每一个文件版权许可及作者信息后,都要对文件内容进行注释说明。

通常,.h文件要对所声明的类的功能和用法作简单说明,.cc文件包含了更多的实现细节或算法讨论,如果你感觉这些实现细节或算法讨论对于阅读有帮助,可以把.cc中的注释放到.h中,并在.cc中指出文档在.h中。

不要单纯在.h.cc间复制注释,复制的注释偏离了实际意义。

3. 类注释(Class Comments)

每个类的定义要附着描述类的功能和用法的注释。

// Iterates over the contents of a GargantuanTable.  Sample usage:
//    GargantuanTable_Iterator* iter = table->NewIterator();
//    for (iter->Seek("foo"); !iter->done(); iter->Next()) {
//      process(iter->key(), iter->value());
//    }
//    delete iter;
class GargantuanTable_Iterator {
  ...
};

如果你觉得已经在文件顶部详细描述了该类,想直接简单的来上一句“完整描述见文件顶部”的话,还是多少在类中加点注释吧。

如果类有任何同步前提(synchronization assumptions),文档说明之。如果该类的实例可被多线程访问,使用时务必注意文档说明。

4. 函数注释(Function Comments)

函数声明处注释描述函数功能,定义处描述函数实现。

函数声明

注释于声明之前,描述函数功能及用法,注释使用描述式("Opens the file")而非指令式("Open the file");注释只是为了描述函数而不是告诉函数做什么。通常,注释不会描述函数如何实现,那是定义部分的事情。

函数声明处注释的内容:

1) inputs(输入)outputs(输出)

2) 对类成员函数而言:函数调用期间对象是否需要保持引用参数,是否会释放这些参数;

3) 如果函数分配了空间,需要由调用者释放;

4) 参数是否可以为NULL

5) 是否存在函数使用的性能隐忧(performance implications)

6) 如果函数是可重入的(re-entrant),其同步前提(synchronization assumptions)是什么?

举例如下:

// Returns an iterator for this table.  It is the client's
// responsibility to delete the iterator when it is done with it,
// and it must not use the iterator once the GargantuanTable object
// on which the iterator was created has been deleted.
//
// The iterator is initially positioned at the beginning of the table.
//
// This method is equivalent to:
//    Iterator* iter = table->NewIterator();
//    iter->Seek("");
//    return iter;
// If you are going to immediately seek to another place in the
// returned iterator, it will be faster to use NewIterator()
// and avoid the extra seek.
Iterator* GetIterator() const;

但不要有无谓冗余或显而易见的注释,下面的注释就没有必要加上“returns false otherwise”,因为已经暗含其中了:

// Returns true if the table cannot hold any more entries.
bool IsTableFull();

注释构造/析构函数时,记住,读代码的人知道构造/析构函数是什么,所以“destroys this object”这样的注释是没有意义的。说明构造函数对参数做了什么(例如,是否是指针的所有者)以及析构函数清理了什么,如果都是无关紧要的内容,直接省掉注释,析构函数前没有注释是很正常的。

函数定义

每个函数定义时要以注释说明函数功能和实现要点,如使用的漂亮代码、实现的简要步骤、如此实现的理由、为什么前半部分要加锁而后半部分不需要。

不要从.h文件或其他地方的函数声明处直接复制注释,简要说明函数功能是可以的,但重点要放在如何实现上。

5. 变量注释(Variable Comments)

通常变量名本身足以很好说明变量用途,特定情况下,需要额外注释说明。

类数据成员

每个类数据成员(也叫实例变量或成员变量)应注释说明用途,如果变量可以接受NULL或-1等警戒值(sentinel values),须说明之,如:

private:
 // Keeps track of the total number of entries in the table.
 // Used to ensure we do not go over the limit. -1 means
 // that we don't yet know how many entries the table has.
 int num_total_entries_;

全局变量(常量)

和数据成员相似,所有全局变量(常量)也应注释说明含义及用途,如:

// The total number of tests cases that we run through in this regression test.
const int kNumTestCases = 6;

6. 实现注释(Implementation Comments)

对于实现代码中巧妙的、晦涩的、有趣的、重要的地方加以注释。

代码前注释

出彩的或复杂的代码块前要加注释,如:

// Divide result by two, taking into account that x
// contains the carry from the add.
for (int i = 0; i < result->size(); i++) {
  x = (x << 8) + (*result)[i];
  (*result)[i] = x >> 1;
  x &= 1;
}

行注释

比较隐晦的地方要在行尾加入注释,可以在代码之后空两格加行尾注释,如:

// If we have enough memory, mmap the data portion too.
mmap_budget = max<int64>(0, mmap_budget - index_->length());
if (mmap_budget >= data_size_ && !MmapData(mmap_chunk_bytes, mlock))
  return;  // Error already logged.

注意,有两块注释描述这段代码,当函数返回时注释提及错误已经被记入日志。

前后相邻几行都有注释,可以适当调整使之可读性更好:

...
DoSomething();                  // Comment here so the comments line up.
DoSomethingElseThatIsLonger();  // Comment here so there are two spaces between
                                // the code and the comment.
...

NULL、true/false、1、2、3……

向函数传入、布尔值或整数时,要注释说明含义,或使用常量让代码望文知意,比较一下:

bool success = CalculateSomething(interesting_value,
                                  10,
                                  false,
                                  NULL);  // What are these arguments??

和:

bool success = CalculateSomething(interesting_value,
                                  10,     // Default base value.
                                  false,  // Not the first time we're calling this.
                                  NULL);  // No callback.

使用常量或描述性变量:

const int kDefaultBaseValue = 10;
const bool kFirstTimeCalling = false;
Callback *null_callback = NULL;
bool success = CalculateSomething(interesting_value,
                                  kDefaultBaseValue,
                                  kFirstTimeCalling,
                                  null_callback);

不要

注意永远不要用自然语言翻译代码作为注释,要假设读你代码的人C++比你强:D:

// Now go through the b array and make sure that if i occurs,
// the next element is i+1.
...        // Geez.  What a useless comment.

7. 标点、拼写和语法(Punctuation, Spelling and Grammar)

留意标点、拼写和语法,写的好的注释比差的要易读的多。

注释一般是包含适当大写和句点(.)的完整的句子,短一点的注释(如代码行尾的注释)可以随意点,依然要注意风格的一致性。完整的句子可读性更好,也可以说明该注释是完整的而不是一点不成熟的想法。

虽然被别人指出该用分号(semicolon)的时候用了逗号(comma)有点尴尬。清晰易读的代码还是很重要的,适当的标点、拼写和语法对此会有所帮助。

8. TODO注释(TODO Comments)

对那些临时的、短期的解决方案,或已经够好但并不完美的代码使用TODO注释。

这样的注释要使用全大写的字符串TODO,后面括号(parentheses)里加上你的大名、邮件地址等,还可以加上冒号(colon):目的是可以根据统一的TODO格式进行查找:

// TODO(kl@gmail.com): Use a "*" here for concatenation operator.
// TODO(Zeke) change this to use relations.

如果加上是为了在“将来某一天做某事”,可以加上一个特定的时间("Fix by November 2005")或事件("Remove this code when all clients can handle XML responses.")。

______________________________________

译者:注释也是比较人性化的约定了:

1. 关于注释风格,很多C++的coders更喜欢行注释,C coders或许对块注释依然情有独钟,或者在文件头大段大段的注释时使用块注释;

2. 文件注释可以炫耀你的成就,也是为了捅了篓子别人可以找你;

3. 注释要言简意赅,不要拖沓冗余,复杂的东西简单化和简单的东西复杂化都是要被鄙视的;

4. 对于Chinese coders来说,用英文注释还是用中文注释,it is a problem,但不管怎样,注释是为了让别人看懂,难道是为了炫耀编程语言之外的你的母语或外语水平吗;

5. 注释不要太乱,适当的缩进才会让人乐意看,但也没有必要规定注释从第几列开始(我自己写代码的时候总喜欢这样),UNIX/LINUX下还可以约定是使用tab还是space,个人倾向于space;

6. TODO很不错,有时候,注释确实是为了标记一些未完成的或完成的不尽如人意的地方,这样一搜索,就知道还有哪些活要干,日志都省了。

posted @ 2008-07-22 17:02 Fox 阅读(3650) | 评论 (4)编辑 收藏

原文地址:

  • 命名约定

最重要的一致性规则是命名管理,命名风格直接可以直接确定命名实体是:类型、变量、函数、常量、宏等等,无需查找实体声明,我们大脑中的模式匹配引擎依赖于这些命名规则。

命名规则具有一定随意性,但相比按个人喜好命名,一致性更重要,所以不管你怎么想,规则总归是规则。

1. 通用命名规则(General Naming Rules)

函数命名、变量命名、文件命名应具有描述性,不要过度缩写,类型和变量应该是名词,函数名可以用“命令性”动词。

如何命名

尽可能给出描述性名称,不要节约空间,让别人很快理解你的代码更重要,好的命名选择:

int num_errors;                  // Good.
int num_completed_connections;   // Good.

丑陋的命名使用模糊的缩写或随意的字符:

int n;                           // Bad - meaningless.
int nerr;                        // Bad - ambiguous abbreviation.
int n_comp_conns;                // Bad - ambiguous abbreviation.

类型和变量名一般为名词:如FileOpenernum_errors

函数名通常是指令性的,如OpenFile()set_num_errors(),访问函数需要描述的更细致,要与其访问的变量相吻合。

缩写

除非放到项目外也非常明了,否则不要使用缩写,例如:

// Good
// These show proper names with no abbreviations.
int num_dns_connections;  // Most people know what "DNS" stands for.
int price_count_reader;   // OK, price count. Makes sense.
 
// Bad!
// Abbreviations can be confusing or ambiguous outside a small group.
int wgc_connections;  // Only your group knows what this stands for.
int pc_reader;        // Lots of things can be abbreviated "pc".

不要用省略字母的缩写:

int error_count;  // Good.
int error_cnt;    // Bad.

2. 文件命名(File Names)

文件名要全部小写,可以包含下划线(_)或短线(-),按项目约定来。

可接受的文件命名:

my_useful_class.cc
my-useful-class.cc
myusefulclass.cc

C++文件以.cc结尾,头文件以.h结尾。

不要使用已经存在于/usr/include下的文件名(译者注,对UNIX、Linux等系统而言),如db.h

通常,尽量让文件名更加明确,http_server_logs.h就比logs.h要好,定义类时文件名一般成对出现,如foo_bar.hfoo_bar.cc,对应类FooBar

内联函数必须放在.h文件中,如果内联函数比较短,就直接放在.h中。如果代码比较长,可以放到以-inl.h结尾的文件中。对于包含大量内联代码的类,可以有三个文件:

url_table.h      // The class declaration.
url_table.cc     // The class definition.
url_table-inl.h  // Inline functions that include lots of code.

参考第一篇-inl.h文件一节。

3. 类型命名(Type Names)

类型命名每个单词以大写字母开头,不包含下划线:MyExcitingClassMyExcitingEnum

所有类型命名——类、结构体、类型定义(typedef)、枚举——使用相同约定,例如:

// classes and structs
class UrlTable { ... 
class UrlTableTester { ... 
struct UrlTableProperties { ...

// typedefs
typedef hash_map<UrlTableProperties *, string> PropertiesMap;

// enums
enum UrlTableErrors { ...

4. 变量命名(Variable Names)

变量名一律小写,单词间以下划线相连,类的成员变量以下划线结尾,如my_exciting_local_variablemy_exciting_member_variable_

普通变量命名

举例:

string table_name;  // OK - uses underscore.
string tablename;   // OK - all lowercase.
string tableName;   // Bad - mixed case.

类数据成员

结构体的数据成员可以和普通变量一样,不用像类那样接下划线:

struct UrlTableProperties {
  string name;
  int num_entries;
}

结构体与类的讨论参考第三篇结构体vs.类一节。

全局变量

对全局变量没有特别要求,少用就好,可以以g_或其他易与局部变量区分的标志为前缀。

5. 常量命名(Constant Names)

在名称前加kkDaysInAWeek

所有编译时常量(无论是局部的、全局的还是类中的)和其他变量保持些许区别,k后接大写字母开头的单词:

const int kDaysInAWeek = 7;

6. 函数命名(Function Names)

普通函数(regular functions,译者注,这里与访问函数等特殊函数相对)大小写混合,存取函数(accessors and mutators)则要求与变量名匹配:MyExcitingFunction()MyExcitingMethod()my_exciting_member_variable()set_my_exciting_member_variable()

普通函数

函数名以大写字母开头,每个单词首字母大写,没有下划线:

AddTableEntry()
DeleteUrl()

存取函数

存取函数要与存取的变量名匹配,这儿摘录一个拥有实例变量num_entries_的类:

class MyClass {
 public:
  ...
  int num_entries() const { return num_entries_; }
  void set_num_entries(int num_entries) { num_entries_ = num_entries; }

 private:
  int num_entries_;
};

其他短小的内联函数名也可以使用小写字母,例如,在循环中调用这样的函数甚至都不需要缓存其值,小写命名就可以接受。

译者注:从这一点上可以看出,小写的函数名意味着可以直接内联使用。

7. 命名空间(Namespace Names)

命名空间的名称是全小写的,其命名基于项目名称和目录结构:google_awesome_project

关于命名空间的讨论和如何命名,参考第二篇命名空间

8. 枚举命名(Enumerator Names)

枚举值应全部大写,单词间以下划线相连:MY_EXCITING_ENUM_VALUE

枚举名称属于类型,因此大小写混合:UrlTableErrors

enum UrlTableErrors {
  OK = 0,
  ERROR_OUT_OF_MEMORY,
  ERROR_MALFORMED_INPUT,
};

9. 宏命名(Macro Names)

你并不打算使用宏,对吧?如果使用,像这样:MY_MACRO_THAT_SCARES_SMALL_CHILDREN

参考第四篇预处理宏,通常是不使用宏的,如果绝对要用,其命名像枚举命名一样全部大写、使用下划线:

#define ROUND(x) ...
#define PI_ROUNDED 3.0
MY_EXCITING_ENUM_VALUE

10. 命名规则例外(Exceptions to Naming Rules)

当命名与现有C/C++实体相似的对象时,可参考现有命名约定:

bigopen()
函数名,参考open()
uint
typedef类型定义
bigpos
structclass,参考pos
sparse_hash_map
STL相似实体;参考STL命名约定
LONGLONG_MAX
常量,类似INT_MAX

______________________________________

译者:命名约定就相对轻松许多,在遵从代码一致性、可读性的前提下,略显随意:

1. 总体规则:不要随意缩写,如果说ChangeLocalValue写作ChgLocVal还有情可原的话,把ModifyPlayerName写作MdfPlyNm就太过分了,除函数名可适当为动词外,其他命名尽量使用清晰易懂的名词;

2. 宏、枚举等使用全部大写+下划线;

3. 变量(含类、结构体成员变量)、文件、命名空间、存取函数等使用全部小写+下划线,类成员变量以下划线结尾,全局变量以g_开头;

4. 普通函数、类型(含类与结构体、枚举类型)、常量等使用大小写混合,不含下划线;

5. 参考现有或相近命名约定。

posted @ 2008-07-22 11:59 Fox 阅读(4170) | 评论 (3)编辑 收藏

原文地址:

  • 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适配器不接受引用形参,这种情况下也必须以指针形参声明函数。

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)

允许合理使用友元类及友元函数。

通常将友元定义在同一文件下,避免读者跑到其他文件中查找其对某个类私有成员的使用。经常用到友元的一个地方是将FooBuilder声明为Foo的友元,FooBuilder以便可以正确构造Foo的内部状态,而无需将该状态暴露出来。某些情况下,将一个单元测试用类声明为待测类的友元会很方便。

友元延伸了(但没有打破)类的封装界线,当你希望只允许另一个类访问某个成员时,使用友元通常比将其声明为public要好得多。当然,大多数类应该只提供公共成员与其交互。

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模式,可以方便的在对象本身之外确定类的类型。

如果你认为上面的方法你掌握不了,可以使用RTTI,但务必请三思,不要去手工实现一个貌似RTTI的方案(RTTI-like workaround),我们反对使用RTTI,同样反对贴上类型标签的貌似类继承的替代方案(译者注,使用就使用吧,不使用也不要造轮子:D)。

8. 类型转换(Casting

使用static_cast<>()等C++的类型转换,不要使用int y = (int)xint 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

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

对于迭代器和其他模板对象使用前缀形式(++i)的自增、自减运算符。

定义:对于变量在自增(++ii++)或自减(--ii--)后表达式的值又没有没用到的情况下,需要确定到底是使用前置还是后置的自增自减。

优点:不考虑返回值的话,前置自增(++i)通常要比后置自增(i++)效率更高,因为后置的自增自减需要对表达式的值i进行一次拷贝,如果i是迭代器或其他非数值类型,拷贝的代价是比较大的。既然两种自增方式动作一样(译者注,不考虑表达式的值,相信你知道我在说什么),为什么不直接使用前置自增呢?

缺点: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_tuint32_tint64_t等整型,在需要确定大小的整型时可以使用它们代替shortunsigned long long等,在C整型中,只使用int。适当情况下,推荐使用标准类型如size_tptrdiff_t

最常使用的是,对整数来说,通常不会用到太大,如循环计数等,可以使用普通的int。你可以认为int至少为32位,但不要认为它会多于32位,需要64位整型的话,可以使用int64_tuint64_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语言中各种内建类型之间的提升转换关系)会致使无符号类型的行为出乎你的意料。

因此,使用断言声明变量为非负数,不要使用无符号型。

13. 64位下的可移植性(64-bit Portability

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

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位常量时使用LLULL作为后缀,如:

int64_t my_value = 0x123456789LL;
uint64_t my_mask = 3ULL << 48;

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

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,这一点是毫无争议的。

对于指针(地址值),到底是用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));

17. Boost库(Boost

只使用Boost中被认可的库。

定义:Boost库集是一个非常受欢迎的、同级评议的(peer-reviewed)、免费的、开源的C++库。

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

缺点:某些Boost库提倡的编程实践可读性差,像元程序(metaprogramming)和其他高级模板技术,以及过度“函数化”("functional")的编程风格。

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

1) Compressed Pairboost/compressed_pair.hpp

2) Pointer Containerboost/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中被认可的库。

posted @ 2008-07-21 14:55 Fox 阅读(5949) | 评论 (5)编辑 收藏

一个好的日志系统,除了可以记录尽可能多的必要信息,方便trace bugs、提供data analysis source这些基本功能之外,其他的貌似不必太在意。但真正当bugs冒出来的时候,要命的是既没有dump,也没有有价值的日志,更要命的是日志居然已经记录了那么多,居然让你查了半天,居然都是没有价值的!

悲剧啊!

日志需要记录的信息大概分为两类:

1) 系统运行情况:启动、加载、读写、关闭、异常

2) 用户使用情况:进入、操作、离开、异常

我可以想到的关于日志系统的要求大致以下几点:

1) 日志系统使用目录树结构:系统日志和用户日志分别记录,正常日志和异常日志分别记录,不置于同一文件夹下,日志文件命名做到令观者一目了然;

2) 记录详尽但不冗余:正确记录日志时间、位置、事件、因果,有可能的话,记录上下文(这要求有点高了);

3) 格式统一但严禁千篇一律:格式统一是指记录内容遵循一定格式,方便查看,严禁千篇一律是指记录要有层次、轻重,不同事件导致的“同一”异常日志不应不加区别,同样是为了方便查看;

4) 与异常处理相辅相成:有dump时,以日志辅助快速定位,没有dump时,日志应尽可能提供有效信息,离系统崩溃的地方越近越好(这一点似乎也有难度)。

________________________________________________

突然想到的,也还没有动手去做,先记下了,欢迎补充。

_____Added on Jul.25th, 2008_______________________

还看到一位兄弟在为我说话,谢谢!

今天在考虑实现时,想到一个很现实的问题,日志几乎是无处不在的,随时随地会有日志记录。不知道有谁对I/O(当然主要是Output)消耗和对系统的影响做过专门测试,猜测就算了:-),我很想知道有没有必要放到专门的线程中,如果放到独立线程中的话,问题就出来了,多长时间写一次?毕竟,记录日志的主要目的就是为了全面记录系统运行和用户使用情况,如果在服务器crash的时候,还有日志(尤其是crash上下文日志)没有被顺利写入,日志的意义也就大打折扣。

谁给点建议?

posted @ 2008-07-18 10:03 Fox 阅读(2035) | 评论 (8)编辑 收藏

这一篇主要提到的是类,Lippman在《Inside The C++ Object Model》第二章中对构造函数作了详尽说明,本文中提到的几个单词基本仿该书中译本侯捷先生的翻译:

explicit:明确的

implicit:隐含的

trivial:没有意义的

non-trivial:有意义的

 

原文地址:

类是C++中基本的代码单元,自然被广泛使用。本节列举了在写一个类时要做什么、不要做什么。

1. 构造函数(Constructor)的职责

构造函数中只进行那些没有实际意义的(trivial,译者注:简单初始化对于程序执行没有实际的逻辑意义,因为成员变量的“有意义”的值大多不在构造函数中确定)初始化,可能的话,使用Init()方法集中初始化为有意义的(non-trivial)数据。

定义:在构造函数中执行初始化操作。

优点:排版方便,无需担心类是否初始化。

缺点:在构造函数中执行操作引起的问题有:

1) 构造函数中不易报告错误,不能使用异常。

2) 操作失败会造成对象初始化失败,引起不确定状态。

3) 构造函数内调用虚函数,调用不会派发到子类实现中,即使当前没有子类化实现,将来仍是隐患。

4) 如果有人创建该类型的全局变量(虽然违背了上节提到的规则),构造函数将在main()之前被调用,有可能破坏构造函数中暗含的假设条件。例如,gflags尚未初始化。

结论:如果对象需要有意义的(non-trivial)初始化,考虑使用另外的Init()方法并(或)增加一个成员标记用于指示对象是否已经初始化成功。

2. 默认构造函数(Default Constructors)

如果一个类定义了若干成员变量又没有其他构造函数,需要定义一个默认构造函数,否则编译器将自动生产默认构造函数。

定义:新建一个没有参数的对象时,默认构造函数被调用,当调用new[](为数组)时,默认构造函数总是被调用。

优点:默认将结构体初始化为“不可能的”值,使调试更加容易。

缺点:对代码编写者来说,这是多余的工作。

结论:

如果类中定义了成员变量,没有提供其他构造函数,你需要定义一个默认构造函数(没有参数)。默认构造函数更适合于初始化对象,使对象内部状态(internal state)一致、有效。

提供默认构造函数的原因是:如果你没有提供其他构造函数,又没有定义默认构造函数,编译器将为你自动生成一个,编译器生成的构造函数并不会对对象进行初始化。

如果你定义的类继承现有类,而你又没有增加新的成员变量,则不需要为新类定义默认构造函数。

3. 明确的构造函数(Explicit Constructors)

对单参数构造函数使用C++关键字explicit。

定义:通常,只有一个参数的构造函数可被用于转换(conversion,译者注:主要指隐式转换,下文可见),例如,定义了Foo::Foo(string name),当向需要传入一个Foo对象的函数传入一个字符串时,构造函数Foo::Foo(string name)被调用并将该字符串转换为一个Foo临时对象传给调用函数。看上去很方便,但如果你并不希望如此通过转换生成一个新对象的话,麻烦也随之而来。为避免构造函数被调用造成隐式转换,可以将其声明为explicit。

优点:避免不合时宜的变换。

缺点:无。

结论:

所有单参数构造函数必须是明确的。在类定义中,将关键字explicit加到单参数构造函数前:explicit Foo(string name);

例外:在少数情况下,拷贝构造函数可以不声明为explicit;特意作为其他类的透明包装器的类。类似例外情况应在注释中明确说明。

4. 拷贝构造函数(Copy Constructors)

仅在代码中需要拷贝一个类对象的时候使用拷贝构造函数;不需要拷贝时应使用DISALLOW_COPY_AND_ASSIGN

定义:通过拷贝新建对象时可使用拷贝构造函数(特别是对象的传值时)。

优点:拷贝构造函数使得拷贝对象更加容易,STL容器要求所有内容可拷贝、可赋值。

缺点:C++中对象的隐式拷贝是导致很多性能问题和bugs的根源。拷贝构造函数降低了代码可读性,相比按引用传递,跟踪按值传递的对象更加困难,对象修改的地方变得难以捉摸。

结论:

大量的类并不需要可拷贝,也不需要一个拷贝构造函数或赋值操作(assignment operator)。不幸的是,如果你不主动声明它们,编译器会为你自动生成,而且是public的。

可以考虑在类的private中添加空的(dummy)拷贝构造函数和赋值操作,只有声明,没有定义。由于这些空程序声明为private,当其他代码试图使用它们的时候,编译器将报错。为了方便,可以使用宏DISALLOW_COPY_AND_ASSIGN:

// 禁止使用拷贝构造函数和赋值操作的宏
// 应在类的private:中使用
#define DISALLOW_COPY_AND_ASSIGN(TypeName) \
  TypeName(const TypeName&);               \
  void operator=(const TypeName&)

class Foo {
public:
  Foo(int f);
  ~Foo();

private:
  DISALLOW_COPY_AND_ASSIGN(Foo);
};

如上所述,绝大多数情况下都应使用DISALLOW_COPY_AND_ASSIGN,如果类确实需要可拷贝,应在该类的头文件中说明原由,并适当定义拷贝构造函数和赋值操作,注意在operator=中检测自赋值(self-assignment)情况。

在将类作为STL容器值得时候,你可能有使类可拷贝的冲动。类似情况下,真正该做的是使用指针指向STL容器中的对象,可以考虑使用std::tr1::shared_ptr。

5. 结构体和类(Structs vs. Classes)

仅当只有数据时使用struct,其它一概使用class。

在C++中,关键字struct和class几乎含义等同,我们为其人为添加语义,以便为定义的数据类型合理选择使用哪个关键字。

struct被用在仅包含数据的消极对象(passive objects)上,可能包括有关联的常量,但没有存取数据成员之外的函数功能,而存取功能通过直接访问实现而无需方法调用,这儿提到的方法是指只用于处理数据成员的,如构造函数、析构函数、Initialize()、Reset()、Validate()。

如果需要更多的函数功能,class更适合,如果不确定的话,直接使用class。

如果与STL结合,对于仿函数(functors)和特性(traits)可以不用class而是使用struct。

注意:类和结构体的成员变量使用不同的命名规则。

6. 继承(Inheritance

使用组合(composition,译者注,这一点也是GoF在《Design Patterns》里反复强调的)通常比使用继承更适宜,如果使用继承的话,只使用公共继承。

定义:当子类继承基类时,子类包含了父基类所有数据及操作的定义。C++实践中,继承主要用于两种场合:实现继承(implementation inheritance),子类继承父类的实现代码;接口继承(interface inheritance),子类仅继承父类的方法名称。

优点:实现继承通过原封不动的重用基类代码减少了代码量。由于继承是编译时声明(compile-time declaration),编码者和编译器都可以理解相应操作并发现错误。接口继承可用于程序上增强类的特定API的功能,在类没有定义API的必要实现时,编译器同样可以侦错。

缺点:对于实现继承,由于实现子类的代码在父类和子类间延展,要理解其实现变得更加困难。子类不能重写父类的非虚函数,当然也就不能修改其实现。基类也可能定义了一些数据成员,还要区分基类的物理轮廓(physical layout)

结论:

所有继承必须是public的,如果想私有继承的话,应该采取包含基类实例作为成员的方式作为替代。

不要过多使用实现继承,组合通常更合适一些。努力做到只在“是一个”("is-a",译者注,其他"has-a"情况下请使用组合)的情况下使用继承:如果Bar的确“是一种”Foo,才令Bar是Foo的子类。

必要的话,令析构函数为virtual,必要是指,如果该类具有虚函数,其析构函数应该为虚函数。

译者注:至于子类没有额外数据成员,甚至父类也没有任何数据成员的特殊情况下,析构函数的调用是否必要是语义争论,从编程设计规范的角度看,在含有虚函数的父类中,定义虚析构函数绝对必要。

限定仅在子类访问的成员函数为protected,需要注意的是数据成员应始终为私有。

当重定义派生的虚函数时,在派生类中明确声明其为virtual。根本原因:如果遗漏virtual,阅读者需要检索类的所有祖先以确定该函数是否为虚函数(译者注,虽然不影响其为虚函数的本质)

7. 多重继承(Multiple Inheritance

真正需要用到多重实现继承(multiple implementation inheritance)的时候非常少,只有当最多一个基类中含有实现,其他基类都是以Interface为后缀的纯接口类时才会使用多重继承。

定义:多重继承允许子类拥有多个基类,要将作为纯接口的基类和具有实现的基类区别开来。

优点:相比单继承,多重实现继承可令你重用更多代码。

缺点:真正需要用到多重实现继承的时候非常少,多重实现继承看上去是不错的解决方案,通常可以找到更加明确、清晰的、不同的解决方案。

结论:只有当所有超类(superclass)除第一个外都是纯接口时才能使用多重继承。为确保它们是纯接口,这些类必须以Interface为后缀。

注意:关于此规则,Windows下有种例外情况(译者注,将在本译文最后一篇的规则例外中阐述)。

8. 接口(Interface

接口是指满足特定条件的类,这些类以Interface为后缀(非必需)。

定义:当一个类满足以下要求时,称之为纯接口:

1) 只有纯虚函数("=0")和静态函数(下文提到的析构函数除外);

2) 没有非静态数据成员;

3) 没有定义任何构造函数。如果有,也不含参数,并且为protected;

4) 如果是子类,也只能继承满足上述条件并以Interface为后缀的类。

接口类不能被直接实例化,因为它声明了纯虚函数。为确保接口类的所有实现可被正确销毁,必须为之声明虚析构函数(作为第1条规则的例外,析构函数不能是纯虚函数)。具体细节可参考Stroustrup的《The C++ Programming Language, 3rd edition》第12.4节。

优点:以Interface为后缀可令他人知道不能为该接口类增加实现函数或非静态数据成员,这一点对于多重继承尤其重要。另外,对于Java程序员来说,接口的概念已经深入人心。

缺点:Interface后缀增加了类名长度,为阅读和理解带来不便,同时,接口特性作为实现细节不应暴露给客户。

结论:。只有在满足上述需要时,类才以Interface结尾,但反过来,满足上述需要的类未必一定以Interface结尾。

9. 操作符重载(Operator Overloading

除少数特定环境外,不要重载操作符。

定义:一个类可以定义诸如+、/等操作符,使其可以像内建类型一样直接使用。

优点:使代码看上去更加直观,就像内建类型(如int)那样,重载操作符使那些Equals()、Add()等黯淡无光的函数名好玩多了。为了使一些模板函数正确工作,你可能需要定义操作符。

缺点:虽然操作符重载令代码更加直观,但也有一些不足

1) 混淆直觉,让你误以为一些耗时的操作像内建操作那样轻巧;

2) 查找重载操作符的调用处更加困难,查找Equals()显然比同等调用==容易的多;

3) 有的操作符可以对指针进行操作,容易导致bugs,Foo + 4做的是一件事,而&Foo + 4可能做的是完全不同的另一件事,对于二者,编译器都不会报错,使其很难调试;

4) 重载还有令你吃惊的副作用,比如,重载操作符&的类不能被前置声明。

结论:

一般不要重载操作符,尤其是赋值操作(operator=)比较阴险,应避免重载。如果需要的话,可以定义类似Equals()、CopyFrom()等函数。

然而,极少数情况下需要重载操作符以便与模板或“标准”C++类衔接(如operator<<(ostream&, const T&)),如果被证明是正当的尚可接受,但你要尽可能避免这样做。尤其是不要仅仅为了在STL容器中作为key使用就重载operator==或operator<,取而代之,你应该在声明容器的时候,创建相等判断和大小比较的仿函数类型。

有些STL算法确实需要重载operator==时可以这么做,不要忘了提供文档说明原因。

参考拷贝构造函数函数重载

10. 存取控制(Access Control

将数据成员私有化,并提供相关存取函数,如定义变量foo_及取值函数foo()、赋值函数set_foo()。

存取函数的定义一般内联在头文件中。

参考继承函数命名

11. 声明次序(Declaration Order

在类中使用特定的声明次序:public:在private:之前,成员函数在数据成员(变量)前。

定义次序如下:public:、protected:、private:,如果那一块没有,直接忽略即可。

每一块中,声明次序一般如下:

1) typedefs和enums;

2) 常量;

3) 构造函数;

4) 析构函数;

5) 成员函数,含静态成员函数;

6) 数据成员,含静态数据成员。

宏DISALLOW_COPY_AND_ASSIGN置于private:块之后,作为类的最后部分。参考拷贝构造函数

.cc文件中函数的定义应尽可能和声明次序一致。

不要将大型函数内联到类的定义中,通常,只有那些没有特别意义的或者性能要求高的,并且是比较短小的函数才被定义为内联函数。更多细节参考译文第一篇的内联函数

12. 编写短小函数(Write Short Functions

倾向于选择短小、凝练的函数。

长函数有时是恰当的,因此对于函数长度并没有严格限制。如果函数超过40行,可以考虑在不影响程序结构的情况下将其分割一下。

即使一个长函数现在工作的非常好,一旦有人对其修改,有可能出现新的问题,甚至导致难以发现的bugs。使函数尽量短小、简单,便于他人阅读和修改代码。

在处理代码时,你可能会发现复杂的长函数,不要害怕修改现有代码:如果证实这些代码使用、调试困难,或者你需要使用其中的一小块,考虑将其分割为更加短小、易于管理的若干函数。

______________________________________

译者:关于类的注意事项,总结一下:

1. 不在构造函数中做太多逻辑相关的初始化;

2. 编译器提供的默认构造函数不会对变量进行初始化,如果定义了其他构造函数,编译器不再提供,需要编码者自行提供默认构造函数;

3. 为避免隐式转换,需将单参数构造函数声明为explicit;

4. 为避免拷贝构造函数、赋值操作的滥用和编译器自动生成,可目前声明其为private且无需实现;

5. 仅在作为数据集合时使用struct;

6. 组合>实现继承>接口继承>私有继承,子类重载的虚函数也要声明virtual关键字,虽然编译器允许不这样做;

7. 避免使用多重继承,使用时,除一个基类含有实现外,其他基类均为纯接口;

8. 接口类类名以Interface为后缀,除提供带实现的虚析构函数、静态成员函数外,其他均为纯虚函数,不定义非静态数据成员,不提供构造函数,提供的话,声明为protected;

9. 为降低复杂性,尽量不重载操作符,模板、标准类中使用时提供文档说明;

10. 存取函数一般内联在头文件中;

11. 声明次序:public->protected->private;

12. 函数体尽量短小、紧凑,功能单一。

posted @ 2008-07-16 17:43 Fox 阅读(4802) | 评论 (4)编辑 收藏

原文地址:

  • 作用域

1. 命名空间(Namespaces)

在.cc文件中,提倡使用不具名的命名空间(unnamed namespaces,译者注:不具名的命名空间就像不具名的类一样,似乎被介绍的很少:-()。使用具名命名空间时,其名称可基于项目或路径名称,不要使用using指示符。

定义:命名空间将全局作用域细分为不同的、具名的作用域,可有效防止全局作用域的命名冲突。

优点:命名空间提供了(可嵌套)命名轴线(name axis,译者注:将命名分割在不同命名空间内),当然,类也提供了(可嵌套)的命名轴线(译者注:将命名分割在不同类的作用域内)。

举例来说,两个不同项目的全局作用域都有一个类Foo,这样在编译或运行时造成冲突。如果每个项目将代码置于不同命名空间中,project1::Foo和project2::Foo作为不同符号自然不会冲突。

缺点:命名空间具有迷惑性,因为它们和类一样提供了额外的(可嵌套的)命名轴线。在头文件中使用不具名的空间容易违背C++的唯一定义原则(One Definition Rule (ODR))

结论:根据下文将要提到的策略合理使用命名空间。

1) 不具名命名空间(Unnamed Namespaces)

在.cc文件中,允许甚至提倡使用不具名命名空间,以避免运行时的命名冲突:

namespace {                                   // .cc 文件中

// 命名空间的内容无需缩进
enum { UNUSED, EOF, ERROR };          // 经常使用的符号
bool AtEof() { return pos_ == EOF; }   // 使用本命名空间内的符号EOF

}  // namespace

然而,与特定类关联的文件作用域声明在该类中被声明为类型、静态数据成员或静态成员函数,而不是不具名命名空间的成员。像上文展示的那样,不具名命名空间结束时用注释// namespace标识。

不能在.h文件中使用不具名命名空间。

2) 具名命名空间(Named Namespaces)

具名命名空间使用方式如下:

命名空间将除文件包含、全局标识的声明/定义以及类的前置声明外的整个源文件封装起来,以同其他命名空间相区分。

// .h文件
namespace mynamespace {

// 所有声明都置于命名空间中
// 注意不要使用缩进
class MyClass {
public:
  ...
  void Foo();
};

}  // namespace mynamespace

// .cc文件
namespace mynamespace {

// 函数定义都置于命名空间中
void MyClass::Foo() {
  ...
}

}  // namespace mynamespace

通常的.cc文件会包含更多、更复杂的细节,包括对其他命名空间中类的引用等。

#include "a.h"

DEFINE_bool(someflag, false, "dummy flag");

class C;  // 全局命名空间中类C的前置声明
namespace a { class A; }  // 命名空间a中的类a::A的前置声明

namespace b {

...code for b...                // b中的代码

}  // namespace b

不要声明命名空间std下的任何内容,包括标准库类的前置声明。声明std下的实体会导致不明确的行为,如,不可移植。声明标准库下的实体,需要包含对应的头文件。

最好不要使用using指示符,以保证命名空间下的所有名称都可以正常使用。

// 禁止——污染命名空间
using namespace foo;

在.cc文件、.h文件的函数、方法或类中,可以使用using。

// 允许:.cc文件中
// .h文件中,必须在函数、方法或类的内部使用
using ::foo::bar;

在.cc文件、.h文件的函数、方法或类中,还可以使用命名空间别名。

// 允许:.cc文件中
// .h文件中,必须在函数、方法或类的内部使用

namespace fbz = ::foo::bar::baz;

2. 嵌套类(Nested Class)

当公开嵌套类作为接口的一部分时,虽然可以直接将他们保持在全局作用域中,但将嵌套类的声明置于命名空间中是更好的选择。

定义:可以在一个类中定义另一个类,嵌套类也称成员类(member class)

class Foo {

private:
  // Bar是嵌套在Foo中的成员类
  class Bar {
    ...
  };

};

优点:当嵌套(成员)类只在被嵌套类(enclosing class)中使用时很有用,将其置于被嵌套类作用域作为被嵌套类的成员不会污染其他作用域同名类。可在被嵌套类中前置声明嵌套类,在.cc文件中定义嵌套类,避免在被嵌套类中包含嵌套类的定义,因为嵌套类的定义通常只与实现相关。

缺点:只能在被嵌套类的定义中才能前置声明嵌套类。因此,任何使用Foo::Bar*指针的头文件必须包含整个Foo的声明。

结论:不要将嵌套类定义为public,除非它们是接口的一部分,比如,某个方法使用了这个类的一系列选项。

3. 非成员函数(Nonmember)、静态成员函数(Static Member)和全局函数(Global Functions)

使用命名空间中的非成员函数或静态成员函数,尽量不要使用全局函数。

优点:某些情况下,非成员函数和静态成员函数是非常有用的,将非成员函数置于命名空间中可避免对全局作用域的污染。

缺点:将非成员函数和静态成员函数作为新类的成员或许更有意义,当它们需要访问外部资源或具有重要依赖时更是如此。

结论:

有时,不把函数限定在类的实体中是有益的,甚至需要这么做,要么作为静态成员,要么作为非成员函数。非成员函数不应依赖于外部变量,并尽量置于某个命名空间中。相比单纯为了封装若干不共享任何静态数据的静态成员函数而创建类,不如使用命名空间。

定义于同一编译单元的函数,被其他编译单元直接调用可能会引入不必要的耦合和连接依赖;静态成员函数对此尤其敏感。可以考虑提取到新类中,或者将函数置于独立库的命名空间中。

如果你确实需要定义非成员函数,又只是在.cc文件中使用它,可使用不具名命名空间或static关联(如static int Foo() {...})限定其作用域。

4. 局部变量(Local Variables)

将函数变量尽可能置于最小作用域内,在声明变量时将其初始化。

C++允许在函数的任何位置声明变量。我们提倡在尽可能小的作用域中声明变量,离第一次使用越近越好。这使得代码易于阅读,易于定位变量的声明位置、变量类型和初始值。特别是,应使用初始化代替声明+赋值的方式。

int i;
i = f();        // 坏——初始化和声明分离
nt j = g();   // 好——初始化时声明

注意:gcc可正确执行for (int i = 0; i < 10; ++i)(i的作用域仅限for循环),因此其他for循环中可重用i。if和while等语句中,作用域声明(scope declaration)同样是正确的。

while (const char* p = strchr(str, '/')) str = p + 1;

注意:如果变量是一个对象,每次进入作用域都要调用其构造函数,每次退出作用域都要调用其析构函数。

// 低效的实现
for (int i = 0; i < 1000000; ++i) {
  Foo f;  // 构造函数和析构函数分别调用1000000次!
  f.DoSomething(i);
}

类似变量放到循环作用域外面声明要高效的多:

Foo f;  // 构造函数和析构函数只调用1次
for (int i = 0; i < 1000000; ++i) {
  f.DoSomething(i);
}

5. 全局变量(Global Variables)

class类型的全局变量是被禁止的,内建类型的全局变量是允许的,当然多线程代码中非常数全局变量也是被禁止的。永远不要使用函数返回值初始化全局变量。

不幸的是,全局变量的构造函数、析构函数以及初始化操作的调用顺序只是被部分规定,每次生成有可能会有变化,从而导致难以发现的bugs。

因此,禁止使用class类型的全局变量(包括STL的string, vector等等),因为它们的初始化顺序有可能导致构造出现问题。内建类型和由内建类型构成的没有构造函数的结构体可以使用,如果你一定要使用class类型的全局变量,请使用单件模式(singleton pattern)

对于全局的字符串常量,使用C风格的字符串,而不要使用STL的字符串:

const char kFrogSays[] = "ribbet";

虽然允许在全局作用域中使用全局变量,使用时务必三思。大多数全局变量应该是类的静态数据成员,或者当其只在.cc文件中使用时,将其定义到不具名命名空间中,或者使用静态关联以限制变量的作用域。

记住,静态成员变量视作全局变量,所以,也不能是class类型!

______________________________________

译者:这一篇主要提到的是作用域的一些规则,总结一下:

1. .cc中的不具名命名空间可避免命名冲突、限定作用域,避免直接使用using提示符污染命名空间;

2. 嵌套类符合局部使用原则,只是不能在其他头文件中前置声明,尽量不要public;

3. 尽量不用全局函数和全局变量,考虑作用域和命名空间限制,尽量单独形成编译单元;

4. 多线程中的全局变量(含静态成员变量)不要使用class类型(含STL容器),避免不明确行为导致的bugs

作用域的使用,除了考虑名称污染、可读性之外,主要是为降低耦合度,提高编译、执行效率。

posted @ 2008-07-14 15:49 Fox 阅读(7609) | 评论 (27)编辑 收藏

原文地址:

  • 背景

Google的开源项目大多使用C++开发。每一个C++程序员也都知道,C++具有很多强大的语言特性,但这种强大不可避免的导致它的复杂,这种复杂会使得代码更易于出现bug、难于阅读和维护。

本指南的目的是通过详细阐述在C++编码时要怎样写、不要怎样写来规避其复杂性。这些规则可在允许代码有效使用C++语言特性的同时使其易于管理。

风格,也被视为可读性,主要指称管理C++代码的习惯。使用术语风格有点用词不当,因为这些习惯远不止源代码文件格式这么简单。

使代码易于管理的方法之一是增强代码一致性,让别人可以读懂你的代码是很重要的,保持统一编程风格意味着可以轻松根据“模式匹配”规则推断各种符号的含义。创建通用的、必需的习惯用语和模式可以使代码更加容易理解,在某些情况下改变一些编程风格可能会是好的选择,但我们还是应该遵循一致性原则,尽量不这样去做。

本指南的另一个观点是C++特性的臃肿。C++是一门包含大量高级特性的巨型语言,某些情况下,我们会限制甚至禁止使用某些特性使代码简化,避免可能导致的各种问题,指南中列举了这类特性,并解释说为什么这些特性是被限制使用的。

由Google开发的开源项目将遵照本指南约定。

注意:本指南并非C++教程,我们假定读者已经对C++非常熟悉。

  • 头文件

通常,每一个.cc文件(C++的源文件)都有一个对应的.h文件(头文件),也有一些例外,如单元测试代码和只包含main()的.cc文件。

正确使用头文件可令代码在可读性、文件大小和性能上大为改观。

下面的规则将引导你规避使用头文件时的各种麻烦。

1. #define的保护

所有头文件都应该使用#define防止头文件被多重包含(multiple inclusion),命名格式当是:<PROJECT>_<PATH>_<FILE>_H_

为保证唯一性,头文件的命名应基于其所在项目源代码树的全路径。例如,项目foo中的头文件foo/src/bar/baz.h按如下方式保护:

#ifndef FOO_BAR_BAZ_H_
#define FOO_BAR_BAZ_H_
...
#endif // FOO_BAR_BAZ_H_

2. 头文件依赖

使用前置声明(forward declarations)尽量减少.h文件中#include的数量。

当一个头文件被包含的同时也引入了一项新的依赖(dependency),只要该头文件被修改,代码就要重新编译。如果你的头文件包含了其他头文件,这些头文件的任何改变也将导致那些包含了你的头文件的代码重新编译。因此,我们宁可尽量少包含头文件,尤其是那些包含在其他头文件中的。

使用前置声明可以显著减少需要包含的头文件数量。举例说明:头文件中用到类File,但不需要访问File的声明,则头文件中只需前置声明class File;无需#include "file/base/file.h"

在头文件如何做到使用类Foo而无需访问类的定义?

1) 将数据成员类型声明为Foo *或Foo &;

2) 参数、返回值类型为Foo的函数只是声明(但不定义实现);

3) 静态数据成员的类型可以被声明为Foo,因为静态数据成员的定义在类定义之外。

另一方面,如果你的类是Foo的子类,或者含有类型为Foo的非静态数据成员,则必须为之包含头文件。

有时,使用指针成员(pointer members,如果是scoped_ptr更好)替代对象成员(object members)的确更有意义。然而,这样的做法会降低代码可读性及执行效率。如果仅仅为了少包含头文件,还是不要这样替代的好。

当然,.cc文件无论如何都需要所使用类的定义部分,自然也就会包含若干头文件。

译者注:能依赖声明的就不要依赖定义。

3. 内联函数

只有当函数只有10行甚至更少时才会将其定义为内联函数(inline function)

定义(Definition):当函数被声明为内联函数之后,编译器可能会将其内联展开,无需按通常的函数调用机制调用内联函数。

优点:当函数体比较小的时候,内联该函数可以令目标代码更加高效。对于存取函数(accessor、mutator)以及其他一些比较短的关键执行函数。

缺点:滥用内联将导致程序变慢,内联有可能是目标代码量或增或减,这取决于被内联的函数的大小。内联较短小的存取函数通常会减少代码量,但内联一个很大的函数(译者注:如果编译器允许的话)将戏剧性的增加代码量。在现代处理器上,由于更好的利用指令缓存(instruction cache),小巧的代码往往执行更快。

结论:一个比较得当的处理规则是,不要内联超过10行的函数。对于析构函数应慎重对待,析构函数往往比其表面看起来要长,因为有一些隐式成员和基类析构函数(如果有的话)被调用!

另一有用的处理规则:内联那些包含循环或switch语句的函数是得不偿失的,除非在大多数情况下,这些循环或switch语句从不执行。

重要的是,虚函数和递归函数即使被声明为内联的也不一定就是内联函数。通常,递归函数不应该被声明为内联的(译者注:递归调用堆栈的展开并不像循环那么简单,比如递归层数在编译时可能是未知的,大多数编译器都不支持内联递归函数)。析构函数内联的主要原因是其定义在类的定义中,为了方便抑或是对其行为给出文档。

4. -inl.h文件

复杂的内联函数的定义,应放在后缀名为-inl.h的头文件中。

在头文件中给出内联函数的定义,可令编译器将其在调用处内联展开。然而,实现代码应完全放到.cc文件中,我们不希望.h文件中出现太多实现代码,除非这样做在可读性和效率上有明显优势。

如果内联函数的定义比较短小、逻辑比较简单,其实现代码可以放在.h文件中。例如,存取函数的实现理所当然都放在类定义中。出于实现和调用的方便,较复杂的内联函数也可以放到.h文件中,如果你觉得这样会使头文件显得笨重,还可以将其分离到单独的-inl.h中。这样即把实现和类定义分离开来,当需要时包含实现所在的-inl.h即可。

-inl.h文件还可用于函数模板的定义,从而使得模板定义可读性增强。

要提醒的一点是,-inl.h和其他头文件一样,也需要#define保护。

5. 函数参数顺序(Function Parameter Ordering)

定义函数时,参数顺序为:输入参数在前,输出参数在后。

C/C++函数参数分为输入参数和输出参数两种,有时输入参数也会输出(译者注:值被修改时)。输入参数一般传值或常数引用(const references),输出参数或输入/输出参数为非常数指针(non-const pointers)。对参数排序时,将所有输入参数置于输出参数之前。不要仅仅因为是新添加的参数,就将其置于最后,而应该依然置于输出参数之前。

这一点并不是必须遵循的规则,输入/输出两用参数(通常是类/结构体变量)混在其中,会使得规则难以遵循。

6. 包含文件的名称及次序

将包含次序标准化可增强可读性、避免隐藏依赖(hidden dependencies,译者注:隐藏依赖主要是指包含的文件中编译时),次序如下:C库、C++库、其他库的.h、项目内的.h。

项目内头文件应按照项目源代码目录树结构排列,并且避免使用UNIX文件路径.(当前目录)和..(父目录)。例如,google-awesome-project/src/base/logging.h应像这样被包含:

#include "base/logging.h"

dir/foo.cc的主要作用是执行或测试dir2/foo2.h的功能,foo.cc中包含头文件的次序如下:

    dir2/foo2.h(优先位置,详情如下)
    C系统文件
    C++系统文件
    其他库头文件
    本项目内头文件

这种排序方式可有效减少隐藏依赖,我们希望每一个头文件独立编译。最简单的实现方式是将其作为第一个.h文件包含在对应的.cc中。

dir/foo.cc和dir2/foo2.h通常位于相同目录下(像base/basictypes_unittest.cc和base/basictypes.h),但也可在不同目录下。

相同目录下头文件按字母序是不错的选择。

举例来说,google-awesome-project/src/foo/internal/fooserver.cc的包含次序如下:

#include "foo/public/fooserver.h"  // 优先位置

#include <sys/types.h>
#include <unistd.h>

#include <hash_map>
#include <vector>

#include "base/basictypes.h"
#include "base/commandlineflags.h"
#include "foo/public/bar.h"

______________________________________

译者:英语不太好,翻译的也就不太好。这一篇主要提到的是头文件的一些规则,总结一下:

1. 避免多重包含是学编程时最基本的要求;

2. 前置声明是为了降低编译依赖,防止修改一个头文件引发多米诺效应;

3. 内联函数的合理使用可提高代码执行效率;

4. -inl.h可提高代码可读性(一般用不到吧:D);

5. 标准化函数参数顺序可以提高可读性和易维护性(对函数参数的堆栈空间有轻微影响,我以前大多是相同类型放在一起);

6. 包含文件的名称使用.和..虽然方便却易混乱,使用比较完整的项目路径看上去很清晰、很条理,包含文件的次序除了美观之外,最重要的是可以减少隐藏依赖,使每个头文件在“最需要编译”(对应源文件处:D)的地方编译,有人提出库文件放在最后,这样出错先是项目内的文件,头文件都放在对应源文件的最前面,这一点足以保证内部错误的及时发现了。

posted @ 2008-07-10 17:35 Fox 阅读(10808) | 评论 (3)编辑 收藏

sudo使普通用户具有root用户的权限,减少root登录,还有日志记录……,主要是为了安全,暂时没有体会@.@。

1. sudo

1) 安装sudo:apt-get install sudo

2) 添加sudo:

  a. chmod +w /etc/sudoers,允许写
  b. vim /etc/sudoers
  c. 添加:newsudo   ALL=(ALL) NOPASSWD: ALL
  d. chmod 0440 /etc/sudoers,更改sudoers文件权限,使之可以修改如vimrc等文件

2. 配置Vim

1) 安装最新Vim:apt-get install vim

2) 语法加亮:vim /etc/vim/vimrc,去掉systax on前的注释"

3) 显示行号:set nu

4) 自动缩进:set autoindent

  C语言缩进:set cindent

3. 配置Profile

1) vim /etc/profile

2) 添加:alias ls='ls --color=auto'

3) logout

更多零零散散的配置就不写了,因为有TX在我的上一篇回帖说我写的很水,说实话,我有点被冤枉。就我目前对Debian的认知水平,也就到这份上了。有句话说的好:工欲善其事,必先利其器。这次哥哥我重新杀回到Debian来,绝对不会只是玩玩儿,我会负责的

后面就会开始用make慢慢写一些东西了。说到底,这一系列日记只是对工具的一个熟悉过程而已。

PS:

GR今天在说,我似乎应该把时间花到有用的东西上面,言下之意,我当下做的事没什么意义,呵呵,其实,转到Linux平台下熟悉一下,也算是有意义的事,对我来讲,闲的无聊的时候看毛片也算有意义的事:D。

当然,话说回来,最近关乎学习的另外一件有意义的事情是看看Kevin推荐的《C++ Templates》和《Modern C++ Design》。

之所以婆婆妈妈的写下来,主要因为我这个人羞耻感不强 + 健忘,写下来还不去做会让我觉得难堪;-)。

posted @ 2008-06-06 01:33 Fox 阅读(2715) | 评论 (7)编辑 收藏

题记:哥(我)基本是Linux盲,刚开始接触,有点像对females的亢奋感。

心血来潮,再次装了VMware,两个机子分别装了简体中文和英文的Debian,只有base-system

前前后后装了不下五、六遍,算是对Debian的安装过程熟悉一下,有点弱智:D。装好系统之后,只装了Vimapt-get install vim

Vim是一个很好玩的东西,自从我在06年结束了TC 2.0的时代之后,已经很久没有在纯文本模式下做过任何操作了(偶尔的装过Debian,用过Vim都不算吧:D)。虽然我一向声称更喜欢命令操作而不是鼠标移动,但那毕竟是在用了六、七年的Windows下(虽然也不长:(),看来多少是有点叶公好龙的感觉。

INSERT mode下,费了半天劲,敲了十来行代码,总觉得自己的操作太笨。Google了一下,国内有一个比较好的专门介绍Vim中文文档的site

原来,在NORMAL mode下,:help可以搞定一切。只是这个help太长了,一时半会儿看不完。退出Vim(:q!)后,命令行运行vimtutor是一个基本教程,还有中文版的,打开全是乱码。

apt-get install zhcon

期间要我插入netinst的disk,但我是用VMware,干脆把/etc/apt/sources.list的cdrom去掉,直接用网络了:-(。

装好之后,/usr/share/vim/vim70/tutor下的tutor.zh.big5tutor.zh.euc还是乱码显示,用Vim打开,:set fileencoding=utf-8之后也一样,难道是因为我选择的英文版吗?后面看了tw一篇非常详尽的图解,获益匪浅。原来是没有启动zhcon#--"!

这儿有个小插曲,执行zhcon,机器就挂掉了,屡试不爽。Google到的解决方案:zhcon --utf8 --drv=vga

感觉执行zhcon之后,文本显示速度巨慢,慢到不可忍受,遂放弃,就当学习英文好了:-)。

之后,把笔记本上的Debian装成简体中文,晚上回来后,笔记本不能上网了,拿GR的机器装Debian,发现用的netinst的iso总是出错,近乎崩溃。该死的艾普宽带又绑定MAC,笔记本上不成网。只好乖乖的学习Vim的使用:D。

执行vimtutor,打开Vim的Tutor,一晚上练了前面四课,还是比较有效果。

对Tutor中常常note的一句话很有体会:do not try to memorize, learn by usage。所以,如果你想知道怎么用Vim,装上

PS01:

1) logout时,遇到There are stopped jobs,才想起来自己之前不小心错误退出Vim,用job看了一下,果然Vim已经stopped,fg之后,正常退出Vim。

2) 刚开始根本没有习惯Vim的光标移动:上下左右对应kjhl,慢慢习惯了之后,在Windows下老是想用kjhl操作,才发觉使用kjhl比用方向键要方便的多,不是吗?

PS02:

1) 近11点又和GR去门口吃冷锅串串,小high一下,每天晚上整点小吃,再整上两瓶,用我在公司推广的流行语说:整的好哦!

2) 再补上一句:明天哥还带你来!

3) 后面冒出一句:操你大爷!

4) 早睡早起身体好!

本周签名:谈政治就像看毛片,过瘾,但要偷偷的,多了反胃……

posted @ 2008-06-05 00:21 Fox 阅读(2553) | 评论 (5)编辑 收藏

写的太杂,实在没法写题目,就用这一周的签名吧,很合现在的心境。

Kevin眼中的我,大概是个重视理论算法胜过编程实践的人,而我的算法和理论基础尚差的出奇(可能这就是知耻而后勇吧:D),可见我的编程实践又会多么的差了。Bugs更是对我整日沉浸于这些不着边际的“空中楼阁”颇有微词,甚至嗤之以鼻。今日若不是要把自己前段时间的豆腐渣粉饰一番,我依然不愿去考虑多线程的具体实现,或者说不是不愿,是不敢,总有一种临深履薄之感。

纵然如此,为了更好的完成工作,我还是拉来Kevin,劳他为我讲解一下多线程,可能是因为我从未仔细看过boost等C++开源库的原因吧,我对于结构封装本身并没有多少概念。说句实话,看到那些模板我就头大,心里想:本来一个简单的东西,为什么要搞的那么复杂呢?当然,我知道,这是因为我对其缺乏了解,在对一样东西没有完全理解就妄测其好坏是自卑的表现:D,所以也请Kevin原谅我的无知,顺便致谢;-)。

还是稍微提一下多线程的东西吧,因为这一次改动并不很大,因此只言片语难以面面俱到,也请各位TX不必较真儿。这儿只是说一下我是怎么偷懒把之前没有使用多线程的I/O部分修改成多线程I/O的,I/O的细节不再详述,而且这台机器上面因为没有VS,仅凭记忆,如果有什么差错,请帮我指出来了:-)。

在项目启动后的初始化中初始化I/O线程:

 1 void SomeApp::Init(void)
 2 {  
 3       // do some other things 
 4 
 5     CIOOperator::Init();  // InitializeCriticalSection for I/O queue(s)
 6 
 7     forint i=0; i<nIOThreadsNum; ++i ) 
 8     { 
 9         CIOOperator *pOpObj = new CIOOperator; 
10         pOpObj->Create(); 
11         m_vecIOThreads.push_back(pOpObj); 
12     } 
13 
14       // do some other things 
15 }


在项目退出前结束I/O线程: 

 1 void SomeApp::Release(void
 2 {  
 3       // do some other things 
 4 
 5     vector<CIOOperator *>::iterator it = m_vecIOThreads.begin(); 
 6     for( ; i!=m_vecIOThreads.end(); ++it ) 
 7     { 
 8         it->End(); 
 9         delete (*it); 
10     } 
11     m_vecIOThreads.clear();
12 
13     CIOOperator::Release();  // DeleteCriticalSectionfor I/O queue(s)
14 
15       // do some other things 
16 


I/O线程函数:  

 1 void ThreadFunc( void *pArgument )
 2 {
 3       // do some other things, like exception handling 
 4 
 5     (CIOOperator *)pArgument->Run();  // operating I/O queue(s) until exit
 6 
 7       // do some other things 
 8 
 9     _endthreadex();
10 }

说多线程复杂,无外乎线程的退出策略同步机制调试异常处理等等。多少还是需要一些知识(尤其是同步)和经验(尤其是调试)。

再回来说一下最近比较关注的算法吧,虽然为某些人所不齿,甚至公然批评我最近比较松懈,实在令我难堪的紧。我又没有消极怠工,难道编写代码是积极,学习算法就是消极吗?乡下来的,且不必理他。当然,多少还是需要注意一下分寸吧。

在受到上次解决烙饼排序问题的打击之后,我开始反思:自己思考和解决问题的角度怎么就那么简单和狭隘?细细想来,从本科毕业之后,几乎再没翻过算法的书,几乎再没做过算法的题,写代码只是为了糊口,只能糊口的代码自然只能以垃圾形容。意识粗糙,操作离谱,整个一下里巴人。

在一番深深的自责之后,痛定思痛,痛何如哉,才感觉自己关于算法的思维空间已经局限于if-else、do-while,连穷举、分治、贪心、回溯这些以前念书时天天挂在嘴边侃侃而谈的常用算法都没有概念了,遑论动态规划、最小二乘法、线性回归等复杂一些的(非)数值算法,关于数据结构的思维空间也已经局限于vector、list、map了,再也没有回忆过stack、tree、graph这些读死书、死读书得来的所谓知识。 

可悲啊,为什么拿到一个困难一点的问题,就只知道画图、编码,而不知道组织算法呢?甚至连这个问题到底有无多项式时间解都不去考虑。然而,一提谁都知道:算法复杂性——数据结构第0章就会提到的基础,真正分析起来,却是力不从心。

所以,接着扫扫盲吧,实在没有必要去搞很多艰深的东西,本来想接下来就写NP难题,可是近来工作上的事情确实有些多,之前一篇已经是被Alex(这家伙却至今未开张……)“催出来”的了。

手头只有MIT英文版的《Introduction to Algorithms》ed.2,于是就从网上找了中文电子版,居然是上个世纪94年南京大学译的第一版。看算法的话,多半是以这本书和Wikipedia为主了。

PS: 另外做的一点事情,似乎和词法分析异常处理等多少有些关联,内容相当琐碎,此刻不再赘述。

对了,和工作、学习并不那么相干的事情就是,今天和几个同事出去钓了几个小时鱼,收获嘛,保密:D。

近期考虑的关键词:无缝世界 网游安全 算法导论 兄弟激情

posted @ 2008-05-11 02:25 Fox 阅读(1743) | 评论 (8)编辑 收藏

仅列出标题
共7页: 1 2 3 4 5 6 7