iOS经典面试题

来源:互联网 发布:好听的网络歌曲虾米 编辑:程序博客网 时间:2024/05/30 05:09

1. 简单的C++程序源程序的基本结构:主函数

int main()

   {

    int x=3;

    printf("%d",x);

    return 1;

   }

问函数既然不会被其它函数调用,为什么要返回1

解答:mian中,c标准认为0表示成功,非0表示错误。具体的值是某中具体出错信息

2. 文件包含:头文件

头文件的作用是什么?

答:一、通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需要按照头文件中的接口声明来调用库功能,而不必关心接口怎么实现的。编译器会从库中提取相应的代码。二、头文件能加强类型安全检查。如果某个接口被实现或被使用时,其方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担。

3. 宏定义:

1)写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。另外,当你写下面的代码时会发生什么事?

least = MIN(*p++, b); 

   解答:

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

MIN(*p++, b)会产生宏的副作用

A.谨慎地将宏定义中的“参数”和整个宏用用括弧括起来。所以,严格地讲,下述解答:

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

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

   都应判0分;

B.防止宏的副作用。

   宏定义#define MIN(A,B) ((A) <= (B) ? (A) : (B))MIN(*p++, b)的作用结果是:

((*p++) <= (b) ? (*p++) : (*p++))

2)对于一个频繁使用的短小函数,C语言中应用什么实现,C++中应用什么实现?

答:c用宏定义,c++inline

4. 预处理

(1) 判断下面的输出:输出:

#include<iostream.h>

using namespace std; 
void main() 
{

int a=10; 
#if (a>1) 
cout<<"a>1"<<endl; 
#else 
cout<<"a<=1"<<endl; 
#endif 
}  

注释:#if在这里是预处理命令 
    #if语句要判断是宏的值,宏的值只有通过#define或者在命令行-DXXX的形式定义。 
    用int a=10;形式定义了一个整型常量,而不是一个宏。 
    对于宏的替换,是在预编译阶段就进行的,会把所有的宏替换为实际的值。而对int a=10; 的解析,却是在编译阶段。 在预编译阶段,只认#define定义的宏,不会理会int  a=10; 定义出来的常量,所以#if永远为假。

(2) 为什么标准头文件都有类似以下的结构?

#ifndef__INCvxWorksh

#define__INCvxWorksh

#endif 

解答:作用是防止头文件被重复包含

5. 基本数据类型,使用sizeof查看大:

1void Func ( char str[100] )

{

   izeof( str ) = ?

  }

解答:函数中数组名作为函数形参时,在函数体内,数组名失去了本身的内涵,仅仅只是一个指针;在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。sizeof( str ) = 4;

2Typedef 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 两个指针。

6. 变量,常量

1)程序的局部变量存在于(堆栈)中,全局变量存在于(静态区 )中,动态申请数据存在于( 堆)中

2)用变量a给出下面的定义

a) 一个整型数(An integer) 

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

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

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 

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

3)全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?

解答:可以,在不同的C文件中以static形式来声明同名全局变量。

可以在不同的C文件中声明同名的全局变量,前提是其中只能有一个C文件中对此变量赋初值,此时连接不会出错。

4)下面的代码输出是什么,为什么?

void foo(void) 

unsigned int a = 6; 

int b = -20; 

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

}

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

7. 运算符的优先级和结合性

int a = 5, b = 7, c; 
c = a+++b;

问:  a? b? 

答:  a = 6, b = 7, c = 12

8. 请说出static关键字尽可能多的作用?

解答:

static关键字至少有下列n个作用:

1)函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;

2)在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;

3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;

4)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;

5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。

9. 请说出const关键字尽可能多的作用?

解答:

1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;

2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const

3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;

4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;

5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。例如:

const classA operator*(const classA& a1,const classA& a2); 

operator*的返回结果必须是一个const对象。如果不是,这样的变态代码也不会编译出错:

classA a, b, c;

(a * b) = c; // a*b的结果赋值,操作(a * b) = c显然不符合编程者的初衷,也没有任何意义。

6const 与指针作用

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的出现。

剖析:

  惊讶吗?小小的staticconst居然有这么多功能,我们能回答几个?如果只能回答1~2个,那还真得闭关再好好修炼修炼。

这个题可以考查面试者对程序设计知识的掌握程度是初级、中级还是比较深入,没有一定的知识广度和深度,不可能对这个问题给出全面的解答。大多数人只能回答出staticconst关键字的部分功能。

10. 关系和逻辑运算

(1) 分别写出BOOL,int,float,指针类型的变量与“零”的比较语句。

答案:

BOOL :    if ( !a ) or if(a)

int :     if ( a == 0)

float :   const EXPRESSION EXP = 0.000001

             if ( a < EXP && a >-EXP)

pointer : if ( a != NULL) or if(a == NULL) 

(2) i最后等于多少?

int i = 1;
int j = i++;
if((i>j++) && (i++ == j)) i+=j;

:
i = 5

11. 循环

1)不能做switch()的参数类型是:

     答:switch的参数不能为实型

2)语句for( )有什么问题?它是什么意思?

     答:无限循环,和while(1)相同。

12. 数组

1)输出结果是什么?

char str1[] = "abc";

char str2[] = "abc";

const char str3[] = "abc";

const char str4[] = "abc";

const char *str5 = "abc";

const char *str6 = "abc";

char *str7 = "abc";

char *str8 = "abc";

cout << ( str1 == str2 ) << endl;

cout << ( str3 == str4 ) << endl;

cout << ( str5 == str6 ) << endl;

cout << ( str7 == str8 ) << endl;

结果是:0 0 1 1

解答:str1,str2,str3,str4是数组变量,它们有各自的内存空间;

str5,str6,str7,str8是指针,它们指向相同的常量区域。

2char* s="AAA";

printf("%s",s);

s[0]='B';

printf("%s",s);

有什么错?

答:"AAA"是字符串常量。s是指针,指向这个字符串常量,所以声明s的时候就有问题。

cosnt char* s="AAA";

然后又因为是常量,所以对是s[0]的赋值操作是不合法的

13. 数组与指针

1main()

{

   int a[5]={1,2,3,4,5};

    int *ptr=(int *)(&a+1);

    printf("%d,%d",*(a+1),*(ptr-1));

}

输出:2,5

*(a+1)就是a[1]*(ptr-1)就是a[4],执行结果是25

&a+1不是首地址+1,系统会认为加一个a数组的偏移,是偏移了一个数组的大小(本例是5int

int *ptr=(int *)(&a+1); 

ptr实际是&(a[5]),也就是a+5

原因如下:

&a是数组指针,其类型为 int (*)[5];而指针加1要根据指针类型加上一定的值,不同类型的指针+1之后增加的大小不同a是长度为5int数组指针,所以要加 5*sizeof(int)所以ptr实际是a[5],但是prt(&a+1)类型是不一样的(这点很重要)所以prt-1只会减去sizeof(int*)

a,&a的地址是一样的,但意思不一样,a是数组首地址,也就是a[0]的地址,&a是对象(数组)首地址,a+1是数组下一元素的地址,即a[1],&a+1是下一个对象的地址,即a[5].

2)改错:

include <stdio.h>

int main(void)

 {

    int **p;

    int arr[100];

    p = &arr;

    return 0;

}

解答:

搞错了,是指针类型不同,

int **p; //二级指针

&arr; //得到的是指向第一维为100的数组的指针

i nclude <stdio.h>

int main(void) {

int **p, *q;

int arr[100];

q = arr;

p = &q;

return 0;

}

14. 字符串处理函数

1)找错

void test (char* str1)  //找错

{

   char string[10];

  if( strlen( str1 ) <= 10 )

  {

   strcpy( string, str1 );

  }

}

解答:strlen运算不包括\0位

2)找错:

void test()  

{

  char string[10], str1[10];

  int i;

  for(i=0; i<10; i++)

  {

   str1[i] = 'a';

  }

 strcpy( string, str1 );

解答:strcpy工作方式,将是str1遍历,赋值给string,遇见\0位结束,而str结束方式未知。

15. 字符数组的初始化

1)下面的输出结果是啥?

void main()

{

char aa[10];

printf("%d",strlen(aa));

}

   答案:15

 分析:sizeof()和初不初始化,没有关系;

strlen()和初始化有关。

2)下面这个程序执行后会有什么错误或者效果:

 #define MAX 255

 int main()

{

    unsigned char A[MAX],i;//i被定义为unsigned char

    for (i=0;i<=MAX;i++)

      A[i]=i;

}

解答:死循环加数组越界访问(C/C++不进行数组越界检查)

MAX=255 

数组A的下标范围为:0..MAX-1,这是其一..

其二.i循环到255,循环内执行: A[255]=255;

这句本身没有问题..但是返回for (i=0;i<=MAX;i++)语句时,由于unsigned char的取值范围在(0..255),i++以后i又为0..无限循环下去.

16. 内存操作(主要集中在指针,变量的生存期及作用范围,良好的动态内存申请和释放习惯):

1)找错

void GetMemory( char *p )

{

  p = (char *) malloc( 100 );

}

void Test( void ) 

{

 char *str = NULL;

 GetMemory( str ); 

 strcpy( str, "hello world" );

 printf( str );

解答:GetMemory( char *p )函数的形参为字符串指针,在函数内部修改形参并不能真正的改变传入形参的值,执行完

char *str = NULL;

GetMemory( str );  

后的str仍然为NULL

2)找错

char *GetMemory( void )

 char p[] = "hello world"; 

 return p; 

}

void Test( void )

 char *str = NULL; 

 str = GetMemory(); 

 printf( str ); 

解答:char p[] = "hello world"; 

return p;  

p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。这是许多程序员常犯的错误,其根源在于不理解变量的生存期。

3)找错

void GetMemory( char **p, int num )

{

 *p = (char *) malloc( num );

}

void Test( void )

{

  char *str = NULL;

  GetMemory( &str, 100 );

 strcpy( str, "hello" ); 

 printf( str ); 

解答:传入GetMemory的参数为字符串指针的指针,但是在GetMemory中执行申请内存及赋值语句

*p = (char *) malloc( num ); 

  后未判断内存是否申请成功,应加上:

if ( *p == NULL )

{

 ...//进行申请内存失败处理

4)这是一个出现频率非常高的错误

char* pChar = new char;

……

int a ;

pChar = &a;

……

delete pChar;

当然这是一个例子,具体的程序各有不同。

这段程序有两个问题。()pChar = &a;将导致pChar原先分配的空间无法再被获取,

就象我们的丢失了朋友的电话号码一样,无法再联系这个朋友了。这就造成了内存泄漏。

如果内存泄漏多了,可能导致系统的崩溃,因为可用的资源将越来越少,直到枯竭为止。

(第二)问题是delete pChar将导致异常发生,因为这时的pChar已经不是指向动态分配的内存了,

而是指向了a分配的栈空间,而栈空间是不能使用delete来回收的,因此将导致内存异常。

17. 求下面函数的返回值(微软)

int func(x) 

{

int countx = 0; 

    while(x) 

countx ++;  

          x = x&(x-1); 

    return countx; 

}  

假定x = 9999。 答案:8

思路:将x转化为2进制,看含有的1的个数。

18. 什么是引用声明和使用引用要注意哪些问题?

答:引用就是某个目标变量的别名”(alias),对应用的操作与对变量直接操作效果完全相同。声明一个引用的时候,切记要对其进行初始化。引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。

19. 将引用作为函数参数有哪些特点?

1传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。

2使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。

3使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。

20. 在什么时候需要使用常引用? 

如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式:const 类型标识符 &引用名=目标变量名;

1

int a ;

const int &ra=a;

ra=1; //错误

a=1; //正确 

2

string foo( );

void bar(string & s); 

那么下面的表达式将是非法的:

bar(foo( ));

bar("hello world"); 

原因在于foo( )"hello world"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。

引用型参数应该在能被定义为const的情况下,尽量定义为const 

21. 将引用作为函数返回值类型的格式、好处和需要遵守的规则?

格式:类型标识符 &函数名(形参列表及类型说明){ //函数体 }

好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生runtime error!

注意事项:

1)不能返回局部变量的引用。这条可以参照Effective C++[1]Item 31。主要原因是局部变量会在函数返回后被销毁,因此被返回的引用就成为了无所指"的引用,程序会进入未知状态。 

2)不能返回函数内部new分配的内存的引用。这条可以参照Effective C++[1]Item 31。虽然不存在局部变量的被动销毁问题,可对于这种情况(返回函数内部new分配内存的引用),又面临其它尴尬局面。例如,被函数返回的引用只是作为一个临时变量出现,而没有被赋予一个实际的变量,那么这个引用所指向的空间(由new分配)就无法释放,造成memory leak

3)可以返回类成员的引用,但最好是const。这条原则可以参照Effective C++[1]Item 30。主要原因是当对象的属性是与某种业务规则(business rule)相关联的时候,其赋值常常与某些其它属性或者对象的状态有关,因此有必要将赋值操作封装在一个业务规则当中。如果其它对象可以获得该属性的非常量引用(或指针),那么对该属性的单纯赋值就会破坏业务规则的完整性。

4)流操作符重载返回值申明为引用的作用:

流操作符<<>>,这两个操作符常常希望被连续使用,例如:cout << "hello" << endl; 因此这两个操作符的返回值应该是一个仍然支持这两个操作符的流引用。可选的其它方案包括:返回一个流对象和返回一个流对象指针。但是对于返回一个流对象,程序必须重新(拷贝)构造一个新的流对象,也就是说,连续的两个<<操作符实际上是针对不同对象的!这无法让人接受。对于返回一个流指针则不能连续使用<<操作符。因此,返回一个流对象引用是惟一选择。这个唯一选择很关键,它说明了引用的重要性以及无可替代性,也许这就是C++语言中引入引用这个概念的原因吧。 赋值操作符=。这个操作符象流操作符一样,是可以连续使用的,例如:x = j = 10;或者(x=10)=100;赋值操作符的返回值必须是一个左值,以便可以被继续赋值。因此引用成了这个操作符的惟一返回值选择。

3

i nclude <iostream.h>

int &put(int n);

int vals[10];

int error=-1;

void main()

