C/C++ IT周道推书

 

    别人的不知道是怎么样的,自己感觉读书流程可以是这样的,大学里面肯定都已经有一本谭浩强的C基础入门书籍了,看完了后可以看下 <<C++primer>>,primer 有点长,但是看完后基础印象应该都有了,然后就需要进行实践开发可以看下 《windows核心编程》, 对了解windows的东西还是不错的,然后还是可以看下 <<effective c++>>及《more effective c++》,这两本看完可以把自己以前写的代码翻出来,然后对照改改,你肯定会说以前写的东西是一坨屎!都吸收完后,你可以写出挺搞质量的代码了,然后再看下设计模式《GOF设计模式》,这样你的代码结构又会更上一层楼,看设计模式的过程中可以看下 读下stl 的源码,你会看到stl 的很多实现都应用了设计模式!

现代编程肯定都不是单机玩玩,所以还要看下网络编程相关的内容,入门的一本就是《计算机网络》,有了这个基础就可以看《TCP/IP详解卷一:协议》,很详细,循序渐进,老外的数据就是讲得细,透彻!然后就是各操作系统下的网络编程学习,现在我们都可以轻易应用开源组件写出10K,100K的服务端,但是我们需要从头去看下当时牛人们突破这些关口的时候的思路,linux 从select到 poll 到epoll,windows 下的 完成端口,然后再去看这些技术的各种实现的网络服务框架,ACE,如果这个都通读了,那就牛逼了,我是只看了一点就没看下去,反省中!

再往上就是架构方面的,比如插件化的东西,SOA等!

最重要的是动手去实践,最重要的是动手去写代码,最重要的是动手,重要的事情说三遍,这是我的一条线!在实践中你会不断发现问题,比如网络编程中之前我自己曾经忽视的ospf和bgp等!,同时你最后会发现基础的重要性,你会发现到原来以前的数据结构算法 这本书真该好好吃透他!千里之行始于租下,COME ON!

当然还有一条线是继续往下面更加基础的专研的,就是吃内核,不断挖各种实现,自己也不断的去造轮子,搞编译器,等等等!这是我敬佩的一种人,但是我自己目前还是往系统应用业务架构上搞了!

C/C++拾遗一

一. new是一个操作符,具体的实现:

1.调用 operate new 函数 分配内存空间!

2.然后调用构造函数!

3.返回正确的指针!

所以new 跟 malloc 的基本差别:

1.属性上:new 是个运算符,malloc 是个函数,malloc 可等同与operate new 函数(operate new stl中的实现就是调用了 malloc)

2.功能上,new 比malloc 多了调用构造函数的过程!

引申:

如果需要限制类对象实例只能分配在堆上:那么我们只需要做的事情是把析构函数设置为私有,这样编译器管理不了对象的生命周期就不能分配在栈上,当然我们需要设置一个公有函数实现对象的释放!同时因为按照编程习惯new/delete 都是成对出现的,现在析构私有,则delete 会报错,所以我们把new 也封装到一个静态函数中去!不直接使用 new!

如果需要限制类对象示例只能分配在栈上:把operate new  函数重载为私有,则new 运算符就调用不了operate new 函数!

二、虚函数实现

C++多态的实现依靠虚函数,而虚函数又是通过虚函数表来实现的。那么里面的实现细节需要关注如下几点:

1.位置:为了最大化查找虚函数效率,V-Table  的指针存放在对象实例最前面的位置!

2.虚函数在虚表中的组织形式:

    2.1.V-Table 存储的是一个类的所有虚函数指针,虚函数地址基类的放在前面,子类在后面,按顺序存储。

    2.2.如果子类中有覆盖基类的,则子类的对象实例中,在虚函数表中,该虚函数地址直接覆盖基类的虚函数地址!

   2.3.如果存在多重继承,会有多个虚函数表,子类虚函数放在第一个虚函数表中,如果存在覆盖,则全部基类函数都被覆盖!

安全性:

因为虚函数表是顺序存储的,所以有时候我们其实可以通过直接函数指针直接寻址的的方式绕过编译器检查访问到一些不能访问的函数,如子类存在而父类中没有的,受保护的虚函数等!

