EDN首页   博客首页 用户登陆  |  注册
aaa
发表于 2008/11/5 16:13:20

1

关于投票

队列的c语言实现

队列是一种与栈相反的数据结构,它是先进先出(first in, first out),同样它也是一种运算受限的结构。

队列的抽象数据类型:

ADT QUEUE =

{

InitQueue(&Q)

//create a queue

DestroyQueue(&q)

//destroy a queue

clearQueue(&q)

//clear the queue

............

}

队列的c语言实现

1:

顺序队列:

/* ******************************************
* FileName:squeue.c
* Author:hufeng Date:2007/11/21
* Version:v1.0
* Description: The program creat a queue and lots
* of operating
* ******************************************/

/* header file */
#include <stdio.h>
#define MAX 20

/* creat a struct */
typedef struct squeue
{
    int date[MAX];      /*declartion a array */
    int front,rear;     /* declarion a front and a rear pointer */

}sq;

/* assign queue each elem */
void Assign_squeue(sq *p, int count)
{
    int i; /* declaration a cycle variable */

    if (p->rear > MAX - 1)
        {
            printf("Overflow!\n");
            return;
        }

    printf("Please enter each elem:\n");
    for (i = 0; i < count; i++)
        {
            scanf("%d", &p->date[++p->rear]);
        }
}


/* insert a new elem in the queue */
void Insert_squeue(sq *p, int num)
{
    if (p->rear > MAX - 1)
        {
            printf("Over flow!\n");
            return;
        }

    else
        {
            p->date[++p->rear] = num;
        }

}


/* Delete a elem */
void Delete_squeue(sq *p)
{
    int x;
    if (p->front == p->rear)
        {
            printf("Empty queue!\n");
            return;
        }
    else
        {
            x = p->date[++p->front];
            printf("P->front = %d\n",p->front);
            printf("The deleted number is:%d\n",x);
        }
}


/* main program */
int main()
{
    sq *p = NULL;      /* declaration a pointer */
    int num;    /* decalration a number of queue */

    p->front = -1;
    p->rear = p->front;

    printf("Please enter the length of queue:\n");
    scanf("%d", &num);

    Assign_squeue(p, num); /* assign array date */

    /* insert a number in the queue */
    printf("Please enter which number you want to insert:\n");
    scanf("%d", &num);
    Insert_squeue(p, num);

    /* delete a number */
    Delete_squeue(p);

    getch();
    return 0;

}

2:

循环队列:

/* ******************************************
* FileName:cycle_queue.c
* Author:hufeng Date:2007/11/21
* Version:v1.0
* Description:The program is a cycle queue
*********************************************/

/* header file */
#include <stdio.h>
#define MAX 20

/* creat a struct */
typedef struct cycle_queue
{
    int date[MAX];
    int front,rear;
}cq;

/* assign queue */
void Assign_CycleQueue(cq *p, int n)
{
    int i;
    int x;

    if (n < 1 || n > MAX - 1)
        {
            printf("Overflow!\n");
            return;
        }

   printf("Please enter each elem:\n");
   for (i = 0; i < n; i++)
        {
            scanf("%d", &x);
            p->date[p->rear++] = x;
        }
}

/* insert a number */
void Insert_CycleQueue(cq *p, int num)
{
    if ((p->rear + 1) % MAX == p->front)
        {
            printf("full queue!\n");
            return;
        }  
    else
        {
            p->date[++p->rear] = num;
        }
}

/* delete number */
void Delete_CycleQueue(cq *p)
{
    int x;
    if ((p->front + 1) % MAX == p->rear)
        {
            printf("empty queue!\n");
            return;
        }


   x = p->date[p->front];
   printf("p->front = %d\n", p->front);
   printf("The deleted number is:%d\n",x);
}

/* main program */
int main()
{

    int num;
    cq *p = NULL;
    p->front = 0;
    p->rear = p->front;

    printf("Please enter the length of queue:\n");
    scanf("%d", &num);

    Assign_CycleQueue(p,num);

    printf("Please enter the inserting number:\n");
    scanf("%d",&num);

    Insert_CycleQueue(p, num);
    Delete_CycleQueue(p);

    getch();
    return 0;
}

3.

链队:

/* ******************************************
* FileName:cycle_queue.c
* Author:hufeng Date:2007/11/21
* Version:v1.0
* Description:The program is a cycle queue
*********************************************/

/* header file */
#include <stdio.h>
#define MAX 20

/* creat a struct */
typedef struct cycle_queue
{
    int date[MAX];
    int front,rear;
}cq;

/* assign queue */
void Assign_CycleQueue(cq *p, int n)
{
    int i;
    int x;

    if (n < 1 || n > MAX - 1)
        {
            printf("Overflow!\n");
            return;
        }

   printf("Please enter each elem:\n");
   for (i = 0; i < n; i++)
        {
            scanf("%d", &x);
            p->date[p->rear++] = x;
        }
}

/* insert a number */
void Insert_CycleQueue(cq *p, int num)
{
    if ((p->rear + 1) % MAX == p->front)
        {
            printf("full queue!\n");
            return;
        }  
    else
        {
            p->date[++p->rear] = num;
        }
}

/* delete number */
void Delete_CycleQueue(cq *p)
{
    int x;
    if ((p->front + 1) % MAX == p->rear)
        {
            printf("empty queue!\n");
            return;
        }


   x = p->date[p->front];
   printf("p->front = %d\n", p->front);
   printf("The deleted number is:%d\n",x);
}

/* main program */
int main()
{

    int num;
    cq *p = NULL;
    p->front = 0;
    p->rear = p->front;

    printf("Please enter the length of queue:\n");
    scanf("%d", &num);

    Assign_CycleQueue(p,num);

    printf("Please enter the inserting number:\n");
    scanf("%d",&num);

    Insert_CycleQueue(p, num);
    Delete_CycleQueue(p);

    getch();
    return 0;
}

系统分类: 软件开发  |  用户分类: 编程语言  |  标签: C语言 队列  |  来源: 转贴  | 

点击查看原文

发表评论 阅读全文(1626) | 回复(0)

发表于 2008/11/5 16:11:03

1

关于投票

修改了一个抓包的程序(c语言队列)

修改了一个抓包的程序(c语言队列)
05 年底写了个抓包的小工具,做某些方面的测试用。但是在大流量的时候,存在一些问题,经常会漏掉一些包没有分析,即使接到了预定义好的包,也没有办法作出某些响应。周末没事,就做了一些改写,主要原理很简单,就是用一个队列(Queue)对高峰流量做一下缓冲,先存下来再慢慢分析。读了计算机专业的人应该就容易了,数据结构有讲。我比较土一点,学经济的,只能自己根据C#的接口YY一个类似的。

      整个程序代码比较长就不贴了,把队列代码贴在下面吧,放在网上的比硬盘的还是方便一点,随时可以查阅,以备不时之需。    



   1. #include <stdio.h>
   2.  
   3. #include <stdlib.h>
   4.  
   5. #include <malloc.h>
   6.  
   7. #include <string.h>
   8.  
   9.  
  10.  
  11. typedef struct _Node
  12.  
  13. {
  14.  
  15.     int     data_len;       // 存储在节点中的数据长度
  16.  
  17.     char    *data;          // 存储在节点中的数据
  18.  
  19.     struct  _Node *next;    // 队列中的下一个节点地址
  20.  
  21. }NODE;
  22.  
  23.  
  24.  
  25. typedef struct _Queue
  26.  
  27. {
  28.  
  29.     NODE    *head;          // 队列的头部
  30.  
  31.     NODE    *end;           // 队列的尾部
  32.  
  33.     int     count;          // 队列长度
  34.  
  35. }QUEUE;
  36.  
  37.  
  38.  
  39. bool InitQueue( QUEUE *queue )
  40.  
  41. {
  42.  
  43.     if( NULL == queue )
  44.  
  45.     {
  46.  
  47.         return false;
  48.  
  49.     }
  50.  
  51.  
  52.  
  53.     queue->head = NULL;
  54.  
  55.     queue->end = NULL;
  56.  
  57.     queue->count = 0;
  58.  
  59.  
  60.  
  61.     return true;
  62.  
  63. }
  64.  
  65.  
  66.  
  67. // 在队列中插入节点
  68.  
  69. bool Enqueue( QUEUE *queue, char *queue_data, int data_len )
  70.  
  71. {
  72.  
  73.     if( NULL == queue || NULL == queue_data )
  74.  
  75.     {
  76.  
  77.         return false;
  78.  
  79.     }
  80.  
  81.  
  82.  
  83.     // 开辟新节点
  84.  
  85.     NODE *new_node = (NODE *)malloc( sizeof(NODE) );
  86.  
  87.     if( NULL == new_node )
  88.  
  89.     {
  90.  
  91.         return false;
  92.  
  93.     }
  94.  
  95.  
  96.  
  97.     // 开辟空间存储数据
  98.  
  99.     new_node->data = (char *)malloc( data_len );
100.  
101.     if( NULL == new_node->data )
102.  
103.     {
104.  
105.         return false;
106.  
107.     }
108.  
109.  
110.  
111.     memcpy( new_node->data, queue_data, data_len );
112.  
113.     new_node->next = NULL;
114.  
115.     new_node->data_len = data_len;
116.  
117.  
118.  
119.     // 如果队列为空,则新节点即是头部,也是尾部
120.  
121.     if( queue->head == NULL )
122.  
123.     {
124.  
125.         queue->head = new_node;
126.  
127.         queue->end = new_node;
128.  
129.     }
130.  
131.     else
132.  
133.     {
134.  
135.         // 如果队列不为空,将此节点连接到队列的尾部
136.  
137.         queue->end->next = new_node;
138.  
139.  
140.  
141.         // 队列新尾部指向此节点
142.  
143.         queue->end = new_node;
144.  
145.     }
146.  
147.  
148.  
149.     queue->count ++;
150.  
151.     return true;
152.  
153. }
154.  
155.  
156.  
157. // 从队列中读出一个节点
158.  
159. NODE *Dequeue( QUEUE *queue )
160.  
161. {
162.  
163.     if( NULL == queue )
164.  
165.     {
166.  
167.         return NULL;
168.  
169.     }
170.  
171.  
172.  
173.     // 如果队列为空,则无数据可从数列读出,直接返回
174.  
175.     if( NULL == queue->head )
176.  
177.     {
178.  
179.         return NULL;
180.  
181.     }
182.  
183.  
184.  
185.     // 保存队列首节点
186.  
187.     NODE *node_tmp = queue->head;
188.  
189.  
190.  
191.     // 将首节点的下一个节点(第二个节点)设置为首节点,即删除了首节点
192.  
193.     queue->head = node_tmp->next;
194.  
195.  
196.  
197.     // 如果新首节点为空,则队列为空
198.  
199.     if( NULL == queue->head )
200.  
201.     {
202.  
203.         queue->end = NULL;
204.  
205.     }
206.  
207.  
208.  
209.     queue->count --;
210.  
211.     return node_tmp;
212.  
213. }
214.  
215.  
216.  
217. // 释放队列所有内存
218.  
219. void FreeQueue( QUEUE *queue )
220.  
221. {
222.  
223.     if( queue )
224.  
225.     {
226.  
227.         return;
228.  
229.     }
230.  
231.  
232.  
233.     NODE *tmp_node1 = queue->head;
234.  
235.     while( tmp_node1 )
236.  
237.     {
238.  
239.         NODE *tmp_node2 = tmp_node1;
240.  
241.  
242.  
243.         free( tmp_node1->data );
244.  
245.         free( tmp_node1 );
246.  
247.  
248.  
249.         tmp_node1->data = NULL;
250.  
251.         tmp_node1 = NULL;
252.  
253.  
254.  
255.         tmp_node1 = tmp_node2->next;
256.  
257.     }
258.  
259. }
260.  
261.  

      这样没抓到一个包,就压入到队列里面。流量较大的尖峰时刻时候队列会比较长,但是流量较小的时候队列就比较短了。服务器流量不会一直保持在顶峰,所以可以把部分过高的流量缓冲到流量较小的时候做分析,测试的时候发现确实稳定了很多。首先需要注意的是设置一个队列最大长度,必要的时候丢掉一些数据免得内存耗尽。另外一个是分析的时候可以多线程读取队列进行分析,这样速度会更快,只是做好线程的同步就好了,CreateMutex是最简单易行的了。我用5线程来分析数据包,基本足够了。

