【M32】在未来时态下发展程序
摘要:1、在未来时态下发展程序,就是接受“事情总会变化”的事实,并准备应对之策。2、记住,程序的维护者通常不是最初的开发者,因此,设计和实现的时候,应该考虑别人更好地理解,修改自己的程序。3、重要的一点就是,以C++语言本身来表现各种规范,而不是依靠注释或者说明文件。举例来说,如果copy构造和copy赋值没有意义,应该将他们声明为private。而不是简单地通过注释告诉用户,不要进行copy构造和copy赋值,因为用户才不管。4、让class的操作符和方法拥有自然的语法和直观的语义,和内置类型保持一致。5、记住,任何事情只要能够做,就会有人做。接受“用户会犯错”的事实。6、努力写出可移植的代码,记
阅读全文
posted @
2014-03-31 20:12
Andy Niu
阅读(244)
推荐(0) 编辑
【M34】如何在同一个程序中结合C++和C
摘要:1、C++和C混合编程的时候,需要考虑产生的目标文件的兼容性。2、名称重整,为什么要搞出名称重整? 连接器要求所有方法名必须独一无二。对于C语言,没问题。C++支持过载,也就是方法名相同,形参表不同的方法,因此编译器编译的时候,必须对方法名重整,保证方法名独一无二,满足连接器的要求。那么问题来了,C++和C混合编程,编译时没有问题,连接时出现问题了,C++进行了名称重载,而C没有,连接时名称对不上了。 怎么解决这个问题?就是使用extern C,告诉编译器,不要进行名称重整。对于C++与C共用的头文件,C++必须使用extern C,而C语言不识别extern C,因此使用预编译。对于__c.
阅读全文
posted @
2014-03-31 18:54
Andy Niu
阅读(545)
推荐(0) 编辑
【M25】将构造方法和非成员方法虚化
摘要:1、所谓虚化,就是根据引用或者指针的真实类型,决定调用哪个方法。2、构造方法虚化,就是根据引用(或者指针)的真实类型,构造出一个对象,如果指针的真实类型是Base,返回Base*;如果指针的真实类型是Derived,返回Derived*。解决办法是:Base定义一个virtual方法Clone,调用new Base(*this),返回Base*;Derived重写Clone方法,调用new Derived(*this),返回Derived*。注意:一般情况下,子类重写父类方法,要求返回类型必须一致。目前,父类返回Base*,子类重写可以返回Derived*,也就是说C++支持协变。(口诀:进去
阅读全文
posted @
2014-03-25 19:56
Andy Niu
阅读(282)
推荐(0) 编辑
【M23】考虑使用其他程序库
摘要:1、程序库的设计是一种权衡的结果。体积小,速度快往往不能移植。可移植,通用的程序库往往意味着冗余和效率低下。2、因此,选择程序库的时候,需要进行取舍。比如:iostream和stdio。iostream具有类型安全性,可扩充,而stdio的效率更高。注意,对性能评估的时候,不要猜,应该进行多次实验进行确认。
阅读全文
posted @
2014-03-25 18:45
Andy Niu
阅读(169)
推荐(0) 编辑
【M18】分期摊还预期的计算成本
摘要:1、基本思想就是:如果将来肯定要做某件事,并且这件事情耗时,提前把东西准备好,先做一部分。常用的使用场景有:2、考虑一个大的数据集合,集合中元素不断变化。经常要取出里面的最大值,正常的做法是:每次调用的时候,计算出最大值,这可能是一个耗时的操作。既然是经常取出最大值,那我就实时(每次对集合增删改的时候)更新最大值,需要的时候直接就返回最大值了。3、考虑stl中的vector,vector有5个元素,假设vector的内存刚好容纳5个元素,现在增加一个元素。那就意味着必须分配一块6个元素的内存,把原来的5个元素和新增的1个元素copy过来。这显然效率很低。解决办法是:预留一些内存。4、较快的速度
阅读全文
posted @
2014-03-25 18:20
Andy Niu
阅读(420)
推荐(0) 编辑
【M17】考虑使用缓式评估
摘要:1、缓式评估其实就是拖延战术,直到逼不得已的时候才去计算。缓式评估的使用场景有:2、引用计数,考虑String,String是一个内含char指针(char指针以'\0'结束)的资源管理类,正常情况下,String的copy构造和copy赋值都是深层copy,也就是对char指针指向的内容做一个副本,这个效率显然很低。考虑String s2 = s1; 后面的代码可能只是读取s2,没有必要做深层copy,s2与s1可以共享一份数据,也就是使用引用计数,来实现String。但是,当修改String的时候,必须做一个深层copy,也就是拖延战术。3、区分读与写,考虑cout<
阅读全文
posted @
2014-03-25 17:59
Andy Niu
阅读(448)
推荐(0) 编辑
C++ for循环效率
摘要:1、考虑二维数组,在C++中,以先行后列的方式存储连续的数组元素。也就是同一行的元素在一起,同一列的元素之间有间隔,且间隔相同。理想情况下,二维数组的元素是随机访问的,可以直接定位,即i*列数+j。因此,对于二层for循环访问二维数组的元素,先行后列与先列后行的效率应该是一样的。但是,二维数组的数据往往需要多个内存分页才能容纳,由于C++同一行的元素在一起,先行后列,更符合空间局部性,可以减少换页。因此,先行后列的效率高。2、考虑二层for循环,有大循环和小循环。大循环在内部效率高。为什么?可简单地认为,跨越循环层是个耗时耗资源的操作,大循环在内部,跨越循环层的次数少。从另一方面理解,大循环在
阅读全文
posted @
2014-03-25 17:57
Andy Niu
阅读(2401)
推荐(0) 编辑
【M24】了解虚方法、多继承、虚基类、RTTI的成本
摘要:1、编译器必须实现出C++语言的特性。一般情况下,我们只需要使用这些特性就好了,不需要关心内部的实现细节。但是,有些特性的实现,会对对象的大小和成员方法的执行速度造成影响。因此,有必要了解内部实现的细节。2、首先考虑虚方法,虚方法是用来实现多态的。多态是指对于指针和引用,表面类型和真实类型不一致的情况下,调用真实类型的虚方法。3、虚方法有关的实现细节为: a、父类有一个虚方法表(vtbl),可以认为是一个方法指针的数组(这里注意:对于数组,我们知道元素的类型必须一致,虚方法表中的虚方法类型是不一样的,这里进行了特殊处理),方法指针指向父类的虚方法。 b、子类整体拷贝父类的虚方法表,对于重写..
阅读全文
posted @
2014-03-12 18:00
Andy Niu
阅读(607)
推荐(0) 编辑
计算机整数的表示
摘要:1、无论是int,还是uint在底层的存储方式是一样的,都会存在溢出和借位的现象,可以认为是个环,有以下几个情况: a、两个数相加,溢出会变成一个小的数。 b、两个数相减,不够减,借一位。 c、求一个负数,拿0减去对应的正数。注意:正6是0x0000 0006;负6可不是0x1000 0006,而是0 - 0x0000 0006 = 0xffff fffa2、对于int,uint存储方式相同,不同的只是编译器把它当成什么数来解释。比如: int a = 0xffff fff9; unsigned int b = 0xffff fff9; 对于a,把它当成int解释,就是-7;对于b...
阅读全文
posted @
2014-03-12 15:30
Andy Niu
阅读(403)
推荐(0) 编辑
Python 存储模型
摘要:1、Python彻底分离了对象和引用,可以认为内存中的对象都是不可修改的,每次修改引用,相当于在堆上重新创建一个对象,引用指向新对象。2、对于数值和字符串,修改意味着引用指向一个新对象。3、集合中的元素都是引用。考虑元组,元组中的引用不能增加删除,也不能修改引用的指向。但是元组本身也是个引用,可以指向另一个元组。4、考虑列表,列表中的引用可以增加删除,也可以修改引用的指向。列表本身也是个引用,也可以指向另一个列表。5、考虑字典,字典的key不能修改指向,value可以修改指向。字典本身也是个引用,也可以指向另一个字典。6、考虑下面的情况,listB = listA, listB 与 listA
阅读全文
posted @
2014-03-12 15:27
Andy Niu
阅读(3880)
推荐(0) 编辑
进程和线程
摘要:1、进程是操作系统进行资源分配和调度的基本单位,线程是CPU调度的基本单位,是进程中一个单一顺序的执行流。2、把进程当成做一件事情,操作系统管理进程,负责调度进程。具体怎么做?是进程的事。一个进程至少有一个主线程,可以有多个辅助线程,真正做事的是线程。进程做的事情:可以是一个线程按顺序一步一步做,也可以启动多个线程,协作完成。3、进程有一块内存,也就是可执行文件装载到操作系统中,从上到下大致为:栈,堆,全局存储区,常量存储区,代码区。而线程可以认为是一个执行流,每个线程都有一个自己的调用堆栈,可以访问进程中的资源,对于共享资源需要进行同步控制。而一个进程是不能访问另一个进程的资源,只能通过进程
阅读全文
posted @
2014-03-10 15:27
Andy Niu
阅读(598)
推荐(1) 编辑
【S17】使用“swap技巧”除去多余的容量
摘要:1、考虑下面的需求,对于vec开始的时候有1000个元素,后来只有10个元素,那么vec的capacity至少还是1000,后面的990个内存单元,没有使用,但是还被vec霸占着。如何释放这些内存呢?2、我们知道,vector进行copy构造的时候,根据rhs 的size进行分配内存。因此,我们可以建立一个临时对象,然后交换一下就可以了。如下: vector(vec).swap(vec); vector(vec) 是个临时对象,可认为capacity为10,而vec的capacity为1000,二者交换后,vec的capacity为10,临时对象析构。3、这里需要注意两点: a、临时对象...
阅读全文
posted @
2014-03-10 12:13
Andy Niu
阅读(488)
推荐(0) 编辑
理解reserve与resize
摘要:1、首先明白capacity与size的概念,capacity表示当前可以容纳多少个元素,size表示当前有多少个元素。为了避免频繁地分配内存,vector预留了一些内存。也就是说:sizecapacity,重新分配内存,把原来的数据copy过来,修改capacity = n。 b、如果ncapacity,重新分配内存,把原来的数据copy过来,在尾部补齐元素。没有指定构造方法,使用default构造方法。
阅读全文
posted @
2014-03-10 11:27
Andy Niu
阅读(400)
推荐(0) 编辑
父类构造方法中调用虚方法
摘要:1、在C++中,明确指出,不要在父类构造方法调用虚方法,为啥? 因为,构造子类对象,首先调用父类构造方法(初始化列表,然后构造方法内代码),然后子类构造方法(初始化列表,然后构造方法内代码),在父类构造方法中,还没有子类的成分,也就是说,当前本质上还是父类对象。因此,调用的方法还是父类方法,不会产生预期的多态行为。2、但是,最近在C#当中发现一个很奇怪的现象:父类构造方法调用虚方法,也会产生多态的行为。确实让人奇怪,只能说编程语言细节太多。C#是如何做到的呢? 自己猜测,可能是在父类构造方法之前,完成了对虚方法表的整体拷贝,并且置换为重写后的方法。
阅读全文
posted @
2014-03-06 20:45
Andy Niu
阅读(1260)
推荐(0) 编辑
【M14】明智运用异常规范
摘要:1、异常规范的使用场景是,承诺方法只抛出什么样的异常,或者不抛出异常。如果运行的时候,不满足承诺,C++自动调用unexpected方法,unexpected调用terminate方法,terminate调用abort方法结束程序。2、有三点需要注意:a、表面上不抛出任何异常,仔细分析还是可能会抛出异常;b、调用其他的方法,而其他的方法可能抛出异常;c、调用系统的方法,系统方法可能抛出异常。因此,承诺只抛出某些异常,或者不抛出异常(异常规范),基本上不太靠谱。3、如果抛出了,非预期的异常。默认情况下,调用unexpected-->terminate-->abort,导致程序结束。因
阅读全文
posted @
2014-03-06 20:35
Andy Niu
阅读(374)
推荐(0) 编辑
【M11】禁止异常流出析构方法之外
摘要:1、在两种情况下,调用析构方法:a、在正常状态下被销毁,栈上的对象离开作用域或者堆上的对象执行delete;b、抛出异常,堆栈回滚,栈上已经构造好的对象,也就是抛出异常之前的代码,自动调用析构方法。注意:只会对已经构造好的栈上对象调用析构方法,而不会对已经初始化好的指针执行delete,因此,使用智能指针可以避免这种情况的资源泄漏。2、考虑下面的情况,析构方法中抛出异常,在外部捕获异常。如果是正常情况下调用析构方法,没有问题。如果由于异常,堆栈回滚对栈上已经构造好的的对象调用析构方法,这个时候析构方法又抛出一个异常,导致C++调用terminate方法,结束程序。3、析构方法抛出异常,还有另外
阅读全文
posted @
2014-03-06 20:05
Andy Niu
阅读(368)
推荐(0) 编辑
理解回调方法
摘要:1、回调方法是什么? 回调方法是把方法作为实参传递给另一个方法,比如传给Fun,Fun的形参是方法指针,回调方法与方法指针类型要匹配,在Fun中完成对方法的调用。2、回调方法解决什么问题?也就是说,它的使用场景是什么? a、事件驱动程序,点击按钮,我们期望做某件事,把自定义的方法传递过去。 b、在STL中,特别常用。STL中的算法,形参往往是个方法指针,允许用户自定义查询条件(也就是一个方法),传递给算法,算法负责调用这个方法。 c、异步回调方法,典型的情况如:socket编程中,acceptor.async_accept(client, callback)。可认为在异步方法async...
阅读全文
posted @
2014-03-05 19:43
Andy Niu
阅读(776)
推荐(0) 编辑
理解sizeof
摘要:1、sizeof返回的是字节个数,内存编址的最小单元是字节。因此,空对象,bool值占用的内存也是一个字节。2、可以对哪些东西求sizeof ? a、对象和类型。如int a; sizeof(a), sizeof(int),二者是等价的。同一类型的对象,大小是一致的,并且在内存中的布局也是一样的,这样编译器才能够按照统一的方式去解释。可以认为sizeof(a),转化为sizeof(int)进行求值。 b、不能对方法名和void,计算sizeof。 c、可以对指针计算sizeof,所有指针的大小都是4个字节。包括:指向数据的指针,指向方法的指针,指向void的指针。3、对于数组名,非常特殊...
阅读全文
posted @
2014-03-04 20:23
Andy Niu
阅读(586)
推荐(0) 编辑
Python int与string之间的转化
摘要:string-->int1、10进制string转化为int int('12')2、16进制string转化为int int('12', 16)int-->string1、int转化为10进制string str(18)2、int转化为16进制string hex(18)考虑,为什么没有16进制int转化为string,可以这么认为不管什么进制,python在内部表示都是10进制,先转化为10进制在进行。如16进制int转化为string,str(0x12),首先变为str(18),再到'18'。那么我想结果为'12'
阅读全文
posted @
2014-03-04 20:10
Andy Niu
阅读(369186)
推荐(2) 编辑
传值与传引用
摘要:1、在C++中,可认为只有传值和传引用。传指针本质上就是传值。将a指针传给b指针,两个指针是两个对象(而引用是别名),它们的值相等,即a、b指向同一块内存。这个时候,要千万注意:修改指针使它指向另一块内存与修改指针指向的内容之间的区别。以b为例说明,修改b使它指向另一块内存,a不变,还是指向原来的内存,内容也不变。修改b指向的内容,由于a,b指向同一块内存,也就是修改a指向的内容,a内容发生变化。2、复制指针是浅拷贝,两个指针地址相同,共享同一块内存。如何进行深拷贝? 把原指针指向的内容做一个整体拷贝,新指针指向拷贝的内容,这就是深拷贝。注意:深拷贝后,每个指针指向不同的内存,内容相等,但是.
阅读全文
posted @
2014-03-04 19:59
Andy Niu
阅读(1779)
推荐(0) 编辑
【M10】在构造方法内阻止资源泄漏
摘要:1、类中没有指针,如果对象构造过程中出现异常,C++保证已经构造好的那一部分自动销毁。注意:这里不是调用析构方法,而是编译器在你的构造方法中插入了一些代码,保证对已经构造好的对象析构。2、类中有指针,比如有a, b两个指针。对象构造时,a初始化完成,b抛出异常,C++只会对已经构造好的对象析构,而不会对已经初始化好的指针执行delete。因此资源泄漏。3、注意,这种情况下,绝不会调用析构方法,因为C++只会析构已经构造完成的对象。接着思考,在堆上分配对象,把指针传出来,对指针delete,可行吗? 不可行,因为构造过程出现异常,指针是传递不出来的。外部的指针还是null。4、思考:为什么C+.
阅读全文
posted @
2014-03-03 20:32
Andy Niu
阅读(418)
推荐(0) 编辑
【M9】利用destructors避免泄漏资源
摘要:1、在堆上获取的动态资源,用户忘记delete,或者由于异常导致没有没执行到delete,都会造成资源泄漏。2、我们知道,栈上的对象,离开作用域,必定要执行析构方法。即使抛出异常,会堆栈回滚,保证已经构造的对象进行析构。3、因此,可以使用栈上的对象,管理资源,在析构方法中释放资源,保证不会资源泄漏。4、考虑更一般化的资源,对于互斥体的加锁,解锁,也是同样的情况,需要保证互斥体一定会解锁。5、对资源管理的类,就是智能指针。因此,需要对不同类型的对象进行管理,智能指针是模板类,在栈上分配,行为像指针。常用的智能指针有auto_ptr,shared_ptr。注意,智能指针默认的删除动作是delete
阅读全文
posted @
2014-03-03 19:49
Andy Niu
阅读(445)
推荐(0) 编辑
C++异常
摘要:相对于C语言,C++增加了异常机制。考虑,异常解决了什么问题,又带来了什么问题。异常解决了什么问题:1、问题检测与问题处理相分离。2、C语言只是返回一个整数,而异常带有上下文信息,方便找出问题。3、C语言返回的整数,可能忘记检查,而异常不可忽略,必须处理。4、异常可以跳级,发生异常后,堆栈回滚,保证栈上的对象析构。异常带来了什么问题:1、代码膨胀。抛出异常,保证栈上的对象析构,编译器必须插入一些代码。2、效率低,抛出异常,必然有临时对象的产生。
阅读全文
posted @
2014-03-03 19:24
Andy Niu
阅读(262)
推荐(0) 编辑
【M5】对定制的“类型转换函数”保持警觉
摘要:1、隐式类型转换有两种情况:单个形参构造方法和隐式类型转换操作符。注意:隐式类型转换不是把A类型的对象a,转化为B类型的对象b,而是使用a对象构造出一个b对象,a对象并没有变化。2、单个形参构造方法包括两种情况:声明只有单个形参;或者声明有多个形参,但是除了第一形参,其他的形参都有默认值,也就是说,只要单个形参就能构造对象。 注意:默认形参必须从右向左进行。思考为什么? 调用方法的时候,从左到右使用实参初始化形参,没有提供实参,就使用默认形参值,因此默认形参必须从右向左进行。比如,有5个形参,后面三个有默认值,调用方法的时候,提供三个实参,后面两个使用默认值。3、隐式类型转换操作符,是一种..
阅读全文
posted @
2014-03-03 19:09
Andy Niu
阅读(307)
推荐(0) 编辑
方法调用的方式
摘要:1、方法调用的方式有:像过程一样调用,嵌套调用,递归调用,回调方法。2、考虑回调方法,回调方法的典型情况是:事件驱动程序和多线程。事件驱动比如:按钮的点击事件。多线程,就是把方法传递给线程。3、考虑,一个方法同时传递给多个线程,这个方法的执行流会重叠,不可预料。举个例子,几个人同时使用一个机器加工原料,肯定乱套。因此,线程方法不同于一般的方法调用,可以认为,每个线程都有自己的调用堆栈。对于共享数据,需要进行同步控制。同时,应该避免使用局部static对象,因为一个线程的修改,其他的线程也受到影响。
阅读全文
posted @
2014-03-03 18:41
Andy Niu
阅读(612)
推荐(0) 编辑
【M4】非必要不提供default 构造方法
摘要:1、default 构造方法意味着,没有外来信息的情况下,进行初始化,构造出一个对象。对于有些对象是很合理的,比如数值之类的对象,可以初始化为0;对于指针之类的对象,初始化为null;对于集合如vector,list,可以初始化为一个空容器。2、对于有些对象,必须要有外来信息,才能构造出一个有意义的对象。比如Person,要有name才能构造出一个有意义的Person对象。3、因此,对于没有外来信息,也能合理构造出一个对象的类,必须提供default 构造方法。对于没有外来信息,不能构造出一个对象的类,不能有default构造方法。4、现在考虑,没有default构造方法,使用类会有哪些限制?
阅读全文
posted @
2014-03-02 18:52
Andy Niu
阅读(592)
推荐(0) 编辑
【M3】绝对不要以多态方式处理数组
摘要:1、考虑下面的情况,有个方法,如下: void Print(ostream& s, const Base array[], int size) { for(int i=0; i< size; ++i) { s<<array[i]; } }2、对于Base baseArray[10]; Print(cout, baseArray, 10) 没有问题。在C++中,子类对象组成的数组,可以复制给父类的引用或者指针。数组名也可以认为是个地址。 考虑: Derived derivedArray[10]; Print(cout, derivedArray, 10) 会...
阅读全文
posted @
2014-03-02 17:10
Andy Niu
阅读(565)
推荐(0) 编辑
100亿个数字找出最大的10个
摘要:1、首先一点,对于海量数据处理,思路基本上是确定的,必须分块处理,然后再合并起来。2、对于每一块必须找出10个最大的数,因为第一块中10个最大数中的最小的,可能比第二块中10最大数中的最大的还要大。3、分块处理,再合并。也就是Google MapReduce 的基本思想。Google有很多的服务器,每个服务器又有很多的CPU,因此,100亿个数分成100块,每个服务器处理一块,1亿个数分成100块,每个CPU处理一块。然后再从下往上合并。注意:分块的时候,要保证块与块之间独立,没有依赖关系,否则不能完全并行处理,线程之间要互斥。另外一点,分块处理过程中,不要有副作用,也就是不要修改原数据,否则
阅读全文
posted @
2014-03-02 15:58
Andy Niu
阅读(17822)
推荐(3) 编辑
【M2】最好使用C++转型操作符
摘要:1、C语言中的转型操作符有两个问题: a、是个通用的转换操作符,也就是说,可以从一个类型转换到其他类型。通用必定是低效率和冗余的,因为要考虑很多情况。 b、在代码中,难以辨认出哪些是类型转换。2、C++提供了新式的转换操作符,有static_cast, dynamic_cast, const_cast, reinterpret_cast, 可以认为对不同的类型转换进行了细化,精确地指出意图,效率高。另外一方面,很容易在代码中辨认出这些类型转换。3、考虑,C++新式转换符的用法。 a、const_cast:去除对象的常量性。 b、dynamic_cast:用于向下转型或者跨系转型,类型必...
阅读全文
posted @
2014-03-02 15:28
Andy Niu
阅读(244)
推荐(0) 编辑
【M26】限制某个class所能产生的对象数量
摘要:1、每当产生一个对象,必定调用构造方法。因此,禁止产生对象的做法就是,将所有的构造方法声明为private。2、只有在类的内部才可以访问private成员,有两层含义:在类的内部可以访问this的private成员,同时可以访问同类对象的private成员。3、将构造方法声明为private,只是限制了在外部调用构造方法产生对象,还是有办法可以产生对象。办法有: a、类暴露一个static方法,在static方法内部调用private构造方法,产生对象返回。 b、在类中声明友元方法,或者友元类,这样的话,就可以访问该类的private构造方法。4、考虑,只产生一个对象。该怎么办?使用友元方..
阅读全文
posted @
2014-03-02 11:19
Andy Niu
阅读(551)
推荐(0) 编辑