[关闭]
@qiezhian 2014-11-15T13:55:21.000000Z 字数 8045 阅读 2667

C/C++笔试知识点

programming


1.运算符优先级

参考运算符优先级表

2.类型转换

(包括算数运算转换与其他类型转换)
2.1 防止精度损失,类型总是提升为较宽的类型
2.2 所有小于整型的有序类型算数表达式在计算前其类型都会被转为整型计算,最后的返回结果从整型结果中“截取”。

3.const用法

const常量复制必须同时初始化

4.sizeof用法

注意内存的数据对齐,sizeof只计算栈中分配的大小(堆中呢??),静态变量是放在全局数据区的,不计算在sizeof内。sizeof是C/C++中的一个操作符(operator)是也,简单的说其作用就是返回一个对象或者类型所占的内存字节数。sizeof()中的内容是不进行编译的,如

  1. int a=8;
  2. sizeof a=6 );
  3. //实际上sizeof( a==6 )相当于sizeof( int ),并且中间不被编译,a还是等于8
  4. int f() { return 0; }
  5. sizeof( f );//对函数使用sizeof,在编译阶段会被函数返回值的类型取代,本例中为sizeof(int)

5.内联函数

inline在编译中不单独产生代码,而是将有关代码嵌入到调用处,会进行参数类型检查

6.指针和引用的区别

引用非空(声明必须初始化)、引用无需测试合法性、可修改区别(指针改变指向,引用改变本体)

7.参数传递

值传递、指针传递、引用传递

8.C++构造函数及初始化

初始化列表是根据初始化变量的声明顺序执行的(!!!)
总结起来,可以初始化的情况有如下四个地方:
1、在类的定义中进行的,只有const 且 static 且 integral 的变量。
2、在类的构造函数初始化列表中, 包括const对象和Reference对象。
3、在类的定义之外初始化的,包括static变量。因为它是属于类的唯一变量
4、普通的变量可以在构造函数的内部,通过赋值方式进行。当然这样效率不高

9.c++类的静态成员变量

和静态常量类型的成员变量的赋值

  1. class Test
  2. {
  3. public:
  4. static int data = 3;
  5. //错误,c++只允许静态常量类型的变量在定义的时候直接初始化
  6. static const int condata= 10;
  7. //正确,满足c++的规定,因为他是一个静态常量成员。
  8. };
  9. int Test::data = 1;
  10. //静态非常量类型的成员变量的赋值应该在类声明的外部,直接这样声明就可以了。

10.c++操作符重载

重载++,--等操作符时,不带参数的是++temp;带参数的是temp++。如

  1. class Test
  2. {
  3. public:
  4. int &operator ++() //前增量
  5. {
  6. this->x++;
  7. return this->x;
  8. }
  9. const int &operator ++(int)//后增量
  10. {
  11. int temp = *this->x;
  12. this->x ++;
  13. return temp;
  14. }
  15. private:
  16. int x;
  17. };

11.C++中虚析构函数的作用

用C++开发的时候,用来做基类的类的析构函数一般都是虚函数。这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。且一般情况下类的析构函数里面都是释放内存资源,而析构函数不被调用的话就会造成内存泄漏。当然,并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。

12.C++空类默认产生四种成员函数

默认构造函数、析构函数、拷贝构造函数、赋值函数。其中以拷贝构造函数与赋值函数最易混淆。拷贝构造函数与赋值函数最大的问题在于“深拷贝”与“浅拷贝”问题,见以下代码分析。

  1. 有几个需要注意的内容:
  2. 1). 构造函数与析构函数的另一个特别之处是没有返回值类型
  3. 2). 构造从类层次的最顶层的基类开始,在每一层中,首先调用基类的构造函数,然后调用成员对象的构造函数。析构则严格按照与构造相反的次序执行,在析构的时候,最低层的派生类的析构函数最开始被调用,然后调用每个基类的析构函数。
  4. 3). “缺省的拷贝构造函数”和“缺省的赋值函数”均采用“浅拷贝”而非“深拷贝”的方式来实现,倘若类中含有指针变量,这两个函数注定将出错。若类中含有指针变量,需要主动写拷贝构造函数与赋值函数来完成想要的功能而不能依靠系统的默认函数。
  5. 4).拷贝构造函数是对未初始化的内存进行初始化操作,而赋值是对现有的已经初始化的对象进行操作。(这里我对“已经初始化”的理解是已经调用了构造函数,并且构造函数体可以未执行,只要调用到即可),赋值函数应该给所有数据成员都初始化。
  6. 为了便于说明我们以String类为例:首先定义String类,而并不实现其成员函数。
  7. Class String{
  8. public:
  9. String(const char *ch=NULL);//默认构造函数
  10. String(const String &str);//拷贝构造函数
  11. ~String(void);
  12. String &operator=(const String &str);//赋值函数
  13. private:
  14. char *m_data;
  15. };
  1. class CExample
  2. {
  3. public:
  4. CExample(){pBuffer=NULL; nSize=0;}
  5. ~CExample(){delete pBuffer;}
  6. void Init(int n){ pBuffer=new char[n]; nSize=n;}
  7. private:
  8. char *pBuffer; //类的对象中包含指针,指向动态分配的内存资源
  9. int nSize;
  10. };
  11. /*********************
  12.        用到拷贝构造函数--main1
  13. **********************/
  14. int main(int argc, char* argv[])
  15. {
  16. CExample theObjone;
  17. theObjone.Init40);
  18. //现在需要另一个对象,需要将他初始化称对象一的状态
  19. CExample theObjtwo=theObjone;
  20. ...
  21. }
  22. //即它们将指向同样的地方,指针虽然复制了,但所指向的空间并没有复制,而是由两个对象共用了。这样不符合要求,对象之间不独立了,并为空间的删除带来隐患。即系统默认进行了“浅拷贝”。
  23. /**************************
  24.           用到赋值函数--main2
  25. ***************************/
  26. int main(int argc, char* argv[])
  27. {
  28. CExample theObjone;
  29. theObjone.Init(40);
  30. CExample theObjthree;
  31. theObjthree.Init(60);
  32. //现在需要一个对象赋值操作,被赋值对象的原内容被清除,并用右边对象的内容填充。
  33. theObjthree=theObjone;
  34. return 0;
  35. }
  36. //由于对象内包含指针,将造成不良后果:指针的值被丢弃了(指针内容丢失了也就是说地址丢失了,但是该地址中所存储的内容没有丢失,但指针指向的内容并未释放。指针的值被复制了,但指针所指内容并未复制。

13.函数重载、函数覆盖、函数隐藏

三个概念

  1. 1、函数重载
  2. 函数重载:是指允许存在多个同名函数,这些函数的参数列表不同,或许是参数个数不同,或许是参数类型不同,或许是两者都不同。重要一点:函数重载是发生在同一个类中。调用时,根据参数类型的不同进行调用,同时编译器在编译期间就确定了要调用的函数。(函数的重载与多态无关)括号里面的话不知道对不对,不同的人有不同的理解,有的说是无关,但是有的说重载是多态性两种之一的编译时的多态性。有高手知道了可以指点一下,谢谢了
  3. 2、函数覆盖
  4. 函数覆盖也被称为函数重写(override),是子类重新定义基类虚函数的方法。
  5. 构成函数覆盖的条件:
  6. 1)基类的函数必须是虚函数(virtual进行声明)
  7. 2)发生覆盖的两个函数必须分别位于派生类和基类中
  8. 3)函数名称和参数列表必须完全相同
  9. 由于c++多态性是通过虚函数来实现的,所以函数覆盖总是和多态联系在一起,并且是程序在运行时才确定要调用的函数,因此也成为动态绑定或者后期绑定。
  10. 3、函数隐藏
  11. 函数隐藏是指子类中具有和基类同名的函数,但是并不考虑参数列表是否相同,从而在子类中隐藏了基类的同名函数。有以下两种情况:
  12. 1)子类函数和基类函数完全相同,只是基类的函数没有使用virtual关键字,此时基类的函数将被隐藏。
  13. 2)子类函数和基类函数名字相同,但是参数列表不同,在这种情况下,无论基类的函数是否声明为virtual,基类的函数都将被隐藏。

14.C++多态性。

  1. 多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。

最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是一定的,而固定的地址将始终调用到同一个函数,这就无法实现一个接口,多种方法的目的了。
其实调用函数的时候,关键看对象的类型。记住是对象的类型,不管是指针还是引用(同样是对象的类型)。 当你用这个指针去调用一个虚函数时,他就到虚函数表中,找这个名字的函数,从指针所指对象的类型(即派生类)依次向父类走,直到遇到第一个与次匹配的函数名。
参考http://blog.csdn.net/hackbuteer1/article/details/7475622

15.C++操作符重载

  1. 操作符的重载有一些规则:
  2. 1. 重载操作符必须具有一个类类型或枚举类型操作数。这条规则强制重载操作符不能重新定义用于内置类型对象的操作符的含义。
  3. 如: int operator+(int int), 不可以
  4. 2. 为类设计重载操作符的时候,必须选择是将操作符设置为类成员还是普通非成员函数。在某些情况下,程序没有选择,操作符必须是成员;在另外一些情况下,有些经验可以指导我们做出决定。下面是一些指导:
  5. a. 赋值(=),下标([]),调用(())和成员访问箭头(->)等操作符必须定义为成员,将这些操作符定义为非成员函数将在编译时标记为错误
  6. b. 像赋值一样,复合赋值操作符通常应定义为类的成员。与赋值不同的是,不一定非得这样做,如果定义为非成员复合赋值操作符,不会出现编译错误。
  7. c. 改变对象状态或与给定类型紧密联系的其他一些操作符,如自增,自减和解引用,通常应定义为类成员。
  8. d 对称的操作符,如算术操作符,相等操作符,关系操作符和位操作符,最好定义为普通非成员函数。
  9. e io操作符必须定义为非成员函数,重载为类的友元。
  1. // OverloadCinCout.cpp : 定义控制台应用程序的入口点。
  2. //
  3. #include "stdafx.h"
  4. #include "iostream"
  5. #include "string"
  6. using namespace std;
  7. class Fruit
  8. {
  9. public:
  10. Fruit(const string &nst = "apple", const string &cst = "green"):name(nst),colour(cst){}
  11. ~Fruit(){}
  12. friend ostream& operator << (ostream& os, const Fruit& f); //输入输出流重载,不是类的成员,
  13. friend istream& operator >> (istream& is, Fruit& f); // 所以应该声明为类的友元函数
  14. private:
  15. string name;
  16. string colour;
  17. };
  18. ostream& operator << (ostream& os, const Fruit& f)
  19. {
  20. os << "The name is " << f.name << ". The colour is " << f.colour << endl;
  21. return os;
  22. }
  23. istream& operator >> (istream& is, Fruit& f)
  24. {
  25. is >> f.name >> f.colour;
  26. if (!is)
  27. {
  28. cerr << "Wrong input!" << endl;
  29. }
  30. return is;
  31. }
  32. int _tmain(int argc, _TCHAR* argv[])
  33. {
  34. Fruit apple;
  35. cout << "Input the name and colour of a kind of fruit." << endl;
  36. cin >> apple;
  37. cout << apple;
  38. return 0;
  39. }

参考http://blog.csdn.net/yushuai007008/article/details/7302975
http://www.chsi.com.cn/xy/com/200807/20080724/7709237.html

16.静态变量初始化

所有对象的初始化只有一次,但对于静态变量(名字一样代表的东西就一样)来说,在程序结束之前它不会被销毁,故其只能初始化一次而可以赋值多次。对于普通变量超出其作用域后会被销毁,则可以在其他地方再声明、初始化(名字一样但代表的东西不一样)。下面代码输出一直是1,因为static变量不能重复声明初始化。

  1. #include <iostream.h>
  2. #include <string.h>
  3. #include <stdlib.h>
  4. void main()
  5. {
  6. for (int i =1;i<5; ++i)
  7. {
  8. static int a = i;
  9. cout << a <<endl;
  10. }
  11. }

17.私有继承

  1. #include "stdafx.h"
  2. #include <iostream>
  3. using namespace std;
  4. /*
  5. 私有继承的两个原则:
  6. (1)、和公有继承相反,如果两个类之间的继承关系为私有,编译器一般不会将派生类对象(如Child)转换成基类对象(如Parent)
  7. (2)、从私有基类继承而来的成员都成为了派生类的私有成员,即使它们在基类中是保护或公有成员.大家可以看出私有继承时派生类与基类不是“is a”的关系,而是意味着“Is-Implement-In-Terms-Of”(以…实现)。如果使类D私有继承于类B,这样做是因为你想利用类B中已经存在的某些代码,而不是因为类型B的对象和类型D的对象之间有什么概念上的关系。
  8. 因此,私有继承在软件“设计”过程中毫无意义,只是在软件“实现”时才有用。
  9. */
  10. class Parent
  11. {
  12. public:
  13. Parent(){}
  14. Parent(int a):m_a(a){}
  15. virtual void print()
  16. {
  17. cout<<"Parent::"<<m_a<<endl;
  18. }
  19. private:
  20. int m_a;
  21. };
  22. //私有继承
  23. class Child : private Parent
  24. {
  25. public:
  26. Child(){}
  27. Child(int a, int b):Parent(a),m_b(b){}
  28. void print()
  29. {
  30. Parent::print();
  31. cout<<"Child::"<<m_b<<endl;
  32. }
  33. private:
  34. int m_b;
  35. };
  36. //全局函数
  37. void Print(Parent &p)
  38. {
  39. p.print();
  40. }
  41. int main()
  42. {
  43. Parent p(1);
  44. Child c(1,3);
  45. Print(p);
  46. Print(c);//编译错误(类型转换”: 从“Child *”到“Parent &”的转换存在,但无法访问),由此可见私有继承不能用于多态
  47. p = c;//编译错误(“类型转换”: 从“Child *”到“const Parent &”的转换存在,但无法访问)
  48. }

18.虚继承

引入虚继承和直接继承会有什么区别呢?
由于有了间接性和共享性两个特征,所以决定了虚继承体系下的对象在访问时必然会在时间和空间上与一般情况有较大不同。
2.1时间:在通过继承类对象访问虚基类对象中的成员(包括数据成员和函数成员)时,都必须通过某种间接引用来完成,这样会增加引用寻址时间(就和虚函数一样),其实就是调整this指针以指向虚基类对象,只不过这个调整是运行时间接完成的。
2.2空间:由于共享所以不必要在对象内存中保存多份虚基类子对象的拷贝,这样较之多继承节省空间。虚拟继承与普通继承不同的是,虚拟继承可以防止出现diamond继承时,一个派生类中同时出现了两个基类的子对象。也就是说,为了保证这一点,在虚拟继承情况下,基类子对象的布局是不同于普通继承的。因此,它需要多出一个指向基类子对象的指针。

  1. 笔试,面试中常考的C++虚拟继承的知识点
  2. //第一种情况
  3. class a
  4. {
  5. virtual void func();
  6. }
  7. class b: public virtual a
  8. {
  9. virtual void foo();
  10. }
  11. //第二种情况
  12. class a
  13. {
  14. virtual void func();
  15. }
  16. class b: public a
  17. {
  18. virtual void foo();
  19. }
  20. //第三种情况
  21. class a
  22. {
  23. virtual void func();
  24. char x;
  25. }
  26. class b: public virtual a
  27. {
  28. virtual void foo();
  29. }
  30. //第四种情况
  31. class a
  32. {
  33. virtual void func();
  34. char x;
  35. }
  36. class b: public a
  37. {
  38. virtual void foo();
  39. }

如果对这四种情况分别求sizeof(a), sizeof(b)。结果是什么样的呢?下面是输出结果:(在vc6.0中运行)
第一种:4,12
第二种:4,4
第三种:8,16
第四种:8,8
想想这是为什么呢?
因为每个存在虚函数的类都要有一个4字节的指针指向自己的虚函数表,所以每种情况的类a所占的字节数应该是没有什么问题的,那么类b的字节数怎么算呢?看“第一种”和“第三种”情况采用的是虚继承,那么这时候就要有这样的一个指针vptr_b_a,这个指针叫虚类指针,也是四个字节;还要包括类a的字节数,所以类b的字节数就求出来了。而“第二种”和“第四种”情况则不包括vptr_b_a这个指针,这回应该木有问题了吧。

19.虚函数

虚函数为了重载和多态的需要,在基类中是由定义的,即便定义是空,所以子类中可以重写也可以不写基类中的函数!
纯虚函数在基类中是没有定义的,必须在子类中加以实现,很像java中的接口函数!
虚函数
引入原因:为了方便使用多态特性,我们常常需要在基类中定义虚函数。
纯虚函数
引入原因:
1、同“虚函数”;
2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。

  1. /*
  2. 纯虚函数就是基类只定义了函数体,没有实现过程定义方法如下
  3. virtual void Eat() = 0; 直接=0 不要 在cpp中定义就可以了.
  4. 纯虚函数相当于接口,不能直接实例话,需要派生类来实现函数定义.
  5. 有的人可能在想,定义这些有什么用啊 ,我觉得很有用.
  6. 比如你想描述一些事物的属性给别人,而自己不想去实现,就可以定
  7. 义为纯虚函数。说的再透彻一些。比如盖楼房,你是老板,你给建筑公司
  8. 描述清楚你的楼房的特性,多少层,楼顶要有个花园什么的建筑公司就可以按照你的方法去实现了,如果你不说清楚这些,可能建筑公司不太了解你需要楼房的特性。用纯需函数就可以很好的分工合作了
  9. */
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注