1. main 函数执行以前,还会执行什么代码?
答案:全局对象的构造函数会在main 函数之前执行,为malloc分配必要的资源,等等。
2. main 主函数执行完毕后,是否可能会再执行一段代码,给出说明?
答案:可以,可以用_onexit 注册一个函数,它会在main 之后执行int fn1(void), fn2(void), fn3(void),fn4 (void);
3.请说出static和const关键字尽可能多的作用。
static关键字至少有下列n个作用:
(1)函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
(2)在模块内的static全局变量可以被模块内所用函数访问,但不能被模块外其它函数访问;
(3)在模块内的static函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内;
(4)在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
(5)在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
const关键字至少有下列n个作用:
(1)欲阻止一个变量被改变,可以使用const关键字。在定义该const变量时,通常需要对它进行初始化,因为以后就没有机会再去改变它了;
(2)对指针来说,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
(3)在一个函数声明中,const可以修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”
4. typedef和define的区别
1) 用法不同:typedef 用来定义一种数据类型的别名,增强程序的可读性。define 主要用来定义常量,以及书写复杂使用频繁的宏。
2) 执行时间不同:typedef 是编译过程的一部分,有类型检查的功能。define 是宏定义,是预编译的部分,其发生在编译之前,只是简单的进行字符串的替换,不进行类型的检查。
3) 作用域不同:typedef 有作用域限定。define 不受作用域约束,只要是在 define 声明后的引用都是正确的。
5. const和define的区别
1) 就起作用的阶段而言: #define是在编译的预处理阶段起作用,而const是在 编译、运行的时候起作用。
2) 就起作用的方式而言: #define只是简单的字符串替换,没有类型检查。而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误。
3) 就存储方式而言:#define只是进行展开,有多少地方使用,就替换多少次,它定义的宏常量在内存中有若干个备份;const定义的只读变量在程序运行过程中只有一份备份。
从代码调试的方便程度而言: const常量可以进行调试的,define是不能进行调试的,因为在预编译阶段就已经替换掉了。
6.如何避免“野指针”
1) 指针初始化时,指针变量声明时进行初始化。
2) 指针释放后,指针 p 被 free 或者 delete 之后,进行制 NULL
3) 指针操作时,超过作用范围,在变量的作用域结束前释放掉变量的地址空间并且让指针指向 NULL。
7. 内存泄漏和内存溢出。
内存溢出:申请内存时,没有足够的内存空间。
内存泄漏:申请的内存未释放
1) malloc和free未成对出现
2) 通过局部分配的内存,未在调用者函数体内释放
3) 不匹配使用new[] 和 delete[]
4) 没有将基类的析构函数定义为虚函数,当基类的指针指向子类时,delete该对象时,不会调用子类的析构函数。
8. 内存分配
1) 从静态存储区域分配:内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在,速度快,不容出错,因为有系统会善后,例如,全局变量,static变量等
2) 在栈上分配
3) 从堆上分配
4) 一个C,C++程序编译时内存分为5大存储区:堆区,栈区,全局区,文字常量区,程序代码区
9.内联函数和宏比较
1) 内联函数在运行时可调试,而宏定义不可以。
2) 编译器会对内联函数的参数类型做安全检查或自动类型转换(同普通函数),而宏定义则不会
3) 内联函数可以访问类的成员变量,宏定义则不能;
10.关于引用
1) 什么是“引用”?申明和使用“引用”要注意哪些问题?
答:引用就是某个目标变量的“别名”(alias),对应用的操作与对变量直接操作效果完全相同。申明一个引用的时候,切记要对其进行初始化。引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。
2) 将“引用”作为函数参数有哪些特点?
(1)传递引用给函数与传递指针的效果是一样的。这时,被调函数的形参就成为原来主调函数中的实参变量或对象的一个别名来使用,所以在被调函数中对形参变量的操作就是对其相应的目标对象(在主调函数中)的操作。
(2)使用引用传递函数的参数,在内存中并没有产生实参的副本,它是直接对实参操作;而使用一般变量传递函数的参数,当发生函数调用时,需要给形参分配存储单元,形参变量是实参变量的副本;如果传递的是对象,还将调用拷贝构造函数。因此,当参数传递的数据较大时,用引用比用一般变量传递参数的效率和所占空间都好。
(3)使用指针作为函数的参数虽然也能达到与使用引用的效果,但是,在被调函数中同样要给形参分配存储单元,且需要重复使用"*指针变量名"的形式进行运算,这很容易产生错误且程序的阅读性较差;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。而引用更容易使用,更清晰。
3) 在什么时候需要使用“常引用”?
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式:const 类型标识符 &引用名=目标变量名;
4) 将“引用”作为函数返回值类型的格式、好处和需要遵守的规则?
(1)不能返回局部变量的引用。
(2)不能返回函数内部new分配的内存的引用
(3)可以返回类成员的引用,但最好是const
(4)流操作符重载返回值申明为“引用”
(5)在另外的一些操作符中,却千万不能返回引用:+-*/ 四则运算符。它们不能返回引用
5) “引用”与指针的区别是什么?
指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;
而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作。此外,就是上面提到的对函数传ref和pointer的区别。
引用必须被初始化,指针不需要;
引用初始化以后不能改变,指针可以改变;
不存在指向空值得引用,但是存在指向空值的指针;
6) 什么时候需要“引用”?
流操作符<<和>>、赋值操作符=的返回值、拷贝构造函数的参数、赋值操作符=的参数、其它情况都推荐使用引用。
11.聚合和组合,关联的区别
关联是表示两个类的一般性联系,比如“学生”和“老师”就是一种关联关系;
聚合表示has-a的关系,是一种相对松散的关系,聚合类不需要对被聚合类负责,如下图所示,用空的菱形表示聚合关系:
从实现的角度讲,聚合可以表示为:
class A {...} class B { A* a; .....}
当B类销毁的时候,A的指针a并不会被销毁的,在内存中还有a的位置了。
组合表示contains-a的关系,关联性强于聚合:组合类与被组合类有相同的生命周期,组合类要对被组合类负责,采用实心的菱形表示组合关系:
实现的形式是:
class A{...} class B{ A a; ...}
B类销毁了后,A也被同时销毁,在内存中就没有a的位置了。
12. New和Delete与malloc和free的区别?
1) new,delete是操作符,可以重载,只能在c++中使用
2) Malloc,free是函数,可以覆盖,c,c++中都可以使用
3) New可以调用对象的构造函数,对应的delete调用相应的析构函数
4) Malloc仅仅分配内存,free仅仅回收内存,并不执行构造函数和析构函数
5) New delete返回的是某种数据类型指针,malloc free返回的是void指针
12. C++是不是类型安全的?
答案:不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)。C#是类型安全的。
13.C++中struct 和 class 的区别
答案:struct 的成员默认是公有的,而类的成员默认是私有的。struct 和 class 在其他方面是功能相当的。
14.当一个类A 中没有生命任何成员变量与成员函数,这时sizeof(A)的值是多少,如果不是零,请解释一下编译器为什么没有让它为零。
答案:肯定不是零。举个反例,如果是零的话,声明一个class A[10]对象数组,而每一个对象占用的空间是零,这时就没办法区分A[0],A[1]…了。
15. 流操作符重载为什么返回引用。
答:在程序中,流操作符>>和<<经常连续使用。因此这两个操作符的返回值应该是一个仍旧支持这两个操作符的流引用。其他的数据类型都无法做到这一点。
16. 构造函数不能是虚函数。
答:从存储空间角度,虚函数对应一个指向vtable虚函数表的指针,这大家都知道,可是这个指向vtable的指针其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。虚函数的实质是不指名类型按照实际类型执行。
17.重载,重写,隐藏的区别和联系
1) 范围区别:被重写的和重写的函数在两个类中,而重载和被重载的函数在同一个类中
2) 参数的区别:被重写的函数和重写的函数的参数列表一定相同,而被重载函数和重载函数的参数列表一定不同
3) Virtual的区别:重写的基类中被重写的函数必须要有virtual修饰,而重载函数和被重载函数可以被virtual修饰,也可以没有。
4) 隐藏”是指派生类的函数屏蔽了与其同名的基类函数。
- 如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏
- 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字(虚函数)
- 隐藏函数和被隐藏函数不在同一个类中
18.深拷贝和浅拷贝,以及拷贝构造函数和=号运算符
如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝
拷贝构造函数的几种场景:
a.一个对象以值传递的方式传入函数体;
b.一个对象以值传递的方式从函数返回;
c.一个对象需要通过另外一个对象进行初始化。
19.C++对象的内存分布
1) 对象中保存的是虚函数表的指针,虚函数表在编译阶段就生成了,同类有相同的虚函数表,不同类虚函数表不同。
2) 函数不占用空间
3) 成员函数地址是全局已知的,对象的内存空间里根本无须保存成员函数地址
4) 空类话对象的内存大小为1
5) 对象内存分布:虚函数表的指针,基类的成员数据,派生的成员数据
20.模板类和类模板
1)类模板重点指出的是模板,专门用于生产类的模子
2)模板类重点指出的是类,表示由模板产生的类
3)模板的头文件.h时,模板的成员函数实现也必须写在头文件.h中
21.模板函数和函数模板
- 函数模板重点关注的是模板
- 模板函数重点是指函数
- 函数模板的本质是:类型参数化
- 类模板和函数模板主要用于数据结构类型的编程,STL,堆栈,队列,等。
22.谈谈对多态的理解
1)多态的目的:多态实现了同样的调用语句有多种不同的表现形态
2)多谈的实现:实现多态要有继承,虚函数重写,父类指针指向子类对象
3)virtual关键字告诉编译器,该函数要支撑多态,不是根据指针类型判断如何调用,而是要根据指针所指向的实际对象类型来判断如何调用
理论基础是动态连编,虚函数重新
4)是设计模式的基础,主要是函数指针做函数参数实现的
23.为什么要有初始化列表
1)当类中含有类类型成员时,必须用初始式进行初始化
2)当类中含有const修饰的变量时,必须用初始式,因为在函数体里就是赋值了。
24.memset 不能初始化什么类型的结构体
memset 不能初始化包含非pod对象成员的结构体。一般使用构造函数初始化结构体。
25.基类为什么需要虚析构函数
防止内存泄漏。想去借助父类指针去销毁子类对象的时候,不能去销毁子类对象。假如没有虚析构函数,释放一个由基类指针指向的派生类对象时,不会触发动态绑定,则只会调用基类的析构函数,不会调用派生类的。派生类中申请的空间则得不到释放导致内存泄漏。
26.C++中值的传递有哪几种方式?
值传递,引用传递,指针传递
27.C++的三大特性?
封装,继承,多态,是什么?怎么用?为什么用?
封装主要是为了保护数据成员不被外部修改,落脚点在保护上;使用class,struct实现,保护数据隔离减少相互干扰;
继承主要是代码的复用;
多态主要是抽象,相同方式调用,不同表现;
28.为什么使用堆,使用堆的原因?
直到运行时才知道一个对象需要多少空间,不知道对象的生存周期。
29.C++不能重载的运算符有哪些?
. , .* , :: , ?:,siezof
30
31.哪些函数不能声明为虚函数?
普通函数,构造函数,内联函数,静态成员函数,友元函数。
C++11/14/17 新特性
1. nullptr替代NULL。
2. auto类型推导。auto it = v.begin();
decltype,decltype 关键字是为了解决 auto 关键字只能对变量进行类型推导的缺陷而出现的,它的用法和 sizeof 很相似:decltype(表达式)
3. for循环的简写。
for(元素类型 元素对象 : 容器对象)
{
//遍历
}
for(auto ch : str)
{
std::cout << ch << std::endl;
}
4. 使用using解决typedef不能重命名模板的问题。因为typedef可以为类型定义一个新的名称,但是模板不是类型。using Uint = unsigned int;
5. override 和 final 关键字。
override确保在派生类中声明的重载函数跟基类的虚函数有相同的签名。final阻止类的进一步派生和虚函数的进一步重载。
6. 智能指针
1)auto_ptr:避免因潜在的内存问题导致程序崩溃. 因为 auto_ptr 有拷贝语义,拷贝后原对象变得无效
2)为了解决 shared_ptr 的循环引用问题,我们可以祭出 weak_ptr
3)shared_ptr多个指针指向一个对象
4)unique_ptr 不仅安全,而且灵活。
7.vector、map、multimap、unordered_map、unordered_multimap的底层数据结构,以及几种map容器如何选择?
底层数据结构:
- vector基于数组,map、multimap基于红黑树,unordered_map、unordered_multimap基于哈希表。
根据应用场景进行选择:
- map/unordered_map 不允许重复元素
- multimap/unordered_multimap 允许重复元素
- map/multimap 底层基于红黑树,元素自动有序,且插入、删除效率高
- unordered_map/unordered_multimap 底层基于哈希表,故元素无序,查找效率高。
8. C++11引入原始字符串使用R"(字符串)";
1.继承分类:单继承,多重继承,多级继承
2.继承的方式:public,protected,private
3.关键字protected表示该成员不能被外界访问,但可被子类成员访问,因此protected只在继承时才有实质性用处,其他情况无多大用处。
4. 静态成员遵守继承的访问权限限制,不能使用派生类进行初始化
5. 友元在继承关系中不会被传递。
参考:https://www.cnblogs.com/fangyukuan/archive/2010/09/18/1829871.html