MOTO面试题

1.介绍一下STL,详细说明STL如何实现vector。
 
  答:STL 是 Standard Template Libary 的,翻译成中文叫做标准模版库.
      它是是容器,算法和迭代器的集合.通过迭代器作为桥梁将容器和算法
      联系起来.容器是用来存储数据的,算法是用来操作数据的.容器是模版化
      的数据集和,算法是泛形化的数据操作.利用 STL 进行开发,可以减轻对于动态内存的操作,利于程序开发和维护.
 
      vector 是一个动态的数组,它的连续性其实是一种假象.vector 动态的扩大容量的大小,是这样来实现的.首先来申请一个适合的存储空间,将原      来的内容拷贝进新的存储空间,再把旧的空间释放掉.
     
2.指针和引用有什么分别;如果传引用比传指针安全,为什么?如果我使用常量指针难道不行吗?

  答: 指针和引用的区别:
   (1) 引用必须初始化,指针无需.
   (2) 引用经过编译器的优化不占用存储空间,
       在 32 位机器下指针占4个字节.
   (3) 引用和被引用的对象是同一个对象,
       指针内存放的是指向对象的地址.
   (4) 指针可以改变所指的对象,
       引用一经初始化就不可改变.
   (5) 引用利于理解,指针容易混淆.

      传引用确实比传指针安全,就如上面(4)所言指针可以改变指向.如果我们为函数传入数组的首地址,而在函数内意外的修改了该首地址,可能会出现意向不到的后果.
   
      我使用常量指针难道不行.这样是不行的,因为常量指针的指针的意识是
      指针不可以改变指向,但是可以更改指针所指的内容.这样也可能带来意 想不到的后果.
      如果非要指针的话,你可以这样的定义 const int*const p = &a;
      但是这样的可读性没有引用好,因此建议使用引用.


3.参数传递有几种方式;实现多态参数传递采用什么方式,如果没有使用某种方式原因是什么;

   答: 参数的传递方式有传递地址和传递数值两种方式。
        
        实现多态参数传递采用传递地址的方式。
       
        第三问不是很清楚。

4.构造函数可否是虚函数,为什么?析构函数呢,可否是纯虚的呢?
 
   答:构造函数不可是虚函数,因为在构造里面是需要明确的知道要构造什。
       虚函数是不确定的,因此可能会带来意想不到的后果。
       析构函数可以是纯虚的。虽然在语法上正确,但是在实际开发过程不应该这样声明,因为基类的纯虚函数不可以定义。因此如果类中有动态申       请的空间,这样可能会造成内存泄漏。

5. 拷贝构造函数相关问题,深拷贝,浅拷贝,临时对象等。

   答: 深拷贝是将资源和指针全都拷贝,而浅拷贝仅仅拷贝指针。

        如果拷贝拷贝构造函数是浅拷贝,程序可能造成对同一块内存删除两次的情况,这样程序会崩溃。还有一种情况如果,指针改变了
        指向将造成内存泄漏。
   
        临时对象的开销比局部对象要小,临时对象不可以做左值。

6. 基类的有1个虚函数,子类还需要申明为virtual吗?为什么。
   
   答: 不需要。因为编译器会把它当作虚函数处理。

7. C++和C定义结构的分别是什么。

   答: C 中只有数据,而且都是公有的。
        C++ 中有数据也有函数,默认的是私有的。

13.C也可以通过精心封装某些函数功能实现重用,那C++的类有什么优点吗,难道仅仅是为实现重用。 

     并不仅仅是这样的。 

     OOD,OOP从根本上改变了程序设计模式和设计思想,具备重大和深远的意义。 

     类的三大最基本的特征:封装,继承,多态. 

  
14.C++特点是什么,如何实现多态?画出基类和子类在内存中的相互关系。 

     多态的基础是继承,需要虚函数的支持,简单的多态是很简单的。 

     子类继承父类大部分的资源,不能继承的有构造函数,析构函数,拷贝构造函数,operator=函数,友元函数等等 

  
15.为什么要引入抽象基类和纯虚函数? 

     主要目的是为了实现一种接口的效果。 

  
16.介绍一下模板和容器。如何实现?(也许会让你当场举例实现) 

     模板可以说比较古老了,但是当前的泛型编程实质上就是模板编程。 

     它体现了一种通用和泛化的思想。 

     STL有7种主要容器:vector,list,deque,map,multimap,set,multiset. 

  
17.你如何理解MVC。简单举例来说明其应用。 

     MVC模式是observer 模式的一个特例,典型的有MFC里面的文档视图架构。 

  
18.多重继承如何消除向上继承的二义性。 

     使用虚拟继承即可. 

 

 

19. 以下三条输出语句分别输出什么?[C易] 

[cpp]  view plain copy
  1. char str1[] = "abc";   
  2. char str2[] = "abc";   
  3. const char str3[] = "abc";   
  4. const char str4[] = "abc";   
  5. const char* str5 = "abc";   
  6. const char* str6 = "abc";   
  7. cout << boolalpha << ( str1==str2 ) << endl; // 输出什么?   
  8. cout << boolalpha << ( str3==str4 ) << endl; // 输出什么?   
  9. cout << boolalpha << ( str5==str6 ) << endl; // 输出什么?  
 


20. 非C++内建型别 A 和 B,在哪几种情况下B能隐式转化为A?[C++中等] 

答: 

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& ); // 赋值操作,虽不是正宗的隐式类型转换,但也可以勉强算一个 


21. 以下代码中的两个sizeof用法有问题吗?[C易] 

[cpp]  view plain copy
  1. void UpperCase( char str[] ) // 将 str 中的小写字母转换成大写字母   
  2. {   
  3. forsize_t i=0; i<sizeof(str)/sizeof(str[0]); ++i )   
  4. if'a'<=str[i] && str[i]<='z' )   
  5. str[i] -= ('a'-'A' );   
  6. }   
  7. char str[] = "aBcDe";   
  8. cout << "str字符长度为: " << sizeof(str)/sizeof(str[0]) << endl;   
  9. UpperCase( str );   
  10. cout << str << endl;  

 

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

[cpp]  view plain copy
  1. int func(x)   
  2. {   
  3. int countx = 0;   
  4. while(x)   
  5. {   
  6. countx ++;   
  7. x = x&(x-1);   
  8. }   
  9. return countx;   
  10. }   


假定x = 9999。 答案:8 

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


23. 什么是“引用”?申明和使用“引用”要注意哪些问题? 

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


24. 将“引用”作为函数参数有哪些特点? 

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

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

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


25. 在什么时候需要使用“常引用”?  

如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式: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 。 


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

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

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

[cpp]  view plain copy
  1. #include <iostream.h>   
  2. int &put(int n);   
  3. int vals[10];   
  4. int error=-1;   
  5. void main()   
  6. {   
  7. put(0)=10; //以put(0)函数值作为左值,等价于vals[0]=10;   
  8. put(9)=20; //以put(9)函数值作为左值,等价于vals[9]=20;   
  9. cout<<vals[0];   
  10. cout<<vals[9];   
  11. }   
  12. int &put(int n)   
  13. {   
  14. if (n>=0 && n<=9 ) return vals[n];   
  15. else { cout<<"subscript error"return error; }   
  16. }   


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

 

27.引用与多态的关系? 

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

例4 

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


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

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


29. 什么时候需要“引用”? 

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

 

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

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

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


31. 下面关于“联合”的题目的输出? 

a) 

[cpp]  view plain copy
  1. #include <stdio.h>   
  2. union   
  3. {   
  4. int i;   
  5. char x[2];   
  6. }a;   
  7. void main()   
  8. {   
  9. a.x[0] = 10;   
  10. a.x[1] = 1;   
  11. printf("%d",a.i);   
  12. }   


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

b) 
[cpp]  view plain copy
  1. main()   
  2. {   
  3. union{    
  4. int i;   
  5. struct{    
  6. char first;   
  7. char second;   
  8. }half;   
  9. }number;   
  10. number.i=0x4241;    
  11. printf("%c%c/n", number.half.first, mumber.half.second);   
  12. number.half.first='a';    
  13. number.half.second='b';   
  14. printf("%x/n", number.i);   
  15. getch();   
  16. }   


答案: AB (0x41对应'A',是低位;Ox42对应'B',是高位) 

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


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

答案: 
[cpp]  view plain copy
  1. char *strcpy(char *strDest, const char *strSrc)   
  2. {   
  3. if ( strDest == NULL || strSrc == NULL)   
  4. return NULL ;   
  5. if ( strDest == strSrc)   
  6. return strDest ;   
  7. char *tempptr = strDest ;   
  8. while( (*strDest++ = *strSrc++) != ‘/0’)   
  9. ;   
  10. return tempptr ;   
  11. }   



33. 已知String类定义如下: 

class String 



public: 

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

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

~ String(); // 析构函数 

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

private: 

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

}; 

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

答案: 
[cpp]  view plain copy
  1. String::String(const char *str)   
  2. {   
  3. if ( str == NULL ) //strlen在参数为NULL时会抛异常才会有这步判断   
  4. {   
  5. m_data = new char[1] ;   
  6. m_data[0] = '/0' ;   
  7. }   
  8. else   
  9. {   
  10. m_data = new char[strlen(str) + 1];  
  11. strcpy(m_data,str);   
  12. }   
  13. }   
  14. String::String(const String &another)   
  15. {   
  16. m_data = new char[strlen(another.m_data) + 1];   
  17. strcpy(m_data,other.m_data);   
  18. }   
  19. String& String::operator =(const String &rhs)   
  20. {   
  21. if ( this == &rhs)   
  22. return *this ;   
  23. delete []m_data; //删除原来的数据,新开一块内存   
  24. m_data = new char[strlen(rhs.m_data) + 1];   
  25. strcpy(m_data,rhs.m_data);   
  26. return *this ;   
  27. }   
  28. String::~String()   
  29. {   
  30. delete []m_data ;   
  31. }  
 

 

 

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

答: 

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


35. #include<file.h> 与 #include "file.h"的区别? 

答: 

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


36.在C++ 程序中调用被C 编译器编译后的函数,为什么要加extern “C”? 

首先,作为extern是C/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中函数声明了foo为extern "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函数例子工程中包含的三个文件的源代码如下: 

[cpp]  view plain copy
  1. #ifndef C_EXAMPLE_H   
  2. #define C_EXAMPLE_H   
  3. extern int add(int x,int y);   
  4. #endif   
  5. #include "cExample.h"   
  6. int add( int x, int y )   
  7. {   
  8. return x + y;   
  9. }   
  10. // c++实现文件,调用add:cppFile.cpp   
  11. extern "C"   
  12. {   
  13. #include "cExample.h"   
  14. }   
  15. int main(int argc, char* argv[])   
  16. {   
  17. add(2,3);   
  18. return 0;   
  19. }   


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

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

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

//C++头文件 cppExample.h 

#ifndef CPP_EXAMPLE_H 

#define CPP_EXAMPLE_H

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

#endif 

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

#include "cppExample.h" 

int add( int x, int y ) 



return x + y; 




extern int add( int x, int y ); 

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



add( 2, 3 ); 

return 0; 

}

 

 

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

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


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

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

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


实现的形式是: 

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

 

 

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

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

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

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


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

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

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

重写:是指子类重新定义复类虚函数的方法。 

从实现原理上来说: 

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

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

 

 

40. 什么是预编译,何时需要预编译:总是使用不经常改动的大型代码体。 

程序由多个模块组成,所有模块都使用一组标准的包含文件和相同的编译选项。在这种情况下,可以将所有包含文件预编译为一个预编译头。 


41. char * const p; 

  char const * p 

  const char *p 


  上述三个有什么区别? 


  char * const p; //常量指针,p的值不可以修改 

  char const * p;//指向常量的指针,指向的常量值不可以改 

  const char *p; //和char const *p 


42. 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是指针,它们指向相同的常量区域。 


43. 以下代码中的两个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。 


44. 一个32位的机器,该机器的指针是多少位? 


指针是多少位只要看地址总线的位数就行了。80386以后的机子都是32的数据总线。所以指针的位数就是4个字节了。 


45. main() 
  { 

   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],执行结果是2,5 

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

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

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

   

  原因如下: 


  &a是数组指针,其类型为 int (*)[5]; 

  而指针加1要根据指针类型加上一定的值,不同类型的指针+1之后增加的大小不同。 

  a是长度为5的int数组指针,所以要加 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].

 

 

46.请问以下代码有什么问题: 
  int main() 

  { 

   char a; 

   char *str=&a; 

   strcpy(str,"hello"); 

   printf(str); 

   return 0; 

  } 


  没有为str分配内存空间,将会发生异常。问题出在将一个字符串复制进一个字符变量指针所指地址。虽然可以正确输出结果,但因为越界进行内在读写而导致程序崩溃。 


47.

  char* s="AAA"; 

  printf("%s",s); 

  s[0]='B'; 

  printf("%s",s); 


  有什么错? 


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


  cosnt char* s="AAA"; 


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


48.写一个“标准”宏,这个宏输入两个参数并返回较小的一个。 


  .#define Min(X, Y) ((X)>(Y)?(Y):(X))//结尾没有; 


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


  while(1){}或者for(;;)

 

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


  定义静态变量 


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


  表示常量不可以修改的变量。 


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


  提示编译器对象的值可能在编译器未监测到的情况下改变。 


53. int (*s[10])(int) 表示的是什么? 


  int (*s[10])(int) 函数指针数组,每个指针指向一个int func(int param)的函数。 


54. 有以下表达式: 


  int a=248; b=4; 

  int const c=21; 

  const int *d=&a; 

  int *const e=&b; 

  int const *f const =&a; 


  请问下列表达式哪些会被编译器禁止?为什么? 


  *c=32;d=&b;*d=43;e=34;e=&a;f=0x321f; 

  *c 这是个什么东东,禁止

*d 说了是const, 禁止 

  e = &a 说了是const 禁止 

  const *f const =&a; 禁止 


55. 交换两个变量的值,不使用第三个变量。即a=3,b=5,交换之后a=5,b=3; 


  有两种解法, 一种用算术算法, 一种用^(异或) 

  a = a + b; 

  b = a - b; 

  a = a - b; 

  or 

  a = a^b;// 只能对int,char.. 

  b = a^b; 

  a = a^b; 

  or 

  a ^= b ^= a; 


56.c和c++中的struct有什么不同? 


  c和c++中struct的主要区别是c中的struct不可以含有成员函数,而c++中的struct可以。c++中struct和class的主要区别在于默认的存取权限不同,struct默认为public,而class默认为private。 


57.

[cpp]  view plain copy
  1. #include <stdio.h>   
  2.  #include <stdlib.h>   
  3.  void getmemory(char *p)   
  4.  {    
  5.   p=(char *) malloc(100);   
  6.   strcpy(p,"hello world");   
  7.  }    
  8.  int main( )   
  9.  {   
  10.   char *str=NULL;   
  11.   getmemory(str);   
  12.   printf("%s/n",str);   
  13.   free(str);   
  14.   return 0;   
  15.  }   
 

  程序崩溃,getmemory中的malloc 不能返回动态内存, free()对str操作很危险 


58.char szstr[10]; 

  strcpy(szstr,"0123456789"); 

  产生什么结果?为什么? 


  长度不一样,会造成非法的OS 


59. 列举几种进程的同步机制,并比较其优缺点。 

  原子操作 

  信号量机制 

  自旋锁 

  管程,会合,分布式系统 


60. 进程之间通信的途径 


  共享存储系统 

  消息传递系统 

  管道:以文件系统为基础 


61. 进程死锁的原因 

  资源竞争及进程推进顺序非法 


62. 死锁的4个必要条件 


  互斥、请求保持、不可剥夺、环路 


63.死锁的处理 


  鸵鸟策略、预防策略、避免策略、检测与解除死锁 


64. 操作系统中进程调度策略有哪几种? 


  FCFS(先来先服务),优先级,时间片轮转,多级反馈 


65. 类的静态成员和非静态成员有何区别? 


  类的静态成员每个类只有一个,非静态成员每个对象一个 


66.纯虚函数如何定义?使用时应注意什么? 


  virtual void f()=0; 

  是接口,子类必须要实现 


67. 数组和链表的区别 

数组:数据顺序存储,固定大小 
连表:数据可以随机存储,大小可动态改变

 

 

68. ISO的七层模型是什么?tcp/udp是属于哪一层?tcp/udp有何优缺点? 

  应用层 

  表示层 

  会话层 

  运输层 

  网络层 

  物理链路层 

  物理层 

  tcp /udp属于运输层 

  TCP 服务提供了数据流传输、可靠性、有效流控制、全双工操作和多路复用技术等。

与 TCP 不同, UDP 并不提供对 IP 协议的可靠机制、流控制以及错误恢复功能等。由于 UDP 比较简单, UDP 头包含很少的字节,比 TCP 负载消耗少。 

  tcp: 提供稳定的传输服务,有流量控制,缺点是包头大,冗余性不好 
  udp: 不提供稳定的服务,包头小,开销小

 

69. (void *)ptr 和 (*(void**))ptr的结果是否相同? 

其中ptr为同一个指针(void *)ptr 和 (*(void**))ptr值是相同的 


70. 

[cpp]  view plain copy
  1. int main()   
  2. {   
  3.     int x=3;   
  4.     printf("%d",x);   
  5.     return 1;   
  6. }  
 
  问函数既然不会被其它函数调用,为什么要返回1? 

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


71. 要对绝对地址0x100000赋值,我们可以用(unsigned int*)0x100000 = 1234;那么要是想让程序跳转到绝对地址是0x100000去执行,应该怎么做? 

  *((void (*)( ))0x100000 ) ( ); 

  首先要将0x100000强制转换成函数指针,即: 

  (void (*)())0x100000 

  然后再调用它: 

  *((void (*)())0x100000)(); 

  用typedef可以看得更直观些: 

  typedef void(*)() voidFuncPtr; 

  *((voidFuncPtr)0x100000)(); 


72.已知一个数组table,用一个宏定义,求出数据的元素个数 

  #define NTBL 

  #define NTBL (sizeof(table)/sizeof(table[0])) 


73. 线程与进程的区别和联系? 线程是否具有相同的堆栈? dll是否有独立的堆栈? 

  进程是死的,只是一些资源的集合,真正的程序执行都是线程来完成的,程序启动的时候操作系统就帮你创建了一个主线程。 

  每个线程有自己的堆栈。DLL中有没有独立的堆栈? 

  这个问题不好回答,或者说这个问题本身是否有问题。因为DLL中的代码是被某些线程所执行,只有线程拥有堆栈,如果DLL中的代码是EXE中的线程所调用,那么这个时候是不是说这个DLL没有自己独立的堆栈?如果DLL中的代码是由DLL自己创建的线程所执行,那么是不是说DLL有独立的堆栈? 

  以上讲的是堆栈,如果对于堆来说,每个DLL有自己的堆,所以如果是从DLL中动态分配的内存,最好是从DLL中删除,如果你从DLL中分配内存,然后在EXE中,或者另外一个DLL中删除,很有可能导致程序崩溃。 


74.unsigned short A = 10; 

  printf("~A = %u/n", ~A); 

  char c=128; 

  printf("c=%d/n",c); 


输出多少?并分析过程 

  第一题,~A =0xfffffff5,int值 为-11,但输出的是uint。所以输出4294967285 


  第二题,c=0x10,输出的是int,最高位为1,是负数,所以它的值就是0x00的补码就是128,所以输出-128。 

这两道题都是在考察二进制向int或uint转换时的最高位处理。 


75.分析下面的程序: 

