转载来自:http://blog.csdn.net/cadcisdhht/article/category/785138

总结

  1. static的作用是表示该函数只作用在类型的静态变量上,与类的实例没有关系;而const的作用是确保函数不能修改类的实例的状态,与类型的静态变量没有关系。因此不能同时用它们。
  2. 空类实例不包含信息,本来求sizeof应该是0。但当我们声明该类型的实例的时候,它必须在内存中占有一定的空间,否则无法使用这些实例。至于占用多少内存,由编译器决定。Visual Studio 2008中每个空类型的实例占用一个byte的空间。由于构造函数和析构函数的调用与类型的实例无关(调用它们只需要知道函数地址即可),在它的实例中不需要增加任何信息。C++的编译器一旦发现一个类型中有虚拟函数,就会为该类型生成虚函数表,并在该类型的每一个实例中添加一个指向虚函数表的指针。在32位的机器上,一个指针占4个字节的空间。
  3. 每个非静态类成员函数都隐藏一个指向自己实例的this指针。
  4. 调用虚函数需要从一个实例中指向虚函数表的指针以得到函数的地址,因此调用虚函数需要一个实例。调用静态成员函数不要实例。
  5. &(pPoint->z)的语意是求pPoint中变量z的地址(pPoint的地址0加z的偏移量8),并不需要访问pPoint指向的内存。
  6. 如果没有标明函数或者变量是的访问权限级别,在struct中,是public的;而在class中,是private的。
  7. 在C++中,成员变量的初始化顺序与变量在类型中的申明顺序相同,而与它们在构造函数的初始化列表中的顺序无关。
  8. 如果允许复制构造函数传值,那么会形成永无休止的递归并造成栈溢出,编译错误。因此C++的标准不允许复制构造函数传值参数,而必须是传引用或者常量引用。
  9. sizeof(数组)是求数组的大小。 sizeof(指针)是求指针的大小,在32位机器上,任意指针都占4个字节的空间。当数组作为函数的参数进行传递时,数组就自动退化为同类型的指针。
  10. 缺省参数的值是在编译的时候,但确定引用、指针的虚函数调用哪个类型的函数是在运行的时候。
  11. char p[] = "Hello World";p是一个数组,会开辟一块内存,并拷贝"Hello World"初始化该数组。

题目

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

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

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

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

[cpp] view plain copy
  1. class A
  2. {
  3. };
  4. class B
  5. {
  6. public:
  7. B() {}
  8. ~B() {}
  9. };
  10. class C
  11. {
  12. public:
  13. C() {}
  14. virtual ~C() {}
  15. };
  16. int _tmain(int argc, _TCHAR* argv[])
  17. {
  18. printf("%d, %d, %d/n", sizeof(A), sizeof(B), sizeof(C));
  19. return 0;
  20. }

分析:答案是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。

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

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

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

[cpp] view plain copy
  1. class A
  2. {
  3. private:
  4. int m_value;
  5. public:
  6. A(int value)
  7. {
  8. m_value = value;
  9. }
  10. void Print1()
  11. {
  12. printf("hello world");
  13. }
  14. virtual void Print2()
  15. {
  16. printf("hello world");
  17. }
  18. };
  19. int _tmain(int argc, _TCHAR* argv[])
  20. {
  21. A* pA = NULL;
  22. pA->Print1();
  23. pA->Print2();
  24. return 0;
  25. }

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

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

[cpp] view plain copy
  1. class A
  2. {
  3. private:
  4. int m_value;
  5. public:
  6. A(int value)
  7. {
  8. m_value = value;
  9. }
  10. void Print1()
  11. {
  12. printf("hello world");
  13. }
  14. void Print2()
  15. {
  16. printf("%d", m_value);
  17. }
  18. };
  19. int _tmain(int argc, _TCHAR* argv[])
  20. {
  21. A* pA = NULL;
  22. pA->Print1();
  23. pA->Print2();
  24. return 0;
  25. }

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

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