详情见:(http://ibinguo.net/2009/06/)

三、对象内存空间

这个也只讲个皮毛,深入的话还是要对汇编,编译原理有深入了解!

前面讲虚函数、虚表的时候,就说了,虚表指针是放在对象实例最前面的位置。那么再来看看类的各种不同成员,不同继承关系的对象内存分布

1.单一继承结构:虚表在最前面,并且虚函数按照顺序在虚表中排列,而各成员变量也按照声明的顺序排列!

2.多重继承结构:每个基类一个虚表,而成员变量紧跟在虚表后面,并且共同的基类每个基类都会重复!

歧义:因为每个基类都有一个重复基类的成员,所以不同子类在调用的时候编译器就不知道要调用具体哪个一个,调用的时候需要加上具体的基类名!

3.由于2的歧义性问题,C++还有一个虚继承,这样多重继承就不会有多个重复拷贝,但是虚继承的基类会在最下面!

四、初始化异常处理

为了处理构造函数成员初始化列表产生的异常,必须将构造函数编写为函数测试块(function try block)。

template <class T> Handle<T>::Handle(T *p)

try : ptr(p), use(new size_t(1))

{

  // empty function body

} catch(const std::bad_alloc &e) {

  handle_out_of_memory(e);

}

关键字try出现在成员初始化列表之前,测试块的复合语句包围了构造函数的函数体。catch子句既可以处理从成员初始化列表中抛出的异常,也可以处理从构造函数函数体中抛出的异常。

数据结构与算法( 六)图

没有最好的数据结构,只有最适合的数据结构,没有最快的算法,只有最适合的算法。

图是一种常见的非线性数据结构,探究图,其实就是将非线性数据线性化的尝试。

图的遍历部分发现一个博客写得挺好,直接修改转过来了

http://www.cnblogs.com/dolphin0520/archive/2011/07/13/2105236.html

一.图定义

1.有向图:边有方向的图。 无相图:边无方向。

2.无向完全图:任两个点之间都有边的图。

3.有向完全图:任两点之间都存在两个有向边的图。

4.路径:任两个顶点之间经过的顶点序列集,长度是顶点序列之间边的的数目。

5.简单路径:路径中没有重复的顶点。

6.连通图:任两个顶点之间都存在路径的图

7.连通分量(极大连通子图):image

8.连通图的生成树:包含图中全部的顶点,但只有n-1条边

9.图可以使用2维数组来表示他们之间的关系,(邻接矩阵单元值为0,表示没有边,1表示有边)。

image

二.图的遍历

图的遍历是树的遍历的推广,是按照某种规则(或次序)访问图中各顶点依次且仅一次的操作,亦是将网络结构按某种规则线性化的过程

由于图存在回路,为区别一顶点是否被访问过和避免顶点被多次访问,在遍历过程中,应记下每个访问过的顶点,即每个顶点对应有一个标志位,初始为False,一旦该顶点被访问,就将其置为True,以后若又碰到该顶点时,视其标志的状态,而决定是否对其访问。

对图的遍历通常有”深度优先搜索“和”广度优先搜索“方法,二者是人工智能的一个基础。

深度优先搜索(Depth First Search,简称DFS)

算法思路:

类似树的先根遍历。设初始化时,图中各顶点均未被访问,从图中某个顶点(设为V0)出发,访问V0,然后搜索V0的一个邻接点Vi,若Vi未被访问,则访问之,在 搜索Vi的一个邻接点(深度优先)…。若某顶点的邻接点全部访问完毕,则回溯(Backtracking)到它的上一顶点,然后再从此顶点又按深度优先的方法搜索下去,…,直到能访问的顶点都访问完毕为止。

设图G10如下图所示:

通过深度优先如下:

广度优先搜索(Breadth First Search),简称BFS

算法思路:

类似树的按层次遍历。初始时,图中各顶点均未被访问,从图中某顶点(V0)出发,访问V0,并依次访问V0的各邻接点(广度优先)。然后,分别从这些被访问过的顶点出发,扔仍按照广度优先的策略搜索其它顶点,….,直到能访问的顶点都访问完毕为止。

为控制广度优先的正确搜索,要用到队列技术,即访问完一个顶点后,让该顶点的序号进队。然后取相应队头(出队),考察访问过的顶点的各邻接点,将未访问过的邻接点访问 后再依次进队,…,直到队空为止。

通过广度优先如下:

下面看一下实现代码:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #define MAX 20
  5. //访问记录
  6. int visit[MAX];
  7. //图的结构设计
  8. typedef struct
  9. {
  10. int vex[MAX];//记录顶点
  11. int adjmatrix[MAX][MAX];//邻接矩阵
  12. int n;//顶点的个数
  13. }GRAPH;
  14. //初始化图
  15. int init_graph(GRAPH *pG)
  16. {
  17.     memset(pG,0,sizeof(GRAPH));
  18.     pG->n = -1;
  19.     printf(“input vex\n”);
  20. while(scanf(“%d”,&pG->vex[++pG->n]));
  21. while(getchar() != ‘\n’);
  22. #ifndef _DEBUG_
  23. int i = 0;
  24. for(i = 0;i < pG->n ;i ++)
  25. {
  26.         printf(“V%d “,pG->vex[i]);
  27. }
  28.     printf(“\n”);
  29. #endif
  30.     return 0;
  31. }
  32. //获取顶点的位置
  33. int locatevex(GRAPH *pG,int vex)
  34. {
  35. int i = 0;
  36. for(i = 0;i < pG->n;i ++)
  37. {
  38. if(pG->vex[i] == vex )
  39.             return i;
  40. }
  41.     return 0;
  42. }
  43. //输入图的顶点之间的边
  44. int input_edge(GRAPH *pG)
  45. {
  46. int vex1,vex2;
  47. int i,j;
  48.     printf(“input edge(i,j):\n”);
  49. //任意字母键结束
  50. while(scanf(“(%d,%d)”,&vex1,&vex2))
  51. {
  52.         getchar();
  53.         i = locatevex(pG,vex1);
  54.         j = locatevex(pG,vex2);
  55.         pG->adjmatrix[i][j] = pG->adjmatrix[j][i] = 1;
  56. }
  57. #ifndef _DEBUG_
  58. int m,n;
  59. for(m = 0;m < pG->n;m ++)
  60. {
  61. for(n = 0;n < pG->n; n ++)
  62. {
  63.             printf(“%d “,pG->adjmatrix[m][n]);
  64. }
  65.         printf(“\n”);
  66. }
  67. #endif
  68.     return 0;
  69. }
  70. //栈的设计
  71. typedef struct
  72. {
  73. int buf[MAX];
  74. int n;
  75. }Stack;
  76. //创建空栈
  77. Stack *create_empty_stack()
  78. {
  79.     Stack *stack;
  80.     stack = (Stack *)malloc(sizeof(Stack));
  81.     stack->n = -1;
  82.     return stack;
  83. }
  84. //出栈
  85. int pop_stack(Stack *stack)
  86. {
  87. int temp;
  88.     temp = stack->buf[stack->n];
  89.     stack->n –;
  90.     return temp;
  91. }
  92. //入栈
  93. int push_stack(Stack *stack,int data)
  94. {
  95.     stack->n ++;
  96.     stack->buf[stack->n] = data;
  97.     return 0;
  98. }
  99. //判断空栈
  100. int is_empty_stack(Stack *stack)
  101. {
  102. if(stack->n == -1)
  103.         return 1;
  104. else
  105.         return 0;
  106. }
  107. int visit_all(GRAPH *pG)
  108. {
  109. int i = 0;
  110. for(i = 0;i < pG->n; i ++)
  111. {
  112. if(visit[i] != 1)
  113.             break;
  114. }
  115. if(i == pG->n)
  116.         return 1;
  117. else
  118.         return 0;
  119. }
  120. //图的深度非递归遍历
  121. int DFS(GRAPH *pG,int v)
  122. {
  123.     Stack *stack;
  124. int i = 0;
  125.     stack = create_empty_stack();
  126.     push_stack(stack,pG->vex[v]);
  127.     visit[v] = 1;
  128.     printf(“V%d “,pG->vex[v]);
  129. while(!is_empty_stack(stack) || !visit_all(pG))
  130. {
  131. for(i = 0;i < pG->n;i ++)
  132. {
  133. if(visit[i] == 0 && pG->adjmatrix[v][i] == 1)
  134.                 break;
  135. }
  136. if(i == pG->n)
  137. {
  138.             v = pop_stack(stack);
  139. }else{
  140.             v = i;
  141.             push_stack(stack,pG->vex[v]);
  142.             visit[v] = 1;
  143.             printf(“V%d “,pG->vex[v]);
  144. }
  145. }
  146.     printf(“\n”);
  147.     return 0;
  148. }
  149. //队列的设计
  150. typedef struct node
  151. {
  152. int data;
  153.     struct node *next;
  154. }ListNode;
  155. typedef struct
  156. {
  157.     ListNode *front;
  158.     ListNode *rear;
  159. }Queue;
  160. //创建空队列
  161. Queue *create_empty_queue()
  162. {
  163.     Queue *queue;
  164.     ListNode *head;
  165.     queue = (Queue *)malloc(sizeof(Queue));
  166.     head = (ListNode *)malloc(sizeof(ListNode));
  167.     queue->front = queue->rear = head;
  168.     return queue;
  169. }
  170. //判断队列是否为空
  171. int is_empty_queue(Queue *queue)
  172. {
  173. if(queue->rear == queue->front)
  174.         return 1;
  175. else
  176.         return 0;
  177. }
  178. //入队
  179. int EnterQueue(Queue *queue,int data)
  180. {
  181.     ListNode *temp;
  182.     temp = (ListNode *)malloc(sizeof(ListNode));
  183.     temp->data = data;
  184.     temp->next = NULL;
  185.     queue->rear->next = temp;
  186.     queue->rear = temp;
  187.     return 0;
  188. }
  189. //出队
  190. int DelQueue(Queue *queue)
  191. {
  192.     ListNode *temp;
  193.     temp = queue->front;
  194.     queue->front = queue->front->next;
  195.     free(temp);
  196.     temp = NULL;
  197.     return queue->front->data;
  198. }
  199. //图的广度遍历
  200. int BFS(GRAPH *pG,int v)
  201. {
  202.     Queue *queue = create_empty_queue();
  203. int i = 0;
  204.     memset(&visit,0,sizeof(visit));
  205.     EnterQueue(queue,v);
  206.     visit[v] = 1;
  207. while(!is_empty_queue(queue))
  208. {
  209.         v = DelQueue(queue);
  210.         printf(“V%d “,pG->vex[v]);
  211. for(i = 0;i < pG->n;i ++)
  212. {
  213. if(visit[i] == 0 && pG->adjmatrix[v][i] == 1)
  214. {
  215.                 EnterQueue(queue,i);
  216.                 visit[i] = 1;
  217. }
  218. }
  219. }
  220.     printf(“\n”);
  221.     return 0;
  222. }
  223. int main()
  224. {
  225.     GRAPH G;
  226. int n;
  227. //输入顶点,初始化图
  228.     init_graph(&G);
  229. //初始化邻接矩阵
  230.     input_edge(&G);
  231. //图的深度遍历
  232.     DFS(&G, 0);
  233. //图的广度遍历
  234.     BFS(&G,0);
  235.     return 0;
  236. }

输出结果:

C点(三)G

前面基本总结介绍了C/C++的基础点,现在再总结下需要注意的高级点。

一.C++模板

模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。

模板是创建泛型类或函数的蓝图或公式。库容器,比如迭代器和算法,都是泛型编程的例子,它们都使用了模板的概念。

每个容器都有一个单一的定义,比如 向量,我们可以定义许多不同类型的向量,比如 vector <int>vector <string>

您可以使用模板来定义函数和类,接下来让我们一起来看看如何使用。

函数模板

模板函数定义的一般形式如下所示:

template <class type> ret-type func-name(parameter list)
{
   // 函数的主体
}  

在这里,type 是函数所使用的数据类型的占位符名称。这个名称可以在函数定义中使用。

下面是函数模板的实例,返回两个数种的最大值:

#include &lt;iostream&gt; #include &lt;string&gt; using namespace std; template &lt;typename T&gt; inline T const&amp; Max (T const&amp; a, T const&amp; b) { return a &lt; b ? b:a; } int main () { int i = 39; int j = 20; cout &lt;&lt; "Max(i, j): " &lt;&lt; Max(i, j) &lt;&lt; endl; double f1 = 13.5; double f2 = 20.7; cout &lt;&lt; "Max(f1, f2): " &lt;&lt; Max(f1, f2) &lt;&lt; endl; string s1 = "Hello"; string s2 = "World"; cout &lt;&lt; "Max(s1, s2): " &lt;&lt; Max(s1, s2) &lt;&lt; endl; return 0; }

类模板

正如我们定义函数模板一样,我们也可以定义类模板。泛型类声明的一般形式如下所示:

template <class type> class class-name {
.
.
.
}

在这里,type 是占位符类型名称,可以在类被实例化的时候进行指定。您可以使用一个逗号分隔的列表来定义多个泛型数据类型。

下面的实例定义了类 Stack<>,并实现了泛型方法来对元素进行入栈出栈操作:

#include &lt;iostream&gt; #include &lt;vector&gt; #include &lt;cstdlib&gt; #include &lt;string&gt; #include &lt;stdexcept&gt; using namespace std; template &lt;class T&gt; class Stack { private: vector&lt;T&gt; elems; // 元素 public: void push(T const&amp;); // 入栈 void pop(); // 出栈 T top() const; // 返回栈顶元素 bool empty() const{ // 如果为空则返回真。 return elems.empty(); } }; template &lt;class T&gt; void Stack&lt;T&gt;::push (T const&amp; elem) { // 追加传入元素的副本 elems.push_back(elem); } template &lt;class T&gt; void Stack&lt;T&gt;::pop () { if (elems.empty()) { throw out_of_range("Stack&lt;&gt;::pop(): empty stack"); } // 删除最后一个元素 elems.pop_back(); } template &lt;class T&gt; T Stack&lt;T&gt;::top () const { if (elems.empty()) { throw out_of_range("Stack&lt;&gt;::top(): empty stack"); } // 返回最后一个元素的副本 return elems.back(); } int main() { try { Stack&lt;int&gt; intStack; // int 类型的栈 Stack&lt;string&gt; stringStack; // string 类型的栈 // 操作 int 类型的栈 intStack.push(7); cout &lt;&lt; intStack.top() &lt;&lt;endl; // 操作 string 类型的栈 stringStack.push("hello"); cout &lt;&lt; stringStack.top() &lt;&lt; std::endl; stringStack.pop(); stringStack.pop(); } catch (exception const&amp; ex) { cerr &lt;&lt; "Exception: " &lt;&lt; ex.what() &lt;&lt;endl; return -1; } } 
二.多线程
//参数依次是:创建的线程id,线程参数,调用的函数,传入的函数参数
        int ret = pthread_create(&tids[i], NULL, say_hello, NULL);
三.C/C++标准库
C标准函数库

image

C++ 标准函数库

继承了C标准函数库及做了一些线程上的修改

C++标准类库

STL,输入输出等,重点就是STL。

C点(一)C++基础点

基于C的基础上再补充C++的一些基础要点。

一.数组指针

#include &lt;iostream&gt; using namespace std; const int MAX = 3; int main () { int var[MAX] = {10, 100, 200}; for (int i = 0; i &lt; MAX; i++) { *var = i; // 这是正确的语法 *(var + i) = 20;//正确 var++; // 这是不正确的 } return 0; } 

二.引用 vs 指针
区别:

引用很容易与指针混淆,它们之间有三个主要的不同:

  • 不存在空引用。引用必须连接到一块合法的内存。
  • 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
  • 引用必须在创建时被初始化。指针可以在任何时间被初始化。
引用的使用:

作为形参使用:更加安全高效,不需要内存复制操作。

作为返回值:被引用的对象不能超出作用域。

int&amp; func() { int q; //! return q; // 在编译时发生错误 static int x; return x; // 安全,x 在函数作用域外依然是有效的 }

三.类对象-封装
拷贝构造函数

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:

  • 通过使用另一个同类型的对象来初始化新创建的对象。

  • 复制对象把它作为参数传递给函数。

  • 复制对象,并从函数返回这个对象。

如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。拷贝构造函数的最常见形式如下:

#include &lt;iostream&gt; using namespace std; class Box { double width; public: friend void printWidth( Box box ); void setWidth( double wid ); }; // 成员函数定义 void Box::setWidth( double wid ) { width = wid; } // 请注意:printWidth() 不是任何类的成员函数 void printWidth( Box box ) { /* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */ cout &lt;&lt; "Width of box : " &lt;&lt; box.width &lt;&lt;endl; } // 程序的主函数 int main( ) { Box box; // 使用成员函数设置宽度 box.setWidth(10.0); // 使用友元函数输出宽度 printWidth( box ); return 0; }
内联函数

C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。

对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。

如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。

在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符。

类静态成员与静态函数

我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。

静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化,如下面的实例所示。

如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。

静态成员函数只能访问静态数据成员,不能访问其他静态成员函数和类外部的其他函数。

静态成员函数有一个类范围,他们不能访问类的 this 指针。您可以使用静态成员函数来判断类的某些对象是否已被创建.

#include <iostream> using namespace std; class Box { public: static int objectCount; // 构造函数定义 Box(double l=2.0, double b=2.0, double h=2.0) { cout <<"Constructor called." << endl; length = l; breadth = b; height = h; // 每次创建对象时增加 1 objectCount++; } double Volume() { return length * breadth * height; } static int getCount() { return objectCount; } private: double length; // 长度 double breadth; // 宽度 double height; // 高度 }; // 初始化类 Box 的静态成员 int Box::objectCount = 0; int main(void) { // 在创建对象之前输出对象的总数 cout << "Inital Stage Count: " << Box::getCount() << endl; Box Box1(3.3, 1.2, 1.5); // 声明 box1 Box Box2(8.5, 6.0, 2.0); // 声明 box2 // 在创建对象之后输出对象的总数 cout << "Final Stage Count: " << Box::getCount() << endl; return 0; }

四.继承+多态

继承:is-a

多态:以虚函数实现,进行面向对象的抽象。同时通过纯虚函数,实现接口设计编程。

五.异常处理

C++ 标准的异常

C++ 提供了一系列标准的异常,定义在 <exception> 中,我们可以在程序中使用这些标准的异常。它们是以父子类层次结构组织起来的,如下所示:

C++ 异常的层次结构

下表是对上面层次结构中出现的每个异常的说明:

image

定义新的异常

可以通过继承和重载 exception 类来定义新的异常。下面的实例演示了如何使用 std::exception 类来实现自己的异常

#include &lt;iostream&gt; #include &lt;exception&gt; using namespace std; struct MyException : public exception { const char * what () const throw () { return "C++ Exception"; } }; int main() { try { throw MyException(); } catch(MyException&amp; e) { std::cout &lt;&lt; "MyException caught" &lt;&lt; std::endl; std::cout &lt;&lt; e.what() &lt;&lt; std::endl; } catch(std::exception&amp; e) { //其他的错误 } }

抛出异常+捕获异常

throw:

double division(int a, int b) { if( b == 0 ) { throw "Division by zero condition!"; } return (a/b); }

try…catch…

try { // 保护代码 }catch( ExceptionName e1 ) { // catch 块 }catch( ExceptionName e2 ) { //任何异常,作为保护代码使用 }catch(... ) { // catch 块 }

六.内存管理

new 可以对内存进行初始化操作即有构造函数,malloc只申请分配内存。

七.预处理*宏

# 和 ## 运算符

# 和 ## 预处理运算符在 C++ 和 ANSI/ISO C 中都是可用的。# 运算符会把 replacement-text 令牌转换为用引号引起来的字符串。

## 运算符用于连接两个令牌。

#include <iostream>
using namespace std;

#define concat(a, b) a ## b
int main()
{
   int xy = 100;
   
   cout << concat(x, y);
   return 0;
}

c点(一)C基础点

记录下C编程的一些基础注意点。常量,全局变量,局部变量,形参变量,作用域,运算符,修饰符。

编译,环境

一.常量

使用 #define 预处理器。:

#include &lt;stdio.h&gt; #define LENGTH 10 #define WIDTH 5 #define NEWLINE '\n' 

使用 const 关键字。:

const int LENGTH = 10; const int WIDTH = 5; const char NEWLINE = '\n';

二:变量+作用域

任何一种编程中,作用域是程序中定义的变量所存在的区域,超过该区域变量就不能被访问。C 语言中有三个地方可以声明变量:

  1. 在函数或块内部的局部变量
  2. 在所有函数外部的全局变量
  3. 形式参数的函数参数定义中

让我们来看看什么是局部变量、全局变量和形式参数。

局部变量

在某个函数或块的内部声明的变量称为局部变量。它们只能被该函数或该代码块内部的语句使用。局部变量在函数外部是不可知的。

全局变量

全局变量是定义在函数外部,通常是在程序的顶部。全局变量在整个程序生命周期内都是有效的,在任意的函数内部能访问全局变量。

全局变量可以被任何函数访问。也就是说,全局变量在声明后整个程序中都是可用的。

形式参数

函数的参数,形式参数,被当作该函数内的局部变量,它们会优先覆盖全局变量。

初始化局部变量和全局变量

当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。定义全局变量时,系统会自动对其初始化。

image

#include &lt;stdio.h&gt; /* 全局变量声明 */ int a = 20; int main () { /* 在主函数中的局部变量声明 */ int a = 10; int b = 20; int c = 0; int sum(int, int); printf ("value of a in main() = %d\n", a); c = sum( a, b); printf ("value of c in main() = %d\n", c); return 0; } /* 添加两个整数的函数 */ int sum(int a, int b) { printf ("value of a in sum() = %d\n", a); printf ("value of b in sum() = %d\n", b); return a + b; }
三.存储类
auto 存储类:

auto 存储类是所有局部变量默认的存储类。

register 存储类

register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。

static 存储类

static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。

static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。

在 C 编程中,当 static 用在类数据成员上时,会导致仅有一个该成员的副本被类的所有对象共享。

extern 存储类

extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 ‘extern’ 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。

四.运算符

位运算符

位运算符作用于位,并逐位执行操作。&、 | 和 ^ 的真值表如下所示:

image

假设如果 A = 60,且 B = 13,现在以二进制格式表示,它们如下所示:

A = 0011 1100

B = 0000 1101

—————–

A&B = 0000 1100

A|B = 0011 1101

A^B = 0011 0001

~A  = 1100 0011

下表显示了 C 语言支持的位运算符。假设变量 A 的值为 60,变量 B 的值为 13,则:

image

五.形参

image

引用:

/* 函数定义 */ void swap(int &amp;x, int &amp;y) { int temp; temp = x; /* 保存地址 x 的值 */ x = y; /* 把 y 赋值给 x */ y = temp; /* 把 temp 赋值给 y */ return; }

指针:

/* 函数定义 */ void swap(int *x, int *y) { int temp; temp = *x; /* 保存地址 x 的值 */ *x = *y; /* 把 y 赋值给 x */ *y = temp; /* 把 temp 赋值给 y */ return; }

六.指针+数组
数组:

#include &lt;stdio.h&gt; /* 函数声明 */ double getAverage(int arr[], int size); int main () { /* 带有 5 个元素的整型数组 */ int balance[5] = {1000, 2, 3, 17, 50}; double avg; /* 传递一个指向数组的指针作为参数 */ avg = getAverage( balance, 5 ) ; /* 输出返回值 */ printf( "平均值是: %f ", avg ); return 0; }

数组指针:

数组名是一个指向数组中第一个元素的常量指针。因此,在下面的声明中:

double balance[50];

balance 是一个指向 &balance[0] 的指针,即数组 balance 的第一个元素的地址。因此,下面的程序片段把 p 赋值为 balance 的第一个元素的地址:

double *p;
double balance[10];

p = balance;

使用数组名作为常量指针是合法的,反之亦然。因此,*(balance + 4) 是一种访问 balance[4] 数据的合法方式。

一旦您把第一个元素的地址存储在 p 中,您就可以使用 *p、*(p+1)、*(p+2) 等来访问数组元素。

指针的指针

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置。

C 中指向指针的指针

七.字符串
字符串

在 C 语言中,字符串实际上是使用 null 字符 ‘\0′ 终止的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符。

下面的声明和初始化创建了一个 “Hello” 字符串。由于在数组的末尾存储了空字符,所以字符数组的大小比单词 “Hello” 的字符数多一个。

char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};

依据数组初始化规则,您可以把上面的语句写成以下语句:

char greeting[] = "Hello";

以下是 C/C++ 中定义的字符串的内存表示:

C/C++ 中的字符串表示

其实,您不需要把 null 字符放在字符串常量的末尾。C 编译器会在初始化数组时,自动把 ‘\0′ 放在字符串的末尾。

操作字符串的函数

image

八.可变参数

有时,您可能会碰到这样的情况,您希望函数带有可变数量的参数,而不是预定义数量的参数。C 语言为这种情况提供了一个解决方案,它允许您定义一个函数,能根据具体的需求接受可变数量的参数。下面的实例演示了这种函数的定义。

int func(int, ... ) 
{
   .
   .
   .
}

int main()
{
   func(1, 2, 3);
   func(1, 2, 3, 4);
}

请注意,函数 func() 最后一个参数写成省略号,即三个点号(),省略号之前的那个参数总是 int,代表了要传递的可变参数的总数。为了使用这个功能,您需要使用 stdarg.h 头文件,该文件提供了实现可变参数功能的函数和宏。具体步骤如下:

  • 定义一个函数,最后一个参数为省略号,省略号前面的那个参数总是 int,表示了参数的个数。
  • 在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。
  • 使用 int 参数和 va_start 宏来初始化 va_list 变量为一个参数列表。宏 va_start 是在 stdarg.h 头文件中定义的。
  • 使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
  • 使用宏 va_end 来清理赋予 va_list 变量的内存。

现在让我们按照上面的步骤,来编写一个带有可变数量参数的函数,并返回它们的平均值:

#include <stdio.h>
#include <stdarg.h>

double average(int num,...)
{

    va_list valist;
    double sum = 0.0;
    int i;

    /* 为 num 个参数初始化 valist */
    va_start(valist, num);

    /* 访问所有赋给 valist 的参数 */
    for (i = 0; i < num; i++)
    {
       sum += va_arg(valist, int);
    }
    /* 清理为 valist 保留的内存 */
    va_end(valist);

    return sum/num;
}

int main()
{
   printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
   printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}

当上面的代码被编译和执行时,它会产生下列结果。应该指出的是,函数 average() 被调用两次,每次第一个参数都是表示被传的可变参数的总数。省略号被用来传递可变数量的参数。

Average of 2, 3, 4, 5 = 3.500000
Average of 5, 10, 15 = 10.000000
九.字符串

 

strlen():返回的长度不包括空结束字符 sizeof() 运算符

int _tmain(int argc, _TCHAR* argv[]) { char str[10] = "1234567"; const char* cstr = "1234567"; void * pvoid; int len = strlen("1234567");// len = 7; len = sizeof("1234567");// len = 8 * sizeof(char) = 8; len = sizeof(str); // len = 10* sizeof(char) = 10; len = sizeof(pvoid);// len = 4 ;指针长度 len = sizeof(cstr);// len = 4 = sizeof(void *) len = strlen(cstr); // len = 7* sizeof(char) = 7; return 0; }

STL容器解析

C++ STL 之所以得到广泛的赞誉,也被很多人使用,不只是提供了像vector, string, list等方便的容器,更重要的是STL封装了许多复杂的数据结构算法和大量常用数据结构操作。vector封装数组,list封装了链表,map和set封装了二叉树(红黑树)等,在封装这些数据结构的时候,STL按照程序员的使用习惯,以成员函数方式提供的常用操作,如:插入、排序、删除、查找等。让用户在STL使用过程中,并不会感到陌生。

一.使用原则

当对象很大时,建立指针的容器而不是对象的容器

1 STL基于拷贝的方式的来工作,任何需要放入STL中的元素,都会被复制。

2 只涉及到指针拷贝操作, 没有额外类的构造函数和赋值构造函数的调用;

3.对象是指针的话,容器销毁前需要自行销毁指针所指向的对象;否则就造成了内存泄漏;

4.对象是指针的话, 使用排序等算法时,需要构造基于对象的比较函数,如果使用默认的比较函数,其结果是基于指针大小的比较,而不是对象的比较;

二.使用场景

deque的使用场景:比如排队购票系统,对排队者的存储可以采用deque,支持头端的快速移除,尾端的快速添加。如果采用vector,则头端移除时,会移动大量的数据,速度慢。
vector与deque的比较:
一:vector.at()比deque.at()效率高,比如vector.at(0)是固定的,deque的开始位置却是不固定的。
二:如果有大量释放操作的话,vector花的时间更少,这跟二者的内部实现有关。
三:deque支持头部的快速插入与快速移除,这是deque的优点。
list的使用场景:比如公交车乘客的存储,随时可能有乘客下车,支持频繁的不确实位置元素的移除插入。
set的使用场景:比如对手机游戏的个人得分记录的存储,存储要求从高分到低分的顺序排列。
map的使用场景:比如按ID号存储十万个用户,想要快速要通过ID查找对应的用户。二叉树的查找效率,这时就体现出来了。如果是vector容器,最坏的情况下可能要遍历完整个容器才能找到该用户。

C++ 虚函数表解析

转载自耗子叔酷壳网(http://coolshell.cn/articles/7992.html

C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。

关于虚函数的使用方法,我在这里不做过多的阐述。大家可以看看相关的C++的书籍。在这篇文章中,我只想从虚函数的实现机制上面为大家 一个清晰的剖析。

当然,相同的文章在网上也出现过一些了,但我总感觉这些文章不是很容易阅读,大段大段的代码,没有图片,没有详细的说明,没有比较,没有举一反三。不利于学习和阅读,所以这是我想写下这篇文章的原因。也希望大家多给我提意见。

言归正传,让我们一起进入虚函数的世界。

虚函数表

对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

这里我们着重看一下这张虚函数表。C++的编译器应该是保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

听我扯了那么多,我可以感觉出来你现在可能比以前更加晕头转向了。 没关系,下面就是实际的例子,相信聪明的你一看就明白了。

假设我们有这样的一个类:

1

2

3

4

5

6

7

class Base {

public:

virtual void f() { cout << "Base::f" << endl; }

virtual void g() { cout << "Base::g" << endl; }

virtual void h() { cout << "Base::h" << endl; }

};

按照上面的说法,我们可以通过Base的实例来得到虚函数表。 下面是实际例程:

1

2

3

4

5

6

7

8

9

10

11

12

typedef void(*Fun)(void);

Base b;

Fun pFun = NULL;

cout << "虚函数表地址:" << (int*)(&b) << endl;

cout << "虚函数表 — 第一个函数地址:" << (int*)*(int*)(&b) << endl;

// Invoke the first virtual function

pFun = (Fun)*((int*)*(int*)(&b));

pFun();

实际运行经果如下:(Windows XP+VS2003, Linux 2.6.22 + GCC 4.1.3)

虚函数表地址:0012FED4
虚函数表 — 第一个函数地址:0044F148
Base::f

通过这个示例,我们可以看到,我们可以通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f(),这在上面的程序中得到了验证(把int* 强制转成了函数指针)。通过这个示例,我们就可以知道如果要调用Base::g()和Base::h(),其代码如下:

1

2

3

(Fun)*((int*)*(int*)(&b)+0);  // Base::f()

(Fun)*((int*)*(int*)(&b)+1);  // Base::g()

(Fun)*((int*)*(int*)(&b)+2);  // Base::h()

这个时候你应该懂了吧。什么?还是有点晕。也是,这样的代码看着太乱了。没问题,让我画个图解释一下。如下所示:

01

注意:在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“/0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。在WinXP+VS2003下,这个值是NULL。而在Ubuntu 7.10 + Linux 2.6.22 + GCC 4.1.3下,这个值是如果1,表示还有下一个虚函数表,如果值是0,表示是最后一个虚函数表。

下面,我将分别说明“无覆盖”和“有覆盖”时的虚函数表的样子。没有覆盖父类的虚函数是毫无意义的。我之所以要讲述没有覆盖的情况,主要目的是为了给一个对比。在比较之下,我们可以更加清楚地知道其内部的具体实现。

一般继承(无虚函数覆盖)

下面,再让我们来看看继承时的虚函数表是什么样的。假设有如下所示的一个继承关系:

02

请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,其虚函数表如下所示:

对于实例:Derive d; 的虚函数表如下:

03

我们可以看到下面几点:
1)虚函数按照其声明顺序放于表中。
2)父类的虚函数在子类的虚函数前面。

我相信聪明的你一定可以参考前面的那个程序,来编写一段程序来验证。

一般继承(有虚函数覆盖)

覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。下面,我们来看一下,如果子类中有虚函数重载了父类的虚函数,会是一个什么样子?假设,我们有下面这样的一个继承关系。

04

为了让大家看到被继承过后的效果,在这个类的设计中,我只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:

05

我们从表中可以看到下面几点,
1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
2)没有被覆盖的函数依旧。

这样,我们就可以看到对于下面这样的程序,

1

2

3

Base *b = new Derive();

b->f();

由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

多重继承(无虚函数覆盖)

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。

06

对于子类实例中的虚函数表,是下面这个样子:

07

我们可以看到:
1) 每个父类都有自己的虚表。
2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

