随笔 - 576  文章 - 0  评论 - 62  阅读 - 219万
03 2014 档案
【M32】在未来时态下发展程序
摘要:1、在未来时态下发展程序,就是接受“事情总会变化”的事实,并准备应对之策。2、记住,程序的维护者通常不是最初的开发者,因此,设计和实现的时候,应该考虑别人更好地理解,修改自己的程序。3、重要的一点就是,以C++语言本身来表现各种规范,而不是依靠注释或者说明文件。举例来说,如果copy构造和copy赋值没有意义,应该将他们声明为private。而不是简单地通过注释告诉用户,不要进行copy构造和copy赋值,因为用户才不管。4、让class的操作符和方法拥有自然的语法和直观的语义,和内置类型保持一致。5、记住,任何事情只要能够做,就会有人做。接受“用户会犯错”的事实。6、努力写出可移植的代码,记 阅读全文
posted @ 2014-03-31 20:12 Andy Niu 阅读(244) 评论(0) 推荐(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) 推荐(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) 推荐(0) 编辑
【M23】考虑使用其他程序库
摘要:1、程序库的设计是一种权衡的结果。体积小,速度快往往不能移植。可移植,通用的程序库往往意味着冗余和效率低下。2、因此,选择程序库的时候,需要进行取舍。比如:iostream和stdio。iostream具有类型安全性,可扩充,而stdio的效率更高。注意,对性能评估的时候,不要猜,应该进行多次实验进行确认。 阅读全文
posted @ 2014-03-25 18:45 Andy Niu 阅读(169) 评论(0) 推荐(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) 推荐(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) 推荐(0) 编辑
C++ for循环效率
摘要:1、考虑二维数组,在C++中,以先行后列的方式存储连续的数组元素。也就是同一行的元素在一起,同一列的元素之间有间隔,且间隔相同。理想情况下,二维数组的元素是随机访问的,可以直接定位,即i*列数+j。因此,对于二层for循环访问二维数组的元素,先行后列与先列后行的效率应该是一样的。但是,二维数组的数据往往需要多个内存分页才能容纳,由于C++同一行的元素在一起,先行后列,更符合空间局部性,可以减少换页。因此,先行后列的效率高。2、考虑二层for循环,有大循环和小循环。大循环在内部效率高。为什么?可简单地认为,跨越循环层是个耗时耗资源的操作,大循环在内部,跨越循环层的次数少。从另一方面理解,大循环在 阅读全文
posted @ 2014-03-25 17:57 Andy Niu 阅读(2401) 评论(0) 推荐(0) 编辑
【M24】了解虚方法、多继承、虚基类、RTTI的成本
摘要:1、编译器必须实现出C++语言的特性。一般情况下,我们只需要使用这些特性就好了,不需要关心内部的实现细节。但是,有些特性的实现,会对对象的大小和成员方法的执行速度造成影响。因此,有必要了解内部实现的细节。2、首先考虑虚方法,虚方法是用来实现多态的。多态是指对于指针和引用,表面类型和真实类型不一致的情况下,调用真实类型的虚方法。3、虚方法有关的实现细节为: a、父类有一个虚方法表(vtbl),可以认为是一个方法指针的数组(这里注意:对于数组,我们知道元素的类型必须一致,虚方法表中的虚方法类型是不一样的,这里进行了特殊处理),方法指针指向父类的虚方法。 b、子类整体拷贝父类的虚方法表,对于重写.. 阅读全文
posted @ 2014-03-12 18:00 Andy Niu 阅读(607) 评论(0) 推荐(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) 推荐(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) 推荐(0) 编辑
进程和线程
摘要:1、进程是操作系统进行资源分配和调度的基本单位,线程是CPU调度的基本单位,是进程中一个单一顺序的执行流。2、把进程当成做一件事情,操作系统管理进程,负责调度进程。具体怎么做?是进程的事。一个进程至少有一个主线程,可以有多个辅助线程,真正做事的是线程。进程做的事情:可以是一个线程按顺序一步一步做,也可以启动多个线程,协作完成。3、进程有一块内存,也就是可执行文件装载到操作系统中,从上到下大致为:栈,堆,全局存储区,常量存储区,代码区。而线程可以认为是一个执行流,每个线程都有一个自己的调用堆栈,可以访问进程中的资源,对于共享资源需要进行同步控制。而一个进程是不能访问另一个进程的资源,只能通过进程 阅读全文
posted @ 2014-03-10 15:27 Andy Niu 阅读(598) 评论(0) 推荐(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) 推荐(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) 推荐(0) 编辑
父类构造方法中调用虚方法
摘要:1、在C++中,明确指出,不要在父类构造方法调用虚方法,为啥? 因为,构造子类对象,首先调用父类构造方法(初始化列表,然后构造方法内代码),然后子类构造方法(初始化列表,然后构造方法内代码),在父类构造方法中,还没有子类的成分,也就是说,当前本质上还是父类对象。因此,调用的方法还是父类方法,不会产生预期的多态行为。2、但是,最近在C#当中发现一个很奇怪的现象:父类构造方法调用虚方法,也会产生多态的行为。确实让人奇怪,只能说编程语言细节太多。C#是如何做到的呢? 自己猜测,可能是在父类构造方法之前,完成了对虚方法表的整体拷贝,并且置换为重写后的方法。 阅读全文
posted @ 2014-03-06 20:45 Andy Niu 阅读(1260) 评论(0) 推荐(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) 推荐(0) 编辑
【M11】禁止异常流出析构方法之外
摘要:1、在两种情况下,调用析构方法:a、在正常状态下被销毁,栈上的对象离开作用域或者堆上的对象执行delete;b、抛出异常,堆栈回滚,栈上已经构造好的对象,也就是抛出异常之前的代码,自动调用析构方法。注意:只会对已经构造好的栈上对象调用析构方法,而不会对已经初始化好的指针执行delete,因此,使用智能指针可以避免这种情况的资源泄漏。2、考虑下面的情况,析构方法中抛出异常,在外部捕获异常。如果是正常情况下调用析构方法,没有问题。如果由于异常,堆栈回滚对栈上已经构造好的的对象调用析构方法,这个时候析构方法又抛出一个异常,导致C++调用terminate方法,结束程序。3、析构方法抛出异常,还有另外 阅读全文
posted @ 2014-03-06 20:05 Andy Niu 阅读(368) 评论(0) 推荐(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) 推荐(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) 推荐(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) 评论(0) 推荐(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) 推荐(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) 推荐(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) 推荐(0) 编辑
C++异常
摘要:相对于C语言,C++增加了异常机制。考虑,异常解决了什么问题,又带来了什么问题。异常解决了什么问题:1、问题检测与问题处理相分离。2、C语言只是返回一个整数,而异常带有上下文信息,方便找出问题。3、C语言返回的整数,可能忘记检查,而异常不可忽略,必须处理。4、异常可以跳级,发生异常后,堆栈回滚,保证栈上的对象析构。异常带来了什么问题:1、代码膨胀。抛出异常,保证栈上的对象析构,编译器必须插入一些代码。2、效率低,抛出异常,必然有临时对象的产生。 阅读全文
posted @ 2014-03-03 19:24 Andy Niu 阅读(262) 评论(0) 推荐(0) 编辑
【M5】对定制的“类型转换函数”保持警觉
摘要:1、隐式类型转换有两种情况:单个形参构造方法和隐式类型转换操作符。注意:隐式类型转换不是把A类型的对象a,转化为B类型的对象b,而是使用a对象构造出一个b对象,a对象并没有变化。2、单个形参构造方法包括两种情况:声明只有单个形参;或者声明有多个形参,但是除了第一形参,其他的形参都有默认值,也就是说,只要单个形参就能构造对象。 注意:默认形参必须从右向左进行。思考为什么? 调用方法的时候,从左到右使用实参初始化形参,没有提供实参,就使用默认形参值,因此默认形参必须从右向左进行。比如,有5个形参,后面三个有默认值,调用方法的时候,提供三个实参,后面两个使用默认值。3、隐式类型转换操作符,是一种.. 阅读全文
posted @ 2014-03-03 19:09 Andy Niu 阅读(307) 评论(0) 推荐(0) 编辑
方法调用的方式
摘要:1、方法调用的方式有:像过程一样调用,嵌套调用,递归调用,回调方法。2、考虑回调方法,回调方法的典型情况是:事件驱动程序和多线程。事件驱动比如:按钮的点击事件。多线程,就是把方法传递给线程。3、考虑,一个方法同时传递给多个线程,这个方法的执行流会重叠,不可预料。举个例子,几个人同时使用一个机器加工原料,肯定乱套。因此,线程方法不同于一般的方法调用,可以认为,每个线程都有自己的调用堆栈。对于共享数据,需要进行同步控制。同时,应该避免使用局部static对象,因为一个线程的修改,其他的线程也受到影响。 阅读全文
posted @ 2014-03-03 18:41 Andy Niu 阅读(612) 评论(0) 推荐(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) 推荐(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) 推荐(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) 评论(2) 推荐(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) 推荐(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) 推荐(0) 编辑

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

点击右上角即可分享
微信分享提示