effective c++笔记

以前的笔记,贴出来,比较“个人化”

导言:
本书主要有价值的部分是设计部分,这部分只有躬亲方才真知,切记!!


1.看书不要充数,保持精力100%集中。看英文要“咬字清楚”这样还可以学英文!

2.显示构造函数:
class A{
 explicit A(int n=0,bool b=true){}
}
void dosomething(A aobject);
then:
A a1,a2(28) ;fine
dosomething(28) ;error! but dosomething(A(28)); -- in this case, if excludes the "explicit"  declaring, then all right!
Constructors declare explicit are usually preferable to non-explicit ones to avoid intended behaviors.

3.assignment and copy constructor.(什么是后调用=操作符什么时候调用拷贝构造函数)
A a=b; this calls the copy constructor.
but:
A a;
a=b; this is a asignment.
dosomething(a); in this case, the compiler will create a temp object which use a copy constructor on 'a'. 

4.undefined behavior.
which is normally caused by invalid pointers.(so if find the program, sometime runnnings normally, other times crashing, check the pointers first!)

5.建立client理念。how to create a friendly interface to your client(the ones who will depend on your codes)

6.多线程编程。这是个重要点。深入研究一下无妨。特别是在嵌入式领域。

7.c++很多概念与设计模式应该基于例子。通过例子来加深理解。所以读effective c++时,那些简略的条目并没有太多指导意义,关键还是通过实践去加深认识。

==================================================================
以下正式开始
==================================================================
1.把c++看成是一组语言的组合:
c;
class c++;
template;
stl;
四大块~~

2.把更多的东西留给编译器而非预处理器!
目标文件会带上一个符号表,使用#define则宏名字不会出现在符号表中。
常量指针:我们有一块区域的内容是固定的,我们通过指针指向它。毫无疑问我们必须:const T * const pt=**的方式来定义(const修饰的总是它右边的东东)。
类成员常量:因为是常量,所以只需要一份copy,故应定义成static。
枚举:枚举类型是int类型,所以当要用到int类型常量时,枚举是个不错的选择。
宏函数的危害:当你传入一个表达式时比如a++时,状况很难预知。宏的不论语义的替换性相当丑陋。要用inline替代宏,还要模板的帮助。比如:
template<class T> inline T& MAX(T& a,T& b){return a>b?a:b;}使用的是引用~~

3.无处不在的使用const。
If the word const appears to the left of the asterisk, what's pointed to is constant; if the word const appears to the right of the asterisk, the pointer itself is constant;
iterator在语义上你可以把它理解为T*,所以如果指针是常量,应该iterator const(等价于const iterator)申明。如果指向的对象是常量,则using const_iterator。
也即:const iterator是常量指针。 const_iterator指向的是常量。
通过常量引用的方式传递参数比较高效:首先必须声明为常量,因为原对象不能被更改,其次必须声明为引用,这样避免调用拷贝构造函数。

4.永远要记得初始化一个对象(包括内建对象)。
Manually initialize objects of built-in type, because C++ only sometimes initializes them itself.
In a constructor, prefer use of the member initialization list to assignment inside the body of the constructor. List data members in the initialization list in the same order they're declared in the class.
Avoid initialization order problems across translation units by replacing non-local static objects with local static objects.

5.尽量使用<iostream>而不是<stdio.h>也不是<iostream.h>(<iostream.h>的名字出现在全局空间)
尽量使用new和delete而不是malloc和free。malloc和free只是简单的分配内存和释放内存。
建议:一心一意的使用new和delete。

6.尽量使用c++风格的注释也即//
这一条呢,我觉得关键是形成自己的风格

7.保证new出来的内存全部释放。类指针成员别忘了在析构函数里释放。最容易举的一个例子:
string *pstr=new string[100];
delete pstr;//no no no, just release one string block 在vc6.0下会出现异常
delete [] pstr;//OK
原则:如果你在new时用了[],那么在析构时别忘了[]
typedef string Commandlines[4];//从此commandlines=string[4] --最好的办法是,别做这种傻事。
Commandlines lines; 删除时delete[] lines.

8.写没有异常处理的程序应该要有一种罪恶感。你留下了一个隐患,说不准哪天就真因此而犯事。
new分配内存真的总是成功吗?c++中可以定义NEW宏:
#define NEW(PTR,TYPE) /
try{PTR=new TYPE;} /
catch (std::bad_alloc& ){assert(1);}
不过这不是一个好方法!
好方法是:
调用set_new_handler函数。头文件<new>:这样在release版本中也能处理
--这以节内容很多,有时间再仔细看!

9.如何写new和delete函数~~(用到时在看)
为什么要写?为了效率(所以通常我们用不着)

10.如果类有成员变量,那么记得写个拷贝构造函数和=操作符。因为你不这样做,编译器在遇到a=b时,会有一个默认的拷贝,结果两个类的指针成员指向了同一块内存,与我们的要求不合。

11.使用初始化参数列表,或者使用init私有初始化函数,避免在构造函数完成初始话。
成员初始化顺序与声明顺序相同,与初始化参数列表顺序无关。
const成员必须在参数化列表中初始化。

12.用静态变量来记录类对象的个数,用静态变量来实现sington模式。同时记得把基类的析构函数声明为虚拟的。否则用基类指针去删除派生类对象时结果是不可预知的。

13.考虑一个没有考虑过的问题:
为什么赋值操作符可以连起来?a=b=c=d=0
编译器怎么分析?如下a=(b=(c=(d=0))
分析d=0,=操作符函数传入参数0(实际上还有this指针)并返回一个引用作为前一个=的参数
--让=函数返回*this。检查自己对自己的赋值情况!!

14.包含引用成员与const成员的类必须自己实现拷贝和=操作符。否则编译器拒绝自动拷贝或赋值。

15.解释型语言更容易进行运行期检查,但c++不是,所以尽量把错误留给编译连接期间。一旦程序跑起来,就不要再出现一大堆不可预知的错误。所以c++程序员一定要熟练,并对编译器有很深的理解。

16.让我们考虑这样一种情况:
A的构造依赖于B的一个静态对象的构造,B的构造又依赖于A的一个静态对象的构造。怎么办?
那么毫无疑问的应该把这种静态关系转移出构造函数,也就是在类A中去掉B成员。把它移到函数中去。
看下面代码:
B& A::getB()
{
 static B b;
 return b;
}
什么时候初始化b?第一次进入该函数的时候。b同样会在全局空间定义,只有一份。问题解决!
这就是singleton模式,当然singleton的最好方式是提供一个静态的singleton函数。

17.重视编译警告,在编写高质量的代码时,把警告级定位最高级(可以手动关掉一些),清理掉所有的警告。
--原则上,忽略一条警告没有问题,但你必须清除这条警告的含义。

18.关于名字空间的问题,尚需做些试验验证之。

19.c++标准库中的一切都是模板。比如cout是ostream的对象而ostream是typedef basic_ostream<char> ostream的定义。又如:
template<class charT,class traits=char_traits<charT>,class allocator<charT>> class basic_string;
typedef basic_string<char> string;
--请多使用标准库,使用容器和算法,一是为了效率,二是为了安全。

20.为什么要强调快速开发?因为软件开发中最重要的部分是:实现那些有别于你的竞争对手的软件功能。

21.要理解c++,首先理解c++的目标,所以可以看看本杰明的书。看设计者怎么说。
c++只是6大块:
c,重载,面向对象,模板,异常,名字空间。
--c++的大概目标:
与c兼容,效率与c相当(5%内),不是一门教学语言,是解决实际问题的语言。
很多问题都是基于如上的考虑(看看meyes怎么说)

22.c++派生类按名字(不是按函数原型)隐藏父类的同名函数。比如:
如果基类 void f(int i);派生类void f(double d);那么它们不构成重载关系,而是后者遮盖前面那个(除非使用using申明)。

23.争取使类的接口完整并且最小。不要去打击用户使用它们的积极性。易用而且够用使我们的目标。

24.区分成员函数,非成员函数,友元函数。不要以为这个很简单,要做出“完美的正确的”判断还真不容易,建议看看书给的例子。
慎用友元,很多情况下申明public的inline的取得成员的成员函数代替即可,没什么性能损失。

25.避免public接口中出现数据成员。这样你就不用想,这个成员该用括号呢还是不用括号。统一的用函数来访问成员就是。
meyes是这么说的:一生中,这可以避免你多少次抓脑袋啊。
--当然还有其他的好处,这样的接口更具有灵活性,因为是函数,所以我们的类可以作修改,而client不用修改他们的代码。

26.必须返回一个对象时不要试图返回一个引用(想想引用的实质是一个别名,一定有一个对象已经创立了)。
在c++中有个说法,尽量让程序高效,但不要过于高效。
我们使用引用的目的是避免构造函数被调用(构造函数往往是递归的,开销很是了得)。

27.不要让重载函数太多,想想能不能通过缺省参数的方式减少重载函数的个数。但不要绝对化。实事求是!

28.避免对指针类型和数字类型重载。禁止编译器自动生成某些函数:申明但不实现。

29.划分全局名字空间:在一个大的项目中,这是必须的(?呵呵)。如果真是个大项目,建议去再仔细研究下。

30.为什么设计才是关键,编码不是关键?大系统项目,在开始类的具体实现前,接口往往趋于稳定。
c++是一门高度类型化的语言,按理,只要合适的类,合适的模板,合适的函数声明被设计出来了,那么代码的具体实现不是一件难事。有一些类(那些在整个项目中经常需要用到就像简单类一样的类应该首先抽象出来并定义好)。

31.const申明的对象不一定真正const。这就好像你心爱的人说永远爱你但并不保险一样。最简单的:
const B b; char *str=*b; ...
b内容是不是总是不变,取决于B的设计是不是滴水不漏。
--某些时候,返回一个const指针是个不错的主意。把复制工作留到外面(你爱怎么用我的数据就怎么用,但别修理我)

32.避免这样的成员函数:其返回值是指向成员的非const引用或指针,但成员的访问级别比这个函数要低(这叫做backdoor)。

33.千叮嘱,万叮嘱,不要把局部变量的指针返回,也不要返回new对象的引用。

34.尽量推迟变量的定义。基于如下理由:
1.定义需要调用构造函数,有开销,某些变量只出现在某些分支中,不应该在开始就去构造。
2.增加程序的可读性

35.内联的目的:产生更短的代码(想想函数调用也是有代码开销的),更快的执行效率(基于代码更多和更高的cach命中率)。
--inline和register关键字都是一种建议。(大多数编译器拒绝内联比较复杂的函数,反汇编下vc6.0就知道)。
不要迷信内联啊。主要用在成员get和set以及简单的计算上(比如返回一个计算结果),某些时候,不妨还是使用宏,不要把这家伙一棍子打死。

36.将文件间的编译依赖性降至最低。这样做的目的,是当你修改某个文件后重新编译连接时比较快。
--在将接口和实现相分离这方面,c++做的不是很出色(使用接口的人永远不需要知道接口是怎么实现的)。
解决办法是用“对类申明的依赖”取代“对类定义的依赖”。

37.延续上一主题,汇成:
尽量让头文件不要依赖别的文件(标准库文件除外);如果不可能,就借助于类的申明,不要依赖类的定义。
具体:如果可以使用对象的指针或引用,就不要使用对象本身(定义某个对象的引用和指针只涉及到对象的申明--为什么?因为,对象是要分配空间的,所以需要知道空间的大小,当然就要知道对象的细节,但是指针和应用都是4字节,只要让编译器知道这个类名是合法的就行了)
还有一点,申明一个函数时,如果用到某个类,是绝对不需要这个类的定义的,即使通过传值来传递参数和返回值。--想不到吧。

38.关于上面一点,还想说一下。那就是头文件的作用(在编译期将插入cpp文件再对单个cpp文件进行编译),头文件的作用第一是申明,第二是一些常量的定义,第三是公共部提取(如inline函数)。可见,我们平时#include头文件惯了,其实引入的头文件中很多都是本cpp编译无关的,可能就为了某个申明而引入某个头文件。实际上我们完全可以自己申明而不引用头文件(前提是前期设计做足)

39.还是关于上面的主题,看看下面几个名词:
句柄类;信封类,主体类,新建类,协议类。
--请仔细看书,不表(可能网上的讨论更有价值)。总之,本主题反映的问题确实是个不大不小的问题,需要些编译器的知识,也关乎于设计模式。

40.公有继承体现“是一个”的关系。记住,这不是一个知识点,而是一个设计原则。B公有继承于A,那么A在概念上包含B,而不是B包含A。意思是:任何使用A的地方都可以使用B,反之不成立。
--公有继承<==>是一个

41.区分接口继承和实现继承。
一种技巧:申明protected成员函数,和公有的虚拟函数。protected成呀函数是功能实体,而公有函数是对外接口。这样派生类可以选择要不要父类提供的实现,如果要,调用父类的protected函数,不要,自己重写公有的虚拟接口。这节内容其实很不错的~~。

42.与上面主题相关:绝不要重新定义继承而来的非虚函数。你会问:有这么严重吗?为了不造成迷糊,还是别这样。

43.绝不要重新定义继承而来的缺省参数值(主要是指虚拟函数的重新定义)。原因是虚拟函数是动态绑定的,但是默认参数是静态绑定的。如果你设计编译器,你也会这样做的。对A *pa=new B()的代码,编译器只会也只能(目前)把pa理解为类A的指针。

44.不要侥幸使用类层次的“向下转换”!

45.三种关系;{//beyes举的例子是如何自己用list模板来实现set模板,显然,这是一个has-a的关系。
 is-a,
 has-a,
 use-a
}两种使用方式{
 composition,组合
 aggregation,聚合 
}

46.区分继承与模板。如果类型会影响行为,那么就用继承,使用多态;否则,使用模板。

47.说出你想说的,只是成功的一半,理解你的所说,同样重要。实现你所说的,是最后一步。

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值