[cpp]  view plain copy
  1. void GetMemory(char **p,int num)   
  2. {   
  3.     *p=(char *)malloc(num);   
  4. }    
  5. int main()   
  6. {   
  7.     char *str=NULL;   
  8.     GetMemory(&str,100);   
  9.     strcpy(str,"hello");   
  10.     free(str);   
  11.     if(str!=NULL)   
  12.     {   
  13.         strcpy(str,"world");   
  14.     }    
  15.     printf("/n str is %s",str);  
  16.     getchar();   
  17. }  
  

  问输出结果是什么? 

  输出str is world。 

  free 只是释放的str指向的内存空间,它本身的值还是存在的.所以free之后,有一个好的习惯就是将str=NULL. 

此时str指向空间的内存已被回收,如果输出语句之前还存在分配空间的操作的话,这段存储空间是可能被重新分配给其他变量的, 

尽管这段程序确实是存在大大的问题(上面各位已经说得很清楚了),但是通常会打印出world来。 

这是因为,进程中的内存管理一般不是由操作系统完成的,而是由库函数自己完成的。 

  当你malloc一块内存的时候,管理库向操作系统申请一块空间(可能会比你申请的大一些),然后在这块空间中记录一些管理信息(一般是在你申请的内存前面一点),并将可用内存的地址返回。但是释放内存的时候,管理库通常都不会将内存还给操作系统,因此你是可以继续访问这块地址的。 

  char a[10],strlen(a)为什么等于15?运行的结果

 

 

76.

[cpp]  view plain copy
  1. #include "stdio.h"   
  2. #include "string.h"   
  3. void main()   
  4. {   
  5.     char aa[10];   
  6.     printf("%d",strlen(aa));   
  7. }  
 
  sizeof()和初不初始化,没有关系; 

  strlen()和初始化有关。 


77. char (*str)[20]; 

  char *str[20];  


78. long a=0x801010; 

  a+5=? 

  0x801010用二进制表示为:“1000 0000 0001 0000 0001 0000”,十进制的值为8392720,再加上5就是8392725罗 


79. 给定结构 

  struct A 

  { 

 char t:4; 

 char k:4; 

 unsigned short i:8; 

 unsigned long m; 

  }; 


  问sizeof(A) = ? 

   

  给定结构 

  struct A 

  { 

 char t:4; 4位 

 char k:4; 4位 

 unsigned short i:8; 8位  

  unsigned long m; // 偏移2字节保证4字节对齐 

  }; // 共8字节 


80. 下面的函数实现在一个数上加一个数,有什么错误?请改正。 

  int add_n ( int n ) 

  { 

  static int i = 100; 

  i += n; 

  return i; 

  } 


  当你第二次调用时得不到正确的结果,难道你写个函数就是为了调用一次?问题就出在 static上? 


81.分析一下 

[cpp]  view plain copy
  1. #include<iostream.h>   
  2. #include <string.h>   
  3. #include <malloc.h>   
  4. #include <stdio.h>   
  5. #include <stdlib.h>   
  6. #include <memory.h>   
  7. typedef struct AA   
  8. {   
  9.       int b1:5;   
  10.       int b2:2;   
  11. }AA;   
  12. void main()   
  13. {   
  14.     AA aa;   
  15.     char cc[100];   
  16.     strcpy(cc,"0123456789abcdefghijklmnopqrstuvwxyz");  
  17.     memcpy(&aa,cc,sizeof(AA));   
  18.     cout << aa.b1 <<endl;   
  19.     cout << aa.b2 <<endl;   
  20. }   
 


  答案是 -16和1 

  首先sizeof(AA)的大小为4,b1和b2分别占5bit和2bit.经过strcpy和memcpy后,aa的4个字节所存放的值是: 0,1,2,3的ASC码,即00110000,00110001,00110010,00110011所以,最后一步:显示的是这4个字节的前5位,和之后的2位分别为:10000,和01,因为int是有正负之分   

  所以:答案是-16和1

 

 

82.求函数返回值,输入x=9999; 

[cpp]  view plain copy
  1. int func ( x )   
  2.   {   
  3.   int countx = 0;   
  4.    while ( x )   
  5.    {   
  6.    countx ++;   
  7.   x = x&(x-1);   
  8.    }   
  9.    return countx;   
  10.   }  
 


  结果呢? 


  知道了这是统计9999的二进制数值中有多少个1的函数,且有9999=9×1024+512+256+15 


  9×1024中含有1的个数为2; 

  512中含有1的个数为1; 

  256中含有1的个数为1; 

  15中含有1的个数为4;

故共有1的个数为8,结果为8。 

  1000 - 1 = 0111,正好是原数取反。这就是原理。 

  用这种方法来求1的个数是很效率很高的。 

  不必去一个一个地移位。循环次数最少。 


  int a,b,c 请写函数实现C=a+b ,不可以改变数据类型,如将c改为long int,关键是如何处理溢出问题 

  bool add (int a, int b,int *c) 

  { 
   *c=a+b; 

   return (a>0 && b>0 &&(*c<a || *c<b) || (a<0 && b<0 &&(*c>a || *c>b))); 
  }

 

 

83. 分析: 

  struct bit 

  {  

   int a:3; 

  int b:2; 

  int c:3; 

  }; 

  int main() 

  { 

   bit s; 

  char *c=(char*)&s; 

  cout<<sizeof(bit)<<endl; 

   *c=0x99; 

  cout << s.a <<endl <<s.b<<endl<<s.c<<endl; 

  int a=-1; 

  printf("%x",a); 

   return 0; 

  } 


  输出为什么是 

  4 

  1 

  -1 

  -4 

  ffffffff 


因为0x99在内存中表示为 100 11 001 , a = 001, b = 11, c = 100。当c为有符合数时, c = 100, 最高1为表示c为负数,负数在计算机用补码表示,所以c = -4;同理 b = -1;当c为有符合数时, c = 100,即 c = 4,同理 b = 3。

 

 

84.位域:有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态,用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字节的二进制位域来表示。一、位域的定义和位域变量的说明位域定义与结构定义相仿,其形式为:

struct 位域结构名 { 位域列表 }; 其中位域列表的形式为:类型说明符位域名:位域长度 
例如:  
  struct bs  

  {  

   int a:8;  

   int b:2;  

   int c:6;  

  };  


  位域变量的说明与结构变量说明的方式相同。可采用先定义后说明,同时定义说明或者直接说明这三种方式。例如:  

  struct bs  

  {  

   int a:8;  

   int b:2;  

   int c:6;  

  }data;  


  说明data为bs变量,共占两个字节。其中位域a占8位,位域b占2位,位域c占6位。对于位域的定义尚有以下几点说明:  


  一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。例如:  

   

  struct bs  

  {  

   unsigned a:4  

   unsigned :0  

   unsigned b:4  

   unsigned c:4  

  }  


  在这个位域定义中,a占第一字节的4位,后4位填0表示不使用,b从第二字节开始,占用4位,c占用4位。  


  由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过8位二进位。  


  位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。例如:  


  struct k  

  {  

   int a:1  

   int :2  

   int b:3  

   int c:2  

  };  


  从以上分析可以看出,位域在本质上就是一种结构类型,不过其成员是按二进位分配的。  


  位域的使用位域的使用和结构成员的使用相同,其一般形式为:位域变量名?位域名位域允许用各种格式输出。  


  main() 

  {  

   struct bs  

   {  

    unsigned a:1;  

    unsigned b:3;  

    unsigned c:4;  

   } 

   bit,*pbit;  

   bit.a=1;  

   bit.b=7;  

   bit.c=15;  

   pri

 

 

85. 改错: 
  #include <stdio.h> 

  int main(void) 

  { 

    int **p; 

  int arr[100]; 

    p = &arr; 

    return 0; 

  } 

  解答:搞错了,是指针类型不同,int **p; //二级指针&arr; //得到的是指向第一维为100的数组的指针 

  #include <stdio.h> 

  int main(void) 

  { 

   int **p, *q; 

   int arr[100]; 

   q = arr; 

   p = &q; 

   return 0; 

  }


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值