effective C++学习笔记
文章平均质量分 51
thefutureisour
ZTE基带部码农,平时工作很忙,不再更新opencv相关内容,也不解答大家问题了。
展开
-
条款1:视C++为一个语言联邦。
C++是一个如此复杂的语言,可以分解为4大部分:1.C语言:比如内置数据类型,数组,指针,预处理,语句等内容均来源于C。2.面向对象的C++:类、构造函数,析构函数,封装,继承,派生,多态,虚函数。3.泛型C++:就是使用模板编程。4.STL库:各种容器,迭代器,算法,函数对象。由于有这4种不同的风格,所以当你对内置数据类型操作时,通过值传递比通过引用传递更高效;但当你使用的是原创 2012-09-04 20:20:05 · 1378 阅读 · 0 评论 -
条款26:尽可能延后变量定义式的出现时间
假如你定义的变量带有构造函数和析构函数,那么你就必须承受它们所带来的成本。比如下面这个函数,把密码置换为“*“string encrytPassword(const string password){ string::size_type miniLength = 4; using namespace std; string encryted; if(password.length原创 2012-09-11 11:21:07 · 1276 阅读 · 0 评论 -
条款27:尽量少做转型操作
类型转化分为两大类:旧风格与新风格旧风格是从C语言继承过来的,分为C-style和function-style。举个例子: double d = 1.2; int i = (int) d;//C-style int j = int(d);//函数风格C++提供了4种新的类型转化:const_cast (表达式)dynamic_cast (表达式)reinterpret_原创 2012-09-11 19:31:04 · 1370 阅读 · 0 评论 -
条款28:避免返回handles指向对象内部成分
首先说明,在第三版中,这一条款有两处明显的错误:第一是在124页下面:这立刻带给我们两个教训:第一,成员变量的封装性最多只等于“返回其引用”的函数的访问级别。本例中虽然ulhc和urhc都被声明为private。这里明显是错了:c++primer里说过,如果类以struct开头,里面的默认标号就是public;而且,如果不是public,pData->ulhc是不能实现的。这里改为:虽然原创 2012-09-12 09:52:56 · 1434 阅读 · 1 评论 -
条款32:确定你的public继承塑模出is-a关系
public继承意味着“is-a”关系。它的意思是:如果B以public形式继承自A,那么B类型对象肯定是一个A对象,反之不成立。A是B的一种抽象,B是A的特例。任何使用A的地方,都能使用B。但是,有时候会犯认识上的错误:比如为类bird定义了函数“fly”,但是当我们从bird派生出企鹅Penguin时,却发现企鹅应该是不会飞的。这该怎么办呢?第一,我们可以修改我们的设计:class原创 2012-09-13 10:15:41 · 862 阅读 · 0 评论 -
条款34:区分接口继承和实现继承
作为类的设计者,我们有时候希望派生类只继承基类成员函数的接口,有时候希望派生类继承基类的接口及实现,同时又能自己重写这些实现,有时希望派生类老老实实的继承这些函数的接口和实现,而且不做任何修改。这三种不同的策略是通过声明基类成员为纯虚函数、虚函数和非虚函数来实现的。看一个例子:class Animal{public: virtual void eat() = 0; virtual原创 2012-09-13 14:31:31 · 1007 阅读 · 0 评论 -
条款30:透彻了解inlining的里里外外
首先,inline函数只是一个申请,而不是命令。编译器可以执行你的申请,也可以拒绝。申请有两种形式:隐式申请:在类内部定义的函数都默认为inline函数,甚至包括内部定义的友元函数。显示申请:使用inline关键字。其次,inline函数一般要放到头文件中,因为编译器需要在程序调用内联函数时立刻将他替换,所以必须要知道这个函数的具体内容。类似的还有模板,必须在让编译器能够在调用模板的的程序原创 2012-09-12 16:06:26 · 983 阅读 · 0 评论 -
条款31:将文件间的关联度降到最低
先看一个例子:class Person{public: Person(const string& nm ,Date d):name(nm),birthday(d){} void getBirthday() { cout<<birthday.getYear()<<"."<<birthday.getMonth()<<"."<<birthday.getDay()<<endl; }原创 2012-09-13 08:56:16 · 862 阅读 · 0 评论 -
条款33:避免掩盖继承而来的名称
这其实是一个作用域带来的问题:局部变量会掩盖同名的外围变量。注意,只要同名就会被掩盖,与类型无关:void main() { int a = 10; { double a = 0.1; cout<<a<<endl;//结果为0.1 } cout<<a<<endl;//结果为10}而对于继承派生体系也是如此,因为派生类继承了基类的所有public部分,所以:c原创 2012-09-13 12:41:28 · 797 阅读 · 0 评论 -
条款37:绝不重新定义继承而来的缺省参数
先上代码:class Base{public: virtual int getVal(int i = 0) { cout<<"基类函数"<<endl; return i; }};class Derived:public Base{public: int getVal(int i = 1) { cout<<"派生类函数"<<end原创 2012-09-16 11:44:16 · 901 阅读 · 0 评论 -
条款39:明智而审慎的使用private继承
先说private继承的特点:1.也就是说,编译器不会讲一个private继承而来的派生类对象转化为一个基类对象。这意味着,priavte继承不再是is-a关系:class Person{protected: string name;};class Student:private Person{private: string schoolNumber;};void ea原创 2012-09-16 19:20:08 · 1091 阅读 · 0 评论 -
条款40:明智而审慎地使用多重继承
多重继承往往会导致二义性:class Base1{public: void func() { cout<<"base1 func"<<endl; }};class Base2{private: bool func() { cout<<"base2 func"<<endl; return true; }};class Derived:public原创 2012-09-16 21:51:39 · 762 阅读 · 0 评论 -
条款:38:通过复合塑模出has-a或“根据某物实现出”
不要被这个诡异的条款名字欺骗了,他说的是一件简单的事情。比如,定义一个“人”类,而人又有地址,那么你应该这么写:class Address{public: string country; string province; string city;};class Person{ string name; Address address;};而不是用Perso原创 2012-09-16 16:30:17 · 999 阅读 · 0 评论 -
条款25:考虑写一个不抛出异常的swap函数
我们可以调用std下的swap函数,这是一个模板函数:既可以: int a = 1; int b = 2; std::swap(a,b); cout也可以(前提这个类型支持复制构造函数和赋值构造函数):class Test{public: Test(int i):val(i){} int getVal(){return val;}private: int原创 2012-09-11 09:03:20 · 2296 阅读 · 1 评论 -
条款35:考虑虚函数以外的其他选择
我们考虑下面这个问题:假设我们在开发一款游戏,游戏中有不同的角色,每个角色有自己的生命值的初始值,生命值的计算方法等等。你会怎么设计这个类呢?我们很自然的就会想到:class GameCharacter{public: virtual int healthValue()const;};就是说基类里定义了一个计算生命值的函数,派生类通过重新定义这个函数来完成不同类型的角色原创 2012-09-15 14:01:55 · 1045 阅读 · 0 评论 -
条款36:绝不重定义继承而来的non-virtual函数
先看一个例子:class Base{public: void func(){cout<<"base function"<<endl;}};class Drived : public Base{public: void func(){cout<<"drived function"<<endl;}};int main(){ Drived d; Base* pb = &原创 2012-09-15 14:04:47 · 1031 阅读 · 0 评论 -
条款42:了解typename的双重意义。
在模板的的声明中,class与typename是没有什么区别的:template T func1(const T&);template T func2(const T&);但是在模板的定义中typename有时候却会派上用场。为了说明问题,我们先了解一下两个名词:从属名称和非从属名称。从属名称是依赖于某个类型的,比如迭代器,它是依赖于你的容器类型的;而非从属名称就不依赖其他类型,比如原创 2012-09-17 14:08:28 · 6933 阅读 · 0 评论 -
条款43:学习处理模板化基类的名称
先看程序:class A{public: void funcA() { cout<<"funcA"<<endl; }};templateclass UseA{public: void useAFunc() { T a; a.funcA(); }};templateclass DrivedUseA:public UseA{public: vo原创 2012-09-17 19:58:04 · 1551 阅读 · 0 评论 -
条款22:将成员变量声明为private
为什么不将成员变量声明为public呢?1.如果变量都是private,那么用户只能通过函数来获得这个变量,而不用考虑“.”或者“->”后面的东西用不用加上“()”。2.使用函数,可以对变量进行精确地控制:有的变量不许访问,有的只读,有的可以读写,甚至是可以“只写”。而且在函数中,可以处理用户输入的不合理的参数。3.封装性。封装性意味着,当你有新的想法需要修改时,只需要改变函数内部的实原创 2012-09-10 15:27:07 · 1958 阅读 · 0 评论 -
条款21:必须返回对象时,别妄想返回其reference。
前一小节已经讨论过,pass by vaule的代价有时候是巨大的,pass by refrence比较方便。那么肯定也有人会立刻想到,函数返回值的时候,能不能也采用这种办法来提高程序的效率呢?为了能够简单且说明问题,这里选择了对于内置类型返回其reference:int& func(){ int i=3; return i;}void doNothing(int i){}原创 2012-09-10 15:24:14 · 1336 阅读 · 0 评论 -
条款24:若所有参数皆需类型转化,请谓词函数采用non-member函数
还是前面那个有理数乘法的例子:class Rational{public: Rational(int numerrator = 0, int denominator = 1):n(numerrator),d(denominator){} const Rational operator*(const Rational& rhs) { n = n * rhs.n; d = d原创 2012-09-10 15:33:00 · 1213 阅读 · 0 评论 -
条款2:尽量以const,enum,inline替换#define
这一条款也被称作:宁可用编译器替换预处理器。从以下几方面说明:(最开始的那一段似乎跟编译原理有关,我是不懂的,就不说了):对于一般用#defined定义的常量,可以使用const类型代替。但是要注意两种特殊情况:假如我们要在头文件中定义一个不能被修改的字符串,那么需要使用const char* const类型的指针: char a = 'A'; char b = 'B'原创 2012-09-04 21:57:59 · 1101 阅读 · 0 评论 -
条款4:确定对象使用前已先被初始化
对于内置类型,必须手工完成这件事。对于其他类型,则依赖于这个类型的构造函数。首先,最好使用初始化列表而不是在函数体内部进行赋值。原因有两个1.有些对象只能初始化,不能赋值:举一个简单的例子:const变量。2.在构造函数体中赋值前,其实已经进行了初始化操作。对于内置类型,这里初始化的都是一些垃圾数字,然后才是赋值。对于自定义类型,先是调用了这个类型的默认构造函数,进行初始化,然后在原创 2012-09-05 22:04:04 · 1708 阅读 · 1 评论 -
条款3:尽可能使用const
const与指针结合const与指针结合有两种情况:一个常指针:指向不能变;指向常对象的指针:可以指向其他的变量,但这些变量必须是const类型。怎么区别它们呢?const在*左边,则是一个指向常对象的指针,而const在*的右边,则是指针的指向不能变。举个例子: int a = 10; int b = 5; const int *pa = &a; //(*pa) = 10; 指原创 2012-09-05 11:14:09 · 839 阅读 · 0 评论 -
条款5:了解C++默默编写并调用哪些函数
这些函数包括:默认构造函数,复制构造函数,赋值构造函数,以及析构函数。这些函数都属于public部分。但是在有些情况下,赋值构造函数时没有意义的,此时编译器就会拒绝构造,举一个例子:template class Test{public: Test(const Type val,string& str):value(val),name(str){} void print(){co原创 2012-09-06 09:53:45 · 668 阅读 · 0 评论 -
条款6:若不想使用编译器自动生成函数,就该明确拒绝
前面已经提到过,编译器会默默的为你生成构造函数,赋值构造函数,复制构造函数和析构函数。但有些时候,我们并不希望其中的一些函数出现,尤其是复制和赋值构造函数。这该怎么办呢?有一个简单的办法,就是将这个两个函数声明放在private中,而不去定义它们。这样,类的用户无法直接对这个类的对象进行赋值和复制。而且,因为你只有声明,而没有定义,所以即使类的成员函数或者友元触发了相关的操作,也会因为没有定义原创 2012-09-06 10:26:29 · 804 阅读 · 0 评论 -
条款9:绝不在构造和析构过程中调用virtual函数
先看一个程序:class Base{public: Base(int val = 0); ~Base(){cout<<"基类析构函数"<<endl;} virtual void fun(){cout<<"base fun"<<endl;}protected: int value;};class Derived:public Base{public: Derived(i原创 2012-09-06 21:38:59 · 1039 阅读 · 0 评论 -
条款7:为多态基类声明virtual析构函数
可以通过一个简单的例子来说明问题,我们先定义了简单的基类和派生类:class Base{public: Base(){cout<<"base 构造"<<endl;}// virtual ~Base(){cout<<"base 析构"<<endl;} ~Base(){cout<<"base 析构"<<endl;} virtual void fun(){cout<<"base fun"原创 2012-09-06 14:42:34 · 1407 阅读 · 0 评论 -
条款10:令operator=返回一个reference to *this
其实这一条很显而易见,只有return *this;那么才能实现连续赋值。于此类似的还有其他的一些运算符。原创 2012-09-06 21:42:13 · 1155 阅读 · 0 评论 -
条款12:复制对象时勿忘其每一部分
对于一个类,如果你自己定义了构造函数和复制控制函数,那么编译器就不会生成默认的构造函数和复制控制函数,而且假设你的复制控制函数漏掉了某个成员,那么编译器也不会提醒这个错误:class Date{public: Date(int d = 1,int m = 1,int w = 1):day(d),month(m),weekday(w){cout<<"基类构造函数"<<endl;} Dat原创 2012-09-07 13:23:52 · 768 阅读 · 0 评论 -
条款18:让接口容易被正确使用,不容易被误用
“最好的情况,就是如果客户企图使用某个接口而却没有获得预期的行为,这个代码就不应该通过编译;如果代码通过了编译,它的作为就该是客户想要的。”所以在接口设计时,应该从用户的角度出发,考虑用户会犯什么错误:假如设计的是一个日期类:class Date{public: Date(int m, int d, int y):day(d),month(m),year(y){}private原创 2012-09-08 10:35:04 · 1074 阅读 · 0 评论 -
条款20:宁以pass by refrence to const 替换 pass by value
我们知道,如果pass by value,那么会发生实参复制一份给形参,然后函数的任何操作都是针对形参的,不会影响实参的值。但是如果实参是一个结构体,或者一个类,那么复制的代价就太大了,因为类的复制需要调用类的复制构造函数(如果是派生类还要调用基类的复制构造函数等等)。在C语言中,我们通常都会选择传递一个指向结构体的指针而不是传递结构体的对象本身。在C++中,我们可以通过引用的方式来处理实参到形参原创 2012-09-08 14:28:41 · 748 阅读 · 0 评论 -
条款19:设计class犹如设计type
类的设计者,应该像编程语言设计者考虑内置数据类型一样考虑类的设计,让这个类有自然地语法,直观的语义。你要考虑的内容如下:1.新tpye的对象应该如何被创建和销毁?这需要考虑的是你的构造函数、析构函数的设计。如果类中包含指针,那么还得考虑如何应对指针带来的种种恐怖的问题:比如内存泄露,重复删除指针等等。2.对象的初始化和对象的赋值该有什么差别。就是要考虑复制操作符和赋值操作符的区别。我们知道原创 2012-09-08 11:02:58 · 1463 阅读 · 0 评论 -
条款13:以对象管理资源
我们都知道,当new一个东西之后,必须delete它。但是问题可能出现在在new和delete之间:比如中间出现了异常,或者return之类的。一种比较好的作法是通过对象来管理:因为当对象的声明周期结束以后,会调用析构函数,而在析构函数中delete,这样的作法就靠谱多了。在标准C++中,定义了2种管理资源的对象:auto_ptr和share_ptr。我们先介绍它们。(他们需要#include原创 2012-09-07 16:29:41 · 737 阅读 · 0 评论 -
条款14:在资源管理中小心copying行为
如果某个管理资源的对象被赋值或者复制,那么会发生什么?这是一个比较头疼的问题,理论上,复制或者赋值时,两个资源管理对象应该指向同一个对象。只有当这两个对象都被删除以后,才会删除这个对象。所以像auto_ptr就直接禁止赋值,每次复制以后,原来的指针就变为空了。而我们希望的是智能指针,使用引用计数来解决这个问题。举一个例子:class Test;class U_PTR{ friend Tes原创 2012-09-07 20:01:18 · 759 阅读 · 0 评论 -
条款16:成对使用new和delete时要采取相同的形式
这条也很简单: int* parr = new int[5];//分配的是一个数组 delete[] parr; int* pval = new int(5);//分配的是一个变量 delete pval;与此相伴的一个问题是:如果一个人typedef了一个数组,那么在new和delete时就要小心了:typedef int ARRAY[4];那么就得: int* pAr原创 2012-09-07 20:48:35 · 635 阅读 · 0 评论 -
条款17:以独立语句将newed的对象置入智能指针
智能指针使我们防止类存泄露的利器。但是下面的情况需要引起我们的注意:void func1(){}void func2(tr1::shared_ptr p,void f()){}int main(){ func2(tr1::shared_ptr (new int(10)),func1); return 0;}我们能够确定,编译器会做3件事:1.new一个int*类型的指原创 2012-09-07 21:07:16 · 706 阅读 · 0 评论 -
条款23:宁以non-member、non-frined替换member函数
有的时候,我们既可以用成员函数实现某个功能,也可以用非成员非友元函数实现:class Test{public: Test(string string1 = "hello ",string string2 = "world!"):s1(string1),s2(string2){} void sayHello(){cout<<s1;} void sayWorld(){cout<<s2<<原创 2012-09-10 15:30:56 · 1280 阅读 · 0 评论 -
条款41:了解隐式接口和编译期多态
假如我们有一个模板函数:template bool judge(T &w){ if(w.size() > 10 && w != valA) return true; else return false;}那么对应的T类型似乎应该是这样的:size()函数返回一个int类型的值,而T类型重载了“!=” 操作符。(这里假设valA也是T类型)。而实际上,这个问题根本没有这原创 2012-09-17 10:00:43 · 810 阅读 · 0 评论