您的位置:首页 > 编程语言 > C语言/C++

C++基础知识面试精选100题系列(1-10题)[C++ basics]

2014-05-23 21:28 615 查看
【原文链接】

/article/5268943.html

题目1

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

分析

不可以。C++编译器在实现const的成员函数的时候为了确保该函数不能修改类的实例的状态,会在函数中添加一个隐式的参数const this*。但当一个成员为static的时候,该函数是没有this指针的。我们也可以这样理解:两者的语意是矛盾的。static是针对类型,与类的实例没有关系;而const是针对实例,确保函数不能修改实例的状态。因此不能同时用它们。

题目2

运行下面的代码,输出是什么?

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

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, 4。

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

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

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

题目3

运行下面中的代码,得到的结果是什么?

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

#include "stdafx.h"

class A

{

private:

int m_value;

public:

A(int value)

{

m_value = value;

}

void Print1()

{

printf("hello world");

}

void Print2()

{

printf("%d", m_value);

}

};

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

{

A *pA = NULL;

pA->Print1(); // "hello world"

pA->Print2(); // ERROR

return 0;

}

分析

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

题目4

运行下面中的代码,得到的结果是什么?

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

class A

{

private:

int m_value;

public:

A(int value)

{

m_value = value;

}

void Print1()

{

printf("hello world");

}

virtual void Print2()

{

printf("hello world");

}

};

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指针为空指针,因此导致内存访问出错。

题目5

静态成员函数能不能同时也是虚函数?

【分析】

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

【[b]题目6[/b]

运行下列C++代码,输出什么?

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

#include "stdafx.h"

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); // 8

return 0;

}

/*

+ pPoint 0x00000000 {x=??? y=??? z=??? } Point3D *

+ &pPoint->x 0x00000000 int *

+ &pPoint->y 0x00000004 int *

+ &pPoint->z 0x00000008 int *

*/

【分析】

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

【[b]题目7[/b]

运行下列C++代码,输出什么?

C++ Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

#include "stdafx.h"

class A

{

public:

A()

{

Print();

}

~A()

{

printf("A is erased.\n");

}

virtual void Print()

{

printf("A is constructed.\n");

}

};

class B: public A

{

public:

B()

{

Print();

}

~B()

{

printf("B is erased.\n");

}

virtual void Print()

{

printf("B is constructed.\n");

}

};

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

{

A *pA = new B();

delete pA;

B *pB = new B();

delete pB;

return 0;

}

/*

A is constructed.

B is constructed.

A is erased.

A is constructed.

B is constructed.

B is erased.

A is erased.

*/

[b]【分析】[/b]

输出结果如上所示。

【题目8】

运行下列C#代码,输出是什么?

C# Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14

namespace ChangesOnString

{

class Program

{

static void Main(string[] args)

{

String str = "hello";

str.ToUpper();

str.Insert(0, " WORLD");

Console.WriteLine(str);

}

}

}

【分析】

输出是hello。由于在.NET中,String有一个非常特殊的性质:String的实例的状态不能被改变。如果String的成员函数会修改实例的状态,将会返回一个新的String实例。改动只会出现在返回值中,而不会修改原来的实例。所以本题中输出仍然是原来的字符串值hello。如果试图改变String的内容,改变之后的值可以通过返回值拿到。用StringBuilder是更好的选择,特别是要连续多次修改的时候。如果用String连续多次修改,每一次修改都会产生一个临时对象,开销太大。

【题目9】

在C++和C#中,struct和class有什么不同?

【分析】

在C++中,如果没有标明函数或者变量是的访问权限级别,在struct中,是public的;而在class中,是private的。

在C#中,如果没有标明函数或者变量的访问权限级别,struct和class中都是private的。

struct和class的区别是:struct定义值类型,其实例在栈上分配内存;class定义引用类型,其实例在堆上分配内存。

【题目10】

运行下图中的C#代码,输出是什么?

C# Code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

namespace StaticConstructor

{

class A

{

public A(string text)

{

Console.WriteLine(text);

}

}

class B

{

static A a1 = new A("a1");

A a2 = new A("a2");

static B()

{

a1 = new A("a3");

}

public B()

{

a2 = new A("a4");

}

}

class Program

{

static void Main(string[] args)

{

B b = new B();

}

}

}

【分析】

打印出四行,分别是a1、a3、a2、a4。

在调用类型B的代码之前先执行B的静态构造函数。静态函数先初始化类型的静态变量,再执行静态函数内的语句。因此先打印a1再打印a3。接下来执行B b = new B(),即调用B的普通构造函数。构造函数先初始化成员变量,在执行函数体内的语句,因此先后打印出a2、a4。

【参考】

http://zhedahht.blog.163.com/blog/static/254111742011012111557832/

【原文链接】

/article/5268943.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: