面试经验谈

来源:互联网 发布:pdfobject.min.js下载 编辑:程序博客网 时间:2024/04/30 00:24
 

以下是在项目面试中可能会提到过的一些问题:

 

1、        介绍一下自己所做过的一些项目,在项目中相应担当了那些工作?

a消息解析工具(市场调研分析(中创等),原型设计,需求分析,用户手册,概要设计,详细设计,单元测试(CPP Unit),安装工具等)。3人,主要研发人员。

b消息解析工具2(需求分析,用户手册完善,概要设计,详细设计,单元测试,系统测试,安装工具)。3人,PL

c协议栈开发与维护(MTP3/M3UA/MTP3B),自学SCTP/SCCP/TCAP/MTP2/SIGTRAN等。

2、        有没有做过白盒测试?测试手法是怎样的?测试实施的标准和依据是什么?

做过。UT测试(MTT工具,PYTHON语言)。

a对程序模块的所有独立的执行路径至少测试一遍。
b
对所有的逻辑判定,取与取的两种情况都能至少测一遍。
c
在循环的边界和运行的界限内执行循环体。
d
测试内部数据结构的有效性,等等。

3、        实时操作系统和一般的操作系统有什么不同?

嵌入式实时系统中采用的操作系统我们称为嵌入式实时操作系统,它既是嵌入式操作系统,又是实时操作系统。作为一种嵌入式操作系统,它具有嵌入式软件共有的可裁剪、低资源占用、低功耗等特点;而作为一种实时操作系统(本文对实时操作系统特性的讨论仅限于强实时操作系统,下面提到的实时操作系统也均指强实时操作系统),它与通用操作系统(如WindowsUnixLinux等)相比有很大的差别,下面我们将通过比较这两种操作系统之间的差别来逐步描述实时操作系统的主要特点。

我们在日常工作学习环境中接触最多的是通用操作系统,通用操作系统是由分时操作系统发展而来,大部分都支持多用户和多进程,负责管理众多的进程并为它们分配系统资源。分时操作系统的基本设计原则是:尽量缩短系统的平均响应时间并提高系统的吞吐率,在单位时间内为尽可能多的用户请求提供服务。由此可以看出,分时操作系统注重平均表现性能,不注重个体表现性能。如对于整个系统来说,注重所有任务的平均响应时间而不关心单个任务的响应时间,对于某个单个任务来说,注重每次执行的平均响应时间而不关心某次特定执行的响应时间。通用操作系统中采用的很多策略和技巧都体现出了这种设计原则,如虚存管理机制中由于采用了LRU等页替换算法,使得大部分的访存需求能够快速地通过物理内存完成,只有很小一部分的访存需求需要通过调页完成,但从总体上来看,平均访存时间与不采用虚存技术相比没有很大的提高,同时又获得了虚空间可以远大于物理内存容量等好处,因此虚存技术在通用操作系统中得到了十分广泛的应用。类似的例子还有很多,如Unix文件系统中文件存放位置的间接索引查询机制等,甚至硬件设计中的Cache技术以及CPU的动态分支预测技术等也都体现出了这种设计原则。由此可见,这种注重平均表现,即统计型表现特性的设计原则的影响是十分深远的。

而对于实时操作系统,前面我们已经提到,它除了要满足应用的功能需求以外,更重要的是还要满足应用提出的实时性要求,而组成一个应用的众多实时任务对于实时性的要求是各不相同的,此外实时任务之间可能还会有一些复杂的关联和同步关系,如执行顺序限制、共享资源的互斥访问要求等,这就为系统实时性的保证带来了很大的困难。因此,实时操作系统所遵循的最重要的设计原则是:采用各种算法和策略,始终保证系统行为的可预测性(predictability)。可预测性是指在系统运行的任何时刻,在任何情况下,实时操作系统的资源调配策略都能为争夺资源(包括CPU、内存、网络带宽等)的多个实时任务合理地分配资源,使每个实时任务的实时性要求都能得到满足。与通用操作系统不同,实时操作系统注重的不是系统的平均表现,而是要求每个实时任务在最坏情况下都要满足其实时性要求,也就是说,实时操作系统注重的是个体表现,更准确地讲是个体最坏情况表现。举例来说,如果实时操作系统采用标准的虚存技术,则一个实时任务执行的最坏情况是每次访存都需要调页,如此累计起来的该任务在最坏情况下的运行时间是不可预测的,因此该任务的实时性无法得到保证。从而可以看出在通用操作系统中广泛采用的虚存技术在实时操作系统中不宜直接采用。

由于实时操作系统与通用操作系统的基本设计原则差别很大,因此在很多资源调度策略的选择上以及操作系统实现的方法上两者都具有较大的差异,这些差异主要体现在以下几点:

(1)    任务调度策略:

通用操作系统中的任务调度策略一般采用基于优先级的抢先式调度策略,对于优先级相同的进程则采用时间片轮转调度方式,用户进程可以通过系统调用动态地调整自己的优先级,操作系统也可根据情况调整某些进程的优先级。

实时操作系统中的任务调度策略目前使用最广泛的主要可分为两种,一种是静态表驱动方式,另一种是固定优先级抢先式调度方式。

静态表驱动方式是指在系统运行前工程师根据各任务的实时要求用手工的方式或在辅助工具的帮助下生成一张任务的运行时间表,这张时间表与列车的运行时刻表类似,指明了各任务的起始运行时间以及运行长度,运行时间表一旦生成就不再变化了,在运行时调度器只需根据这张表在指定的时刻启动相应的任务即可。静态表驱动方式的主要优点是:

?         运行时间表是在系统运行前生成的,因此可以采用较复杂的搜索算法找到较优的调度方案;

?         运行时调度器开销较小;

?         系统具有非常好的可预测性,实时性验证也比较方便;

这种方式主要缺点是不灵活,需求一旦发生变化,就要重新生成整个运行时间表。

由于具有非常好的可预测性,这种方式主要用于航空航天、军事等对系统的实时性要求十分严格的领域。

