C++经典面试问题

C++经典面试问题,第1张

   c++经典面试问题分享

1,关于动态申请内存

答:内存分配方式三种:

(1)从静态存储区域分配:内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。

全局变量,static变量。

(2)在栈上创建:在执行函数时,函数内局部变量的存储单元都可以在栈上创建,

函数执行结束时这些存储单元自动被释放。

栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

(3)用malloc或new申请内存之后,应该立即检查指针值是否为null.防止使用指针值为null的内存,

不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。避免数组或指针的下标越界,

特别要当心发生“多1”或者“少1” *** 作。动态内存的申请与释放必须配对,防止内存泄漏。

用free或delete释放了内存之后,立即将指针设置为null,防止产生“野指针”。从堆上分配,亦称动态内存分配。

程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。

动态内存的生存期由程序员决定,使用非常灵活。(int *parrayint myarray[6]parray = &myarray[0])

如果在申请动态内存时找不到足够大的内存块,malloc和new将返回null指针,

判断指针是否为null,如果是则马上用return语句终止本函数,

或者马上用exit(1)终止整个程序的运行,为new和malloc设置异常处理函数。

2,c++指针攻破

答案:指针是一个变量,专门存放内存地址,特点是能访问所指向的内存

指针本身占据了4个字节的长度

int **ptr//指针的类型是 int

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

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

ptr++:指针ptr的值加上了sizeof(int)

ptr+=5:将指针ptr的值加上5*sizeof(int)

指针的赋值:

把一个变量的地址赋予指向相同数据类型的指针变量( int aint *ipip=&a)

把一个指针变量的值赋予指向相同类型变量的另一个指针变量(int aint *pa=&aint *pbpb=pa)

把数组的首地址赋予指向数组的指针变量(int a[5],*papa=a也可写为:pa=&a[0])

如果给指针加1或减1 ,实际上是加上或减去指针所指向的数据类型大小。

当给指针加上一个整数值或减去一个整数值时,表达式返回一个新地址。

相同类型的两个指针可以相减,减后返回的整数代表两个地址间该类型的实例个数。

int cc=new (int*)[10]声明一个10个元素的数组,数组每个元素都是一个int *指针,

每个元素还可以单独申请空间,因为cc的类型是int*型的指针,所以你要在堆里申请的话就要用int *来申请

int a= new int * [2] //申请两个int * 型的空间

a[0] = new int[4]////为a的第一个元素申请了4个int 型空间,a[0] 指向了此空间的首地址处

a[1] = new int[3]//为a的第二个元素又申请了3个int 型空间,a[1]指向了此空间首地址处

指针数组初始化赋值:

一维指针开辟空间:char *strint *arrscanf("%d",&n)

str=(char*)malloc(sizeof(char)*n)

arr=(int*)malloc(sizeof(int)*n)

二维指针开辟空间:int **arr, iscanf("%d%d",&row,&col)

arr=(int)malloc(sizeof(int)*row)