多重继承(有虚函数覆盖)

下面我们再来看看,如果发生虚函数覆盖的情况。

08

下图中,我们在子类中覆盖了父类的f()函数。

09

下面是对于子类实例中的虚函数表的图:

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:

1

2

3

4

5

6

7

8

9

10

11

Derive d;

Base1 *b1 = &d;

Base2 *b2 = &d;

Base3 *b3 = &d;

b1->f(); //Derive::f()

b2->f(); //Derive::f()

b3->f(); //Derive::f()

b1->g(); //Base1::g()

b2->g(); //Base2::g()

b3->g(); //Base3::g()

安全性

每次写C++的文章,总免不了要批判一下C++。这篇文章也不例外。通过上面的讲述,相信我们对虚函数表有一个比较细致的了解了。水可载舟,亦可覆舟。下面,让我们来看看我们可以用虚函数表来干点什么坏事吧。

一、通过父类型的指针访问子类自己的虚函数

我们知道,子类没有重载父类的虚函数是一件毫无意义的事情。因为多态也是要基于函数重载的。虽然在上面的图中我们可以看到Base1的虚表中有Derive的虚函数,但我们根本不可能使用下面的语句来调用子类的自有虚函数:

1

2

Base1 *b1 = new Derive();

b1->f1();  //编译出错

