C++

1  1.栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2 2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3 3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后由系统释放。-->分别是data区,bbs区
4 4、文字常量区 —常量字符串就是放在这里的。 程序结束后由系统释放->coment区
5 5、程序代码区—存放函数体的二进制代码。-->code区
6 Bss 代码段 数据段
View Code

 2、堆和栈的区别

1 1.管理方式不同:栈,由编译器自动管理,无需程序员手工控制;堆:产生和释放由程序员控制。
2 2.空间大小不同:栈的空间有限;堆内存可以达到4G,。
3 3.能否产生碎片不同:栈不会产生碎片,因为栈是种先进后出的队列。堆则容易产生碎片,多次的new/delete会造成内存的不连续,从而造成大量的碎片。
4 4.生长方向不同:堆的生长方式是向上的,栈是向下的。
5 5.分配方式不同:堆是动态分配的。栈可以是静态分配和动态分配两种,但是栈的动态分配由编译器释放。
6 6.分配效率不同:栈是机器系统提供的数据结构,计算机底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令。堆则是由C/C++函数库提供,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
7  7.堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。有时候分配大量的内存空间,还是用堆好一些。尽量用栈,而不是用堆。 无论是堆还是栈,都要防止越界现象的发生。
堆栈区别

3、说下栈和队列的区别?那么你能不能用两个队列实现一个栈 (或者两个栈实现一个队列),写出具体的代码

  1 答:1.队列先进先出,栈先进后出。
  2   2.对插入和删除操作的"限定":栈是限定只能在表的一端进行插入和删除操作的线性表。             
  3 队列是限定只能在表的一端进行插入和在另一端进行删除操作的线性表。     
  4 3.遍历数据速度不同。栈只能从头部取数据也就最先放入的需要遍历整个栈最后才能取出来,而且在遍历数据的时候还得为数据开辟临时空间,保持数据在遍历前的一致性队列则不同,他基于地址指针进行遍历,而且可以从头或尾部开始遍历,但不能同时遍历,无需开辟临时空间,因为在遍历的过程中不影像数据结构,速度要快的多
  5 可将线性表和栈及队列的插入和删除操作对比如下:
  6 5.线性表允许在表内任一位置进行插入和删除
  7      Insert(L,i,x)(1≤i≤n+1)
  8      Delete(L,i) (1≤i≤n) 如线性表
  9 栈只允许在表尾一端进行插入和删除
 10     Insert(L,n+1,x)
 11     Delete(L,n)
 12     队列只允许在表尾一端进行插入,在表头一端进行删除
 13     Insert(L,n+1,x)
 14     Delete(L,1)
 15 
 16 1. 用两个堆栈实现一个队列。
 17 思路:对于insert,把数据插入到第一个堆栈中;
 18       对于remove,如果第二个堆栈为空,把第一个堆栈的所有元素pop出来并放入第二个堆栈中,然后返回第二个堆栈的第一个元素。
 19 using namespace std;
 20 class Queueby2s{
 21 private: 
 22     Stack s1;
 23     Stack s2;
 24     int len;
 25 public:
 26     Queueby2s(): len(0) { }
 27     int getSize() { return len; }
 28     void enQueue(int n)
 29     {
 30         s1.push(n);
 31         ++len;
 32     }
 33     void deQueue()
 34     {
 35         int ret;
 36         if(0 == len)
 37         {
 38          cout<<"UNDERFLOW!"<<endl;
 39          return -1;
 40         }
 41         if(s2.size() == 0)
 42         {
 43             while(s1.size() > 0)
 44            {
 45               s2.push( s1.pop() );
 46            }
 47            ret = s2.pop();
 48            --len;
 49            return ret;
 50         }
 51     }
 52 };
 53 2. 用两个队列实现一个栈。
 54 思路:对于Push: 如果两个队列都为空,就插入到第一个队列中;否则就插入到非空的那个队列中;
 55    对于Pop: 把非空的那个队列的每个元素remove出来,然后插入到另一个队列中,直到剩下最后一个元素,然后将其返回。
 56 
 57 //implement Stack by 2 queues
 58 class Stackby2q{
 59 private:
 60     Queue q1;
 61     Queue q2;
 62     int len;
 63 public:
 64     Stackby2q():len(0) {}
 65     int getSize() {return len;}
 66     void push(int n)
 67     {
 68         //if both queues empty, insert into q1.
 69         //otherwise, insert into the non-empty queue.
 70         if( ( q1.size() == 0 ) && ( q2.size == 0 ) )
 71         {
 72             q1.enqueue(n);
 73         }
 74         else if(q1.size() > 0)
 75             q1.enqueue(n);
 76         else if(q2.size() > 0)
 77             q2.enqueue(n);
 78         ++len;
 79     }
 80     int pop()
 81     {
 82         int ret;
 83         if(0 == len)
 84         {
 85             cout<<"UNDERFLOW!"<<endl;
 86            return -1;
 87         }
 88         if(q1.size() > 0) // q2 is empty
 89         {
 90             while(q1.size() > 1)
 91             q2.enqueue( q1.dequeue() );
 92             ret = q1.dequeue();
 93          return ret;
 94         }
 95         else // q1 is empty
 96         {
 97             while(q2.size() > 1)
 98             q1.enqueue( q2.dequeue() );
 99             ret = q2.dequeue();
100             return ret;
101         }
102     }
103 };
104 3. 用1个栈实现一个队列。
105 思路:用递归的方法把数据从最底部移出来。
106 4.用1个队列实现一个栈。
107 思路:对于每次pop,用递归的方法反转队列元素的排列,然后返回第一个元素。
View Code

 4、循环队列

 1 由于队列有元素出列,front就向后移动,所以队列前面的空间就空了出来。为了更合理的利用空间,人们想了一个办法:将队列的首尾相连接。这样当rear移动到LENGTH时,会再从0开始循环。那当什么时候队列满呢?当rear等于front的时候。可是队列为空的时候也是同样的条件,那不就没法判断了吗?又有人提出了这样的想法:牺牲一个存储空间,front前面不存数据,当rear在front前面的时候就是满了,如图:
 2  
 3 
 4  
 5 当rear在front之前时,队列中剩余一个空间,有 LENGTH - 1个元素,所以rear也为
 6 LENGTH - 1。这时就算是队列满了。于是
 7     满的判断条件应为:(rear+1)%LENGTH == front 。
 8     空的判断条件为 rear == front。
 9  
10 所以一些操作有些变化: 
11 5、入队列操作
12 [cpp] view plaincopyprint?
13 #include"queue.h"  
14   
15 void insert_sequence_queue(sequence_queue *sq,datatype data){  
16     if((sq->rear+1)%LENGTH == sq->front){  
17         printf("the queue is full\n");  
18         exit(1);  
19     }  
20     sq->data[sq->rear] = data;  
21     sq->rear= (sq->rear+1)%LENGTH;  
22 }  
23 
24 6、出队列操作
25 [cpp] view plaincopyprint?
26 #include"queue.h"  
27   
28 datatype delete_sequence_queue(sequence_queue *sq){  
29     if(sq->rear == sq->front){  
30         printf("the queue is empty!\n");  
31         exit(1);  
32     }    
33     if(sq->front+1 == LENGTH){  
34         sq->front = (sq->front+1)%LENGTH;       //其实就是0       
35         return sq->data[LENGTH-1];  
36     }          
37     sq->front++;  
38     return sq->data[sq->front-1];  
39 }  
40 
41  
42 7、打印队列的内容
43 [cpp] view plaincopyprint?
44 #include"queue.h"  
45   
46 void display_sequence_queue(sequence_queue *sq){  
47     if(sq->front == sq->rear){  
48         printf("the queue is empty!\n");  
49         exit(1);  
50     }     
51     int i ;  
52     for(i=sq->front;i!=sq->rear;){  
53         printf("%c ",sq->data[i]);              
54         i=(i+1)%LENGTH;  
循环队列

5、哈弗曼编码

 1 1、哈夫曼编码简介
 2 哈夫曼编码(Huffman Coding)是一种编码方式,哈夫曼编码是可变字长编码(VLC)的一种。Huffman于1952年提出一种编码方法,该方法完全依据字符出现概率来构造异字头的平均长度最短的码字,有时称之为最佳编码,一般就叫做Huffman编码(有时也称为霍夫曼编码)。
 3 2、哈夫曼编码的核心思想
 41)每一个字符用一个0、1串作为其代码,并要求任意一个字符的代码都不是其他字符代码的前缀;
 52)用字符在文件中出现的频率表来建立一个用0、1串表示各字符 的最优表示方式,即使出现频率高的字符获得较短的编码,出现频率较低的字符获得较长的编码;
 63)将字符在文件中出现的频率值作为一棵二叉树的叶子结点的权值,并通过构造一棵哈夫曼树得到最优前缀码。
 7 3、哈夫曼树的构建
 8 啥夫曼树又称最优二叉树。它是由n个带权叶子结点构成的所有二叉树中,带权路径长度(即树中所有叶子结点的带权路径长度之和)最小的二叉树。
 9 构造哈夫曼树的步骤如下:
10 哈夫曼编码步骤: 
11 一、对给定的n个权值{W1,W2,W3,...,Wi,...,Wn}构成n棵二叉树的初始集合F= {T1,T2,T3,...,Ti,...,Tn},其中每棵二叉树Ti中只有一个权值为Wi的根结点,它的左右子树均为空。(为方便在计算机上实现算 法,一般还要求以Ti的权值Wi的升序排列。)
12 二、在F中选取两棵根结点权值最小的树作为新构造的二叉树的左右子树,新二叉树的根结点的权值为其左右子树的根结点的权值之和。
13 三、从F中删除这两棵树,并把这棵新的二叉树同样以升序排列加入到集合F中。
14 四、重复二和三两步,直到集合F中只有一棵二叉树为止。 
15 简易的理解就是,假如我有A,B,C,D,E五个字符,出现的频率(即权值)分别为5,4,3,2,1,那么我们第一步先取两个最小权值作为左右子树构造一个新树,即取1,2构成新树,其结点为1+2=3,如图: 
16 
17 虚线为新生成的结点,第二步再把新生成的权值为3的结点放到剩下的集合中,所以集合变成{5,4,3,3},再根据第二步,取最小的两个权值构成新树,如图: 
18 
19 再依次建立哈夫曼树,如下图: 
20 
21 其中各个权值替换对应的字符即为下图: 
22 
23 所以各字符对应的编码为:A->11,B->10,C->00,D->011,E->010 
24 霍夫曼编码是一种无前缀编码。解码时不会混淆。其主要应用在数据压缩,加密解密等场合。
25 4、哈夫曼编码
26 得到哈夫曼树后,自顶向下按路径编号,指向左节点的边编号0,指向右节点的边编号1,从根到叶节点的所有边上的0和1连接起来,就是叶子节点中字符的哈夫曼编码。
哈弗曼编码

6、哈希表 解决冲突的方法

 1 哈希表是根据设定的哈希函数H(key)和处理冲突方法将一组关键字映射到一个有限的地址区间上,并以关键字在地址区间中的象作为记录在表中的存储位置,这种表称为哈希表或散列,所得存储位置称为哈希地址或散列地址。作为线性数据结构与表格和队列等相比,哈希表无疑是查找速度比较快的一种。
 2 什么是哈希冲突:如:数组的长度是5。这时有一个数据是6。那么如何把这个6存放到长度只有5的数组中呢。按照取模法,计算6%5,结果是1,那么就把6放到数组下标是1的位置。那么,7就应该放到2这个位置。到此位置,哈希冲突还没有出现。这时,有个数据是11,按照取模法,1151,也等于1。那么原来数组下标是1的地方已经有数了,是6。这时又计算出1这个位置,那么数组1这个位置,就必须储存两个数了。这时,就叫
 3 哈希冲突。冲突之后就要按照顺序来存放了。如果数据的分布比较广泛,而且储存数据的数组长度比较大。那么哈希冲突就比较少。否则冲突是很高的。
 4 
 5 虽然我们不希望发生冲突,但实际上发生冲突的可能性仍是存在的。当关键字值域远大于哈希表的长度,而且事先并不知道关键字的具体取值时。冲突就难免会发 生。另外,当关键字的实际取值大于哈希表的长度时,而且表中已装满了记录,如果插入一个新记录,不仅发生冲突,而且还会发生溢出。因此,处理冲突和溢出是 哈希技术中的两个重要问题。
 6 
 7 
 8 
 9 一、解决散列碰撞(collision)的方法
101)链接法
112)开放寻址法
12 a.线性探测:h(k, i) = (h'(k) + i) mod m
13 b.二次探测:h(k, i) = (h'(k) + c1*i + c2 *i^2) mod m
14 c.双重散列:h(k, i) = (h1(k) + i * h2(k)) mod m
153)完全散列:设计一个较小的二次散列表
16 1、开放定址法
17  用开放定址法解决冲突的做法是:当冲突发生时,使用某种探查(亦称探测)技术在散列表中形成一个探查(测)序列。沿此序列逐个单元地查找,直到找到给定 的关键字,或者碰到一个开放的地址(即该地址单元为空)为止(若要插入,在探查到开放的地址,则可将待插入的新结点存人该地址单元)。查找时探查到开放的 地址则表明表中无待查的关键字,即查找失败。
18 注意:
19 ①用开放定址法建立散列表时,建表前须将表中所有单元(更严格地说,是指单元中存储的关键字)置空。
20 ②空单元的表示与具体的应用相关。
21  按照形成探查序列的方法不同,可将开放定址法区分为线性探查法、线性补偿探测法、随机探测等。
221)线性探查法(Linear Probing)
23 该方法的基本思想是:
24 将散列表T[0..m-1]看成是一个循环向量,若初始探查的地址为d(即h(key)=d),则最长的探查序列为:
25 d,d+l,d+2,…,m-101,…,d-1
26  即:探查时从地址d开始,首先探查T[d],然后依次探查T[d+1],…,直到T[m-1],此后又循环到T[0],T[1],…,直到探查到T[d-1]为止。
27 探查过程终止于三种情况:
28  (1)若当前探查的单元为空,则表示查找失败(若是插入则将key写入其中);
29 (2)若当前探查的单元中含有key,则查找成功,但对于插入意味着失败;
30  (3)若探查到T[d-1]时仍未发现空单元也未找到key,则无论是查找还是插入均意味着失败(此时表满)。
31 利用开放地址法的一般形式,线性探查法的探查序列为:
32 hi=(h(key)+i)%m 0≤i≤m-1 //即di=i
33 用线性探测法处理冲突,思路清晰,算法简单,但存在下列缺点:
34 ① 处理溢出需另编程序。一般可另外设立一个溢出表,专门用来存放上述哈希表中放不下的记录。此溢出表最简单的结构是顺序表,查找方法可用顺序查找。
35 ② 按上述算法建立起来的哈希表,删除工作非常困难。假如要从哈希表 HT 中删除一个记录,按理应将这个记录所在位置置为空,但我们不能这样做,而只能标上已被删除的标记,否则,将会影响以后的查找。
36 ③ 线性探测法很容易产生堆聚现象。所谓堆聚现象,就是存入哈希表的记录在表中连成一片。按照线性探测法处理冲突,如果生成哈希地址的连续序列愈长 ( 即不同关键字值的哈希地址相邻在一起愈长 ) ,则当新的记录加入该表时,与这个序列发生冲突的可能性愈大。因此,哈希地址的较长连续序列比较短连续序列生长得快,这就意味着,一旦出现堆聚 ( 伴随着冲突 ) ,就将引起进一步的堆聚。
372)线性补偿探测法
38 线性补偿探测法的基本思想是:
39 将线性探测的步长从 1 改为 Q ,即将上述算法中的 j = (j + 1) % m 改为: j = (j + Q) % m ,而且要求 Q 与 m 是互质的,以便能探测到哈希表中的所有单元。
40 【例】 PDP-11 小型计算机中的汇编程序所用的符合表,就采用此方法来解决冲突,所用表长 m = 1321 ,选用 Q = 2541 
423)随机探测
43 随机探测的基本思想是:
44 将线性探测的步长从常数改为随机数,即令: j = (j + RN) % m ,其中 RN 是一个随机数。在实际程序中应预先用随机数发生器产生一个随机序列,将此序列作为依次探测的步长。这样就能使不同的关键字具有不同的探测次序,从而可以避 免或减少堆聚。基于与线性探测法相同的理由,在线性补偿探测法和随机探测法中,删除一个记录后也要打上删除标记。
45 
46 2、拉链法
471)拉链法解决冲突的方法
48  拉链法解决冲突的做法是:将所有关键字为同义词的结点链接在同一个单链表中。若选定的散列表长度为m,则可将散列表定义为一个由m个头指针组成的指针数 组T[0..m-1]。凡是散列地址为i的结点,均插入到以T[i]为头指针的单链表中。T中各分量的初值均应为空指针。在拉链法中,装填因子α可以大于 1,但一般均取α≤1492)拉链法的优点
50 与开放定址法相比,拉链法有如下几个优点:
51 ①拉链法处理冲突简单,且无堆积现象,即非同义词决不会发生冲突,因此平均查找长度较短;
52 ②由于拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;
53 ③开放定址法为减少冲突,要求装填因子α较小,故当结点规模较大时会浪费很多空间。而拉链法中可取α≥1,且结点较大时,拉链法中增加的指针域可忽略不计,因此节省空间;
54 ④在用拉链法构造的散列表中,删除结点的操作易于实现。只要简单地删去链表上相应的结点即可。而对开放地址法构造的散列表,删除结点不能简单地将被删结 点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是因为各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在 用开放地址法处理冲突的散列表上执行删除操作,只能在被删结点上做删除标记,而不能真正删除结点。
55 
563)拉链法的缺点
57  拉链法的缺点是:指针需要额外的空间,故当结点规模较小时,开放定址法较为节省空间,而若将节省的指
58 二、散列函数(哈希函数)的选择
591)若函数为h(k)=k,就是直接寻址表
602)除法散列法:h(k) = k mod m (mod 模)
613)乘法散列法:h(k) = m * (k * A mod 1) (0<A<1)
624)全域散列:从一组仔细设计的散列函数中随机地选择一个。(即使对同一个输入,每次也都不一样,平均性态较好)
哈希

  布隆过滤器:http://www.cnblogs.com/haippy/archive/2012/07/13/2590351.html

7、类成员函数的重载和重写的区别

 1 重载overload:是函数名相同,参数列表不同 重载只是在类的内部存在。但是不能靠返回类型来判断。
 2 重写override:也叫做覆盖。子类重新定义父类中有相同名称和参数的虚函数。函数特征相同。但是具体实现不同,主要是在继承关系中出现的 。
 3 重写需要注意:
 4 1 被重写的函数不能是static的。必须是virtual的
 5 2 重写函数必须有相同的类型,名称和参数列表
 6 3 重写函数的访问修饰符可以不同。尽管virtual是private的,派生类中重写改写为public,protected也是可以的
 7 重定义 (redefining)也叫做隐藏:
 8 子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) 。
 9 如果一个类,存在和父类相同的函数,那么,这个类将会覆盖其父类的方法,除非你在调用的时候,强制转换为父类类型,否则试图对子类和父类做类似重载的调用是不能成功的。 
10  综上所述,总结如下:
11 1 成员函数重载特征:
12    a 相同的范围(在同一个类中)
13    b 函数名字相同
14    c 参数不同
15    d virtual关键字可有可无
16 2 重写(覆盖)是指派生类函数覆盖基类函数,特征是:
17    a 不同的范围,分别位于基类和派生类中
18    b 函数的名字相同
19    c 参数相同
20    d 基类函数必须有virtual关键字
21 3 重定义(隐藏)是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
22    a 如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏。
23    b 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有vitual关键字,此时,基类的函数被隐藏。
重载和重写

8、链表和数组区别

 1 7.简述链表和数组的区别
 2 数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少或不插入和删除元素,就应该用数组。
 3 链表恰好相反,链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起。比如:上一个元素有个指针指到下一个元素,以此类推,直到最后一个元素。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表数据结构了。
 4 *C++语言中可以用数组处理一组数据类型相同的数据,但不允许动态定义数组的大小,即在使用数组之前必须确定数组的大小。而在实际应用中,用户使用数组之前有时无法准确确定数组的大小,只能将数组定义成足够大小,这样数组中有些空间可能不被使用,从而造成内存空间的浪费。链表是一种常见的数据组织形式,它采用动态分配内存的形式实现。需要时可以用new分配内存空间,不需要时用delete将已分配的空间释放,不会造成内存空间的浪费。
 5   (1) 从逻辑结构角度来看
 6      a, 数组必须事先定义固定的长度(元素个数),不能适应数据动态地增减的情况。当数据增加时,可能超出原先定义的元素个数;当数据减少时,造成内存浪费。
 7      b,链表动态地进行存储分配,可以适应数据动态地增减的情况,且可以方便地插入、删除数据项。(数组中插入、删除数据项时,需要移动其它数据项)
 8   (2)从内存存储角度来看
 9        a,(静态)数组从栈中分配空间, 对于程序员方便快速,但自由度小。
10         b, 链表从堆中分配空间, 自由度大但申请管理比较麻烦.
11 从上面的比较可以看出,如果需要快速访问数据,很少或不插入和删除元素,就应该用数组;相反,如果需要经常插入和删除元素就需要用链表数据结构了
View Code

9、内存泄漏

  1 1. 什么是内存泄漏(memory leak)?
  2  指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。 
  3 A memory leak is a particular type of unintentional memory consumption by a computer program where the program fails to release memory when no longer needed. This condition is normally the result of a bug in a program that prevents it from freeing up memory that it no longer needs.This term has the potential to be confusing, since memory is not physically lost from the computer. Rather, memory is allocated to a program, and that program subsequently loses the ability to access it due to program logic flaws. 
  4 2. 对于C和C++这种没有Garbage Collection 的语言来讲,我们主要关注两种类型的内存泄漏:
  5 堆内存泄漏(Heap leak)。对内存指的是程序运行中根据需要分配通过malloc,realloc new等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak. 
  6    系统资源泄露(Resource Leak).主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。  
  7 3. 如何解决内存泄露?
  8 内存泄露的问题其困难在于1.编译器不能发现这些问题。2.运行时才能捕获到这些错误,这些错误没有明显的症状,时隐时现。3.对于手机等终端开发用户来说,尤为困难。下面从三个方面来解决内存泄露:
  9 第一,良好的编码习惯,尽量在涉及内存的程序段,检测出内存泄露。当程式稳定之后,在来检测内存泄露时,无疑增加了排除的困难和复杂度。
 10 使用了内存分配的函数,要记得要使用其想用的函数释放掉,一旦使用完毕。
 11 Heap memory:
 12 malloc\realloc ------  free
 13 new \new[] ----------  delete \delete[]
 14 GlobalAlloc------------GlobalFree 
 15 要特别注意数组对象的内存泄漏
 16      MyPointEX *pointArray =new MyPointEX [100];
 17      其删除形式为:
 18     delete []pointArray 
 19 Resource Leak :对于系统资源使用之前要仔细看起使用方法,防止错误使用或者忘记释放掉系统资源。
 20 我们看MSDN上一个创建字体的例子:
 21  RECT rect;
 22 HBRUSH hBrush;
 23 FONT hFont;
 24 hdc = BeginPaint(hWnd, &ps);
 25  hFont = reateFont(48,0,0,0,FW_DONTCARE,FALSE,TRUE,FALSE,DEFAULT_CHARSET,OUT_OUTLINE_PRECIS, CLIP_DEFAULT_PRECIS,CLEARTYPE_QUALITY, VARIABLE_PITCH,TEXT("Impact"));
 26 SelectObject(hdc, hFont); 
 27 SetRect(&rect, 100,100,700,200);
 28 SetTextColor(hdc, RGB(255,0,0));
 29 DrawText(hdc, TEXT("Drawing Text with Impact"), -1,&rect, DT_NOCLIP);    
 30 DeleteObject(hFont);  
 31  EndPaint(hWnd, &ps);
 32  
 33 如果使用完成时候忘记释放字体,就造成了资源泄漏。 
 34   对于基于引用计数的系统对象尤其要注意,因为只有其引用计数为0时,该对象才能正确被删除。而其使用过程中有其生成的新的系统资源,使用完毕后,如果没有及时删除,都会影响其引用计数。
 35  IDNS *m_pDns//define a DNS object.
 36   If(NULL == m_pDns)
 37  38    IEnv_CreateInstance (m_pEnv,AEECLSID_DNS,(void **) (&m_pDns))
 39   }
 40 If(m_pDns)
 41 {
 42     Char szbuff[256];
 43     IDNS_AddQuestions(M_pDns,AEEDNSTYPE_A,ADDDNSCLASS_IN,szbuff);
 44     IDNS_Start(m_pDns,this);
 45    const AEEDNSResponse * pDnsResponse = NULL;
 46    IDNS_GetResponse(pMe->m_pDns, &pDnsResponse);
 47 …………………………………………………………
 48 …………………………………………………………..
 49 ………………………………………………………..
 50 }
 51 DNS_Release(pMe->m_pDns);//当程序运行到此时,其返回值不是0,是1,其含义是程序已经产生内存泄露了,系统已经有一个由DNS所产生的内核对象没有释放,而当这段代码多次执行之后,内存泄露将不断增加……..
 52 m_pDns=NULL;
 53   }
 54 看起来很不直观,仔细分析就会发现,对象pDnsResponse是从m_pDns产生新的object,所以m_pDns的引用计数会增加,因此在使用完pDnsResponse,应该release 该对象使其引用计数恢复正常。
 55  
 56 对于资源,也可使用RAII,RAII(Resource acquisition is initialization)资源获取即初始化,它是一项很简单的技术,利用C++对象生命周期的概念来控制程序的资源,例如内存,文件句柄,网络连接以及审计追踪(audit trail)等.RAII的基本技术原理很简单.若希望保持对某个重要资源的跟踪,那么创建一个对象,并将资源的生命周期和对象的生命周期相关联.如此一来,就可以利用C++复杂老练的对象管理设施来管理资源.(有待完善) 
 57 例2: 
 58 Struct ITypeface *pTypeface;
 59 if (pTypeface)
 60 {
 61 IANY_CreateInstance(g_pApplet->m_pIShell,AEECLSID_BTFETypeface,void**)& Typeface);
 62 } 
 63 接下来我们就可以从这个接口上面创建字体,比如
 64 IHFont **pihf=NULL;
 65    ITypeface_NewFontFromFile(ITypeface,……,&pihf).
 66    ITypeface_NewFontFrommemory(ITypeface,……..,&pihf)
 67   ITypeface_NewFontFromClassID(IType,……,&pihf)
 68  
 69   但是要切记,这些字体在使用完成后一定要release掉,否则最后 iTypeface的引用计数就是你最后没有删除掉的字体的个数。 
 70 第二,重载  newdelete。这也是大家编码过程中常常使用的方法。
 71 下面给出简单的sample来说明。
 72 memchecker.h
 73 structMemIns
 74 {
 75    void * pMem;
 76    int m_nSize;
 77    char m_szFileName[256];
 78    int m_nLine;
 79     MemIns * pNext;
 80 };
 81 classMemManager
 82 {
 83 public:
 84    MemManager();
 85     ~MemManager();
 86 private:
 87     MemIns *m_pMemInsHead;
 88    int m_nTotal;
 89 public:
 90    static MemManager* GetInstance();
 91    void Append(MemIns *pMemIns);
 92    void Remove(void *ptr);
 93    void Dump(); 
 94  
 95 };
 96 void *operatornew(size_tsize,constchar*szFile, int nLine);
 97 void operatordelete(void*ptr,constchar*szFile, int nLine);
 98  void operatordelete(void*ptr);
 99 void*operatornew[] (size_tsize,constchar*szFile,int nLine);
100 void operatordelete[](void*ptr,constchar*szFile, int nLine);
101 void operatordelete[](void *ptr);
102  
103 memechecker.cpp
104 #include"Memchecher.h"
105 #include<stdio.h>
106 #include<malloc.h>
107 #include<string.h>
108  
109 MemManager::MemManager()
110 {
111     m_pMemInsHead=NULL;
112     m_nTotal=NULL;
113 }
114 MemManager::~MemManager()
115 {
116  
117 }
118 voidMemManager::Append(MemIns *pMemIns)
119 {
120    pMemIns->pNext=m_pMemInsHead;
121     m_pMemInsHead = pMemIns;
122    m_nTotal+= m_pMemInsHead->m_nSize;
123  
124 }
125 voidMemManager::Remove(void *ptr)
126 {
127     MemIns * pCur = m_pMemInsHead;
128     MemIns * pPrev = NULL;
129    while(pCur)
130     {
131        if(pCur->pMem ==ptr)
132         {
133            if(pPrev)
134             {
135                pPrev->pNext =pCur->pNext;
136             }
137            else
138             {
139                m_pMemInsHead =pCur->pNext;
140             }
141            m_nTotal-=pCur->m_nSize;
142            free(pCur);
143            break;
144         }
145        pPrev = pCur;
146        pCur = pCur->pNext;
147     }
148  
149 }
150 voidMemManager::Dump()
151 {
152     MemIns * pp = m_pMemInsHead;
153    while(pp)
154     {
155        printf( "File is %s\n", pp->m_szFileName );
156        printf( "Size is %d\n", pp->m_nSize );
157        printf( "Line is %d\n", pp->m_nLine );
158         pp = pp->pNext;
159     }
160  
161 }
162  
163 voidPutEntry(void *ptr,intsize,constchar*szFile, int nLine)
164 {
165     MemIns * p = (MemIns *)(malloc(sizeof(MemIns)));
166    if(p)
167     {
168        strcpy(p->m_szFileName,szFile);
169        p->m_nLine = nLine;
170        p->pMem = ptr;
171        p->m_nSize = size;
172         MemManager::GetInstance()->Append(p);
173     }
174 }
175 voidRemoveEntry(void *ptr)
176 {
177     MemManager::GetInstance()->Remove(ptr);
178 }
179  
180  
181 void *operatornew(size_tsize,constchar*szFile, int nLine)
182 {
183    void * ptr = malloc(size);
184    PutEntry(ptr,size,szFile,nLine);
185    return ptr;
186 }
187 voidoperatordelete(void *ptr)
188 {
189    RemoveEntry(ptr);
190    free(ptr);
191 }
192 void operatordelete(void*ptr,constchar * file, intline)
193 {
194    RemoveEntry(ptr);
195    free(ptr);
196 }
197  
198 void*operatornew[] (size_tsize,constchar* szFile,intnLine)
199 {
200    void * ptr = malloc(size);
201    PutEntry(ptr,size,szFile,nLine);
202    return ptr;
203 }
204  
205 void operatordelete[](void *ptr)
206 {
207    RemoveEntry(ptr);
208    free(ptr);
209 }
210  
211 void operatordelete[](void*ptr,constchar*szFile,intnLine)
212  {
213    RemoveEntry(ptr);
214    free(ptr);
215 }
216 #definenewnew(__FILE__,__LINE__)
217 MemManagerm_memTracer;
218  
219 MemManager*MemManager::GetInstance()
220 {
221    return&m_memTracer;
222 223 void main()
224 {
225    int *plen =newint ;
226     *plen=10;
227    delete plen;
228    char *pstr=newchar[35];
229    strcpy(pstr,"hello memory leak");
230     m_memTracer.Dump();
231    return ;
232 }
233  其主要思路是将分配的内存以链表的形式自行管理,使用完毕之后从链表中删除,程序结束时可检查改链表,其中记录了内存泄露的文件,所在文件的行数以及泄露的大小哦。
234 第三,Boost 中的smart pointer(待完善,结合大家的建议)
235 第四,一些常见的工具插件,详见我的Blog中相关文章。
236 4. 由内存泄露引出内存溢出话题:
237 所谓内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是会产生内存溢出的问题。
238 常见的溢出主要有:
239 内存分配未成功,却使用了它。
240 常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p 是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc 或new 来申请内存,应该用if(p==NULL)或if(p!=NULL)进行防错处理。
241 内存分配虽然成功,但是尚未初始化就引用它。
242 内存分配成功并且已经初始化,但操作越过了内存的边界。
243 例如在使用数组时经常发生下标“多1”或者“少1”的操作。特别是在for 循环语句中,循环次数很容易搞错,导致数组操作越界。
244 使用free 或delete 释放了内存后,没有将指针设置为NULL。导致产生“野指针”。
245 程序中的对象调用关系过于复杂,实在难以搞清楚某个对象究竟是否已经释放了内存,此时应该重新设计数据结构,从根本上解决对象管理的混乱局面。(这点可是深有感受,呵呵)
246 
247 不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
View Code

 10、有了malloc和free 为什么还要new 和delete

1 new的工作和malloc都是申请内存  堆内存以堆块的形式进行管理,一个堆块包括头部和数据部分,对于空闲的堆块有一条或者多条链表进行管理,当new的时候回边里该链表找到适合的堆块一分为二,这个过程就好比malloc的申请内存,他两个最主要的区别是,new的时候会自动调用构造函数,delect的时候自动调用析构函数  
2 the end
View Code

11、this指针

1 this指针:隐含在非静态成员函数中的特殊指针,它是当前正在调用此成员函数的对象的指针。
View Code

12、虚函数  纯虚函数

1 定义一个函数为虚函数,不代表函数为不被实现的函数,定义它为虚函数是为了允许用基类的指针来调用子类的这个函数
2 定义一个函数为纯虚函数,才代表函数没有被实现,定义他是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。
3 有纯虚函数的类是抽象类,不能生成对象,只能派生。他派生的类的纯虚函数没有被改写,那么,它的派生类还是个抽象类。
4     定义纯虚函数就是为了让基类不可实例化化, 
5 因为实例化这样的抽象数据结构本身并没有意义. 
6 或者给出实现也没有意义
7 1.为了安全.因为避免任何需要明确但是因为不小心而导致的未知的结果. 提醒子类去做应做的实现. 
8 2.为了效率,不是程序执行的效率,而是为了编码的效率.
View Code

如果不用virtual  无论是基类对象还是派生类对象调用的都是基类的ShowMember成员函数。

不用virtual  父类指针调用不了子类的函数

析构函数为什么声明为虚函数

1 析构函数的作用与构造函数正好相反,是在对象的生命期结束时,释放系统为对象所分配的空间,即要撤消一个对象。
2 用对象指针来调用一个函数,有以下两种情况:
3 1.如果是虚函数,会调用派生类中的版本。(在有派生类的情况下)
4 2.如果是非虚函数,会调用指针所指类型的实现版本。
5 析构函数也会遵循以上两种情况,因为析构函数也是函数嘛,不要把它看得太特殊。 当对象出了作用域或是我们删除对象指针,析构函数就会被调用。
6 当派生类对象出了作用域,派生类的析构函数会先调用,然后再调用它父类的析构函数, 这样能保证分配给对象的内存得到正确释放。
7 但是,如果我们删除一个指向派生类对象的基类指针,而基类析构函数又是非虚的话, 那么就会先调用基类的析构函数(上面第2种情况),派生类的析构函数得不到调用。
8 基类指针可以指向派生类的对象(多态性),如果删除该指针delete []p;就会调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。如果析构函数不被声明成虚函数,则编译器实施静态绑定,在删除基类指针时,只会调用基类的析构函数而不调用派生类析构函数,这样就会造成派生类对象析构不完全。所以,将析构函数声明为虚函数是十分必要的。
9 (我自己总结的:就是如果不把析构函数声明成虚析构,该类有子类,当程序结束的时候,只会销毁父类的东东,而子类的东东没人管,所以把父类的析构声明成虚的,这样在调用父类析构前调用子类的析构,实现父子子父!)
View Code

 

13、vector和数组的区别  vector是否连续?vector在内存上是怎么存在的

1 数组是栈上分配空间,vector是堆上分配空间,
2 数组是C++语法里的基本数据类型,vector是属于C++标准库中提供的功能。
3 数组在内存中分配连续的内存空间,多次分配释放会有内存碎片。
4 Vector是动态增长的,也是连续的,如果Vector当前容量不足,就会重新分配内存,把原来那部分数据复制到新的内存中,
5 数组申请空间时必须指明数组长度。
6 Vector申请空间不需要指明长度。
View Code

14、atoi实现

 1 #include <iostream>
 2 #include <cstring>
 3 using namespace std;
 4 
 5 const int maxn = 1005;
 6 const int MAX = 0x7fffffff;
 7 
 8 bool is_vilad;
 9 
10 int Atoi(const char * str) {
11     if(str == NULL) {
12         return 0;
13     }
14     int flag = 1;
15     while(*str == ' ') {
16         str ++;
17     }
18     if(*str == '-') {
19         flag = -1;
20         str++;
21     }
22     if(*str == '+') {
23         flag = 1;
24         str++;
25     }
26     long long ans = 0;
27     while(*str) {
28         if(*str >= '0' && *str <= '9') {
29             ans = ans * 10 + (*str - '0');
30             //cout << ans << endl;
31             if((flag && ans > MAX) || (!flag && ans > MAX + 1LL)) {
32                 is_vilad = false;
33                 return 0;
34             }
35         }
36         else {
37             is_vilad = false;
38             return 0;
39         }
40         str ++;
41     } 
42     return ans * flag;
43 }
44 
45 int main() {
46     string str;
47     while(getline(cin, str)) {
48         is_vilad = true;
49         int ans = Atoi(str.c_str());
50         if(!is_vilad) {
51             cout << "error" << endl;
52         }else {
53             cout << ans << endl;
54         }
55     }
56 }
View Code

15、string实现

  1 //16.CString类实现。
  2 #include<iostream>
  3 #include<iomanip>
  4 using namespace std;
  5 
  6 class String{
  7     friend ostream& operator<< (ostream&,String&);//重载<<运算符
  8     friend istream& operator>> (istream&,String&);//重载>>运算符
  9     public:
 10     String(const char* str=NULL);                //赋值构造兼默认构造函数(char)
 11     String(const String &other);                 //赋值构造函数(String)
 12     String& operator=(const String& other);       //operator=
 13     String operator+(const String &other)const;  //operator+
 14     bool operator==(const String&);              //operator==
 15     char& operator[](unsigned int);              //operator[]
 16     size_t size(){return strlen(m_data);};
 17     ~String(void) {delete[] m_data;}
 18     private:
 19     char *m_data; // 用于保存字符串
 20 };
 21 
 22 inline String::String(const char* str)   
 23 {
 24     if(!str)m_data=0;      //声明为inline函数,则该函数在程序中被执行时是语句直接替换,而不是被调用
 25     else {
 26         m_data=new char[strlen(str)+1];
 27         strcpy(m_data,str);
 28     }
 29 }
 30 
 31 inline String::String(const String &other)
 32 {
 33     if(!other.m_data)m_data=0;//在类的成员函数内可以访问同种对象的私有成员(同种类则是友元关系)
 34     else
 35     {
 36         m_data=new char[strlen(other.m_data)+1];
 37         strcpy(m_data,other.m_data);
 38     }
 39 }
 40 
 41 inline String& String::operator=(const String& other)
 42 {
 43     if (this!=&other)
 44     {
 45         delete[] m_data;
 46         if(!other.m_data) m_data=0;
 47         else
 48         {
 49             m_data = new char[strlen(other.m_data)+1];
 50             strcpy(m_data,other.m_data);
 51         }
 52     }
 53     return *this;
 54 }
 55 inline String String::operator+(const String &other)const
 56 {
 57     String newString;
 58     if(!other.m_data)
 59         newString = *this;
 60     else if(!m_data)
 61         newString = other;
 62     else
 63     {
 64         newString.m_data = new char[strlen(m_data)+strlen(other.m_data)+1];
 65         strcpy(newString.m_data,m_data);
 66         strcat(newString.m_data,other.m_data);
 67     }
 68     return newString;
 69 }
 70 
 71 inline bool String::operator==(const String &s)    
 72 {
 73     if ( strlen(s.m_data) != strlen(m_data) )
 74         return false;
 75     return strcmp(m_data,s.m_data)?false:true;
 76 }
 77 
 78 inline char& String::operator[](unsigned int e)
 79 {
 80     if (e>=0&&e<=strlen(m_data))
 81         return m_data[e];
 82 }
 83 
 84 ostream& operator<<(ostream& os,String& str)
 85 {
 86     os<< str.m_data;
 87     return os;
 88 }
 89 
 90 istream&operator>>( istream &input, String &s )
 91 {
 92     char temp[ 255 ]; //用于存储输入流
 93     input>>setw(255)>>temp;
 94     s = temp; //使用赋值运算符
 95     return input; //使用return可以支持连续使用>>运算符
 96 }
 97 
 98 int main()
 99 {
100     String str1="Aha!";
101     String str2="My friend";
102     String str3 = str1+str2;
103     cout<<str3<<"/n"<<str3.size()<<endl;
104     return 0;
105 }
String

16、malloc  free 内部实现

编译器在你malloc或free的时候不是直接去跟内核要  而是预先申请了一部分内存  把它管理起来  

申请的内存以堆块的形式进行组织成链表

程序中free之后,但是堆的内存还是没有释放

 1 17.Malloc和free内部实现
 2 内存分配是按照堆块实现的,一个堆块是由头部和有效载荷量组成,其中的有效载荷量就是我们申请的堆的大小。头部块包括块大小和是否可用这两个部分组成。在内存中这些堆块以链表形势组成
 3 malloc函数的实质体现在,它有一个将可用的内存块连接为一个长长的列表的所谓空闲链表。调用malloc函数时,它沿连接表寻找一个大到足以满足用户请求所需要的内存块。然后,将该内存块一分为二(一块的大小与用户请求的大小相等,另一块的大小就是剩下的字节)。接下来,将分配给用户的那块内存传给用户,并将剩下的那块(如果有的话)返回到连接表上。调用free函数时,它将用户释放的内存块连接到空闲链上。到最后,空闲链会被切成很多的小内存片段,如果这时用户申请一个大的内存片段,那么空闲链上可能没有可以满足用户要求的片段了。于是,malloc函数请求延时,并开始在空闲链上翻箱倒柜地检查各内存片段,对它们进行整理,将相邻的小空闲块合并成较大的内存块。 
 4 glibc是GNU发布的libc库,即c运行库。glibc是linux系统中最底层的api,几乎其它任何运行库都会依赖于glibc。
 5 glibc维护了不止一个不定长的内存块链表,而是好几个,每一个这种链表负责一个大小范围,这种做法有效减少了分配大内存时的遍历开销,类似于哈希的方式,将很大的范围的数据散列到有限的几个小的范围内而不是所有数据都放在一起,虽然最终还是要在小的范围内查找,但是最起码省去了很多的开销,如果只有一个不定长链表那么就要全部遍历,如果分成3个,就省去了2/3的开销,总之这个策略十分类似于散列。glibc另外的策略就是不止维护一类空闲链表,而是另外再维护一个缓冲链表和一个高速缓冲链表,在分配的时候首先在高速缓存中查找,失败之后再在空闲链表查找,如果找到的内存块比较大,那么将切割之后的剩余内存块插入到缓存链表,如果空闲链表查找失败那么就往缓存链表中查找. 如果还是没有合适的空闲块,就向内存申请比请求数更大的内存块,然后把剩下的内存放入链表中。
 6 
 7 在对内存块进行了 free 调用之后,我们需要做的是诸如将它们标记为未被使用的等事情,并且,在调用 malloc 时,我们要能够定位未被使用的内存块。因此, malloc返回的每块内存的起始处首先要有这个结构:
 8 
 9 这就解释了,为什么在程序中free之后,但是堆的内存还是没有释放。
10 
11 //清单 3. 内存控制块结构定义
12 struct mem_control_block {
13     int is_available;
14     int size;
15 };
16 现在,您可能会认为当程序调用 malloc 时这会引发问题 —— 它们如何知道这个结构?答案是它们不必知道;在返回指针之前,我们会将其移动到这个结构之后,把它隐藏起来。这使得返回的指针指向没有用于任何其他用途的内存。那样,从调用程序的角度来看,它们所得到的全部是空闲的、开放的内存。然后,当通过 free() 将该指针传递回来时,我们只需要倒退几个内存字节就可以再次找到这个结构。
17   在讨论分配内存之前,我们将先讨论释放,因为它更简单。为了释放内存,我们必须要做的惟一一件事情就是,获得我们给出的指针,回退 sizeof(struct mem_control_block) 个字节,并将其标记为可用的。这里是对应的代码:
18 
19 
20 清单 4. 解除分配函数
21 void free(void *firstbyte) {
22     struct mem_control_block *mcb;
23 /* Backup from the given pointer to find the
24  * mem_control_block
25  */
26    mcb = firstbyte - sizeof(struct mem_control_block);
27 /* Mark the block as being available */
28   mcb->is_available = 1;
29 /* That''s It!  We''re done. */
30   return;
31 }
32 如您所见,在这个分配程序中,内存的释放使用了一个非常简单的机制,在固定时间内完成内存释放。
View Code

17、new 的实现  

operator new  构造函数

operator重载可以有效防止内存泄漏  可以优化内存池

  1 18.为什么用new时会调用构造,如何实现
  2 一 new运算符和operator new():
  3      new:指我们在C++里通常用到的运算符,比如A* a = new A;  对于new来说,有new和::new之分,前者位于std
  4    operator new():指对new的重载形式,它是一个函数,并不是运算符。对于operator new来说,分为全局重载和类重载,全局重载是void* ::operator new(size_t size),在类中重载形式 void* A::operator new(size_t size)。还要注意的是这里的operator new()完成的操作一般只是分配内存,事实上系统默认的全局::operator new(size_t size)也只是调用malloc分配内存,并且返回一个void*指针。而构造函数的调用(如果需要)是在new运算符中完成的。
  5  先简单解释一下new和operator new之间的关系:
  6      关于这两者的关系,我找到一段比较经典的描述(来自于www.cplusplus.com 见参考文献):
  7 operator new can be called explicitly as a regular function, but in C++, new is an operator with a very specific
  8 behavior: An expression with the new operator, first calls function operator new (i.e., this function) with the size of its type specifier as first argument, and if this is successful, it then automatically initializes or constructs
  9 the object (if needed). Finally, the expression evaluates as a pointer to the appropriate type.
 10      比如我们写如下代码:
 11      A* a = new A;
 12      我们知道这里分为两步:1.分配内存,2.调用A()构造对象。事实上,分配内存这一操作就是由operator new(size_t)来完成的,如果类A重载了operator new,那么将调用A::operator new(size_t ),如果没有重载,就调用::operator new(size_t ),全局new操作符由C++默认提供。因此前面的两步也就是:1.调用operator new 2.调用构造函数。这里再一次提出来是因为后面关于这两步会有一些变形,在关于placement new那里会讲到。先举个简单例子
 13 //平台:Visual Stdio 2008
 14 #include<iostream>
 15 class A
 16 {
 17 public:
 18 A()
 19      {
 20           std::cout<<"call A constructor"<<std::endl;
 21      }
 22 
 23      ~A()
 24      {
 25           std::cout<<"call A destructor"<<std::endl;
 26      }
 27 }
 28 int _tmain(int argc, _TCHAR* argv[])
 29 {
 30 
 31      A* a = new A;
 32 delete a;
 33 
 34 system("pause");
 35 return 0;
 36 }
 37 下面我们跟踪一下A反汇编代码,由于Debug版本反汇编跳转太多,因此此处通过Release版本在A* a = new A;处设断点反汇编:
 38 在Release版本中,构造函数和析构函数都是直接展开的。
 39     A* a = new A;
 40 01301022  push        1    ;不含数据成员的类占用一字节空间,此处压入sizeof(A)
 41 01301024  call        operator new (13013C2h) ;调用operator new(size_t size)
 42 01301029  mov         esi,eax ;返回值保存到esi
 43 0130102B  add         esp,4 ;平衡栈
 44 0130102E  mov         dword ptr [esp+8],esi ;
 45 01301032  mov         dword ptr [esp+14h],0 
 46 0130103A  test        esi,esi ;在operator new之后,检查其返回值,如果为空(分配失败),则不调用A()构造函数
 47 0130103C  je          wmain+62h (1301062h) ;为空 跳过构造函数部分
 48 0130103E  mov         eax,dword ptr [__imp_std::endl (1302038h)] ;构造函数内部,输出字符串
 49 01301043  mov         ecx,dword ptr [__imp_std::cout (1302050h)] 
 50 01301049  push        eax  
 51 0130104A  push        offset string "call A constructor" (1302134h) 
 52 0130104F  push        ecx  
 53 01301050  call        std::operator<<<std::char_traits<char>> (13011F0h) 
 54 01301055  add         esp,8 
 55 01301058  mov         ecx,eax 
 56 0130105A  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char>>::operator<< (1302040h)] 
 57 01301060  jmp         wmain+64h (1301064h) ;构造完成,跳过下一句
 58 01301062  xor         esi,esi ;将esi置空,这里的esi即为new A的返回值
 59 01301064  mov         dword ptr [esp+14h],0FFFFFFFFh 
 60     delete a;
 61 0130106C  test        esi,esi ;检查a是否为空
 62 0130106E  je          wmain+9Bh (130109Bh) ;如果为空,跳过析构函数和operator delete
 63 01301070  mov         edx,dword ptr [__imp_std::endl (1302038h)] ;析构函数 输出字符串
 64 01301076  mov         eax,dword ptr [__imp_std::cout (1302050h)] 
 65 0130107B  push        edx  
 66 0130107C  push        offset string "call A destructor" (1302148h) 
 67 01301081  push        eax  
 68 01301082  call        std::operator<<<std::char_traits<char>> (13011F0h) 
 69 01301087  add         esp,8 
 70 0130108A  mov         ecx,eax 
 71 0130108C  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char>>::operator<< (1302040h)] 
 72 01301092  push        esi  ;压入a 
 73 01301093  call        operator delete (13013BCh) ;调用operator delete 
 74 01301098  add         esp,4 
 75 通过反汇编可以看出A* = new A包含了operator new(sizeof(A))和A()两个步骤(当然,最后还要将值返回到a)
 76          delete a包含了~A()和operator delete(a)两个步骤。
 77operator new的三种形式:
 78 operator new有三种形式:
 79 throwing (1)    void* operator new (std::size_t size) throw (std::bad_alloc);
 80 nothrow (2)    void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw();
 81 placement (3)    void* operator new (std::size_t size, void* ptr) throw();
 82 (1)(2)的区别仅是是否抛出异常,当分配失败时,前者会抛出bad_alloc异常,后者返回null,不会抛出异常。它们都分配一个固定大小的连续内存。
 83 用法示例:
 84 A* a = new A; //调用throwing(1)
 85 A* a = new(std::nothrow) A; //调用nothrow(2)
 863)是placement new,它也是对operator new的一个重载,定义于<new>中,它多接收一个ptr参数,但它只是简单地返回ptr。其在new.h下的源代码如下:
 87 #ifndef __PLACEMENT_NEW_INLINE
 88 #define __PLACEMENT_NEW_INLINE
 89 inline void *__cdecl operator new(size_t, void *_P)
 90         {return (_P); }
 91 #if     _MSC_VER >= 1200
 92 inline void __cdecl operator delete(void *, void *)
 93     {return; }
 94 #endif
 95 #endif
 96 那么它究竟有什么用呢?事实上,它可以实现在ptr所指地址上构建一个对象(通过调用其构造函数),这在内存池技术上有广泛应用。
 97 它的调用形式为:
 98 new(p) A(); //也可用A(5)等有参构造函数。
 99 前面说到,new运算符都会调用operator new,而这里的operator new(size_t, void*)并没有什么作用,真正起作用的是new运算符的第二个步骤:在p处调用A构造函数。这里的p可以是动态分配的内存,也可以是栈中缓冲,如char buf[100]; new(buf) A();
