C/C++ 笔试、面试题目总结一

1.    指出以下变量数据存储位置

全局变量int(*g_pFun)(int);g_pFun=myFunction;g_pFun存储的位置      (A ) 为全局的函数指针指向空间的位置( B) 所有函数代码位于TEXT段

函数内部变量 static int nCount;                                                              ( A) 静态变量总是在DATA段或BSS段中

函数内部变量 char p[]=”AAA”;  p 指向空间的位置                                   ( C)  局域变量的静态数组,空间在Stack中

函数内部变量 char *p=”AAA”;  p 指向空间的位置                                    ( E) ,”AAA”为一字符常量空间,不同编译器有不同处理方法,大部分保存在TEXT(代码段中),也有编译的rodata段中

函数内部变量 char *p=new char; p的位置(C ) 指向空间的位置(D ) 所有malloc空间来自于heap(堆)(在堆上分配,指针还是在栈中,指针指向堆)

A.    数据段

B.    代码段

C.    堆栈

D.    堆

E.    不一定, 视情况而定


2.    以下程序的输出结果为 ( )

1.
#include <iostream>
using namespace std;
int main( )
{
    int num[5]={1,2,3,4,5};
    cout <<*((int *)(&num+1)-1) <<endl;
    return 0;
}

A. 1        B.2        C. 3        D. 4       E. 5       F. 0        G. 未初始化内存,无法确定

2.
#include <iostream>
using namespace std;
int main( )
{
    int num[5]={1,2,3,4,5};
    cout <<*((int *)((&num+1)-1)) <<endl;
    return 0;
}

A. 1         B.2        C. 3        D. 4         E. 5         F. 0        G. 未初始化内存,无法确定

在C语言中,一维数组名表示数组的首地址,而且是一个指针.如上例num,

对&num,表示指针的指针.意味着这里强制转换为二维数组指针.

这样 &num+1 等同于 num[5][1],为代码空间. (&num+1)-1表示 num[4][0].即num[4].所以这里答案是E.

 

扩展题目:

*((int *)(num+1)-1)   的值是多少?   A

Num是首指针,num+1是第二个元素指针,-1后又变成首指针.所以这里是答案是num[0]即,A.1


3.    以下哪些是程序间可靠的通讯方式( C ),哪些可以用于跨主机通讯( C,D ,F).Windows命名管道跨机器也可跨机器.

A. 信号         B. 管道               C. TCP          D. UDP         E. PIPE         F,.串口I/O


4.判断类的大小

1.考虑字节对齐,虚函数指针大小
class a
{
public:
    virtual  void  funa( );
    virtual  void  funb( );
    void  fun( );
    static  void  fund( );
    static  int  si;
private:
    int  i;
    char  c;
};

问: 在32位编译器默认情况下,sizeof(a)等于( )字节?

A. 28             B. 25      C.24          D. 20           E. 16       F.12             G. 8

这里需要考虑三个问题,一是虚函数表vtable的入口表地址,二是字节对齐.三 ,静态成员是所有对象共享,不计入sizeof空间.

在大部分C++的实现中,带有虚函数的类的前4个BYTE是虚函数vtable表的这个类入口地址.所以sizeof必须要加入这个4个byte的长度,除此外,类的sizoef()为所有数据成员总的sizeof之和,这里是int i,和char c.其中char c被字节对齐为4.这样总长度为12

32位Windows 系统或Linux系统下

2考虑字节对齐
struct
{
    char  a;  //1字节
    char  b;  //1字节
    char  c;   //1字节
}A;
struct
{
    short  a;  //2字节
    short  b;  //2字节
    short  c;  //2字节
}B;
struct
{
    short  a;   //2字节
    long  b;    //8字节
    char  c;    //1字节
}C;

printf(“%d,%d,%d\n”,sizeof(A),sizeof(B),sizeof(C)); 的执行结果为: ( )

A. 3,6,7         B. 3,6,8         C. 4,8,12              D. 3,6,12      E. 4,6,7         F. 4,8,9

C语法的字节对齐规则有两种情况要字节对齐, 在VC++,gcc测试都是如此

1)    对同一个数据类型(short,int,long)发生了跨段分布,(在32CPU里,即一个数据类型分布在两个段中)才会发生字节对齐.

2)    数据类型的首部和尾部必须有其一是与4对齐.而且违反上一规则.

l  Sizeof(A),sizeof(B)虽然总字节数不能被4整除.但刚好所有数据平均分布在以4为单位的各个段中.所以无需字节对齐,所以结果是 3和6

l  struct {char a;char b;char c;char d;char e;}F; 的sizoef(F)是等于5.

l  用以下实例更加清楚

struct {
    char a[20];
    short b;
}A;
struct {
    char a[21];
    short b;
}B;
Sizeof(A)=22,sizoef(B)=24.

因为前者没有发生跨段分布.后者,如果不字节对齐.a[21]占用最后一个段的首地址,b无法作到与首部与尾部与4对齐,只能在a[21]与b之间加入一个byte,使用b的尾部与4对齐.


 迷糊了。。。http://blog.csdn.net/21aspnet/article/details/6729724链接地址,有空仔细读。。。

5.    依据程序,以下选择中那个是对的? (  )

class A
{
int  m_nA;
};
class B
{
int   m_nB;
};
class C:public A,public B
{
int  m_nC;
};
void f (void)
{
    C* pC=new C;
    B* pB=dynamic_cast<B*>(pC);
    A* pA=dynamic_cast<A*>(pC);
}

A. pC= =pB,(int)pC= =(int)B                                  B. pC= =pB,(int)pC!=(int)pB

C. pC!=pB,(int)pC= =(int)pB                                  D. pC!=pB,(int)pC!=(int)pB
可以打印出来地址看一下结果:相当于下面的程序:

#include <stdio.h>
class A
{
public:
    int  m_nA;
};

class B
{
public:
    int   m_nB;
};

class C:public A,public B
{
public:
    int  m_nC;
};
void f (void)
{
    C* pC=new C;
    B* pB=dynamic_cast<B*>(pC);
    A* pA=dynamic_cast<A*>(pC);
}
void f1 (void)
{
    C* pC=new C;
    pC->m_nA = 1;
    pC->m_nB = 2;
    pC->m_nC = 3;
    B* pB=dynamic_cast<B*>(pC);
    A* pA=dynamic_cast<A*>(pC);
    printf("A=%x,B=%x,C=%x,iA=%d,iB=%d,iC=%d\n",pA,pB,pC,(int)pA,(int)pB,(int)pC);
}
int  main()
{
    f1();
    return 0;
}
结果为:


可以看出来  :pA和pC的地址是相同的。  (int)pA与(int)pC的值也是相同的。原因是: C从,A,B继承下来,而pB强制转换后,只能取到C中B的部分.所以pB在pC向后偏移4个BYTE,(即m_nA)的空间

6. 以下程序的输出为______________

#include<iostream>
using std::cout;
class A
{
public:
    void f(void)
    {
        cout<< "A::f" <<' ';
    }
    virtual void g(void)
    {
        cout <<"A::g" << ' ';
    }
};
class B : public A
{
public:
    void f(void)
    {
        cout << "B :: f " << ' ';
    }

    void g(void)
    {
        cout << "B:: g " << ' ';
    }
};
int main()
{
    A* pA =new B;
    pA->f();
    pA->g();
    B* pB = (B*)pA;
    pB->f();
    pB->g();
}

输出结果: A::f B:: g  B :: f  B:: g   主要考察了多态的使用:

多态中虚函数调用.

1.f()为非虚函数,这样强制转换后,执行本类的同名函数.

2.G()为虚函数,指针总是执行虚函数,这就是多态.    

派生类指针不可以指向 基类对象,如果 B* pB = new  B;则会出现错误的。多态的原理就是基类的指针或者引用实际中可以指向派生类对象。

7.以下为window NT 下32 位C++程序,请填写如下值

class myclass
{
int a ;
int b;
};
char *p = “hello”;
char str[] = “world”;
myclass classes[2];
void *p2= malloc(100);

sizeof(p)=_4__   (计算的是指针的大小   32位所以是4个字节)

sizeof(str)=_6_  (包括字符串结束符标志)

sizeof(classes)=_16__

sizeof(p2)=_4___   (计算的是指针的大小   32位所以是4个字节)

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


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

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

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

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


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

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

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

从实现原理上来说:

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

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

10. 多态的作用?


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

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


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

好处:在内存中不产生被返回值的副本;(注意:正是因为这点原因,所以返回一个局部变量的引用是不可取的。因为随着该局部变量生存期的结束,相应的引用也会失效,产生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分配的对象的引用、返回一个静态对象引用。根据前面提到的引用作为返回值的三个规则,第2、3两个方案都被否决了。静态对象的引用又因为((a+b) == (c+d))会永远为true而导致错误。所以可选的只剩下返回一个对象了。


12.static有什么用途?(请至少说明两种)

1)限制变量的作用域
2)限制变量的存储域
对于 1)来说, 先来介绍它的第一条也是最重要的一条:隐藏。

  当我们同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。为理解这句话,我们来写一个例子:

       我们要同时编译两个源文件和一个Makefile,一个是a.c,另一个是main.c.

a.c:

#include <stdio.h>
char a = ’A’; //global variable
void msg()
{ 
	printf(”Hello\n”);
}
main.c
#include <stdio.h>
int main(void)
{
	extern char a; // extern variable must be declared before use 
	printf(”%c “, a);
	(void)msg();
	return 0;
}

下面是Makefile的内容:CC =gcc

  SRC := $(shell ls *.c)

  OBJS := $(patsubst %.c, %.o, $(SRC))

  TARGET := Main

  $(TARGET): $(OBJS)

  $(CC) $(LIBS) -o $@ $^

  %.o: %.c $(CC) $(CFLAGS) -c -o $@ $<

  clean:rm -f $(TARGET) *.o

      程序的运行结果是:A Hello

      你可能会问:为什么在a.c中定义的全局变量a和函数msg能在main.c中使用?前面说过,所有未加static前缀的全局变量和函数都具有全 局可见性,其它的源文件也能访问。此例中,a是全局变量,msg是函数,并且都没有加static前缀,因此对于另外的源文件main.c是可见的。

  如果加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static,main.c就看不到它们了。利用这一特性可以在不同的 文件中定义同名函数和同名变量,而不必担心命名冲突。Static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏,而对于变 量,static还有下面两个作用。

       2)static的第二个作用是保持变量内容的持久。用static声明的变量是存储在静态存储区,在整个程序运行期间都会存在,包括在函数中声明的static变量。

  存储在静态数据区的变量会在程序刚开始运行时就完成初始化,也是唯一的一次初始化。共有两种变量存储在静态存储区:全局变量和static变量,只 不过和全局变量比起来,static可以控制变量的可见范围,说到底static还是用来隐藏的

     static的第三个作用是默认初始化为0.其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。

  在静态数据区,内存中所有的字节默认值都是0×00,某些时候这一特点可以减少程序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有 元素都置0,然后把不是0的几个元素赋值。如果定义成静态的,就省去了一开始置0的操作。再比如要把一个字符数组当字符串来用,但又觉得每次在字符数组末 尾加‘\0’太麻烦。如果把字符串定义成静态的,就省去了这个麻烦,因为那里本来就是‘\0’。

  

  最后对static的三条作用做一句话总结。首先static的最主要功能是隐藏,其次因为static变量存放在静态存储区,所以它具备持久性和默认值0.

13.static有什么用途?(请至少说明两种)

(一)由C/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack): 由编译器自动分配释放,存放函数的参数值,局部变量等。其操作方式类似于数据结构中的栈。
2、堆区(heap): 一般由程序员分配释放(malloc/free、new/delete), 若程序员不释放,程序结束时可能由操作系统回收。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表。
3、全局区(static): 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束后由系统释放。
4、文字常量区: 常量字符串就是放在这里的, 程序结束后由系统释放。
5、程序代码区: 存放函数体的二进制代码。
看下代码来区分以上几种区。
Example:
int a = 0; // 全局初始化区
char *p1; // 全局未初始化区
main()
{
    int a; // 栈区
    char s[] = “abc”; // 栈区
    char *p2; // 栈区
    char *p3 = “123456″; // 123456\0在常量区,p3在栈上。
    static int c =0; // 全局(静态)初始化区
    p1 = (char *)malloc(10);
    p2 = (char *)malloc(20); // 分配得来得10和20字节的区域就在堆区。
    strcpy(p1, “123456″); // 123456\0放在常量区,编译器可能会将它与p3所指向的”123456″优化成一个地方。
}

(二、)堆和栈的理论知识
2.1 申请方式
栈: 由系统自动分配。 例如,声明在函数中一个局部变量 int a; 系统自动在栈中为a开辟空间
堆: 需要程序员自己申请,并指明大小,在c中malloc函数:如p1 = (char *)malloc(10); 在C++中用new运算符 如p2 = (char *)malloc(10);
但是注意局部变量p1、p2本身是在栈中的,但是他们指向的申请到的内存是在堆区,这点要明确!
2.2 申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时, 会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
2.3 申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
2.4 申请效率的比较:
栈:由系统自动分配,速度较快。但程序员是无法控制的。
堆:是由malloc/new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。

2.5 堆和栈中的存储内容
栈: 在函数调用时,第一个进栈的是主函数中的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

2.6 存取效率的比较
char s1[] = “aaaaaaaaaaaaaaa”;
char *s2 = “bbbbbbbbbbbbbbbbb”;
aaaaaaaaaaa是在运行时刻赋值的;
而bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。



c++内存分配的五种方法

在C++中,内存分成5个区,他们分别是堆、栈、自由存储区、全局/静态存储区和常量存储区。

 栈,就是那些由编译器在需要的时候分配,在不需要的时候自动清楚的变量的存储区。里面的变量通常是局部变量、函数参数等。

 堆,就是那些由new分配的内存块,他们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete。如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收。

 自由存储区,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。

 全局/静态存储区,全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为初始化的和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区。

 常量存储区,这是一块比较特殊的存储区,他们里面存放的是常量,不允许修改(当然,你要通过非正当手段也可以修改,而且方法很多)











  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值