系统分类: 软件开发  |  用户分类: 编程语言  |  标签: C语言 队列  |  来源: 转贴  | 

点击查看原文

发表评论 阅读全文(694) | 回复(0)

发表于 2008/11/5 16:08:01

1

关于投票

数据结构C语言实现系列——队列

#include <stdio.h>
#include
<stdlib.h>

typedef
int elemType;
/************************************************************************/
/* 以下是关于队列链接存储操作的6种算法 */
/************************************************************************/
struct sNode{
elemType data;
/* 值域 */
struct sNode *next; /* 链接指针 */
};
struct queueLK{
struct sNode *front; /* 队首指针 */
struct sNode *rear; /* 队尾指针 */
};

/* 1.初始化链队 */
void initQueue(struct queueLK *hq)
{
hq
->front = hq->rear = NULL; /* 把队首和队尾指针置空 */
return;
}

/* 2.向链队中插入一个元素x */
void enQueue(struct queueLK *hq, elemType x)
{
/* 得到一个由newP指针所指向的新结点 */
struct sNode *newP;
newP
= malloc(sizeof(struct sNode));
if(newP == NULL){
printf(
"内存空间分配失败! ");
exit(
1 );

}
/* 把x的值赋给新结点的值域,把新结点的指针域置空 */
newP
->data = x;
newP
->next = NULL;
/* 若链队为空,则新结点即是队首结点又是队尾结点 */
if(hq->rear == NULL){
hq
->front = hq->rear = newP;
}
else{ /* 若链队非空,则依次修改队尾结点的指针域和队尾指针,使之指向新的队尾结点 */
hq
->rear = hq->rear->next = newP; /* 注意赋值顺序哦 */
}
return;
}

/* 3.从队列中删除一个元素 */
elemType outQueue(
struct queueLK *hq)
{
struct sNode *p;
elemType temp;
/* 若链队为空则停止运行 */
if(hq->front == NULL){
printf(
"队列为空,无法删除! ");
exit(
1);
}
temp
= hq->front->data; /* 暂存队尾元素以便返回 */
p
= hq->front; /* 暂存队尾指针以便回收队尾结点 */
hq
->front = p->next; /* 使队首指针指向下一个结点 */
/* 若删除后链队为空,则需同时使队尾指针为空 */
if (hq->front == NULL)
{

hq->rear = NULL;
}
free(p);
/* 回收原队首结点 */
return temp; /* 返回被删除的队首元素值 */
}

/* 4.读取队首元素 */
elemType peekQueue(
struct queueLK *hq)
{
/* 若链队为空则停止运行 */
if(hq->front == NULL){
printf(
"队列为空,无法删除! ");
exit(
1);
}
return hq->front->data; /* 返回队首元素 */
}

/* 5.检查链队是否为空,若为空则返回1, 否则返回0 */
int emptyQueue(struct queueLK *hq)
{
/* 判断队首或队尾任一个指针是否为空即可 */
if(hq->front == NULL){
return 1;
}
else{
return 0;
}
}

/* 6.清除链队中的任何元素 */
void clearQueue(struct queueLK *hq)
{
struct sNode *p = hq->front; /* 队首指针赋给p */
/* 依次删除队列中的每一个结点,最后使队首指针为空 */
while(p != NULL){
hq
->front = hq->front->next;
free(p);
p = hq->front;
}
/* 循环结束后队首指针已为空 */
hq
->rear = NULL; /* 置队尾指针为空 */
return;
}

/************************************************************************/

int main(int argc, char* argv[])
{
struct queueLK q;
int a[8] = {3, 8, 5, 17, 9, 30, 15, 22};
int i;
initQueue(
&q);
for(i = 0; i < 8; i ){
enQueue(
&q, a[i]);
}
printf(
"%d ", outQueue(&q)); printf("%d ", outQueue(&q));
enQueue(
&q, 68);
printf(
"%d ", peekQueue(&q)); printf("%d ", outQueue(&q));
while(!emptyQueue(&q)){
printf(
"%d ", outQueue(&q));
}
printf(
" ");
clearQueue( &q);

system("pause");
}
Word-BREAK: break-all; PADDING-TOP: 4px; BORDER-BOTTOM: windowtext 0.5pt solid">

#include <stdio.h>
#include
<stdlib.h>

typedef
int elemType;
/************************************************************************/
/* 以下是关于队列顺序存储操作的6种算法 */
/************************************************************************/

struct queue{
elemType
*queue; /* 指向存储队列的数组空间 */
int front, rear, len; /* 队首指针(下标),队尾指针(下标),队列长度变量 */
int maxSize; /* queue数组长度 */
};

void againMalloc(struct queue *q)
{
/* 空间扩展为原来的2倍,原内容被自动拷贝到p所指向的存储空间中 */
elemType
*p;
p
= realloc(q->queue, 2 * q->maxSize * sizeof(elemType));
/* 动态存储空间分配,若失败则退出运行 */
if(!p){
printf(
"空间分配失败! ");
exit(
1);
}
q
->queue = p; /* 使queue指向新的队列空间 */
/* 把原队列的尾部内容后移maxSize个位置 */
if(q->rear != q->maxSize - 1){

int i;
for(i = 0; i <= q->rear; i ){
q
->queue[i q->maxSize] = q->queue[i];
}
q
->rear = q->maxSize; /* 队尾指针后移maxSize个位置 */
}
q
->maxSize = 2 * q->maxSize; /* 把队列空间大小修改为新的长度 */
return;
}

/* 1.初始化队列 */
void initQueue(struct queue *q, int ms)
{
/* 检查ms是否有效,若无效则退出运行 */
if(ms <= 0){
printf(
"ms值非法! ");
exit(
1);
}
q
->maxSize = ms; /* 置队列空间大小为ms */
/* 动态存储空间分配,若失败则退出运行 */
q
->queue = malloc(ms * sizeof(elemType));
if(!q->queue){
printf(
"内存空间分配失败! ");
exit(
1);
}
q
->front = q->rear = 0; /* 初始置队列为空 */
return;
}

/* 2.向队列中插入元素x */
void enQueue(struct queue *q, elemType x)
{
/* 当队列满时进行动态生分配 */
if((q->rear 1) % q->maxSize == q->front){
againMalloc(q);
}
q
->rear = (q->rear 1) % q->maxSize; /* 求出队尾的下一个位置 */
q
->queue[q->rear] = x; /* 把x的值赋给新的队尾 */
return;
}