100 
101 我们仍然可以通过一个例子来验证:
102 #include <iostream>
103 class A
104 {
105 public:
106     A()
107     {
108         std::cout<<"call A constructor"<<std::endl;
109     }
110 
111     ~A()
112     {
113         std::cout<<"call A destructor"<<std::endl;
114     }
115 };
116 int _tmain(int argc, _TCHAR* argv[])
117 {
118 
119     A* p = (A*)::operator new(sizeof(A)); //分配
120 
121     new(p) A();    //构造
122     
123     p->~A();    //析构
124 
125     ::operator delete(p); //释放
126 
127     system("pause");
128     return 0;
129 }
130 
131 上面的代码将对象的分配,构造,析构和释放分离开来,这也是new和delete运算符两句就能完成的操作。
132 先直接运行可以看到程序输出:
133 
134 再分别注释掉new(a) A();和a->~A();两句,可以看到对应的构造和析构函数将不会被调用。
135 然后查看反汇编:
136 平台: Visual Studio 2008 Debug版
137     A* a = (A*)::operator new(sizeof(A)); //分配
138 00F9151D  push        1    
139 00F9151F  call        operator new (0F91208h) ;调用::operator new(size_t size)也就是throwing(1)版本
140 00F91524  add         esp,4 
141 00F91527  mov         dword ptr [ebp-14h],eax ;返回地址放入[ebp-14h] 即为p
142 
143     new(a) A();    //构造
144 00F9152A  mov         eax,dword ptr [ebp-14h] 
145 00F9152D  push        eax  
146 00F9152E  push        1    ;压入p
147 00F91530  call        operator new (0F91280h);调用operator new(size_t, void* p)即placement(3)版本 只是简单返回p
148 00F91535  add         esp,8 
149 00F91538  mov         dword ptr [ebp-0E0h],eax ;将p放入[ebp-0E0h]
150 00F9153E  mov         dword ptr [ebp-4],0 
151 00F91545  cmp         dword ptr [ebp-0E0h],0   ;判断p是否为空
152 00F9154C  je          wmain+81h (0F91561h)     ;如果为空 跳过构造函数
153 00F9154E  mov         ecx,dword ptr [ebp-0E0h] ;取出p到ecx
154 00F91554  call        A::A (0F91285h)            ;调用构造函数 根据_thiscall调用约定 this指针通过ecx寄存器传递
155 00F91559  mov         dword ptr [ebp-0F4h],eax ;将返回值(this指针)放入[ebp-0F4h]中
156 00F9155F  jmp         wmain+8Bh (0F9156Bh)     ;跳过下一句
157 00F91561  mov         dword ptr [ebp-0F4h],0   ;将[ebp-0F4h]置空 当前面判断p为空时执行此语句
158 00F9156B  mov         ecx,dword ptr [ebp-0F4h] ;[ebp-0F4h]为最终构造完成后的this指针(或者为空) 放入ecx
159 00F91571  mov         dword ptr [ebp-0ECh],ecx ;又将this放入[ebp-0ECh] 这些都是调试所用
160 00F91577  mov         dword ptr [ebp-4],0FFFFFFFFh 
161     
162     a->~A();    //析构
163 00F9157E  push        0    
164 00F91580  mov         ecx,dword ptr [ebp-14h] ;从[ebp-14h]中取出p
165 00F91583  call        A::`scalar deleting destructor' (0F91041h) ;调用析构函数(跟踪进去比较复杂 如果在Release下,构造析构函数都是直接展开的)
166 
167     ::operator delete(a); //释放
168 00F91588  mov         eax,dword ptr [ebp-14h]   ;将p放入eax
169 00F9158B  push        eax              ;压入p
170 00F9158C  call        operator delete (0F910B9h);调用operator delete(void* )
171 00F91591  add         esp,4 </span>
172 从反汇编中可以看出,其实operator new调用了两次,只不过每一次调用不同的重载函数,并且placement new的主要作用只是将p放入ecx,并且调用其构造函数。
173 事实上,在指定地址上构造对象还有另一种方法,即手动调用构造函数:p->A::A(); 这里要加上A::作用域,否则编译器会报错:
174 error C2273: “函数样式转换”: 位于“->”运算符右边时非法
175 用p->A::A();替换掉new(p) A();仍然能达到同样的效果,反汇编:
176     A* a = (A*)::operator new(sizeof(A)); //分配
177 010614FE  push        1    
178 01061500  call        operator new (1061208h) 
179 01061505  add         esp,4 
180 01061508  mov         dword ptr [a],eax 
181 
182     //new(a) A();    //构造
183     a->A::A();
184 0106150B  mov         ecx,dword ptr [a] 
185 0106150E  call        operator new (1061285h) 
186 
187     a->~A();    //析构
188 01061513  push        0    
189 01061515  mov         ecx,dword ptr [a] 
190 01061518  call        A::`scalar deleting destructor' (1061041h) 
191 
192     ::operator delete(a); //释放
193 0106151D  mov         eax,dword ptr [a] 
194 01061520  push        eax  
195 01061521  call        operator delete (10610B9h) 
196 01061526  add         esp,4
197 比之前的方法更加简洁高效(不需要调用placement new)。不知道手动调用构造函数是否有违C++标准或有什么隐晦,我在其他很多有名的内存池(包括SGI STL alloc)实现上看到都是用的placement new,而不是手动调用构造函数。
198 
199operator new重载:
200    前面简单提到过 A* p = new A;所发生的事情:先调用operator new,如果类A重载了operator new,那么就使用该重载版本,否则使用全局版本::operatro new(size_t size)。那么类中可以重载operator new的哪些版本?全局operator new可以重载吗?全局和类中重载分别会在什么时机调用?
201     1.在类中重载operator new
202 上面提到的throwing(1)和nothrow(2)的operator new是可以被重载的,比如:
203 #include <iostream>
204 class A
205 {
206 public:
207     A()
208     {
209         std::cout<<"call A constructor"<<std::endl;
210     }
211 
212     ~A()
213     {
214         std::cout<<"call A destructor"<<std::endl;
215     }
216     void* operator new(size_t size)
217     {
218         std::cout<<"call A::operator new"<<std::endl;
219         return malloc(size);
220     }
221 
222     void* operator new(size_t size, const std::nothrow_t& nothrow_value)
223     {
224         std::cout<<"call A::operator new nothrow"<<std::endl;
225         return malloc(size);
226     }
227 };
228 int _tmain(int argc, _TCHAR* argv[])
229 {
230     A* p1 = new A;
231     delete p1;
232 
233     A* p2 = new(std::nothrow) A;
234     delete p2;
235 
236     system("pause");
237     return 0;
238 }
239 
240 
241 如果类A中没有对operator new的重载,那么new A和new(std::nothrow) A;都将会使用全局operator new(size_t size)。可将A中两个operator new注释掉,并且在A外添加一个全局operator new重载:
242 void* ::operator new(size_t size)
243 {
244     std::cout<<"call global operator new"<<std::endl;
245     return malloc(size);
246 }
247 程序输出:
248 
249 注意,这里的重载遵循作用域覆盖原则,即在里向外寻找operator new的重载时,只要找到operator new()函数就不再向外查找,如果参数符合则通过,如果参数不符合则报错,而不管全局是否还有相匹配的函数原型。比如如果这里只将A中operator new(size_t, const std::nothrow_t&)删除掉,就会报错:
250 error C2660: “A::operator new”: 函数不接受 2 个参数。
251 至于placement new,它本身就是operator new的一个重载,不需也尽量不要对它进行改写,因为它一般是搭配 new(p) A(); 工作的,它的职责只需简单返回指针。
252 对operator new的重载还可以添加自定义参数,如在类A中添加
253 void* operator new(size_t size, int x, int y, int z)
254 {
255     std::cout<<"X="<<x<<"  Y="<<y<<" Z="<<z<<std::endl;
256     return malloc(size);
257 }
258 这种重载看起来没有什么大作用,因为它operator new需要完成的任务只是分配内存,但是通过对这类重载的巧妙应用,可以让它在动态分配内存调试和检测中大展身手。这将在后面operator new重载运用技巧中,展现。
259 2.重载全局operator new
260     全局operator new的重载和在类中重载并无太大区别,当new A;时,如果类A中没有重载operator new,那么将调用全局operator new函数,如果没有重载全局operator new,最后会调用默认的全局operator new261     
262 3.类中operator new和全局operator new的调用时机
263     前面已经提到了在new时的调用顺序,但是这里提出来的原因是还存在一个全局的new运算符,也就是::new,这个运算符会直接调用全局operator new,并且也会调用构造函数。这可能让人很犯迷糊,只做了解即可。这里提到的调用时机都是指通过new运算符调用,没有讨论其他情况,比如主动调用。
264operator new运用技巧和一些实例探索
265     1.operator new重载运用于调试:
266     前面提到如何operator new的重载是可以有自定义参数的,那么我们如何利用自定义参数获取更多的信息呢,这里一个很有用的做法就是给operator new添加两个参数:char* file, int line,这两个参数记录new运算符的位置,然后再在new时将文件名和行号传入,这样我们就能在分配内存失败时给出提示:输出文件名和行号。
267     那么如何获取当前语句所在文件名和行号呢,windows提供两个宏:__FILE__和__LINE__。利用它们可以直接获取到文件名和行号,也就是 new(__FILE__, __LINE__) 由于这些都是不变的,因此可以再定义一个宏:#define new new(__FILE__, __LINE__)。这样我们就只需要定义这个宏,然后重载operator new即可。
268      源代码如下,这里只是简单输出new的文件名和行号。
269 //A.h
270 class A
271 {
272 public:
273     A()
274     {
275         std::cout<<"call A constructor"<<std::endl;
276     }
277 
278     ~A()
279     {
280         std::cout<<"call A destructor"<<std::endl;
281     }
282 
283     void* operator new(size_t size, const char* file, int line)
284     {
285         std::cout<<"call A::operator new on file:"<<file<<"  line:"<<line<<std::endl;
286         return malloc(size);
287         return NULL;
288     }
289 
290 };
291 //Test.cpp
292 #include <iostream>
293 #include "A.h"
294 #define new new(__FILE__, __LINE__)
295 
296 int _tmain(int argc, _TCHAR* argv[])
297 {
298     A* p1 = new A;
299     delete p1;
300 
301     A* p2 = new A;
302     delete p2;
303 
304     system("pause");
305     return 0;
306 }
307 输出:
308 
309 
310 注意:需要将类的声明实现与new的使用隔离开来。并且将类头文件放在宏定义之前。否则在类A中的operator new重载中的new会被宏替换,整个函数就变成了: void* operator new(__FILE__, __LINE__)(size_t size, char* file, int line)
311 编译器自然会报错。
312     2.内存池优化
313     operator new的另一个大用处就是内存池优化,内存池的一个常见策略就是分配一次性分配一块大的内存作为内存池(buffer或pool),然后重复利用该内存块,每次分配都从内存池中取出,释放则将内存块放回内存池。在我们客户端调用的是new运算符,我们可以改写operator new函数,让它从内存池中取出(当内存池不够时,再从系统堆中一次性分配一块大的),至于构造和析构则在取出的内存上进行,然后再重载operator delete,它将内存块放回内存池。关于内存池和operator new在参考文献中有一篇很好的文章。这里就不累述了。
314     3.STL中的new
315     在SGI STL源码中,defalloc.h和stl_construct.h中提供了最简单的空间配置器(allocator)封装,见《STL源码剖析》P48。它将对象的空间分配和构造分离开来,虽然在defalloc.h中仅仅是对::operator new和::operator delete的一层封装,但是它仍然给STL容器提供了更加灵活的接口。SGI STL真正使用的并不是defalloc.h中的分配器,而是stl_alloc.h中的SGI精心打造的"双层级配置器",它将内存池技术演绎得淋漓尽致,值得细细琢磨。顺便提一下,在stl_alloc.h中并没有使用::operator new/delete 而直接使用malloc和free。具体缘由均可参见《STL源码剖析》。
316 
317 五 delete的使用
318    delete的使用基本和new一致,包括operator delete的重载方式这些都相似,只不过它的参数是void*,返回值为void。但是有一点需要注意,operator delete的自定义参数重载并不能手动调用。比如
319 void* operator new(size_t size, int x)
320 {
321 cout<<" x = "<<x<<endl;
322 return malloc(size);    
323 }
324 void operator delete(void* p, int x)
325 {
326 cout<<" x = "<<x<<endl;
327 free(p);
328 }
329 如下调用是无法通过的:
330 A* p = new(3) A;//Ok
331 delete(3) p;//error C2541: “delete”: 不能删除不是指针的对象
332 那么重载operator delete有什么作用?如何调用?事实上以上自定义参数operator delete 只在一种情况下被调用:当new运算符抛出异常时。
333 可以这样理解,只有在new运算符中,编译器才知道你调用的operator new形式,然后它会调用对应的operator delete。一旦出了new运算符,编译器对于你自定义的new将一无所知,因此它只会按照你指定的delete运算符形式来调用operator delete,而至于为什么不能指定调用自定义delete(也就是只能老老实实delete p),这个就不知道了。
334 细心观察的话,上面operator new用于调试的例子代码中,由于我们没有给出operator new对应的operator delete。在VS2008下会有如下警告:
335 warning C4291: “void *A::operator new(size_t,const char *,int)”: 未找到匹配的删除运算符;如果初始化引发异常,则不会释放内存
336 
337 六 关于new和内存分配的其他
338    1.set_new_handler 
339    还有一些零散的东西没有介绍到,比如set_new_handler可以在malloc(需要调用set_new_mode(1))或operator new内存分配失败时指定一个入口函数new_handler,这个函数完成自定义处理(继续尝试分配,抛出异常,或终止程序),如果new_handler返回,那么系统将继续尝试分配内存,如果失败,将继续重复调用它,直到内存分配完毕或new_handler不再返回(抛出异常,终止)。下面这段程序完成这个测试:
340 #include <iostream>
341 #include <new.h>// 使用_set_new_mode和set_new_handler
342 void nomem_handler()
343 {
344     std::cout<<"call nomem_handler"<<std::endl;
345 }
346 int main()
347 {
348     _set_new_mode(1);  //使new_handler有效
349     set_new_handler(nomem_handler);//指定入口函数 函数原型void f();
350     std::cout<<"try to alloc 2GB memory...."<<std::endl;
351     char* a = (char*)malloc(2*1024*1024*1024);
352     if(a)
353         std::cout<<"ok...I got it"<<std::endl;
354     free(a);
355     system("pause");
356 }
357 程序运行后会一直输出call nomem_handler 因为函数里面只是简单输出,返回,系统尝试分配失败后,调用nomem_handler函数,由于该函数并没有起到实际作用(让可分配内存增大),因此返回后系统再次尝试分配失败,再调用nomem_handler,循环下去。
358 在SGI STL中的也有个仿new_handler函数:oom_malloc
359 
360     2.new分配数组
361     A* p = new A[3];中,会直接调用全局的operator new[](size_t size),而不管A中是否有operator new[]的重载。而delete[]p却会优先调用A::operator delete[](void*)(如果A中有重载)。另外还要注意的是,在operator new[](size_t size)中传入的并不是sizeof(A)*3。而要在对象数组的大小上加上一个额外数据,用于编译器区分对象数组指针和对象指针以及对象数组大小。在VS2008下这个额外数据占4个字节,一个int大小。测试代码如下
362 //A.h
363 class A
364 {
365 public:
366     A()
367     {
368         std::cout<<"call A constructor"<<std::endl;
369     }
370 
371     ~A()
372     {
373         std::cout<<"call A destructor"<<std::endl;
374     }
375 
376     void* operator new(size_t size)
377     {
378         std::cout<<"call A::operator new[] size:"<<size<<std::endl;
379         return malloc(size);
380     }
381     void operator delete[](void* p)
382     {
383         std::cout<<"call A::operator delete[]"<<std::endl;
384         free(p);
385     } 
386     void operator delete(void* p)
387     {
388         free(p);
389     } 
390 };
391 输出:
392 
393 
394 简单跟踪了一下:
395 operator new[]返回的是0x005b668 而最后new运算符返回给p的是0x005b66c。也就是说p就是数组的起始地址,这样程序看到的内存就是线性的,不包括前面的额外数据。
396 
397 在内存中,可以看到前面的四个字节额外数据是0x00000003 也就是3,代表数组元素个数。后面三个cd是堆在Debug中的默认值(中文的cdcd就是"",栈的初始值为cc,0xcccc中文"")。再后面的0xfdfdfdfd应该是堆块的结束标志,前面我有博客专门跟踪过。
398 
399 
400 注:其实在malloc源码中也有内存池的运用,而且也比较复杂。最近在参考dlmalloc版本和STL空间适配器,真没有想到一个内存分配能涉及这么多的东西。
View Code

 18、设计模式

 1 工厂模式:定义一个用于创建对象的接口,让子类决定实例化哪一个类
 2 当一个类不知道它所必须创建的对象的类的时候。
 3 当一个类希望由它的子类来指定它所创建的对象的时候。
 4 抽象工厂:
 5 提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。 
 6 一个系统要独立于它的产品的创建、组合和表示时。
 7 一个系统要由多个产品系列中的一个来配置时。
 8 当你要强调一系列相关的产品对象的设计以便进行联合使用时。
 9 当你提供一个产品类库,而只想显示它们的接口而不是实现时
10 
11 
12 单例模式:
13 public class Singleton{
14 private static Singleton instance = new Singleton();//定义实例变量
15             private Singleton(){};                        //私有化构造方法
16             public static Singleton getInstance(){
17                 return instance;
18             }
19 }
20 在单例类被加载时候,就实例化一个对象交给自己的引用
21 
22 在内存中只有一个对象,节省内存空间。
23 避免频繁的创建销毁对象,可以提高性能。
24 避免对共享资源的多重占用。
25 可以全局访问。
26 
27 需要频繁实例化然后销毁的对象。
28 创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
29 有状态的工具类对象。
30 频繁访问数据库或文件的对象。
31 以及其他我没用过的所有要求只有一个对象的场景。
32 
33 比如读取一个配置文件  只需要读一次就可以了
View Code

 19、typedef和#define的区别

typedef常用于定一个别名 在编译的过程中   #define常用于常亮  在预处理过程之中

typedef : 定义别名  和struct使用  

缺陷 :

typedef char * TYPE

const TYPE   不是const char *     而是 char * const

typedef  static int  INT 不可行

 

20、指针和引用的区别

1 1.sizeof
2 2.++
3 3.变
4 4空
5 5.实体别名
6 6.const
View Code

引用实现多态