固定优先级抢先式调度方式则与通用操作系统中采用的基于优先级的调度方式基本类似,但在固定优先级抢先式调度方式中,进程的优先级是固定不变的,并且该优先级是在运行前通过某种优先级分配策略(如Rate-MonotonicDeadline-Monotonic等)来指定的。这种方式的优缺点与静态表驱动方式的优缺点正好完全相反,它主要应用于一些较简单、较独立的嵌入式系统,但随着调度理论的不断成熟和完善,这种方式也会逐渐在一些对实时性要求十分严格的领域中得到应用。目前市场上大部分的实时操作系统采用的都是这种调度方式。

(2)    内存管理:

关于虚存管理机制我们在上面已经进行了一些讨论。为解决虚存给系统带来的不可预测性,实时操作系统一般采用如下两种方式: 

?         在原有虚存管理机制的基础上增加页面锁功能,用户可将关键页面锁定在内存中,从而不会被swap程序将该页面交换出内存。这种方式的优点是既得到了虚存管理机制为软件开发带来的好处,又提高了系统的可预测性。缺点是由于TLB等机制的设计也是按照注重平均表现的原则进行的,因此系统的可预测性并不能完全得到保障;

?         采用静态内存划分的方式,为每个实时任务划分固定的内存区域。这种方式的优点是系统具有较好的可预测性,缺点是灵活性不够好,任务对存储器的需求一旦有变化就需要重新对内存进行划分,此外虚存管理机制所带来的好处也丧失了。

目前市场上的实时操作系统一般都采用第一种管理方式。

(3)    中断处理:

在通用操作系统中,大部分外部中断都是开启的,中断处理一般由设备驱动程序来完成。由于通用操作系统中的用户进程一般都没有实时性要求,而中断处理程序直接跟硬件设备交互,可能有实时性要求,因此中断处理程序的优先级被设定为高于任何用户进程。

但对于实时操作系统采用上述的中断处理机制是不合适的。首先,外部中断是环境向实时操作系统进行的输入,它的频度是与环境变化的速率相关的,而与实时操作系统无关。如果外部中断产生的频度不可预测,则一个实时任务在运行时被中断处理程序阻塞的时间开销也是不可预测的,从而使任务的实时性得不到保证;如果外部中断产生的频度是可预测的,一旦某外部中断产生的频度超出其预测值(如硬件故障产生的虚假中断信号或预测值本身有误)就可能会破坏整个系统的可预测性。其次,实时操作系统中的各用户进程一般都有实时性要求,因此中断处理程序优先级高于所有用户进程的优先级分配方式是不合适的。

一种较适合实时操作系统的中断处理方式为:除时钟中断外,屏蔽所有其它中断,中断处理程序变为周期性的轮询操作,这些操作由核心态的设备驱动程序或由用户态的设备支持库来完成。采用这种方式的主要好处是充分保证了系统的可预测性,主要缺点是对环境变化的响应可能不如上述中断处理方式快,另外轮询操作在一定程度上降低了CPU的有效利用率。另一种可行的方式是:对于采用轮询方式无法满足需求的外部事件,采用中断方式,其它时间仍然采用轮询方式。但此时中断处理程序与所以其它任务一样拥有优先级,调度器根据优先级对处于就绪态的任务和中断处理程序统一进行处理器调度。这种方式使外部事件的响应速度加快,并避免了上述中断方式带来第二个问题,但第一个问题仍然存在。

此外为提高时钟中断响应时间的可预测性,实时操作系统应尽可能少地屏蔽中断。

(4)    共享资源的互斥访问:

通用操作系统一般采用信号量机制来解决共享资源的互斥访问问题。

对于实时操作系统,如果任务调度采用静态表驱动方式,共享资源的互斥访问问题在生成运行时间表时已经考虑到了,在运行时无需再考虑。如果任务调度采用基于优先级的方式,则传统的信号量机制在系统运行时很容易造成优先级倒置问题(Priority Inversion),即当一个高优先级任务通过信号量机制访问共享资源时,该信号量已被一低优先级任务占有,而这个低优先级任务在访问共享资源时可能又被其它一些中等优先级的任务抢先,因此造成高优先级任务被许多具有较低优先级的任务阻塞,实时性难以得到保证。因此在实时操作系统中,往往对传统的信号量机制进行了一些扩展,引入了如优先级继承协议(Priority Inheritance Protocol)、优先级顶置协议(Priority Ceiling Protocol)以及Stack Resource Policy等机制,较好地解决了优先级倒置的问题。

(5)    系统调用以及系统内部操作的时间开销:

进程通过系统调用得到操作系统提供的服务,操作系统通过内部操作(如上下文切换等)来完成一些内部管理工作。为保证系统的可预测性,实时操作系统中的所有系统调用以及系统内部操作的时间开销都应是有界的,并且该界限是一个具体的量化数值。而在通用操作系统中对这些时间开销则未做如此限制。

(6)    系统的可重入性:

在通用操作系统中,核心态系统调用往往是不可重入的,当一低优先级任务调用核心态系统调用时,在该时间段内到达的高优先级任务必须等到低优先级的系统调用完成才能获得CPU,这就降低了系统的可预测性。因此,实时操作系统中的核心态系统调用往往设计为可重入的。

(7)    辅助工具:

实时操作系统额外提供了一些辅助工具,如实时任务在最坏情况下

 

4、        有没有用过状态机?

用过,状态和触发条件。

5、        C语言实现一个双向链表,在双向链表中再加入一个插入算法。

#include<stdio.h>
#include<stdlib.h>
#include<malloc.h>
struct list{
int data;
struct list *next;
struct list *pre;
};
typedef struct list node;
typedef node *link;
link front=NULL,rear,ptr,head=NULL;

link push(int item){
link newnode=(link)malloc(sizeof(node));
newnode->data=item;
if(head==NULL)
{
head=newnode;
head->next=NULL;
head->pre=NULL;
rear=head;
}
else
{
rear->next=newnode;
newnode->pre=rear;
newnode->next=NULL;
rear=newnode;
}
return head;
}

void makenull(){
front=NULL;
rear=NULL;
}

empty(){
if(front==NULL)
return 1;
else
return 0;
}

int tops(){
if(empty())
return NULL;
else
return rear->data;
}

void pop(){
if(empty())
printf("stack is empty!/n");
else
rear=rear->pre;
}

void display(link l){
link p;
p=l;
while(p!=NULL){
printf("%d->",p->data);
p=p->next;
}
}


