美文网首页
后台开发常见面试题-- c++

后台开发常见面试题-- c++

作者: 什锦甜 | 来源:发表于2018-07-19 00:28 被阅读197次

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提供六大组件,彼此可以组合套用:

  1. 容器(Containers):各种数据结构,如:序列式容器vector、list、deque、关联式容器set、map、multiset、multimap。用来存放数据。从实现的角度来看,STL容器是一种class template。
  2. 算法(algorithms):各种常用算法,如:sort、search、copy、erase。从实现的角度来看,STL算法是一种 function template。注意一个问题:任何的一个STL算法,都需要获得由一对迭代器所标示的区间,用来表示操作范围。这一对迭代器所标示的区间都是前闭后开区间,例如[first, last)
  3. 迭代器(iterators):容器与算法之间的胶合剂,是所谓的“泛型指针”。共有五种类型,以及其他衍生变化。从实现的角度来看,迭代器是一种将 operator*、operator->、operator++、operator- - 等指针相关操作进行重载的class template。所有STL容器都有自己专属的迭代器,只有容器本身才知道如何遍历自己的元素。原生指针(native pointer)也是一种迭代器。
  4. 仿函数(functors):行为类似函数,可作为算法的某种策略(policy)。从实现的角度来看,仿函数是一种重载了operator()的class或class template。一般的函数指针也可视为狭义的仿函数。
  5. 配接器(adapters):一种用来修饰容器、仿函数、迭代器接口的东西。例如:STL提供的queue 和 stack,虽然看似容器,但其实只能算是一种容器配接器,因为它们的底部完全借助deque,所有操作都由底层的deque供应。改变 functors接口者,称为function adapter;改变 container 接口者,称为container adapter;改变iterator接口者,称为iterator adapter。
  6. 配置器(allocators):负责空间配置与管理。从实现的角度来看,配置器是一个实现了动态空间配置、空间管理、空间释放的class template。

这六大组件的交互关系:container(容器) 通过 allocator(配置器) 取得数据储存空间,algorithm(算法)通过 iterator(迭代器)存取 container(容器) 内容,functor(仿函数) 可以协助 algorithm(算法) 完成不同的策略变化,adapter(配接器) 可以修饰或套接 functor(仿函数)

  • 序列式容器:
  1. vector-数组,元素不够时再重新分配内存,拷贝原来数组的元素到新分配的数组中。
  2. list-单链表。
  3. deque-分配中央控制器map(并非map容器),map记录着一系列的固定长度的数组的地址.记住这个map仅仅保存的是数组的地址,真正的数据在数组中存放着.deque先从map中央的位置(因为双向队列,前后都可以插入元素)找到一个数组地址,向该数组中放入数据,数组不够时继续在map中找空闲的数组来存数据。当map也不够时重新分配内存当作新的map,把原来map中的内容copy的新map中。所以使用deque的复杂度要大于vector,尽量使用vector。
  4. stack-基于deque。
  5. queue-基于deque。
  6. heap-完全二叉树,使用最大堆排序,以数组(vector)的形式存放。
  7. priority_queue-基于heap。
  8. 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。

相关文章

网友评论

      本文标题:后台开发常见面试题-- c++

      本文链接:https://www.haomeiwen.com/subject/ayaapftx.html