任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,所以,这样的程序根本无法编译通过。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。(关于这方面的尝试,通过阅读后面附录的代码,相信你可以做到这一点)

二、访问non-public的虚函数

另外,如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。

如:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

class Base {

private:

virtual void f() { cout << "Base::f" << endl; }

};

class Derive : public Base{

};

typedef void(*Fun)(void);

void main() {

Derive d;

Fun  pFun = (Fun)*((int*)*(int*)(&d)+0);

pFun();

}

结束语

C++这门语言是一门Magic的语言,对于程序员来说,我们似乎永远摸不清楚这门语言背着我们在干了什么。需要熟悉这门语言,我们就必需要了解C++里面的那些东西,需要去了解C++中那些危险的东西。不然,这是一种搬起石头砸自己脚的编程语言。

附录一:VC中查看虚函数表

我们可以在VC的IDE环境中的Debug状态下展开类的实例就可以看到虚函数表了(并不是很完整的)

附录 二:例程

下面是一个关于多重继承的虚函数表访问的例程:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

#include <iostream>

using namespace std;

class Base1 {

public:

virtual void f() { cout << "Base1::f" << endl; }

virtual void g() { cout << "Base1::g" << endl; }

virtual void h() { cout << "Base1::h" << endl; }

};

class Base2 {

public:

virtual void f() { cout << "Base2::f" << endl; }

virtual void g() { cout << "Base2::g" << endl; }

virtual void h() { cout << "Base2::h" << endl; }

};