void main(){
int n,i;
printf("input your first data!/n");
scanf("%d",&n);
front=push(n);
/*another data*/
for(i=0;i<3;i++)
{
printf("input:/n");
scanf("%d",&n);
push(n);
}
ptr=front;
display(ptr);
printf("/n Please enter any key to pop");
pop();
ptr=front;
display(ptr);

}

6、        以前工作中接触过哪些芯片,他们的特点是什么?比如用的是ARM几?

  • VxWorks image 装入ARM的过程 (以芯片PS7111为例):

   ARM7 有两种运行模式, Boot 模式和 Normal 模式, Boot模式主要是把程序装入(down load load )Flash ROM中用的, Normal模式是一般运行程序用的.

   ARM7 Boot 模式时, Flash 的地址是0x70000000片选型号是CS0  (Normal模式下,Flash地址为0x00000000)

    ARM7内部有128byteBootROM2KSRAM,当需要Download VxWorks image,ARM启动采用Boot方式启动运行存在128byte BootROM中的程序初始化ARM内部的COM口,从COM口接受数据到2KSRAM,这2K程序是用来真正Load VxWorks的,2K程序Load完毕后系统自动跳转到这2K程序执行,它的作用是再次初始化内部的COM,通过COM口接受VxWorksDRAM,然后由DRAM写入FLASH。在主板2K SRAM运行的Boot Load程序执行过程,可参看程序示例中ARM Boot Load程序


写入完毕后,切换到Normal模式重新启动系统,系统自动跳到FLASH 0X00000000开始运行VXWORKS

PC机上的COM1ARM内部的UART1COM)通信来Download VxWorks

 

7、        内存管理方面的一些问题。

页和分段系统有许多相似之处,但在概念上两者完全不同,主要表现在:
1
、页是信息的物理单位,分页是为实现离散分配方式,以消减内存的外零头,提高内存的利用率;或者说,分页仅仅是由于系统管理的需要,而不是用户的需要。

段是信息的逻辑单位,它含有一组其意义相对完整的信息。分段的目的是为了能更好的满足用户的需要。

2
、页的大小固定且由系统确定,把逻辑地址划分为页号和页内地址两部分,是由机器硬件实现的,因而一个系统只能有一种大小的页面。

段的长度却不固定,决定于用户所编写的程序,通常由编辑程序在对源程序进行编辑时,根据信息的性质来划分。

3
、分页的作业地址空间是维一的,即单一的线性空间,程序员只须利用一个记忆符,即可表示一地址。

分段的作业地址空间是二维的,程序员在标识一个地址时,既需给出段名,又需给出段内地址。

8、        一些关键字的用法。

const是一个C语言的关键字,它限定一个变量不允许被改变。使用const在一定程度上可以提高程序的健壮性,另外,在观看别人代码的时候,清晰理解const所起的作用,对理解对方的程序也有一些帮助。

  虽然这听起来很简单,但实际上,const的使用也是c语言中一个比较微妙的地方,微妙在何处呢?请看下面几个问题。
  问题:const变量 & 常量
  为什么我象下面的例子一样用一个const变量来初始化数组,ANSI C的编译器会报告一个错误呢?

const int n = 5;
int a[n];


  答案与分析:
  1)、这个问题讨论的是常量只读变量的区别。常量肯定是只读的,例如5 “abc”,等,肯定是只读的,因为程序中根本没有地方存放它的值,当然也就不能够去修改它。而只读变量则是在内存中开辟一个地方来存放它的值,只不过这个值由编译器限定不允许被修改。C语言关键字const就是用来限定一个变量不允许被改变的修饰符(Qualifier)。上述代码中变量n被修饰为只读变量,可惜再怎么修饰也不是常量。而ANSI C规定数组定义时维度必须是常量只读变量也是不可以的。
  2)、注意:在ANSI C中,这种写法是错误的,因为数组的大小应该是个常量,而const int n,n只是一个变量(常量 != 不可变的变量,但在标准C++中,这样定义的是一个常量,这种写法是对的),实际上,根据编译过程及内存分配来看,这种用法本来就应该是合理的,只是ANSI C对数组的规定限制了它。
  3)、那么,在ANSI C 语言中用什么来定义常量呢?答案是enum类型和#define宏,这两个都可以用来定义常量。

  问题:const变量 & const 限定的内容
  下面的代码编译器会报一个错误,请问,哪一个语句是错误的呢?

typedef char * pStr;
char string[4] = /"abc/";
const char *p1 = string;
const pStr p2 = string;
p1++;
p2++;


  答案与分析:
  问题出在p2++上。
  1)const使用的基本形式: const char m;
  限定m不可变。
  2)、替换1式中的m, const char *pm;
  限定*pm不可变,当然pm是可变的,因此问题中p1++是对的。
  3)、替换1char, const newType m;
  限定m不可变,问题中的charptr就是一种新类型,因此问题中p2不可变,p2++是错误的。
  问题:const变量 & 字符串常量
  请问下面的代码有什么问题?

char *p = /"i/'m hungry!/";
p[0]= /'I/';


  答案与分析
  上面的代码可能会造成内存的非法写操作。分析如下, “i/'m hungry”实质上是字符串常量,而常量往往被编译器放在只读的内存区,不可写。p初始指向这个只读的内存区,而p[0] = /'I/'则企图去写这个地方,编译器当然不会答应。
  问题:const变量 & 字符串常量2
  请问char a[3] = /"abc/" 合法吗?使用它有什么隐患?
  答案与分析
  在标准C中这是合法的,但是它的生存环境非常狭小;它定义一个大小为3的数组,初始化为“abc”,,注意,它没有通常的字符串终止符/'//0/',因此这个数组只是看起来像C语言中的字符串,实质上却不是,因此所有对字符串进行处理的函数,比如strcpyprintf等,都不能够被使用在这个假字符串上。
  问题5const & 指针
  类型声明中const用来修饰一个常量,有如下两种写法,那么,请问,下面分别用const限定不可变的内容是什么?
  1)const在前面

const int nValue //nValueconst
const char *pContent; //*pContent
const, pContent可变
const (char *) pContent;//pContent
const,*pContent可变
char* const pContent; //pContent
const,*pContent可变
const char* const pContent; //pContent
*pContent都是const


  2)const在后面,与上面的声明对等

int const nValue // nValueconst
char const * pContent;// *pContent
const, pContent可变
(char *) const pContent;//pContent
const,*pContent可变
char* const pContent;// pContent
const,*pContent可变
char const* const pContent;// pContent
*pContent都是const


  答案与分析:
  const和指针一起使用是C语言中一个很常见的困惑之处,在实际开发中,特别是在看别人代码的时候,常常会因为这样而不好判断作者的意图,下面讲一下我的判断原则:
  沿着*号划一条线,const和谁在一边,那么谁就是const,即const限定的元素就是它。你可以根据这个规则来看上面声明的实际意义,相信定会一目了然。
  另外,需要注意:对于const (char *) ; 因为char *是一个整体,相当于一个类型( char),因此,这是限定指针是const

惨痛教训:

假设在test.h中定义了一个static bool g_test=false;

test1.ctest2.c都包含test.h,则test1.ctest2.c分别生成两份g_test,在test1.c 中置g_test=true,test2.c中仍然为false并未改变!shit!!

一、c程序存储空间布局

C程序一直由下列部分组成:

      1)正文段——CPU执行的机器指令部分;一个程序只有一个副本;只读,防止程序由于意外事故而修改自身指令;
      2
)初始化数据段(数据段)——在程序中所有赋了初值的全局变量,存放在这里。
      3
)非初始化数据段(bss段)——在程序中没有初始化的全局变量;内核将此段初始化为0
      4
)栈——增长方向:自顶向下增长;自动变量以及每次函数调用时所需要保存的信息(返回地址;环境信息)。
      5
)堆——动态存储分。

|-----------|
|                 |
|-----------|
|   
         | 
|-----------|
|    |            |
|   |/           |
|                 |
|                 |
|   /|           |
|    |            |
|-----------|
|   
         |
|-----------|
|
未初始化
  |
|-----------|
|  
初始化
  |
|-----------|
正文段
   |
|-----------|

二、 面向过程程序设计中的static

1. 全局静态变量

   在全局变量之前加上关键字static,全局变量就被定义成为一个全局静态变量

   1)内存中的位置:静态存储区(静态存储区在整个程序运行期间都存在)

   2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)

   3)作用域:全局静态变量在声明他的文件之外是不可见的。准确地讲从定义之处开始到文件结尾。


看下面关于作用域的程序:
//teststatic1.c
void display();
extern int n;
int main()
{
  n = 20;
  printf("%dn",n);
  display();
  return 0;
}
 
//teststatic2.c 
static int n;   //
定义全局静态变量,自动初始化为0,仅在本文件中可见
void display()
{
  n++;
  printf("%dn",n);
}
 

文件分别编译通过,但link的时候teststatic.c中的变量n找不到定义,产生错误。
 
定义全局静态变量的好处:

<1>不会被其他文件所访问,修改

<2>其他文件中可以使用相同名字的变量,不会发生冲突。

2. 局部静态变量

  在局部变量之前加上关键字static,局部变量就被定义成为一个局部静态变量

  1)内存中的位置:静态存储区

  2)初始化:未经初始化的全局静态变量会被程序自动初始化为0(自动对象的值是任意的,除非他被显示初始化)

  3)作用域:作用域仍为局部作用域,当定义它的函数或者语句块结束的时候,作用域随之结束。

  注:当static用来修饰局部变量的时候,它就改变了局部变量的存储位置,从原来的栈中存放改为静态存储区。但是局部静态变量在离开作用域之后,并没有被销毁,而是仍然驻留在内存当中,直到程序结束,只不过我们不能再对他进行访问。

      static用来修饰全局变量的时候,它就改变了全局变量的作用域(在声明他的文件之外是不可见的),但是没有改变它的存放位置,还是在静态存储区中。

3. 静态函数

  在函数的返回类型前加上关键字static,函数就被定义成为静态函数。

  函数的定义和声明默认情况下是extern的,但静态函数只是在声明他的文件当中可见,不能被其他文件所用。

  例如:
//teststatic1.c
void display();
static void staticdis();
int main()
{
  display();
  staticdis();
  renturn 0;
}
 
//teststatic2.c
void display()
{
  staticdis();
  printf("display() has been called n");
}
 
static void staticdis()
{
  printf("staticDis() has been calledn");
}
 
文件分别编译通过,但是连接的时候找不到函数staticdis()的定义,产生错误。
 
实际上编译也未过,vc2003报告teststatic1.c中静态函数staticdis已声明但未定义 ;by imjacob
定义静态函数的好处:

<1> 其他文件中可以定义相同名字的函数,不会发生冲突

<2> 静态函数不能被其他文件所用。
 
存储说明符autoregisterexternstatic,对应两种存储期:自动存储期和静态存储期。
 
auto
register对应自动存储期。具有自动存储期的变量在进入声明该变量的程序块时被建立,它在该程序块活动时存在,退出该程序块时撤销。

关键字externstatic用来说明具有静态存储期的变量和函数。用static声明的局部变量具有静态存储持续期(static storage duration),或静态范围(static extent)。虽然他的值在函数调用之间保持有效,但是其名字的可视性仍限制在其局部域内。静态局部对象在程序执行到该对象的声明处时被首次初始化。

由于static变量的以上特性,可实现一些特定功能。

1 统计次数功能

声明函数的一个局部变量,并设为static类型,作为一个计数器,这样函数每次被调用的时候就可以进行计数。这是统计函数被调用次数的最好的办法,因为这个变量是和函数息息相关的,而函数可能在多个不同的地方被调用,所以从调用者的角度来统计比较困难。代码如下:
 
void count();
int main()
{
 int i;
 for (i = 1; i <= 3; i++)
  count();
  return 0;
}
void count()
{
 static num = 0;
 num++;
 printf(" I have been called %d",num,"timesn");
}

输出结果为:
I have been called 1 times.
I have been called 2 times.
I have been called 3 times.

1)auto
  这个关键字用于声明变量的生存期为自动,即将不在任何类、结构、枚举、联合和函数中定义的变量视为全局变量,而在函数中定义的变量视为局部变量。这个关键字不怎么多写,因为所有的变量默认就是auto的。