[cpp] view plain copy
  1. struct Point3D
  2. {
  3. int x;
  4. int y;
  5. int z;
  6. };
  7. int _tmain(int argc, _TCHAR* argv[])
  8. {
  9. Point3D* pPoint = NULL;
  10. int offset = (int)(&(pPoint)->z);
  11. printf("%d", offset);
  12. return 0;
  13. }

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

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

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

[cpp] view plain copy
  1. class A
  2. {
  3. public:
  4. A()
  5. {
  6. Print();
  7. }
  8. virtual void Print()
  9. {
  10. printf("A is constructed./n");
  11. }
  12. };
  13. class B: public A
  14. {
  15. public:
  16. B()
  17. {
  18. Print();
  19. }
  20. virtual void Print()
  21. {
  22. printf("B is constructed./n");
  23. }
  24. };
  25. int _tmain(int argc, _TCHAR* argv[])
  26. {
  27. A* pA = new B();
  28. delete pA;
  29. return 0;
  30. }

答案:先后打印出两行:A is constructed. B is constructed. 调用B的构造函数时,先会调用B的基类及A的构造函数。然后在A的构造函数里调用Print。由于此时实例的类型B的部分还没有构造好,本质上它只是A的一个实例,他的虚函数表指针指向的是类型A的虚函数表。因此此时调用的Print是A::Print,而不是B::Print。接着调用类型B的构造函数,并调用Print。此时已经开始构造B,因此此时调用的Print是B::Print。

同样是调用虚拟函数Print,我们发现在类型A的构造函数中,调用的是A::Print,在B的构造函数中,调用的是B::Print。因此虚函数在构造函数中,已经失去了虚函数的动态绑定特性。

题目(八):在C++中,struct和class有什么不同?

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

题目(九):运行下图中的C++代码,输出是什么?

[cpp] view plain copy
  1. #include <iostream>
  2. class A
  3. {
  4. private:
  5. int n1;
  6. int n2;
  7. public:
  8. A(): n2(0), n1(n2 + 2)
  9. {
  10. }
  11. void Print()
  12. {
  13. std::cout << "n1: " << n1 << ", n2: " << n2 << std::endl;
  14. }
  15. };
  16. int _tmain(int argc, _TCHAR* argv[])
  17. {
  18. A a;
  19. a.Print();
  20. return 0;
  21. }

答案:输出n1是一个随机的数字,n2为0。在C++中,成员变量的初始化顺序与变量在类型中的申明顺序相同,而与它们在构造函数的初始化列表中的顺序无关。因此在这道题中,会首先初始化n1,而初始n1的参数n2还没有初始化,是一个随机值,因此n1就是一个随机值。初始化n2时,根据参数0对其初始化,故n2=0。

题目(十):编译运行下图中的C++代码,结果是什么?(A)编译错误;(B)编译成功,运行时程序崩溃;(C)编译运行正常,输出10。请选择正确答案并分析原因。

[cpp] view plain copy
  1. #include <iostream>
  2. class A
  3. {
  4. private:
  5. int value;
  6. public:
  7. A(int n)
  8. {
  9. value = n;
  10. }
  11. A(A other)
  12. {
  13. value = other.value;
  14. }
  15. void Print()
  16. {
  17. std::cout << value << std::endl;
  18. }
  19. };
  20. int _tmain(int argc, _TCHAR* argv[])
  21. {
  22. A a = 10;
  23. A b = a;
  24. b.Print();
  25. return 0;
  26. }

答案:编译错误。在复制构造函数中传入的参数是A的一个实例。由于是传值,把形参拷贝到实参会调用复制构造函数。因此如果允许复制构造函数传值,那么会形成永无休止的递归并造成栈溢出。因此C++的标准不允许复制构造函数传值参数,而必须是传引用或者常量引用。在Visual Studio和GCC中,都将编译出错。

题目(十一):运行下图中的C++代码,输出是什么?

[cpp] view plain copy
  1. int SizeOf(char pString[])
  2. {
  3. return sizeof(pString);
  4. }
  5. int _tmain(int argc, _TCHAR* argv[])
  6. {
  7. char* pString1 = "google";
  8. int size1 = sizeof(pString1);
  9. int size2 = sizeof(*pString1);
  10. char pString2[100] = "google";
  11. int size3 = sizeof(pString2);
  12. int size4 = SizeOf(pString2);
  13. printf("%d, %d, %d, %d", size1, size2, size3, size4);
  14. return 0;
  15. }

答案:4, 1, 100, 4。pString1是一个指针。在32位机器上,任意指针都占4个字节的空间。*pString1是字符串pString1的第一个字符。一个字符占一个字节。pString2是一个数组,sizeof(pString2)是求数组的大小。这个数组包含100个字符,因此大小是100个字节。而在函数SizeOf中,虽然传入的参数是一个字符数组,当数组作为函数的参数进行传递时,数组就自动退化为同类型的指针。

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

[cpp] view plain copy
  1. #include <iostream>
  2. class A
  3. {
  4. public:
  5. A()
  6. {       std::cout << "A is created." << std::endl;        }
  7. ~A()
  8. {       std::cout << "A is deleted." << std::endl;        }
  9. };
  10. class B : public A
  11. {
  12. public:
  13. B()
  14. {       std::cout << "B is created." << std::endl;        }
  15. ~B()
  16. {       std::cout << "B is deleted." << std::endl;        }
  17. };
  18. int _tmain(int argc, _TCHAR* argv[])
  19. {
  20. A* pA = new B();
  21. delete pA;
  22. return 0;
  23. }

答案:输出三行,分别是: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中创建了一些资源,比如文件句柄、内存等,在这种情况下都得不到释放,从而导致资源泄漏。

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

[cpp] view plain copy
  1. class A
  2. {
  3. public:
  4. virtual void Fun(int number = 10)
  5. {
  6. std::cout << "A::Fun with number " << number;
  7. }
  8. };
  9. class B:public A
  10. {
  11. public:
  12. virtual void Fun(int number = 20)
  13. {
  14. std::cout << "B::Fun with number " << number;
  15. }
  16. };
  17. int main()
  18. {
  19. B b;
  20. A &a = b;
  21. a.Fun();
  22. }

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

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

问题(十四):运行如下的C代码,输出是什么?

[cpp] view plain copy
  1. char* GetString1()
  2. {
  3. char p[] = "Hello World";
  4. return p;
  5. }
  6. char* GetString2()
  7. {
  8. char *p = "Hello World";
  9. return p;
  10. }
  11. int _tmain(int argc, _TCHAR* argv[])
  12. {
  13. printf("GetString1 returns: %s. /n", GetString1());
  14. printf("GetString2 returns: %s. /n", GetString2());
  15. return 0;
  16. }

答案:输出两行,第一行GetString1 returns: 后面跟的是一串随机的内容,而第二行GetString2 returns: Hello World.两个函数的区别在于GetString1中是一个数组,而GetString2中是一个指针。

当运行到GetString1时,p是一个数组,会开辟一块内存,并拷贝"Hello World"初始化该数组。接着返回数组的首地址并退出该函数。由于p是GetString1内的一个局部变量,当运行到这个函数外面的时候,这个数组的内存会被释放掉。因此在_tmain函数里再去访问这个数组的内容时,结果是随机的。

当运行到GetString2时,p是一个指针,它指向的是字符串常量区的一个常量字符串。该常量字符串是一个全局的,并不会因为退出函数GetString2而被释放掉。因此在_tmain中仍然根据GetString2返回的地址得到字符串"Hello World"。

问题(十五):运行下图中C代码,输出的结果是什么?

[cpp] view plain copy
  1. int _tmain(int argc, _TCHAR* argv[])
  2. {
  3. char str1[] = "hello world";
  4. char str2[] = "hello world";
  5. char* str3 = "hello world";
  6. char* str4 = "hello world";
  7. if(str1 == str2)
  8. printf("str1 and str2 are same./n");
  9. else
  10. printf("str1 and str2 are not same./n");
  11. if(str3 == str4)
  12. printf("str3 and str4 are same./n");
  13. else
  14. printf("str3 and str4 are not same./n");
  15. return 0;
  16. }

答案:输出两行。第一行是str1 and str2 are not same,第二行是str3 and str4 are same。