{

put(0)=10; //put(0)函数值作为左值,等价于vals[0]=10; 

put(9)=20; //put(9)函数值作为左值,等价于vals[9]=20; 

cout<<vals[0]; 

cout<<vals[9];

int &put(int n)

{

if (n>=0 && n<=9 ) return vals[n]; 

else { cout<<"subscript error"; return error; }

5)在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引用,Effective C++[1]Item23详细的讨论了这个问题。主要原因是这四个操作符没有side effect,因此,它们必须构造一个对象作为返回值,可选的方案包括:返回一个对象、返回一个局部变量的引用,返回一个new分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,第23两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。

22. “引用与多态的关系?

引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。

4

Class A; Class B : Class A{...};  B b; A& ref = b;

23. “引用与指针的区别是什么?

指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。此外,就是上面提到的对函数传refpointer的区别。

24. 什么时候需要引用

流操作符<<>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。

25. 结构与联合有和区别?

1. 结构和联合都是由多个不同的数据类型成员组成但在任何同一时刻联合中只存放了一个被选中的成员(所有成员共用一块地址空间)而结构的所有成员都存在(不同成员的存放地址不同)。 

2. 对于联合的不同成员赋值将会对其它成员重写,  原来成员的值就不存在了而对于结构的不同成员赋值是互不影响的。

26. 下面关于联合的题目的输出?

a) #i nclude <stdio.h>

union

{

int i;

char x[2];

}a;

void main()

{

a.x[0] = 10; 

a.x[1] = 1;

printf("%d",a.i);

}

答案:266 (低位低地址,高位高地址,内存占用情况是Ox010A

b)  main() 

     { 

          union{                   /*定义一个联合*/ 

               int i; 

               struct{             /*在联合中定义一个结构*/ 

                    char first; 

                    char second; 

               }half; 

          }number; 

          number.i=0x4241;         /*联合成员赋值*/ 

          printf("%c%c\n", number.half.first, mumber.half.second); 

          number.half.first='a';   /*联合中结构成员赋值*/ 

          number.half.second='b'; 

          printf("%x\n", number.i); 

          getch(); 

     } 

答案: AB   (0x41对应'A',是低位;Ox42对应'B',是高位)   6261 (number.inumber.half共用一块地址空间)

27. 已知strcpy的函数原型:char *strcpy(char *strDest, const char *strSrc)其中strDest 是目的字符串,strSrc 是源字符串。不调用C++/C 的字符串库函数,请编写函数 strcpy

答案:

char *strcpy(char *strDest, const char *strSrc)

{

if ( strDest == NULL || strSrc == NULL)

return NULL ;

if ( strDest == strSrc)

return strDest ;

char *tempptr = strDest ;

while( (*strDest++ = *strSrc++) != ‘\0’)

;

return tempptr ;

}

28. 已知String类定义如下:

class String

{

public:

String(const char *str = NULL); // 通用构造函数

String(const String &another); // 拷贝构造函数

~ String(); // 析构函数

String & operater =(const String &rhs); // 赋值函数

private:

char *m_data; // 用于保存字符串

};

尝试写出类的成员函数实现。

答案:

String::String(const char *str)

{

   if ( str == NULL ) //strlen在参数为NULL时会抛异常才会有这步判断

     {

       m_data = new char[1] ;

       m_data[0] = '\0' ;

     }

   else

    {

       m_data = new char[strlen(str) + 1];

       strcpy(m_data,str);

String::String(const String &another)

{

    m_data = new char[strlen(another.m_data) + 1];

    strcpy(m_data,other.m_data);

}

String& String::operator =(const String &rhs)

{

    if ( this == &rhs)

        return *this ;

    delete []m_data; //删除原来的数据,新开一块内存

    m_data = new char[strlen(rhs.m_data) + 1];

    strcpy(m_data,rhs.m_data);

    return *this ;

}

String::~String()

{

    delete []m_data ;

}

29. h头文件中的ifndef/define/endif 的作用?

答:防止该头文件被重复引用。

30. #i nclude<file.h> 与 #i nclude "file.h"的区别?

答:前者是从Standard Library的路径寻找和引用file.h,而后者是从当前工作路径搜寻并引用file.h

31. 在C++ 程序中调用被编译器编译后的函数,为什么要加extern “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

include "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"

{

include "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 */

include "cExample.h"

int add( int x, int y )

{

return x + y;

}

32. 结构与联合有和区别?

1. 结构和联合都是由多个不同的数据类型成员组成, 但在任何同一时刻, 联合中只存放了一个被选中的成员(所有成员共用一块地址空间), 而结构的所有成员都存在(不同成员的存放地址不同)。 

 2. 对于联合的不同成员赋值, 将会对其它成员重写,  原来成员的值就不存在了, 而对于结构的不同成员赋值是互不影响的。

   6261 (number.i和number.half共用一块地址空间)

33. 已知strcpy的函数原型:char *strcpy(char *strDest, const char *strSrc)其中strDest 是目的字符串,strSrc 是源字符串。不调用C++/C 的字符串库函数,请编写函数 strcpy。

答案:

char *strcpy(char *strDest, const char *strSrc)

{

if ( strDest == NULL || strSrc == NULL)

return NULL ;

if ( strDest == strSrc)

return strDest ;

char *tempptr = strDest ;

while( (*strDest++ = *strSrc++) != ‘\0’)

;

return tempptr ;

34. 关联、聚合(Aggregation)以及组合(Composition)的区别?

涉及到UML中的一些概念:关联是表示两个类的一般性联系,比如学生老师就是一种关联关系;聚合表示has-a的关系,是一种相对松散的关系,聚合类不需要对被聚合类负责,如下图所示,用空的菱形表示聚合关系:

从实现的角度讲,聚合可以表示为:

class A {...}  class B { A* a; .....}

而组合表示contains-a的关系,关联性强于聚合:组合类与被组合类有相同的生命周期,组合类要对被组合类负责,采用实心的菱形表示组合关系:

                            

实现的形式是:

class A{...} class B{ A a; ...}

35. 面向对象的三个基本特征,并简单叙述之?

1. 封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected,public)

2. 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。

3. 多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。

36. 重载(overload)和重写(overried,有的书也叫做覆盖)的区别?

常考的题目。从定义上来说:

重载:是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。

重写:是指子类重新定义复类虚函数的方法。从实现原理上来说:

重载:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!

重写:和多态真正相关。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚绑定)。

37. 多态的作用?

主要是两个:1. 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;2. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。

38. AdoAdo.net的相同与不同?

除了“能够让应用程序处理存储于DBMS 中的数据“这一基本相似点外,两者没有太多共同之处。但是Ado使用OLE DB 接口并基于微软的COM 技术,而ADO.NET 拥有自己的ADO.NET 接口并且基于微软的.NET 体系架构。众所周知.NET 体系不同于COM 体系,ADO.NET 接口也就完全不同于ADOOLE DB 接口,这也就是说ADO.NET ADO是两种数据访问方式。ADO.net 提供对XML 的支持。

39. New delete malloc free 的联系与区别?

答案:都是在堆(heap)上进行动态的内存操作。用malloc函数需要指定内存分配的字节数并且不能初始化对象,new 会自动调用对象的构造函数。delete 会调用对象的destructor,而free 不会调用对象的destructor.

40. #define DOUBLE(x) x+x i = 5*DOUBLE(5); 是多少?

答案:30

41. 有哪几种情况只能用intialization list 而不能用assignment? 

答案:当类中含有constreference 成员变量;基类的构造函数都需要初始化表。

42.  C++是不是类型安全的?

答案:不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)C#是类型安全的。

43. main 函数执行以前,还会执行什么代码?

答案:全局对象的构造函数会在main 函数之前执行。

44. 描述内存分配方式以及它们的区别?

1) 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。

2) 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。

3) 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc new 申请任意多少的内存,程序员自己负责在何时用free delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。

45. struct 和 class 的区别

答案:struct 的成员默认是公有的,而类的成员默认是私有的。struct 和 class 在其他方面是功能相当的。 

从感情上讲,大多数的开发者感到类和结构有很大的差别。感觉上结构仅仅象一堆缺乏封装和功能的开放的内存位,而类就象活的并且可靠的社会成员,它有智能服务,有牢固的封装屏障和一个良好定义的接口。既然大多数人都这么认为,那么只有在你的类有很少的方法并且有公有数据(这种事情在良好设计的系统中是存在的!)时,你也许应该使用 struct 关键字,否则,你应该使用 class 关键字。

46. 当一个类中没有生命任何成员变量与成员函数,这时sizeof(A)的值是多少,如果不是零,请解释一下编译器为什么没有让它为零。(Autodesk

答案:肯定不是零。举个反例,如果是零的话,声明一个class A[10]对象数组,而每一个对象占用的空间是零,这时就没办法区分A[0],A[1]…了。

47. 在8086 汇编下,逻辑地址和物理地址是怎样转换的?(Intel

答案:通用寄存器给出的地址,是段内偏移地址,相应段寄存器地址*10H+通用寄存器内地址,就得到了真正要访问的地址。

48. 比较C++中的4种类型转换方式? 

重点是static_cast, dynamic_castreinterpret_cast的区别和应用。

49. 分别写出BOOL,int,float,指针类型的变量与“零”的比较语句。

答案:

BOOL :    if ( !a ) or if(a)

int :     if ( a == 0)

float :   const EXPRESSION EXP = 0.000001

          if ( a < EXP && a >-EXP)

pointer : if ( a != NULL) or if(a == NULL) 

50. 请说出const#define 相比,有何优点?

答案:

1const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。

 2) 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。

51. 简述数组与指针的区别?

数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块。

(1)修改内容上的差别

char a[] = hello;

a[0] = X;

char *p = world; // 注意指向常量字符串

p[0] = X; // 编译器不能发现该错误,运行时错误

(2) 用运算符sizeof 可以计算出数组的容量(字节数)。sizeof(p),p 为指针得到的是一个指针变量的字节数,而不是所指的内存容量。C++/C 语言没有办法知道指针所指的内存容量,除非在申请内存时记住它。

注意当数组作为函数的参数进行传递时,该数组自动退化为同类型的指针。

char a[] = "hello world";

char *p = a;

cout<< sizeof(a) << endl; // 12 字节

cout<< sizeof(p) << endl; // 4 字节

计算数组和指针的内存容量

void Func(char a[100])

{

cout<< sizeof(a) << endl; // 4 字节而不是100 字节

}

52. 类成员函数的重载、覆盖和隐藏区别?

答案:

a.成员函数被重载的特征:

1)相同的范围(在同一个类中);

2)函数名字相同;

3)参数不同;

4virtual 关键字可有可无。

b.覆盖是指派生类函数覆盖基类函数,特征是:

1)不同的范围(分别位于派生类与基类);

2)函数名字相同;

3)参数相同;

4)基类函数必须有virtual 关键字。

c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。

2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)

53. There are two int variables: a and b, dont use if? :switchor other judgement statements, find out the biggest one of the two numbers.

答案:( ( a + b ) + abs( a - b ) ) / 2

54. 如何打印出当前源文件的文件名以及源文件的当前行号?

答案:

cout << __FILE__ ;

cout<<__LINE__ ;

__FILE____LINE__是系统预定义宏,这种宏并不是在某个文件中定义的,而是由编译器定义的。

55. main 主函数执行完毕后,是否可能会再执行一段代码,给出说明?

答案:可以,可以用_onexit 注册一个函数,它会在main 之后执行int fn1(void), fn2(void), fn3(void), fn4 (void);

void main( void )

{

String str("zhanglin");

_onexit( fn1 );

_onexit( fn2 );

_onexit( fn3 );

_onexit( fn4 );

printf( "This is executed first.\n" );

}

int fn1()

{

printf( "next.\n" );

return 0;

}

int fn2()

{

printf( "executed " );

return 0;

}

int fn3()