(2)register
  这个关键字命令编译器尽可能的将变量存在CPU内部寄存器中而不是通过内存寻址访问以提高效率。

(5)volatile
  表明某个变量的值可能在外部被改变,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。它可以适用于基础类型如:int,char,long......也适用于C的结构和C++的类。当对结构或者类对象使用volatile修饰的时候,结构或者类的所有成员都会被视为
volatile.
  该关键字在多线程环境下经常使用,因为在编写多线程的程序时,同一个变量可能被多个线程修改,而程序通过该变量同步各个线程。

  简单示例:
   DWORD __stdcall threadFunc(LPVOID signal)
   
{
     
int* intSignal="reinterdivt"_cast(signal);
     
*intSignal=2;
     
while(*intSignal!=1)
     
sleep(1000);
     
return 0;
   
}
  该线程启动时将intSignal 置为2,然后循环等待直到intSignal 1 时退出。显然intSignal的值必须在外部被改变,否则该线程不会退出。但是实际运行的时候该线程却不会退出,即使在外部将它的值改为1,看一下对应的伪汇编代码就明白了:

     mov ax,signal
     
label:
     
if(ax!=1)
     
goto label
  对于C编译器来说,它并不知道这个值会被其他线程修改。自然就把它cache在寄存器里面。C 编译器是没有线程概念的,这时候就需要用到volatilevolatile 的本意是指:这个值可能会在当前线程外部被改变。也就是说,我们要在threadFunc中的intSignal前面加上volatile关键字,这时候,编译器知道该变量的值会在外部改变,因此每次访问该变量时会重新读取,所作的循环变为如下面伪码所示:

     label:
     
mov ax,signal
     
if(ax!=1)
     
goto label
  注意:一个参数既可以是const同时是volatile,是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

(6)extern
  extern 意为外来的”···它的作用在于告诉编译器:有这个变量,它可能不存在当前的文件中,但它肯定要存在于工程中的某一个源文件中或者一个Dll的输出中。

 

9、        用二分法对一个数组进行排序。

//BCB下测试  
  #include   <iostream.h>  
  #include   <stdlib.h>  
  #include   <conio.h>  
   
  #define   MAX(a1,a2)   ((a1   >   a2)?a1:a2)  
  #define   MIN(a1,a2)   ((a1   <   a2)?a1:a2)  
  #define   NULL     0  
  #define   TRUE     1  
  #define   FALSE   0  
   
  //
将两个一排序的数组(a1a2)合并到resu
 
  int   unite(int   *   a1,int   *   a2,int   l1,int   l2,int   *   resu)  
  {  
      if(a1   ==   NULL   ||   a2   ==   NULL   ||   resu   ==   NULL   ||   a1   <   0   ||   a2   <   0)  
          return(FALSE);  
   
  //
resu的每一个单元选择一个合适的内容(想不到更好的表述
:P)  
      int   i;//a1
的当前单元
 
      int   j;//a2
的当前单元
 
      int   k;//resu
的当前单元
 
  //
先将两个数组中的一个处理完
 
      for(i   =   k   =   j   =   0;i   <   l1   &&   j   <   l2;k++)  
      {  
          if(a1[i]   <   a2[j])  
              resu[k]   =   a1[i++];  
          else  
              resu[k]   =   a2[j++];  
      }  
  //
将未处理完的放入结果中
 
      if(i   <   l1)  
          for(;i   <   l1;i++)  
              resu[k++]   =   a1[i];  
      if(j   <   l2)  
          for(;j   <   l2;j++)  
              resu[k++]   =   a2[j];  
   
      return(TRUE);  
  }  
   
  int   sort(int   *   array,int   *   resu   ,int   len)  
  {  
      if(array   ==   NULL   ||   resu   ==   NULL   ||   len   <   0   ||   array   ==   resu)  
          return(FALSE);  
   
  //
如果只有0个单元成功返回
 
      if(len   ==   0)  
          return(TRUE);  
   
  //
如果只有1个单元,放到结果中,成功返回
 
      if(len   ==   1)  
      {  
          *resu   =   *array;  
          return(TRUE);  
      }  
   
  //
如果有俩个,排序返回
 
      if(len   ==   2)  
      {  
          resu[0]   =   MIN(array[0],array[1]);  
          resu[1]   =   MAX(array[0],array[1]);  
          return(TRUE);  
      }  
   
      int   i;  
      int   m   =   len   /   2;  
      int   is_ok;  
  //
将数组拆成两部分,分别排序(递归)
 
      is_ok     =   sort(array,resu,m);  
      is_ok   &=   sort(array   +   m,resu   +   m,len   -   m);  
   
  //
将排序好的两部分拷贝到原数组中
(array)  
      for(i   =   0;i   <   len;i++)  
          array[i]   =   resu[i];  
  //
两部分合并
 
      is_ok   &=   unite(array,array   +   m,m,len-m,resu);  
      return(is_ok);  
  }  
   
  void   build_rand(int   *   array,int   len)  
  {  
      int   i;  
      randomize();  
      for(i   =   0;i   <   len;i++)  
          array[i]   =   rand();  
  }  
   
  void   list_array(int   *   array,int   len)  
  {  
      int   i;  
      cout<<"=============================================="<<endl;  
      for(i   =   1;i   <=   len;i++)  
      {  
          cout<<array[i   -   1]<<'/t';  
          if(i   %   8   ==   0)  
              cout<<endl;  
      }  
      cout<<endl;  
  }  
   
  void   main(void)  
  {  
      int   a[20];  
      int   r[20];  
   
      build_rand(a,20);  
      list_array(a,20);  
      sort(a,r,20);  
      list_array(r,20);  
      getch();  
  }

快速排序

