后台开发常见面试题-- c++
static
控制变量的存储方式和可见性。
-
修饰局部变量
一般情况下,对于局部变量是存放在栈区的,并且局部变量的生命周期在该语句块执行结束时便结束了。但是如果用static进行修饰的话,该变量便存放在静态数据区,其生命周期一直持续到整个程序执行结束。但是在这里要注意的是,虽然用static对局部变量进行修饰过后,其生命周期以及存储空间发生了变化,但是其作用域并没有改变,其仍然是一个局部变量,作用域仅限于该语句块。 -
修饰全局变量
对于一个全局变量,它既可以在本源文件中被访问到,也可以在同一个工程的其它源文件中被访问(只需用extern进行声明即可)。用static对全局变量进行修饰改变了其作用域的范围,由原来的整个工程可见变为本源文件可见。 -
修饰函数
用static修饰函数的话,情况与修饰全局变量大同小异,就是改变了函数的作用域。 -
C++中的static
如果在C++中对类中的某个函数用static进行修饰,则表示该函数属于一个类而不是属于此类的任何特定对象;如果对类中的某个变量进行static修饰,表示该变量为类以及其所有的对象所有。它们在存储空间中都只存在一个副本。可以通过类和对象去调用。
1)类的静态成员:
在cpp中必须对他进行初始化,初始化时使用作用域运算符来标明他所属类,其属于该类的所有成员共有,只有一个拷贝;
2)类的静态成员函数:
类的静态函数是该类的范畴内的全局函数,不能访问类的私有成员,只能访问类的静态成员,不需要类的实例即可调用;实际上,他就是增加了类的访问权限的全局函数
const的含义及实现机制
const名叫常量限定符,用来限定特定变量,以通知编译器该变量是不可修改的。习惯性的使用const,可以避免在函数中对某些不应修改的变量造成可能的改动。
const修饰基本数据类型
-
const修饰一般常量及数组
基本数据类型,修饰符const可以用在类型说明符前,也可以用在类型说明符后,其结果是一样的。在使用这些常量的时候,只要不改变这些常量的值便好。 -
const修饰指针变量及引用变量&
如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;
如果const位于星号的右侧,const就是修饰指针本身,即指针本身是常量。
const应用到函数中
-
作为参数的const修饰符
调用函数的时候,用相应的变量初始化const常量,则在函数体中,按照const所修饰的部分进行常量化,保护了原对象的属性。
[注意]:参数const通常用于参数为指针或引用的情况; -
作为函数返回值的const修饰符
声明了返回值后,const按照"修饰原则"进行修饰,起到相应的保护作用。 -
const在类中的用法
不能在类声明中初始化const数据成员。正确的使用const实现方法为:const数据成员的初始化只能在类构造函数的初始化表中进行 -
const 修饰类成员函数,其目的是防止成员函数修改被调用对象的值,如果我们不想修改一个调用对象的值,所有的成员函数都应当声明为const成员函数。 其意义上是不能修改所在类的的任何变量。注意:const关键字不能与static关键字同时使用,因为static关键字修饰静态成员函数,静态成员函数不含有this指针,即不能实例化,const成员函数必须具体到某一实例。
-
const修饰类对象,定义常量对象
常量对象只能调用常量函数,别的成员函数都不能调用。
http://www.cnblogs.com/wintergrass/archive/2011/04/15/2015020.html
1)const成员函数可以访问非const对象的非const数据成员、const数据成员,也可以访问const对象内的所有数据成员;
2)非const成员函数可以访问非const对象的非const数据成员、const数据成员,但不可以访问const对象的任意数据成员;
3)作为一种良好的编程风格,在声明一个成员函数时,若该成员函数并不对数据成员进行修改操作,应尽可能将该成员函数声明为const 成员函数。
static, const 和 static const 变量的初始化问题
-
const 常量的在超出其作用域的时候会被释放,但是 static 静态变量在其作用域之外并没有释放,只是不能访问。
-
static 修饰的是静态变量,静态函数。对于类来说,静态成员和静态函数是属于整个类的,而不是属于对象。可以通过类名来访问,但是其作用域限制于包含它的文件中。
-
static 变量在类内部声明,但是必须在类的外部进行定义和初始化。
-
const 常量在类内部声明,但是定义只能在构造函数的初始化列表进行。
虚函数: 虚函数的作用和实现原理,什么是虚函数,有什么作用?
C++的多态分为静态多态(编译时多态)和动态多态(运行时多态)两大类。静态多态通过重载、模板来实现;动态多态就是通过本文的主角虚函数来体现的。
- 虚函数实现原理:包括虚函数表、虚函数指针等
虚函数就是对父类同名函数的重写。在成员函数声明前面加上virtual修饰,就变成了虚函数。调用函数的哪个版本只有在运行时才能确定(动态绑定),因此需要虚函数表,也就导致虚函数会比普通函数成员所花费的时间和空间要多一些。
虚函数的作用说白了就是:当调用一个虚函数时,被执行的代码必须和调用函数的对象的动态类型相一致。编译器需要做的就是如何高效的实现提供这种特性。不同编译器实现细节也不相同。大多数编译器通过vtbl(virtual table)和vptr(virtual table pointer)来实现的。 当一个类声明了虚函数或者继承了虚函数,这个类就会有自己的vtbl。vtbl实际上就是一个函数指针数组,有的编译器用的是链表,不过方法都是差不多。vtbl数组中的每一个元素对应一个函数指针指向该类的一个虚函数,同时该类的每一个对象都会包含一个vptr,vptr指向该vtbl的地址。
虚函数不能是构造函数,不能是静态函数,不能是内联函数,可以是析构函数。
-
结论:
1)每个声明了虚函数或者继承了虚函数的类,都会有一个自己的vtbl,同时该类的每个对象都会包含一个vptr去指向该vtbl
2)虚函数按照其声明顺序放于vtbl表中, vtbl数组中的每一个元素对应一个函数指针指向该类的虚函数
3)如果子类覆盖了父类的虚函数,将被放到了虚表中原来父类虚函数的位置
4)在多继承的情况下,每个父类都有自己的虚表。子类的成员函数被放到了第一个父类的表中
C++中引用和指针的区别
它们都是地址的概念,其中指针指向一块内存,它的内容是所指内存的地址;而引用是某块内存的别名,具体来说,指针是一个变量的地址,引用是一个变量的别名。
但它们的不同之处也很明显,体现在以下方面:
- 指针是一个实体,而引用仅是个别名;
- 引用必须被初始化,指针不必;
- 引用只能在定义时被初始化一次,之后不可变;指针可以改变所指的对象;
- 可以有const指针,但是没有const引用;
- 不存在指向空值的引用,但是存在指向空值的指针,即引用不能为空,指针可以为空;
- “sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小;
- 指针和引用的自增(++)运算意义不一样;
- 程序为指针变量分配内存区域,而引用不需要分配内存区域;
内存分配:
- 内存分配方式有三种:
(1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
(2)在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
(3)从堆上分配,亦称动态内存分配。 程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
STL原理及实现: STL各类型容器实现,STL共有六大组件
STL提供六大组件,彼此可以组合套用:
- 容器(Containers):各种数据结构,如:序列式容器vector、list、deque、关联式容器set、map、multiset、multimap。用来存放数据。从实现的角度来看,STL容器是一种class template。
- 算法(algorithms):各种常用算法,如:sort、search、copy、erase。从实现的角度来看,STL算法是一种 function template。注意一个问题:任何的一个STL算法,都需要获得由一对迭代器所标示的区间,用来表示操作范围。这一对迭代器所标示的区间都是前闭后开区间,例如[first, last)
- 迭代器(iterators):容器与算法之间的胶合剂,是所谓的“泛型指针”。共有五种类型,以及其他衍生变化。从实现的角度来看,迭代器是一种将 operator*、operator->、operator++、operator- - 等指针相关操作进行重载的class template。所有STL容器都有自己专属的迭代器,只有容器本身才知道如何遍历自己的元素。原生指针(native pointer)也是一种迭代器。
- 仿函数(functors):行为类似函数,可作为算法的某种策略(policy)。从实现的角度来看,仿函数是一种重载了operator()的class或class template。一般的函数指针也可视为狭义的仿函数。
- 配接器(adapters):一种用来修饰容器、仿函数、迭代器接口的东西。例如:STL提供的queue 和 stack,虽然看似容器,但其实只能算是一种容器配接器,因为它们的底部完全借助deque,所有操作都由底层的deque供应。改变 functors接口者,称为function adapter;改变 container 接口者,称为container adapter;改变iterator接口者,称为iterator adapter。
- 配置器(allocators):负责空间配置与管理。从实现的角度来看,配置器是一个实现了动态空间配置、空间管理、空间释放的class template。
这六大组件的交互关系:container(容器) 通过 allocator(配置器) 取得数据储存空间,algorithm(算法)通过 iterator(迭代器)存取 container(容器) 内容,functor(仿函数) 可以协助 algorithm(算法) 完成不同的策略变化,adapter(配接器) 可以修饰或套接 functor(仿函数)
- 序列式容器:
- vector-数组,元素不够时再重新分配内存,拷贝原来数组的元素到新分配的数组中。
- list-单链表。
- deque-分配中央控制器map(并非map容器),map记录着一系列的固定长度的数组的地址.记住这个map仅仅保存的是数组的地址,真正的数据在数组中存放着.deque先从map中央的位置(因为双向队列,前后都可以插入元素)找到一个数组地址,向该数组中放入数据,数组不够时继续在map中找空闲的数组来存数据。当map也不够时重新分配内存当作新的map,把原来map中的内容copy的新map中。所以使用deque的复杂度要大于vector,尽量使用vector。
- stack-基于deque。
- queue-基于deque。
- heap-完全二叉树,使用最大堆排序,以数组(vector)的形式存放。
- priority_queue-基于heap。
- slist-双向链表。
-
关联式容器:
- set,map,multiset,multimap-基于红黑树(RB-tree),一种加上了额外平衡条件的二叉搜索树。
- hash table-散列表。将待存数据的key经过映射函数变成一个数组(一般是vector)的索引,例如:数据的key%数组的大小=数组的索引(一般文本通过算法也可以转换为数字),然后将数据当作此索引的数组元素。有些数据的key经过算法的转换可能是同一个数组的索引值(碰撞问题,可以用线性探测,二次探测来解决),STL是用开链的方法来解决的,每一个数组的元素维护一个list,他把相同索引值的数据存入一个list,这样当list比较短时执行删除,插入,搜索等算法比较快。
- hash_map,hash_set,hash_multiset,hash_multimap-基于hashtable。