class Base3 {

public:

virtual void f() { cout << "Base3::f" << endl; }

virtual void g() { cout << "Base3::g" << endl; }

virtual void h() { cout << "Base3::h" << endl; }

};

class Derive : public Base1, public Base2, public Base3 {

public:

virtual void f() { cout << "Derive::f" << endl; }

virtual void g1() { cout << "Derive::g1" << endl; }

};

typedef void(*Fun)(void);

int main()

{

Fun pFun = NULL;

Derive d;

int** pVtab = (int**)&d;

//Base1's vtable

//pFun = (Fun)*((int*)*(int*)((int*)&d+0)+0);

pFun = (Fun)pVtab[0][0];

pFun();

//pFun = (Fun)*((int*)*(int*)((int*)&d+0)+1);

pFun = (Fun)pVtab[0][1];

pFun();

//pFun = (Fun)*((int*)*(int*)((int*)&d+0)+2);

pFun = (Fun)pVtab[0][2];

pFun();

//Derive's vtable

//pFun = (Fun)*((int*)*(int*)((int*)&d+0)+3);

pFun = (Fun)pVtab[0][3];

pFun();

//The tail of the vtable

pFun = (Fun)pVtab[0][4];

cout<<pFun<<endl;

//Base2's vtable

//pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

pFun = (Fun)pVtab[1][0];

pFun();

//pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

pFun = (Fun)pVtab[1][1];

pFun();

pFun = (Fun)pVtab[1][2];

pFun();

//The tail of the vtable

pFun = (Fun)pVtab[1][3];

cout<<pFun<<endl;

//Base3's vtable

//pFun = (Fun)*((int*)*(int*)((int*)&d+1)+0);

pFun = (Fun)pVtab[2][0];

pFun();

//pFun = (Fun)*((int*)*(int*)((int*)&d+1)+1);

pFun = (Fun)pVtab[2][1];

pFun();

pFun = (Fun)pVtab[2][2];

pFun();

//The tail of the vtable

pFun = (Fun)pVtab[2][3];

cout<<pFun<<endl;

return 0;

}

注:本文年代久远,所有的示例都是在32位机上跑的。