A& a=b;

不存在引用的数组  因为引用不占内存

存在数组的引用

 21、strcpy  memcpy memset 

 1 memset 按字节赋值
 2 #include <iostream>
 3 using namespace std;
 4 
 5 const int maxn = 1024;
 6 
 7 char * Strcpy(char *dst,const char *src)   //[1]
 8 {
 9     assert(dst != NULL && src != NULL);
10     char *ret = dst;  //[3]
11     while ((*dst++=*src++)!='\0'); //[4]
12     *dst++='\0';
13     return ret;
14 }
15 void Memcpy(char *a, const char *b, int cnt) {
16     if(!b) return;
17     if(cnt <= 0) return ;
18     for(int i = 0; i < cnt; i++) {
19         *a++=*b++;
20     }
21     *a++='\0';
22 }
23 
24 int main() {
25     char a[maxn]; char b[maxn];
26     while(cin >> b) {
27         cout << Strcpy(a, b) << endl;
28     }
29 
30 }
View Code

22、const

const char *p 本身可改变  指向内容不可变    

char * const p 本身不可变  指向内容可变

const int & a   不可通过a 改变 内容

int const & a   不可通过a改变内容

 1 1. const修饰函数的参数
 2 如果参数作输出用,不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加const 修饰,否则该参数将失去输出功能。
 3 const 只能修饰输入参数:
 4 如果输入参数采用“指针传递”,那么加const 修饰可以防止意外地改动该指针,起到保护作用。
 5 将“const &”修饰输入参数的用法总结如下:
 6 (1)对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const 引用传递”,目的是提高效率。例如将void Func(A a) 改为void Func(const A &a)。
 7 (2)对于内部数据类型的输入参数,不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x) 不应该改为void Func(const int &x)。
 8 2. const 修饰函数的返回值
 9 如果给以“指针传递”方式的函数返回值加const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。例如函数
