您的位置:首页 > 编程语言 > C语言/C++

Effective C++笔记 【1. Accustoming yourself to C++】

2010-10-19 22:56 253 查看

让自己习惯C++

01:视C++为一个语言联邦

C++可以认为是4个次语言(sublanguage)组成:

C。 区块(blocks),语句(statements),预处理(preprocessor),内置数据类型(built-in data types),数组(arrays),指针(pointers) 统统来自C。

Object-Oriented C++。 是原来 C with Classes 所诉求的: 类(classes) ,封装(encapsulation),继承(inheritance),多态(polymorphism),虚函数(动态绑定)…… 这是面向对象设计的古典守则

Template C++。 这是C++ 反省编程的部分。

STL。

Tips: 高效的编程守则要求视状况变化,取决于你使用C++的那部分子语言。

比如对内置类型(C)传值调用比较高效,而自定义类型(Object-Oriented C++)则传 const引用 高效,但当你跨入STL会发现,由于迭代器和函数对象都是在C指针之上塑造出的,所以对STL迭代器和函数对象而言,传值守则再次适用。

02:尽量以 const , enum , inline 替换 #define

或者说”宁可 以编译器 替换 预处理器“比较好。

因为 #define 不被视为语言的一部分。 当你 #define A 123。 记号名称 A 也许从未被编译器看到;也许在便已开始处理源码前就被预处理器移走了。于是记号名称 A 有可能没进入记号表(symbol table)内。