int   sort(int   array,int   len)  
  {  
      if(array   ==   NULL   ||   len   <   0)  
          return   (FALSE);  
  //
只有一个或者没有需要比较的单元,直接返回成功
 
      if(len   <   1)  
          return   (TRUE);  
      int   m,i,k;  
  //
以数组的第一个数位分界点,将数组分为两个部分
 
  //
左小,右大
 
      for(m   =   i   =   0;i   <   len;i++)  
      {  
  //
如果找到一个比分界点的数小的数,交换
 
          if(array[i]   <   array[m])  
          {  
              k   =   array[i];  
              array[i]   =   array[m];  
              array[m]   =   k;  
              m   =   i;  
          }  
      }  
  //
如果只有两个元素,则已经完成排序,返回成功
 
  //
也可以继续递归,下面也必然成功返回
 
      if(len   ==   2)  
          return(TRUE);  
  //
将数组分成两部分分别排序
 
      int   resu   =   sort(array,m);  
      resu   &=   sort(array   +   m   +   1,len   -   m   -   1);  
      return(resu);  
  }  

10、    函数指针是什么?

函数指针是指向函数的指针
指针函数是返回指针的函数

11、    用选择法对数组中的10个字符按由大到小排序。

#include "stdio.h"
#include "string.h"

void choicesort(int string[])  //
选择法排序

{
    int i,j,k,t;
         for (i=0;i<10-1;i++)
     {
         k=i;
         for (j=i+1;j<10;j++)
         
             if (string[j] < string[i])  k=j;
                 
          if( k != i)
          {
              t=string[i];
              string[i]=string[k];
              string[k]=t;
          }
     }
     
           
    printf("the sorted number is :/n");
    for (i=0;i<10;i++)
        printf("%5d",string[i]);
    printf("/n");
}

void bubblesort(int string[])//
冒泡法排序
{
    int i,j,t,n;

    for (i=0;i <9; i++)
        for(j=0;j<10-i;j++)
            if(string[j] > string[j+1])
            {
              t=string[j];
              string[j]=string[j+1];
              string[j+1]=t;
            }

    printf("the sorted number is :/n");
        for (i=0;i<10;i++)
        printf("%5d",string[i]);
      printf("/n");
}

main()
{
    int i,str[10];
    printf("input the number :/n");
    for(i=0;i<10;i++)
        scanf("%d",&str[i]);
    for (i=0;i<10;i++)
        printf("%5d",str[i]);
    printf("/n");
    choicesort(str);
 //  bubblesort(str);
}

 

#include<stdio.h>
#define sizemax 10
/*
直接插入排序算法:(从小到大排序)
*/
void InsertSort(int array[],int n)
{
    int i,j,temp,temp1;
    int count=0;
    for(i=1;i<n;i++)
    {
        temp=array[i];
        for(j=i-1;j>=0;j--)/* 
每次循环完毕数组的0i-1项为一个有序的序列
*/
        {
            count=0;/*
这里count是标记位,可以减少比较次数
*/
            if(array[j]>temp)
            {
                temp1 = array[j+1];
                array[j+1] = array[j];
                array[j] = temp1;
                count++;

            }

        
            if(count==0)
                break;

        }
    }
}
/* 
希尔排序算法:(从大到小排序)
*/
void ShellSort(int array[],int n)
{
    int i,d,temp;
    
    for(d=n/2;d>0;d--)
        for(i=0;i<n-d;i++)
        {
            if(array[i]<array[i+d])
            {
                temp=array[i];
                array[i]=array[i+d];
                array[i+d]=temp;

            }
        }
}
/*
冒泡排序算法:(从小到大排序)
*/
void BubbleSort(int array[],int n)
{
    int i,j,temp;
    for(i=0;i<n;i++)
    {
        for(j=n-1;j>i;j--)
        {
            if(array[j]<array[j-1])
            {
                temp=array[j];
                array[j]=array[j-1];
                array[j-1]=temp;

            }
        }
    }
}


/*
快速排序算法
*/
void QuickSort(int array[],int start,int end)
{
    int i,j,temp;
    i=start;
    j=end-1;
    if(i<j)
    {
        temp=array[start];
        while(i!=j)
        {
            
            while(i<j && array[j]>temp)
                j--;
            if(i<j && array[j]<temp)
            {
                array[i]=array[j];
                i++;
            }
            while(i<j && array[i]<temp)
                i++;
            if(i<j && array[i]>temp)
            {
                array[j]=array[i];
                j--;
            }
        }
        array[i]=temp;
        QuickSort(array,start,i);
        QuickSort(array,i+1,end);
    }

}
    



/*
归并排序(要求被排序的两段都要有序
)

void merge(int array[],int n)
{
    int i,fir_start,sec_start,mid;
    int temp[sizemax];
    int k=0;
    mid=n/2;
    fir_start=0;
    sec_start=mid;
    for(i=0;i<mid;i++)
    {
        if(array[fir_start]<=array[sec_start])
        {
            
            temp[k]=array[fir_start];
            fir_start++;
            k++;
        }
        else
        {
            temp[k]=array[sec_start];
            sec_start++;
            k++;
        }
    }
    while(fir_start<mid)
    {
        temp[k++]=array[fir_start++];
    }
    while(sec_start<n)
    {
        temp[k++]=array[sec_start++];
    }
    for(i=0;i<n;i++)
        array[i]=temp[i];
        

}
*/