10 const char * GetString(void);
11 如下语句将出现编译错误:
12 char *str = GetString();
13 正确的用法是
14 const char *str = GetString();
15 
16 如果返回值不是内部数据类型,将函数A GetA(void) 改写为const A & GetA(void)的确能提高效率。但此时千万千万要小心,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错。
17 函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。
18 例如:
19 class A
20 {
21 A & operate = (const A &other); // 赋值函数
22 };
23 A a, b, c; // a, b, c 为A 的对象
24 a = b = c; // 正常的链式赋值
25 (a = b) = c; // 不正常的链式赋值,但合法
26 如果将赋值函数的返回值加const 修饰,那么该返回值的内容不允许被改动。上例中,语句 a = b = c 仍然正确,但是语句 (a = b) = c 则是非法的。
27 3. const修饰成员函数
28 关于Const函数的几点规则:
29 a. const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数.
30 b. const对象的成员是不可修改的,然而const对象通过指针维护的对象却是可以修改的.
31 c. const成员函数不可以修改对象的数据,不管对象是否具有const性质.它在编译时,以是否修改成员数据为依据,进行检查.
32 e. 然而加上mutable修饰符的数据成员,对于任何情况下通过任何手段都可修改,自然此时的const成员函数是可以修改它的
View Code

 23、extern作用  声明外部变量  extern Flag    extern “C”   extern function()

  1 extern 作用1:声明外部变量
  2 现代编译器一般采用按文件编译的方式,因此在编译时,各个文件中定义的全局变量是
  3 互相透明的,也就是说,在编译时,全局变量的可见域限制在文件内部。
  4 
  5 例1:
  6 创建一个工程,里面含有A.cpp和B.cpp两个简单的C++源文件:
  7 //A.cpp:
  8 int iRI;
  9 int main()
 10 {
 11 //.....
 12 }
 13 
 14 //B.cpp
 15 int iRI;
 16 
 17 gcc A.cpp -c
 18 gcc B.cpp -c
 19 编译出A.o, B.o都没有问题。
 20 但当gcc A.o B.o -o test时,
 21 main.o:(.bss+0x0): multiple definition of `iRI'
 22 b.o:(.bss+0x0): first defined here
 23 报错:重定义。
 24 (但有个非常意外的发现:当同样的代码,使用A.c B.c.并使用gcc编译时,竟然不会报重定义的错误,非常不明白是怎么回事。)
 25 这就是说,在编译阶段,各个文件中定义的全局变量相互是透明的,编译A时觉察不到B中也定义了i,同样,编译B时觉察不到A中也定义了i。
 26 但是到了链接阶段,要将各个文件的内容“合为一体”,因此,如果某些文件中定义的全局变量名相同的话,在这个时候就会出现错误,也就是上面提示的重复定义的错误。因此,各个文件中定义的全局变量名不可相同。
 27 
 28 但如果用下列方式:在B.cpp中定义iRI;在A.cpp中直接使用。则编译A.cpp时就无法通过。
 29 //A.cpp
 30 int main()
 31 {
 32 iRI=64;
 33 }
 34 
 35 //B.cpp
 36 int iRI;
 37 
 38 gcc A.cpp -c
 39 was not declared in this scope.
 40 
 41 因为编译器按照文件方式编译,所以编译A.cpp时,并不知道B.cpp中定义了iRI。
 42 也就是说:文件中定义的全局变量的可见性扩展到整个程序是在链接完成之后,而在编译阶段,他们的可见性仍局限于各自的文件。
 43 解决方案如下:
 44 编译器的目光不够长远,编译器没有能够意识到,某个变量符号虽然不是本文件定义的,但是它可能是在其它的文件中定义的。
 45 虽然编译器不够远见,但是我们可以给它提示,帮助它来解决上面出现的问题。这就是extern的作用了。
 46 extern的原理很简单,就是告诉编译器:“你现在编译的文件中,有一个标识符虽然没有在本文件中定义,但是它是在别的文件中定义的全局变量,你要放行!”
 47 //A.cpp:
 48 extern int iRI;
 49 int main()
 50 {
 51 iRI = 64;
 52 //.....
 53 }
 54 
 55 //B.cpp
 56 int iRI;
 57 这样编译就能够通过。
 58 extern int iRI; //并未分配空间,只是通知编译器,在其它文件定义过iRI。
 59 
 60 
 61 extern 作用2:在C++文件中调用C方式编译的函数
 62 C方式编译和C++方式编译
 63 相对于C,C++中新增了诸如重载等新特性。所以全局变量和函数名编译后的命名方式有很大区别。
 64 int a;
 65 int functionA();
 66 对于C方式编译:
 67 int a;=> _a
 68 int functionA(); => _functionA
 69 对于C++方式编译:
 70 int a; =>xx@xxx@a
 71 int functionA(); => xx@xx@functionA
 72 可以看出,因为要支持重载,所以C++方式编译下,生成的全局变量名和函数名复杂很多。与C方式编译的加一个下划线不同。
 73 于是就有下面几种情况:
 74 例2:C++调用C++定义的全局变量
 75 //A.cpp:
 76 extern int iRI;
 77 int main()
 78 {
 79 iRI = 64;
 80 //.....
 81 }
 82 //B.cpp
 83 int iRI;
 84 gcc A.cpp -c
 85 gcc B.cpp -c
 86 gcc A.o B.o -o test
 87 那么在编译链接时都没问题。
 88 
 89 例3:C++调用C定义的全局变量
 90 //A.cpp:
 91 extern int iRI;
 92 int main()
 93 {
 94 iRI = 64;
 95 //.....
 96 }
 97 //B.c
 98 int iRI;
 99 编译时没有问题,
100 gcc A.cpp -c
101 gcc B.c -c
102 但链接时,gcc B.o A.o -o test
103 则会报iRI没有定义。为什么呢?
104 因为gcc看到A.cpp,就使用C++方式编译,看到B.c,就使用C方式编译。
105 所以在A.cpp中的iRI=>XXX@XXX_iRI;
106 而B.c中iRI=〉_iRI;
107 所以在链接时,A.cpp想找到XXX@XXX_iRI,当然找不到。所以就需要告诉编译器,iRI是使用C方式编译的。
108 //A.cpp:
109 extern "C"
110 {
111 int iRI;
112 }
113 int main()
114 { iRI = 64;
115 //.....
116 }
117 //B.c
118 int iRI;
119 这样,当编译A.cpp时,编译器就知道iRI为C方式编译的。就会使用 _iRI。这样B.c提供的_iRI就可以被A.cpp找到了。
120 
121 例4:C++调用C定义的function
122 //A.cpp
123 extern int functionA();
124 
125 int main()
126 {
127 functionA();
128 }
129 
130 //B.c
131 int functionA()
132 {
133 //....
134 }
135 gcc A.cpp -c
136 gcc B.c -c
137 都没有问题。但同样的,gcc A.o B.o -o test
138 则报错,找不到functionA();
139 这是因为gcc将A.cpp认为是C++方式编译,B.c是C方式编译。
140 所以functionA在B.c中为:_functionA. 在A.cpp中为:XX@XXX_functionA
141 所以在链接时A.cpp找不到XX@XX_function.
142 于是需要通知编译器,functionA()是C方式编译命名的。
143 //A.cpp
144 extern "C"
145 {
146 int functionA();
147 }
148 int main()
149 {
150 functionA();
151 }
152 
153 //B.c
154 int functionA()
155 {
156 //....
157 }
158 于是,编译链接都可以通过。
159 总结:
160 extern "C"
161 {
162 functionA();
163 }//不止是声明,并且还指出:这个function请用C方式编译。所以不需要再次extern.
164 extern"C"
165 {
166 extern functionA();
167 }//这样做没什么太大意义。
View Code

24、野指针

 1 29.什么是野指针
 2 “野指针”不是NULL指针,而是指向“垃圾”的内存指针,其主要成因是:指针变量没有被初始化,或者指针p被free或者delete之后,没有置为NULL。
 3 首先”野指针”的概念只会出现在像C和C++这种没有自动内存垃圾回收功能的高级语言中, 所以java或c#肯定不会有野指针的概念. 当我们用malloc为一个指针分配一个空间后, 用完这个指针,把它free掉,但是没有让这个指针指向NULL或某一个特定的空间. 比如:
 4 char *pTemp = (char *)malloc(sizeof(char)*255);
 5 free(pTemp);
 6 上面两段代码执行后, temp就变成一个”野指针”了. 如果再次用这个指针,比如下面这样:
 7 strcpy(pTemp, “aaaa”);
 8 就是不对的.
 9 Windows系统有4G的虚拟内存空间,系统各2GB的两部分,高部分是系统本身的,低部分是用户进程空间. 而且每一个进程与其它进程间是隔离的, 互不影响.
10 当一个指针成为“野指针”时,它指向哪里就是不可预知的了. 当你用了一个指向不可预知的指针时,即使程序运行没有问题, 那也是非常危险的. 因为这个”野指针”指向的内存空间,可能是某个重要的数据或其它程序,甚至是系统的重要内存位置. 这样造成的危害是不可预知的,这个不可预知包括危害程度的不可预知和危害时间的不可预知的. 像一颗不知何时会爆的定时炸弹.
11 另外, “野指针”还有一个危害,很少为人所知, 就是如果一个程序里有”野指针”, 容易被病毒或黑客攻击. 原理很简单,在这个”野指针”指向的内存中放点有害的东东, 你使用这个”野指针”时,自然中招.
12 造成野指针的原因
13 1、指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的默认值是随机的,它会乱指一气。
14 2、指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。
15 3、指针操作超越了变量的作用范围。这种情况让人防不胜防。
16 
17 五 正确使用指针
18 通常避免野指针的办法是正确的使用指针 
19 1.声明一个pointer的时候注意初始化为null :
20 int* pInt = NULL; 
21 
22 2.分配完内存以后注意ASSERT:
23 pInt = new int[num]; 
24 ASSERT(pInt != NULL); 
25 
26 3. 删除时候注意用对操作符:
27 对于new int类型的,用delete 
28 对于new int[]类型的,用delete [] 
29 
30 4.删除完毕以后记得给他null地址: 
31 delete [] pInt; 
32 pInt = NULL; 
33 
34 5.记住,谁分配的谁回收,不要再一个函数里面分配local pointer,送到另外一个函数去delete。 
35 
36 6.返回local address是非常危险的,如必须这样做,请写注释到程序里面,免得忘记。
View Code

25、sizeof和strlen  

sizeof 结构体  字节对齐

sizeof类   虚函数 虚表

 1 一、sizeof
 2 sizeof(...)是运算符,在头文件中typedef为unsigned int,其值在编译时即计算好了,参数可以是数组、指针、类型、对象、函数等。
 3 它的功能是:获得保证能容纳实现所建立的最大对象的字节大小。
 4 由于在编译时计算,因此sizeof不能用来返回动态分配的内存空间的大小。实际上,用sizeof来返回类型以及静态分配的对象、结构或数组所占的空间,返回值跟对象、结构、数组所存储的内容没有关系。
 5 具体而言,当参数分别如下时,sizeof返回的值表示的含义如下:
 6 数组——编译时分配的数组空间大小;
 7 指针——存储该指针所用的空间大小(存储该指针的地址的长度,是长整型,应该为4);
 8 类型——该类型所占的空间大小;
 9 对象——对象的实际占用空间大小;
10 函数——函数的返回类型所占的空间大小。函数的返回类型不能是void。
11 **************
12 
13 二、strlen
14 strlen(...)是函数,要在运行时才能计算。参数必须是字符型指针(char*)。当数组名作为参数传入时,实际上数组就退化成指针了。
15 它的功能是:返回字符串的长度。该字符串可能是自己定义的,也可能是内存中随机的,该函数实际完成的功能是从代表该字符串的第一个地址开始遍历,直到遇到结束符NULL。返回的长度大小不包括NULL。
16 1.sizeof操作符的结果类型是size_t,它在头文件中typedef为unsigned int类型。 
17 该类型保证能容纳实现所建立的最大对象的字节大小。 
18 2.sizeof是算符,strlen是函数。 
19 3.sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以''\0''结尾的。 
20 sizeof还可以用函数做参数,比如: 
21 short f(); 
22 printf("%d\n", sizeof(f())); 
23 输出的结果是sizeof(short),即2。 
24 4.数组做sizeof的参数不退化,传递给strlen就退化为指针了。 
25 5.大部分编译程序 在编译的时候就把sizeof计算过了 是类型或是变量的长度这就是sizeof(x)可以用来定义数组维数的原因 
26 char str[20]="0123456789"; 
27 int a=strlen(str); //a=10; 
28 int b=sizeof(str); //而b=20; 
29 6.strlen的结果要在运行的时候才能计算出来,时用来计算字符串的长度,不是类型占内存的大小。 
30 7.sizeof后如果是类型必须加括弧,如果是变量名可以不加括弧。这是因为sizeof是个操作符不是个函数。 
31 8.当适用了于一个结构类型时或变量, sizeof 返回实际的大小, 
32 当适用一静态地空间数组, sizeof 归还全部数组的尺寸。 
33 sizeof 操作符不能返回动态地被分派了的数组或外部的数组的尺寸 
34 9.数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址, 
35 如: 
36 fun(char [8]) 
37 fun(char []) 
38 都等价于 fun(char *) 
39 在C++里参数传递数组永远都是传递指向数组首元素的指针,编译器不知道数组的大小 
40 如果想在函数内知道数组的大小, 需要这样做: 
41 进入函数后用memcpy拷贝出来,长度由另一个形参传进去 
42 fun(unsiged char *p1, int len) 
43 { 
44 unsigned char* buf = new unsigned char[len+1] 
45 memcpy(buf, p1, len); 
46 } 
47 我们能常在用到 sizeof 和 strlen 的时候,通常是计算字符串数组的长度 
48 看了上面的详细解释,发现两者的使用还是有区别的,从这个例子可以看得很清楚: 
49 char str[20]="0123456789"; 
50 int a=strlen(str); //a=10; >>>> strlen 计算字符串的长度,以结束符 0x00 为字符串结束。 
51 int b=sizeof(str); //而b=20; >>>> sizeof 计算的则是分配的数组 str[20] 所占的内存空间的大小,不受里面存储的内容改变。 
52 上面是对静态数组处理的结果,如果是对指针,结果就不一样了 
53 char* ss = "0123456789"; 
54 sizeof(ss) 结果 4 ===》ss是指向字符串常量的字符指针,sizeof 获得的是一个指针的之所占的空间,应该是 长整型的,所以是4 
55 sizeof(*ss) 结果 1 ===》*ss是第一个字符 其实就是获得了字符串的第一位'0' 所占的内存空间,是char类 型的,占了 156 strlen(ss)= 10 >>>> 如果要获得这个字符串的长度,则一定要使用 strlen
View Code

26、stl

  1 31.stl模版中都有啥
  2 STL就是Standard Template Library,标准模板库。
  3 STL六大组件
  4 容器(Container)
  5 算法(Algorithm)
  6 迭代器(Iterator)
  7 仿函数(Function object  8 适配器(Adaptor)
  9 空间配置器(allocator)
 10 1、容器
 11 作为STL的最主要组成部分--容器,分为向量(vector),双端队列(deque),表(list),队列(queue),堆栈(stack),集合(set),多重集合(multiset),映射(map),多重映射(multimap)。 
 12 容器    特性     所在头文件 
 13 向量vector     可以用常数时间访问和修改任意元素,在序列尾部进行插入和删除时,具有常数时间复杂度,对任意项的插入和删除就有的时间复杂度与到末尾的距离成正比,尤其对向量头的添加和删除的代价是惊人的高的     <vector> 
 14 双端队列deque     基本上与向量相同,唯一的不同是,其在序列头部插入和删除操作也具有常量时间复杂度     <deque> 
 15 表list     对任意元素的访问与对两端的距离成正比,但对某个位置上插入和删除一个项的花费为常数时间。     <list> 
 16 队列queue     插入只可以在尾部进行,删除、检索和修改只允许从头部进行。按照先进先出的原则。     <queue> 
 17 堆栈stack     堆栈是项的有限序列,并满足序列中被删除、检索和修改的项只能是最近插入序列的项。即按照后进先出的原则     <stack> 
 18 集合set     由节点组成的红黑树,每个节点都包含着一个元素,节点之间以某种作用于元素对的谓词排列,没有两个不同的元素能够拥有相同的次序,具有快速查找的功能。但是它是以牺牲插入删除操作的效率为代价的     <set> 
 19 多重集合multiset     和集合基本相同,但可以支持重复元素具有快速查找能力     <set> 
 20 映射map     由{键,值}对组成的集合,以某种作用于键对上的谓词排列。具有快速查找能力     <map> 
 21 多重集合multimap     比起映射,一个键可以对应多了值。具有快速查找能力     <map> 
 22 STL容器能力表:
 23 
 24 2、算法
 25 算法部分主要由头文件<algorithm>,<numeric>和<functional>组成。< algorithm>是所有STL头文件中最大的一个,它是由一大堆模版函数组成的,可以认为每个函数在很大程度上都是独立的,其中常用到的功能范 围涉及到比较、交换、查找、遍历操作、复制、修改、移除、反转、排序、合并等等。<numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作。<functional>中则定义了一些模板类,用以声明函数对象。 
 26 STL的算法也是非常优秀的,它们大部分都是类属的,基本上都用到了C++的模板来实现,这样,很多相似的函数就不用自己写了,只要用函数模板就可以了。 
 27 我们使用算法的时候,要针对不同的容器,比如:对集合的查找,最好不要用通用函数find(),它对集合使用的时候,性能非常的差,最好用集合自带的find()函数,它针对了集合进行了优化,性能非常的高。
 28 3、迭代器
 29 它的具体实现在<itertator>中,我们完全可以不管迭代器类是怎么实现的,大多数的时候,把它理解为指针是没有问题的(指针是迭代器的一个特例,它也属于迭代器),但是,决不能完全这么做。 
 30 迭代器功能
 31 输入迭代器 
 32 Input iterator     向前读 
 33 Reads forward     istream 
 34 输出迭代器 
 35 Output iterator     向前写 
 36 Writes forward     ostream,inserter 
 37 前向迭代器 
 38 Forward iterator     向前读写 
 39 Read and Writes forward     
 40 双向迭代器 
 41 Bidirectional iterator     向前向后读写 
 42 Read and Writes forward and 
 43 backward     list,set,multiset,map,mul 
 44 timap 
 45 随机迭代器 
 46 Random access iterator     随机读写 
 47 Read and Write with random 
 48 access     vector,deque,array,string 
 49 4、仿函数
 50 仿函数,又或叫做函数对象,是STL六大组件之一;仿函数虽然小,但却极大的拓展了算法的功能,几乎所有的算法都有仿函数版本。例如,查找算法find_if就是对find算法的扩展,标准的查找是两个元素相等就找到了,但是什么是相等在不同情况下却需要不同的定义,如地址相等,地址和邮编都相等,虽然这些相等的定义在变,但算法本身却不需要改变,这都多亏了仿函数。仿函数(functor)又称之为函数对象(function object),其实就是重载了()操作符的struct,没有什么特别的地方。
 51 如以下代码定义了一个二元判断式functor:
 52 struct IntLess
 53 {
 54 bool operator()(int left, int right) const
 55 {
 56 return (left < right);
 57 };
 58 };
 59 为什么要使用仿函数呢?
 60 1).仿函数比一般的函数灵活。
 61 2).仿函数有类型识别,可以作为模板参数。
 62 3).执行速度上仿函数比函数和指针要更快的。
 63 怎么使用仿函数?
 64 除了在STL里,别的地方你很少会看到仿函数的身影。而在STL里仿函数最常用的就是作为函数的参数,或者模板的参数。
 65 在STL里有自己预定义的仿函数,比如所有的运算符,=,-,*,、比如'<'号的仿函数是less
 66 template<class _Ty>
 67 struct less : public binary_function<_Ty, _Ty, bool>
 68 { // functor for operator<
 69 bool operator()(const _Ty& _Left, const _Ty& _Right) const
 70 { // apply operator< to operands
 71 return (_Left < _Right);
 72 }
 73 };
 74 从上面的定义可以看出,less从binary_function<...>继承来的,那么binary_function又是什么的?
 75 template<class _Arg1, class _Arg2, class _Result>
 76 struct binary_function
 77 { // base class for binary functions
 78 typedef _Arg1 first_argument_type;
 79 typedef _Arg2 second_argument_type;
 80 typedef _Result result_type;
 81 };
 82 其实binary_function只是做一些类型声明而已,别的什么也没做,但是在STL里为什么要做这些呢?如果你要阅读过STL的源码,你就会发现,这样的用法很多,其实没有别的目的,就是为了方便,安全,可复用性等。但是既然STL里面内定如此了,所以作为程序员你必须要遵循这个规则,否则就别想安全的使用STL。
 83 比如我们自己定一个仿函数。可以这样:
 84 template <typename type1,typename type2>
 85 class func_equal :public binary_function<type1,type2,bool>
 86 {
 87 inline bool operator()(type1 t1,type2 t2) const//这里的const不能少
 88 {
 89 return t1 == t2;//当然这里要overload==
 90 }
 91 }
 92 我们看这一行: inline bool operator()(type1 t1,type2 t2) const//这里的const不能少
 93 inline是声明为内联函数,我想这里应该不用多说什么什么了,关键是为什么要声明为const的?要想找到原因还是看源码,加入如果我们这里写一行代码,find_if(s.begin(),s.end(),bind2nd(func_equal(),temp)),在bind2nd函数里面的参数是const类型的,const类型的对象,只能访问cosnt修饰的函数!
 94 与binary_function(二元函数)相对的是unary_function(一元函数),其用法同binary_function
 95 struct unary_function { 
 96 typedef _A argument_type; 
 97 typedef _R result_type; 
 98 }; 
 99 注:仿函数就是重载()的class,并且重载函数要为const的,如果要自定义仿函数,并且用于STL接配器,那么一定要从binary_function或者,unary_function继承。
100 5、适配器
101 适配器是用来修改其他组件接口的STL组件,是带有一个参数的类模板(这个参数是操作的值的数据类型)。STL定义了3种形式的适配器:容器适配器,迭代器适配器,函数适配器。
102 容器适配器:包括栈(stack)、队列(queue)、优先(priority_queue)。使用容器适配器,stack就可以被实现为基本容器类型(vector,dequeue,list)的适配。可以把stack看作是某种特殊的vector,deque或者list容器,只是其操作仍然受到stack本身属性的限制。queue和priority_queue与之类似。容器适配器的接口更为简单,只是受限比一般容器要多。
103 迭代器适配器:修改为某些基本容器定义的迭代器的接口的一种STL组件。反向迭代器和插入迭代器都属于迭代器适配器,迭代器适配器扩展了迭代器的功能。
104 函数适配器:通过转换或者修改其他函数对象使其功能得到扩展。这一类适配器有否定器(相当于""操作)、绑定器、函数指针适配器。函数对象适配器的作用就是使函数转化为函数对象,或是将多参数的函数对象转化为少参数的函数对象。
105 例如:
106 在STL程序里,有的算法需要一个一元函数作参数,就可以用一个适配器把一个二元函数和一个数值,绑在一起作为一个一元函数传给算法。 
107 例如: 
108 find_if(coll.begin(), coll.end(), bind2nd(greater <int>(), 42)); 
109 这句话就是找coll中第一个大于42的元素。 
110 greater <int>(),其实就是">"号,是一个2元函数 
111 bind2nd的两个参数,要求一个是2元函数,一个是数值,结果是一个1元函数。
112 bind2nd就是个函数适配器。
113 6、空间配置器
114 STL的内存配置器在我们的实际应用中几乎不用涉及,但它却在STL的各种容器背后默默做了大量的工作,STL内存配置器为容器分配并管理内存。统一的内存管理使得STL库的可用性、可移植行、以及效率都有了很大的提升。
115 SGI-STL的空间配置器有2种,一种仅仅对c语言的malloc和free进行了简单的封装,而另一个设计到小块内存的管理等,运用了内存池技术等。在SGI-STL中默认的空间配置器是第二级的配置器。
116 SGI使用时std::alloc作为默认的配置器。
117 A).alloc把内存配置和对象构造的操作分开,分别由alloc::allocate()和::construct()负责,同样内存释放和对象析够操作也被分开分别由alloc::deallocate()和::destroy()负责。这样可以保证高效,因为对于内存分配释放和构造析够可以根据具体类型(type traits)进行优化。比如一些类型可以直接使用高效的memset来初始化或者忽略一些析构函数。对于内存分配alloc也提供了2级分配器来应对不同情况的内存分配。
118 B).第一级配置器直接使用malloc()和free()来分配和释放内存。第二级视情况采用不同的策略:当需求内存超过128bytes的时候,视为足够大,便调用第一级配置器;当需求内存小于等于128bytes的时候便采用比较复杂的memeory pool的方式管理内存。
119 C).无论allocal被定义为第一级配置器还是第二级,SGI还为它包装一个接口,使得配置的接口能够符合标准即把配置单位从bytes转到了元素的大小
120 四、注意细节:
121 1、auto_ptr 不能用new[]所生成的array作为初值,因为释放内存时用的是delete,而不是delete[]
122 2、就搜寻速度而言,hash table通常比二叉树还要快5~10倍。hash table不是C++标准程序库的一员。
123 3、迭代器使用过程中优先选用前置式递增操作符(++iter)而不是选择后置式递增操作符(iter++)。
124 3、迭代器三个辅助函数:advance(),distance(),iter_swap()。
125     advance()可令迭代器前进
126     distance()可处理迭代器之间的距离。
127     iter_swap()可交换两个迭代器所指内容。
128 4、hasp函数 makeheap()、push_heap()、pop_heap()、sort_heap()
129 5、’/0’在string之中并不具有特殊意义,但是在一般C形式的string中却用来标记字符串结束。在string中,字符 ‘/0’和其他字符的地位完全相同。string中有三个函数可以将字符串内容转换成字符数组或C形式的string。
130 data()    以字符数组的形式返回字符串内容。但末未追加’/0’字符,返回类型并非有效的C形式string。
131 c_str()    以C形式返回字符串内容(在末尾端添加’/0’字符)。
132 copy()    将字符串内容复制到“调用者提供的字符数组”中,不添加’/0’字符。
133 6、容器中用empty来代替检查size是否为0;当使用new得到指针的容器时,切记在容器销毁前delete那些指针;千万不要把auto_ptr放入容器中。
134 7、尽量使用vector和string来代替动态申请的数组;避免使用vector<bool>,vector<bool>有两个问题.第一,它不是一个真正STL容器,第二,它并不保存bool类型。
135 8、迭代器使用过程中,尽量使用iterator代替const_iterator,reverse_iterator和const_reverse_iterator;使用distance和advance把const_iterators转化成iterators。
136 typedef deque<int> IntDeque; // 和以前一样
137 typedef IntDeque::iterator Iter;
138 typedef IntDeque::const_iterator ConstIter;
139 IntDeque d;
140 ConstIter ci;
141 ... // 让ci指向d
142 Iter i(d.begin()); // 初始化i为d.begin()
143 advance(i, distance(i, ci)); // 调整i,指向ci位置
144 9、避免对set和multiset的键值进行修改。
145 10、永远让比较函数对相同元素返回false。
146 11、排序选择:
147 1)如果你需要在vector、string、deque或数组上进行完全排序,你可以使用sort或stable_sort。 
148 2)如果你有一个vector、string、deque或数组,你只需要排序前n个元素,应该用partial_sort。 
149 3)如果你有一个vector、string、deque或数组,你需要鉴别出第n个元素或你需要鉴别出最前的n个元素,而不用知道它们的顺序,nth_element是你应该注意和调用的。 
150 4)如果你需要把标准序列容器的元素或数组分隔为满足和不满足某个标准,你大概就要找partition或stable_partition。 
151 5)如果你的数据是在list中,你可以直接使用partition和stable_partition,你可以使用list的sort来代替sort和stable_sort。如果你需要partial_sort或nth_element提供的效果,你就必须间接完成这个任务。
152 12、如果你真的想删除东西的话就在类似remove的算法后接上erase。remove从一个容器中remove元素不会改变容器中元素的个数,erase是真正删除东西。
153 13、提防在指针的容器上使用类似remove的算法,在调用类似remove的算法前手动删除和废弃指针。
154 14、尽量用成员函数代替同名的算法,有些容器拥有和STL算法同名的成员函数。关联容器提供了count、find、lower_bound、upper_bound和equal_range,而list提供了remove、remove_if、unique、sort、merge和reverse。大多数情况下,你应该用成员函数代替算法。这样做有两个理由。首先,成员函数更快。其次,比起算法来,它们与容器结合得更好(尤其是关联容器)。那是因为同名的算法和成员函数通常并不是是一样的。
155 15、容器中使用自定义的结构体时,如果用到拷贝与赋值,结构体需要重载operator=符号;比较容器分成相等与不等,相等时重载operator==符号,不等时重载operator<符号。比如set、map、multiset、multimap、priority_queue等容器类要求重载operator<符号。
156 16、Map/Multimap,Sets/Multisets都不能用push_back,push_front,因为它是自动排序的。
157 Set内的相同数值的元素只能出现一次,Multisets内可包含多个数值相同的元素。
158 Map内的相同数值的元素只能出现一次,Multimap内可包含多个数值相同的元素。内部由二叉树实现,便于查找。
159 17string 与 数字之间的转换,转换的方法有很多种,一般使用stringstream来实现转换。比如:
160 #include <iostream>
161 #include <sstream> 
162 #include <string> 
163 using namespace std; 
164 int main() 
165 { 
166 int i=0; 
167 string temp; 
168 stringstream s; 
169 //string转换为数字
170 temp = “1234”; 
171 s<<temp; 
172 s>>i; 
173 cout<<i<<endl; 
174 //数字转换为string
175 i=256; 
176 s<<i;
177 temp = s.str();
178 cout<<temp<<end;
179 system("pause"); 
180 return 0;
181 }
182 18、对于自定义的结构体,放入容器中,最好不要对容器进行内存初始化(不要调用memset,zeromemory函数),否则如果结构体中有指针类型的变量时,就会出现问题。
183 19、Vector的函数泄漏问题
184 定义了一个
185 struct temp
186 {
187 char name[256];
188 int i;
189 }
190 Vector<temp> vect;
191 当对这个vect执行pushback一些temp的结构体后,执行clear这样是否会内存泄露?可以释放掉temp结构体中的name内存吗? 
192 解决方法:
193 不行,clear只是把那些元素全部删除掉,并不是释放内存。再者,你这样的定义容器是不需要释放内存的,如果你这样定义,std::vector <temp> *pVec。就需要了。先pVec->clear()再 pVec->swap( (std::vector <temp>)(*pVec) )。就能实现内存的释放。 
194 20、stl之map erase方法的正确使用
195 STL的map表里有一个erase方法用来从一个map中删除掉指令的一个节点,不存在任何问题。
196 如果删除多一个节点时,需要使用正确的调用方法。比如下面的方法是有问题:
197 for(ITER iter=mapTest.begin();iter!=mapTest.end();++iter)
198 {
199 cout<<iter->first<<":"<<iter->second<<endl;
200 mapTest.erase(iter);
201 }
202 这是一种错误的写法,会导致程序行为不可知.究其原因是map 是关联容器,对于关联容器来说,如果某一个元素已经被删除,那么其对应的迭代器就失效了,不应该再被使用;否则会导致程序无定义的行为。
203 正确的使用方法:
204 1).使用删除之前的迭代器定位下一个元素。STL建议的使用方式
205 for(ITER iter=mapTest.begin();iter!=mapTest.end();)
206 {
207 cout<<iter->first<<":"<<iter->second<<endl;
208 mapTest.erase(iter++);
209 }
210 或者
211 for(ITER iter=mapTest.begin();iter!=mapTest.end();)
212 {
213 ITER iterTmp = iter;
214 iter++;
215 cout<<iterTmp->first<<":"<<iterTmp->second<<endl;
216 mapTest.erase(iterTmp);
217 }
218 2). erase() 成员函数返回下一个元素的迭代器
219 for(ITER iter=mapTest.begin();iter!=mapTest.end();)
220 {
221 cout<<iter->first<<":"<<iter->second<<endl;
222 iter=mapTest.erase(iter);
223 }
224 21、boost::bind总结 
225 bind 是一组重载的函数模板.用来向一个函数(或函数对象)绑定某些参数. bind的返回值是一个函数对象. 
226 性质:
227 不是函数,是一个class,是一个多元仿函数
228 模板参数:
229 带模板参数,但不需要,会自动推导!
230 构造函数参数:
231 格式:_需要绑定类型,_参数1,_参数2,_参数3,_参数4…
232 _需要绑定类型:可以是普通函数,类成员函数,成员变量
233 _参数N:可以是一个占位符,或者实际参数。
234 如果绑定的类型是一个类成员函数或变量,那么第一个参数必须是对象或者对象指针。
235 仿函数参数:
236 任意
237 仿函数返回值
238 如果绑定的是函数,返回绑定函数的返回值。
239 如果绑定是成员变量,返回成员变量值
240 占位符:
241 _1,_2,_3,_4….._9
242 占位符的数字表示仿函数时对应参数的位置。
243 一个bind里可以嵌入多个bind,但占位符是相对于这一块的bind是共享。
244 注意事项
245 a)如果绑定的是类函数,传入对象时,最好使用对象指针,如果使用对象实例会产生多次对象复制。如果非要传对象而不想多次被复制传在在使用ref或cref(ref的const版)
246 b) 跟lambda混用时一定要特别小心
247 第一、 会与lambda的占位符有冲突
248 第二、 lambda库里有跟同样名字的bind,功能类似,但没有此功能强大
249 总结
250 无模板参数,构函数对绑定函数负责,仿函数是任意的。
View Code

27、static

  持久、隐藏、0、共享、生命周期、无this supper 

  1 →静态:static
  2 用法:是一个修饰符,用于修饰成员(成员变量,成员函数)
  3int age(成员变量)实例变量和 static int age(静态成员变量)类变量的区别
  4 1、存放为着
  5 实例变量是随着对象的创建而存在堆内存中
  6 类变量随着类的加载而存在于方法区中
  7 2、生命周期
  8 实例变量随着对象的消失而消失
  9 类变量生命周期最长,随着类的消失而消失
 10 
 11 →静态使用注意事项
 12 1、静态方法只能访问静态成员
 13 2、静态方法中不可以定义this,super关键字
 14 因为静态优先于对象存在,所以静态方法中不可以出现this
 15 3、主函数是静态的
 16 →静态的好处和坏处
 17 好处:对对象的数据进行单独空间的存储,节省空间,没有必要每个对象中的存储一份
 18 可以直接被类名调用(Person.country)
 19 坏处:生命周期过长。
 20 访问出现局限性。(只能访问静态)
 21 →什么时候使用静态呢?
 22 要从两个方面入手
 23 因为静态修饰的内容有成员变量和成员方法(函数)
 24 
 25 什么时候定义静态变量(类变量)呢?
 26 当对象中出现共享数据时,该数据被静态所修饰
 27 对象中的特有数据要定义成非静态存在于堆内存中。
 28 
 29 什么时候定义静态函数呢?
 30 当功能内部没有访问到非静态数据(对象的特有数据),那么该功能可以定义为静态的。
 31 
 32 →静态的应用
 33 每一个应用程序都有共性的功能
 34 可以将这些功能进行抽取,独立封装
 35 以便复用。
 36 把成员方法都定义成static的直接用类名调用。
 37 34.Static函数的作用。
 38 在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。
 39 静态函数的例子:
 40 ?
 41 1
 42 2
 43 3
 44 4
 45 5
 46 6
 47 7
 48 8
 49 9
 50 10
 51 11
 52 12    //Example4 
 53 #include<iostream.h>
 54 staticvoidfn();//声明静态函数
 55 voidmain() 
 56 { 
 57 fn(); 
 58 } 
 59 voidfn()//定义静态函数
 60 { 
 61 intn=10; 
 62 cout<<n<<endl; 
 63 }
 64 定义静态函数的好处:
 65 静态函数不能被其它文件所用;
 66 其它文件中可以定义相同名字的函数,不会发生冲突;
 67 静态成员函数
 68 与静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全部服务而不是为某一个类的具体对象服务。静态成员函数与静态数据成员一样,都是类的内部 实现,属于类定义的一部分。普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下,this 是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指 针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。下面举个静态成员函数的例子。
 69 //Example 6
 70 #include <iostream.h>
 71 class Myclass
 72 { public: Myclass(int a,int b,int c);
 73 static void GetSum(); // 声明静态成员函数
 74 private: int a,b,c;
 75 static int Sum;//声明静态数据成员
 76 }
 77 int Myclass::Sum=0;//定义并初始化静态数据成员
 78 Myclass::Myclass(int a,int b,int c)
 79 { this->a=a; this->b=b; this->c=c;
 80 Sum+=a+b+c; //非静态成员函数可以访问静态数据成员
 81 }
 82 void Myclass::GetSum() //静态成员函数的实现
 83 {// cout<<a<<endl; //错误代码,a是非静态数据成员
 84 cout<<"Sum="<<Sum<<endl;
 85 }
 86 void main()
 87 { Myclass M(1,2,3);
 88 M.GetSum();
 89 Myclass N(4,5,6);
 90 N.GetSum();
 91 Myclass::GetSum();
 92 }
 93 关于静态成员函数,可以总结为以下几点:
 94 出现在类体外的函数定义不能指定关键字static;
 95 静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
 96 非静态成员函数可以任意地访问静态成员函数和静态数据成员;
 97 静态成员函数不能访问非静态成员函数和非静态数据成员;
 98 由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;
 99 调用静态成员函数,可以用成员访问操作符(.)和(->;)为一个类的对象或指向类对象的指针调用静态成员函数,也可以直接使用如下格式:
100 <;类名>::<;静态成员函数名>;(<;参数表>;)
101 调用类的静态成员函数。
102 35.静态的全局变量和全局变量一样吗,若有不一样的地方那体现在哪;
103 全局变量与全局静态变量的区别: 
104 (a)若程序由一个源文件构成时,全局变量与全局静态变量没有区别。 
105 (b)若程序由多个源文件构成时,全局变量与全局静态变量不同:全局静态变量使得该变量成为定义该变量的源文件所独享,即:全局静态变量对组成该程序的其它源文件是无效的。 
106 (c)具有外部链接的静态;可以在所有源文件里调用;除了本文件,其他文件可以通过extern的方式引用;
107 
108 静态全局变量的作用: 
109 (a)不必担心其它源文件使用相同变量名,彼此相互独立。
110 (b)在某源文件中定义的静态全局变量不能被其他源文件使用或修改。
111 (c) 只能在本文件中使用!具有内部链接的静态;不允许在其他文件里调用;例如:一个程序由两个源文件组成,其中在一个源文件中定义了“int n;”,在另一个源文件中定义了“static int n;”则程序给它们分别分配了不同的空间,两个值互不干扰。
112 全局变量、静态全局变量、静态局部变量和局部变量的区别
113 变量可以分为:全局变量、静态全局变量、静态局部变量和局部变量。
114 按存储区域分,全局变量、静态全局变量和静态局部变量都存放在内存的静态存储区域,局部变量存放在内存的栈区。
115 按作用域分,全局变量在整个工程文件内都有效;静态全局变量只在定义它的文件内有效;静态局部变量只在定义它的函数内有效,并且程序仅分配一次内存,函数返回后,该变量不会消失;局部变量在定义它的函数内有效,但是函数返回后失效。
116 全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
117 从以上分析可以看出,把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
118 static函数与普通函数作用域不同,只在定义该变量的源文件内有效。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件。
119 static全局变量与普通的全局变量有什么区别:static全局变量只初使化一次,防止在其他文件单元中被引用;
120 static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;
121 static函数与普通函数有什么区别:static函数与普通函数作用域不同,只在定义该变量的源文件内有效;
122 全局变量和静态变量如果没有手工初始化,则由编译器初始化为0。局部变量的值不可知。
View Code

28、c和c++区别

  C S for & main

29、父类指针指向子类对象

 1 父类引用指向子类对象指的是:
 2 例如父类Animal,子类Cat,Dog。其中Animal可以是类也可以是接口,Cat和Dog是继承或实现Animal的子类。
 3 Animal animal = new Cat();
 4 即声明的是父类,实际指向的是子类的一个对象。
 5 那这么使用的优点是什么,为什么要这么用?可以用这几个关键词来概括:多态、动态链接,向上转型
 6 也有人说这是面向接口编程,可以降低程序的耦合性,即调用者不必关心调用的是哪个对象,只需要针对接口编程就可以了,被调用者对于调用者是完全透明的。让你更关注父类能做什么,而不去关心子类是具体怎么做的,你可以随时替换一个子类,也就是随时替换一个具体实现,而不用修改其他.
 7 父类子类指针函数调用注意事项
 8 1,如果以一个基础类指针指向一个衍生类对象(派生类对象),那么经由该指针只能访问基础类定义的函数
 9 2,如果以一个衍生类指针指向一个基础类对象,必须先做强制转型动作(explicit cast),这种做法很危险,也不符合生活习惯,在程序设计上也会给程序员带来困扰。(一般不会这么去定义)
10 3,如果基础类和衍生类定义了相同名称的成员函数,那么通过对象指针调用成员函数时,到底调用那个函数要根据指针的原型来确定,而不是根据指针实际指向的对象类型确定。
View Code

 30、深拷贝浅拷贝

 1 浅拷贝是源对象和拷贝对象公用一份实体,仅仅是名称不同,对其中任何一个对象的操作都会影响另一个对象
 2 深拷贝源对象和拷贝对象是相互独立的
 3 
 4 浅拷贝的问题
 5 如果一个指针   进行了浅拷贝  两个指针指向同一个内存地址的话  在析构的时候delete两次该地址  造成程序崩溃
 6 
 7 39.什么是深拷贝浅拷贝?
 8 浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。对其中任何一个对象的改动都会影响另外一个对象。举个例子,一个人一开始叫张三,后来改名叫李四了,可是还是同一个人,不管是张三缺胳膊少腿还是李四缺胳膊少腿,都是这个人倒霉。
 9   深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。举个例子,一个人名叫张三,后来用他克隆(假设法律允许)了另外一个人,叫李四,不管是张三缺胳膊少腿还是李四缺胳膊少腿都不会影响另外一个人。比较典型的就是Value(值)对象,如预定义类型Int32,Double,以及结构(struct),枚举(Enum)等。
10 浅拷贝会出现什么问题呢?
11 假如有一个成员变量的指针,char *m_data;
12 其一,浅拷贝只是拷贝了指针,使得两个指针指向同一个地址,这样在对象块结束,调用函数析构的时,会造成同一份资源析构2次,即delete同一块内存2次,造成程序崩溃。
13 其二,浅拷贝使得obj.m_data和obj1.m_data指向同一块内存,任何一方的变动都会影响到另一方。
14 其三,在释放内存的时候,会造成obj1.m_data原有的内存没有被释放(这句话,刚开始我不太理解,如果没有走自定义的拷贝构造函数,申请内存空间,A obj1(obj);也不走默认构造函数,走的是默认的拷贝构造函数,何来分配空间直说,更不会造成obj1.m_data原有的内存没有被释放,这里刚开始我一直有疑问),造成内存泄露。
15 事实是这样的,当delete obj.m_data, obj.m_data内存被释放后,由于之前obj.m_data和obj1.m_data指向的是同一个内存空间,obj1.m_data所指的空间不能在被利用了,delete obj1.m_data也不会成功,一致已经无法操作该空间,所以导致内存泄露。
16 简单的来说就是,在有指针的情况下,浅拷贝只是增加了一个指针指向已经存在的内存,而深拷贝就是增加一个指针并且申请一个新的内存,使这个增加的指针指向这个新的内存,采用深拷贝的情况下,释放内存的时候就不会出现在浅拷贝时重复释放同一内存的错误!
17 我列举一个例子来说吧:
18 你正在编写C++程序中有时用到,操作符的重载。最能体现深层拷贝与浅层拷贝的,就是‘=’的重载。
19 看下面一个简单的程序:
20 
21 class string
22 {
23 
24 char *m_str;
25 
26 public:
27 
28 string(char *s)
29 
30 {
31 
32 m_str=s;
33 
34 }
35 
36 string()
37 {};
38 
39 String & operator=(const string s)
40 
41 {
42 
43 m_str=s.m_str;
44 
45 return *this
46 }
47 
48 };
49 
50 int main()
51 
52 {
53 
54 string s1("abc"),s2;
55 
56 s2=s1;
57 
58 cout<<s2.m_str;
59 
60 }
61 
62 上面的 =重载其是就是实现了浅拷贝原因。是由于对象之中含有指针数据类型.s1,s2恰好指向同一各内存。所以是浅拷贝。而你如果修改一下原来的程序:
63 string&operator=(const string&s)
64 
65 {
66 
67 if(strlen(m_str)!=strlen(s.m_str))
68 
69 m_str=new char[strlen(s.m_str)+1];
70 
71 if(*this!=s)
72 
73 strcopy(m_str,s.m_str);
74 
75 return *this;
76 
77 }
78 这样你就实现了深拷贝,原因是你为被赋值对象申请了一个新的内存所以就是深拷贝。
View Code

31、结构体、联合体区别

 1 1、结构体的定义
 2 struct 结构体名
 3 {
 4 数据类型1 成员名1;
 5 数据类型2 成员名2;
 6 ......
 7 };
 8 2、联合体的定义
 9 union 联合体名
10 {
11 数据类型1 成员名1;
12 数据类型2 成员名2;
13 };
14 3、两者之间的区别:
151)在同一时刻,结构体的每个成员都有值,但是联合体在同一时刻只有一个成员有值(或理解为结构体的sizeof是所有成员的和,而联合体的sizeof等于其最长的成员的sizeof);
162)当对结构体变量的其中一个成员进行修改时,对其他成员没有影响,但是修改联合体时,则会将原来的成员值覆盖。
View Code

32、字节对齐的意义

1 提高cpu效率
2 现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
3 对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数据。显然在读取效率上下降很多。
4 字节对齐方式可以更充分地利用存储空间,但是这会降低计算机读写数据的速度,是一种以时间换取空间的方式。
View Code

 33、动态链接库  静态链接库

  1 静态库:在编译的时候加载生成目标文件,在运行时不用加载库,在运行时对库没有依赖性。
  2 动态库:在目标文件运行时加载,手动加载,且对库有依赖性。
  3 
  4 两者区别:
  5 一,静态库的使用需要:
  6 1 包含一个对应的头文件告知编译器lib文件里面的具体内容
  7 2 设置lib文件允许编译器去查找已经编译好的二进制代码 
  8 
  9 二,动态库的使用:
 10 程序运行时需要加载动态库,对动态库有依赖性,需要手动加入动态库
 11 
 12 三,依赖性:
 13 静态链接表示静态性,在编译链接之后, lib库中需要的资源已经在可执行程序中了, 也就是静态存在,没有依赖性了
 14 动态,就是实时性,在运行的时候载入需要的资源,那么必须在运行的时候提供 需要的 动态库,有依赖性, 运行时候没有找到库就不能运行了 
 15 
 16 四,区别:
 17 简单讲,静态库就是直接将需要的代码连接进可执行程序;动态库就是在需要调用其中的函数时,根据函数映射表找到该函数然后调入堆栈执行。
 18 做成静态库可执行文件本身比较大,但不必附带动态库
 19 做成动态库可执行文件本身比较小,但需要附带动态库 
 20 
 21 五:
 22 首先纠正所谓“静态连接就是把需要的库函数放进你的exe之中”的说法。在真实世界中,有三个概念:Use static libary, static linked DLL, dynamic linked DLL.
 23 多数人混淆了static libary 和 static linked DLL的概念,当然他们有似是而非的“相似之处”,比如都用到.lib,下面具体说明。
 24 使用静态库(Use static libary)是把.lib和其他.obj一起build在目标文件中,目标文件可以是.exe,也可以是.dll或.oxc等。一般情况下,可以根本就没有“对应的”.dll 文件,如C Run Time(CRT)库。一个例子就是,写一个main(){},build出来并不是只有几个字节,当然有人会说那还有exe文件头呢?是,即使加上文件头的尺寸,build出的执行文件仍然“莫名的大”。实际上那多出来的部分就是CRT静态库。姑且可以把静态库.lib理解成外部程序的obj文件比较合理,它包含了函数的实现。
 25 下面再谈static linked DLL 和 dynamic linked DLL又如何?
 26 静态链接 (static linked DLL)从操作上在VC的Project|Settings...|Link (tab)|General (category)|Object/library modules 中设置和添加。比如要使用SDK中的PropertySheet() API, 就要在这里添加 comctl32.lib,然后再调用的源程序中#include <prsht.h> , 使用的地方直接调用PropertySheet()。当程序.exe启动时,系统会把对应comctl32.dll加载进来。作为DLL的静态引入库的.lib不包含函数的实现,只包含用于系统加载的信息,如对应的DLL名称,函数歧视地只在对应的DLL中的便宜等等。相比动态链接而言,静态链接是很简单的。
 27 动态链接是使用LoadLibrary()/GetProcessAddress()和FreeLibrary(),详见下面的例子。
 28 
 29 {
 30 typedef BOOL (WINAPI *LPFNSHELLEXECUTEEX)(LPSHELLEXECUTEINFO);
 31 
 32 hShell32Dll = LoadLibrary(TEXT( "SHELL32.DLL "));
 33 if (!hShell32Dll) { goto End; }
 34 
 35 lpfnShellExecuteEx = (LPFNSHELLEXECUTEEX)GetProcAddress(hShell32Dll,
 36 API_NAME(ShellExecuteEx));
 37 if (!lpfnShellExecuteEx) { goto End; }
 38 
 39 ...
 40 fOk = (*lpfnShellExecuteEx)(pShellExecuteInfo);
 41 ...
 42 End:
 43 if (hShell32Dll) {
 44 FreeLibrary(hShell32Dll);
 45 }
 46 lpfnShellExecuteEx = NULL;
 47 ...
 48 }
 49 有人会想,动态链接这样麻烦,为什么还要用呢?这里有一个技术问题,对这个问题的解决直接导致了动态加载的需求。问题是有些DLL只在某个Windows版本中存在,或某个API只在某些Windows版本中被加入指定的DLL。当你使用静态链接的.exe试图在不支持的Windows版本上运行时,系统会弹出系统对话框提示某某.dll无法加载或无法定位某某API的消息,然后就中止.exe的运行。像这样因为个别功能的实现依赖于某个DLL,当这个DLL不可用时导致整个.exe无法运行是不明智的。避免这样的结局只有用动态链接。
 50 . 静态链接库与动态链接库
 51 静态链接库(Static Library,简称LIB)与动态链接库(Dynamic Link Library,简称DLL)都是共享代码的方式。如果使用静态链接库(也称静态库),则无论你愿不愿意,.LIB文件中的指令都会被直接包含到最终生成的.EXE文件中。但是若使用.DLL文件,该.DLL文件中的代码不必被包含在最终的.EXE文件中,.EXE文件执行时可以“动态”地载入和卸载这个与.EXE文件独立的.DLL文件。
 52 2.1. 动态链接方式
 53 链接一个DLL有两种方式:
 54 2.1.1 载入时动态链接(Load-Time Dynamic Linking)
 55 使用载入时动态链接,调用模块可以像调用本模块中的函数一样直接使用导出函数名调用DLL中的函数。这需要在链接时将函数所在DLL的导入库链接到可执行文件中,导入库向系统提供了载入DLL时所需的信息及用于定位DLL函数的地址符号。(相当于注册,当作API函数来使用,其实API函数就存放在系统DLL当中。)
 56 2.1.2 运行时动态链接(Run-Time Dynamic Linking)
 57 使用运行时动态链接,运行时可以通过LoadLibrary或LoadLibraryEx函数载入DLL。DLL载入后,模块可以通过调用GetProcAddress获取DLL函数的入口地址,然后就可以通过返回的函数指针调用DLL中的函数了。如此即可避免导入库文件了。
 58 2.2. 二者优点及不足
 59 2.2.1 静态链接库的优点
 60 (1) 代码装载速度快,执行速度略比动态链接库快;
 61 (2) 只需保证在开发者的计算机中有正确的.LIB文件,在以二进制形式发布程序时不需考虑在用户的计算机上.LIB文件是否存在及版本问题,可避免DLL地狱等问题。
 62 2.2.2 动态链接库的优点
 63 (1) 更加节省内存并减少页面交换;
 64 (2) DLL文件与EXE文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性;
 65 (3) 不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数。
 66 2.2.3 不足之处
 67 (1) 使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费;
 68 (2) 使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行时动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败;
 69 (3) 使用动态链接库可能造成DLL地狱。
 70 2.3. DLL 地狱
 71 DLL 地狱(DLL Hell)是指因为系统文件被覆盖而让整个系统像是掉进了地狱。
 72 简单地讲,DLL地狱是指当多个应用程序试图共享一个公用组件时,如某个DLL或某个组件对象模型(COM)类,所引发的一系列问题。
 73 最典型的情况是,某个应用程序将要安装一个新版本的共享组件,而该组件与机器上的现有版本不向后兼容。虽然刚安装的应用程序运行正常,但原来依赖前一版本共享组件的应用程序也许已无法再工作。在某些情况下,问题的起因更加难以预料。比如,当用户浏览某些web站点时会同时下载某个Microsoft ActiveX控件。如果下载该控件,它将替换机器上原有的任何版本的控件。如果机器上的某个应用程序恰好使用该控件,则很可能也会停止工作。
 74 在许多情况下,用户需要很长时间才会发现应用程序已停止工作。结果往往很难记起是何时的机器变化影响到了该应用程序。
 75 这些问题的原因是应用程序不同组件的版本信息没有由系统记录或加强。而且,系统为某个应用程序所做的改变会影响机器上的所有应用程序—现在建立完全从变化中隔离出来的应用程序并不容易。
 76 三、 静态链接库的创建与使用
 77 在此通过一个实例来介绍静态库的创建与使用。在该实例中,我们将一个实现两整数相加求和的函数封装到静态库中供其他程序调用。
 78 1. 创建
 79 首先,使用Visual Studio 2008来创建一个带预编译头的静态库项目Static,该项目包含在名为Library的解决方案中。
 80 1.1. 创建静态库项目
 81 创建一个不带预编译头的静态链接库项目有以下几个步骤:
 82 (1) 单击菜单命令 “文件”-“新建”-“项目”,弹出“新建项目”对话框;
 83 (2) 在弹出的“新建项目”对话框中,选择左边“类别”列表中选择 “Visual C++”-“Win32”,在右边的“模版”中选择“Win32项目”;
 84 (3) 在下方输入项目名称“Static”,并选择项目创建的位置,勾选“生成解决方案”,并输入解决方案名称“Library”,然后点击“确定”按钮;
 85 (4) 点击两次“下一步”按钮,进入“应用程序设置”界面;
 86 (5) 在“应用程序设置”界面中,选择“静态库”,并确保下方“附加选项”中的“预编译头”被勾选,然后点击“完成”按钮。
 87 1.2. 编辑项目
 88 经过上面的步骤,初步创建了一个带预编译头的静态库项目,接下来编辑该项目以达到我们的创建静态库的目的。
 89 首先添加一个用于定义导出函数的源文件Static.cpp,编码实现两个整数相加的Add函数。源文件代码如下:
 90 #include “StdAfx.h”     // 标准头文件
 91 int Add(int a, int b)
 92 {
 93 return a + b;
 94 }
 95 接着点击菜单命令,“工具”-“生成Static”。如果一切顺利的话,就会在解决方案的“Debug”目录中生成了名为“Static.lib”的静态链接库。
 96 同时,需要给该静态链接库编写一个声明头文件Static.h,以便在链接时告知编译该链接库中的导出函数声明。Static.h中的代码很简单,只要声明一下Add函数就可以:
 97 #ifndef __STATIC_H__          // 防止该头文件重复引用
 98 #define __STATIC_H__
 99 int Add(int a, int b);             // 声明导出函数
100 #endif
101 接着点击菜单命令,“工具”-“生成Static”。如果一切顺利的话,就会在Library解决方案的Debug目录中生成了名为MyDLL.lib的静态链接库。
102 2. 使用
103 在Library解决方案下,再添加一个Win32控制台应用程序空项目UseLIB。程序主文件名为UseLIB.cpp,其中包含用于调用Add函数的程序入口函数main。将刚才创建的Static.lib及其声明头文件Static.h一同复制到UseLIB项目目录下。并在源文件UseLIB.cpp中使用预编译命令链接Static.lib(也可以在IDE的项目属性中设置链接器选项,或者只复制Static.h文件并设置UseLIB项目的“项目依赖项”为Static项目)。
104 源文件UseLIB.cpp中的代码如下:
105 #pragma comment(lib, “Static.lib”)         // 链接静态库Static.lib
106 #include <stdio.h>
107 #include “Static.h”     // 包含Static.lib的声明头文件,声明导出函数Add
108 int main(void)
109 {
110      int a = 1, b = 2;
111 printf(“%d+%d=%d\n”, a, b, Add(a, b));     // 调用Static.lib中的Add函数
112 return 0;
113 }
114 接下来点击菜单命令,“工具”-“生成UseLIB”。如果顺利的话,就会在Library解决方案的Debug目录中生成了名为UseLIB.exe的可执行执文件,运行UseLIB.exe,将在控制台中输出结果:1+2=3
115 3. 注意
116 由于项目中创建的源文件为.CPP文件,即C++源文件,因此Visual C++按C++规范,并采用__cdecl调用约定对其进行编译。这样得到的导出函数就不能被C语言程序所调用。解决该问题的办法是在函数体名称前添加extern “C”修饰,告诉编译器,该函数按照C语言规范,并采用__cdecl调用约定进行编译。因此源文件Add.cpp中的代码可修改如下:
117 extern “C” int add(int a, int b)
118 {
119 return a + b;
120 }
121 最后重新编译该静态链接库项目,导出函数Add就能够被C语言程序所调用了。
122 另一种不改变代码的方法是在“Static属性页”左边的列表中选择“配置属性”-“C/C++”-“高级”,然后在右边的“调用约定”选择“__cdecl (/Gd)”,“编译为”选择“编译为C++代码 (/TP)”。这种方法在不同的IDE上设置方法有所不同。
123 四、 动态链接库的创建与使用
124 在此同样通过一个实例来介绍动态链接库的创建与使用。在实例中,依然使用Add函数进行讲解,这样一方面可以沿用上面静态链接的有关内容,另一方面也可以了解动态链接库与静态链接库在创建和使用上的异同。
125 1. 创建
126 首先,在之前创建的Library解决方案中添加一个带预编译头的动态链接库项目,项目名称为Dynamic。使用不同IDE的朋友可以根据实际情况进行创建。
127 1.1. 创建动态链接库项目
128 创建一个带预编译头的动态链接库项目有以下几个步骤:
129 (1) 单击菜单命令 “文件”-“新建”-“项目”,弹出“新建项目”对话框;
130 (2) 在弹出的“新建项目”对话框中,选择左边列表的“Visual C++”–“Win32”,在右边的项目模版中选择“Win32项目”;
131 (3) 在下方输入项目名称“Dynamic”,并选择项目创建的位置,然后点击“确定”按钮;
132 (4) 点击两次“下一步”按钮,进入“应用程序设置”界面;
133 (5) 在“应用程序设置”界面中,选择“DLL”,然后点击“完成”按钮。
134 1.2. 编辑项目
135 Dynamic项目自动生成的dllmain.cpp源文件含有一个名为DllMain的函数,该函数是DLL被链接时的入口函数,它由系统自动调用,在这里我们不用去理会它。
136 与前面创建静态态链接库类似的,首先添加一个用于定义导出函数的源文件Dynamic.cpp,编码实现两个整数相加的Add函数。源文件代码如下:
137 extern “C” __declspec(dllexport) int Add(int a, int b)     // 声明为DLL导出函数
138 {
139 return a + b;
140 }
141 与前面静态链接库不同,在Add函数体名称前不只添加了extern “C”修饰,还多添加了一个 __declspec(dllexport)修饰。__declspec(dllexport)修饰的作用是告诉编译器,这个函数将作为导出函数,并在输入库中生成该函数的地址符号等信息,这样其他程序就可以使用载入时动态链接方式来调用该函数。另外,extern “C”在封装DLL还有另一个作用,就是告诉编译器,在DLL中的导出函数不要使用函数名修饰规则,这样在采用运行时动态链接时就可以直接使用原函数名来调用导出函数了。关于函数调用方式和导出方式的详细说明在后面还将提出,现在先撇开这些烦人的问题。
142 接着点击菜单命令,“工具”-“生成Dynamic”。如果一切顺利的话,就会在Library解决方案的Debug目录中生成了名为Dynamic.dll的动态链接库和名为Dynamic.lib的导入库(与静态链接库不同,只存放DLL的导出表,不包含代码)。
143 最后需要给该Dynamic.dll的输入库Dynamic.lib编写一个声明头文件Dynamic.h,以便在以后链接时告知编译器该链接库中的具体的导入内容(一般包括代码和资源)。Dynamic.h中的代码很简单,只要声明一下Add函数就可以:
144 #ifndef __DYNAMIC_H__     // 防止该头文件重复引用
145 #define __DYNAMIC_H__
146 __declspec(dllexport) int Add(int a, int b);             // 声明导出函数
147 #endif
148 2. 使用
149 在同Library解决方案中,添加一个名为UseDLL的Win32控制台应用程序空项目。程序主文件名为UseDLL.cpp,其中包含用于调用Add函数的程序入口函数main。一下使用两种动态链接方式来链接Dynamic.dll。
150 2.1. 载入时动态链接
151 载入时动态链接是一种轻松使用动态链接库的方法,它使得使用动态链接库如同使用静态链接库一样方便。将导入库Dynamic.lib及其声明头文件Dynamic.h一同复制到UseDLL项目目录下,并把Dynamic.dll复制到项目的Debug目录中。并在源文件UseDLL.cpp中使用预编译命令链接Dynamic.lib(也可以在IDE的项目属性中设置链接器选项,或者只复制Dynamic.h文件并设置UseDLL项目的“项目依赖项”为Dynamic项目)。
152 源文件UseDLL.cpp中的代码如下:
153 #pragma comment(lib, “Dynamic.lib”)      // 链接导入库Dynamic.lib
154 #include <stdio.h>
155 #include “Dynamic.h”     // 包含Dynamic.lib的声明头文件,提供导出函数Add的声明
156 int main(void)
157 {
158      int a = 1, b = 2;
159 printf(“%d+%d=%d\n”, a, b, Add(a, b));     // 调用Dynamic.DLL中的Add函数
160 getchar();                                 // 用于查看输出结果
161 return 0;
162 }
163 几乎跟使用静态链接库一样。接下来点击菜单命令,“工具”-“生成UseDLL”。如果一切顺利的话,就会在Library解决方案的Debug目录中生成了名为UseDLL.exe的可执行文件,运行UseDLL.exe文件,将在控制台中输出结果:1+2=3
164 2.2. 运行时动态链接
165 运行时动态链接的代码相对麻烦些,需要使用到Windows的三个API函数,还要进行一些判断以防止不必要的麻烦。我们在UseDLL项目的基础上做些修改来实现运行时动态链接。这里只需要把Dynamic.dll复制到UseDLL项目的Debug目录中,因为不用在编译的时候链接导入库,只要在运行根据需要链接Dynamic.dll。下面先给出修改后的源文件Dynamic.cpp的代码:
166 #include <windows.h>     // 用于声明window API函数及宏等
167 #include <stdio.h>
168 typedef int (* FuncAdd)(int a, int b);     // 定义将要调用的导出函数Add的指针类型
169 int main(void)
170 {
171      FuncAdd Add;     // 定义Add函数指针
172      int a = 1, b = 2;
173      HMODULE hDLL = LoadLibrary(TEXT("MyDLL.dll"));     // 载入DLL,并获取其句柄
174      if (hDLL)         // MyDLL.dll载入成功
175      {
176          Add = (FuncAdd)GetProcAddress(hDLL, "Add");     // 获取导出函数Add指针
177          if (Add)         // 正确获取Add函数指针
178          {
179              printf("%d+%d=%d\n", a, b, Add(a, b));     // 调用导出函数Add
180          }
181          else             // 没有找到Add函数
182          {
183              printf("Add Not Found!\n");
184          }
185      }
186      else         // MyDLL.dll载入失败
187      {
188          printf("LoadLibrary Failed!\n");
189      }
190      getchar();
191      FreeLibrary((TEXT("MyDLL.dll"));     // 释放DLL
192      return 0;
193 }
194 看到了吧,调用方法比较繁琐。由于没有链接导入库,不能使用地址符号定位导出函数的入口地址,只能通过GetProcAdress来获取其在地址空间中的指针,再通过指针调用。但程序在运行之前,GetProcAdress无法判断指针的有效性。因此,为了防止Dynamic项目中不存在Add函数而使程序在运行时出错,有必要在调用Add之前判断其函数指针的有效性。
195 最后,点击菜单命令,“工具”-“重新生成UseDLL”。如果一切顺利的话,就会在Library解决方案的Debug目录中生成了名为UseDLL.exe的可执行文件,运行UseDLL.exe文件,将在控制台中输出结果:1+2=3
196 五、 补充阅读
197 1. Visual Studio 2008中DLL项目只生成dll文件不生成lib文件的解决方案
198 在创建动态链接库项目时,如果在“应用程序设置”中只勾选“预编译头”,而没有勾选“空项目”或“导出符号”,那么在对该项目进行编译链接时将只会生成动态链接库dll文件,不生成导入库lib文件。此问题的解决办法如下:
199 (1) 右键单击该项目,在弹出的菜单中点击“添加”-“新建项”;
200 (2) 在弹出的“添加新项”对话框中,在左边“类别”列表中选择“Visual C++”-“代码”,在右边“模版”中选择“模块定义文件(.def)”;
201 (3) 在“名称”输入框中输入任意名称,这里使用该项目名称,如“MyDLL”,单击“确定”按钮;
202 (4) 重新生成该项目,即生成lib文件。
203 注意,此时在项目属性的配置列表“配置属性”-“链接器”-“输入”中的“模块定义文件”项目中将出现刚才创建的模块定义文件MyDLL.def。如果此前不是添加“新建项”,而是添加“现有项”,那么必须在此项目上填写该现有模块定义文件的文件名,否则将不会生成lib文件。
204 2. 动态链接库的应用举例
205 (1) 所有的Windows系统调用(Windows API函数)都是以动态链接库的形式提供的。我们在Windows目录下的system32文件夹中会看到kernel32.dll、user32.dll和gdi32.dll,windows的大多数API都包含在这些DLL中。kernel32.dll中的函数主要处理内存管理和进程调度;user32.dll中的函数主要控制用户界面;gdi32.dll中的函数则负责图形方面的操作。与这些动态库相对应的导入库分别为kernel32.lib、user32.lib和gdi32.lib。
206 (2) 软件的自动更新。Windows应用的开发者常常利用动态链接库来分发软件更新。他们生成一个动态库的新版本,然后用户可以下载,并用它替代当前的版本。当然,新、旧版本动态库的输出接口(即导出函数)必须一致。下一次用户运行应用程序时,应用将自动链接和加载新的动态库。
207 (3) 软件插件技术。许多Windows应用软件都支持插件扩展方式,如IE浏览器、Photoshop、Office等等。插件在本质上都是动态库。
208 (4) 可扩展的Web服务器。
209 (5) 每个Windows驱动程序在本质上都是动态链接库。
210 3. __declspec(dllexport)与 .def文件
21132 位编译器版本中,可以使用__declspec(dllexport)关键字从DLL导出数据、函数、类或类成员函数。__declspec(dllexport)将导出指令添加到对象文件(即obj文件),若要导出函数,__declspec(dllexport)关键字必须出现在调用约定关键字的左边(如果指定了关键字)。例如:
212 __declspec(dllexport) void __cdecl Function1(void);
213 若要导出类中的所有公共数据成员和成员函数,关键字必须出现在类名的左边,如下所示:
214 class __declspec(dllexport) CExampleExport : public CObject
215 { ... class definition ... };
216 生成DLL时,通常创建一个包含正在导出的函数原型和/或类的头文件,并将__declspec(dllexport)添加到头文件中的声明。
217 若要提高代码的可读性,请为__declspec(dllexport)定义一个宏并对正在导出的每个符号使用该宏:
218 #define Export __declspec(dllexport)
219 模块定义文件(.def)是包含一个或多个描述各种DLL属性的模块语句的文本文件。二者的目的都是将公共符号导入到应用程序中或从 DLL 导出函数。添加__declspec(dllexport)是为了提供不使用.def文件从 .EXE或 .DLL导出函数的简单方法。如果不使用__declspec (dllimport)或__declspec(dllexport)导出DLL函数,则DLL需要.def文件。
220 并不是任何时候选择添加__declspec(dllexport)而放弃.def的方式都是好的。如果DLL是提供给VC++用户使用的,只需要把编译DLL时产生的.lib提供给用户,它可以很轻松地调用你的DLL。但是如果DLL是供VB、PB、Delphi用户使用的,那么会产生一个小麻烦。因为VC++对于__declspec(dllexport) 声明的函数会进行名称转换,比如函数:
221 __declspec(dllexport) int __stdcall IsWinNT()
222 会转换为IsWinNT@0,这样你在VB中必须这样声明:
223 Declare Function IsWinNT Lib "my.dll" Alias "IsWinNT@0" () As Long
224 @的后面的数由于参数类型不同而可能不同。这显然不太方便。所以如果要想避免这种转换,就要使用.def文件方式。
225 4. 函数调用约定(Calling Convention)
226 函数调用约定不仅决定了发生函数调用时函数参数的入栈顺序,还决定了是由调用者函数还是被调用函数负责清除栈中的参数,还原堆栈。函数调用约定有很多方式,除了常见的__cdecl,__fastcall和__stdcall之外,C++的编译器还支持thiscall方式,不少C/C++编译器还支持naked call方式。这么多函数调用约定常常令许多程序员很迷惑,到底它们是怎么回事,都是在什么情况下使用呢?下面就分别介绍这几种函数调用约定。
227 4.1. 各种调用约定
228 4.1.1 __cdecl
229 编译器的命令行参数是/Gd,__cdecl是C Declaration的缩写,是C和C++程序的缺省调用方式。所有参数从右到左依次入栈,这些参数由调用者清除,每一个调用它的函数都包含清空堆栈的代码,称为手动清栈。所以产生的可执行文件大小会比调用__stdcall函数的大。被调用函数无需要求调用者传递多少参数,调用者传递过多或者过少的参数,甚至完全不同的参数都不会产生编译阶段的错误。所有非C++成员函数和那些没有用__stdcall或__fastcall声明的函数都默认是__cdecl方式,它使用C函数调用方式。
230 4.1.2 __stdcall
231 __stdcall是Standard Call的缩写,是Pascal程序的缺省调用方式,通常用于Win32 API中,是WIN32 API的标准调用方式:所有参数从右到左依次入栈,如果是调用类成员的话,最后一个入栈的是this指针。这些堆栈中的参数由被调用的函数在返回后清除,使用的指令是 retn X,X表示参数占用的字节数,CPU在ret之后自动弹出X个字节的堆栈空间。称为自动清栈。函数在编译的时候就必须确定参数个数,并且调用者必须严格的控制参数的生成,不能多,不能少,否则返回后会出错。
232 4.1.3 __fastcall
233 __fastcall 是编译器指定的快速调用方式。由于大多数的函数参数个数很少,使用堆栈传递比较费时。因此__fastcall通常规定将前两个(或若干个)参数由寄存器传递,其余参数还是通过堆栈传递。不同编译器编译的程序规定的寄存器不同。返回方式和__stdcall相当。
234 4.1.4 __thiscall
235 __thiscall 是为了解决类成员调用中this指针传递而规定的。__thiscall要求把this指针放在特定寄存器中,该寄存器由编译器决定。VC使用ecx,Borland的C++编译器使用eax。返回方式和__stdcall相当。
236 4.1.5 naked call
237 采用前面几种函数调用约定的函数,编译器会在必要的时候自动在函数开始添加保存ESI,EDI,EBX,EBP寄存器的代码,在退出函数时恢复这些寄存器的内容,使用naked call方式声明的函数不会添加这样的代码,这也就是为什么称其为naked的原因吧。naked call不是类型修饰符,故必须和_declspec共同使用。
238 4.2. 综述
239 __fastcall 和 __thiscall涉及的寄存器由编译器决定,因此不能用作跨编译器的接口。所以Windows上的COM对象接口都定义为__stdcall调用方式。
240 带有可变参数的函数必须且只能使用__cdecl方式,例如下面的函数:
241 int printf(char * fmtStr, ...);
242 int scanf(char * fmtStr, ...);
243 __stdcall和__cdecl这两个关键字看起来似乎很少和我们打交道,但是看了下面的定义(来自windef.h),你一定会觉得惊讶:
244 #define CALLBACK __stdcall
245 #define WINAPI __stdcall
246 #define WINAPIV __cdecl
247 #define APIENTRY WINAPI
248 #define APIPRIVATE __stdcall
249 #define PASCAL __stdcall
250 #define cdecl _cdecl
251 #ifndef CDECL
252 #define CDECL _cdecl
253 #endif
254 几乎我们写的每一个WINDOWS API函数都是__stdcall类型的,为什么?
255 首先,我们谈一下两者之间的区别:
256 WINDOWS的函数调用时需要用到栈。当函数调用完成后,栈需要清除,这里就是问题的关键,如何清除?
257 如果我们的函数使用了__cdecl,那么栈的清除工作是由调用者,用COM的术语来讲就是客户来完成的。这样带来了一个棘手的问题,不同的编译器产生栈的方式不尽相同,那么调用者能否正常的完成清除工作呢?答案是不能。
258 如果使用__stdcall,上面的问题就解决了,函数自己解决清除工作。所以,在跨开发平台的调用中,我们都使用__stdcall(虽然有时是以WINAPI的样子出现),如JNI。
259 那么为什么还需要__cdecl呢?当我们遇到这样的函数如fprintf()它的参数是可变的,不定长的,被调用者事先无法知道参数的长度(如typedef int (*MYPROC)(LPTSTR, ...);),事后的清除工作也无法正常的进行,因此,这种情况我们只能使用__cdecl。
260 到这里我们有一个结论,如果你的程序中没有涉及可变参数,最好使用__stdcall关键字。
261 5. 函数名字修饰(Decorated Name)规则
262 函数的名字修饰(Decorated Name)就是编译器在编译期间创建的一个字符串,用来指明函数的定义或原型。LINK程序或其他工具有时需要指定函数的名字修饰来定位函数的正确位置。多数情况下程序员并不需要知道函数的名字修饰,LINK程序或其他工具会自动区分他们。当然,在某些情况下需要指定函数的名字修饰,例如在C++程序中,为了让LINK程序或其他工具能够匹配到正确的函数名字,就必须为重载函数和一些特殊的函数(如构造函数和析构函数)指定名字装饰。另一种需要指定函数的名字修饰的情况是在汇编程序中调用C或C++的函数。如果函数名字,调用约定,返回值类型或函数参数有任何改变,原来的名字修饰就不再有效,必须指定新的名字修饰。C和C++程序的函数在内部使用不同的名字修饰方式,下面将分别介绍这两种方式。
263 5.1. C编译器的函数名修饰规则 
264 对于__stdcall调用约定,编译器和链接器会在输出函数名前加上一个下划线前缀,函数名后面加上一个“@”符号和其参数的字节数,例如_functionname@number。__cdecl调用约定仅在输出函数名前加上一个下划线前缀,例如_functionname。__fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,例如@functionname@number。
265 5.2. C++编译器的函数名修饰规则
266 C++的函数名修饰规则有些复杂,但是信息更充分,通过分析修饰名不仅能够知道函数的调用方式,返回值类型,参数个数甚至参数类型。不管__cdecl,__fastcall还是__stdcall调用方式,函数修饰都是以一个“?”开始,后面紧跟函数的名字,再后面是参数表的开始标识和按照参数类型代号拼出的参数表。对于__stdcall方式,参数表的开始标识是“@@YG”,对于__cdecl方式则是“@@YA”,对于__fastcall方式则是“@@YI”。参数表的拼写代号如下所示:
267 代号     类型
268 X          void
269 D          char
270 E          unsigned char
271 F          short
272 H          int
273 I          unsigned int
274 J          long
275 K          unsigned long
276 M          float
277 N          double
278 _N          bool
279 O          long double
280 PA         指针前缀
281 AA         引用前缀
282 V类名@@     类
283 指针的方式有些特别,用PA表示指针,用PB表示const类型的指针,如果是引用,则在类型代号前加上AA。后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复。如果相同类型的引用连续出现,则以“1”代替,每个“1”都代表一次重复。U表示结构类型,通常后跟结构体的类型名,用“@@”表示结构类型名的结束。函数的返回值不作特殊处理,它的描述方式和函数参数一样,紧跟着参数表的开始标志,也就是说,函数参数表的第一项实际上是表示函数的返回值类型。参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。下面举三个个例子。
284 函数声明:int Function1(char *var1,unsigned long);
285 函数修饰:?Function1@@YGHPADK@Z
286 函数声明:void Function2();
287 函数修饰:?Function2@@YGXXZ
288 函数原型(Test为自定义类):
289 void abc(int a, long b, char* c, char* d, bool &e, Test f, short g);
290 函数修饰名:?abc@@YAXHJPAD0AA_NVTest@@F@Z
291 对于C++的类成员函数(其调用方式是thiscall),函数的名字修饰与非成员的C++函数稍有不同,首先就是在函数名字和参数表之间插入以“@”字符引导的类名;其次是参数表的开始标识不同,公有(public)成员函数的标识是“@@QAE”,保护(protected)成员函数的标识是“@@IAE”,私有(private)成员函数的标识是“@@AAE”,如果函数声明使用了const关键字,则相应的标识应分别为“@@QBE”,“@@IBE”和“@@ABE”。如果参数类型是类实例的引用,则使用“AAV1”,对于const类型的引用,则使用“ABV1”。下面就以类CTest为例说明C++成员函数的名字修饰规则:
292 class CTest
293 {
294 ......
295 private:
296 void Function(int);
297 protected:
298 void CopyInfo(const CTest &src);
299 public:
300 long DrawText(HDC hdc, long pos, const TCHAR* text, RGBQUAD color, BYTE bUnder, bool bSet);
301 long InsightClass(DWORD dwClass) const;
302 ......
303 };
304 对于成员函数Function,其函数修饰名为“?Function@CTest@@AAEXH@Z”,字符串“@@AAE”表示这是一个私有函数。成员函数CopyInfo只有一个参数,是对类CTest的const引用参数,其函数修饰名为“?CopyInfo@CTest@@IAEXABV1@@Z”。DrawText是一个比较复杂的函数声明,不仅有字符串参数,还有结构体参数和HDC句柄参数,需要指出的是HDC实际上是一个HDC__结构类型的指针,这个参数的表示就是“PAUHDC__@@”,其完整的函数修饰名为“?DrawText@CTest@@QAEJPAUHDC__@@JPBDUtagRGBQUAD@@E_N@Z”。InsightClass是一个共有的const函数,它的成员函数标识是“@@QBE”,完整的修饰名就是“?InsightClass@CTest@@QBEJK@Z”。
305 无论是C函数名修饰方式还是C++函数名修饰方式均不改变输出函数名中的字符大小写,这和PASCAL调用约定不同,PASCAL约定输出的函数名无任何修饰且全部大写。
306 extern “C”使得其作用的函数采用C名字修饰方式进行编译。不要在C程序(源程序文件以.c作为后缀)中使用extern “C”,否则会出现错误。因为C编译器不认识extern “C”。
307 5.3. 查看函数的名字修饰
308 有两种方式可以检查你的程序中的函数的名字修饰:使用编译输出列表或使用Dumpbin工具。使用/FAc,/FAs或/FAcs命令行参数可以让编译器输出函数或变量名字列表。使用dumpbin.exe /SYMBOLS命令也可以获得obj文件或lib文件中的函数或变量名字列表。此外,还可以使用 undname.exe 将修饰名转换为未修饰形式。
309 使用动态DLL有两种方法,一种是隐式链接,一种是显式链接,如果用loadlibrary就是显示链接,用lib就属于隐式链接。
310 两种方法对于你的程序调用动态库时没有任何区别,只是你在编程时,步骤是不一样的。显式调用麻烦了点,但可以没有相应的lib库;隐式调用,使用起来比较简单,有函数的声明就可以了,但必须有lib库。
311 隐式加载默认是加载到内存中的,始终占用内存。
312 显示加载,你加载时占用内存,释放了就不占用内存了。如果该DLL已经载入,loadlibrary只是会增加一个引用计数,相同,freelibrary也只是减少引用计数,如果引用计数为0时,DLL才从内存中移除。
313 显式和隐式只是对于代码编写时来说的,最后产生的可执行程序,不管是显式和隐式,都是用loadlibrary载入的。显式与隐式不是用在这些方面的,显式加载适合需要动态的选 用DLL的情况。
314 在VC中两种方式的具体方法:
315 一、动态库的隐示调用:
316 在 VC 工程中直接链接静态输入库XXX.lib,然后即可像调用其它源文件中
317 的函数一样调用DLL中的函数了。
318 二、动态库的显式调用:
319 显式调用动态库步骤:
320 1、创建一个函数指针,其指针数据类型要与调用的 DLL 引出函数相吻
321 合。
322 2、通过 Win32 API 函数LoadLibrary()显式的调用DLL,此函数返回
323 DLL 的实例句柄。
324 3、通过 Win32 API 函数GetProcAddress()获取要调用的DLL 的函数地
325 址,把结果赋给自定义函数的指针类型。
326 4、使用函数指针来调用 DLL 函数。
327 5、最后调用完成后,通过 Win32 API 函数FreeLibrary()释放DLL 函数。
328 动态链接库的加载方式: 
329 1)隐式链接 
330 使用lib引入库和相关头文件,dll文件配合使用。 2)显示加载 
331 只是使用dll文件即可。 显示加载的使用方法:
332  void CDlltextDlg::OnAdd() 
333  {  // TODO: Add your control notification handler code here  
334     HINSTANCE hnst=LoadLibrary("dll2");//得到动态链接库的句柄 
335     typedef int (*ADDPROC)(int a,int b);//定义函数指针类型 
336    //用函数指针变量来调用函数 int *add(int a,int b)表示函数返回指针值 
337    ADDPROC Add=(ADDPROC)GetProcAddress(hnst,"add");//得到动态链接库中add导出函数的地址 
338    if(!Add)  {   MessageBox("获得函数地址失败!");  
339     return;  } 
340     CString str; 
341     str.Format("5+3=%d",Add(5,3)); 
342     MessageBox(str); 
343     FreeLibrary(hnst);//释放动态链接库 
344 } 
345 GetProcAddress也可以采用函数序号的方式来进行调用,不过最好是用函数名来获取函数地址。 
346 ADDPROC Add=(ADDPROC)GetProcAddress(hnst,MAKEINTRESOURCE(1) ); 
347 //得到动态链接库中add导出函数的地址,第1个函数表示为add函数  
348 FreeLibrary减少加载动态链接库的引用计数,当引用计数为0的时候,该模块将从调用进程的地址空间中被卸载。句柄不再有效。 
349  动态链接库的好处:在需要的时候加载动态链接库某个函数。 
350 隐式链接的缺点:使用比较简单,在程序的其他部分可以任意使用函数,但是当程序访问十来个dll动态链接库的时候,此时如果都使用隐式链接的时候,启动此程序的时候,这十来个动态链接库都需要加载到内存,映射到内存的地址空间,这就会加大进程的启动时间,而且程序运行过程中,只是在某个条件下使用某个函数,如果使用隐式链接会造成资源的浪费。这样需要采用动态加载的方式。
View Code

34、delete 和delete[]

1 当用delete来释放用new int[]申请的内存空间时,由于其为基本数据类型没有析构函数,所以使用delete与delete []相同,
2 A *a = new A[10];
3 delete a; //仅释放了a指针指向的全部内存空间 但是只调用了a[0]对象的析构函数 剩下的从a[1]到a[9]这9个用户自行分配的m_cBuffer对应内存空间将不能释放
4 从而造成内存泄漏
View Code

35、main函数为什么返回0,返回0代表什么,成功返回后程序还会走其他函数吗;

 1 return返回的数值由程序的作者自定。返回不同的值可以代表不同的含义,一般是代表出错的原因,传统上返回0代表程序正常结束。
 2 main 函数的返回值用于说明程序的退出状态。如果返回 0,则代表程序正常退出,否则代表程序异常退出。
 3 成功返回后程序还会走其他函数如:
 4 借助C库函数atexit()。利用atexit()函数可以在程序终止前完成一些“清理”工作——如果将指向一组函数的指针传递给atexit()函数,那么在程序退出main()函数后(此时程序还未终止)就能自动调用这组函数。在使用atexit()函数时你要注意这样两点:
 5 第一: 由atexit()函数指定的要在程序终止前执行的函数要用关键字void说明,并且不能带参数;
 6 第二: 由atexit()函数指定的函数在入栈时的顺序和调用atexit()函数的顺序相反,即它们在执行时遵循后进先出(lifo)的原则。
 7  
 8 #include<stdlib.h>
 9 #include<stdio.h>
10  
11 void my_exit1(void)
12 {
13     printf("my_exit1() function !/n");
14 }                
15  
16 void my_exit2(void)
17 {
18     printf("my_exit2() function !/n");
19 } 
20  
21 void  main()
22 {
23     atexit ( my_exit1 );
24     atexit ( my_exit2 );
25     printf("now, eixt this program.../n");
26 }
27 输出结果为:
28 now, eixt this program...
29 my_exit2() function !
30 my_exit1() function !
31 可以用_onexit 注册一个函数,它会在main 之后执行int fn1(void), fn2(void), fn3(void), fn4 (void);
32 void main( void )
33 {
34 String str("zhanglin");
35 _onexit( fn1 );
36 _onexit( fn2 );
37 _onexit( fn3 );
38 _onexit( fn4 );
39 printf( "This is executed first.\n" );
40 }
41 int fn1()
42 {
43 printf( "next.\n" );
44 return 0;
45 }
46 int fn2()
47 {
48 printf( "executed " );
49 return 0;
50 }
51 int fn3()
52 {
53 printf( "is " );
54 return 0;
55 }
56 int fn4()
57 {
58 printf( "This " );
59 return 0;
60 }
View Code

 36、map hashmap区别

1 4.1 hash_map和map的区别在哪里? 
2 构造函数。hash_map需要hash函数,等于函数;map只需要比较函数(小于函数).
3 存储结构。hash_map采用hash表存储,map一般采用红黑树(RB Tree)实现。因此其memory数据结构是不一样的。 
4 4.2 什么时候需要用hash_map,什么时候需要用map?
5 总 体来说,hash_map 查找速度会比map快,而且查找速度基本和数据量大小无关,属于常数级别;而map的查找速度是log(n)级别。并不一定常数就比log(n) 小,hash还有hash函数的耗时,明白了吧,如果你考虑效率,特别是在元素达到一定数量级时,考虑考虑hash_map。但若你对内存使用特别严格,希望程序尽可能少消耗内存,那么一定要小心,hash_map可能会让你陷入尴尬,特别是当你的hash_map对象特别多时,你就更无法控制了,而且 hash_map的构造速度较慢。 
6 现在知道如何选择了吗?权衡三个因素: 查找速度, 数据量, 内存使用。
View Code

37、Visual Stutio上的两种形式debug和release知道吗,用过吗,他们的区别是什么;

 1 1、Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。
 2 
 3 2、Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好地使用。
 4 
 5 3、debug程序通常比release程序要慢,尤其是处理视频方面release要比debug快很多。
 6 
 7 4、只有DEBUG版的程序才能设置断点、单步执行、使用 TRACE/ASSERT等调试输出语句。REALEASE不包含任何调试信息
 8 
 9 5、在Debug模式和Release模式下调试,Debug模式消耗更多的内存,所以运行较慢
10 
11 在实际情况中,应该把在Release模式下生成的应用程序部署到服务器上
12 
13 
14 本文转载自柯乐义http://keleyi.com/a/bjad/sueodavb.htmDebug 版本
15 参数 含义 
16 /MDd /MLd 或 /MTd 使用 Debug runtime library (调试版本的运行时刻函数库) 
17 /Od 关闭优化开关 
18 /D "_DEBUG" 相当于 #define _DEBUG,打开编译调试代码开关 (主要针对assert函数) 
19 /ZI 创建 Edit and continue(编辑继续)数据库,这样在调试过程中如果修改了源代码不需重新编译
20 /gz 可以帮助捕获内存错误 
21 /Gm 打开最小化重链接开关, 减少链接时间 
22  
23 Release 版本
24 参数 含义 
25 /MD /ML 或 /MT 使用发布版本的运行时刻函数库 
26 /O1 或 /O2 优化开关,使程序最小或最快 
27 /D "NDEBUG" 关闭条件编译调试代码开关 (即不编译assert函数) 
28 /gf 合并重复的字符串, 并将字符串常量放到只读内存, 防止被修改 
29  
30 实际上,Debug 和 Release 并没有本质的界限,他们只是一组编译选项的集合,编译器只是按照预定的选项行动。事实上,我们甚至可以修改这些选项,从而得到优化过的调试版本或是带跟踪语句的发布版本。
View Code

38、设计模式设计原则

 1 设计模式分为三种类型,共23种。
 2 创建型模式:单例模式、抽象工厂模式、建造者模式、工厂模式、原型模式。
 3 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式。
 4 行为型模式:模版方法模式、命令模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式、状态模式、策略模式、职责链模式、访问者模式。
 5 Abstract Factory(抽象工厂模式):提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。
 6 Adapter(适配器模式):将一个类的接口转换成客户希望的另外一个接口。Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
 7 Bridge(桥接模式):将抽象部分与它的实现部分分离,使它们都可以独立地变化。
 8 Builder(建造者模式):将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
 9 Chain of Responsibility(职责链模式):为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。
10 Command(命令模式):将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。
11 Composite(组合模式):将对象组合成树形结构以表示“部分-整体”的层次结构。它使得客户对单个对象和复合对象的使用具有一致性。
12 Decorator(装饰模式):动态地给一个对象添加一些额外的职责。就扩展功能而言, 它比生成子类方式更为灵活。
13 Facade(外观模式):为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。
14 Factory Method(工厂模式):定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。
15 Flyweight(享元模式):运用共享技术有效地支持大量细粒度的对象。
16 Interpreter(解析器模式):给定一个语言, 定义它的文法的一种表示,并定义一个解释器, 该解释器使用该表示来解释语言中的句子。
17 Iterator(迭代器模式):提供一种方法顺序访问一个聚合对象中各个元素,而又不需暴露该对象的内部表示。
18 Mediator(中介模式):用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。
19 Memento(备忘录模式):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。
20 Observer(观察者模式):定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。
21 Prototype(原型模式):用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。
22 Proxy(代理模式):为其他对象提供一个代理以控制对这个对象的访问。
23 Singleton(单例模式):保证一个类仅有一个实例,并提供一个访问它的全局访问点。 单例模式是最简单的设计模式之一,但是对于Java的开发者来说,它却有很多缺陷。在九月的专栏中,David Geary探讨了单例模式以及在面对多线程(multi-threading)、类装载器(class loaders)和序列化(serialization)时如何处理这些缺陷。
24 State(状态模式):允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。
25 Strategy(策略模式):定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。
26 Template Method(模板方法模式):定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。
27 Visitor(访问者模式):表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
28 设计原则:为什么要提倡“Design Pattern呢?根本原因是为了代码复用,增加可维护性。那么怎么才能实现代码复用呢?面向对象有几个原则:开闭原则(Open Closed Principle,OCP)、里氏代换原则(Liskov Substitution Principle,LSP)、依赖倒转原则(Dependency Inversion Principle,DIP)、接口隔离原则(Interface Segregation Principle,ISP)、合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)、最小知识原则(Principle of Least Knowledge,PLK,也叫迪米特法则)。
29 开闭原则具有理想主义的色彩,它是面向对象设计的终极目标。其他几条,则可以看做是开闭原则的实现方法。
30 设计模式就是实现了这些原则,从而达到了代码复用、增加可维护性的目的。
31 开闭原则
32 此原则是由Bertrand Meyer提出的。原文是:“Software entities should be open for extension,but closed for modification”。就是说模块应对扩展开放,而对修改关闭。模块应尽量在不修改原(是“原”,指原来的代码)代码的情况下进行扩展。那么怎么扩展呢?我们看工厂模式“factory pattern”:假设中关村有一个卖盗版盘和毛片的小子,我们给他设计一“光盘销售管理软件”。我们应该先设计一“光盘”接口。如图:
33 [pre]
34 ______________
35 |<>|
36 | 光盘 |
37 |_____________|
38 |+卖() |
39 | |
40 |_____________|
41 [/pre]
42 而盗版盘和毛片是其子类。小子通过“DiscFactory”来管理这些光盘。代码为:
43 ?
44 1
45 2
46 3
47 4
48 5
49 6    public class DiscFactory{
50 public static 光盘
51 getDisc(Stringname){
52 return(光盘)Class.forName(name).newInstance();
53 }
54 }
55 有人要买盗版盘,怎么实现呢?
56 ?
57 1
58 2
59 3
60 4
61 5
62 6    public class 小子{
63 public static void main(String[] args){
64 光盘 d = DiscFactory.getDisc("盗版盘");
65 d.卖();
66 }
67 }
68 如果有一天,这小子良心发现了,开始卖正版软件。没关系,我们只要再创建一个“光盘”的子类“正版软件”就可以了,不需要修改原结构和代码。怎么样?对扩展开放,对修改关闭——“开闭原则”。
69 工厂模式是对具体产品进行扩展,有的项目可能需要更多的扩展性,要对这个“工厂”也进行扩展,那就成了“抽象工厂模式”。
70 里氏代换原则
71 里氏代换原则是由Barbara Liskov提出的。如果调用的是父类的话,那么换成子类也完全可以运行。比如:
72 ?
73 1
74 2    光盘 d = new 盗版盘();
75 d.卖();
76 要将“盗版盘”类改为“毛片”类,没问题,完全可以运行。Java编译程序会检查程序是否符合里氏代换原则。还记得java继承的一个原则吗?子类override方法的访问权限不能小于父类对应方法的访问权限。比如“光盘”中的方法“卖”访问权限是“public”,那么“盗版盘”和“毛片”中的“卖”方法就不能是protected或private,编译不能通过。为什么要这样呢?你想啊:如果“盗版盘”的“卖”方法是private。那么下面这段代码就不能执行了:
77 ?
78 1
79 2    光盘 d = new 盗版盘();
80 d.卖();
81 可以说:里氏代换原则是继承复用的一个基础。
82 依赖倒转原则
83 抽象不应该依赖于细节,细节应当依赖于抽象。
84 要针对接口编程,而不是针对实现编程。
85 传递参数,或者在组合聚合关系中,尽量引用层次高的类。
86 主要是在构造对象时可以动态的创建各种具体对象,当然如果一些具体类比较稳定,就不必在弄一个抽象类做它的父类,这样有画蛇添足的感觉
87 接口隔离原则
88 定制服务的例子,每一个接口应该是一种角色,不多不少,不干不该干的事,该干的事都要干。
89 合成/聚合复用
90 合成/聚合复用原则(Composite/Aggregate Reuse Principle,CARP)经常又叫做合成复用原则。合成/聚合复用原则就是在一个新的对象里面使用一些已有的对象,使之成为新对象的一部分;新的对象通过向这些对象的委派达到复用已有功能的目的。它的设计原则是:要尽量使用合成/聚合,尽量不要使用继承。
91 就是说要少用继承,多用合成关系来实现。我曾经这样写过程序:有几个类要与数据库打交道,就写了一个数据库操作的类,然后别的跟数据库打交道的类都继承这个。结果后来,我修改了数据库操作类的一个方法,各个类都需要改动。“牵一发而动全身”!面向对象是要把波动限制在尽量小的范围。
92 在Java中,应尽量针对Interface编程,而非实现类。这样,更换子类不会影响调用它方法的代码。要让各个类尽可能少的跟别人联系,“不要与陌生人说话”。这样,城门失火,才不至于殃及池鱼。扩展性和维护性才能提高。
93 理解了这些原则,再看设计模式,只是在具体问题上怎么实现这些原则而已。张无忌学太极拳,忘记了所有招式,打倒了“玄冥二老”,所谓“心中无招”。设计模式可谓招数,如果先学通了各种模式,又忘掉了所有模式而随心所欲,可谓OO(Object-Oriented,面向对象)之最高境界。
94 最少知识原则
95 也叫迪米特法则。不要和陌生人说话,即一个对象应对其他对象有尽可能少的了解。
View Code

39、不适用> < 实现max  max = (a + b + abs(a -b) ) / 2

 1 int abs(int x)
 2 {
 3 return (((x>>31)&1)?(~x+1):x) //?:不算是if语句吧
 4 }
 5 解释一下
 6 x>>31位,将符号位移到最右端(当前的int还是32位的....以前16,以后64或更高...)
 7 考虑到如果是负数的话左边会补1而不是补0
 8 屏蔽一下
 9 (x>>32)&0x00000001
10 也就是(x>>32)&1
11 在C++里面,0为false,非0为true
12 当有符号(负数)时,条件为真,则应计算绝对值了,否则直接返回
13 对负数求相反数
14 首先要知道负数在计算机内部的表示是补码表示的
15 关于补码的知识,请查相关书籍,计算机相关课程应该介绍过了
16 取反+1得补码得相反数
17 (~x+1)
18 好了,将abs(x)替换到原式((a+b)+abs(a-b))/2
19 同时将a+b等都加上括号
20 得到最终得结果
21 #define max(a,b) (((a)+(b))+(((((a)-(b))>>31)&1)?(~((a)-(b))+1):((a)-(b))))/2
22 
23 验证后,结果正确
View Code

40、extern  auto regist auto 区别

1 一、简要性比较
2 extern 外部变量声明,是指这是一个已在别的地方定义过的对象,这里只是对变量的一次重复引用,不会产生新的变量。 
3 static 静态数据,数据存放在全局数据区,但作用域只是本 文件/函数 中,所以你可以在两个不同的文件/函数内部申明同名的static变量,但是 它们是两个不同的全局变量。 如果是定义在函数内,那么该对象具有无链接,函数外不能对其访问。如果是定义在函数外,那么该对象具 有内部链接,其它程序文件不能对其访问
4 auto 普通局部栈变量,是自动存储,这种对象会自动创建和销毁 ,建议这个变量要放在堆栈上面,调用函数时分配内存,函数结束时释放内 存。一般隐藏auto默认为自动存储类别。我们程序都变量大多是自动变量。
5 register 寄存器变量,请求编译器将这个变量保存在CPU的寄存器中,从而加快程序的运行.
6 系统的寄存器是有限制的,声明变量时如:register int i.这种存储类型可以用于频繁使用的变量。
View Code

 41、位运算

 1 56.位运算,都有什么,怎么用的?
 2 一.逻辑运算符 
 3 1.& 位与运算 
 4 1) 运算规则 
 5 位与运算的实质是将参与运算的两个数据,按对应的二进制数逐位进行逻辑与运算。例如:int型常量4和7进行位与运算的运算过程如下:
 6 4=0000 0000 0000 0100 &7 =0000 0000 0000 0111= 0000 0000 0000 0100
 7 对于负数,按其补码进行运算。例如:例如:int型常量-4和7进行位与运算的运算过程如下: -4=1111 1111 1111 1100 &7 =0000 0000 0000 0111= 0000 0000 0000 0100
 8 2) 典型应用 
 9 (1) 清零 
10 清零:快速对某一段数据单元的数据清零,即将其全部的二进制位为0。例如整型数a=321对其全部数据清零的操作为a=a&0x0321=0000 0001 0100 0001 &0=0000 0000 0000 0000
11 = 0000 0000 0000 0000
12 (2) 获取一个数据的指定位 
13 获取一个数据的指定位。例如获得整型数a=的低八位数据的操作为a=a&0xFF321=
14 0000 0001 0100 0001 & 0xFF =0000 0000 1111 11111
15 = 0000 0000 0100 0001
16 获得整型数a=的高八位数据的操作为a=a&0xFF00。==a&0XFF00==
17 321=0000 0001 0100 0001 & 0XFF00=1111 1111 0000 0000
18 = 0000 0001 0000 0000
19 (3)保留数据区的特定位 
20 保留数据区的特定位。例如获得整型数a=的第7-8位(从0开始)位的数据操作为: 110000000
21 321=0000 0001 0100 0001 & 384=0000 0001 1000 0000
22 =0000 0001 0000 0000
23 2. | 位或运算 
24 1) 运算规则 
25 位或运算的实质是将参与运算的两个数据,按对应的二进制数逐位进行逻辑或运算。例如:int型常量5和7进行位或运算的表达式为5|7,结果如下:5= 0000 0000 0000 0101
26 | 7= 0000 0000 0000 0111=0000 0000 0000 0111
27 2) 主要用途 
28 (1) 设定一个数据的指定位。例如整型数a=321,将其低八位数据置为1的操作为a=a|0XFF321= 0000 0001 0100 0001 | 0000 0000 1111 1111=0000 0000 1111 1111 
29 逻辑运算符||与位或运算符|的区别 
30 条件“或”运算符 (||) 执行 bool 操作数的逻辑“或”运算,但仅在必要时才计算第二个操作数。 x || y , x | y 不同的是,如果 x 为 true,则不计算 y(因为不论 y 为何值,“或”操作的结果都为 true)。这被称作为“短路”计算。
31 3. ^ 位异或 
32 1) 运算规则 
33 位异或运算的实质是将参与运算的两个数据,按对应的二进制数逐位进行逻辑异或运算。只有当对应位的二进制数互斥的时候,对应位的结果才为真。例如:int型常量5和7进行位异或运算的表达式为5^7,结果如下:5=0000 0000 0000 0101^7=0000 0000 0000 0111 
34 = 0000 0000 0000 0010
35 2) 典型应用 
36 (1)定位翻转 
37 定位翻转:设定一个数据的指定位,将1换为0,0换为1。例如整型数a=321,,将其低八位数据进行翻位的操作为a=a^0XFF; 
38 (2)数值交换 
39 数值交换。例如a=3,b=4。在例11-1中,无须引入第三个变量,利用位运算即可实现数据交换。以下的操作可以实现a,b两个数据的交换:
40 a=a^b;
41 b=b^a;
42 a=a^b;
43 4.~ 位非 
44 位非运算的实质是将参与运算的两个数据,按对应的二进制数逐位进行逻辑非运算。
45 
46 二.位移运算符
47     
48 1.位左移
49 左移运算的实质是将对应的数据的二进制值逐位左移若干位,并在空出的位置上填0,最高位溢出并舍弃。例如int a,b;
50 a=5;
51 b=a<<2;
52 则b=20,分析过程如下:
53 (a)10=(5)10=(0000 0000 0000 0101)2
54 b=a<<2;
55 b=(0000 0000 0001 0100)2=(20)10
56 从上例可以看出位运算可以实现二倍乘运算。由于位移操作的运算速度比乘法的运算速度高很多。因此在处理数据的乘法运算的时,采用位移运算可以获得较快的速度。
57 提示 将所有对2的乘法运算转换为位移运算,可提高程序的运行效率
58 2.位右移    
59 位右移运算的实质是将对应的数据的二进制值逐位右移若干位,并舍弃出界的数字。如果当前的数为无符号数,高位补零。例如:
60 int (a)10=(5)10=(0000 0000 0000 0101)2
61 b=a>>2;
62 b=(0000 0000 0000 0001)2=(1)10
63 如果当前的数据为有符号数,在进行右移的时候,根据符号位决定左边补0还是补1。如果符号位为0,则左边补0;但是如果符号位为1,则根据不同的计算机系统,可能有不同的处理方式。可以看出位右移运算,可以实现对除数为2的整除运算。
64 提示 将所有对2的整除运算转换为位移运算,可提高程序的运行效率    
65 3.复合的位运算符
66 在C语言中还提供复合的位运算符,如下:
67 &=、!=、>>=、<<=和^=
68 例如:a&=0x11等价于 a= a&0x11,其他运算符以此类推。
69 不同类型的整数数据在进行混合类型的位运算时,按右端对齐原则进行处理,按数据长度大的数据进行处理,将数据长度小的数据左端补0或1。例如char a与int b进行位运算的时候,按int 进行处理,char a转化为整型数据,并在左端补0。
70 补位原则如下:
71 1) 对于有符号数据:如果a为正整数,则左端补0,如果a 为负数,则左端补1。
72 2) 对于无符号数据:在左端补0。
73 4.例子
74 例11-2 获得一个无符号数据从第p位开始的n位二进制数据。假设数据右端对齐,第0位二进制数在数据的最右端,获得的结果要求右对齐。
75 #include 
76 /*getbits:获得从第p位开始的n位二进制数 */
77 unsigned int getbits(unsigned int x, unsigned int p, unsigned n)
78 {
79 unsigned int a;
80 unsigned int b;
81 a=x>>(p+1);
82 b=~(~0<<n);</n);
83 return a&b;
84 }
85 提示 在某一平台进行程序开发时,首先要求了解此系统的基本数据类型的有效范围, 对涉及的位运算进行评估,特别是要对边界数据进行检测,确保计算正确。
86     
View Code

42、局部变量和全局变量重名   使用全局变量  cout << ::a <<endl;

1 1、局部变量能否和全局变量重名?   
2 答:能,局部会屏蔽全局。要用全局变量,需要使用 ":: " 
3   局部变量可以与全局变量同名,在函数内引用这个变量时,会用到同名的局部变量,而不会用到全局变量。对于有些编译器而言,在同一个函数内可以定义多个同名的局部变量,比如在两个循环体内都定义一个同名的局部变量,而那个局部变量的作用域就在那个循环体内。
4 2、如何引用一个已经定义过的全局变量?   
5 答:extern 
6   可以用引用头文件的方式,也可以用extern关键字,如果用引用头文件方式来引用某个在头文件中声明的全局变理,假定你将那个变写错了,那么在编译期间会报错,如果你用extern方式引用时,假定你犯了同样的错误,那么在编译期间不会报错,而在连接期间报错。
7 3、全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?  
8 答:可以,在不同的C文件中以static形式来声明同名全局变量。   可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时连接不会出错
View Code

 43、i++ 和 ++i

i++先做其他的在++   ++i先++后做其他

i++会有一份临时变量  所以效率低

44、类所占内存大小

 1 成员函数还是以一般的函数一样的存在。a.fun()是通过fun(a.this)来调用的。所谓成员函数只是在名义上是类里的。其实成员函数的大小不在类的对象里面,类所占内存的大小不包括成员函数的大小,虚拟成员函数除外。同一个类的多个对象共享函数代码。而我们访问类的成员函数是通过类里面的一个指针实现,而这个指针指向的是一个table,table里面记录的各个成员函数的地址(当然不同的编译可能略有不同的实现)。所以我们访问成员函数是间接获得地址的。所以这样也就增加了一定的时间开销,这也就是为什么我们提倡把一些简短的,调用频率高的函数声明为inline形式(内联函数)。 
 2 (一)
 3 class CBase 
 4 { 
 5 }; 
 6 sizeof(CBase)=1 7 为什么空的什么都没有是1呢?
 8 c++要求每个实例在内存中都有独一无二的地址。//注意这句话!!!!!!!!!!
 9 空类也会被实例化,所以编译器会给空类隐含的添加一个字节,这样空类实例化之后就有了独一无二的地址了。所以空类的sizeof为1。
10 (二) 
11 class CBase 
12 { 
13 int a; 
14 char p; 
15 }; 
16 sizeof(CBase)=8;
17 记得对齐的问题。int 占4字节//注意这点和struct的对齐原则很像!!!!!
18 char占一字节,补齐3字节
19 (三)
20 class CBase 
21 { 
22 public: 
23 CBase(void); 
24 virtual ~CBase(void); 
25 private: 
26 int a; 
27 char *p; 
28 }; 
29 再运行:sizeof(CBase)=12
30 C++ 类中有虚函数的时候有一个指向虚函数的指针(vptr),在32位系统分配指针大小为4字节。无论多少个虚函数,只有这一个指针,4字节。//注意一般的函数是没有这个指针的,而且也不占类的内存。
31 (四)
32 class CChild : public CBase 
33 { 
34 public: 
35 CChild(void); 
36 ~CChild(void); 
37 virtual void test();
38 private: 
39 int b; 
40 }; 
41 输出:sizeof(CChild)=1642 可见子类的大小是本身成员变量的大小加上父类的大小。//其中有一部分是虚拟函数表的原因,一定要知道父类子类共享一个虚函数指针
43 (五)
44 #include<iostream.h>class a {};class b{};class c:public a{virtual void fun()=0;};class d:public b,public c{};int main(){cout<<"sizeof(a)"<<sizeof(a)<<endl;cout<<"sizeof(b)"<<sizeof(b)<<endl;cout<<"sizeof(c)"<<sizeof(c)<<endl;cout<<"sizeof(d)"<<sizeof(d)<<endl;return 0;}程序执行的输出结果为:sizeof(a) =1sizeof(b)=1sizeof(c)=4sizeof(d)=8前三种情况比较常见,注意第四种情况。类d的大小更让初学者疑惑吧,类d是由类b,c派生迩来的,它的大小应该为二者之和5,为什么却是8 呢?这是因为为了提高实例在内存中的存取效率.类的大小往往被调整到系统的整数倍.并采取就近的法则,里哪个最近的倍数,就是该类的大小,所以类d的大小为8个字节. 
45 总结:
46 空的类是会占用内存空间的,而且大小是1,原因是C++要求每个实例在内存中都有独一无二的地址。
47 (一)类内部的成员变量:
48 普通的变量:是要占用内存的,但是要注意对齐原则(这点和struct类型很相似)。static修饰的静态变量:不占用内容,原因是编译器将其放在全局变量区。 
49 (二)类内部的成员函数:普通函数:不占用内存。虚函数:要占用4个字节,用来指定虚函数的虚拟函数表的入口地址。所以一个类的虚函数所占用的地址是不变的,和虚函数的个数是没有关系的。
View Code

 45、友元函数

 1 1、为什么要引入友元函数:在实现类之间数据共享时,减少系统开销,提高效率
 2       具体来说:为了使其他类的成员函数直接访问该类的私有变量
 3 
 4       即:允许外面的类或函数去访问类的私有变量和保护变量,从而使两个类共享同一函数
 5 
 6       优点:能够提高效率,表达简单、清晰
 7 
 8       缺点:友元函数破环了封装机制,尽量不使用成员函数,除非不得已的情况下才使用友元函数。
 9 2、什么时候使用友元函数:
10 
11       1)运算符重载的某些场合需要使用友元。
12 
13       2)两个类要共享数据的时候
14 3、怎么使用友元函数:
15 
16 友元函数的参数:
17 
18        因为友元函数没有this指针,则参数要有三种情况:
19 
20        1、  要访问非static成员时,需要对象做参数;--常用(友元函数常含有参数)
21 
22        2、  要访问static成员或全局变量时,则不需要对象做参数
23 
24        3、  如果做参数的对象是全局对象,则不需要对象做参数
25 友元函数的位置:
26 
27        因为友元函数是类外的函数,所以它的声明可以放在类的私有段或公有段且没有区别。
28 
29 友元函数的调用:
30 
31        可以直接调用友元函数,不需要通过对象或指针
32 
33 友元函数的分类:
34 
35 根据这个函数的来源不同,可以分为三种方法:
36 
37 1、普通函数友元函数:
38 
39        a) 目的:使普通函数能够访问类的友元
40 
41        b) 语法:声明位置:公有私有均可,常写为公有
42 
43                         声明: friend + 普通函数声明
44 
45                         实现位置:可以在类外或类中
46 
47                         实现代码:与普通函数相同(不加不用friend和类::)
48 
49                         调用:类似普通函数,直接调用
50 class girl;
51 
52 class boy
53 {  
54 private:
55     char *name;  
56     int age;  
57 public:  
58     boy();
59     void disp(girl &);   
60 };  
61 
62 void boy::disp(girl &x) //函数disp()为类boy的成员函数,也是类girl的友元函数 
63 { 
64     cout<<"boy's name is:"<<name<<",age:"<<age<<endl;//正常情况,boy的成员函数disp中直接访问boy的私有变量
65     cout<<"girl's name is:"<<x.name<<",age:"<<x.age<<endl; 
66     //借助友元,在boy的成员函数disp中,借助girl的对象,直接访问girl的私有变量
67     //正常情况下,只允许在girl的成员函数中访问girl的私有变量
68 }
69 
70 class girl
71 {  
72 private73     char *name;  
74     int age;  
75     friend boy;   //声明类boy是类girl的友元  
76 public:  
77     girl();   
78 };  
79 void main()  
80 {   
81     boy b;  
82     girl g;  
83     b.disp(g);  //b调用自己的成员函数,但是以g为参数,友元机制体现在函数disp中
84 }
View Code

 46、实现一个不可以继承的类

 1 1、把构造析构变为私有  然后创建一个静态对象   每次用的时候都把这个静态对象返回    
 2 2、创建一个类的友元类  友元类再虚继承这个类  这个累的构造和析构都是私有的 那么这个友元类就无法继承了  因为继承这个类的话必然要调用基类的构造函数  由于是私有的  无法构造  所以  直接就完犊子了
 3 class CNoHeritance
 4 {
 5 private:
 6     CNoHeritance(){}
 7     ~CNoHeritance(){}
 8     friend class CParent;
 9 };
10 
11 class CParent : virtual public CNoHeritance
12 {
13 public:
14     CParent(int v){m_v = v;}
15     ~CParent(){};
16 private:
17     int m_v;
18 
19 public:
20     void fun(){cout << "The value is: " << m_v << endl;}
21 };
22 
23 class CChild : public CParent
24 {
25 public:
26     CChild():CParent(10){}
27     ~CChild(){}
28 };
View Code

 如果  b虚继承a c 直接继承b   那么c也会直接调用a的构造函数   原因是  有个指向base类的虚指针   

所以sizeof一个虚基类  除了字节对齐  还要考虑  有个虚指针   如果有虚函数  还要考虑虚表的指针

 47、vector 中的reserve  和 resize  reserve是预留一部分空间   resize是重新分配

reserve是预留但是不真正创建对象  用的时候必须push_back

resize  是分配 并且创建对象

48、在map迭代器遍历过程中插入元素会不会出现问题

单线程 不会  

多线程  会   因为旋转操作不是原子的

posted @ 2016-08-31 15:13  悠悠我心。  阅读(421)  评论(0编辑  收藏  举报