解决之道: 以一个常量替换上述宏(#define) : const int A = 123;
两种特殊情况:

定义常量指针。 常量定义式通常放在头文件内(便于被不同源码含入),因此有必要将指针(不是所指之物)声明为const。

class专属常量。 为了将常量作用域限制于class内,必须让它成为class的一个成员; 为确保此常量至多只有一份实体,必须声明为一个static成员。

class GamePlayer{
private:
static const int NumTurns = 5;//常量声明
int scores[NumTurns];//使用该常量
...
};

这是声明式,而非定义。 通常C++要求对你是用的任何东西提供一个定义式。
但 class专属常量 且 static 且 是整数型(int,char,bool) 时,必须提供定义式:
const int GamePlayer::NumTurns;// 定义式,这个式子放在cpp文件, 因为在声明中付了初值所以没有再赋值。 这种做法完全是为了给编译器一个交代。 (这一部分书中比较长且我觉得比较偏,就没详细记,如果有需要再翻书去吧)。

如果在编译期间,需要一个class常量值(比如编译期间就要知道数组大小)。可用
"the enum hack "补偿做法: “一个属于 枚举类型 的数值 可权充 int 被使用”。
class GamePlayer {
private:
enum {NumTurns = 5};// "the enum hack"
//令NumTurns成为5的一个记号名称
int scores[NumTurns]; //这就没问题了
...
};

enum hack 重要性:
第一,enum hack 像#define而不像const. 取const地址合法,而取enum地址不合法,取#define也不合法。如果不想让别人获得一个pointer或reference指向你的整数常量,enum可以实现这个约束。
第二,enum hack 是模板元编程的基础技术。

#define 宏的误用。

宏(macro)像函数,但不会有函数调用(function call)的开销。
使用宏 ,必须注意,给所有实参加上小括号 (防止 乱序和多次计算,但即使这样也有可能带来麻烦);
使用 template inline函数。

有个const,enum和inline,我们对预处理器的需求降低了。

TIPs: 单纯常量 , 最好用const 或 enum 替换 #define

形似函数的宏 , 最好用 inline 函数替换 。

条款03:尽可能使用const

const 在*左边,表示被指物是常量; const 在*右边, 指针自身是常量。 (我的方法:离哪个近,哪个是常量: const int* p / int* const p)

被指物事常量,有两种写法都可以,但我不喜欢后一种,可是必须认识:
void f1(const Widget* pw);//f1 获得一个指针,指向一个不变的Widget 对象
void f2( Widget const * pw) ;//f2 也是一样

【STL迭代器】 系以指针为根据模塑出来,作用像个 T* 指针。 声明迭代器 为const (像声明一个 T* const指针),表示这个迭代器不能指向不同的地址,但所指内存中的值可以改变。

令函数返回常量值,可降低因客户错误造成的意外。 除非有需要改动参数,否则 【给函数返回值 加上const 是个好习惯 】。

【const成员函数】: int func() const

许多人漠视一个事实: 【成员函数常量性不同,可以被重载】。 但应避免const和non-const成员函数功能重复(造成编译困难,资源消耗)。【可以令 non-const版本 调用 const版本】,用转型动作,先static_cast<const A>转成const对象,调用函数, 再const_cast去掉const。

一个概念【bitwise constness】(也是C++对constness的定义):成员函数只有在不改变对象的任何成员变量 时(static除外)才可以说const。 编译器只需找成员变量赋值动作即可。
但是,不直观的是,一个更改了“指针所指物”的成员函数不会引发编译器异议 。 因为 只有指针(而非其所指物)隶属于对象。

mutable 释放non-static成员变量的bitwise constness约束(就是让const成员函数可以给成员变量赋值)

Tips: 1.const可帮助编译器侦测错误。

2.编译器强制实施bitwise constness,但你应该使用“概念上的常量性”

3.const,non-const成员函数避免重复。

条款04:确定对象被使用前已被初始化

永远在使用对象前将它初始化。
不要混淆【赋值 assignment】和【初始化 initialization】:
class A{
public:
A(const std::string& name,const std::list<PhoneNumber>& phones);
private:
std::string theName;
std::list<PhoneNumber> thePhones;
int numTimes;
};
A::A(const std::string& name,const std::list<PhoneNumber>& phones)
{
theName = names;    //这些都是赋值
thePhones = phones; //而非初始化
numTimes = 0;
}

C++ 对象的成员变量初始化动作 发生在 进入构造函数本体之前。 发生在这些成员的 default 构造函数被自动调用之时。
编译器会为用户自定义类型的成员变量自动调用default构造函数——如果那些变量没有在“成员初值列表”中指定。 但内置类型不会,所以必须手动附初值(否则可能就是随机数)。
构造函数较佳写法: 使用成员初始化列表:
A::A(const std::string& name,const std::list<PhoneNumber>& phones)
:theName(name),       //这些都是初始化
thePhones(phones),   // 如果写thePhones(),则也调用该变量的default构造函数
numTimes(0)
{}                    //现在构造函数本体不需要任何动作

这个效率高: 上面基于赋值的版本,先调用default构造函数为theName,thePhones设初值,然后再给他们赋值。
TIps: 总是在初值列表中列出所有成员变量。

C++成员初始化顺序: 先base classes,再derived classes。 class的成员变量总以其声明顺序初始化(而不是初始化列表的顺序)。
【static对象】 从构造出来一直到程序结束为止,main()结束时自动调用它们的析构函数。
分为 【local static对象】:函数内的static对象 ; 和【non-local static对象】:其他所有。
【编译单元】:产生单一目标文件的源码。一般就是,单一源码文件+其所含入得头文件。
【跨编译单元的初始化次序问题】:
两个源码文件,每个都含有至少一个non-local static对象。 如果编译单元内某个non-local static对象 初始化动作 使用了 另一个编译单元内的 某个non-local static对象,所用到的这个对象可能尚未初始化。因为C++对”定义于不同编译单元内的non-local static 对象“的初始化次序无明确定义。
解决: 将每个non-local static 对象搬到 自己的专属函数内(该对象在此函数内声明为static), 这些函数返回一个指向它所含对象的reference。(这是模式设计中Singleton模式中的一个常见实现手法)。
因为C++保证,函数内的local static 对象会在"该函数被调用期间" , "首次遇上该对象定义式"时,被初始化 。 所以,以"函数调用",保证了你获得的那个reference指向一个历经了初始化的对象。

Tips:1. 为内置类型手工初始化 ,C++不保证初始化他们。
2. 构造函数使用【成员初始化列表】, 列表中变量次序按声明的次序排。
3. 避免“跨编译单元的初始化次序问题”,使用 【local static对象】(函数内的static对象)。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: