常考到的笔试题总结

 

1. 在C++程序中调用被C编译器编译后的函数,为什么要加extern“C”?
C++语言支持函数重载,C语言不支持函数重载。函数被C++编译后,在库中的名字与C语言的不同。假设某函数的原型为:void foo(int x,int y)。该函数被C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。
C++提供了C连接交换指定符号extern“C”解决名字匹配的问题。
2. ifdefine/define/endif 的作用是防止该头文件被重复使用。
3. 在main()函数结束后要执行一段程序怎么处理:
如果你需要加入一段在 main 退出后执行的代码,可以使用 atexit() 函数,注册一个函数。  // 该函数在 main 函数结束之后执行,并且是从后向前执行,有点类、、 // 似与析构函数
 
语法:  
  #include   <stdlib.h>  
int   atexit(void   (*function)(void));  
 void fn1(void),fn2(void),fn3(void),fn4(void);  
 int   main(   void   )  
 {     atexit(   fn1   );  
        atexit(   fn2   );  
        atexit(   fn3   );  
        atexit(   fn4   );  
        printf(   "This   is   executed   first./n"   );      
}
   void   fn1()  
 {         printf(   "next./n"   );  
 }   
 void   fn2()  
 {           printf(   "executed   "   );  
 } 
 void   fn3()  
 {      printf(   "is   "   );  
 }  
 void   fn4()  
 {          printf(   "This   "   );  
 } 
4. 该代码的执行结果为11.
#define s(x) (x*x)
void main()
{        int a,b=3;
         a=s(b+2);
         cout<<a<<endl;
}
解析:在该代码中,在宏定义中执行过程为(b+2*b+2); 如果将宏定义改为:#define s(x) ((x)* (x)),则输出为25。
 
 5.  在C程序中,.的用法主要有定义常量、修饰函数参数、修饰函数返回值等3个用处。在C++程序中,它还可以修饰函数的定义体,定义类中某个成员函数为恒态函数,即不改变类中的数据成员。
答案:const的用法:(1)可以定义const常量。(2)const可以修饰函数的参数和返问值,甚至函数的定义体。被const修饰的东西都受到强制保护,可以预防意外的变动,能提高程序的健壮性。
6.  const与define的区别:
   答案:C++语言可以用const定义常量,也可以用#define定义常量,但是前者比后者有更多的优点:
   (1)const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换中可能会产生意想不到的错误(边际效应)。
   (2)有些集成化的调试上.具可以对const常量进行调试,但是不能对宏常量进行调试。在C++程序中只使用。const常量而不使用宏常量,即const常量完全取代宏常量。
7#include <stdio.h>
#include <string>
 
using namespace std;
struct{
            short a1;
            short a2;
            short a3;
}A;
struct{
            long al;
            short a2;
}B;
 
int main()
{
char* ssl="0123456789";
char ss2[]="0123456789";
char ss3[100]="0123456789";
int ss4[100];
char q1[]="abc";
char q2[]="a/n";
char * q3="a/n";
char *str1=(char*)malloc(100);
 
void *str2=(void*)malloc(100);
 
cout<<sizeof(ssl)<<" ";
cout<<sizeof(ss2)<<" ";
cout<<sizeof(ss3)<<" ";
cout<<sizeof(ss4)<<" ";
cout<<sizeof(q1)<<" ";
cout<<sizeof(q2)<<" ";
cout<<sizeof(q3)<<" ";
cout<<sizeof(A)<<" ";
cout<<sizeof(B)<<" ";
cout<<sizeof(str1)<<" ";
cout<<sizeof(str2)<<" ";
 
return 0;
}
输出结果:4 11 100 400 4 3 4 6 8 4 4
注:对于类和结构,即便其为空的类或结果,编译器也要给其分配1个内存空间。空类或结构的对象也是占1个内存空间。
解析:ss1是一个字符指针,指针的大小是一个定值,就是4,所以sizeof(ss1)是4;
      ss2是一个字符数组,这个数组最初未定大小,由具体填充值来定。填充值是"0123456789”。 1个字符所占空间是1位,10个就是10位,再加上隐含的”/0”,所以一共是11位。
     ss3也是一个字符数组,这个数组开始预分配100,所以它的大小一共是100位。
     ss4也是一个整型数组,这个数组开始预分配100,但每个整型变量所占空间是4,所以它的大小一共是400位。
    q1与ss2类似,所以是4
    q2里面有一个”/n”,”/n”算做一位,所以它的空间大小是3;
