《Effective C++》读书笔记01:视c++为一个语言联邦
1.c语言;
2.经典的面向对象c++:包括了class,封装,继承,多态,虚函数等经典的OO思想;
3.c++模板编程,也称为泛型编程,这一部分相对内容较新,我个人正在学习《C++ Templates》加深理解;
4.STL:vector,list,map,iterator相信大家都很熟悉了吧:)
你看,这里的每一个部分都值得你花上半年的时间去理解,那些所谓的《XX天精通C++》根本就不可能实现,学好C++需要的是不断的积累和实践,希望等我毕业的时候能说自己精通C++了吧。
《Effective C++》读书笔记02:用const,enum,inline减少#define出场机会
以前学习c语言时,老师教导我们,对于数字最好用宏(#define)来定义它。以至于一直习惯于#define Age 12这样的写法。
在c语言的环境中,这确实是增加程序可读性的有效做法,可是在c++中,可以有更优美的实现方式。
1.当你写下
#define NUMBER 11.12
如果接下来使用NUBMER时出现了编译错误,那么由于在预处理过程中NUMBER已经被替换为11.12了,编译器指出错误发生在11.12上,如果使用者不是你,肯定会对11.12感到莫名其妙,要花不少时间追踪它。
替换方法有:
const double Number = 11.12;
这样一来,错误定位肯定没有问题了,而且如果多次使用这个常量,宏替换会产生多份11.12,而后者保证只有一个副本。
2.我以前写过这样的宏替换代码:
#define MAX(a, b) ((a) > (b) ? (a):(b)) //得到两个数中的最大值
在每个变量外都加了括号以后,使用起来确实没碰到过什么问题。可是作者这样使用这个函数:
int a = 5, b = 0;
MAX(++a, b); //a会被递增两次
MAX(++a, b+10); //由于b+10>a,a只被递增一次!!
会出现很大的问题!
在c++中,可以使用inline函数来解决:
template<typename T>
inline T MAX(const T& a, const T&b)
{
return a > b ? a : b;
}
使用reference-to-const可以确保不对对象进行修改;而inline函数本身可以在编译时进行优化,提高编译速度。
Note:
1.对于一般常量,最好用const和enum替换#define;
2.对于类似函数的宏,最好改用inline函数替换#define。
《Effective C++》读书笔记03:多才多艺的const
const是我们写c++代码时的常客,对于那些我们不希望修改的对象,最好用const进行修饰。
1.下面来看看一些惯用法:
1 char greeting[] = "Hello";
2 char* p = greeting; //非const指针,非const数据
3 const char* p = greeting; //非const指针,const数据
4 char const *p = greeting; //非const指针,const数据
5 char* const p = greeting; //const指针,非const数据
6 const char* const p = greeting; //const指针,const数据
3和4行虽然形式不同,不过功能是类似的。
3和5有本质的不同,可以这么理解:const会修饰在它后面所有的代码,比如第3,4中的const修饰char* p,表示char*,而在第5中const修饰p,表示指针。
可这些在STL中有所不同,由于iterator是一个指针,用const修饰一个iterator会类似于上面第3行,而如果要产生第5行的效果,需要使用const_iterator:
1 using namespace std;
2
3 vector<int> vec;
4 const vector<int>::iterator iter = vec.begin();//类似于T* const
5 *iter = 10; //正确,可以修改iter所指的值
6 ++iter; //错误,iter是const
7
8 vector<int>::const_iterator cIter = vec.begin();//类似于const T*
9 *cIter = 10; //错误,cIter指向的值是const
10 ++cIter; //正确
总结一下:在一般的应用中const会修饰其后的变量和修饰符,而只有在STL的iterator中,const iterator==T* const;const_iterator==const T*。
记住,尽可能地将不能修改的变量声明为const!
2.在成员函数中使用const时,如果将成员函数声明为const,则函数中任何对任何成员变量进行修改都会导致错误,这样可以防止我们对对象无意的修改
1 class TestBlock
2 {
3 public:
4 void ConstFunc() const;//const函数
5 private:
6 char* text;
7 int size;
8 bool isValid;
9 }
10
11 void TestBlock::ConstFunc() const
12 {
13 if(!isValid)
14 {
15 size = strlen(text);//错误
16 isValid = true;//错误
17 }
18 }
可以通过将成员变量声明为mutable(可变的)来消除这种错误
1 mutable int size;
2 mutable bool isValid;
3.const可用于重载,如果两个函数参数完全一致,可以通过将返回值声明为const或将函数声明为const来进行重载,不过在实现的时候,可以用非const函数来调用const函数,从而减少代码重复。这一块感觉用不太多,暂时就不管了。
《Effective C++》读书笔记04:确保对象在使用之前被初始化
我自己在写代码的时候也经常会遇到忘记初始化某对象的问题,而且这些错误比较难以调试,Meyers提出了一些避免这些错误的解决方法:
1.手工初始化所以内置类型:
这一条很好理解,对于int,enum等内置类型,在使用前一定要初始化。
2.对于类类型等用户自定义的对象,使用成员初值列初始化所有的对象:
1 using namespace std;
2
3 class PhoneNumber{};
4 class Customer
5 {
6 public:
7 Customer(const string& name, const string& address,
8 const PhoneNumber& phone);
9 private:
10 string theName;
11 string theAddress;
12 PhoneNumber thePhone;
13 int usedTimes;
14 }
对于Customer类的构造函数定义,一般我们会这么写:
Code
1 Customer::Customer(const string& name, const string& address, const PhoneNumber& phone)
2 {
3 theName = name; //这些都是赋值
4 theAddress = address;//而不是初始化
5 thePhone = phone;
6 usedTimes = 0;
7 }
可是,在c++中,对不是内置型的对象的初始化都发生在进入构造函数之前,也就是说,在进行theName = name;赋值之前,theName就已经进行了初始化了,这个过程调用自己的默认构造函数。
紧接着有立刻进行了赋值操作,这样会造成额外的浪费,所以我们可以这样写构造函数:
1 Customer::Customer(const string& name, const string& address, const PhoneNumber& phone)
2 :theName(name),//成员初始化列
3 theAddress(address),
4 thePhone(phone),
5 usedTimes(0)//内置类型也一并初始化
6 {
7 }
使用了成员初始化列的方法,在进入构造函数体之前就进行了初始化,减少了赋值的开销,同时为了保持一致性,将内置类型也一并进行了初始化。
还有一点要记住:在成员初始化列中对变量的初始化次序是按照变量声明的次序的,也就是说,即使将上面的次序任意改变,也改变不了初始化次序,所以我们要尽可能地按照使用的顺序来声明变量!
3.在多个编译单元内的non-local static对象的初始化次序问题:
non-local static对象表示在程序执行过程中一直存在的对象,像类中声明的static变量,全局变量,而在普通函数中声明的static变量称为local static变量。
那么当有多个不同的编译单元(即存在于不同的文件中)时,对这些non-local static对象的初始化次序,在c++中,是不确定的,而且也没法确定!
当两个或多个文件中的non-local static对象发生关联时,问题就出现了。
解决方法就是使用了设计模式中的:Singleton单件模式,将对non-local static的访问移到函数中,将其转变为local static变量,确保其被初始化了再使用。
如果是多个non-local static对象互相之间都有关联,那。。对不起,是设计出了问题。
《Effective C++》读书笔记05:c++默默为您编写的函数
这一部分的条款讲的都是类的构造/析构/赋值函数的使用。
当你写下一个:
1 class Empty {};
经过了编译器的处理,就好像你写下了如下的代码:
1 class Empty
2 {
3 public:
4 Empty() {} //default构造函数
5 Empty(const Empty& rhs) {} //copy构造函数
6 ~Empty() {} //析构函数
7
8 Empty& operator=(const Empty& rhs) {}//copy assignment操作符
9 }
你看,c++编译器会在你需要的时候创建
1.default构造函数
2.析构函数
3.copy构造函数
4.copy assignment函数
这样一来,你就可以写如下代码了:
1 Empty e1; //调用了default构造函数
2
3 Empty e2(e1); //调用了copy构造函数
4 e2 = e1; //调用了copy assignment函数
5 //调用析构函数
好吧,知道了有这些函数,可这些函数用来干什么?为什么编译器要写这些函数?
1.default构造函数和2.析构函数主要是给编译器一个放置代码的地方,可以用来调用基类和non-static成员变量的构造函数和析构函数。
3.copy构造函数和4.copy assignment函数,编译器创建的版本只是简单的将每一个non-static成员变量拷贝到目标对象,看下面这个例子:
1 using namespace std;
2
3 class NameObject
4 {
5 public:
6 NameObject(const char* name, const int& value);
7 NameObject(const string& name, const int& value);
8
9 private:
10 string nameValue;
11 int objectValue;
12 }
13
14 NameObject no1("Smallest Prime Number", 2);
15 NameObject no2(no1); //以no1.nameValue和no1.objectValue的值设定no2的值
16 //nameValue是string,首先会调用string的copy构造函数并以no1.nameValue作为参数
17 //由于objectValue是内置int型,所以会直接将它的每一位拷贝过去。
上面的例子是理想的情况,就是每个变量都是可以直接赋值过去的,没有引用类型和const类型,假如有这两种类型的成员变量,则会报错
1 class NameObject
2 {
3 public:
4 NameObject(string& name, const int& value);
5 private:
6 string& nameValue; //引用类型
7 const int objectValue; //const类型
8 };
9
10 int main()
11 {
12 string newDog("DaHuang");
13 string oldDog("XiaoGuai");
14 NameObject p(newDog, 2);
15 NameObject s(oldDog, 36);
16 p = s; //错误,不能更改non-static的引用成员的指向,不能更改const成员的值
17 //所以编译器提示不能 使用default assignment,并报错
18 return 0;
19 system("PAUSE");
20 }
所以,在存在这样的成员变量时,尽可能自己定义coyy构造函数和copy assignment函数
《Effective C++》读书笔记06:如果不要编译器自动生成的函数,就明确拒绝
有时候,我们需要定义一些独一无二的类,这些类是如此的特别,以至于我们不能将其拷贝或者赋值:
1 class Family {};
2
3 Family mine;
4 Family your;
5 Family her(mine);//不能通过编译
6 mine = your; //也不能通过
可是上一个专题讲到,即使我们不定义copy构造函数和copy assignment函数,编译器还是会自动定义它们,所以,为了阻止上述行为,我们一般的解决方法就是:
1 class Family
2 {
3 public:
4
5 private:
6 Family(const Family&); //我们将其定义在private中
7 Family& operator=(const Family&); //并且故意不实现它们:)
8 }
这样,如果用户无意中调用它们,就会在编译期间得到错误。而如果你在member函数和friend函数中调用,则会是连接器发出抱怨;
也可以将连接期间的问题移到编译期间,可以这么做:
1 class Uncopyable
2 {
3 protected:
4 Uncopyable() {};
5 ~Uncopyable() {};
6 private:
7 Uncopyable(const Uncopyable&); //阻止copy
8 Uncopyable& operator=(const Uncopyalbe&); //阻止
9 };
10
11 class Family: private Uncopyable //继承以后就完成了
12 {
13
14 };
《Effective C++》读书笔记07:为多态基类声明virtual析构函数
这个问题在实践中偶尔会碰到,设计一个TimeKeeper基类和一些派生类来记录时间:
1 class TimeKeeper
2 {
3 public:
4 TimeKeeper();
5 ~TimeKeeper();
6
7 };
8
9 class AtomicClock: public TimeKeeper {}; //原子钟
10 class WaterClock: public TimeKeeper {}; //水钟
在使用时,我们可能会使用factory工厂方法:
1 TimeKeeper* getTimeKeeper();//返回一个指针,指向一个派生类的动态分配的对象
2
3 TimeKeeper* ptk = getTimeKeeper();//从继承体系中得到一个动态分配对象
4
5 delete ptk;//负责的删除它
删除的时候就会出现问题,因为ptk这个指针指向的是基类,那删除的指令会执行基类TimeKeeper的析构函数,该函数不是virtual函数。
在c++中,这样的情况下其删除行为没有被定义,一般会只删除基类的成分,而派生类的那些元素没有被删除,这就是形成资源泄露,败坏数据结构,在调试器上浪费很多时间的绝佳途径哦(引用原文翻译)。
解决的方法就是定义一个基类的virtual析构函数,这样一来,删除行为就会在派生类中实现,不会只删除一部分。
一般来说,只要类中有virtual函数,就要定义一个virtual析构函数。不过,如果类中没有virtual函数,就不需要也不应该定义virtual析构函数,这样不仅没用,而且也会增加额外开支,且会产生很多的兼容性问题,因为virtual机制是c++特有的。
另外,c++中很多类的实现都是不带virtual的,比如:string,STL中的vector,list,set,trl::unordered_map,如果继承它们很可能出现上述的错误,所以作者提醒大家:拒绝继承标准容器或者其它只有非virtual析构函数的类!
《Effective C++》读书笔记 08:别让异常逃离析构函数
这节和异常有关,这一块是我不太熟悉的,只能先把自己理解的记录下来。
1 class Widget
2 {
3 public:
4
5 ~Widget() {} //假设这里会吐出一个异常
6 };
7
8 void doSomething()
9 {
10 std::vector<Widget> v;
11
12 }//v在这里自动销毁
上面的代码中,假设v含有10个Widget,如果在前面几个的析构函数中弹出异常,则程序会过早结束或者出现不明确行为。
确实不鼓励在析构函数中抛出异常,可是如果程序在析构函数中必须执行一个动作,而该动作可能会在失败时抛出异常,该怎么办呢?比如下例:
1 class DBConnection
2 {
3 public:
4 static DBConnection create();
5 void close();//关闭连接,失败则抛出异常
6 };
为了确保用户不忘记调用close()关闭连接,我们可以创建一个管理DBConnection资源的类:
1 class DBConn
2 {
3 public:
4 ~DBConn()
5 {
6 db.close();
7 }
8 private:
9 DBConnection db;
10 };
这样在使用时,如果close没有异常,则会很完美,不然DBConn就会使得它离开close函数,这会出现上述问题。
我们可以在DBConn的析构函数中,自己提前处理这个异常,但是这么做对于“导致close抛出异常”的情况无法做出反应。
一个比较好的策略是重新定义DBConn接口,给用户一个机会自己处理这种异常,比如,给用户定义一个函数close:
1 class DBConn
2 {
3 public:
4 void close()//让用户有机会自己捕捉异常
5 {
6 db.close();
7 closed = true;
8 }
9
10 ~DBConn()
11 {
12 if(!closed)
13 {
14 try{
15 db.close();
16 }
17 catch(){
18 //记录下对close的调用失败
19 }
20 }
21 private:
22 DBConnection db;
23 bool closed;
24 };
这样一来,就有了双保险,用户可以自己处理异常,如果他们不处理,则析构函数会自动吞下异常。
总结:
1.在析构函数中尽可能不要吐出异常,如果真要吐出就在析构函数中捕获所以的异常,并提前结束程序或吞下它们;
2.如果用户需要自己处理异常,则在类中应该提供一个普通函数处理。
分享到:
相关推荐
从给出的部分内容来看,读书笔记主要聚焦于以下几个知识点: 1. C++语言的联邦概念:C++是一个由多个次语言构成的语言联邦,这包括了C语言核心、面向对象的C++、模板C++以及标准模板库(STL)。这种理解对于深入...
Effective C++读书笔记 本文总结了Effective C++读书笔记,涵盖了C++的四个主要次语言:C、Object-Oriented C++、Template C++和STL。同时,文章还讨论了高效编程守则,包括使用const、enum和inline替换#define,...
effective C++ 很好 很不错 最好的C++进阶资料
摘录了《Effective C++》 (Scott Meyers 著)中有参考价值的编写代码建议,方面阅读
Effective C++是一本深入探讨C++编程实践的书籍,它提供了许多提高代码质量和效率的建议。以下是基于标题、描述和部分内容的关键知识点: 1. **虚函数的声明与使用**: - 在C++中,虚函数是实现多态性的关键。它们...
《Effective Modern C++:改善C++11和C++14的42个具体做法(影印版)(英文版)》中包括以下主题:剖析花括号初始化、noexcept规范、完美转发、智能指针make函数的优缺点;讲解std∷move,std∷forward,rvalue引用和全局...
自己整理的侯捷版本<<Effective C++ 改善程序与设计的55个具体做法>> 学习笔记.
内容简介:有人说C++程序员可以分为两类,读过Effective C++的和没读过的。当您读过《Effective C++:改善程序与设计的55个具体做法(第3版)(中文版)(双色)》之后,就获得了迅速提升自己C++功力的一个契机。
此标题暗示了文档是针对《Effective STL》一书的深度阅读笔记。《Effective STL》是一本关于C++标准模板库(STL)的权威指南,由Scott Meyers撰写,旨在帮助程序员更高效、更安全地使用STL。精版笔记意味着作者对书中...
Effective C++笔记1 本笔记总结了Effective C++的第1到第11条款,涵盖了C++语言的多个方面,包括构造函数、拷贝构造函数、拷贝赋值函数、const关键字、enum、inline函数、定义域、static变量、初始化、编译器生成的...
Effective C++笔记1 本笔记概括了 Effective C++ 中的四十个规则的第一个部分,涵盖了 C++ 语言的基本概念、const 的使用、inline 函数的应用、对象的初始化等方面。 规则 1:将 C++ 视为语言联合体 -------------...
《Effective C++》是Scott Meyers撰写的一本经典C++编程指南,旨在帮助开发者写出更高效、更可维护的代码。...阅读Roy T的读书笔记可以帮助我们回顾和巩固这些关键概念,更好地将它们应用于实际项目中。
### Effective_C++_3rd笔记的关键知识点概览 #### 视C++为一个语言联邦(条款01) C++作为一门复杂且功能丰富的语言,它实际上涵盖了多种编程范型,包括过程式、面向对象、函数式、泛型以及元编程。这种多样性的...
通过这些学习笔记,我们可以了解到《Effective C++》不仅为我们提供了C++编程中的一些基本和高级技巧,也教会我们如何遵循最佳实践,编写出更加高效、健壮的C++代码。学习这些知识点,对于提高C++编程能力和解决实际...
通常C++要求你对所使用的任何东西提供一个定义式,但如果它是个class专属常量 又是static且为整数类型(integral type,例如int,char,bool),则可特殊处理。只要不取它们 的地址,你可以声明并使用它们而无需提供...
通过阅读这份详尽的《Effective C++3 学习笔记》,读者不仅可以深入了解C++语言的高级特性,还能掌握编写高效、安全的C++代码的最佳实践。对于任何渴望提升C++编程技能的开发者来说,这是一份不可或缺的参考资料。
《Effective C++中文版》是一本关于C++编程的高级技术书籍,由Scott Meyers所著。书中提供了深入浅出的讲解和实践技巧,帮助读者掌握C++编程中的高级和关键概念。通过对代码片段的分析,可以提炼出C++语言编程中的多...