{

printf( "is " );

return 0;

int fn4()

{

printf( "This " );

return 0;

}

The _onexit function is passed the address of a function (func) to be called when the program terminates normally. Successive calls to _onexit create a register of functions that are executed in LIFO (last-in-first-out) order. The functions passed to _onexit cannot take parameters.

56. 如何判断一段程序是由编译程序还是由C++编译程序编译的?

答案:

#ifdef __cplusplus

cout<<"c++";

#else

cout<<"c";

#endif

57. 文件中有一组整数,要求排序后输出到另一个文件中

答案:

include<iostream>

include<fstream>

using namespace std;

void Order(vector<int>& data) //bubble sort

{

int count = data.size() ;

int tag = false ; // 设置是否需要继续冒泡的标志位

for ( int i = 0 ; i < count ; i++)

{

for ( int j = 0 ; j < count - i - 1 ; j++)

{

if ( data[j] > data[j+1])

{

tag = true ;

int temp = data[j] ;

data[j] = data[j+1] ;

data[j+1] = temp ;

}

}

if ( !tag )

break ;

}

}

void main( void )

{

vector<int>data;

ifstream in("c:\\data.txt");

if ( !in)

{

cout<<"file error!";

exit(1);

}

int temp;

while (!in.eof())

{

in>>temp;

data.push_back(temp);

}

in.close(); //关闭输入文件流

Order(data);

ofstream out("c:\\result.txt");

if ( !out)

{

cout<<"file error!";

exit(1);

}

for ( i = 0 ; i < data.size() ; i++)

out<<data[i]<<" ";

out.close(); //关闭输出文件流

}

58. 链表题:一个链表的结点结构

struct Node

{

int data ;

Node *next ;

};

typedef struct Node Node ;

(1)已知链表的头结点head,写一个函数把这个链表逆序 ( Intel)

Node * ReverseList(Node *head) //链表逆序

{

if ( head == NULL || head->next == NULL )

return head;

Node *p1 = head ;

Node *p2 = p1->next ;

Node *p3 = p2->next ;

p1->next = NULL ;

while ( p3 != NULL )

{

p2->next = p1 ;

p1 = p2 ;

p2 = p3 ;

p3 = p3->next ;

}

p2->next = p1 ;

head = p2 ;

return head ;

}

(2)已知两个链表head1 head2 各自有序,请把它们合并成一个链表依然有序。(保留所有结点,即便大小相同)

Node * Merge(Node *head1 , Node *head2)

{

if ( head1 == NULL)

return head2 ;

if ( head2 == NULL)

return head1 ;

Node *head = NULL ;

Node *p1 = NULL;

Node *p2 = NULL;

if ( head1->data < head2->data )

{

head = head1 ;

p1 = head1->next;

p2 = head2 ;

}

else

{

head = head2 ;

p2 = head2->next ;

p1 = head1 ;

}

Node *pcurrent = head ;

while ( p1 != NULL && p2 != NULL)

{

if ( p1->data <= p2->data )

{

pcurrent->next = p1 ;

pcurrent = p1 ;

p1 = p1->next ;

}

else

{

pcurrent->next = p2 ;

pcurrent = p2 ;

p2 = p2->next ;

}

}

if ( p1 != NULL )

pcurrent->next = p1 ;

if ( p2 != NULL )

pcurrent->next = p2 ;

return head ;

}

(3)已知两个链表head1 head2 各自有序,请把它们合并成一个链表依然有序,这次要求用递归方法进行。 (Autodesk)

答案:

Node * MergeRecursive(Node *head1 , Node *head2)

{

if ( head1 == NULL )

return head2 ;

if ( head2 == NULL)

return head1 ;

Node *head = NULL ;

if ( head1->data < head2->data )

{

head = head1 ;

head->next = MergeRecursive(head1->next,head2);

}

else

{

head = head2 ;

head->next = MergeRecursive(head1,head2->next);

}

return head ;

}

59. 分析一下这段程序的输出 (Autodesk)

#include <iostream>

#include <string>

using namespace std;

class B

{

public:

B()

{

cout<<"default constructor"<<endl;

}

~B()

{

cout<<"destructed"<<endl;

}

B(int i):data(i)

{

cout<<"constructed by parameter " << data <<endl;

}

private:

int data;

};

B Play(B b) 

{

return b ;

}

///  *  

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

{                            

B t1 = Play(5);

B t2 = Play(t1);

return 0;

}

//*  /

/*

//(2)                  

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

{                      

B t1 = Play(5); 

B t2 = Play(10);

return 0;

}

 */

60. 写一个函数找出一个整数数组中,第二大的数 (microsoft

答案:

const int MINNUMBER = -32767 ;

int find_sec_max( int data[] , int count)

{

int maxnumber = data[0] ;

int sec_max = MINNUMBER ;

for ( int i = 1 ; i < count ; i++)

{

if ( data[i] > maxnumber )

{

sec_max = maxnumber ;

maxnumber = data[i] ;

}

else

{

if ( data[i] > sec_max )+

}

}

return sec_max ;

}

61. 写一个在一个字符串(n)中寻找一个子串(m)第一个位置的函数。

KMP算法效率最好,时间复杂度是O(n+m)

62. 多重继承的内存分配问题:

   比如有class A : public class B, public class C {}

   那么A的内存结构大致是怎么样的?

这个是compiler-dependent不同的实现其细节可能不同。

如果不考虑有虚函数、虚继承的话就相当简单;否则的话,相当复杂。

可以参考《深入探索C++对象模型》,或者

63. 如何判断一个单链表是有环的?(注意不能用标志位,最多只能用两个额外指针)

  struct node { char val; node* next;}

   bool check(const node* head) {} //return false : 无环;true: 有环

一种On)的办法就是(搞两个指针,一个每次递增一步,一个每次递增两步,如果有环的话两者必然重合,反之亦然):

bool check(const node* head)

{

    if(head==NULL)  return false;

    node *low=head, *fast=head->next;

    while(fast!=NULL && fast->next!=NULL)

    {

        low=low->next;

        fast=fast->next->next;

        if(low==fast) return true;

    }

    return false;

}

64. 是不是一个父类写了一个virtual 函数,如果子类覆盖它的函数不加virtual ,也能实现多态?

virtual修饰符会被隐形继承的。

private 也被继承,只事派生类没有访问权限而已

virtual可加可不加

子类的空间里有父类的所有变量(static除外)

同一个函数只存在一个实体(inline除外)

子类覆盖它的函数不加virtual ,也能实现多态。

在子类的空间里,有父类的私有变量。私有变量不能直接访问。

65. 输入一个字符串,将其逆序后输出。(使用C++,不建议用伪码)

#include <iostream>

using namespace std;

void main()

{

  char a[50];memset(a,0,sizeof(a));

  int i=0,j;

char t;

  cin.getline(a,50,'\n');

  for(i=0,j=strlen(a)-1;i<strlen(a)/2;i++,j--)

  {

   t=a[i];

      a[i]=a[j];

   a[j]=t;

  }

  cout<<a<<endl;  

}

//第二种

string str;

cin>>str;

str.replace;

cout<<str;

66. 请简单描述Windows内存管理的方法。

内存管理是操作系统中的重要部分,两三句话恐怕谁也说不清楚吧~~

我先说个大概,希望能够抛砖引玉吧

当程序运行时需要从内存中读出这段程序的代码。代码的位置必须在物理内存中才能被运行,由于现在的操作系统中有非常多的程序运行着,内存中不能够完全放下,所以引出了虚拟内存的概念。把哪些不常用的程序片断就放入虚拟内存,当需要用到它的时候在load入主存(物理内存)中。这个就是内存管理所要做的事。内存管理还有另外一件事需要做:计算程序片段在主存中的物理位置,以便CPU调度。

内存管理有块式管理,页式管理,段式和段页式管理。现在常用段页式管理

块式管理:把主存分为一大块、一大块的,当所需的程序片断不在主存时就分配一块主存空间,把程序片断load入主存,就算所需的程序片度只有几个字节也只能把这一块分配给它。这样会造成很大的浪费,平均浪费了50%的内存空间,但时易于管理。

页式管理:把主存分为一页一页的,每一页的空间要比一块一块的空间小很多,显然这种方法的空间利用率要比块式管理高很多。

段式管理:把主存分为一段一段的,每一段的空间又要比一页一页的空间小很多,这种方法在空间利用率上又比页式管理高很多,但是也有另外一个缺点。一个程序片断可能会被分为几十段,这样很多时间就会被浪费在计算每一段的物理地址上(计算机最耗时间的大家都知道是I/O吧)。

段页式管理:结合了段式管理和页式管理的优点。把主存分为若干页,每一页又分为若干段。好处就很明显,不用我多说了吧。

各种内存管理都有它自己的方法来计算出程序片断在主存中的物理地址,其实都很相似。

这只是一个大概而已,不足以说明内存管理的皮毛。无论哪一本操作系统书上都有详细的讲解

67. #i nclude "stdafx.h"

#define SQR(X) X*X

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

{

  int a = 10;

  int k = 2;

int m = 1;

a /= SQR(k+m)/SQR(k+m); 

  printf("%d\n",a); 

  return 0;

}

这道题目的结果是什么啊?

define 只是定义而已,在编择时只是简单代换X*X而已,并不经过算术法则的

a /= (k+m)*(k+m)/(k+m)*(k+m);

=>a /= (k+m)*1*(k+m);

=>a = a/9;

=>a = 1;

68. const 符号常量;

(1)const char *p

(2)char const *p

(3)char * const p

说明上面三种描述的区别;

如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;

如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。

(1)const char *p

一个指向char类型的const对象指针,p不是常量,我们可以修改p的值,使其指向不同的char,但是不能改变它指向非char对象,如:

const char *p;

char c1='a';

char c2='b';

p=&c1;//ok

p=&c2;//ok

*p=c1;//error

(2) char const *p

(3) char * const p

这两个好象是一样的,此时*p可以修改,而p不能修改。

(4) const char * const p

这种是地址及指向对象都不能修改。

69. 下面是C语言中两种if语句判断方式。请问哪种写法更好?为什么?

 int n;

 if (n == 10) // 第一种判断方式

 if (10 == n) // 第二种判断方式

如果少了个=,编译时就会报错,减少了出错的可能行,可以检测出是否少了=

70. 下面的代码有什么问题?

void DoSomeThing(...)

{

 char* p;

 ...

 p = malloc(1024);  // 分配1K的空间

 if (NULL == p)

  return;

...

 p = realloc(p, 2048); // 空间不够,重新分配到2K

 if (NULL == p)

  return;

 ...

}

A:

p = malloc(1024);     应该写成: p = (char *) malloc(1024);    没有释放p的空间,造成内存泄漏。

71. 下面的代码有什么问题?并请给出正确的写法。

void DoSomeThing(char* p)

{

 char str[16];

 int n;

 assert(NULL != p);

 sscanf(p, "%s%d", str, n);

 if (0 == strcmp(str, "something"))

 {

  ...

 }

}

A:

sscanf(p, "%s%d", str, n);   这句该写成: sscanf(p, "%s%d", str, &n);

72. 下面代码有什么错误?

Void test1() 

 char string[10]; 

 char *str1="0123456789";

 strcpy(string, str1); 

结果:数组越界

73. 下面代码有什么问题?

Void test2() 

  char string[10], str1[10]; 

  for(i=0; i<10;i++) 

  { 

     str1[i] ='a'; 

  strcpy(string, str1); 

结果:数组越界

74. 下面代码有什么问题?

Void test3(char* str1) 

  char string[10]; 

  if(strlen(str1)<=10) 

  { 

    strcpy(string, str1); 

  } 

结果:数组越界

==strcpy拷贝的结束标志是查找字符串中的\0 因此如果字符串中没有遇到\0的话 会一直复制,直到遇到\0,上面的123都因此产生越界的情况

建议使用 strncpy 和 memcpy

75. 下面代码有什么问题?

#define MAX_SRM 256 

DSN get_SRM_no() 

  static int SRM_no; //是不是这里没赋初值?

  int I; 

  for(I=0;I<MAX_SRM;I++,SRM_no++) 

  { 

    SRM_no %= MAX_SRM; 

    if(MY_SRM.state==IDLE) 

    { 

      break; 

    } 

  } 

  if(I>=MAX_SRM) 

    return (NULL_SRM); 

  else 

    return SRM_no; 

系统会初始化static int变量为0,但该值会一直保存,所谓的不可重入...

76. 写出运行结果:

{// test1

    char str[] = "world"; cout << sizeof(str) << ": ";

    char *p    = str;     cout << sizeof(p) << ": ";

    char i     = 10;      cout << sizeof(i) << ": ";

    void *p    = malloc(10);  cout << sizeof(p) << endl;

}

结果:6414

77. 写出运行结果:

{// test2

    union V {

 struct X {

  unsigned char s1:2;

  unsigned char s2:3;

  unsigned char s3:3;

 } x;

unsigned char c;

    } v;

v.c = 100;

printf("%d", v.x.s3); 

}

结果:3

78. 用C++写个程序,如何判断一个操作系统是16位还是32位的?不能用sizeof()函数

A1:

16位的系统下,

int i = 65536;

cout << i; // 输出0

int i = 65535;

cout << i; // 输出-1

32位的系统下,

int i = 65536;

cout << i; // 输出65536

int i = 65535;

cout << i; // 输出65535

A2:

int a = ~0;

if( a>65536 ) 

{

    cout<<"32 bit"<<endl;

}

else

{

    cout<<"16 bit"<<endl;

}

79. CC++有什么不同?

从机制上:c是面向过程的(但c也可以编写面向对象的程序);c++是面向对象的,提供了类。但是,

c++编写面向对象的程序比c容易

从适用的方向:c适合要求代码体积小的,效率高的场合,如嵌入式;c++适合更上层的,复杂的;  llinux核心大部分是c写的,因为它是系统软件,效率要求极高。

从名称上也可以看出,c++c多了+,说明c++c的超集;那为什么不叫c+而叫c++呢,是因为c++

c来说扩充的东西太多了,所以就在c后面放上两个+;于是就成了c++

C语言是结构化编程语言,C++是面向对象编程语言。

C++侧重于对象而不是过程,侧重于类的设计而不是逻辑的设计。

80. 在不用第三方参数的情况下,交换两个参数的值

方法一;

#i nclude <stdio.h>

void main()

{

        int i=60;

        int j=50;

        i=i+j;

        j=i-j;

        i=i-j;

        printf("i=%d\n",i);

        printf("j=%d\n",j);

}

方法二:

i^=j;

j^=i;

i^=j;

方法三:

// 用加减实现,而且不会溢出

a = a+b-(b=a)

81. 有关位域的面试题(为什么输出的是一个奇怪的字符)

a.t = 'b';效果相当于 a.t= 'b' & 0xf;

'b' --> 01100010

'b' & 0xf -->>00000010

所以输出Ascii码为2的特殊字符

char t:4;就是4bit的字符变量,同样

unsigned short i:8;就是8bit的无符号短整形变量

82. int i=10, j=10, k=3; k*=i+j; k最后的值是?

60

83. 进程间通信的方式有?

进程间通信的方式有 共享内存, 管道 ,Socket ,消息队列 , DDE

84. 位结构

struct A 

char t:4; 

char k:4; 

unsigned short i:8; 

unsigned long m; 

sizeof(A)=?(不考虑边界对齐)

结果;7

struct CELL             // Declare CELL bit field

{

   unsigned character  : 8;  // 00000000 ????????

   unsigned foreground : 3;  // 00000??? 00000000

   unsigned intensity  : 1;  // 0000?000 00000000

   unsigned background : 3;  // 0???0000 00000000

   unsigned blink      : 1;  // ?0000000 00000000

} screen[25][80];       // Array of bit fields

    位结构是一种特殊的结构在需按位访问一个字节或字的多个位时位结构 

比按位运算符更加方便。

    位结构定义的一般形式为

     struct位结构名

          数据类型 变量名整型常数

          数据类型 变量名整型常数

     } 位结构变量

    其中数据类型必须是int(unsignedsigned)。 整型常数必须是非负的整 

范围是0~15, 表示二进制位的个数即表示有多少位。 

变量名是选择项可以不命名这样规定是为了排列需要。 

    例如下面定义了一个位结构。 

     struct{ 

          unsigned incon: 8;  /*incon占用低字节的0~78*/ 

          unsigned txcolor: 4;/*txcolor占用高字节的0~3位共4*/ 

          unsigned bgcolor: 3;/*bgcolor占用高字节的4~6位共3*/ 

          unsigned blink: 1;  /*blink占用高字节的第7*/ 

     }ch; 

位结构成员的访问与结构成员的访问相同。

    例如访问上例位结构中的bgcolor成员可写成

      ch.bgcolor 

注意

(1) 位结构中的成员可以定义为unsigned, 也可定义为signed,  但当成员长度为1会被认为是unsigned类型。因为单个位不可能具有符号。 

(2) 位结构中的成员不能使用数组和指针但位结构变量可以是数组和指针,如果是指针其成员访问方式同结构指针。 

(3) 位结构总长度(位数), 是各个位成员定义的位数之和,  可以超过两个字节。 

(4) 位结构成员可以与其它结构成员一起使用。

例如

     struct info{ 

          char name[8]; 

          int age; 

          struct addr address; 

          float pay; 

          unsigned state: 1; 

          unsigned pay: 1; 

          }workers;

    上例的结构定义了关于一个工人的信息。其中有两个位结构成员每个位结 

构成员只有一位因此只占一个字节但保存了两个信息该字节中第一位表示工 

人的状态第二位表示工资是否已发放。由此可见使用位结构可以节省存贮空间。

85. 下面的函数实现在一个固定的数上加上一个数,有什么错误,改正 

int add_n(int n) 

  static int i=100; 

  i+=n; 

  return i; 

:

因为static使得i的值会保留上次的值。

去掉static就可了

86. 下面的代码有什么问题?

A1:

class A 

public: 

  A() { p=this; } 

  ~A() { if(p!=NULL) { delete p; p=NULL; } } 

 A* p; 

};

会引起无限递归

A2:

union a {