for(i=0i

arr[i]=(int*)malloc(sizeof(int)*col)

结构体指针数组,例如typedef struct{ char xint y}quan,*qquan

定义一个结构体指针数组如:qquan a[max]

for(i=0i

{

a[i]=(qquan)malloc(sizeof(quan))

memset(a[i],0,sizeof(quan))

}

指针数组赋值

float a[]={100,200,300,400,500}

float *p[5]={&a[0],&a[1],&a[2],&a[3],&a[4]}

char *units[1000]

char get_unit[250]

for(int i=0i

scanf("%s", get_unit)strcpy(units[i],get_unit)}

3,复杂指针解析:

(1)int (*func)(int *p)

(*func)()是一个函数,func是一个指向这类函数的指针,就是一个函数指针,这类函数具有int*类型的形参,返回值类型是 int。

(2)int (*func)(int *p, int (*f)(int*))

func是一个指向函数的指针,这类函数具有int *和int (*)(int*)这样的形参。形参int (*f)(int*),f也是一个函数指针

(3)int (*func[5])(int *p)

func数组的元素是函数类型的指针,它所指向的函数具有int*类型的形参,返回值类型为int。

(4)int (*(*func)[5])(int *p)

func是一个指向数组的指针,这个数组的元素是函数指针,这些指针指向具有int*形参,返回值为int类型的函数。

(5)int (*(*func)(int *p))[5]

func是一个函数指针,这类函数具有int*类型的形参,返回值是指向数组的指针,所指向的数组的元素是具有5个int元素的数组。

注意:

需要声明一个复杂指针时,如果把整个声明写成上面所示的形式,对程序可读性是一大损害。

应该用typedef来对声明逐层,分解,增强可读性,例如对于声明:int (*(*func)(int *p))[5]

这样分解:typedef int (*para)[5]typedef para (*func)(int *)

例如:int (*(*func)[5][6])[7][8]

func是一个指向数组的指针,这类数组的元素是一个具有5x6个int元素的二维数组,而这个二维数组的元素又是一个二维数组。

typedef int (*para)[7][8]

typedef para (*func)[5][6]

例如:int (*(*(*func)(int *))[5])(int *)

func是一个函数指针,这类函数的返回值是一个指向数组的指针,

所指向数组的元素也是函数指针,指向的函数具有int*形参,返回值为int。

typedef int (*para1)(int*)

typedef para1 (*para2)[5]

typedef para2 (*func)(int*)

4,函数指针详解

答:函数指针是指向一个函数入口的指针

一个函数指针只能指向一种类型的函数,即具有相同的返回值和相同的参数的函数。

函数指针数组定义:void(*fun[3])(void*)相应指向类a的成员函数的指针:void (a::*pmf)(char *, const char *)

指向外部函数的指针:void (*pf)(char *, const char *)void strcpy(char * dest, const char * source)pf=strcpy

5,野指针

答:“野指针”是很危险的,if语句对它不起作用。“野指针”的成因主要有两种:

(1)指针变量没有被初始化。指针变量在创建的同时应当被初始化,要么将指针设置为null,要么让它指向合法的内存。

char *p = nullchar *str = (char *) malloc(100)

(2)指针p被free或者delete之后,没有置为null

(3)指针 *** 作超越了变量的作用范围。所指向的内存值对象生命期已经被销毁

6,引用和指针有什么区别?

答:引用必须初始化,指针则不必引用初始化以后不能改变,指针可以改变其指向的对象

不存在指向空值的引用,但存在指向控制的指针

引用是某个对象的别名,主要用来描述函数和参数和返回值。而指针与一般的变量是一样的,会在内存中开辟一块内存。

如果函数的参数或返回值是类的对象的话,采用引用可以提高程序的效率。

7,c++中的const用法

答:char * const p// 指针不可改,也就说指针只能指向一个地址,不能更改为其他地址,修饰指针本身

char const * p// 所指内容不可改,也就是说*p是常量字符串,修饰指针所指向的变量

const char * const p 和 char const * const p// 内容和指针都不能改

const修饰函数参数是它最广泛的一种用途,它表示函数体中不能修改参数的值,

传递过来的参数在函数内不可以改变,参数指针所指内容为常量不可变,参数指针本身为常量不可变

在引用或者指针参数的时候使用const限制是有意义的,而对于值传递的参数使用const则没有意义

const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改。

const修饰的对象,该对象的任何非const成员函数都不能被调用,因为任何非const成员函数会有修改成员变量的企图。

const修饰类的成员变量,表示成员常量,不能被修改,同时它只能在初始化列表中赋值。static const 的成员需在声明的地方直接初始。

const修饰类的成员函数,则该成员函数不能修改类中任何非const成员。一般写在函数的最后来修饰。

在函数实现部分也要带const关键字.

对于const类对象/指针/引用,只能调用类的const成员函数,因此,const修饰成员函数的最重要作用就是限制对于const对象的使用

使用const的一些建议:在参数中使用const应该使用引用或指针,而不是一般的对象实例

const在成员函数中的三种用法(参数、返回值、函数)要很好的使用

const在成员函数中的三种用法(参数、返回值、函数)要很好的使用

不要轻易的将函数的返回值类型定为const除了重载 *** 作符外一般不要将返回值类型定为对某个对象的const引用

8,const常量与define宏定义的区别

答:(1) 编译器处理方式不同。define宏是在预处理阶段展开,生命周期止于编译期。

只是一个常数、一个命令中的参数,没有实际的存在。

#define常量存在于程序的代码段。const常量是编译运行阶段使用,const常量存在于程序的数据段.

(2)类型和安全检查不同。define宏没有类型,不做任何类型检查,仅仅是展开。

const常量有具体的类型,在编译阶段会执行类型检查。

(3) 存储方式不同。define宏仅仅是展开,有多少地方使用,就展开多少次,不会分配内存。

const常量会在内存中分配(可以是堆中也可以是栈中)

9,解释堆和栈的区别

答:1、栈区(stack)— 由编译器自动分配释放,存放函数的参数值,局部变量的值等。其 *** 作方式类似于数据结构中的栈。

由系统自动分配。声明在函数中一个局部变量 int b系统自动在栈中为b开辟空间 。

只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。

在windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域,栈的大小是2m。

如果申请的空间超过栈的剩余空间时,将提示overflow。

栈由系统自动分配,速度较快。但程序员是无法控制的。

函数调用时,第一个进栈的是主函数中后的下一条指令,的地址,然后是函数的各个参数。

在大多数的c编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。

堆区(heap) — 一般由程序员分配释放,若程序员不释放,程序结束时可能由os回收 。

注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,需要程序员自己申请,并指明大小,在c中malloc函数

在c++中用new运算符。首先应该知道 *** 作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,

另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

堆是向高地址扩展的数据结构,是不连续的内存区域。而链表的遍历方向是由低地址向高地址。

堆的大小受限于计算机系统中有效的虚拟内存。

堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便

一般是在堆的头部用一个字节存放堆的大小。

10,论述含参数的宏和函数的优缺点

(1)函数调用时,先求出实参表达式的值,然后代入形参。而使用带参的宏只是进行简单的字符替换

(2)函数调用是在程序运行时处理的,分配临时的内存单元而宏展开是在编译时进行的,在展开时不进行

内存分配,不进行值得传递处理,没有“返回值”概念

(3)对函数中的形参和实参都要定义类型,类型要求一致,如不一致则进行类型转换。而宏不存在类型问题

(4)调用函数只可得到一个返回值,而用宏则可以设法得到几个结果

(5)实用宏次数多时,宏展开后源程序变长,没展开一次源程序增长,函数调用则不会

(6)宏替换不占用运行时间,只占编译时间,而函数调用占用运行时间

11,c++的空类,默认产生哪些类成员函数?

答:class empty

{

public:

empty()//缺省构造函数

empty(const empty&)//拷贝构造函数

~empty()//虚构函数

empty&operator(const empty&) //赋值运算符

empty&operator&()//取址运算符

const empty* operator&() const// 取址运算符 const

}

12,谈谈类和结构体的区别

答:结构体在默认情况下的成员都是public的,而类在默认情况下的成员是private的。结构体和类都必须使用new创建,

struct保证成员按照声明顺序在内存在存储,而类不保证。

13,c++四种强制类型转换

答:(1)const_cast

字面上理解就是去const属性,去掉类型的const或volatile属性。

struct sa{ int k}const sa ra

ra.k = 10//直接修改const类型,编译错误 sa&rb = const_cast(ra)rb.k = 10//可以修改

(2)static_cast

主要用于基本类型之间和具有继承关系的类型之间的转换。用于指针类型的转换没有太大的意义

static_cast是无条件和静态类型转换,可用于基类和子类的转换,基本类型转换,把空指针转换为目标类型的空指针,

把任何类型的表达式转换成void类型,static_cast不能进行无关类型(如非基类和子类)指针之间的转换。

int adouble d = static_cast(a)//基本类型转换

int &pn = &avoid *p = static_cast(pn)//任意类型转换为void

(3)dynamic_cast

你可以用它把一个指向基类的指针或引用对象转换成继承类的对象

动态类型转换,运行时类型安全检查(转换失败返回null)

基类必须有虚函数,保持多态特性才能用dynamic_cast

只能在继承类对象的指针之间或引用之间进行类型转换

class baseclass{public: int m_inumvirtual void foo(){}}

class derivedclass:baseclass{public: char* szname[100]void bar(){}}

baseclass* pb = new derivedclass()

derivedclass *p2 = dynamic_cast(pb)

baseclass* pparent = dynamic_cast(p2)

//子类->父类,动态类型转换,正确

(4)reinterpreter_cast

转换的类型必须是一个指针、引用、算术类型、函数指针或者成员指针。

主要是将一个类型的指针,转换为另一个类型的指针

不同类型的指针类型转换用reinterpreter_cast

最普通的用途就是在函数指针类型之间进行转换

int dosomething(){return 0}

typedef void(*funcptr)(){}

funcptr funcptrarray[10]

funcptrarray[0] = reinterpreter_cast(&dosomething)

14,c++函数中值的传递方式有哪几种?

答:函数的三种传递方式为:值传递、指针传递和引用传递。

15,将“引用”作为函数参数有哪些特点

答:(1)传递引用给函数与传递指针的效果是一样的,这时,被调函数的形参就成为原来主调函数的实参变量或者

对象的一个别名来使用,所以在被调函数中形参的 *** 作就是对相应的目标对象的 *** 作

(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参 *** 作,当参数数据较大时,引用

传递参数的效率和所占空间都好

(3)如果使用指针要分配内存单元,需要重复使用“*指针变量名”形式进行计算,容易出错且阅读性较差。

本文集中了多条常见的C# Net经典面试题目例如 NET中类和结构的区别 ASP NET页面之间传递值的几种方式? 并简明扼要的给出了答案 希望能对学习C# Net的读者有所帮助

请你说说 NET中类和结构的区别?

答 结构和类具有大体的语法 但是结构受到的限制比类要多 结构不能申明有默认的构造函数 为结构的副本是又编译器创建和销毁的 所以不需要默认的构造函数和析构函数 结构是值类型 所以对结构变量所做的改变不会影响其的原值 而类是应用类型 改变其变量的值会改变其原值 申明结构用Struck关键字 申明类用class关键字 向方法传递结构是时是通过值传递的 而不是通过引用 与类不同 结构的实例化可以不使用New关键字 类可以实现接口

死锁的必要条件?怎么克服?

答 系统的资源不足 进程的推进的顺序不合适 资源分配不当 一个资源每次只能被一个进程使用 一个资源请求资源时 而此时这个资源已阻塞 对已获得资源不放 进程获得资源时 未使用完前 不能强行剥夺

接口是否可以继承接口?抽象类是否可以实现接口?抽象类是否可以继承实体类?

答 接口是可以继承接口的 抽象类是可以实现接口的 抽象类可以继承实体类 但是有个条件 条件是 实体类必须要有明确的构造函数

构造器Constructor是否可以被继承?是否可以被Override?

答 Constructor不可以被继承 因此不能被重写(Overriding) 但可以被重载(Overloading)

当一个线程进入一个对象的方法后 其它线程是否可以进入该对象的方法?

答 不可以 一个对象的方法只能由一个线程访问

用最有效的方法算出等已 对于几?

答 <<

C#是否可以对内存直接进行 *** 作?

答 这个问题比较难回答 也是个很大的问题 但是可以这样问答 C#是可以对内存进行直接 *** 作的 虽然很少用到指针 但是C#是可以使用指针的 在用的时候需要在前边加unsafe 中使用了垃圾回收机制(GC)功能 它替代了程序员 不过在C#中不可以直接使用finalize方法 而是在析构函数中调用基类的finalize()方法

Error和Exception有是区别?

答 error表示恢复不是不可能 但是很困难 exception表示一种实际或实现问题 它表示程序运行正常不可以发生的

谈谈final finally finallize的区别?

答 final用于申明属性 方法和类 表示属性不可变 方法不可以被覆盖 类不可以被继承

Finally是异常处理语句结构中 表示总是执行的部分

Finallize表示是object类一个方法 在垃圾回收机制中执行的时候会被调用被回收对象的方法

HashMap和Hashtable区别?

答 HashMap是Hashtable的轻量级实现 非线程安全的实现他们都实现了map接口 主要区别是HashMap键值可以为空null 效率可以高于Hashtable

Collection和Collections的区别?

答 Collection是集合类的上级接口 Collections是针对集合类的一个帮助类 它提供一系列静态方法来实现对各种集合的搜索 排序 线程安全化 *** 作

C#中委托是什么?事件是不是一种委托?

答 委托是一种安全的类似与函数指针 但是她比指针要安全的多 它可以把方法作为一个参数传递给另一个方法 可以理解为指向函数的引用 事件是一种消息机制 它是一种委托 委托不带方法体

Override Overload 的区别?

答 Override是重写的意思 它表示重写基类的方法 而且方法的名称 返回类型 参数类型 参数个数要与基类相同

Overload是重载是意思 它也表示重

写基类的方法 但是只要方法名相同 别的可以不同

在一个BS结构中需要传递变量值时 不能使用session coolke application 你有几中方法?

答 this server Transfer Querystring

C#种索引器实现过程 是否只能根据数字索引?

答 不是的 可以是任意类型

New有种用法?

答 有 种 第一种是 实例化如 New Class()

第二种是 public new 隐藏基类的方法

第三种是 在泛型类申明中的任何类型参数都必须有公共的无参构造函数

任何把一个Array复制到Arraylist中?

答 Foreach (object o in Array) ArrayList Add ( )

等有好多中种方法 自己想 概述反射和序列化? 答 反射 要给发射下一个定义还是比较难的 这里先说说我的理解 反射提供了封装程序集 模块和类型对象 可以用反射动态地创建类型的实例 将类型绑定到现有对象 或者从现有对象类型里获取类型 然后调用类型的方法或访问字段和属性

序列化 将对象转换为另一种媒介传输的格式过程 如 序列化一个对象 用Http通过internet在客户端和服务器之间传递该对象 在另一端用反序列化从该流中重新得到对象

Const和ReadOnly?

答 Const用来申明编程时申明常量 ReadOnly用来申明运行时常量

UDP和TCP连接有和异同?

答 TCP是传输控制协议 提供的是面向连接的 是可靠的 字节流服务 当用户和服务器彼此进行数据交互的时候 必须在他们数据交互前要进行TCP连接之后才能传输数据 TCP提供超时重拨 检验数据功能 UDP是用户数据报协议 是一个简单的面向数据报的传输协议 是不可靠的连接

进程和线程分别该怎么理解?

答 进程是比线程大的程序运行单元 都是由 *** 作系统所体会的系统运行单元 一个程序中至少要有一个进程 有一个进程中 至少要有一个线程 线程的划分尺度要比进程要小 进程拥有独立的内存单元 线程是共享内存 从而极大的提高了程序的运行效率同一个进程中的多个线程可以并发执行

ASP NET页面之间传递值的几种方式?

答 QueryString session cookies application server Transfer respose redictor

什么叫应用程序域?什么是托管代码?什么是强类型系统?什么是装箱和拆箱?什么是重载?CTS CLS和CLR分别作何解释?

答 应用程序域 就是为安全性 可靠性 隔离性 和版本控制 及卸载程序提供的隔离边界 它通常由运行库宿主创建 应用程序域提供了一个更安全 用途更广的处理单元

托管代码 使用CLR编译语言编辑器开发编写的代码就叫托管代码

装箱和拆箱 是把值类型转换为引用类型的过程 是隐式的 相反的过程就是拆箱 是显式的

CTS是公共类型系统 CLS是公共语言规范 CLR公共语言运行库

强类型系统 每个变量和对象都必须具有申明类型

值类型和引用类型的区别?

答 值类型的值是存放在堆栈中的 改变其值 不改变变量原有的值 而引用类型的值是存放在栈中的 其引用的地址是存放在堆栈中的 改变其值也就改变了变量原有的值 值类型不允许包含null值 然而可空类型可以将null赋值给值类型l

的身份验证方式有哪些?

答 windows forms passport

解释一下UDDI WSDL的意义及其作用?

答 UDDI是统一描述集成协议 是一套基于Web的 分布式的 为WEB服务提供的信息注册的实现标准规范 同时为也是为企业本身提供的Web服务注册以让别的企业能够发现并访问的协议标准 提供了基于标准的规范 用于描述和发现服务 还提供了一组基于因特网的实现

WSDL这是一个基于XML的描述WEB服务的接口

什么是SOAP?

答 是简单访问协议 是在分布式环境中 交换信息并实现远程调用的协议 是一个基于XML的协议 使用SOAP 可以不考虑任何传输协议 但通常还是HTTP协议 可以允许任何类型的对象或代码 在任何平台上 以任一种语言相互通信 它是一种轻量级协议

如何部署一个页面?

答 vs vs 里边都有发表机制 vs 可以发布然后在复制部署

Vs 可以直接部署到对应的位置

如何理中的垃圾回收机制?

答 NET中的垃圾回收机制是引用程序对内存的回收和释放 当每次用new关键字创建一个对象时 运行库都要从托管堆中为其分配内存 因为空间是有限的 最终垃圾回收机制是要回收不用的内存的 已释放内存 重新使用

面向对象的三大基本原则?

答 封装 继承 多态

在 NET中所有类的基类是?

答 object

能用foreach遍历访问的对象需要实现?

答 需要实现IEnumerable接口和GetEnumerator ()方法

Heap与stack的差别?

答 Heap是堆 空间是由手动 *** 作分配和释放的 它的存储区很大的自由存储区

lishixinzhi/Article/program/net/201311/12642

C语言面试常见问题

预处理器(Preprocessor)

1 . 用预处理指令#define 声明一个常数,用以表明1年中有多少秒(忽略闰年问题)

#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL

我在这想看到几件事情:

1) #define 语法的基本知识(例如:不能以分号结束,括号的使用,等等)

2)懂得预处理器将为你计算常数表达式的值,因此,直接写出你是如何计算一年中有多少秒而不是计算出实际的值,是更清晰而没有代价的。

3) 意识到这个表达式将使一个16位机的整型数溢出-因此要用到长整型符号L,告诉编译器这个常数是的长整型数。

4) 如果你在你的表达式中用到UL(表示无符号长整型),那么你有了一个好的起点。记住,第一印象很重要。

2 . 写一个"标准"宏MIN ,这个宏输入两个参数并返回较小的一个。

#define MIN(A,B) ((A) <= (B) ? (A) : (B))

这个测试是为下面的目的而设的:

1) 标识#define在宏中应用的基本知识。这是很重要的。因为在 嵌入(inline) *** 作符 变为标准C的一部分之前,宏是方便产生嵌入代码的唯一方法,对于嵌入式系统来说,为了能达到要求的性能,嵌入代码经常是必须的方法。

2)三重条件 *** 作符的知识。这个 *** 作符存在C语言中的原因是它使得编译器能产生比if-then-else更优化的代码,了解这个用法是很重要的。

3) 懂得在宏中小心地把参数用括号括起来

4) 我也用这个问题开始讨论宏的副作用,例如:当你写下面的代码时会发生什么事?

least = MIN(*p++, b)

3. 预处理器标识#error的目的是什么?

如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道答案。

死循环(Infinite loops)

4. 嵌入式系统中经常要用到无限循环,你怎么样用C编写死循环呢?

这个问题用几个解决方案。我首选的方案是:

while(1)

{

}

一些程序员更喜欢如下方案:

for()

{

}

这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的基本原理。如果他们的基本答案是:"我被教着这样做,但从没有想到过为什么。"这会给我留下一个坏印象。

第三个方案是用 goto

Loop:

...

goto Loop

应试者如给出上面的方案,这说明或者他是一个汇编语言程序员(这也许是好事)或者他是一个想进入新领域的BASIC/FORTRAN程序员。

数据声明(Data declarations)

5. 用变量a给出下面的定义

a) 一个整型数(An integer)

b)一个指向整型数的指针( A pointer to an integer)

c)一个指向指针的的指针,它指向的指针是指向一个整型数( A pointer to a pointer to an intege)r

d)一个有10个整型数的数组( An array of 10 integers)

e) 一个有10个指针的数组,该指针是指向一个整型数的。(An array of 10 pointers to integers)

f) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers)

g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)

h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )

答案是:

a) int a// An integer

b) int *a// A pointer to an integer

c) int **a// A pointer to a pointer to an integer

d) int a[10]// An array of 10 integers

e) int *a[10]// An array of 10 pointers to integers

f) int (*a)[10]// A pointer to an array of 10 integers

g) int (*a)(int)// A pointer to a function a that takes an integer argument and returns an integer

h) int (*a[10])(int)// An array of 10 pointers to functions that take an integer argument and return an integer

人们经常声称这里有几个问题是那种要翻一下书才能回答的问题,我同意这种说法。当我写这篇文章时,为了确定语法的正确性,我的确查了一下书。但是当我被面试的时候,我期望被问到这个问题(或者相近的问题)。因为在被面试的这段时间里,我确定我知道这个问题的答案。应试者如果不知道所有的答案(或至少大部分答案),那么也就没有为这次面试做准备,如果该面试者没有为这次面试做准备,那么他又能为什么出准备呢?

Static

6. 关键字static的作用是什么?

这个简单的问题很少有人能回答完全。在C语言中,关键字static有三个明显的作用:

1)在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。

2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量。

3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。

大多数应试者能正确回答第一部分,一部分能正确回答第二部分,同是很少的人能懂得第三部分。这是一个应试者的严重的缺点,因为他显然不懂得本地化数据和代码范围的好处和重要性。

Const

7.关键字const有什么含意?

我只要一听到被面试者说:"const意味着常数",我就知道我正在和一个业余者打交道。去年Dan Saks已经在他的文章里完全概括了const的所有用法,因此ESP(译者:Embedded Systems Programming)的每一位读者应该非常熟悉const能做什么和不能做什么.如果你从没有读到那篇文章,只要能说出const意味着"只读"就可以了。尽管这个答案不是完全的答案,但我接受它作为一个正确的答案。(如果你想知道更详细的答案,仔细读一下Saks的文章吧。)

如果应试者能正确回答这个问题,我将问他一个附加的问题:

下面的声明都是什么意思?

const int a

int const a

const int *a

int * const a

int const * a const

/******/

前两个的作用是一样,a是一个常整型数。第三个意味着a是一个指向常整型数的指针(也就是,整型数是不可修改的,但指针可以)。第四个意思a是一个指向整型数的常指针(也就是说,指针指向的整型数是可以修改的,但指针是不可修改的)。最后一个意味着a是一个指向常整型数的常指针(也就是说,指针指向的整型数是不可修改的,同时指针也是不可修改的)。如果应试者能正确回答这些问题,那么他就给我留下了一个好印象。顺带提一句,也许你可能会问,即使不用关键字 const,也还是能很容易写出功能正确的程序,那么我为什么还要如此看重关键字const呢?我也如下的几下理由:

1) 关键字const的作用是为给读你代码的人传达非常有用的信息,实际上,声明一个参数为常量是为了告诉了用户这个参数的应用目的。如果你曾花很多时间清理其它人留下的垃圾,你就会很快学会感谢这点多余的信息。(当然,懂得用const的程序员很少会留下的垃圾让别人来清理的。)

2) 通过给优化器一些附加的信息,使用关键字const也许能产生更紧凑的代码。

3) 合理地使用关键字const可以使编译器很自然地保护那些不希望被改变的参数,防止其被无意的代码修改。简而言之,这样可以减少bug的出现。

Volatile

8. 关键字volatile有什么含意?并给出三个不同的例子。

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:

1) 并行设备的硬件寄存器(如:状态寄存器)

2) 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)

3) 多线程应用中被几个任务共享的变量

回答不出这个问题的人是不会被雇佣的。我认为这是区分C程序员和嵌入式系统程序员的最基本的问题。搞嵌入式的家伙们经常同硬件、中断、RTOS等等打交道,所有这些都要求用到volatile变量。不懂得volatile的内容将会带来灾难。

假设被面试者正确地回答了这是问题(嗯,怀疑是否会是这样),我将稍微深究一下,看一下这家伙是不是直正懂得volatile完全的重要性。

1)一个参数既可以是const还可以是volatile吗?解释为什么。

2)一个指针可以是volatile 吗?解释为什么。

3)下面的函数有什么错误:

int square(volatile int *ptr)

{

return *ptr * *ptr

}

下面是答案:

1)是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。

2)是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。

3) 这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:

int square(volatile int *ptr)

{

int a,b

a = *ptr

b = *ptr

return a * b

}

由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:

long square(volatile int *ptr)

{

int a

a = *ptr

return a * a

}

位 *** 作(Bit manipulation)

9. 嵌入式系统总是要用户对变量或寄存器进行位 *** 作。给定一个整型变量a,写两段代码,第一个设置a的bit 3,第二个清除a 的bit 3。在以上两个 *** 作中,要保持其它位不变。

对这个问题有三种基本的反应

1)不知道如何下手。该被面者从没做过任何嵌入式系统的工作。

2) 用bit fields。Bit fields是被扔到C语言死角的东西,它保证你的代码在不同编译器之间是不可移植的,同时也保证了的你的代码是不可重用的。我最近不幸看到 Infineon为其较复杂的通信芯片写的驱动程序,它用到了bit fields因此完全对我无用,因为我的编译器用其它的方式来实现bit fields的。从道德讲:永远不要让一个非嵌入式的家伙粘实际硬件的边。

3) 用 #defines 和 bit masks *** 作。这是一个有极高可移植性的方法,是应该被用到的方法。最佳的解决方案如下:

#define BIT3 (0x1 <<3)

static int a

void set_bit3(void)

{

a |= BIT3

}

void clear_bit3(void)

{

a &= ~BIT3

}

一些人喜欢为设置和清除值而定义一个掩码同时定义一些说明常数,这也是可以接受的。我希望看到几个要点:说明常数、|=和&=~ *** 作。

访问固定的内存位置(Accessing fixed memory locations)

10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。

这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:

int *ptr

ptr = (int *)0x67a9

*ptr = 0xaa55

A more obscure approach is:

一个较晦涩的方法是:

*(int * const)(0x67a9) = 0xaa55

即使你的品味更接近第二种方案,但我建议你在面试时使用第一种方案。

中断(Interrupts)

11. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字 __interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。

__interrupt double compute_area (double radius)

{

double area = PI * radius * radius

printf("\nArea = %f", area)

return area

}

这个函数有太多的错误了,以至让人不知从何说起了:

1)ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。

2) ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。

3) 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。

4) 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。

代码例子(Code examples)

12 . 下面的代码输出是什么,为什么?

void foo(void)

{

unsigned int a = 6

int b = -20

(a+b >6) ? puts(">6") : puts("<= 6")

}

这个问题测试你是否懂得C语言中的整数自动转换原则,我发现有些开发者懂得极少这些东西。不管如何,这无符号整型问题的答案是输出是 ">6"。原因是当表达式中存在有符号类型和无符号类型时所有的 *** 作数都自动转换为无符号类型。因此-20变成了一个非常大的正整数,所以该表达式计算出的结果大于6。这一点对于应当频繁用到无符号数据类型的嵌入式系统来说是丰常重要的。如果你答错了这个问题,你也就到了得不到这份工作的边缘。

13. 评价下面的代码片断:

unsigned int zero = 0

unsigned int compzero = 0xFFFF

/*1's complement of zero */

对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:

unsigned int compzero = ~0

这一问题真正能揭露出应试者是否懂得处理器字长的重要性。在我的经验里,好的嵌入式程序员非常准确地明白硬件的细节和它的局限,然而PC机程序往往把硬件作为一个无法避免的烦恼。

到了这个阶段,应试者或者完全垂头丧气了或者信心满满志在必得。如果显然应试者不是很好,那么这个测试就在这里结束了。但如果显然应试者做得不错,那么我就扔出下面的追加问题,这些问题是比较难的,我想仅仅非常优秀的应试者能做得不错。提出这些问题,我希望更多看到应试者应付问题的方法,而不是答案。不管如何,你就当是这个娱乐吧...

动态内存分配(Dynamic memory allocation)

14. 尽管不像非嵌入式计算机那么常见,嵌入式系统还是有从堆(heap)中动态分配内存的过程的。那么嵌入式系统中,动态分配内存可能发生的问题是什么?

这里,我期望应试者能提到内存碎片,碎片收集的问题,变量的持行时间等等。这个主题已经在ESP杂志中被广泛地讨论过了(主要是 P.J. Plauger, 他的解释远远超过我这里能提到的任何解释),所有回过头看一下这些杂志吧!让应试者进入一种虚假的安全感觉后,我拿出这么一个小节目:

下面的代码片段的输出是什么,为什么?

char *ptr

if ((ptr = (char *)malloc(0)) == NULL)

puts("Got a null pointer")

else

puts("Got a valid pointer")

这是一个有趣的问题。最近在我的一个同事不经意把0值传给了函数malloc,得到了一个合法的指针之后,我才想到这个问题。这就是上面的代码,该代码的输出是"Got a valid pointer"。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。

Typedef

15 Typedef 在C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:

#define dPS struct s *

typedef struct s * tPS

以上两种情况的意图都是要定义dPS 和 tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?

这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是:typedef更好。思考下面的例子:

dPS p1,p2

tPS p3,p4

第一个扩展为

struct s * p1, p2

.

上面的代码定义p1为一个指向结构的指,p2为一个实际的结构,这也许不是你想要的。第二个例子正确地定义了p3 和p4 两个指针。

晦涩的语法

16 . C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?

int a = 5, b = 7, c

c = a+++b

这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成:

c = a++ + b

因此, 这段代码持行后a = 6, b = 7, c = 12。

如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/yw/11356313.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-15
下一篇 2023-05-15

发表评论

登录后才能评论

评论列表(0条)

保存