void main()
{
    int i;
    int array[sizemax] = {8,9,11,12,13,3,4,5,6,10};
    
    printf("/t/t/t
排序算法汇总
 v1.0/n/n");
/*
    InsertSort(array,10);
    printf("
直接插入排序算法:(从小到大排序)
/n");
    for(i = 0;i<10;i++)
        printf("%d/t",array[i]);
    printf("/n");

    ShellSort(array,10);
    printf("
希尔排序算法:(从大到小排序)
/n");
    for(i = 0;i<10;i++)
        printf("%d/t",array[i]);
    printf("/n");
    
    BubbleSort(array,10);
    printf("
冒泡排序算法:(从小到大排序)
/n");
    for(i = 0;i<10;i++)
        printf("%d/t",array[i]);
    printf("/n");
    */

    QuickSort(array,0,10);
    printf("
快速排序算法:(从小到大排序)
/n");
    for(i = 0;i<10;i++)
        printf("%d/t",array[i]);
    printf("/n");

    

}

12、    请说明如何在C++中调用使用C编译的函数库,反之在C程序中如果要调用C++编译的文件中的函数该如何处理?

 

首先,作为externC/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。

通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数

extern "C"是连接申明(linkage declaration),extern "C"修饰的变量和函数是按照C语言方式编译和连接的,来看看C++中对类似C的函数是怎样编译的:

作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在符号库中的名字与C语言的不同。例如,假设某个函数的原型为:

void foo( int x, int y );

   

该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字(不同的编译器可能生成的名字不同,但是都采用了相同的机制,生成的新名字称为“mangled name”)。

_foo_int_int 这样的名字包含了函数名、函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。例如,在C++中,函数void foo( int x, int y )void foo( int x, float y )编译生成的符号是不相同的,后者为_foo_int_float

同样地,C++中的变量除支持局部变量外,还支持类成员变量和全局变量。用户所编写程序的类成员变量可能与全局变量同名,我们以"."来区分。而本质上,编译器在进行编译时,与函数的处理相似,也为类中的变量取了一个独一无二的名字,这个名字与用户程序中同名的全局变量名字不同。

未加extern "C"声明时的连接方式

假设在C++中,模块A的头文件如下:

// 模块A头文件  moduleA.h

#ifndef MODULE_A_H

#define MODULE_A_H

int foo( int x, int y );

#endif   

在模块B中引用该函数:

// 模块B实现文件  moduleB.cpp

i nclude "moduleA.h"

foo(2,3);

    实际上,在连接阶段,连接器会从模块A生成的目标文件moduleA.obj中寻找_foo_int_int这样的符号!

extern "C"声明后的编译和连接方式

extern "C"声明后,模块A的头文件变为:

// 模块A头文件  moduleA.h

#ifndef MODULE_A_H

#define MODULE_A_H

extern "C" int foo( int x, int y );

#endif   

在模块B的实现文件中仍然调用foo( 2,3 ),其结果是:

1)模块A编译生成foo的目标代码时,没有对其名字进行特殊处理,采用了C语言的方式;

2)连接器在为模块B的目标代码寻找foo(2,3)调用时,寻找的是未经修改的符号名_foo

如果在模块A中函数声明了fooextern "C"类型,而模块B中包含的是extern int foo( int x, int y ) ,则模块B找不到模块A中的函数;反之亦然。

所以,可以用一句话概括extern “C”这个声明的真实目的(任何语言中的任何语法特性的诞生都不是随意而为的,来源于真实世界的需求驱动。我们在思考问题时,不能只停留在这个语言是怎么做的,还要问一问它为什么要这么做,动机是什么,这样我们可以更深入地理解许多问题):实现C++C及其它语言的混合编程。   

明白了C++extern "C"的设立动机,我们下面来具体分析extern "C"通常的使用技巧:

extern "C"的惯用法

1)在C++中引用C语言中的函数和变量,在包含C语言头文件(假设为cExample.h)时,需进行下列处理:

extern "C"

{

i nclude "cExample.h"

}

而在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。

C++引用C函数例子工程中包含的三个文件的源代码如下:

/* c语言头文件:cExample.h */

#ifndef C_EXAMPLE_H

#define C_EXAMPLE_H

extern int add(int x,int y);

#endif

/* c语言实现文件:cExample.c */

i nclude "cExample.h"

int add( int x, int y )

{

return x + y;

}

// c++实现文件,调用addcppFile.cpp

extern "C"

{

i nclude "cExample.h"

}

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

{

add(2,3);

return 0;

}

如果C++调用一个C语言编写的.DLL时,当包括.DLL的头文件或声明接口函数时,应加extern "C" {  }

2)在C中引用C++语言中的函数和变量时,C++的头文件需添加extern "C",但是在C语言中不能直接引用声明了extern "C"的该头文件,应该仅将C文件中将C++中定义的extern "C"函数声明为extern类型。

C引用C++函数例子工程中包含的三个文件的源代码如下:

//C++头文件 cppExample.h

#ifndef CPP_EXAMPLE_H

#define CPP_EXAMPLE_H

extern "C" int add( int x, int y );

#endif

//C++实现文件 cppExample.cpp

i nclude "cppExample.h"

int add( int x, int y )

{

return x + y;

}

/* C实现文件 cFile.c

/* 这样会编译出错:#i nclude "cExample.h" */

extern int add( int x, int y );

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

{

add( 2, 3 );

return 0;

}

 

13、    请说明头文件中的ifndef/define/endif干什么用?

编译宏

14、    为什么指针被free以后要赋值?

当你调用 free() 的时候, 传入指针指向的内存被释放, 但调用函数的指针值可能保持不变, 因为 C 的按值传参语义意味着被调函数永远不会永久改变参数的值。

赋值后指向空指针,避免以后非法访问一个已释放的内存。

15、    编写一个函数测试系统为big_endian还是little_endian.

 

1> 如何判断一个板子的cpu big-endian 还是 Littleendian的?

c实现非常简单,10行左右,就可以判断了,关键考察新人是否了解了什么是endian big-endianlittle-endian的区别在哪里,如果这些不清楚,就算c再强,也是憋不出来的。

2> 判断了endian 后,如何进行转换,分别用函数和宏来实现。

如果说上面的那个,可能不能正确的考察出新人的c水平,下面这个,可就可以显示了。

尤其是写一个宏, 来实现。 我觉得宏最能体现出一个人的水平了, 大家都知道一个功能强大的,但是写法又非常简单的宏,是不好写的。 尤其是注意类型转换, 大扩号什么的。 写一个函数就容易多了。 

实现起来,或者用宏,或者用函数的形式,都可以,最好都试一下。主要看的就是宏的使用。 

比如:

写成函数的形式:
typedef unsigned int u32 ;
typedef unsigned short u16 ;

u16 bswap16(u16);
u32 bswap32(u32);


写成宏的形式:

#define BSWAP_16(x)
....
#define BSWAP_32(x)
....

比如: 0x1234 变成: 0x3412

或者: 0x12345678 变成 0x78563412

--
在下面的回复写出来,就有点乱了,干脆在这里铁出来吧 ,格式比较好:


1
》判断endian的问题, 很简单。

判断endian
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
      short int a = 0x1234;
      char *p = (char *)&a;
    
      printf("p=%#hhx/n",*p);

      if(*p == 0x34)
          printf("Little endian /n");
      else if(*p == 0x12)
          printf("Big endian /n");
      else
          printf("Unknow endian /n");

      return 0;
}

 

2>如何进行转换:


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

typedef unsigned int u32;
typedef unsigned short u16;

#if 0
//simple: not check varible types

#define BSWAP_16(x) /
            ( (((x) & 0x00ff) << 8 ) | /
       (((x) & 0xff00) >> 8 ) /
       )

//complex:check varible types

#else
#define BSWAP_16(x) /
       (u16) ( ((((u16)(x)) & 0x00ff) << 8 ) | /
                   ((((u16)(x)) & 0xff00) >> 8 ) /
            )


#endif
#define BSWAP_32(x) /
       (u32) ( (( ((u32)(x)) & 0xff000000 ) >> 24) | /
                      (( ((u32)(x)) & 0x00ff0000 ) >> 8 ) | /
       (( ((u32)(x)) & 0x0000ff00 ) << 8 ) | /
       (( ((u32)(x)) & 0x000000ff ) << 24) /
                )

u16 bswap16(u16 x)
{
      return (x & 0x00ff) << 8 |
       (x & 0xff00) >> 8
      ;
}

u32 bswap32(u32 x)
{
      return       ( x & 0xff000000 ) >>24 |
          ( x & 0x00ff0000 ) >>8 |
          ( x & 0x0000ff00 ) <<8 |
          ( x & 0x000000ff ) << 24
      ;
}
    

int main(void)
{
      //u16 var_short = 0x123490;

      //u32 var_int = 0x1234567890;

      //关键是要能对错误进行处理,给一个0x123490 照样能得出 0x9034的值,而且, 占内存要小的

      printf("macro conversion:%#x/n",BSWAP_16(0x123490 ));//要能正确转换

      printf("macro conversion:%#x/n", BSWAP_32(0x1234567890)); //要能正确转换
      printf("-----------------/n");
    
      printf("function conversion:%#x/n",bswap16(0x123490));
      printf("function conversion:%#x/n", bswap32(0x1234567890));
    
      return 0;
}

16、    用伪代码编写二叉树的遍历函数。(不使用递归)--以说明程序流程为主,无需太多代码。

 

Status InOrderTraverse(BiTree T, Status (*Visit)(TelemType e))
{
//
采用二叉链表存储结构,Visit是对数据元素操作的应用函数。

//
中序遍历二叉树T的非递归算法,对每个数据元素调用函数Visit

InitStack(S);
p=T;
while(p||!StackEmpty(S))
{
if(p)
{
Push(S,p);
p=p->lchild;
}//if
根指针进栈,遍历左子树

else
{
Pop(S,p);
if(!Visit(p->data))
return ERROR;
p=p->rchild;
}
}
return OK;
}

17、    一个求解Josephus问题的函数。有n个人围成一圈,顺序排号,从第s个人开始报数(从1m报数),凡报到m的人退出圈子,用整数序列123,……,n表示顺序围坐在圆桌周围的人,并采用数组存储输出序列。

18、    写出你所知道的设计模式(至少5种),用UML的类图画出其中一个的示意图。

 

整个设计模式贯穿一个原理:面对接口编程,而不是面对实现.目标原则是:降低耦合,增强灵活性.  
一些基本的设计模式


Abstract Factory
:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。(使用得非常频繁。)

Adapter
:将一个类的接口转换成客户希望的另外一个接口。A d a p t e r模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

Bridge
:将抽象部分与它的实现部分分离,使它们都可以独立地变化。

Builder
:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。

Chain of Responsibility
:为解除请求的发送者和接收者之间耦合,而使多个对象都有机会处理这个请求。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它。

Command
:将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可取消的操作。

Composite
:将对象组合成树形结构以表示部分-整体的层次结构。它使得客户对单个对象和复合对象的使用具有一致性。

Decorator
:动态地给一个对象添加一些额外的职责。就扩展功能而言, 它比生成子类方式更为灵活。

Facade
:为子系统中的一组接口提供一个一致的界面, F a c a d e模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

Factory Method
:定义一个用于创建对象的接口,让子类决定将哪一个类实例化。Factory Method使一个类的实例化延迟到其子类。

Flyweight
:运用共享技术有效地支持大量细粒度的对象。

Interpreter
:给定一个语言, 定义它的文法的一种表示,并定义一个解释器, 该解释器使用该表示来解释语言中的句子。

Iterator
:提供一种方法顺序访问一个聚合对象中各个元素, 而又不需暴露该对象的内部表示。

Mediator
:用一个中介对象来封装一系列的对象交互。中介者使各对象不需要显式地相互引用,从而使其耦合松散,而且可以独立地改变它们之间的交互。

Memento
:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到保存的状态。

Observer
:定义对象间的一种一对多的依赖关系,以便当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动刷新。

Prototype
:用原型实例指定创建对象的种类,并且通过拷贝这个原型来创建新的对象。

Proxy
:为其他对象提供一个代理以控制对这个对象的访问。

Singleton
:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
State
:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它所属的类。

Strategy
:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。本模式使得算法的变化可独立于使用它的客户。

Template Method
:定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

Visitor
:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。

附:http://tianleiwu.spaces.live.com/blog/cns!c00b51ce0ad262d7!1456.entry

设计模式的鼻祖(四个作者, 简称"四人帮")列举了23个模式, 但真正在开发中常用的模式有哪些呢? 今天我去给实习生做了一个讲座, 其中讨论到的一个问题就是这个.

我对一个搜索引擎的代码进行分析,结果如下:

最常用:Factory Method, Strategy, Singleton, Iterator
偶尔用:
Abstract Factory, Builder, Adapter, Bridge, Composite, Interpreter, Command, Mediator, Observer, State
几乎不用: Prototype, Decorator, Flyweight, Facade, Proxy, Template Method, Chain of Responsibility, Memento, Visitor

 

19、    下面这段template函数safe_reinterpret_castFrom类转换为To类型

#define STATIC_CHECK(expr){char unnamed[(expr)?1:0];}

template<class To,class From>

To safe_reinterpret_cast(From from)

{

       assert(sizeof(From)<=sizeof(To));

       return reinterpret_cast<To>(from);

}

如果把程序中的assert语句换成STATIC_CHECK(sizeof(From)<=sizeof(To))有何不同?

原创粉丝点击