 int a_int1;

 double a_double;

 int a_int2;

};

typedef struct

{

 a a1;

 char y;

} b;

class c

{

 double c_double;

 b b1;

 a a2;

};

输出cout<<sizeof(c)<<endl;的结果?

答: 

VC6环境下得出的结果是32

另:

我(sun)在VC6.0+win2k下做过试验:

short - 2

int-4

float-4

double-8

指针-4

sizeof(union)以结构里面size最大的为union的size

87. 解析C语言中的sizeof

一、 sizeof的概念

sizeof是C语言的一种单目操作符,如C语言的其他操作符++、--等。它并不是函数。sizeof操作符以字节形式给出了其操作数的存储大小。操作数可以是一个表达式或括在括号内的类型名。操作数的存储大小由操作数的类型决定。 

二、 sizeof的使用方法

1) 用于数据类型  

sizeof使用形式:sizeoftype)  

数据类型必须用括号括住。如sizeofint)。  

2) 用于变量  

sizeof使用形式:sizeofvar_name)或sizeofvar_name  

变量名可以不用括号括住。如sizeof(var_name)sizeofvar_name等都是正确形式。带括号的用法更普遍,大多数程序员采用这种形式。  

  注意:sizeof操作符不能用于函数类型,不完全类型或位字段。不完全类型指具有未知存储大小的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。  

sizeof(max)若此时变量max定义为intmax(),sizeof(char_v) 若此时char_v定义为charchar_v[MAX]MAX未知,sizeof(void)都不是正确形式。 

 

三、 sizeof的结果  

  sizeof操作符的结果类型是size_t,它在头文件中typedefunsignedint类型。该类型保证能容纳实现所建立的最大对象的字节大小。  

1) 若操作数具有类型charunsignedcharsignedchar,其结果等于1。  
  ANSIC正式规定字符类型为1字节。  

2) intunsignedint 、shortintunsignedshort 、longint 、unsignedlong 、 floatdoublelongdouble类型的sizeof 在ANSIC中没有具体规定,大小依赖于实现,一般可能分别为2222、 444810。  

3) 当操作数是指针时,sizeof依赖于编译器。例如MicrosoftC/C++7.0中,near类指针字节数为2farhuge类指针字节数为4。一般Unix的指针字节数为4。  

4) 当操作数具有数组类型时,其结果是数组的总字节数。  

5) 联合类型操作数的sizeof是其最大字节成员的字节数。结构类型操作数的sizeof是这种类型对象的总字节数,包括任何垫补在内。 
让我们看如下结构:  
struct{charb;doublex;}a;  
在某些机器上sizeofa=12,而一般sizeofchar+sizeofdouble=9。  
这是因为编译器在考虑对齐问题时,在结构中插入空位以控制各成员对象的地址对齐。如double类型的结构成员x要放在被4整除的地址.

6) 如果操作数是函数中的数组形参或函数类型的形参,sizeof给出其指针的大小。  

四、 sizeof与其他操作符的关系  

sizeof的优先级为2级,比/%3级运算符优先级高。它可以与其他操作符一起组成表达式。如i*sizeofint);其中iint类型变量。  

五、 sizeof的主要用途  

1) sizeof操作符的一个主要用途是与存储分配和I/O系统那样的例程进行通信。例如:  
void*mallocsize_tsize,  
size_tfread(void*ptr,size_tsize,size_tnmemb,FILE*stream)。  

2) sizeof的另一个的主要用途是计算数组中元素的个数。例如:  
void*memsetvoid*s,intc,sizeof(s))。  

六、 建议  

  由于操作数的字节数在实现时可能出现变化,建议在涉及到操作数字节大小时用sizeof来代替常量计算。本文主要包括二个部分,第一部分重点介绍在VC中,怎么样采用sizeof来求结构的大小,以及容易出现的问题,并给出解决问题的方法,第二部分总结出VCsizeof的主要用法。 

88. sizeof应用在结构上的情况 

请看下面的结构: 

struct MyStruct 

double dda1; 

char dda; 

int type 

}; 

对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样求: 

sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13 

但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)16。你知道为什么在VC中会得出这样一个结果吗? 

其实,这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了"对齐"处理。在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式(vc6.0,32位系统)。 

类型 

对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量) 

Char 

偏移量必须为sizeof(char)1的倍数 

int 

偏移量必须为sizeof(int)4的倍数 

float 

偏移量必须为sizeof(float)4的倍数 

double 

偏移量必须为sizeof(double)8的倍数 

Short 

偏移量必须为sizeof(short)2的倍数 

各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。同时VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节。 

下面用前面的例子来说明VC到底怎么样来存放结构的。 

struct MyStruct 

double dda1; 

char dda; 

int type 

}; 

为上面的结构分配空间的时候,VC根据成员变量出现的顺序和对齐方式,先为第一个成员dda1分配空间,其起始地址跟结构的起始地址相同(刚好偏移量0刚好为sizeof(double)的倍数),该成员变量占用sizeof(double)=8个字节;接下来为第二个成员dda分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,所以把dda存放在偏移量为8的地方满足对齐方式,该成员变量占用 sizeof(char)=1个字节;接下来为第三个成员type分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9,不是sizeof (int)=4的倍数,为了满足对齐方式对偏移量的约束问题,VC自动填充3个字节(这三个字节没有放什么东西),这时下一个可以分配的地址对于结构的起始地址的偏移量为12,刚好是sizeof(int)=4的倍数,所以把type存放在偏移量为12的地方,该成员变量占用sizeof(int)=4个字节;这时整个结构的成员变量已经都分配了空间,总的占用的空间大小为:8+1+3+4=16,刚好为结构的字节边界数(即结构中占用最大空间的类型所占用的字节数sizeof(double)=8)的倍数,所以没有空缺的字节需要填充。所以整个结构的大小为:sizeof(MyStruct)=8+1+ 3+4=16,其中有3个字节是VC自动填充的,没有放任何有意义的东西。 

下面再举个例子,交换一下上面的MyStruct的成员变量的位置,使它变成下面的情况: 

struct MyStruct 

char dda; 

double dda1;   

int type 

}; 

这个结构占用的空间为多大呢?在VC6.0环境下,可以得到sizeof(MyStruc)24。结合上面提到的分配空间的一些原则,分析下VC怎么样为上面的结构分配空间的。(简单说明) 

struct MyStruct 

  char dda;//偏移量为0,满足对齐方式,dda占用1个字节; 

double dda1;//下一个可用的地址的偏移量为1,不是sizeof(double)=8 

             //的倍数,需要补足7个字节才能使偏移量变为8(满足对齐             //方式),因此VC自动填充7个字节,dda1存放在偏移量为

             //的地址上,它占用8个字节。 

int type//下一个可用的地址的偏移量为16,是sizeof(int)=4的倍 

           //数,满足int的对齐方式,所以不需要VC自动填充,type存 

           //放在偏移量为16的地址上,它占用4个字节。 

}//所有成员变量都分配了空间,空间总的大小为1+7+8+4=20,不是结构 

   //的节边界数(即结构中占用最大空间的类型所占用的字节数sizeof 

   //(double)=8)的倍数,所以需要填充4个字节,以满足结构的大小为 

   //sizeof(double)=8的倍数。 

所以该结构总的大小为:sizeof(MyStruc)1+7+8+4+4=24。其中总的有7+4=11个字节是VC自动填充的,没有放任何有意义的东西。 

VC对结构的存储的特殊处理确实提高CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。 

VC 中提供了#pragma pack(n)来设定变量以n字节对齐方式。n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果n小于该变量的类型所占用的字节数,那么偏移量为n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;  

否则必须为n的倍数。下面举例说明其用法。 

#pragma pack(push) //保存对齐状态 

#pragma pack(4)//设定为4字节对齐 

struct test 

  char m1; 

  double m4; 

  int  m3; 

};

#pragma pack(pop)//恢复对齐状态 

以上结构的大小为16,下面分析其存储情况,首先为m1分配空间,其偏移量为0,满足我们自己设定的对齐方式(4字节对齐),m1占用1个字节。接着开始为 m4分配空间,这时其偏移量为1,需要补足3个字节,这样使偏移量满足为n=4的倍数(因为sizeof(double)大于n,m4占用8个字节。接着为m3分配空间,这时其偏移量为12,满足为4的倍数,m3占用4个字节。这时已经为所有成员变量分配了空间,共分配了16个字节,满足为n的倍数。如果把上面的#pragma pack(4)改为#pragma pack(16),那么我们可以得到结构的大小为24。(请读者自己分析) 

89. sizeof用法总结 

VC中,sizeof有着许多的用法,而且很容易引起一些错误。下面根据sizeof后面的参数对sizeof的用法做个总结。 

A.  参数为数据类型或者为一般变量。例如sizeof(int),sizeof(long)等等。这种情况要注意的是不同系统系统或者不同编译器得到的结果可能是不同的。例如int类型在16位系统中占2个字节,在32位系统中占4个字节。 

B.  参数为数组或指针。下面举例说明

int a[50];  //sizeof(a)=4*50=200; 求数组所占的空间大小 

int *a=new int[50];// sizeof(a)=4; a为一个指针,sizeof(a)是求指针 

                   //的大小,32位系统中,当然是占4个字节。 

C.  参数为结构或类。Sizeof应用在类和结构的处理情况是相同的。但有两点需要注意,第一、结构或者类中的静态成员不对结构或者类的大小产生影响,因为静态变量的存储位置与结构或者类的实例地址无关。 

第二、没有成员变量的结构或类的大小为1,因为必须保证结构或类的每一 个实例在内存中都有唯一的地址。 

下面举例说明, 

Class Test{int a;static double c};//sizeof(Test)=4. 

Test *s;//sizeof(s)=4,s为一个指针。 

Class test1{ };//sizeof(test1)=1; 

D.  参数为其他。下面举例说明。 

int func(char s[5]); 

   { 

     cout<<sizeof(s);//这里将输出4,本来s为一个数组,但由于做为函 

 //数的参数在传递的时候系统处理为一个指针,所 

 //sizeof(s)实际上为求指针的大小。 

  return 1; 

sizeof(func("1234"))=4//因为func的返回类型为int,所以相当于 //sizeof(int). 

以上为sizeof的基本用法,在实际的使用中要注意分析VC的分配变量的分配策略,这样的话可以避免一些错误。 

90. i最后等于多少?

int i = 1;

int j = i++;

if((i>j++) && (i++ == j)) i+=j;

i = 5

91. 求出结果

unsigned short array[]={1,2,3,4,5,6,7};

int i = 3;

*(array + i) = ?

4

92. 求出结果

class A

{

  virtual void func1()

  void func2();

}

Class B: class A

{

  void func1(){cout << "fun1 in class B" << endl;}

  virtual void func2(){cout << "fun2 in class B" << endl;}

A, A中的func1B中的func2都是虚函数.

B, A中的func1B中的func2都不是虚函数.

C, A中的func2是虚函数.B中的func1不是虚函数.

D, A中的func2不是虚函数,B中的func1是虚函数.

A

93. 数据库:抽出部门,平均工资,要求按部门的字符串顺序排序,不能含有"human resource"部门,

employee结构如下:employee_id, employee_name, depart_id,depart_name,wage

:

select depart_name, avg(wage)

from employee

where depart_name <> 'human resource'

group by depart_name

order by depart_name

94. 给定如下SQL数据库:Test(num INT(4)) 请用一条SQL语句返回num的最小值,但不许使用统计功能,如MINMAX

:

select top 1 num

from Test

order by num desc

95. 输出下面程序结果。 

#i nclude <iostream.h> 

class A 

public:

 virtual void print(void) 

 { 

    cout<<"A::print()"<<endl; 

 } 

};

class B:public A 

public:

 virtual void print(void) 

 { 

   cout<<"B::print()"<<endl;

 }; 

}; 

class C:public B

{

public:

 virtual void print(void)

 {

  cout<<"C::print()"<<endl;

 }

};

void print(A a) 

   a.print(); 

void main(void) 

   A a, *pa,*pb,*pc; 

   B b; 

   C c; 

    pa=&a; 

   pb=&b; 

   pc=&c; 

    a.print(); 

   b.print(); 

   c.print(); 

    pa->print(); 

   pb->print(); 

   pc->print(); 

   print(a); 

   print(b); 

   print(c); 

}

A:

A::print()

B::print()

C::print()

A::print()

B::print()

C::print()

A::print()

A::print()

A::print()

96. 试编写函数判断计算机的字节存储顺序是开序(little endian)还是降序(bigendian)

答:

bool IsBigendian()

{

 unsigned short usData = 0x1122;

 unsigned char  *pucData = (unsigned char*)&usData;

 return (*pucData == 0x22);

}

97. 简述Critical SectionMutex的不同点

:

对几种同步对象的总结

1.Critical Section

A.速度快

B.不能用于不同进程

C.不能进行资源统计(每次只可以有一个线程对共享资源进行存取)

2.Mutex

A.速度慢

B.可用于不同进程

C.不能进行资源统计

3.Semaphore

A.速度慢

B.可用于不同进程

C.可进行资源统计(可以让一个或超过一个线程对共享资源进行存取)

4.Event

A.速度慢

B.可用于不同进程

C.可进行资源统计

98. 一个数据库中有两个表:

一张表为Customer,含字段ID,Name;

一张表为Order,含字段ID,CustomerID(连向CustomerID的外键),Revenue

写出求每个CustomerRevenue总和的SQL语句。

建表

create table customer

(

ID int primary key,Name char(10)

)

go

create table [order]

(

ID int primary key,CustomerID  int foreign key references customer(id) , Revenue float

)

go

--查询

select Customer.ID, sum( isnull([Order].Revenue,0) )

from customer full join [order] 

on( [order].customerid=customer.id )

group by customer.id

99. 请指出下列程序中的错误并且修改

void GetMemory(char *p){

  p=(char *)malloc(100);

}

void Test(void){

  char *str=NULL;

  GetMemory=(str);

  strcpy(str,"hello world");

  printf(str);

}

A:错误--参数的值改变后,不会传回

GetMemory并不能传递动态内存,Test函数中的 str一直都是 NULL

strcpy(str, "hello world");将使程序崩溃。

修改如下:

方法一:

char *GetMemory(){

  char *p=(char *)malloc(100);

  return p;

}

