Effective C++学习
导读
声明式
extern int x;
std::size_t numDigits(int num);
class Person;
template<typename T>
class GraphNode;
签名式
std::size_t (int); //这个函数获得一个int,返回一个size_t
定义式
int x;
std::size_t numDigits(int num)
{
std::size_t digitSoFar = 1;
while((num/10) != 0) ++digitSoFar;
return digitSoFar;
}
class Person{
public:
Person();
~Person();
};
template<typename T>
class GraphNode{
public:
GraphNode();
~GraphNode();
};
初始化(通常由构造函数执行,所谓默认构造函数就是可以被调用但不含实参, 要不无参要不都有默认参数)
class A{
public:
A();
};
------------------------------------------------------------------------
class B{
public:
explicit B(int x = 0; bool y = true); //explicit阻止隐式类型转换,但仍可被显式类型转换
};
------------------------------------------------------------------------
class C{
public:
explicit C(int x); //这个就不是default构造函数
};
尽量使用explicit!!
------------------------------------------------------------------------
class D{
public:
D(); //默认构造函数
D(const D& d) //拷贝构造
D& operator=(const D& d)//拷贝赋值构造
}
D d1; //默认
D d2(d1); //拷贝
d1 = d2; //拷贝赋值
D d3 = d1; //拷贝!!!! 区别就在于,新创建的d3是拷贝,已经存在的是拷贝赋值
class作为入参的时候,尽量不要值传递(本质上会用拷贝构造),用引用传递
条款01 视c++为一个语言联邦
何为联邦,即看作很多语言的组合
- C语言:以C为基础,但缺少模板、异常、重载
- object-oriented C++: 包括类(析构 构造) 封装 继承 多态 虚函数(动态绑定)
- Template c++: 泛型编程
- STL:包含容器 算法 迭代器 函数对象
- c一般用值传递,c++引用传递
条款02 尽量以const enum inline 替换#define
- 意思是:尽量用编译器,而不是预处理器
- 原因1:用#define A 1.2 的话,A可能不会进入记号表,后面报错不会有A,只会有1.2 并不知道具体是哪
- 原因2:用#define会导致每个地方都展开,生成很多oject code(类机器语言) 但如果用const double就不会
- 使用const的时候,一定要用指向的常量的指针
- define的变量不具有作用域,必要时需要在class privite里定义
const char* str = "hello"; //指针str指向常量,不能通过str修改hello的值,但是可以改变指向, 不能改char*,即*str = "abc"报错
char* const str = "hello"; //指针指向一块固定的地方,但是可以改这块地址的值,,不能改str,即str = &p2 报错
const char* const str = "hello" //不能通过指针str修改hello的值,也不能改变指向
//所以 我们一般用第三个来代替#define,同时用string代替char*
const std::string str("hello")
//静态成员变量一般不允许类内初始化,可能会导致一个很尴尬的情况,如下
class Person{
privete:
//static const int age;
enum {age = 5};
int score[age]; //age不能类内初始化,所以这个地方会报错,解决方案是用enum
}
enum变量无法取他的地址,这个和define很像
//宏展开函数会带来意想不到的后果
#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b)) //用 a b 较大的一个调用f(x)函数
//因为直接宏替换,会导致第一次选择b(a+1),第二次选择a (a+2)
int a = 5, b = 0;
CALL_WITH_MAX(++a, b);
CALL_WITH_MAX(++a, b+10);
//使用模板+inline可以解决此问题
template <typename T>
inline void CallWithMax(const T&a,const T&b)
{
f(a > b ? a : b)
}
总结 : 单纯变量用const/enum替换#define,形似函数的宏,用inline替换
条款03 尽量使用const
- 迭代器本身可以++,迭代器前的const 相当于T* const xxx
std::vector<int> vec;
const std::vector<int>::iterator it = vec.begin(); //相当于T* const
*it = 10; //OK
++it; //NOK
std::vector<int>::const_iterator it = vec.begin(); //相当于const T*
*it = 10; //NOK
++it; //OK
- 类实例化为const 就只能调用const成员函数
- 普通类的实例,是非const都能调用
- const成员函数只能修改mutable成员变量(如果const成员函数返回成员的一个指针,还是可以通过指针修改的,这样不算)
+ 正常优先调用重载的非const函数,非要调用const要这么做 (也有可能避免重载函数重复):
class Person {
public:
const char& operator[](std::size_t pos) const{
return text[pos];
}
char& operator[](std::size_t pos) {
//return text[pos];
return const_cast<char&>(static_cast<const Person&>(*this)[pos];
// 含义是 把*this(this是指向当前对象指针 *解引用 表示当前对象) 强转为const对象,强制调用const的成员函数,然后把结果的const强制去掉(const_cast)
}
privite:
string text;
}
条款04 使用前初始化
- 对于class成员变量,推荐使用成员初值列,以声明顺序初始化,而非初值列的顺序!
Person::Person(const std::string &name, const std::vector<int> &phones) : name(name), phones(phones), age(0) {} // name phones ages都是成员变量,三者都是拷贝构造
Person::Person() : name(), phones(), age(0) {} 默认构造
- A全局static变量的初始化依赖另一个编译单元的全局static变量B,但是B还没初始化,问题出现了(全局static变量只是说具有从头到尾生命周期的变量)
// ✅ 有问题的变量(跨编译单元依赖)
MyClass objA(1); // 全局变量(外部链接)
static MyClass objB(2); // 全局变量(内部链接)
namespace { MyClass objC(3); } // 匿名命名空间中的变量
- 解决方案是不要直接ObjA objA 初始化,而是另起一个函数
ObjA &getObj()
{
static ObjA obj;
return obj;
}
外部本来可能是extern ObjA obj ,现在改为调用getObj函数
条款05 了解c++默认编写调用的函数
- // 以下四个都是需要时自动创建(public且inline)的,构造、析构、拷贝构造、拷贝赋值(定义了则不会自动创建)
- 拷贝构造和拷贝复制只处理源对象的非静态成员变量,对于内置类型复制每个bit,对于其他如string直接调用它的构造函数
- 如果class成员里是引用(不允许改变引用指向)或者class等,会编译失败,而不是编译器帮你创建拷贝构造拷贝赋值
class Empty {
public:
Empty() {}
Empty(const Empty& empty){}
~Empty(){}
Empty()& operator=(const Empty& empty){}
}
条款06 不想编译器自动生成的函数需手动删除
- 方式1:声明为private且不写定义,正常编译就报错,要是想通过friend等方式绕过,链接会报错
- 方式2:父类的对应函数声明为private, 继承这个父类,编译就全爆了,包含friend的绕过方法
- 方式3: = delete;
条款07 为多子父类声明虚析构
- 父类的析构非虚,只析构自己不管子类,因此有子类的必须用虚析构
- 类内有其他虚函数的,必须用虚析构
- 此外情况不要用虚析构,增加开销(多一个虚函数表)
- final关键字禁止派生子类,stl基本都不能派生,有类似的问题
class Person final {}

浙公网安备 33010602011771号