q3是一个字符指针,指针的大小是一个定值,就是4,所以sizeof(q3)是4。
    A和B是两个结构体。在默认情况下,为了方便对结构体内元素的访问和管理,当结构体内的元素的长度都小于处理器的位数的时候,便以结构体里面最长的数据元素为对齐单位,也就是说,结构体的长度一定是最长的数据元素的整数倍。如果结构体内存在长度大于处理器位数的元素,那么就以处理器的位数为对齐单位。但是结构体内类型相同的连续元素将在连续的空间内,和数组一样。
    结构体A中有3个short类型变量,各自以2字节对齐,结构体对齐参数按默认的8字节对齐,则a1 , a2 , a3都取2字节对齐,sizeof(A)为6,其也是2的整数倍。B中a1为4字节对齐,a2为2字节对齐,结构体默认对齐参数为8,则al取4字节对齐,a2取2字节对齐;结构体大小6字节,6不为4的整数倍,补空字节,增到8时,符合所有条件,则sizeof(B)为8。
    CPU的优化规则大致原则是这样的:对于n字节的元素(n=2,4,8,….),它的首地址能被n整除,才一能获得最好的性能。设计编译器的时候可以遵循这个原则:对于每一个变量,可以从当前位置向后找到第一个满足这个条件的地址作为首地址。例子比较特殊,因为即便采用这个原则,得到的结果也应该为6(long的首地址偏移量0000 , short首地址偏移量0004,都符合要求)。但是结构体一般会面临数组分配的问题。编译器为了优化这种情况,干脆把它的大小设为8,这样就没有麻烦了,否则的话,会出现单个结构体的大小为6,而大小为n的结构体数组大小却为8x (n-1)+6的尴尬局面。IBM出这道题并不在于考查理解语言本身和编译器,而在于应聘者对计算机底层机制的理解和设计程序的原则。也就是说,如果让你设计编译器,你将怎样解决内存对齐的问题。
 
7.     sizeof与strlen两者的区别如下:
      (1)sizeof操作符的结果类型是size_t,它在头文件中的typedef为unsigned int类型。该类型保证能容纳实现所建立的最大对象的字节大小。
      (2) sizeof是算符,strlen是函数。
      (3)sizeof可以用类型做参数,strlen只能用char*做参数,且必须是以“/n”结尾的。sizeof还可以用函数做参数,比如:
Short f();
Printf(“%d/n”,sizeof(f()));
输出的结果是sizeof(short) 即2。
      (4)数组做sizeof的参数不退化,传递给strlen就退化为指针。
      (5)大部分编译程序在编译的时候就把sizeof计算过了,是类型或是变量的长度。这就是sizeof(x)可以用来定义数组维数的原因:
Char str[20]=”0123456789”;
Int a=strlen(str);
Int b=sizeof(str);
      (6)strlen的结果要在运行的时候才能计算出来,用来计算字符串的长度,而不是类型占内存的大小。
      (7)sizeof后如果是类型必须加括号,如果是变量名可以不加括号。这是因为}i}eof是个操作符而不是个函数。
      (8)当使用了一个结构类型或变量时,sizeof返回实际的大小。当使用一静态的空间数组时,sizeof返回全部数组的尺寸。sizeof操作符不能返同被动态分配的数组或外部的数组的尺寸。
      (9)数组作为参数传给函数时传的是指针而不是数组,传递的是数组的首地址,如:fun(char[8]),fun(char[])都等价于fun(chr *)。在C++里传递数组永远都是传递指向数组首元素的指针,编译器不知道数组的大小。如果想在函数内知道数组的大小,需要这样做:进入函数后用memcpy将数组拷贝出来,长度由另一个形参传进去。代码如下;
fun (unsigned char *p1,int len)
{
 unsigned char* huf=new unsigned char[len+1]
 memcpy (buf,p1,len);
}
      (10)计算结构变量的大小就必须讨论数据对齐问题。为了使CPU存取的速度最快(这同CPU取数操作有关,详细的介绍可以参考一些计算机原理方面的书),C++在处理数据时经常把结构变量中的成员的大小按照4或8的倍数计算,这就叫数据对齐(data alignment )。这样做可能会浪费一些内存,但在理论上CPU速度快了。当然,这样的设置会在读写一些别的应用程序生成的数据文件或交换数据时带来不便。MS VC++中的对齐设定,有时候sizeof得到的与实际不等。一般在VC++中加上#pragma pack(n)的设定即可。或者如果要按字节存储,而不进行数据对齐,可以在options对话框中修改Advanced Compiler选项卡中的“Data Alignment”为按字节对齐。
      (11)sizeof操作符不能用于函数类型、不完全类型或位字段。不完全类型指具有未知存储大小数据的数据类型,如未知存储大小的数组类型、未知内容的结构或联合类型、void类型等。
 
8. 写出下面代码的输出结果:
char var[10];
int test(char var[])
 {return sizeof(var);}
解:Sizeof(var)的大小为4,因为var[]等价于*var,已经退化为一个指针。
 
9.       空类所占空间为1,单一继承的空类空间也为1,多重继承的空类空间还是1。但是虚继承涉及到虚表(虚指针),所以所占的空间为4。
10.   内联函数和宏的差别是什么?
    答案:内联函数和普通函数相比可以加快程序运行的速度,因为不需要中断调用,在编译的时候内联函数一可以直接被镶嵌到目标代码中。而宏只是个简单的替换。
    内联函数要做参数类型检查,这是内联函数跟宏相比的优势。
inline是指嵌人代码,就是在调用函数的地方不是跳转,而是把代码直接写到那里去。对于短小的代码来说,inline可以带来一定的效率提升,而且和C时代的宏函数相比,inline更安全可靠。可是这个是以增加空间消耗为代价的。至于是否需要inline函数,就需要根据你的实际情况取舍了。
    inline一般只用于如下情况:
   (I)一个函数不断被重复调用。
   (2)函数只有简单的几行,且函数内不包含for, vvhirte, switch语句。
   一般来说,我们写刁程序没有必要定义成iniine,但是如果要完成一个工程项目,当一个简单函数被调用多次时,则应该考虑用inline
    宏在C语言里极其重要,而在C++里用得就少多了。关于宏的第一规则是:绝不应该去使用它,除非你不得不这样做。几乎每个宏都表明了程序设计语言里或者程序里或者程序员的一个缺陷,因为它将在编译器看到程序的正文之前重新摆布这些正文。宏也是许多程序设计工具的主要麻烦。所以,如果你使用了宏,你就应该准备着只能从各种工具(如排错系统、交叉引用系统、轮廓程序等)中得到较少的服务。
 宏是在代码处不加任何验证的简单替代,而内联函数是将代码直接插入调用处,而减少了普通函数调用时的资源消耗。
 宏不是函数,只是在编译前(编译顶处理阶段)将程序中有关字符串替换成宏体。
 inline函数是函数,但在编译中不单独产生代码,而是将有关代码嵌入到调用处。
  inline fac(float i){return i*i},//没有写返回值的
 printf ("bb=%d”,facet(8)); //调用时就是执行printf ("bb=%d”,8*8);
 
11.   指针和引用的区别:
    答案:(1)非空区别。在任何情况厂都不能使用指向空值的引用。一个引用必须总是指向某些对象。因此如果你使用·个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针要高。
    (2)合法性区别。在使用引用之前不需要测试它的合法性。相反,指针则应该总是被测试,防止其为空。
(3)可修改区别。指针与引用的另一个重要的不同是指针可以被重新赋值以指向另个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变,但是指定的对象其内容可以改变。
 (4)应用区别。总的来说,在以下情况「你应该使用指针:冷是你考虑到存在.不指向任何对象的可能(在这种情况下,你能够设置指针为空),一是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。
 
12. 类的this指针有以下特点:
(1) this只能在成员函数中使用。
全局函数、静态函数都不能使用this。
实际上,成员函数默认第一个参数为T* const this。 如 class A{public: int func(int p){}};
其中,func的原型在编译器看来应该是:int func(A* canst this, int p);
 (2)由此可见,this在成员函数的开始前构造,在成员的结束后清除。
    这个生命周期同任何一个函数的参数是一样的,没有任何区别。
    当调用一个类的成员函数时,编译器将类的指针作为函数的this参数传递进去。如:
        A a;
        a.func(10);
        此处,编译器将会编译成:
A::funs(&a, 10);
看起来和静态函数没差别,不过,区别还是有的.编译器通常会对this指针做一些优化,因此,this指针的传递效率比较高—如VC通常是通过ecx寄存器传递this参数的。
(3)几个this指针的易混问题。
 1) this指针是什么时候创建的?
   this在成员函数的开始执行前构造,在成员的执行结束后清除。
   但是如果class或者struct里面没有方法的话,它们是没有构造函数的,只能当做C的struct使用。采用TYPE xx的方式定义的话,在栈里分配内存,这时候this指针的值就是这块内存的地址。采用new的方式创建对象的话,在堆里分配内存,new操作符通过ecx返回分配的地址,然后设置给指针变量。之后去调用构造函数(如果有构造函数的话),这时将这个内存决的地址传给ecx,之后构造函数里面怎么处理请看上面的回答。
 2) this指针存放在何处?堆、栈、全局变量,还是其他?
    this指针会因编译器不同而有不同的放置位置。可能是栈,也可能是寄存器,甚至全局变量。在汇编级别里面,一个值只会以3种形式出现:立即数、寄存器值和内存变量值。不是存放在寄存器就是存放在内存中,它们并不是和高级语言变量对应的。
 3 ) this指针是如何传递给类中的函数的?绑定?还是在函数参数的首参数就是this指针?那么,this指针又是如何找到“类实例后函数”的?
    大多数编译器通过ecx寄存器传递this指针。事实上,这也是一个潜规则。一般来说,不同编译器都会遵从一致的传参规则,否则不同编译器产生的obj就无法匹配了。
    在call之前,编译器会把对应的对象地址放到ecx中。this是通过函数参数的首参数来传递的。this指针在调用之前生成,至于“类实例后函数”,没有这个说法。类在实例化时,只分配类中的变量空间,并没有为函数分配空间。自从类的函数定义完成后,它就在那儿,不会跑的。
    4 ) this指针是如何访问类中的变量的?
   如果不是类,而是结构的话,那么,如何通过结构指针来访问结构中的变量呢?如果你明白这一点的话,就很容易理解这个问题了。
    在C++中,类和结构是只有一个区别的:类的成员默认是private,而结构是public。
    this是类的指针,如果换成结构,那this就是结构的指针了。
    5)我们只有获得一个对象后,才能通过对象使用this指针。如果我们知道一个对象this指针的位置,可以直接使用吗?
    this指针只有在成员函数中才有定义。因此,你获得一个对象后,也不能通过对象使用this指针。所以,我们无法知道一个对象的this指针的位置(只有在成员函数里才有this指针的位置))当然,在成员函数里,你是可以知道this指针的位置的(可以通过&this获得),也可以直接使用它。
   6)每个类编译后,是否创建一个类中函数表保存函数指针。以便用来调用函数?
 普通的类函数(不论是成员函数,还是静态函数)都不会创建一个函数表来保存函数指针。只有虚函数才会被放到函数表中。
    但是,即使是虚函数,如果编译器能明确知道调用的是哪个函数,编译器就不会通过函数表中的指针来间接调用,而是会直接调用该函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值