现在的位置: 首页 > 综合 > 正文

C++笔试题

2013年05月04日 ⁄ 综合 ⁄ 共 3369字 ⁄ 字号 评论关闭

 

题目(一):我们可以用static修饰一个类的成员函数,也可以用const修饰类的成员函数(写在函数的最后表示不能修改成员变量,不是指写在前面表示返回值为常量)。请问:能不能同时用staticconst修饰类的成员函数?

分析:答案是不可以。C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数const
this*
。但当一个成员为static的时候,该函数是没有this指针的。也就是说此时static的用法和static是冲突的。

我们也可以这样理解:两者的语意是矛盾的。static的作用是表示该函数只作用在类型的静态变量上,与类的实例没有关系;而const的作用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系。因此不能同时用它们。

题目(二):运行下面的代码,输出是什么?

class A

{

};

 

class B

{

public:

B() {}

~B() {}

};

 

class C

{

public:

C() {}

virtual~C() {}

};

 

int _tmain(int argc, _TCHAR* argv[])

{

printf("%d,%d, %d\n",sizeof(A),
sizeof(B),
sizeof(C));

return 0;

}

分析:答案是1, 1, 4class
A
是一个空类型,它的实例不包含任何信息,本来求sizeof应该是0。但当我们声明该类型的实例的时候,它必须在内存中占有一定的空间,否则无法使用这些实例。至于占用多少内存,由编译器决定。Visual
Studio 2008
中每个空类型的实例占用一个byte的空间。

class Bclass A的基础上添加了构造函数和析构函数。由于构造函数和析构函数的调用与类型的实例无关(调用它们只需要知道函数地址即可),在它的实例中不需要增加任何信息。所以sizeof(B)sizeof(A)一样,在Visual
Studio 2008
中都是1

class Cclass B的基础上把析构函数标注为虚拟函数。C++的编译器一旦发现一个类型中有虚拟函数,就会为该类型生成虚函数表,并在该类型的每一个实例中添加一个指向虚函数表的指针。在32位的机器上,一个指针占4个字节的空间,因此sizeof(C)4

题目(三):运行下面中的代码,得到的结果是什么?

class A

{

private:

intm_value;

 

public:

A(int value)

{

m_value = value;

}

voidPrint1()

{

printf("helloworld");

}

voidPrint2()

{

printf("%d", m_value);

}

};

 

int _tmain(int argc, _TCHAR* argv[])

{

A* pA = NULL;

pA->Print1();

pA->Print2();

 

return 0;

}

分析:答案是Print1调用正常,打印出hello
world
,但运行至Print2时,程序崩溃。调用Print1时,并不需要pA的地址,因为Print1的函数地址是固定的。编译器会给Print1传入一个this指针,该指针为NULL,但在Print1中该this指针并没有用到。只要程序运行时没有访问不该访问的内存就不会出错,因此运行正常。在运行print2时,需要this指针才能得到m_value的值。由于此时this指针为NULL,因此程序崩溃了。

题目(四):运行下面中的代码,得到的结果是什么?

class A

{

private:

intm_value;

 

public:

A(int value)

{

m_value = value;

}

voidPrint1()

{

printf("helloworld");

}

virtual void Print2()

{

printf("helloworld");

}

};

 

int _tmain(int argc, _TCHAR* argv[])

{

A* pA = NULL;

pA->Print1();

pA->Print2();

 

return 0;

}

分析:答案是Print1调用正常,打印出hello
world
,但运行至Print2时,程序崩溃。Print1的调用情况和上面的题目一样,不在赘述。由于Print2是虚函数。C++调用虚函数的时候,要根据实例(即this指针指向的实例)中虚函数表指针得到虚函数表,再从虚函数表中找到函数的地址。由于这一步需要访问实例的地址(即this指针),而此时this指针为空指针,因此导致内存访问出错。

题目(五):静态成员函数能不能同时也是虚函数?

分析:答案是不能。调用静态成员函数不要实例。但调用虚函数需要从一个实例中指向虚函数表的指针以得到函数的地址,因此调用虚函数需要一个实例。两者相互矛盾。

 

题目(六):运行下列C++代码,输出什么?

struct Point3D

{

int x;

int y;

int z;

};

 

int _tmain(int argc, _TCHAR* argv[])

{

Point3D* pPoint = NULL;

int offset= (int)(&(pPoint)->z);

 

printf("%d", offset);

return 0;

}

答案:输出8。由于在pPoint->z的前面加上了取地址符号,运行到此时的时候,会在pPoint的指针地址上加z在类型Point3D中的偏移量8。由于pPoint的地址是0,因此最终offset的值是8

&(pPoint->z)的语意是求pPoint中变量z的地址(pPoint的地址0z的偏移量8),并不需要访问pPoint指向的内存。只要不访问非法的内存,程序就不会出错。

 

题目(15:运行下图中代码,输出的结果是什么?这段代码有什么问题?

#include<iostream>

 

class A

{

public:

A()

{

std::cout << "A is created."<< std::endl;

}

 

~A()

{

std::cout << "A is deleted."<< std::endl;

}

};

 

class B :public A

{

public:

B()

{

std::cout << "B is created."<< std::endl;

}

 

~B()

{

std::cout << "B is deleted."<< std::endl;

}

};

 

int _tmain(int argc, _TCHAR* argv[])

{

A* pA = new B();

delete pA;

 

return 0;

}

答案:输出三行,分别是:A is created. B is created. A is deleted。用new创建B时,回调用B的构造函数。在调用B的构造函数的时候,会先调用A的构造函数。因此先输出A
is created. B is created.

接下来运行delete语句时,会调用析构函数。由于pA被声明成类型A的指针,同时基类A的析构函数没有标上virtual,因此只有A的析构函数被调用到,而不会调用B的析构函数。

由于pA实际上是指向一个B的实例的指针,但在析构的时候只调用了基类A的析构函数,却没有调用B的析构函数。这就是一个问题。如果在类型B中创建了一些资源,比如文件句柄、内存等,在这种情况下都得不到释放,从而导致资源泄漏。

 

问题(16:运行如下的C++代码,输出是什么?

classA

{

public:

virtualvoid Fun(int
number = 10)

{

std::cout <<"A::Fun with number " << number;

}

};

 

classB:public A

{

public:

virtualvoid Fun(int
number = 20)

{

std::cout <<"B::Fun with number " << number;

}

};

 

intmain()

{

Bb;

A&a = b;

a.Fun();

}

答案输出B::Fun
with number 10
。由于a是一个指向B实例的引用,因此在运行的时候会调用B::Fun。但缺省参数是在编译期决定的。在编译的时候,编译器只知道a是一个类型a的引用,具体指向什么类型在编译期是不能确定的,因此会按照A::Fun的声明把缺省参数number设为10

这一题的关键在于理解确定缺省参数的值是在编译的时候,但确定引用、指针的虚函数调用哪个类型的函数是在运行的时候。

抱歉!评论已关闭.