/* 3.从队列中删除元素并返回 */
elemType outQueue(
struct queue *q)
{
/* 若队列为空则终止运行 */
if(q->front == q->rear){
printf(
"队列为空,无法删除! ");
exit(
1);
}
q
->front = (q->front 1) % q->maxSize; /* 使队首指针指向下一个位置 */
return q->queue[q->front]; /*返回队首元素 */
}
/* 4.读取队首元素,不改变队列状态 */
elemType peekQueue(
struct queue *q)
{
/* 若队列为空则终止运行 */
if(q->front == q->rear){
printf(
"队列为空,无法删除! ");
exit(
1);
}
return q->queue[(q->front 1) % q->maxSize];/* 队首元素是队首指针的下一个位置中的元素 */
}

/* 5.检查一个队列是否为空,若是则返回1,否则返回0 */
int emptyQueue(struct queue *q)
{
if(q->front == q->rear){
return 1;
}
else{
return 0;
}
}

/* 6.清除一个队列,并释放动态存储空间 */
void clearQueue(struct queue *q)
{
if(q->queue != NULL){
free(q
->queue);
q
->queue = NULL; /* 配置队列空间指针为空 */
q
->front = q->rear = 0; /* 配置队列为空 */
q
->maxSize =0; /* 配置队列大小为0 */
}
return;
}

/************************************************************************/

int main(int argc, char* argv[])
{
struct queue q;
int a[8] = {3, 8, 5, 17, 9, 30, 15, 22};
int i;
initQueue(
&q, 5);
for(i = 0; i < 8; i ){
enQueue(
&q, a[i]);
}
printf(
"%d ", outQueue(&q)); printf("%d ", outQueue(&q));
enQueue(
&q, 68);
printf(
"%d ", peekQueue(&q)); printf("%d ", outQueue(&q));
while(!emptyQueue(&q)){
printf(
"%d ", outQueue(&q));
}
printf(
" ");
clearQueue(
&q);
system(
"pause");
return 0;
}

 

系统分类: 软件开发  |  用户分类: 编程语言  |  标签: C语言 数据结构 队列  |  来源: 转贴  | 

点击查看原文

发表评论 阅读全文(595) | 回复(0)

发表于 2008/9/9 11:32:22

2

关于投票

C宏——智者的利刃,愚者的恶梦!

文章来自:http://blog.vckbase.com/smileonce/archive/2005/03/27/4081.html

水平不高不低的C++程序员最喜欢挂在嘴上的一句话就是:C宏,万恶之首,错误的开端,应该被废弃。

请注意,我用了一句不敬的修饰语“水平不高不低的”。为什么这么说?因为水平低都插不上话,都在在静静地听老前辈布道呢。水平高的,比如Bane Stroustrup老人家,也只是说若干种场合下C++语言能够提供比C macro更好的解决方案,而没有完全否定C macro的价值。但是话就怕传来传去,一传就走样。久而久之,就被传成上面那句话。其实说来也很好笑:java程序员经常说java比C++好,说C++手动释放内存老搞内存泄漏;C++程序员便反驳说,那是你水平低不会用。但是谈到C宏,水平不高不低的C++程序员居然也走java的老路了——明明是自己不会用,自己知道的少,却把责任推卸到C宏上。你自己笨我管不着,但是错误的言论如果误导后人就不好了吧。:)

本文就举几个简单的使用C宏的例子,如果这些例子用C++不用宏的语法能更好的解决,那么你一定要回复blog告诉我,这样下次我就不乱说话了。否则,笑笑很生气,后果很严重。:)


例一、用C宏,书写代码更简洁
这段代码写网络程序的朋友都很眼熟,是Net/3中mbuf的实现。

struct mbuf
{
    struct m_hdr mhdr;
    union {
        struct 
        {
            struct pkthdr MH_pkthdr; /* M_PKTHDR set */
            union 
            {
                struct m_ext MH_ext; /* M_EXT set */
                char MH_databuf[MHLEN];
            } MH_dat;
        } MH;
        char M_databuf[MLEN];        /* !M_PKTHER, !M_EXT*/
    } M_dat;
};

上面的代码,假如我想访问最里层的MH_databuf,那么我必须写M_dat.MH.MH_dat.MH_databuf; 这是不是很长,很难写呀?这样的代码阅读起来也不明了。其实,对于MH_pkthdr、MH_ext、MH_databuf来说,虽然不是在一个结构层次上,但是如果我们站在mbuf之外来看,它们都是mbuf的属性,完全可以压扁到一个平面上去看。所以,源码中有这么一组宏:

#define m_next      m_hdr.mh_next
#define m_len       m_hdr.mh_len
#define m_data      m_hdr.mh_data
... ...
#define m_pkthdr    M_dat.MH.MH_pkthdr
#define m_pktdat    M_dat.MH.MH_dat.MH_databuf
... ...

这样写起代码来,是不是很精练呢!


例二、用C宏,实现跨平台和编译器的需要
这方面的例子太好举了,一举一大摞,就从VC的库源码中随意copy一段出来吧。

#ifndef _CRTAPI1
#if _MSC_VER >= 800 && _M_IX86 >= 300
#define _CRTAPI1 __cdecl
#else  /* _MSC_VER >= 800 && _M_IX86 >= 300 */
#define _CRTAPI1
#endif  /* _MSC_VER >= 800 && _M_IX86 >= 300 */
#endif  /* _CRTAPI1 */

#ifndef _SIZE_T_DEFINED
typedef unsigned int size_t;
#define _SIZE_T_DEFINED
#endif  /* _SIZE_T_DEFINED */

#ifndef _MAC
#ifndef _WCHAR_T_DEFINED
typedef unsigned short wchar_t;
#define _WCHAR_T_DEFINED
#endif  /* _WCHAR_T_DEFINED */
#endif  /* _MAC */
 
#ifndef _NLSCMP_DEFINED
#define _NLSCMPERROR    2147483647  /* currently == INT_MAX */
#define _NLSCMP_DEFINED
#endif  /* _NLSCMP_DEFINED */

请问,这些指示宏如何取代呢?如果真的是没有了这些宏,实现起来就更麻烦了吧。


例三、用C宏,自动生成代码
这方面的例子也是多得很,不过有鉴于很多朋友不用很多编译器,不做嵌入式的开发,我就举个win平台的例子吧。我们知道MFC实现了windows的消息映射,比如:

ON_COMMAND(IDM_ABOUT, OnAbout)
ON_COMMAND(IDM_FILENEW, OnFileNew)

它是如何实现的IDM_ABOUT和OnAbout的关联的呢?这要用到几个宏。

#define DECLARE_MESSAGE_MAP() \
private: \
 static const AFX_MSGMAP_ENTRY _messageEntries[]; \
protected: \
 static AFX_DATA const AFX_MSGMAP messageMap; \
 virtual const AFX_MSGMAP* GetMessageMap() const; \

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
 const AFX_MSGMAP* theClass::GetMessageMap() const \
  { return &theClass::messageMap; } \
 AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
 { &baseClass::messageMap, &theClass::_messageEntries[0] }; \
 AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
 { \

 #define ON_COMMAND(id, memberFxn) \
        { WM_COMMAND, 0, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)memberFxn },

 #define END_MESSAGE_MAP() \
  {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
 }; \
#define DECLARE_MESSAGE_MAP() \
private: \
 static const AFX_MSGMAP_ENTRY _messageEntries[]; \
protected: \
 static AFX_DATA const AFX_MSGMAP messageMap; \
 virtual const AFX_MSGMAP* GetMessageMap() const; \

#define BEGIN_MESSAGE_MAP(theClass, baseClass) \
 const AFX_MSGMAP* theClass::GetMessageMap() const \
  { return &theClass::messageMap; } \
 AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap = \
 { &baseClass::messageMap, &theClass::_messageEntries[0] }; \
 AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] = \
 { \

 #define ON_COMMAND(id, memberFxn) \
        { WM_COMMAND, 0, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)memberFxn },

 #define END_MESSAGE_MAP() \
  {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
 }; \

嘿嘿,就这么几个宏,就构造出一个消息数组来。


例四、用C宏,智者思维的火花
说了半天了,嘴皮子都干了,举个例子大家轻松一下——看看人家老外是怎么用宏的。
这个例子摘自《C专家编程》。

根据位模式构建图形
图标(icon)或者图形(glyph),是一种小型的位模式映射于屏幕产生的图像。一个位代表图像上的一个像素。如果一个位被设置,那么它所代表的像素就是“亮”的。如果一个位被清除,那么它所代表的像素就是“暗”的。所以,一系列的整数值能够用于为图像编码。类似Iconedit这样的工具就是用于绘图的,他们所输出的是一个包含一系列整型数的ASCII文件,可以被一个窗口程序所包含。它所存在的问题是程序中的图标只是一串十六进制数。在C语言中,典型的16X16的黑白图形可能如下:

static unsigned short stopwatch[] = {
0x07C6,
0x1FF7,
0x383B,
0x600C,
0x600C,
0xC006,
0xC006,
0xDF06,
0xC106,
0xC106,
0x610C,
0x610C,
0x3838,
0x1FF0,
0x07C0,
0x0000
};


正如所看到的那样,这些C语言常量并未有提供有关图形实际模样的任何线索。这里有一个惊人的#define定义的优雅集合,允许程序建立常量使它们看上去像是屏幕上的图形。

#define X )*2+1
#define _ )*2
#define s ((((((((((((((((0 /* For building glyphs 16 bits wide */


定义了它们之后,只要画所需要的图标或者图形等,程序会自动创建它们的十六进制模式。使用这些宏定义,程序的自描述能力大大加强,上面这个例子可以转变为:

static unsigned short stopwatch[] =
{
s _ _ _ _ _ X X X X X _ _ _ X X _ ,
s _ _ _ X X X X X X X X X _ X X X ,
s _ _ X X X _ _ _ _ _ X X X _ X X ,
s _ X X _ _ _ _ _ _ _ _ _ X X _ _ ,
s _ X X _ _ _ _ _ _ _ _ _ X X _ _ ,
s X X _ _ _ _ _ _ _ _ _ _ _ X X _ ,
s X X _ _ _ _ _ _ _ _ _ _ _ X X _ ,
s X X _ X X X X X _ _ _ _ _ X X _ ,
s X X _ _ _ _ _ X _ _ _ _ _ X X _ ,
s X X _ _ _ _ _ X _ _ _ _ _ X X _ ,
s _ X X _ _ _ _ X _ _ _ _ X X _ _ ,
s _ X X _ _ _ _ X _ _ _ _ X X _ _ ,
s _ _ X X X _ _ _ _ _ X X X _ _ _ ,
s _ _ _ X X X X X X X X X _ _ _ _ ,
s _ _ _ _ _ X X X X X _ _ _ _ _ _ ,
s _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
};


显然,与前面的代码相比,它的意思更为明显。标准的C语言具有八进制、十进制和十六进制常量,但没有二进制常量,否则的话倒是一种更为简单的绘制图形模式的方法。

如果抓住书的右上角,并斜这看这一页,可能会猜测这是一个用于流行窗口系统的“cursor busy”小秒表图形。我是在几年前从Usenet   comp.lang.c新闻组学到这个技巧的。

千万不要忘了在绘图结束后清除这些宏定义,否这很可能会给你后面的代码带来不可预测的后果。

好了,今天的废话就到这里了。水能载舟,亦能覆舟,把握好手中的双刃剑,让它好好的为你服务吧,别割破了手。:)

系统分类: 软件开发  |  用户分类: 编程语言  |  标签: 无标签  |  来源: 转贴  | 

点击查看原文

发表评论 阅读全文(425) | 回复(0)

发表于 2008/8/27 9:12:02

2

关于投票

C语言的联合(union)介绍

“联合”与“结构”有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间, 一个结构变量的总长度是各成员长度之和。而在“联合”中,各成员共享一段内存空间, 一个联合变量的长度等于各成员中最长的长度。应该说明的是, 这里所谓的共享不是指把多个成员同时装入一个联合变量内, 而是指该联合变量可被赋予任一成员值,但每次只能赋一种值, 赋入新值则冲去旧值。如前面介绍的“单位”变量, 如定义为一个可装入“班级”或“教研室”的联合后,就允许赋予整型值(班级)或字符串(教研室)。要么赋予整型值,要么赋予字符串,不能把两者同时赋予它。联合类型的定义和联合变量的说明一个联合类型必须经过定义之后, 才能把变量说明为该联合类型。 

一、联合的定义 

定义一个联合类型的一般形式为:  
union 联合名  
{  
成员表  
}; 
成员表中含有若干成员,成员的一般形式为: 类型说明符 成员名 成员名的命名应符合标识符的规定。 
例如:  
union perdata 

   int class; 
   char office; 
}; 
  定义了一个名为perdata的联合类型,它含有两个成员,一个为整型,成员名为class;另一个为字符数组,数组名为office。联合定义之后,即可进行联合变量说明,被说明为perdata类型的变量,可以存放整型量class或存放字符数组office。 

二、联合变量的说明 

  联合变量的说明和结构变量的说明方式相同, 也有三种形式。即先定义,再说明;定义同时说明和直接说明。以perdata类型为例,说明如下:  
union perdata 

   int class; 
   char officae; 
}; 
union perdata a,b; /*说明a,b为perdata类型*/ 
或者可同时说明为:  
union perdata 
{  
int class; 
char office;  
}a,b; 
或直接说明为:  
union 
{  
int class; 
char office;  
}a,b  
经说明后的a,b变量均为perdata类型。 它们的内存分配示意图如图7—8所示。a,b变量的长度应等于 perdata 的成员中最长的长度, 即等于 
office数组的长度,共10个字节。从图中可见,a,b变量如赋予整型值时,只使用了2个字节,而赋予字符数组时,可用10个字节。 

联合变量的赋值和使用 

  对联合变量的赋值,使用都只能是对变量的成员进行。 联合变量的成员表示为: 联合变量名.成员名 例如,a被说明为perdata类型的变量之后,可使用 a.class a.office 不允许只用联合变量名作赋值或其它操作。 也不允许对联合变量作初始化赋值,赋值只能在程序中进行?挂偾康魉得鞯氖?一个联合变量, 每次只能赋予一个成员值?痪浠八?一个联合变量的值就是联合变员的某一个成员值。 
[例7.15]设有一个教师与学生通用的表格,教师数据有姓名,年龄,职业,教研室四项。学生有姓名,年龄,职业,班级四项。 
编程输入人员数据, 再以表格输出。 
[code:1:8d8ee8c82c] 
main() 

   struct 
   { 
      char name[10]; 
      int age; 
      char job; 
      union 
      { 
         int class; 
         char office[10]; 
      } depa; 
   }body[2]; 
int n,i; 
   for(i=0;i<2;i++) 
   { 
      printf("input name,age,job and department\n"); 
      scanf("%s %d %c",body[i].name,&body[i].age,&body[i].job); 
      if(body[i].job=='s') 
         scanf("%d",&body[i].depa.class); 
      else 
         scanf("%s",body[i].depa.office); 
   } 
   printf("name\tage job class/office\n"); 
   for(i=0;i<2;i++) 
   { 
   if(body[i].job=='s') 
      printf("%s\t%3d %3c %d\n",body[i].name,body[i].age ,body[i].job,body[i].depa.class); 
   else 
      printf("%s\t%3d %3c %s\n",body[i].name,body[i].age, body[i].job,body[i].depa.office); 
   } 

[/code:1:8d8ee8c82c] 
  本例程序用一个结构数组body来存放人员数据, 该结构共有四个成员。其中成员项depa是一个联合类型, 这个联合又由两个成员组成,一个为整型量class,一个为字符数组office。在程序的第一个for语句中,输入人员的各项数据,先输入结构的前三个成员name,age和job,然后判别job成员项,如为"s"则对联合depa·class输入(对学生赋班级编号)否则对depa·office输入(对教师赋教研组名)。 

  在用scanf语句输入时要注意,凡为数组类型的成员,无论是结构成员还是联合成员,在该项前不能再加"&"运算符。如程序第18行中 
body[i].name是一个数组类型,第22行中的body[i].depa.office也是数组类型,因此在这两项之间不能加"&"运算符。程序中的第二个for语句用于输出各成员项的值: 

本章小结 

1. 结构和联合是两种构造类型数据,是用户定义新数据类型的重要手段。结构和联合有很多的相似之处,它们都由成员组成。成员可以具有不同的数据类型。成员的表示方法相同。都可用三种方式作变量说明。 

2. 在结构中,各成员都占有自己的内存空间,它们是同时存在的。一个结构变量的总长度等于所有成员长度之和。在联合中,所有成员不能同时占用它的内存空间,它们不能同时存在。联合变量的长度等于最长的成员的长度。 

3. “.”是成员运算符,可用它表示成员项,成员还可用“->”运算符来表示。 

4. 结构变量可以作为函数参数,函数也可返回指向结构的指针变量。而联合变量不能作为函数参数,函数也不能返回指向联合的指针变量。但可以使用指向联合变量的指针,也可使用联合数组。 

5. 结构定义允许嵌套,结构中也可用联合作为成员,形成结构和联合的嵌套。 

6. 链表是一种重要的数据结构,它便于实现动态的存储分配。本章介绍是单向链表,还可组成双向链表,循环链表等。

系统分类: 软件开发  |  用户分类: 编程语言  |  标签: C语言 union  |  来源: 转贴  | 

点击查看原文

发表评论 阅读全文(5465) | 回复(1)

发表于 2008/8/27 9:12:01

2

关于投票

C语言的联合(union)介绍

“联合”与“结构”有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间, 一个结构变量的总长度是各成员长度之和。而在“联合”中,各成员共享一段内存空间, 一个联合变量的长度等于各成员中最长的长度。应该说明的是, 这里所谓的共享不是指把多个成员同时装入一个联合变量内, 而是指该联合变量可被赋予任一成员值,但每次只能赋一种值, 赋入新值则冲去旧值。如前面介绍的“单位”变量, 如定义为一个可装入“班级”或“教研室”的联合后,就允许赋予整型值(班级)或字符串(教研室)。要么赋予整型值,要么赋予字符串,不能把两者同时赋予它。联合类型的定义和联合变量的说明一个联合类型必须经过定义之后, 才能把变量说明为该联合类型。 

一、联合的定义 