str1和str2是两个字符串数组。我们会为它们分配两个长度为12个字节的空间,并把"hello world"的内容分别拷贝到数组中去。这是两个初始地址不同的数组,因此比较str1和str2的值,会不相同。str3和str4是两个指针,我们无需为它们分配内存以存储字符串的内容,而只需要把它们指向"hello world“在内存中的地址就可以了。由于"hello world”是常量字符串,它在内存中只有一个拷贝,因此str3和str4指向的是同一个地址。因此比较str3和str4的值,会是相同的。

问题(十六):运行下图中的C++代码,打印出的结果是什么?

[cpp] view plaincopy
  1. bool Fun1(char* str)
  2. {
  3. printf("%s\n",str);
  4. return false;
  5. }
  6. bool Fun2(char* str)
  7. {
  8. printf("%s\n",str);
  9. return true;
  10. }
  11. int _tmain(int argc, _TCHAR* argv[])
  12. {
  13. bool res1,res2;
  14. res1 = (Fun1("a")&& Fun2("b")) || (Fun1("c") || Fun2("d"));
  15. res2 = (Fun1("a")&& Fun2("b")) &&(Fun1("c") || Fun2("d"));
  16. return res1|| res2;
  17. }

答案 :打印出4行,分别是a、c、d、a。

在C/C++中,与、或运算是从左到右的顺序执行的。在计算rest1时,先计算Fun1(“a”)&& Func2(“b”)。首先Func1(“a”)打印出内容为a的一行。由于Fun1(“a”)返回的是false,无论Func2(“b”)的返回值是true还是false,Fun1(“a”)&& Func2(“b”)的结果都是false。由于Func2(“b”)的结果无关重要,因此Func2(“b”)会略去而不做计算。接下来计算Fun1(“c”)|| Func2(“d”),分别打印出内容c和d的两行。

在计算rest2时,首先Func1(“a”)打印出内容为a的一行。由于Func1(“a”)返回false,和前面一样的道理,Func2(“b”)会略去不做计算。由于Fun1(“a”)&& Func2(“b”)的结果是false,不管Fun1(“c”)&& Func2(“d”)的结果是什么,整个表达式得到的结果都是false,因此Fun1(“c”) || Func2(“d”)都将被忽略。

问题(十七):运行下面的C++代码,打印的结果是什么?

[cpp] view plaincopy
  1. class Base
  2. {
  3. public:
  4. voidprint() { doPrint();}
  5. private:
  6. virtual void doPrint() {cout << "Base::doPrint" << endl;}
  7. };
  8. class Derived : public Base
  9. {
  10. private:
  11. virtual void doPrint() {cout << "Derived::doPrint" << endl;}
  12. };
  13. int _tmain(int argc, _TCHAR* argv[])
  14. {
  15. Base b;
  16. b.print();
  17. Derived d;
  18. d.print();
  19. return 0;
  20. }

答案:输出两行,分别是Base::doPrint和Derived::doPrint。在print中调用doPrint时,doPrint()的写法和this->doPrint()是等价的,因此将根据实际的类型调用对应的doPrint。所以结果是分别调用的是Base::doPrint和Derived::doPrint2。如果感兴趣,可以查看一下汇编代码,就能看出来调用doPrint是从虚函数表中得到函数地址的。