void Test(void){

  char *str=NULL;

  str=GetMemory(){

  strcpy(str,"hello world");

  printf(str);

}

方法二:void GetMemory2(char **p)变为二级指针.

void GetMemory2(char **p, int num)

{

 *p = (char *)malloc(sizeof(char) * num);

}

100. 程序改错

class mml

{

  private:

    static unsigned int x;

  public:

    mml(){ x++; }

    mml(static unsigned int &) {x++;}

    ~mml{x--;}

  pulic:

    virtual mon() {} = 0;

    static unsigned int mmc(){return x;}

    ......                      

 };

class nnl:public mml

{

  private:

    static unsigned int y;

  public:

    nnl(){ x++; }

    nnl(static unsigned int &) {x++;}

    ~nnl{x--;}

  public:

    virtual mon() {};

     static unsigned int nnc(){return y;}

    ......                   

};

代码片断:

mml* pp = new nnl;

..........

delete pp;

A:

基类的析构函数应该为虚函数

virtual ~mml{x--;}

101. 用写一个输入的整数,倒着输出整数的函数,要求用递归方法 ;

:

void fun( int a )

{

 printf( "%d", a%10 );

 a /= 10;

 if( a <=0 )return;

 fun( a );

}

102. 写出程序结果:

void Func(char str[100])

{

  printf("%d\n", sizeof(str));

}

:4

分析:

指针长度

103. int id[sizeof(unsigned long)]; 这个对吗?为什么?

:

这个 sizeof是编译时运算符,编译时就确定了

可以看成和机器有关的常量。

本文主要包括二个部分,第一部分重点介绍在VC中,怎么样采用sizeof来求结构的大小,以及容易出现的问题,并给出解决问题的方法,第二部分总结出VCsizeof的主要用法。 

104. sizeof应用在结构上的情况 

请看下面的结构: 

struct MyStruct 

double dda1; 

char dda; 

int type 

}; 

对结构MyStruct采用sizeof会出现什么结果呢?sizeof(MyStruct)为多少呢?也许你会这样求: 

sizeof(MyStruct)=sizeof(double)+sizeof(char)+sizeof(int)=13 

但是当在VC中测试上面结构的大小时,你会发现sizeof(MyStruct)16。你知道为什么在VC中会得出这样一个结果吗? 

其实,这是VC对变量存储的一个特殊处理。为了提高CPU的存储速度,VC对一些变量的起始地址做了"对齐"处理。在默认情况下,VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式(vc6.0,32位系统)。 

类型 

对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量) 

Char 

偏移量必须为sizeof(char)1的倍数 

int 

偏移量必须为sizeof(int)4的倍数 

float 

偏移量必须为sizeof(float)4的倍数 

double 

偏移量必须为sizeof(double)8的倍数 

Short 

偏移量必须为sizeof(short)2的倍数 

各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节VC会自动填充。同时VC为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍?/

C/C++程序员应聘常见面试题深入剖析

引言:

本文的写作目的并不在于提供C/C++程序员求职面试指导,而旨在从技术上分析面试题的内涵。文中的大多数面试题来自各大论坛,部分试题解答也参考了网友的意见。

许多面试题看似简单,却需要深厚的基本功才能给出完美的解答。企业要求面试者写一个最简单的strcpy函数都可看出面试者在技术上究竟达到了怎样的程度,我们能真正写好一个strcpy函数吗?我们都觉得自己能,可是我们写出的strcpy很可能只能拿到10分中的2分。读者可从本文看到strcpy函数从2分到10分解答的例子,看看自己属于什么样的层次。此外,还有一些面试题考查面试者敏捷的思维能力。 

  分析这些面试题,本身包含很强的趣味性;而作为一名研发人员,通过对这些面试题的深入剖析则可进一步增强自身的内功。

105. static变量和static 函数各有什么特点?

:

static变量:在程序运行期内一直有效,如果定义在函数外,则在编译单元内可见,如果在函数内,在在定义的block内可见;

static函数:在编译单元内可见;

106. 找错题:

试题1

void test1()