定义一个联合类型的一般形式为:  
union 联合名  
{  
成员表  
}; 
成员表中含有若干成员,成员的一般形式为: 类型说明符 成员名 成员名的命名应符合标识符的规定。 
例如:  
union perdata 

   int class; 
   char office; 
}; 
  定义了一个名为perdata的联合类型,它含有两个成员,一个为整型,成员名为class;另一个为字符数组,数组名为office。联合定义之后,即可进行联合变量说明,被说明为perdata类型的变量,可以存放整型量class或存放字符数组office。 

二、联合变量的说明 

  联合变量的说明和结构变量的说明方式相同, 也有三种形式。即先定义,再说明;定义同时说明和直接说明。以perdata类型为例,说明如下:  
union perdata 

   int class; 
   char officae; 
}; 
union perdata a,b; /*说明a,b为perdata类型*/ 
或者可同时说明为:  
union perdata 
{  
int class; 
char office;  
}a,b; 
或直接说明为:  
union 
{  
int class; 
char office;  
}a,b  
经说明后的a,b变量均为perdata类型。 它们的内存分配示意图如图7—8所示。a,b变量的长度应等于 perdata 的成员中最长的长度, 即等于 
office数组的长度,共10个字节。从图中可见,a,b变量如赋予整型值时,只使用了2个字节,而赋予字符数组时,可用10个字节。 

联合变量的赋值和使用 

  对联合变量的赋值,使用都只能是对变量的成员进行。 联合变量的成员表示为: 联合变量名.成员名 例如,a被说明为perdata类型的变量之后,可使用 a.class a.office 不允许只用联合变量名作赋值或其它操作。 也不允许对联合变量作初始化赋值,赋值只能在程序中进行?挂偾康魉得鞯氖?一个联合变量, 每次只能赋予一个成员值?痪浠八?一个联合变量的值就是联合变员的某一个成员值。 
[例7.15]设有一个教师与学生通用的表格,教师数据有姓名,年龄,职业,教研室四项。学生有姓名,年龄,职业,班级四项。 
编程输入人员数据, 再以表格输出。 
[code:1:8d8ee8c82c] 
main() 

   struct 
   { 
      char name[10]; 
      int age; 
      char job; 
      union 
      { 
         int class; 
         char office[10]; 
      } depa; 
   }body[2]; 
int n,i; 
   for(i=0;i<2;i++) 
   { 
      printf("input name,age,job and department\n"); 
      scanf("%s %d %c",body[i].name,&body[i].age,&body[i].job); 
      if(body[i].job=='s') 
         scanf("%d",&body[i].depa.class); 
      else 
         scanf("%s",body[i].depa.office); 
   } 
   printf("name\tage job class/office\n"); 
   for(i=0;i<2;i++) 
   { 
   if(body[i].job=='s') 
      printf("%s\t%3d %3c %d\n",body[i].name,body[i].age ,body[i].job,body[i].depa.class); 
   else 
      printf("%s\t%3d %3c %s\n",body[i].name,body[i].age, body[i].job,body[i].depa.office); 
   } 

[/code:1:8d8ee8c82c] 
  本例程序用一个结构数组body来存放人员数据, 该结构共有四个成员。其中成员项depa是一个联合类型, 这个联合又由两个成员组成,一个为整型量class,一个为字符数组office。在程序的第一个for语句中,输入人员的各项数据,先输入结构的前三个成员name,age和job,然后判别job成员项,如为"s"则对联合depa·class输入(对学生赋班级编号)否则对depa·office输入(对教师赋教研组名)。 

  在用scanf语句输入时要注意,凡为数组类型的成员,无论是结构成员还是联合成员,在该项前不能再加"&"运算符。如程序第18行中 
body[i].name是一个数组类型,第22行中的body[i].depa.office也是数组类型,因此在这两项之间不能加"&"运算符。程序中的第二个for语句用于输出各成员项的值: 

本章小结 

1. 结构和联合是两种构造类型数据,是用户定义新数据类型的重要手段。结构和联合有很多的相似之处,它们都由成员组成。成员可以具有不同的数据类型。成员的表示方法相同。都可用三种方式作变量说明。 

2. 在结构中,各成员都占有自己的内存空间,它们是同时存在的。一个结构变量的总长度等于所有成员长度之和。在联合中,所有成员不能同时占用它的内存空间,它们不能同时存在。联合变量的长度等于最长的成员的长度。 

3. “.”是成员运算符,可用它表示成员项,成员还可用“->”运算符来表示。 

4. 结构变量可以作为函数参数,函数也可返回指向结构的指针变量。而联合变量不能作为函数参数,函数也不能返回指向联合的指针变量。但可以使用指向联合变量的指针,也可使用联合数组。 

5. 结构定义允许嵌套,结构中也可用联合作为成员,形成结构和联合的嵌套。 

6. 链表是一种重要的数据结构,它便于实现动态的存储分配。本章介绍是单向链表,还可组成双向链表,循环链表等。

系统分类: 软件开发  |  用户分类: 编程语言  |  标签: C语言 union  |  来源: 转贴  | 

点击查看原文

发表评论 阅读全文(421) | 回复(0)

发表于 2008/8/27 9:09:52

2

关于投票

C/C++语言void及void指针深层探索

1.概述 

许多初学者对C/C++语言中的void及void指针类型不甚理解,因此在使用上出现了一些错误。本文将对void关键字的深刻含义进行解说,并详述void及void指针类型的使用方法与技巧。 

2.void的含义 

void的字面意思是“无类型”,void *则为“无类型指针”,void *可以指向任何类型的数据。 

void几乎只有“注释”和限制程序的作用,因为从来没有人会定义一个void变量,让我们试着来定义: 

void a; 

这行语句编译时会出错,提示“illegal use of type 'void'”。不过,即使void a的编译不会出错,它也没有任何实际意义。 

void真正发挥的作用在于: 

(1) 对函数返回的限定; 

(2) 对函数参数的限定。 

众所周知,如果指针p1和p2的类型相同,那么我们可以直接在p1和p2间互相赋值;如果p1和p2指向不同的数据类型,则必须使用强制类型转换运算符把赋值运算符右边的指针类型转换为左边指针的类型。 

例如: 

float *p1; 

int *p2; 

1 = p2; 

其中p1 = p2语句会编译出错,提示“'=' : cannot convert from 'int *' to 'float *'”,必须改为: 

1 = (float *)p2; 

而void *则不同,任何类型的指针都可以直接赋值给它,无需进行强制类型转换: 

void *p1; 

int *p2; 

1 = p2; 

但这并不意味着,void *也可以无需强制类型转换地赋给其它类型的指针。因为“无类型”可以包容“有类型”,而“有类型”则不能包容“无类型”。道理很简单,我们可以说“男人和女人都是人”,但不能说“人是男人”或者“人是女人”。下面的语句编译出错: 

void *p1; 

int *p2; 

2 = p1; 

提示“'=' : cannot convert from 'void *' to 'int *'”。 

3.void的使用 

下面给出void关键字的使用规则: 

规则一 如果函数没有返回值,那么应声明为void类型 

在C语言中,凡不加返回值类型限定的函数,就会被编译器作为返回整型值处理。但是许多程序员却误以为其为void类型。例如: 

add ( int a, int b ) 



return a + b; 



int main(int argc, char* argv[]) 



rintf ( "2 + 3 = %d", add ( 2, 3) ); 



程序运行的结果为输出: 

2 + 3 = 5 

这说明不加返回值说明的函数的确为int函数。 

林锐博士《高质量C/C++编程》中提到:“C++语言有很严格的类型安全检查,不允许上述情况(指函数不加类型声明)发生”。可是编译器并不一定这么认定,譬如在Visual C++6.0中上述add函数的编译无错也无警告且运行正确,所以不能寄希望于编译器会做严格的类型检查。 

因此,为了避免混乱,我们在编写C/C++程序时,对于任何函数都必须一个不漏地指定其类型。如果函数没有返回值,一定要声明为void类型。这既是程序良好可读性的需要,也是编程规范性的要求。另外,加上void类型声明后,也可以发挥代码的“自注释”作用。代码的“自注释”即代码能自己注释自己。 

规则二 如果函数无参数,那么应声明其参数为void 

在C++语言中声明一个这样的函数: 

int function(void) 



return 1; 



则进行下面的调用是不合法的: 

function(2); 

因为在C++中,函数参数为void的意思是这个函数不接受任何参数。 

我们在Turbo C 2.0中编译: 

#include "stdio.h" 

fun() 



return 1; 



main() 



rintf("%d",fun(2)); 

getchar(); 



编译正确且输出1,这说明,在C语言中,可以给无参数的函数传送任意类型的参数,但是在C++编译器中编译同样的代码则会出错。在C++中,不能向无参数的函数传送任何参数,出错提示“'fun' : function does not take 1 parameters”。 

所以,无论在C还是C++中,若函数不接受任何参数,一定要指明参数为void。 

规则三 小心使用void指针类型 

按照ANSI(American National Standards Institute)标准,不能对void指针进行算法操作,即下列操作都是不合法的: 

void * pvoid; 

void++; //ANSI:错误 

void += 1; //ANSI:错误 

//ANSI标准之所以这样认定,是因为它坚持:进行算法操作的指针必须是确定知道其指向数据类型大小的。 

//例如: 

int *pint; 

int++; //ANSI:正确 

int++的结果是使其增大sizeof(int)。 

但是大名鼎鼎的GNU(GNU's Not Unix的缩写)则不这么认定,它指定void *的算法操作与char *一致。 

因此下列语句在GNU编译器中皆正确: 

void++; //GNU:正确 

void += 1; //GNU:正确 

void++的执行结果是其增大了1。 

在实际的程序设计中,为迎合ANSI标准,并提高程序的可移植性,我们可以这样编写实现同样功能的代码: 

void * pvoid; 

(char *)pvoid++; //ANSI:正确;GNU:正确 

(char *)pvoid += 1; //ANSI:错误;GNU:正确 

GNU和ANSI还有一些区别,总体而言,GNU较ANSI更“开放”,提供了对更多语法的支持。但是我们在真实设计时,还是应该尽可能地迎合ANSI标准。 

规则四 如果函数的参数可以是任意类型指针,那么应声明其参数为void * 

典型的如内存操作函数memcpy和memset的函数原型分别为: 

void * memcpy(void *dest, const void *src, size_t len); 

void * memset ( void * buffer, int c, size_t num ); 

这样,任何类型的指针都可以传入memcpy和memset中,这也真实地体现了内存操作函数的意义,因为它操作的对象仅仅是一片内存,而不论这片内存是什么类型。如果memcpy和memset的参数类型不是void *,而是char *,那才叫真的奇怪了!这样的memcpy和memset明显不是一个“纯粹的,脱离低级趣味的”函数! 

下面的代码执行正确: 

//示例:memset接受任意类型指针 

int intarray[100]; 

memset ( intarray, 0, 100*sizeof(int) ); //将intarray清0 

//示例:memcpy接受任意类型指针 

int intarray1[100], intarray2[100]; 

memcpy ( intarray1, intarray2, 100*sizeof(int) ); //将intarray2拷贝给intarray1 

有趣的是,memcpy和memset函数返回的也是void *类型,标准库函数的编写者是多么地富有学问啊! 

规则五 void不能代表一个真实的变量 

下面代码都企图让void代表一个真实的变量,因此都是错误的代码: 

void a; //错误 

function(void a); //错误 

void体现了一种抽象,这个世界上的变量都是“有类型”的,譬如一个人不是男人就是女人(还有人妖?)。 

void的出现只是为了一种抽象的需要,如果你正确地理解了面向对象中“抽象基类”的概念,也很容易理解void数据类型。正如不能给抽象基类定义一个实例,我们也不能定义一个void(让我们类比的称void为“抽象数据类型”)变量。 

4.总结 

小小的void蕴藏着很丰富的设计哲学,作为一名程序设计人员,对问题进行深一个层次的思考必然使我们受益匪浅。

系统分类: 软件开发  |  用户分类: 编程语言  |  标签: C++ 指针  |  来源: 转贴  | 

点击查看原文

发表评论 阅读全文(312) | 回复(0)

发表于 2008/8/11 21:40:23

2

关于投票

有限状态机的c实现

網絡上可以搜索到很多有限狀態機的代碼和理論分析,這兒僅僅是做一個簡單的例子,僅供入門參考。

这儿以四位密码校验作为状态机的例子,连续输入2479就可以通过密码测试。一个非常简单的例子,在实际的状态机实例中,状态转移表要更復雜一些,不過方式非常類似。在狀態查詢的地方可以做優化,同時對于輸入量也可以做有效性優化。具體代碼如下:
c.h

typedef enum{
STATE1 = 1,
STATE2,
STATE3,
STATE4,
STATE5,//password pass
//...ADD here
}STATE;

typedef enum{
INPUT1 = '2',
INPUT2 = '4',
INPUT3 = '7',
INPUT4 = '9',
}INPUT;

typedef struct
{
STATE cur_state;
INPUT input;
STATE next_state;
}STATE_TRANS;

c.c

#include   <stdio.h>  
#include "c.h"

STATE_TRANS state_trans_arry[] =
{
{STATE1,INPUT1,STATE2},
{STATE2,INPUT2,STATE3},
{STATE3,INPUT3,STATE4},
{STATE4,INPUT4,STATE5},
};
#define STATE_TRANS_CNT (sizeof(state_trans_arry)/sizeof(state_trans_arry[0]))

int main()  
{
int i;
char ch;
STATE state_machine = STATE1;

while(ch != 'e')
{
   ch = getchar();
   if((ch >= '0') && (ch <= '9'))//for digit password input only
   {
    for(i = 0;i < STATE_TRANS_CNT;i++)
    {
     if((ch == state_trans_arry[i].input) && (state_machine == state_trans_arry[i].cur_state))
     {
      state_machine = state_trans_arry[i].next_state;
      continue;
     }
     else if(i == (STATE_TRANS_CNT - 1))//no transfer match,reset state
     {
      state_machine = STATE1;
     }
    }
    if(state_machine == STATE5)
     printf("Password correct,state transfer machine pass!\n");
   }
}
return 0;
}

系统分类: 软件开发  |  用户分类: 编程语言  |  标签: 状态机 C语言  |  来源: 转贴  | 

点击查看原文

发表评论 阅读全文(1179) | 回复(0)

发表于 2008/8/11 12:43:52

2

关于投票

深入理解C语言指针的奥秘

                                              深入理解C语言指针的奥秘


指针的概念

  指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址。 要搞清一个指针需要搞清指针的四方面的内容:指针的类型,指针所指向的 类型,指针的值或者叫指针所指向的内存区,还有指针本身所占据的内存区。让我们分别说明。  先声明几个指针放着做例子:

  例一:

  (1)int*ptr;

  (2)char*ptr;

  (3)int**ptr;

  (4)int(*ptr)[3];

  (5)int*(*ptr)[4];

  如果看不懂后几个例子的话,请参阅我前段时间贴出的文章<<如何理解c和c ++的复杂类型声明>>。

  指针的类型

  从语法的角度看,你只要把指针声明语句里的指针名字去掉,剩下的部分就是这个指针的类型。这是指针本身所具有的类型。让我们看看例一中各个指针的类型:

  (1)int*ptr;//指针的类型是int*

  (2)char*ptr;//指针的类型是char*

  (3)int**ptr;//指针的类型是int**

  (4)int(*ptr)[3];//指针的类型是int(*)[3]

  (5)int*(*ptr)[4];//指针的类型是int*(*)[4]

  怎么样?找出指针的类型的方法是不是很简单?

  指针所指向的类型

  当你通过指针来访问指针所指向的内存区时,指针所指向的类型决定了编译器将把那片内存区里的内容当做什么来看待。

  从语法上看,你只须把指针声明语句中的指针名字和名字左边的指针声明符*去掉,剩下的就是指针所指向的类型。例如:

  (1)int*ptr;//指针所指向的类型是int

  (2)char*ptr;//指针所指向的的类型是char

  (3)int**ptr;//指针所指向的的类型是int*

  (4)int(*ptr)[3];//指针所指向的的类型是int()[3]

  (5)int*(*ptr)[4];//指针所指向的的类型是int*()[4]

  在指针的算术运算中,指针所指向的类型有很大的作用。

  指针的类型(即指针本身的类型)和指针所指向的类型是两个概念。当你对C越来越熟悉时,你会发现,把与指针搅和在一起的"类型"这个概念分成"指针的类型"和"指针所指向的类型"两个概念,是精通指针的关键点之一。我看了不少书,发现有些写得差的书中,就把指针的这两个概念搅在一起了,所以看起书来前后矛盾,越看越糊涂。
  指针的值,或者叫指针所指向的内存区或地址

  指针的值是指针本身存储的数值,这个值将被编译器当作一个地址,而不是一个一般的数值。在32位程序里,所有类型的指针的值都是一个32位整数,因为32位程序里内存地址全都是32位长。 指针所指向的内存区就是从指针的值所代表的那个内存地址开始,长度为si zeof(指针所指向的类型)的一片内存区。以后,我们说一个指针的值是XX,就相当于说该指针指向了以XX为首地址的一片内存区域;我们说一个指针指向了某块内存区域,就相当于说该指针的值是这块内存区域的首地址。

  指针所指向的内存区和指针所指向的类型是两个完全不同的概念。在例一中,指针所指向的类型已经有了,但由于指针还未初始化,所以它所指向的内存区是不存在的,或者说是无意义的。

  以后,每遇到一个指针,都应该问问:这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?

  指针本身所占据的内存区

  指针本身占了多大的内存?你只要用函数sizeof(指针的类型)测一下就知道了。在32位平台里,指针本身占据了4个字节的长度。

  指针本身占据的内存这个概念在判断一个指针表达式是否是左值时很有用。

  指针的算术运算

  指针可以加上或减去一个整数。指针的这种运算的意义和通常的数值的加减运算的意义是不一样的。例如:

  例二:

  1、chara[20];

  2、int*ptr=a;

  ...

  ...

  3、ptr++;

  在上例中,指针ptr的类型是int*,它指向的类型是int,它被初始化为指向整形变量a。接下来的第3句中,指针ptr被加了1,编译器是这样处理的:它把指针ptr的值加上了sizeof(int),在32位程序中,是被加上了4。由于地址是用字节做单位的,故ptr所指向的地址由原来的变量a的地址向高地址方向增加了4个字节。
由于char类型的长度是一个字节,所以,原来ptr是指向数组a的第0号单元开始的四个字节,此时指向了数组a中从第4号单元开始的四个字节。

  我们可以用一个指针和一个循环来遍历一个数组,看例子:

  例三:


intarray[20];
int*ptr=array;
...
//此处略去为整型数组赋值的代码。
...
for(i=0;i<20;i++)
{
 (*ptr)++;
 ptr++;
}


这个例子将整型数组中各个单元的值加1。由于每次循环都将指针ptr加1,所以每次循环都能访问数组的下一个单元。

  再看例子:

  例四:

  1、chara[20];

  2、int*ptr=a;

  ...
  ...

  3、ptr+=5;

  在这个例子中,ptr被加上了5,编译器是这样处理的:将指针ptr的值加上5乘sizeof(int),在32位程序中就是加上了5乘4=20。由于地址的单位是字节,故现在的ptr所指向的地址比起加5后的ptr所指向的地址来说,向高地址方向移动了20个字节。在这个例子中,没加5前的ptr指向数组a的第0号单元开始的四个字节,加5后,ptr已经指向了数组a的合法范围之外了。虽然这种情况在应用上会出问题,但在语法上却是可以的。这也体现出了指针的灵活性。

  如果上例中,ptr是被减去5,那么处理过程大同小异,只不过ptr的值是被减去5乘sizeof(int),新的ptr指向的地址将比原来的ptr所指向的地址向低地址方向移动了20个字节。

  总结一下,一个指针ptrold加上一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值增加了n乘sizeof(ptrold所指向的类型)个字节。就是说,ptrnew所指向的内存区将比ptrold所指向的内存区向高地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。

  一个指针ptrold减去一个整数n后,结果是一个新的指针ptrnew,ptrnew的类型和ptrold的类型相同,ptrnew所指向的类型和ptrold所指向的类型也相同。ptrnew的值将比ptrold的值减少了n乘sizeof(ptrold所指向的类型)个字节,就是说,ptrnew所指向的内存区将比ptrold所指向的内存区向低地址方向移动了n乘sizeof(ptrold所指向的类型)个字节。

  运算符&和*


  这里&是取地址运算符,*是...书上叫做"间接运算符"。

  &a的运算结果是一个指针,指针的类型是a的类型加个*,指针所指向的类型是a的类型,指针所指向的地址嘛,那就是a的地址。

  *p的运算结果就五花八门了。总之*p的结果是p所指向的东西,这个东西有这些特点:它的类型是p指向的类型,它所占用的地址是p所指向的地址。

  例五:


inta=12;
intb;
int*p;
int**ptr;
p=&a;
//&a的结果是一个指针,类型是int*,指向的类型是int,指向的地址是a的地址。
*p=24;
//*p的结果,在这里它的类型是int,它所占用的地址是p所指向的地址,显然,*p就是变量a。
ptr=&p;
//&p的结果是个指针,该指针的类型是p的类型加个*,在这里是int **。该指针所指向的类型是p的类型,这里是int*。该指针所指向的地址就是指针p自己的地址。
*ptr=&b;
//*ptr是个指针,&b的结果也是个指针,且这两个指针的类型和所指向的类型是一样的,所以用&b来给*ptr赋值就是毫无问题的了。
**ptr=34;
//*ptr的结果是ptr所指向的东西,在这里是一个指针,对这个指针再做一次*运算,结果就是一个int类型的变量。 


  指针表达式

  一个表达式的最后结果如果是一个指针,那么这个表达式就叫指针表式。

  下面是一些指针表达式的例子:

  例六:


inta,b;
intarray[10];
int*pa;
pa=&a;//&a是一个指针表达式。
int**ptr=&pa;//&pa也是一个指针表达式。
*ptr=&b;//*ptr和&b都是指针表达式。
pa=array;
pa++;//这也是指针表达式。 


  例七:


char*arr[20];
char**parr=arr;//如果把arr看作指针的话,arr也是指针表达式
char*str;
str=*parr;//*parr是指针表达式
str=*(parr+1);//*(parr+1)是指针表达式
str=*(parr+2);//*(parr+2)是指针表达式 


  由于指针表达式的结果是一个指针,所以指针表达式也具有指针所具有的四个要素:指针的类型,指针所指向的类型,指针指向的内存区,指针自身占据的内存。

  好了,当一个指针表达式的结果指针已经明确地具有了指针自身占据的内存的话,这个指针表达式就是一个左值,否则就不是一个左值。

  在例七中,&a不是一个左值,因为它还没有占据明确的内存。*ptr是一个左值,因为*ptr这个指针已经占据了内存,其实*ptr就是指针pa,既然pa已经在内存中有了自己的位置,那么*ptr当然也有了自己的位置。

  数组和指针的关系

  如果对声明数组的语句不太明白的话,请参阅我前段时间贴出的文章<<如何理解c和c++的复杂类型声明>>。

  数组的数组名其实可以看作一个指针。看下例:

  例八:


intarray[10]={0,1,2,3,4,5,6,7,8,9},value;
...
...
value=array[0];//也可写成:value=*array;
value=array[3];//也可写成:value=*(array+3);
value=array[4];//也可写成:value=*(array+4); 


  上例中,一般而言数组名array代表数组本身,类型是int[10],但如果把array看做指针的话,它指向数组的第0个单元,类型是int*,所指向的类型是数组单元的类型即int。因此*array等于0就一点也不奇怪了。同理,array+3是一个指向数组第3个单元的指针,所以*(array+3)等于3。其它依此类推。

又请问怎样通过指针pstr来访问ss的三个成员变量?

  答案:

*pstr;//访问了ss的成员a。
*(pstr+1);//访问了ss的成员b。
*(pstr+2)//访问了ss的成员c。

  虽然我在我的MSVC++6.0上调式过上述代码,但是要知道,这样使用pstr来访问结构成员是不正规的,为了说明为什么不正规,让我们看看怎样通过指针来访问数组的各个单元:

  例十二:

intarray[3]={35,56,37};
int*pa=array;

  通过指针pa访问数组array的三个单元的方法是:

*pa;//访问了第0号单元
*(pa+1);//访问了第1号单元
*(pa+2);//访问了第2号单元

  从格式上看倒是与通过指针访问结构成员的不正规方法的格式一样。

  所有的C/C++编译器在排列数组的单元时,总是把各个数组单元存放在连续的存储区里,单元和单元之间没有空隙。但在存放结构对象的各个成员时,在某种编译环境下,可能会需要字对齐或双字对齐或者是别的什么对齐,需要在相邻两个成员之间加若干个"填充字节",这就导致各个成员之间可能会有若干个字节的空隙。

  所以,在例十二中,即使*pstr访问到了结构对象ss的第一个成员变量a,也不能保证*(pstr+1)就一定能访问到结构成员b。因为成员a和成员b之间可能会有若干填充字节,说不定*(pstr+1)就正好访问到了这些填充字节呢。这也证明了指针的灵活性。要是你的目的就是想看看各个结构成员之间到底有没有填充字节,嘿,这倒是个不错的方法。

  通过指针访问结构成员的正确方法应该是象例十二中使用指针ptr的方法。

  指针和函数的关系

  可以把一个指针声明成为一个指向函数的指针。


intfun1(char*,int);
int(*pfun1)(char*,int);
pfun1=fun1;
....
....
inta=(*pfun1)("abcdefg",7);//通过函数指针调用函数。 


  可以把指针作为函数的形参。在函数调用语句中,可以用指针表达式来作为实参。

  例十三:


intfun(char*);
inta;
charstr[]="abcdefghijklmn";
a=fun(str);
...
...
intfun(char*s)
{
intnum=0;
for(inti=0;i{
num+=*s;s++;
}
returnnum;


  这个例子中的函数fun统计一个字符串中各个字符的ASCII码值之和。前面说了,数组的名字也是一个指针。在函数调用中,当把str作为实参传递给形参s后,实际是把str的值传递给了s,s所指向的地址就和str所指向的地址一致,但是str和s各自占用各自的存储空间。在函数体内对s进行自加1运算,并不意味着同时对str进行了自加1运算。

指针类型转换


  当我们初始化一个指针或给一个指针赋值时,赋值号的左边是一个指针,赋值号的右边是一个指针表达式。在我们前面所举的例子中,绝大多数情况下,指针的类型和指针表达式的类型是一样的,指针所指向的类型和指针表达式所指向的类型是一样的。

  例十四:

  1、floatf=12.3;

  2、float*fptr=&f;

  3、int*p;
 
  在上面的例子中,假如我们想让指针p指向实数f,应该怎么搞?是用下面的语句吗?

  p=&f;

  不对。因为指针p的类型是int*,它指向的类型是int。表达式&f的结果是一个指针,指针的类型是float*,它指向的类型是float。两者不一致,直接赋值的方法是不行的。至少在我的MSVC++6.0上,对指针的赋值语句要求赋值号两边的类型一致,所指向的类型也一致,其它的编译器上我没试过,大家可以试试。为了实现我们的目的,需要进行"强制类型转换":


p=(int*)&f; 


  如果有一个指针p,我们需要把它的类型和所指向的类型改为TYEP*TYPE, 那么语法格式是:

  (TYPE*)p;

  这样强制类型转换的结果是一个新指针,该新指针的类型是TYPE*,它指向的类型是TYPE,它指向的地址就是原指针指向的地址。而原来的指针p的一切属性都没有被修改。

  一个函数如果使用了指针作为形参,那么在函数调用语句的实参和形参的结合过程中,也会发生指针类型的转换。

  例十五:


voidfun(char*);
inta=125,b;
fun((char*)&a);
...
...
voidfun(char*s)
{
charc;
c=*(s+3);*(s+3)=*(s+0);*(s+0)=c;
c=*(s+2);*(s+2)=*(s+1);*(s+1)=c;
}


  注意这是一个32位程序,故int类型占了四个字节,char类型占一个字节。函数fun的作用是把一个整数的四个字节的顺序来个颠倒。注意到了吗?在函数调用语句中,实参&a的结果是一个指针,它的类型是int*,它指向的类型是int。形参这个指针的类型是char*,它指向的类型是char。这样,在实参和形参的结合过程中,我们必须进行一次从int*类型到char*类型的转换。结合这个例子,我们可以这样来想象编译器进行转换的过程:编译器先构造一个临时指针char*temp, 然后执行temp=(char*)&a,最后再把temp的值传递给s。所以最后的结果是:s的类型是char*,它指向的类型是char,它指向的地址就是a的首地址。

  我们已经知道,指针的值就是指针指向的地址,在32位程序中,指针的值其实是一个32位整数。那可不可以把一个整数当作指针的值直接赋给指针呢?就象下面的语句:


unsignedinta;
TYPE*ptr;//TYPE是int,char或结构类型等等类型。
...
...
a=20345686;
ptr=20345686;//我们的目的是要使指针ptr指向地址20345686(十进制

ptr=a;//我们的目的是要使指针ptr指向地址20345686(十进制) 


  编译一下吧。结果发现后面两条语句全是错的。那么我们的目的就不能达到了吗?不,还有办法:


unsignedinta;
TYPE*ptr;//TYPE是int,char或结构类型等等类型。
...
...
a=某个数,这个数必须代表一个合法的地址;
ptr=(TYPE*)a;//呵呵,这就可以了。 


  严格说来这里的(TYPE*)和指针类型转换中的(TYPE*)还不一样。这里的(TYPE*)的意思是把无符号整数a的值当作一个地址来看待。上面强调了a的值必须代表一个合法的地址,否则的话,在你使用ptr的时候,就会出现非法操作错误。

  想想能不能反过来,把指针指向的地址即指针的值当作一个整数取出来。完 全可以。下面的例子演示了把一个指针的值当作一个整数取出来,然后再把这个整数当作一个地址赋给一个指针:

  例十六:


inta=123,b;
int*ptr=&a;
char*str;
b=(int)ptr;//把指针ptr的值当作一个整数取出来。
str=(char*)b;//把这个整数的值当作一个地址赋给指针str。 


  现在我们已经知道了,可以把指针的值当作一个整数取出来,也可以把一个整数值当作地址赋给一个指针。

指针的安全问题

  看下面的例子:

  例十七:


chars='a';
int*ptr;
ptr=(int*)&s;
*ptr=1298; 

 

  指针ptr是一个int*类型的指针,它指向的类型是int。它指向的地址就是s的首地址。在32位程序中,s占一个字节,int类型占四个字节。最后一条语句不但改变了s所占的一个字节,还把和s相临的高地址方向的三个字节也改变了。这三个字节是干什么的?只有编译程序知道,而写程序的人是不太可能知道的。也许这三个字节里存储了非常重要的数据,也许这三个字节里正好是程序的一条代码,而由于你对指针的马虎应用,这三个字节的值被改变了!这会造成崩溃性的错误。

  让我们再来看一例:

  例十八:

  1、chara;

  2、int*ptr=&a;

  ...
  ...

  3、ptr++;

  4、*ptr=115;

  该例子完全可以通过编译,并能执行。但是看到没有?第3句对指针ptr进行自加1运算后,ptr指向了和整形变量a相邻的高地址方向的一块存储区。这块存储区里是什么?我们不知道。有可能它是一个非常重要的数据,甚至可能是一条代码。而第4句竟然往这片存储区里写入一个数据!这是严重的错误。所以在使用指针时,程序员心里必须非常清楚:我的指针究竟指向了哪里。在用指针访问数组的时候,也要注意不要超出数组的低端和高端界限,否则也会造成类似的错误。

  在指针的强制类型转换:ptr1=(TYPE*)ptr2中,如果sizeof(ptr2的类型)大于sizeof(ptr1的类型),那么在使用指针ptr1来访问ptr2所指向的存储区时是安全的。如果sizeof(ptr2的类型)小于sizeof(ptr1的类型),那么在使用指针ptr1来访问ptr2所指向的存储区时是不安全的。至于为什么,读者结合例十七来想一想,应该会明白的。

 

 

 

系统分类: 软件开发  |  用户分类: 编程语言  |  标签: C语言 指针  |  来源: 转贴  | 

点击查看原文

发表评论 阅读全文(336) | 回复(0)

发表于 2008/5/17 0:57:50

2

关于投票

谈谈我对C51语言的看法

目前单片机的编程中,C51语言可以说是十分盛行,这种语言我也用过,但基本上都是用汇编语言.
C51语言的好处是可读性强、移植性好、浮点运算,除此之外没有别的好处了.我们不妨将两种语言作一个比较:
1、效率方面:我曾对编译后的C51语言分析过,可以发现由它生成的代码数量,至少是汇编语言的两倍以上,有的甚至高达十倍以上.占用空间大了,效率自然低.
2、移植性:C51的可移植性是比汇编好一些,但是单片机程序不同于微机控件程序那样通用,几乎没有一个程序是可以到处移植的.单片机中最常用的移植程序大概就是运算子程序了,在这方面,汇编格式的运算子程序移植起来一个也不比C51逊色.
3、浮点运算:以我的感觉,单片机不适合采用浮点运算,我曾经做过试验,编译了一个1.2+2.3的浮点运算,编译后代码长达2K多,完成这个运算所需要的时间就不用说了.我从八四年就开始研究单片机,到现在做过的系统中,含有单片机的超过五十项,所有系统中的运算,全部都采用定点运算,几个定点子程序成了法宝,代码效率非常高,请看下面的一段:

;W = W130 * (130 / Dia) * (130 / Dia) = W130 * 16900 / (Dia * Dia)
CalAndMem:      MOV     R5, SetPie130War1H
                MOV     R4, SetPie130War1L
                MOV     R3, #HIGH 16900
                MOV     R2, #LOW 16900
                CALL    QKMUL                           ;W130 * 16900
;双字节乘法子程QKMUL
;(R3、R2)*(R5、R4)=R7、R6、R5、R4
;R3、R5、R7为高字节
                MOV     A, SetDiamt
                MOV     B, A
                MUL     AB
                MOV     R3, B
                MOV     R2, A
                CALL    NDIV                            ; /(Dia * Dia)
;二进制除法子程:NDIV
;入口:R7654=被除数,R7高字节,R32=除数,R3高字节
;出口:R76=余数,R7高字节,R54=商,R5高字节
;R765432用于参数传递,不用保护
                MOV     SetPieWar1H, R5                 ;当前缸径下的压力报警点
                MOV     SetPieWar1L, R4

--------
其中的子程序都是网上的成品,非常精简.

4、实时性:用汇编编程序的人都知道,单片机的实时性要求很高,经常需要计算这段程序需要多长时间,什么时间来中断,中断后执行多长时间,对原程序有没有妨碍,等等,这都是C51程序难以做到的.
5、节省内存:当然,这和代码效率是成反比的,同样的内存所能完成的功能也就大大提高.
永远记住:高性能的代码是用汇编语言编写出来的,而不是用高级语言生成的,高质量的PCB是有经验的工程技术人员用手工排出来的,而不是软件自动布线出来的.

系统分类: 单片机  |  用户分类: 编程语言  |  标签: C51  |  来源: 转贴  | 

点击查看原文

发表评论 阅读全文(761) | 回复(0)

发表于 2008/5/9 0:50:23

1

关于投票

华为内部员工C++中级培训教材

点击下载华为内部员工C++中级培训教材

系统分类: 资源共享  |  用户分类: 编程语言  |  标签: C++  |  来源: 整理  | 

点击查看原文

发表评论 阅读全文(639) | 回复(1)

发表于 2008/5/9 0:49:15

1

关于投票

C 语言常见问题集

点击下载C 语言常见问题集

系统分类: 资源共享  |  用户分类: 编程语言  |  标签: 语言  |  来源: 整理  | 

点击查看原文

发表评论 阅读全文(417) | 回复(0)

发表于 2008/5/9 0:42:26

1

关于投票

C++实践之路

点击下载C++实践之路.part1

点击下载C++实践之路.part2

系统分类: 资源共享  |  用户分类: 编程语言  |  标签: C++  |  来源: 整理  | 

点击查看原文

发表评论 阅读全文(446) | 回复(0)

发表于 2008/5/9 0:34:25

1

关于投票

Secure Programming Cookbook for C and C++

点击下载Secure Programming Cookbook for C and C++.part1

点击下载Secure Programming Cookbook for C and C++.part2

 

 

系统分类: 资源共享  |  用户分类: 编程语言  |  标签: and C++  |  来源: 整理  | 

点击查看原文

发表评论 阅读全文(517) | 回复(0)

发表于 2008/5/9 0:31:22

1

关于投票

C++ Primer Fourth Edition

点击下载C++ Primer Fourth Edition.part1

点击下载C++ Primer Fourth Edition.part2

系统分类: 资源共享  |  用户分类: 编程语言  |  标签: C++  |  来源: 整理  | 

点击查看原文

发表评论 阅读全文(477) | 回复(0)

2Next >Total , Page /