C/C++面试题精选相关推荐

  1. Top 10国际大厂人工智能岗位经典面试题精选

    Top 10国际大厂人工智能岗位经典面试题精选 https://www.toutiao.com/a6635196559355019780/ 2018-12-15 20:31:25 AI专业应届毕业生年 ...

  2. 机器学习笔试题精选(二)

    https://blog.csdn.net/red_stone1/article/details/81023976 上次 机器学习笔试题精选(一)中,我们详细解析了机器学习笔试 15 道题.今天,红色 ...

  3. 程序员面试题精选100题(31)-从尾到头输出链表[数据结构]

    题目:输入一个链表的头结点,从尾到头反过来输出每个结点的值.链表结点定义如下: struct ListNode {int m_nKey;ListNode* m_pNext; }; 分析:这是一道很有意 ...

  4. fibonacci数列前20项_面试题精选:神奇的斐波那契数列

    斐波那契数列,其最开始的几项是0.1.1.2.3.5.8.13.21.34-- ,后面的每一项是前两项之和,事实上,斐波那契在数学上有自己的严格递归定义. f0 = 0 f1 = 1 f(n) = f ...

  5. 机器学习笔试题精选(七)

    红色石头的个人网站:redstonewill.com 机器学习是一门理论性和实战性都比较强的技术学科.在应聘机器学习相关工作岗位时,我们常常会遇到各种各样的机器学习问题和知识点.为了帮助大家对这些知识 ...

  6. 终章 | 机器学习笔试题精选

    点击上方"AI有道",选择"置顶公众号" 关键时刻,第一时间送达! 读本文大约需要 9 分钟 机器学习是一门理论性和实战性都比较强的技术学科.在应聘机器学习相关 ...

  7. 太和二中计算机考试,安徽省太和二中高二数学下册期末考试试题精选

    安徽省太和二中高二数学下册期末考试试题精选 一.选择题(50分) 1.设 是两条不同的直线, 是两个不同的平面,下列命题中正确的是( D ) A . 若 , , ,则 B.若 , , ,则 C.若 , ...

  8. 两个数组中对应的下标的值合成一个新的数组_剑指 offer 面试题精选图解 03 . 数组中重复的数字

    今天分享的题目来源于 LeetCode 上的剑指 Offer 系列 面试题03. 数组中重复的数字. 题目链接:https://leetcode-cn.com/problems/shu-zu-zhon ...

  9. Unity面试题精选(7)

    洪流学堂,让你快人几步. 本篇文章首发于我的公众号:洪流学堂 整理了一些Unity面试题目,希望可以帮助到你. 面试官:简述一下对象池,你觉得在FPS里哪些东西适合使用对象池? 答: 对象池就存放需要 ...

  10. Unity面试题精选(6)

    洪流学堂,让你快人几步. 本篇文章首发于我的公众号:洪流学堂 整理了一些Unity面试题目,希望可以帮助到你. 面试官:在C#中using和new这两个关键字有什么意义? 答: using 关键字有两 ...

最新文章

  1. android 贝塞尔曲线点击区域,白话经典贝塞尔曲线及其在 Android 中的应用
  2. 从零开始学建站-主机篇
  3. android jack log,Android:JACK编译错误汇总及解决
  4. springdatajpa命名规则_简单了解下spring data jpa
  5. 纯CSS实现多级菜单,兼容IE6
  6. 基于策略的一种高效内存池的实现
  7. Android中SQLiteOpenHelper类的onUpgrade方法浅谈
  8. Scala学习小小总结
  9. android camera API1调用camera HAL3流程学习总结
  10. 计算机二级ppt文小雨,计算机二级PPT真题:制作日月潭介绍PPT
  11. MFC的坐标转换GetClientRect/GetWindowRect/ClientToScreen/GetCursorPos/ScreenToClient
  12. 维刻柠檬鲜果冰怎么样?
  13. 剑指offer(41-50题)详解
  14. 腾讯云服务器迁移报错Other go2tencentcloud are running.
  15. 李智慧 - 架构师训练营 第四周
  16. 电子与通信工程专硕考分_分享我的苏州大学电子与通信工程专硕考研经历
  17. 安卓libc setenv函数内存泄漏
  18. python代码下出现红线_python踩坑系列之导入包时下划红线及报错“No module named”问题...
  19. MySQL的核心日志
  20. 日常快捷键、代码快捷键

热门文章

  1. 微型计算机硬件的最小配置包括,职中计算机应用基础第一章测试题及答案
  2. mybatis一级缓存命中条件
  3. 7、Linux中文件类型、文件属性
  4. 第三讲、Linux常用命令
  5. svm 彻底的过程
  6. 【自动驾驶】12.百度Apollo对ROS的优化【详细干货】
  7. Redis 数据类型介绍
  8. Java学习之javassist
  9. Hadoop For Windows
  10. C++11中值得关注的几大变化