好处:代码整体结构清晰、明了。java里强制如此。
比如:UrlEncoder
FileParser
如:
有符号类型 无符号类型 描述
int8_t uint8_t 宽度恰为8的有/无符号整数类型
int16_t uint16_t 宽度恰为16的有/无符号整数类型
int32_t uint32_t 宽度恰为32的有/无符号整数类型
int64_t uint64_t 宽度恰为64的有/无符号整数类型
intptr_t uintptr_t 足以保存指针的有/无符号整数类型
尽量不要使用int、long等,因为其宽度取决于操作系统和编译器。比如long可能是32bit或64bit。
使用单例,封装性更好。
在已有条件/循环语句代码上增加新代码时不容易出错
switch语句中要有default分支,保证在遗漏case标签处理时能够有一个缺省的处理行为。
如果switch条件变量是枚举类型,加上default分支处理有些多余,现代编译器都具备检查是否在switch语句中遗漏了某些枚举值的case分支的能力,会有相应的warning提示。 另外,加上default分支处理,一些静态代码分析工具会报告default分支是死代码(deadcode)的warning。java也有这种情况。
缺点:没有任何业务含义;修改麻烦
//xx
const MAX_VALUE = 120; //xx
if (a > MAX_VALUE )
if(b < MAX_VALUE )
现在有git、svn这样的版本管理工具,删掉也不怕丢失。
#ifndef VOS_INCLUDE_TIMER_TIMER_H
#define VOS_INCLUDE_TIMER_TIMER_H
...
#endif
建议按照稳定度排序:
举例来说,Foo.cpp中包含头文件的次序如下::
#include "Foo/Foo.h"
#include
#include
#include
#include
#include "platform/Base.h"
#include "platform/Framework.h"
#include "project/public/Log.h"
原因:容易造成符号冲突。
在CPP文件的实现代码处可以视情况使用。
这跟java是不同的,java里类的成员变量会由编译器自动初始化。
class Foo {
private:
Foo(const Foo&);
Foo& operator=(const Foo&);
};
C++11下也可用delete关键字(注意这里的修饰符是public):
class Foo {
public:
Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;
};
结果不可知。
比如在基类的构造函数里调用虚函数,有可能触发派生类的构造,要知道这时候基类还没构造完呢。父之不存,子将焉附!
同样的道理,基类的析构函数调用虚函数,这个行为可能下放到派生类,但此时派生类已销毁了!(派生类销毁在基类之前,构造则在基类之后)。
原因:只有基类析构函数是虚拟的,才能保证派生类的析构函数被调用。
好处:引用肯定非空,避免繁琐的NULL检查。这是C++强于java的地方,java里即使引用也可能为null。
把:
f(A* a){
…
}
改成
f(const A& a) {
…
}
因为多重继承使用过程中有下面的典型问题:
数据不可变多安心啊,且并发编程下可以少费脑子去考虑锁的问题。
dynamic_cast:downcast,类似于java的instanceof,尽量少用,往往是类的继承关系设计的不合理。
reinterpret_cast:这个就是强转,很危险。
const_cast:去掉数据的不可变性,不利于代码的稳定和并发操作
f(A* a)
A* p = new A();
auto_ptr p(new A());
C++11普遍推广之前,为方便指针管理,一般要用auto_ptr。但这玩意也有缺点:它有一个隐式的所有权转移行为,容易出问题,比如不能放容器里、也不建议通过函数参数传递。
使用auto_ptr常见的有两种场景,一是作为智能指针传递到产生auto_ptr的函数外部,二是使用auto_ptr作为RAII管理类,在超出auto_ptr的生命周期时自动释放资源。 对于第1种场景,可以使用std::shared_ptr来代替。 对于第2种场景,可以
使用C++11标准中的std::unique_ptr来代替。其中std::unique_ptr是std::auto_ptr的代替品,支持显式的所有权转移。
具体到unique_ptr和shared_ptr,优先使用前者。因为:
严格的说来,unique_ptr是C++14的标准,晚于shared_ptr。我们只需使用gcc5及以上的版本就没问题。
unique_ptr可以认为是“禁掉了隐式所有权转移的auto_ptr”(实现策略是禁用了拷贝构造和赋值运算符重载)。如果需要明确的所有权转移,使用std::move。所以有:
auto foo = std::make_unique();
auto foo1 = foo; //Cannot compile!
auto foo2 = std::move(foo); //it's ok, foo has nothing now
比如“模板元编程”,当年很痴迷,觉得很适合炫技,现在觉得太烧脑,代码不好维护。
auto用于自动类型推断,本质上是一个类型占位符,而非真实的类型。可用于替代冗长的类型名(比如iterator)
注意:auto只能替代值类型,所以如果传一个引用给auto,它会自动去除引用,比如下面的例子:
class Foo
{
public:
Foo() = default;
Foo(const Foo&) = delete;
Foo& operator=(const Foo&) = delete;
void doSth()
{
std::cout << "do sth" << std::endl;
}
};
Foo s;
Foo& ref = s;
auto s1 = ref; //Cannot compile
要编译正确,需改成:
auto& s1 = ref;
这个是照抄了Java的@override注解,用于检查派生类的虚函数定义是否与基类一致,重构的时候能起一定的保护作用。
Lambda有几个优点:
Lambda在Java里是使用匿名类来实现的,upvalue作为匿名类构造器的参数传递进来,从而做到长期持有,C++估计是类似的实现机制。由于java有gc,所以不用考虑upvalue生存周期的问题。但C++则不然,在C++里使用Lambda,我们引用的upvalue可能只是栈上的对象,在lambda执行时已经不存在了,这是需要特别注意的。
Lambda的形式:
[upvalue] (lambda参数) mutable|exception -> 返回值类型 {函数体}
upvalue(也叫函数对象参数)有以下形式:
lambda参数有一点不好,必须明确给出参数类型,做不到自动推断(java和scala里都是可以自动推断的)。
返回值类型不是必给的,编译器可以推断出来。
一个例子:
std::vector l, r;
l.push_back("e0");
l.push_back("e1");
std::transform(l.begin(), l.end(), std::back_inserter(r), [](const std::string& v){return v + "_wrapper";});
std::for_each(r.begin(), r.end(), [](const std::string& v){std::cout << v << std::endl;});