{

 char string[10];

 char* str1 = "0123456789";

 strcpy( string, str1 );

解答:

  字符串str1需要11个字节才能存放下(包括末尾的'\0'),而string只有10个字节的空间,strcpy会导致数组越界;

试题2

void test2()

{

 char string[10], str1[10];

 int i;

 for(i=0; i<10; i++)

 {

  str1 = 'a';

 }

 strcpy( string, str1 );

}

解答:

如果面试者指出字符数组str1不能在数组内结束可以给3;如果面试者指出strcpy(string, str1)调用使得从str1内存起复制到string内存起所复制的字节数具有不确定性可以给7分,在此基础上指出库函数strcpy工作方式的给10;

试题3

void test3(char* str1)

{

 char string[10];

 if( strlen( str1 ) <= 10 )

 {

  strcpy( string, str1 );

 }

}

解答:

if(strlen(str1) <= 10)应改为if(strlen(str1) < 10),因为strlen的结果未统计'\0'所占用的1个字节。

剖析:考查对基本功的掌握:

(1)字符串以'\0'结尾;

(2)对数组越界把握的敏感度;

(3)库函数strcpy的工作方式,如果编写一个标准strcpy函数的总分值为10,下面给出几个不同得分的答案:

试题4

void GetMemory( char *p )

{

 p = (char *) malloc( 100 );

}

void Test( void ) 

{

 char *str = NULL;

 GetMemory( str ); 

 strcpy( str, "hello world" );

 printf( str );

解答:

传入中GetMemory( char *p )函数的形参为字符串指针,在函数内部修改形参并不能真正的改变传入形参的值,执行完
char *str = NULL;

GetMemory( str ); 
后的str仍然为NULL;

试题5

char *GetMemory( void )

 char p[] = "hello world"; 

 return p; 

}

void Test( void )

 char *str = NULL; 

 str = GetMemory(); 

 printf( str ); 

解答:

char p[] = "hello world"; 
return p; 
p[]数组为函数内的局部自动变量,在函数返回后,内存已经被释放。这是许多程序员常犯的错误,其根源在于不理解变量的生存期。

试题6

void GetMemory( char **p, int num )

{

 *p = (char *) malloc( num );

}

void Test( void )

{

 char *str = NULL;

 GetMemory( &str, 100 );

 strcpy( str, "hello" ); 

 printf( str ); 

解答:

GetMemory避免了试题4的问题,传入GetMemory的参数为字符串指针的指针,但是在GetMemory中执行申请内存及赋值语句*p = (char *) malloc( num ); 后未判断内存是否申请成功,应加上:

if ( *p == NULL )

{

 ...//进行申请内存失败处理

Test函数中也未对malloc的内存进行释放。

试题7

void Test( void )

{

 char *str = (char *) malloc( 100 );

 strcpy( str, "hello" );

 free( str ); 

 ... //省略的其它语句

解答:

试题7存在与试题6同样的问题,在执行char *str = (char *) malloc(100); 

后未进行内存是否申请成功的判断;另外,在free(str)后未置str为空,导致可能变成一个""指针,应加上:str = NULL; 

剖析:

试题47考查面试者对内存操作的理解程度,基本功扎实的面试者一般都能正确的回答其中50~60的错误。但是要完全解答正确,却也绝非易事。

对内存操作的考查主要集中在:

1) 指针的理解;

2) 变量的生存期及作用范围;

3) 良好的动态内存申请和释放习惯。

107. 再看看下面的一段程序有什么错误:

swap( int* p1,int* p2 )

{

 int *p;

 *p = *p1;

 *p1 = *p2;

 *p2 = *p;

swap函数中,p是一个""指针,有可能指向系统区,导致程序运行的崩溃。在VC++DEBUG运行时提示错误"Access Violation"。该程序应该改为:

swap( int* p1,int* p2 )

{

 int p;

 p = *p1;

 *p1 = *p2;

 *p2 = p;

108. 字符串拷贝void strcpy( char *strDest, const char *strSrc ) 

2

void strcpy( char *strDest, char *strSrc )

{

  while( (*strDest++ = * strSrc++) != '\0' );

4

void strcpy( char *strDest, const char *strSrc ) 

//将源字符串加const,表明其为输入参数,加2

{

  while( (*strDest++ = * strSrc++) != '\0' );

7

void strcpy(char *strDest, const char *strSrc) 

{

 //对源地址和目的地址加非0断言,加3

 assert( (strDest != NULL) && (strSrc != NULL) );

 while( (*strDest++ = * strSrc++) != '\0' );

10

//为了实现链式操作,将目的地址返回,加3!

char * strcpy( char *strDest, const char *strSrc ) 

{

 assert( (strDest != NULL) && (strSrc != NULL) );

 char *address = strDest; 

 while( (*strDest++ = * strSrc++) != '\0' ); 

 return address;

2分到10分的几个答案我们可以清楚的看到,小小的strcpy竟然暗藏着这么多玄机,真不是盖的!需要多么扎实的基本功才能写一个完美的strcpy!

109. 对strlen的掌握,它没有包括字符串末尾的'\0'

读者看了不同分值的strcpy版本,应该也可以写出一个10分的strlen函数了,完美的版本为:

int strlen( const char *str ) //输入参数const

{

 assert( strt != NULL ); //断言字符串地址非0

 int len;

 while( (*str++) != '\0' ) 

 

  len++; 

 

 return len;

110. 分别给出BOOLintfloat,指针变量 与"零值"比较的 if 语句(假设变量名为var

解答:

BOOL型变量:if(!var)

int型变量: if(var==0)

float型变量:

const float EPSINON = 0.00001;

if ((x >= - EPSINON) && (x <= EPSINON)

指针变量:  if(var==NULL)

剖析:

  考查对0值判断的"内功"BOOL型变量的0判断完全可以写成if(var==0),而int型变量也可以写成if(!var),指针变量的判断也可以写成if(!var),上述写法虽然程序都能正确运行,但是未能清晰地表达程序的意思。

  一般的,如果想让if判断一个变量的"""",应直接使用if(var)if(!var),表明其为"逻辑"判断;如果用if判断一个数值型变量(shortintlong),应该用if(var==0),表明是与0进行"数值"上的比较;而判断指针则适宜用if(var==NULL),这是一种很好的编程习惯。

浮点型变量并不精确,所以不可将float变量用"==""!="与数字比较,应该设法转化成">=""<="形式。如果写成if (x == 0.0),则判为错,得0分。

111. 以下为Windows NT下的32C++程序,请计算sizeof的值

void Func ( char str[100] )

{

 sizeof( str ) = ?

}

void *p = malloc( 100 );

sizeof ( p ) = ? 

解答:

sizeof( str ) = 4

sizeof ( p ) = 4 

剖析:

Func ( char str[100] )函数中数组名作为函数形参时,在函数体内,数组名失去了本身的内涵,仅仅只是一个指针;在失去其内涵的同时,它还失去了其常量特性,可以作自增、自减等操作,可以被修改。

数组名的本质如下:

(1) 数组名指代一种数据结构,这种数据结构就是数组;

例如:
char str[10];
cout << sizeof(str) << endl; 
输出结果为10str指代数据结构char[10]

(2) 数组名可以转换为指向其指代实体的指针,而且是一个指针常量,不能作自增、自减等操作,不能被修改;
char str[10]; 
str++; //编译出错,提示str不是左值  

(3) 数组名作为函数形参时,沦为普通指针。
Windows NT 32位平台下,指针的长度(占用内存的大小)为4字节,故sizeof( str ) sizeof ( p ) 都为4

112. 写一个"标准"MIN,这个宏输入两个参数并返回较小的一个。另外,当你写下面的代码时会发生什么事?

least = MIN(*p++, b); 

解答:

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

MIN(*p++, b)会产生宏的副作用

剖析:

这个面试题主要考查面试者对宏定义的使用,宏定义可以实现类似于函数的功能,但是它终归不是函数,而宏定义中括弧中的"参数"也不是真的参数,在宏展开的时候对"参数"进行的是一对一的替换。

程序员对宏定义的使用要非常小心,特别要注意两个问题:

1) 谨慎地将宏定义中的"参数"和整个宏用用括弧括起来。所以,严格地讲,下述解答:

#define MIN(A,B) (A) <= (B) ? (A) : (B)
#define MIN(A,B) (A <= B ? A : B ) 
都应判0;

2) 防止宏的副作用。

宏定义#define MIN(A,B) ((A) <= (B) ? (A) : (B))MIN(*p++, b)的作用结果是:
((*p++) <= (b) ? (*p++) : (*p++)) 这个表达式会产生副作用,指针p会作三次++自增操作。

  除此之外,另一个应该判0分的解答是:#define MIN(A,B) ((A) <= (B) ? (A) : (B)); 这个解答在宏定义的后面加";",显示编写者对宏的概念模糊不清,只能被无情地判0分并被面试官淘汰。

113. 为什么标准头文件都有类似以下的结构? 

#ifndef __INCvxWorksh

#define __INCvxWorksh 

#ifdef __cplusplus

extern "C"

{

#endif 

/*...*/ 

#ifdef __cplusplus

}

#endif 

#endif /* __INCvxWorksh */ 

解答:

头文件中的编译宏
#ifndef__INCvxWorksh
#define__INCvxWorksh

#endif 
的作用是防止被重复引用。

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

该函数被C编译器编译后在symbol库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。_foo_int_int这样的名字包含了函数名和函数参数数量及类型信息,C++就是考这种机制来实现函数重载的。

为了实现CC++的混合编程,C++提供了C连接交换指定符号extern "C"来解决名字匹配问题,函数声明前加上extern "C"后,则编译器就会按照C语言的方式将该函数编译为_foo,这样C语言中就可以调用C++的函数了。

114. 编写一个函数,作用是把一个char组成的字符串循环右移n个。比如原来是"abcdefghi"如果n=2,移位后应该是"hiabcdefgh" 

函数头是这样的:

//pStr是指向以'\0'结尾的字符串的指针

//steps是要求移动的n

void LoopMove ( char * pStr, int steps )

{

 //请填充...

解答:

正确解答1

void LoopMove ( char *pStr, int steps )

{

 int n = strlen( pStr ) - steps;

 char tmp[MAX_LEN]; 

 strcpy ( tmp, pStr + n ); 

 strcpy ( tmp + steps, pStr); 

 *( tmp + strlen ( pStr ) ) = '\0';

 strcpy( pStr, tmp );

正确解答2

void LoopMove ( char *pStr, int steps )

{

 int n = strlen( pStr ) - steps;

 char tmp[MAX_LEN]; 

 memcpy( tmp, pStr + n, steps ); 

 memcpy(pStr + steps, pStr, n ); 

 memcpy(pStr, tmp, steps ); 

剖析:

  这个试题主要考查面试者对标准库函数的熟练程度,在需要的时候引用库函数可以很大程度上简化程序编写的工作量。

  最频繁被使用的库函数包括:

1) strcpy

2) memcpy

3) memset

 

115. 编写类String的构造函数、析构函数和赋值函数,已知类String的原型为:

class String

 public: 

  String(const char *str = NULL); // 普通构造函数 

  String(const String &other); // 拷贝构造函数 

  ~ String(void); // 析构函数 

  String & operate =(const String &other); // 赋值函数 

 private: 

  char *m_data; // 用于保存字符串 

}; 

解答:

//普通构造函数

String::String(const char *str) 

{

 if(str==NULL) 

 {

  m_data = new char[1]; // 得分点:对空字符串自动申请存放结束标志'\0'的空

  //加分点:对m_dataNULL 判断

  *m_data = '\0'; 

 

 else

 {

  int length = strlen(str); 

  m_data = new char[length+1]; // 若能加 NULL 判断则更好 

  strcpy(m_data, str); 

 }

}

// String的析构函数

String::~String(void) 

{

 delete [] m_data; // delete m_data;

}

//拷贝构造函数

String::String(const String &other) // 得分点:输入参数为const

 int length = strlen(other.m_data); 

 m_data = new char[length+1];   对m_dataNULL 判断

 strcpy(m_data, other.m_data); 

}

//赋值函数

String & String::operate =(const String &other) // 得分点:输入参数为const

 if(this == &other) //得分点:检查自赋值

  return *this; 

 delete [] m_data; //得分点:释放原有的内存资源

 int length = strlen( other.m_data ); 

 m_data = new char[length+1]; //加分点:对m_dataNULL 判断

 strcpy( m_data, other.m_data ); 

 return *this; //得分点:返回本对象的引用

剖析:

  能够准确无误地编写出String类的构造函数、拷贝构造函数、赋值函数和析构函数的面试者至少已经具备了C++基本功的60%以上!

  在这个类中包括了指针类成员变量m_data,当类中包括指针类成员变量时,一定要重载其拷贝构造函数、赋值函数和析构函数,这既是对C++程序员的基本要求,也是《EffectiveC++》中特别强调的条款。

仔细学习这个类,特别注意加注释的得分点和加分点的意义,这样就具备了60%以上的C++基本功!

116. 技巧题:

试题1

请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1

解答:

int checkCPU()

{

 {

  union w

  

   int a;

   char b;

  } c;

  c.a = 1;

  return (c.b == 1);

 }

剖析:

嵌入式系统开发者应该对Little-endianBig-endian模式非常了解。采用Little-endian模式的CPU对操作数的存放方式是从低字节到高字节,而Big-endian模式对操作数的存放方式是从高字节到低字节。例如,16bit宽的数0x1234Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址 存放内容 

0x4000 0x34 

0x4001 0x12 

而在Big-endian模式CPU内存中的存放方式则为:

内存地址 存放内容 

0x4000 0x12 

0x4001 0x34 

32bit宽的数0x12345678Little-endian模式CPU内存中的存放方式(假设从地址0x4000开始存放)为:

内存地址 存放内容 

0x4000 0x78 

0x4001 0x56 

0x4002 0x34 

0x4003 0x12 

  而在Big-endian模式CPU内存中的存放方式则为:

内存地址 存放内容 

0x4000 0x12 

0x4001 0x34 

0x4002 0x56 

0x4003 0x78 

联合体union的存放顺序是所有成员都从低地址开始存放,面试者的解答利用该特性,轻松地获得了CPU对内存采用Little-endian还是Big-endian模式读写。如果谁能当场给出这个解答,那简直就是一个天才的程序员。

试题2

写一个函数返回1+2+3++n的值(假定结果不会超过长整型变量的范围) 

  解答:

int Sum( int n )

 return ( (long)1 + n) * n / 2;//return (1l + n) * n / 2;

}

  剖析:

  对于这个题,只能说,也许最简单的答案就是最好的答案。下面的解答,或者基于下面的解答思路去优化,不管怎么"折腾",其效率也不可能与直接return ( 1 l + n ) * n / 2相比

int Sum( int n )

{

 long sum = 0;

 for( int i=1; i<=n; i++ )

 {

  sum += i;

 }

 return sum;

}

117. 多态类中的虚函数表是Compile-Time,还是Run-Time时建立的?

答:

虚函数表是Compile-Time时建立的。

118. 将一个 1M -10M 的文件,逆序存储到另一个文件,就是前一个文件的最后一个,字符存到新文件的第一个字符,以此类推。

答:

有很多不同的解法,用C还是C++、要简单还是要效率、允不允许用STL等都相关。基本想法是:如果用C,就用文件读写函数和文件指针定位函数(read, write, lseek)从输入文件的文件尾开始一个一个字符反向地读,每读一个字符,文件指针都要重定位,再顺序输出到结果文件。如果用C++/STL,就定义一个容器(vector, deque都行,list则空间效率太差),将整个输入文件一次读到容器里,用reverse反一个序,再输出到结果文件。

#include <iostream>

#include <fstream>

#include <deque>

#include <iterator>

#include <algorithm>

using namespace std;

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

{

        if (argc != 3)

        {

    cerr << "Usage: " << argv[0] << " <input> <output>" << endl;

    return 1;

        }

  ifstream ifs(argv[1]);  // 输入文件

  if (!ifs)

  {

    cerr << "Open file " << argv[1] << " error!" << endl;

    return 2;

  }

  ifs.unsetf(ios::skipws);  // 不能跳过输入文件中的空格

  ofstream ofs(argv[2]);  // 输出文件

  if (!ofs)

  {

    cerr << "Create file " << argv[2] << " error!" << endl;

    return 2;

  }

   deque<char> dc;  // 用于存放文件中的字符的容器

  // 从输入文件中读取字符,反向插入到容器dc

  copy(istream_iterator<char>(ifs), istream_iterator<char>(), front_inserter(dc));

  // 顺序将容器dc中的字符输出到文件中

  copy(dc.begin(), dc.end(), ostream_iterator<char>(ofs, ""));

return 0;

}

119. main主函数执行完毕后,是否可能会再执行一段代码?

答:

当然有,用户自定义类型的全局变量和一些类里面的静态成员都需要析构,这些工作是在main()函数结束以后做的。

120. 一个父类写了一个virtual 函数,如果子类覆盖它的函数不加virtual ,也能实现多态?在子类的空间里,有没有父类的这个函数,或者父类的私有变量?

答:

可以,只要函数中的形参类型一致就行,返回值类型可以不一样。

121. 给一个字符串、例如 “ababc”要求返回“ab” 因为“ab”连续重复出现且最长。用C/C++语言写一函数完成该算法,给出复杂度

答:

有点难度,一下子想不出好点子。最简单的是用三层循环来重复比较,复杂度是O(n^3),感觉应该有更好的算法。

void findLongestSameSubstring(char const* str, char *result)

{

  char const *p1, *p2;

  int len = strlen(str), i, j, max;

*result = 0;

  max = 0;

  for (i = 0; i < len; ++i)

  {

    p1 = str + i;

    for (j = (len+i)/2; j > i; --j)

    {

      p2 = str + j;

      int sublen = p2 - p1;

      if (memcmp(p1, p2, sublen) == 0)

      {

        if (sublen > max)

        {

          memcpy(result, p1, sublen);

          result[sublen] = 0;

          max = sublen;

        }

      }

    }

  }

}

122. 对序列11235813。。。。  是Fab..数列23513...Fab..质数数列,因为他们与自己前面的Fab...数列都互质,给出k,返回第k小的Fab..质数

答:

无算法可言,纯粹的考编程,需要一个判断是否互质的函数,然后用两层循环(外循环生成Fab数,内循环判断是否与已有数列的所有数互质)去找数列中的质数并计数,计到k时输出就是了。

#include <iostream>

#include <vector>

#include <algorithm>

#include <functional>

#include <cassert>

using namespace std;

int gcf(int n1, int n2)

{

  int r;

  if (n1 < n2)

  {

    swap(n1, n2);

  }

  assert (n2 > 0);

  while (1)

  {

    r = n1 % n2;

    if (r == 0)

    {

      break;

    }

    n1 = n2;

    n2 = r;

  }

  return n2;

}

bool notCrossPrime(int n1, int n2)

{

  return gcf(n1, n2) != 1;

}

int main(void)

{

        vector<int> fab;

  fab.push_back(1);

  fab.push_back(1);

 int s, k, next;

  cout << "k = ";

  cin >> k;

  while (k > 0)

  {

    s = fab.size();

    next = fab[s-2] + fab[s-1];

    if (find_if(fab.begin(), fab.end(), bind1st(ptr_fun(notCrossPrime), next)) 

        == fab.end())

    {

      cout << next << " is prime" << endl;

      --k;

    } else {

      cout << next << " is not prime" << endl;

    }

    fab.push_back(next);

  }

  cout << "Answer: " << next << endl;

        return 0;

}

123. 101个硬币100真、1假,真假区别在于重量。请用无砝码天平称两次给出真币重还是假币重的结论。

:

方法一:

101个先取出2,
33,33
第一次称,如果不相等,说明有一堆重或轻
那么把重的那堆拿下来,再放另外35个中的33
如果相等,说明假的重,如果不相等,新放上去的还是重的话,说明假的轻(不可能新放上去的轻)
第一次称,如果相等的话,这66个肯定都是真的,从这66个中取出35个来,与剩下的没称过的35个比
下面就不用说了


方法二:
3题也可以拿A(50),B(50)比一下,一样的话拿剩下的一个和真的比一下。
如果不一样,就拿其中的一堆。比如A(50)再分成两堆25比一下,一样的话就在
B(50)中,不一样就在A(50)中,结合第一次的结果就知道了。

124. 完成字符串拷贝可以使用 sprintfstrcpy 及 memcpy 函数,请问这些函数有什么区别,你喜欢使用哪个,为什么?

答:

sprintf主要用于格式化,strcpy是真正为字符串拷贝准备的,memcpy需要你给出字符串长度。如果只是为了字符串拷贝,当然最好用strcpy了。

125. 变量的声明和定义有什么区别?

答:

声明不分配内存,定义则要分配内存。

126. 请写出下面代码在 32 位平台上的运行结果,并说明 sizeof 的性质:

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

        char a[30];

        char *b = (char *)malloc(20 * sizeof(char));

        printf("%d\n", sizeof(a));

        printf("%d\n", sizeof(b));

        printf("%d\n", sizeof(a[3]));

        printf("%d\n", sizeof(b+3));

        printf("%d\n", sizeof(*(b+4)));

        return 0 ;

}

答:

30

4

1

4

1

127. 请完成以下题目。注意,请勿直接调用 ANSI C 函数库中的函数实现。

a)请编写一个 函数,该函数给出一个字节中被置 的位的个数,并请给出该题的至少一个不同解法。

b)请编写一个 函数,该函数将给定的一个字符串转换成整数。

c)请编写一个 函数,该函数将给定的一个整数转换成字符串。

d)请编写一个 函数,该函数将一个字符串逆序。

e)请编写一个 函数,该函数在给定的内存区域搜索给定的字符,并返回该字符所在位置索引值。

f)请编写一个 函数,该函数在一个字符串中找到可能的最长的子字符串,该字符串是由同一字符组成的。

给出演示上述函数功能的一个简单程序,并请编写对应的 Makefile 文件

128. 我们需要编写一个图形相关的应用程序,需要处理大量图形(Shape)信息,

图形有矩形(Rectangle),正方形(Square),圆形 (Circle)等种类,应用需要计算这些图形的面积,并且可能需要在某个设备上进行显示(使用在标准输出上打印信息的方式做为示意)

a)请用面向对象的方法对以上应用进行设计,编写可能需要的类

b)请给出实现以上应用功能的示例性代码,从某处获取图形信息,并且进行计算和绘制

c)如果你的Square继承自Rectangle,请给出理由,如果不是,请给出理由,并且请比较两种方式的优劣

d)请问你所编写的类,在如下代码中会有何表现,请解释

    void test_rectangle_area(Rectangle& r)

    {

        r.set_width(10);

        r.set_height(15);

        assert(r.area() == 150);

    }

Shape是基类,有纯虚函数area()draw()等;RectangleSquareCircle是派生类,重载各自的area()draw()等。不建议Square派生自Rectangle,如果有继承关系,可以不用重写area()draw(),但是对于Square要求的长宽相等则不好控制。象d)中的代码,就可能会异常中止。

129. 假设现有一个单向的链表,但是只知道只有一个指向该节点的指针p,并且假设这个节点不是尾节点,试编程实现删除此节点

答案:

O(1)的办法:用p指向的节点的下一节点的值替换p指向的节点的值,然后删除p指向的节点的下一节点。

130. 写一个程序,把一个100以内的自然数分解因数。(自然数分解因数就是将一个自然数分解为几个素数的乘积,提示,由于该数不是很大,所以可以将质数保存在数组中,以加快计算速度)

131. 编写一个Identify的分配、释放的函数,为110000之间的自然数。

132. 分别实现itoaatoi.

133. Consider the following code:

#include <stdio.h>

#include <string.h>

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

int i = 1;

char buf[4];

strcpy(buf, "AAAA");

printf("%d\n", i);

return 0;

}

a) When compiled and executed on x86, why does this program usually not 

output what the programmer intended?

b) Name several ways in which the security problem that causes this 

program not to output what the programmer intended can be prevented 

WITHOUT changing the code.

答:

"AAAA"字符串实际上占了5个字节(最后有一个\0),因此strcpybuf时引起了溢出。在x86环境下,溢出的结果可能是将i0了,因此会输出0

134. int w=1,x=2,y=3,z=4;

m=(w<x)?w:x;

m=(m<y)?m:y;

m=(m<2)?m:z;

printf("m=%d",m);        说出结果

答:

m=1

135. 说出结果

#include <stdio.h>

main()

{

    FILE *fp;

    int i,a[4]={1,2,3,4},b;

    fp=fopen("data.dat","wb");//这里帮忙解释一下

    for(i=0;i<4;i++)

    fwrite(&a[i],sizeof(int),1,fp);//这里也帮忙看一下

    fclose(fp);

    fp=fopen("data.dat","rb");

    fseek(fp,-2L*sizeof(int),SEEK_END);//还有这里

    fread(&b,sizeof(int),1,fp);//这里还有也看一下

    fclose(fp);

    printf("b=%d\n",b); 

}

答:

b=3

136. 有双向循环链表结点:

typedef struct node

{

  int date;

  struct node *front,*next;

}_Node;

有两个双向循环链表AB,知道其头指针为:pHeadA,pHeadB,请写一函数将两上链表中date值相同的结点删除

答:

题意不明。如果一个date值在一个链表中出现一次,而在另一个链表中出现两次,是都删除还是每个链表删一个?以下解法按都删除来理解,循环取链表A的各个节点(记为p),和B中的各节点比较,如果没有发现一样的,则循环处理A的下一个节点;如果有则将B中与p相同的节点删掉,然后再在A中循环找与p相同的节点删掉,删完所有BA中与p相同的节点后再循环处理A的下一个节点。

137. 说出结果.

char * GetStr()

{

char *tmp;

tmp = "123"

return tmp;

}

void main()

{

printf("%s", GetStr());

}

会输出123吗?123创建在堆上还是栈上呢?123的空间是什么时候释放的?

会输出123123既不在堆上,也不栈上,它是在正文段(即与程序的代码同一段),它的空间是静态空间,程序执行完后,与整个程序一起释放。

138. 字符指针、浮点数指针、以及函数指针这三种类型的变量哪个占用的内存最大?为什么?

答:

所有指针占的空间都一样,通常等于机器的字长。因为它们并不直接存放数据,而是存放一个数据存放地点的地址码,无论放什么数据,地址码所占的空间是一样的。

139. 类ClassBClassA派生,那么ClassA *a = new ClassB(); 试问该表达是否合法?为什么?

答:

合法。基类的指针可以指向派生类的对象,反之则不行。

140. 如果ClassA中定义并实现虚函数int func(void)ClassB中也实现该函数,那么上述变量a->func()将调用哪个类里面的函数?如果int func(void)不是虚函数,情况又如何?为什么?

答:

调用类B的函数;如果不是虚函数,则调用类A的函数。虚函数的调用是在运行期根据对象的实际类型动态绑定的,非虚成员函数的绑定则是在编译期根据变量类型决定的。

141. char **p, a[16][8];  问:p=a是否会导致程序在以后出现问题?为什么?

答:

142. 如下所述的if elseswitch语句哪个的效率高?为什么?

答:

143. 在同一个进程中,一个模块是否可以通过指针操作破坏其它模块的内存,为什么?

答:

144. 应用程序在运行时的内存包括代码区和数据区,其中数据区又包括哪些部分?

答:

145. Assignment 2: Picture Processing

Use C++, Java, or similar languages or/and any middleware such as EJB and J2EE to process a  picture with a high resolution (3 Mega Pixels for example). Use some methodologies to degrade  the resolution of the picture to make it quicker for browsing. Then divide the degraded  picture into 9 sectors equally. Click any of the 9 sectors will result a detailed picture for  this sector with the same resolution as that of the original picture. This assignment is  designed for you to demonstrate your ability to handle pictures. 

146. 用<<,>>,|,&实现一个WORD(2个字节)的高低位交换!!

答:

(w >> 8) | (w << 8),要求w是无符号的。

147. 要开辟P1,P2,P3,P4内存来做缓冲,大小自定,但这四个缓冲的大小要一样,并且是连续的!

答:

char *p = malloc(4*SIZE_PER_BUF);

p1 = p;

p2 = p1 + SIZE_PER_BUF;

p3 = p2 + SIZE_PER_BUF;

p4 = p3 + SIZE_PER_BUF;

148. 有一浮点型数组A,C语言写一函数实现对浮点数组A进行降序排序,并输出结果,要求要以数组A作为函数的入口.(建议用冒泡排序法)

答:

149. 找错:

#include <string.h>

#include <stdio.h>

class Base

{

private:

char * name;

public:

Base(char * className)

{

name = new char[strlen(className)];

strcpy(name, className);

}

~Base()

{

delete name;

}

char * copyName()

{

char newname [256];

strcpy(newname, name);

return newname;

}

char * getName()

{

return name;

}

static void print(Base base)

{

printf("name: %s\n" , base.name);

}

};

class Subclass : public Base

{

public:

Subclass(char * className) : Base(className)

{

}

};

int main()

{

Base * pBase = new Subclass("test");

Base::print(*pBase);

printf("name: %s\n", pBase->getName());

printf("new name: %s\n", pBase->copyName());

return 0;

}

答:

Base::Base(char*)构造函数中,new分配的空间比所需的少一个字节,strcpy会导致溢出。

Base::copyName()函数中,返回的char*指向局部变量newname,局部变量会在函数返回时释放,导致返回值无效。

Base::print(Base)采用传值的方式调用,会引发一个Base的复制构造函数,而Base没有定义复制构造函数,则编译器给出一个缺省的,这个缺省的复制构造函数会导致两个Base对象的成员变量name指向同一块内存;这样其中一个对象析构时,这块共用的内存将被释放,导致另一个对象的name指向一个无效的地址。

150. 编写一个函数,函数接收一个字符串,是由十六进制数组成的一组字符串,函数的功能是把接到的这组字符串转换成十进制数字.并将十进制数字返回.

答:

使用了C的两个库函数:strtolsprintf

void hex2dec(char const* hex, char *dec)

{

  char *p;

  sprintf(dec, "%ld", strtol(hex, &p, 16));

}

151. 编写一个函数将一条字符串分成两部分,将前半部分按ASCII码升序排序,后半部分不变,(如果字符串是奇数则中间的字符不变,)最后再将前后两部分交换,然后将该字符串输出,

测试字符串“ADZDDJKJFIEJHGI

答:

#include <iostream>

#include <algorithm>

using namespace std;

int main(void)

{

  char str[] = "ADZDDJKJFIEJHGI";

  int len = sizeof(str) - 1;

  cout << str << endl;

char *firstEnd = str + len / 2;

  char *secondBegin = str + len - len / 2;

  sort(str, firstEnd);

  swap_ranges(str, firstEnd, secondBegin);

  cout << str << endl;      

  return 0;

}

152. 找错

Void test1()

{

    char string[10];

    char* str1="0123456789";

strcpy(string, str1);

}

Void test2()

{

    char string[10], str1[10];

for(I=0; I<10;I++)

{

        str1[i] ='a';

}

strcpy(string, str1);

}

Void test3(char* str1)

{

    char string[10];

    if(strlen(str1)<=10)

{

    strcpy(string, str1);

}

}

答:

test1string只有10个字节,放不下str1str111个字节(含最后一个\0

test2str1最后没有以\0结束

test3:同样,当str1长度为10时,它需要的存储空间是11个字节,string放不下

153. 找错

#define MAX_SRM 256

DSN get_SRM_no()

{

        static int SRM_no;

        int I;

        for(I=0;I{

SRM_no %= MAX_SRM;

if(MY_SRM.state==IDLE)

{

    break;

}

}

if(I>=MAX_SRM)

return (NULL_SRM);

else

return SRM_no;

}

答:

程序贴的有问题,可能缺了几行

154. 写出程序运行结果

int sum(int a)

{

        auto int c=0;

        static int b=3;

c+=1;

b+=2;

return(a+b+C);

}    

void main()

{

        int I;

int a=2;

for(I=0;I<5;I++)

{

    printf("%d,", sum(a));

}

}

答:

输出结果:8,10,12,14,16,

155. 算出结果:   

int func(int a)

{

    int b;

    switch(a)

    {

        case 1: 30;

        case 2: 20;

        case 3: 16;

        default: 0

}

return b;

}

func(1)=?

答:

Switch语句有错误

156. 算出结果:

int a[3];

a[0]=0; a[1]=1; a[2]=2;

int *p, *q;

p=a;

q=&a[2];

a[q-p]=?

答:

a[q-p]=2

157. 定义 int **a[3][4], 则变量占有的内存空间为:_____

答:

32位字长的系统下,占48字节。a占的空间应该在12个指针的空间,如果每个指针4字节,就是48字节

158. 编写一个函数,要求输入年月日时分秒,输出该年月日时分秒的下一秒。如输入20041231235959秒,则输出200511000秒。

答:

{

  struct tm stm, *pstm;

  time_t tmt;

  cout << "Input Y,M,D,h,m,s: ";

  cin >> stm.tm_year >> stm.tm_mon >> stm.tm_mday >> stm.tm_hour 

      >> stm.tm_min >> stm.tm_sec;

  stm.tm_year -= 1900;

  stm.tm_mon --;

  stm.tm_isdst = 0;

  tmt = mktime(&stm) + 1;

  pstm = localtime(&tmt);

  cout << pstm->tm_year+1900 << "" << pstm->tm_mon+1 << ""

       << pstm->tm_mday << "" << pstm->tm_hour << "

       << pstm->tm_min << "" << pstm->tm_sec << "" << endl;

  

return 0;

}

159. 写一个函数,判断一个int型的整数是否是2的幂,即是否可以表示成2^X的形式(不可以用循环)

答:

bool IsTwoPower(int s)

{

  return (s > 0) && ((s & (s-1))==0);

}

160. AB从一堆玻璃球(共100个)里向外拿球,规则如下:

1A先拿,然后一人一次交替着拿;

2)每次只能拿1个或2个或4个;

3)谁拿最后一个球,谁就是最后的失败者;

  问AB谁将是失败者?写出你的判断步骤。

答:

A是失败者。当且仅当球数为3n+1时,A是失败者,否则B是。使用归纳法:容易验证球数分别为123时,失败者分别是ABB;当球数为3n+1时,A有三个选择:

a) 1个球,剩3n个,轮到B拿,根据归纳这时后拿者即A为失败者;

b) 2个球,剩3n-1个,轮到B拿,根据归纳这时后拿者即A为失败者;

c)4个球,剩3(n-1)-1个,轮到B拿,根据归纳这时后拿者即A为失败者;

当球数为3n时,A选择拿2个,剩3(n-1)+1个,轮到B拿,根据归纳这时先拿者即B为失败者;当球数为3n-1时,A选择拿1个或4个,剩3(n-1)+1个或3(n-2)+1个,轮到B拿,根据归纳这时先拿者即B为失败者。

161. 已知:无序数组,折半查找,各元素值唯一。

函数原型是:Binary_Seach(int array[], int iValue, int iCount)

array是数组,在里面用折半查找的方法找等于iValue的值,找到返回1否则0iCount是元素个数

答:

使用二分查找的前题是数组已排序,所以要先对数组排序,再进行查找。如果用STL的话,可以写成:

sort(array, array+iCount);

return binary_search(array, array+iCount, iValue);

不过这会修改array里的元素次序,如果不允许修改的话,就需要一个临时数组用于排序和查找。

162. 统计一个字符串中字符出现的次数

答:

如果用STL的话,很容易:

return count(str, str+strlen(str), ch);

163. 100位以上的超大整数的加法(主要考虑数据结构和加法的实现)。

答:

用一个vector<int>表示一个超大整数,每个元素表示其中4位数字(十进制),从低位到高位存储。实现加法时,将两个vector<int>的每个元素依次相加,如果结果大于9999则向下一个元素进位。

164. 对如下电文:"CASTCASTSATATATASA"给出Huffman编码。

答:

先统计各字母的出现次数:C=2A=7S=4T=5

再得出编码:A=0T=10C=110S=111

代入原串得到:11001111011001111011101001001001110

165. int (* (*f)(int, int))(int)表示什么含义?

答:

定义一个函数指针类型的变量ff可以指向一个函数,该函数以两个int为参数,并且返回值是这样一种函数指针类型:它指向一个以单个int为参数并返回int值的函数。拆开来写的话,可以写成:

typedef int (*T)(int);

T (*f)(int, int);

166. x=x+1x+=1x++,为这三个语句的效率排序。并说明为什么。

答:

从左到右效率递增。如果x是内置类型的话,x++可以被编译为一条高效的机器指令,x+=1也可以编译为一条机器指令但指令里带有操作数1,而x=x+1则要编译多条机器指令,包括取值、运算、存值。如果x是用户定义类型的话,三个语句的效率要视用户对运算符的重载情况而定,但通常用户会首先实现operator++,然后是operator+=,它们两个的效率可能较为接近,最后会用operator+=来实现operator+,它的效率通常会最低。

167. 中缀表达式 A-(B+C/D)*E的后缀形式是什么?

答:

ABCD/+E*-,或者清楚一些:A((B(CD/)+)E*)-

168. struct S1

{

char c;

int i;

};

sizeof(S1) = ?

class X{

public:

X();

virtual ~X();

void myMemberFunc();

static void myStaticFunc();

virtual void myVirtualFunc();

private:

int i;

char * pstr;

char a;

}

sizeof(X) = ?

答:

结果要视机器字长以及编译器对齐开关的设置而定,32位字长机器下缺省设置的结果是:

sizeof(S1) = 8  为了对齐ic占了4字节,i再占4字节

sizeof(X) = 16  i4字节,pstr也占4字节,本为a可以只要1字节,但由于定义了虚拟成员函数,后面要加一个vptr,为了对齐vptra占了4字节,最后vptr也是4字节

169. 找出两个字符串中最大子字符串,"abractyeyt","dgdsaeactyey"的最大子串为"actyet"

答:

C++string类,可以省不少事:

#include <iostream>

#include <string>

using namespace std;

string longestCommonSubstring(string const& s1, string const& s2)

{

  int n = min(s1.length(), s2.length());

  int len1 = s1.length();

  string ss;

  for (int i = n; i > 0; --i)

  {

    for (int j = 0; j <= len1-i; ++j)

    {

      ss = s1.substr(j, i);

      if (s2.find(ss, 0) != string::npos)

      {

        return ss;

      }

    }

  }

  return string();

}

int main(void)

{

  cout << longestCommonSubstring("abractyeyt","dgdsaeactyey") << endl;

  return 0;

}

170. 有一百个整数,其中有负数,找出连续三个数之和最大的部分.

答:

用一个循环逐个计算连续三个数的和,进行比较,并保留最大值及其位置,循环结束后即完成。如果题目改为求连续任意个数之和最大,则有点复杂了。

171. 写一程序实现快速排序假设数据输入为一文件

快速算法描述如下 

Algorithm Partition 

Input: sequence a0, ..., an-1 with n elements 

Output: permutation of the sequence such that all elements a0, ..., aj are less than or equal to all

elements ai, ..., an-1 (i > j) 

Method: 

choose the element in the middle of the sequence as comparison element x 

let i = 0 and j = n-1 

while ij 

    search the first element ai which is greater than or equal to x 

    search the last element aj which is less than or equal to x 

    if ij 

exchange ai and aj 

    let i = i+1 and j = j-1 

After partitioning the sequence, Quicksort treats the two parts recursively by the same procedure.

The recursion ends whenever a part consists of one element only. 

答:

172. 写一算法检测单向链表中是否存在环(whether there is a loop in a link list),

要求算法复杂度(Algorithm's complexityO(n)) 并只使用常数空间(space is O(c)). 

注意,你只知道一个指向单向链表头的指针。链表的长度是不定的,而且环出现的地方也是不定的,环有可能在头,有可能在中间。而且要求是检测不能破坏环的结构.

答:

设两个指针,开始时全部指向链表头,然后开始循环,每次循环中一个指针下移一项,另一个指针下移两项,一直循环至以下两个条件之一被满足:

如果有一个指针回到了链表头,则该链表无环;

如果两个指针相等,则该链表有环。

173. 设下列函数已经通过了调试

bool Sort_Array(ArrayType * Pinputarray, ArrayType * Poutarray);

该函数在内存中排序,能把字节数最大为100M字节的ArrayType类型的数组排序。其中ArrayType是一个

预定义的数组类型(细节无关紧要),PinputarrayPoutarray分别为排序前的指针和排序后的指针。

请用c语言的伪码风格设计一个算法,他调用上面给出的函数完成下列从输入到输出的任务:

输入:排序前的大文件,名称为char * pinoutfilename ,其内容为用分号分隔的ArrayType类型的数组元素,可装满4100M字节的数组。

输出:排序后的大文件char * poutoutfilename

答:

分四次从输入文件中读数据,每次读100M字节,每次读完后调用Sort_Array将这100M字节的数据排序,并分别输出成四个100M的临时文件;

取两个100M临时文件进行合并排序,输出为一个200M的临时文件;

同样再将另两个100M临时文件合并为一个200M临时文件;

最后将两个200M临时文件合并排序,输出结果文件。

174. 用最有效率的方法算出2乘以8等於几?

答:

8 << 1

175. 错误的转义字符是 (a )

A.'\091'  B.'\\'

C.'\0'  D.'\''

176. 若数组名作实参而指针变量作形参,函数调用实参传给形参的是 (d )

A.数组的长度  B.数组第一个元素的值

C.数组所有元素的值 D.数组第一个元素的地址

177. 变量的指针含意是指变量的 (b )

A.值  B.地址

C.存储 D.名字

178. 某文件中定义的静态全局变量(或称静态外部变量)其作用域是 (d )

A.只限某个函数  B.本文件

C.跨文件  D.不限制作用域

179. 解二次方程:a*x*x+b*x+c

int Quadratic( double a,double b,double c,double& x1,double& x2);

返回值:解的个数

180. 最大公约数

DWORD Divisor( DWORD dwFirst, DWORD dwSecond );

返回值:最大公约数

181. 根据蒙特卡洛算法计算圆周率

double PI( DOWRD dwCount/*测试次数*/ )

返回值:PI

182. 无符号整数乘法,乘数为32bit,结果为64bit

提示:32bit整数分解为16bit相乘

void Multiply( DWORD dwFirst, DWORD dwSecond, DWORD& dwHigh, DWORD& dwLower );

183. 链表排序(从小到大)

节点定义为:

struct Node{

  int nValue;

  struct Node* pNext;

};

最后一个节点的pNext = NULL.

Node* SortChain( Node* pHead );

返回值:链表头

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

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

我在这想看到几件事情: 

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

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

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

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

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

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

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

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

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

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

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

least = MIN(*p++, b);

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

如果你不知道答案,请看参考文献1。这问题对区分一个正常的伙计和一个书呆子是很有用的。只有书呆子才会读C语言课本的附录去找出象这种 

问题的答案。当然如果你不是在找一个书呆子,那么应试者最好希望自己不要知道

答案。

死循环(Infinite loops

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

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

while(1) { } 

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

for(;;) { } 

这个实现方式让我为难,因为这个语法没有确切表达到底怎么回事。如果一个应试者给出这个作为方案,我将用这个作为一个机会去探究他们这样做的 

基本原理。如果他们的基本答案是:"我被教着这样做,但从没有想到过为什么。"这会给我留下一个坏印象。 

第三个方案是用 goto 

Loop: 

... 

goto Loop; 

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

数据声明(Data declarations) 

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

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

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

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

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

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

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

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

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

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

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

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

int square(volatile int *ptr) 

return *ptr * *ptr; 

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

int square(volatile int *ptr) 

int a,b; 

a = *ptr; 

b = *ptr; 

return a * b; 

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

long square(volatile int *ptr) 

int a; 

a = *ptr; 

return a * a; 

}

位操作(Bit manipulation

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

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

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

2). bit fieldsBit 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) 

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

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

int *ptr; 

ptr = (int *)0x67a9; 

*ptr = 0xaa55;

一个较晦涩的方法是: 

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

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

中断(Interrupts) 

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

__interrupt double compute_area (double radius) 

double area = PI * radius * radius; 

printf(" Area = %f", area); 

return area; 

}

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

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

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

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

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

代码例子(Code examples

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

void foo(void) 

unsigned int a = 6; 

int b = -20; 

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

}

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

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

unsigned int zero = 0; 

unsigned int compzero = 0xFFFF; 

/*1's complement of zero */

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

unsigned int compzero = ~0;

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

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

动态内存分配(Dynamic memory allocation

194. 尽管不像非嵌入式计算机那么常见,嵌入式系统还是有从堆(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"。我用这个来开始讨论这样的一问题,看看被面试者是否想到库例程这样做是正确。得到正确的答案固然重要,但解决问题的方法和你做决定的基本原理更重要些。

195. 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 两个指针。

196. 以下三条输出语句分别输出什么?

char str1[] = "abc";

char str2[] = "abc";

const char str3[] = "abc"; 

const char str4[] = "abc"; 

const char* str5 = "abc";

const char* str6 = "abc";

cout << boolalpha << ( str1==str2 ) << endl; // 输出什么?

cout << boolalpha << ( str3==str4 ) << endl; // 输出什么?

cout << boolalpha << ( str5==str6 ) << endl; // 输出什么? 

答:分别输出false,false,truestr1str2都是字符数组,每个都有其自己的存储区,它们的值则是各存储区首地址,不等;str3str4同上,只是按const语义,它们所指向的数据区不能修改。str5str6并非数组而是字符指针,并不分配存储区,其后的“abc”以常量形式存于静态数据区,而它们自己仅是指向该区首地址的指针,相等。

197. 以下代码中的两个sizeof用法有问题吗?

void UpperCase( char str[] ) // 将 str 中的小写字母转换成大写字母

{

for( size_t i=0; i<sizeof(str)/sizeof(str[0]); ++i )

if( 'a'<=str[i] && str[i]<='z' )

str[i] -= ('a'-'A' );

}

char str[] = "aBcDe";

cout << "str字符长度为: " << sizeof(str)/sizeof(str[0]) << endl;

UpperCase( str );

cout << str << endl;

答:函数内的sizeof有问题。根据语法,sizeof如用于数组,只能测出静态数组的大小,无法检测动态分配的或外部数组大小。函数外的str是一个静态定义的数组,因此其大小为6,函数内的str实际只是一个指向字符串的指针,没有任何额外的与数组相关的信息,因此sizeof作用于上只将其当指针看,一个指针为4个字节,因此返回4

198. 非C++内建型别 和 B,在哪几种情况下B能隐式转化为A

答:

a. class B : public A { ……} // B公有继承自A,可以是间接继承的

b. class B { operator A( ); } // B实现了隐式转化为A的转化

c. class A { A( const B& ); } // A实现了non-explicit的参数为B(可以有其他带默认值的参数)构造函数

d. A& operator= ( const A& ); // 赋值操作,虽不是正宗的隐式类型转换,但也可以勉强算一个

199. 以下代码有什么问题?

struct Test

{

Test( int ) {}

Test() {}

void fun() {}

};

void main( void )

{

Test a(1);

a.fun();

Test b();

b.fun();

}

答:变量b定义出错。按默认构造函数定义对象,不需要加括号。

200. 以下代码有什么问题?

cout << (true?1:"1") << endl;

答:三元表达式“?:”问号后面的两个操作数必须为同一类型。

201. 以下代码能够编译通过吗,为什么?

unsigned int const size1 = 2;

char str1[ size1 ];

unsigned int temp = 0;

cin >> temp;

unsigned int const size2 = temp;

char str2[ size2 ];

答:str2定义出错,size2非编译器期间常量,而数组定义要求长度必须为编译期常量。

202.  以下反向遍历array数组的方法有什么错误?

vector array;

array.push_back( 1 );

array.push_back( 2 );

array.push_back( 3 );

for( vector::size_type i=array.size()-1; i>=0; --i ) // 反向遍历array数组

{

cout << array[i] << endl;

}

答:首先数组定义有误,应加上类型参数:vector<int> array。其次vector::size_type被定义为unsigned int,即无符号数,这样做为循环变量的i0时再减1就会变成最大的整数,导致循环失去控制。

203.  以下代码中的输出语句输出0吗,为什么?

struct CLS

{

int m_i;

CLS( int i ) : m_i(i) {}

CLS()

{

CLS(0);

}

};

CLS obj;

cout << obj.m_i << endl;

答:不能。在默认构造函数内部再调用带参的构造函数属用户行为而非编译器行为,亦即仅执行函数调用,而不会执行其后的初始化表达式。只有在生成对象时,初始化表达式才会随相应的构造函数一起调用。

204.  C++中的空类,默认产生哪些类成员函数?

答:

class Empty

{

public:

Empty(); // 缺省构造函数

Empty( const Empty& ); // 拷贝构造函数

~Empty(); // 析构函数

Empty& operator=( const Empty& ); // 赋值运算符

Empty* operator&(); // 取址运算符

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

};

205.  以下两条输出语句分别输出什么?

float a = 1.0f;

cout << (int)a << endl;

cout << (int&)a << endl;

cout << boolalpha << ( (int)a == (int&)a ) << endl; // 输出什么?

float b = 0.0f;

cout << (int)b << endl;

cout << (int&)b << endl;

cout << boolalpha << ( (int)b == (int&)b ) << endl; // 输出什么?

答:分别输出falsetrue。注意转换的应用。(int)a实际上是以浮点数a为参数构造了一个整型数,该整数的值是1(int&)a则是告诉编译器将a当作整数看(并没有做任何实质上的转换)。因为1以整数形式存放和以浮点形式存放其内存数据是不一样的,因此两者不等。对b的两种转换意义同上,但是0的整数形式和浮点形式其内存数据是一样的,因此在这种特殊情形下,两者相等(仅仅在数值意义上)。

注意,程序的输出会显示(int&)a=1065353216,这个值是怎么来的呢?前面已经说了,1以浮点数形式存放在内存中,按ieee754规定,其内容为0x0000803F(已考虑字节反序)。这也就是a这个变量所占据的内存单元的值。当(int&)a出现时,它相当于告诉它的上下文:“把这块地址当做整数看待!不要管它原来是什么。”这样,内容0x0000803F按整数解释,其值正好就是1065353216(十进制数)。

通过查看汇编代码可以证实“(int)a相当于重新构造了一个值等于a的整型数”之说,而(int&)的作用则仅仅是表达了一个类型信息,意义在于为cout<<==选择正确的重载版本。

206. 以下代码有什么问题?

typedef vector IntArray;

IntArray array;

array.push_back( 1 );

array.push_back( 2 );

array.push_back( 2 );

array.push_back( 3 );

// 删除array数组中所有的2

for( IntArray::iterator itor=array.begin(); itor!=array.end(); ++itor )

{

if( 2 == *itor ) array.erase( itor );

}

答:同样有缺少类型参数的问题。另外,每次调用“array.erase( itor );”,被删除元素之后的内容会自动往前移,导致迭代漏项,应在删除一项后使itor--,使之从已经前移的下一个元素起继续遍历。

207. 写一个函数,完成内存之间的拷贝。[考虑问题是否全面]

答:

void* mymemcpy( void *dest, const void *src, size_t count )

{

char* pdest = static_cast<char*>( dest );

const char* psrc = static_cast<const char*>( src );

if( pdest>psrc && pdest<psrc+cout ) 能考虑到这种情况就行了

{

for( size_t i=count-1; i!=-1; --i )

pdest[i] = psrc[i];

}

else

{

for( size_t i=0; i<count; ++i )

pdest[i] = psrc[i];

}

return dest;

}

208.  以下代码有什么问题?[C]

void char2Hex( char c ) // 将字符以16进制表示

{

char ch = c/0x10 + '0'; if( ch > '9' ) ch += ('A'-'9'-1);

char cl = c%0x10 + '0'; if( cl > '9' ) cl += ('A'-'9'-1);

cout << ch << cl << ' ';

}

char str[] = "I love 中国";

for( size_t i=0; i<strlen(str); ++i )

char2Hex( str[i] );

cout << endl;

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

int a = 5, b = 7, c; 

c = a+++b;

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

c = a++ + b; 

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

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

210. What will print out? 

A1:

main() 

char *p1="name"; 

char *p2; 

p2=(char*)malloc(20); 

memset (p2, 0, 20); 

while(*p2++ = *p1++); 

printf("%sn",p2); 

Answer:empty string. 

A2:

main() 

int x=20,y=35; 

x=y++ + x++; 

y= ++y + ++x; 

printf("%d%dn",x,y); 

Answer : 5794 

A3:

main() 

int x=5; 

printf("%d,%d,%d\n",x,x<<2,x>>2); 

}

Answer: 5,20,1 

A4:

#define swap(a,b) a=a+b;b=a-b;a=a-b; 

void main()

int x=5, y=10; 

swap (x,y); 

printf("%d %dn",x,y); 

swap2(x,y); 

printf("%d %dn",x,y); 

int swap2(int a, int b) 

int temp; 

temp=a; 

b=a; 

a=temp; 

return 0; 

Answer: 10, 5

10, 5 

A5:

main()

char *ptr = " Cisco Systems"; 

*ptr++; printf("%sn",ptr); 

ptr++; 

printf("%sn",ptr); 

Answer:Cisco Systems

isco systems 

A6:

main()

char s1[]="Cisco"; 

char s2[]= "systems"; 

printf("%s",s1); 

Answer: Cisco 

A7:

main()

char *p1; 

char *p2; 

p1=(char *)malloc(25); 

p2=(char *)malloc(25); 

strcpy(p1,"Cisco"); 

strcpy(p2,"systems"); 

strcat(p1,p2); 

printf("%s",p1); 

Answer: Ciscosystems 

A8:

The following variable is available in file1.c, who can access it?:

static int average;

Answer: all the functions in the file1.c can access the variable. 

A9:

#define TRUE 0 // some code 

while(TRUE) 

// some code 

Answer: This will not go into the loop as TRUE is defined as 0. 

A10:

int x; 

int modifyvalue() 

return(x+=10); 

int changevalue(int x) 

return(x+=1); 

void main()

int x=10; 

x++; 

changevalue(x); 

x++; 

modifyvalue(); 

printf("First output:%dn",x); 

x++; 

changevalue(x); 

printf("Second output:%dn",x); 

modifyvalue(); 

printf("Third output:%dn",x); 

Answer: 12 , 13 , 13 

A11:

main()

int x=10, y=15; 

x = x++; 

y = ++y; 

printf("%d %dn",x,y); 

Answer: 11, 16 

A12:

main()

int a=0; 

if(a==0) 

printf("Cisco Systemsn"); 

printf("Cisco Systemsn"); 

Answer: Two lines with "Cisco Systems" will be printed. 

 

原创粉丝点击