C++常用基础知识

智能指针https://blog.csdn.net/sinat_21026543/article/details/79811340
  • 问题1,你知道虚函数吗?

    答案:实现多态所必须,父类类型的指针指向子类的实例,执行的时候会执行之类中定义的函数。虚函数是基类希望派生类重新定义的,可以被直接调用,基类中希望派生类继承的函数不能定义为虚函数。
  • 普通函数不能是虚函数,也就是说这个函数必须是某一个类的成员函数,不可以是一个全局函数,否则会导致编译错误。
    静态成员函数不能是虚函数static成员函数是和类同生共处的,他不属于任何对象,使用virtual也将导致错误。
    内联函数不能是虚函数如果修饰内联函数如果内联函数被virtual修饰,计算机会忽略inline使它变成存粹的虚函数。
    构造函数不能是虚函数,否则会出现编译错误。
  • 问题2,析构函数可以是虚函数吗?
    答案: 如果有子类的话,析构函数必须是虚函数。否则析构子类类型的指针时,析构函数有可能不会被调用到。虚析构函数,基类的析构函数通常为虚函数,这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。http://blog.csdn.net/starlee/article/details/619827

  • 问题3,多态的实现。答案:简而言之编译器根据虚函数表找到恰当的虚函数。对于一个父类的对象指针类型变量,如果给他赋父类对象的指针,那么他就调用父类中的函数,如果给他赋子类对象的指针,他就调用子类中的函数。函数执行之前通过查虚函数表来查找调用的函数。多态是指同一个名称可以代表不同的含义;多态可以分为静态多态和动态多态;静态多态是编译时的多态;动态多态是运行时的多态。静态多态是通过函数重载、运算符重载实现;动态多态是通过虚函数实现的。c++多态性,是通过虚函数来实现的,简单地概括为一个接口,多种方法。声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法

  • 问题4.多态的作用
    1)应用程序不必为每一个派生类编写功能调用,只需要对抽象基类进行处理即可。大大提高程序的可复用性。//继承
  • 2)派生类的功能可以被基类的方法或引用变量所调用,这叫向后兼容,可以提高可扩充性和可维护性。 //多态的真正作用,以前需要用switch实现。动态绑定,多态。可以编写程序使用继承层次中任意类型的对象,无需关心对象的具体类型。通过基类的引用(或指针)调用虚函数时发生动态绑定。引用(或指针)既可以指向基类对象,也可以指向派生类对象,这是关键。被调用函数是由所指对象实际类型决定的。
  • 问题5,虚函数表是针对类还是针对对象的?
    答案:虚函数表是针对类的,一个类的所有对象的虚函数表都一样。

  • 问题6,纯虚函数和虚函数有什么区别
    A. 纯虚函数就是定义了一个虚函数但并没有实现,原型后面加"=0"。virtual int func() = 0;
    B.包含纯虚函数的类都是抽象类,不能生成实例。
    C.在虚函数表中纯虚函数,不能够被直接调用,只能在派生类中重写。因为在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。含有纯虚函数的类成为抽象类,不能生成对象。

  • 问题7,构造函数可以是虚函数吗?为什么模版类里面不能存在虚函数(实际上问你虚函数原理)
    答案:构造函数不能是虚函数,每个对象的虚函数表指针是在构造函数中初始化的,因为构造函数没执行完,所以虚函数表指针还没初始化好,构造函数的虚函数不起作用,所以构造函数不能是虚函数。

  • 问题8.构造函数中可以调用虚函数吗? 答案:就算调用虚函数也不起作用,调用虚函数同调用一般的成员函数一样。
  • 问题9.析构函数中可以调用虚函数吗?
    答案: 可以调用会跟普通函数一样的,析构函数中调用虚函数也不起作用,调用虚函数同调用一般的成员函数一样。析构函数的顺序是先派生类后基类,有可能内容已经被析构没了,所以虚函数不起作用。
  • 问题10.构造初始化的执行顺序,析构函数的执行顺序?
    构造时:父类的构造函数 --> 子类的构造函数
    析构时:子类的析构函数 --> 父类的析构函数
  • 问题11.函数的重载,重写和隐藏?
  • 成员函数重载overload特征:a相同的范围(在同一个类中); b函数名字相同; c参数不同; d virtual关键字可有可无。

    重写(盖override)是指派生类函数覆盖基类函数,特征是:a不同的范围,分别位于基类和派生类中;b函数的名字相同c参数相同;d基类函数必须有virtual关键字。

    重定义(隐藏redefining)是指派生类的函数屏蔽了与其同名的基类函数,规则如下:a如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏;b如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有vitual关键字,此时,基类的函数被隐藏。

  • 问题12.地址的增长方向,堆和栈的区别?
    除了栈以外,堆、只读数据区、全局变量地址增长方向都是从低到高的,数组元素是分布在连续递增的地址上的

向高地址扩展
C/C++函数库提供的,不一定唯一。不同堆分的内存无法互相操作。特点是灵活方便,数据适应面广泛,但是效率有一定降低,堆空间的分配总是动态的,虽然程序结束时所有的数据空间都会被释放回系统,但是精确的申请内存/释放内存匹配,不然内存泄漏。
顺序随意
不连续

高地址向低地址
机器系统提供的数据结构

1.栈分配比堆快,只需要一条指令就呢给配所有的局部变量

2.栈不会出现内存碎片

3.栈对象好管理

对子程序的调用就是直接利用栈完成的,进程/线程是唯一的
特点是快速高效,缺点是有限制,数据不灵活。
先入后出
连续
  • 问题13.struct和class的区别
  • struct和class大体相似,但是区别的访问权限
    struct默认的访问权限是public,class默认是private
  • 问题14.C++内存管理方式
    自由存储区全局/静态区常量存储区

:存放函数参数以及局部变量,在出作用域时,将自动被释放.栈内存分配运算内置于处理器的指令集中,效率很高,但分配的内存容量有限.

:new分配的内存块(包括数组,类实例等),delete手动释放.如果未释放,在整个程序结束后,OS会帮你回收掉.malloc分配的内存块,free手动释放.

代码区:是编译器生成的一个exe区段,拥有可读和可执行属性。存放函数体的二进制代码。

全局/静态区:全局变量(global)和静态变量(static)存于此处.(在以前的C语言中,全局变量又分为初始化的和未初始化的,C++不分)

常量存储区:常量(const)存于此处,此存储区不可修改

局部静态变量和全局变量的区别?

全局变量:具有全局作用域,全局变量只需在一个源文件中定义,就可以作用于所有的源文件。

静态局部变量:具有局部作用域,它只被初始化一次,自从第一次被初始化直到程序运行结束都一直存在

静态全局变量:具有全局作用域,它与全局变量的区别在于如果程序包含多个文件的话,它作用于定义它的文件里,不能作用到其它文件里,即被 static 关键字修饰过的变量具有文件作用域。

局部变量:具有局部作用域,它是自动对象(auto),它在程序运行期间不是一直存在,而是只在函数执行期间存在,函数的一次调用执行结束后,变量被撤销,其所占用的内存也被收回。

  • 问题15.C++是不是类型安全的?不是两个不同类型的指针之间可以强制转换(用reinterpret cast)。
  • 问题16.一个类没有定义属性和函数sizeof大小是多少,编辑器为什么这么做?
    一个空类对象的大小是1byte。这是被编译器安插进去的一个字节,这样就使得这个空类的实例得以在内存中配置独一无二的地址
  • 问题17.函数内存空间放在哪里?放在代码段里
  • 问题18.局部变量可否与全局变量重名?可以,类型也可以不同,全局变量就是定义在函数体外的变量。
  • 问题19.引用和指针有什么区别?
    1.指针保存的是另外一个对象的地址,引用是定义一个对象的别名。
    2.对指针进行解引用操作能够访问所指的对象,指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作
    3.引用不能为空引用必须初始化,引用在赋值之后不能修改
    4.对引用进行赋值改变的是指向的对象的值,而不是让该引用指向另外的对象。指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。
  • 问题20.将引用作为函数参数和返回值有哪些特点?有哪些注意事项?
    作为函数参数时候不产生拷贝,直接传递内存地址的值。
    作为返回值的时候必须的有效的,而指针可以是无效,例如空指针等等
  • 问题21.阐述extern "C"和extern的作用?
    extern "C"告知编译器以C的形式编译,因为c中没有函数重载,函数在底层的签名是函数名,而c++的函数签名是函数名返回值参数类型1参数类型2__
    extern作用声明外部变量,现在现代编译器一般采用按文件编译的方式,因此在编译时,各个文件中定义的全局变量是互相透明的,也就是说,在编译时,全局变量的可见域限制在文件内部。只进行声明而不定义,表示是当前变量或者函数的定义不在当前模块(文件)内,在其他的模块中,告诉编译器在其它文件中找这个变量或者函数的定义。一般都把一些经常用到的枚举和变量之类的写在.h头文件中。这样要引用时直接include “头文件名”就可以了调用里面所有的枚举和变量了。在大型项目中,引用别的.c文件中的函数则只能用extern,因为.c文件是不能 include的。所以想引用别的.c文件中的函数和全局变量、枚举等等的就只能用extern。extern 可以使用其它文件的变量,如:file1中,int counter,file 2 中, extern int counter. 对于const常量,必须显式指定为extern,file1中,extern int counter = 0; file2 中,extern int counter。
  • 问题22.内存对齐问题,为什么需要内存对齐?
        需要字节对齐的根本原因在于CPU访问数据的效率问题。假设上面整型变量的地址不是自然对齐,比如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第一次取从0x00000002-0x00000003的一个short,第二次取从0x00000004-0x00000005的一个short然后组合得到所要的数据,如果变量在0x00000003地址上的话则要访问三次内存,第一次为char,第二次为short,第三次为char,然后组合得到整型数据。而如果变量在自然对齐位置上,则只要一次就可以取出数据。一些系统对对齐要求非常严格,比如sparc系统,如果取未对齐的数据会发生错误.

    http://www.cppblog.com/cc/archive/2006/08/01/10765.html
    (PS:内存块的声明是按照内存变量书写顺序来设定的)
  • 问题23.什么是内联函数inline
    内联函数是指用inline关键字修饰的函数。在类内定义的函数被默认成内联函数。内联函数从源代码层看,有函数的结构,而在编译后,却不具备函数的性质。内联函数不是在调用时发生控制转移,而是在编译时将函数体嵌入在每一个调用处。编译时,类似宏替换,使用函数体替换调用处的函数名。一般在代码中用inline修饰,但是能否形成内联函数,需要看编译器对该函数定义的具体处理。
  • 问题24.内联函数inline和宏定义#define的区别?
    内联函数在编译时展开,宏在预编译时展开。在编译的时候,内联函数可以直接被镶嵌到目标代码中,而宏只是一个简单的文本替换。内联函数可以完成诸如类型检测、语句是否正确等编译功能,宏就不具有这样的功能。
  • 问题26.#define和const定义一个常量的区别? const和static的区别?static标识的成员有什么意义?可被哪些函数调用?
  • 1.用const修饰函数的参数:如果参数作输出用,不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加const修饰,否则该参数将失去输出功能。const只能修饰输入参数:如果输入参数采用“指针传递”,那么加const修饰可以防止意外地改动该指针,起到保护作用。
  • 2.用const修饰函数的返回值:如果给以“指针传递”方式的函数返回值加const修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const修饰的同类型指针。
  • 3. const 成员函数:任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改了数据成员,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性。
  • 【const和static的区别】
const就是只读的意思,只在声明中使用;
static一般有2个作用,规定作用域和存储方式.对于局部变量,static规定其为静态存储方式,每次调用的初始值为上一次调用的值,调用结束后存储空间不释放;
对于全局变量,如果以文件划分作用域的话,此变量只在当前文件可见;对于static函数也是在当前模块内函数可见.
static const 应该就是上面两者的合集.
下面分别说明:
全局:
const,只读的全局变量,其值不可修改.
static,规定此全局变量只在当前模块(文件)中可见.
static const,既是只读的,又是只在当前模块中可见的.
文件:
文件指针可当作一个变量来看,与上面所说类似.
函数:
const,返回只读变量的函数.
static,规定此函数只在当前模块可见.
类:
const,一般不修饰类
static,C++中似乎没有静态类这个说法,一般还是拿类当特殊的变量来看.C#中有静态类的详细说明,且用法与普通类大不相同.
  • 【const和#define的区别】
  • 角度1: 就定义常量说的话:

    const 定义的常数是变量 也带类型, #define 定义的只是个常数 不带类型。

    角度2: 就起作用的阶段而言:
    define是在编译的预处理阶段起作用,而const是在 编译、运行的时候起作用。

    角度3: 就起作用的方式而言:
    define只是简单的字符串替换,没有类型检查。而const有对应的数据类型,是要进行判断的,可以避免一些低级的错误。 define只是简单的字符串替换会导致边界效应

    角度4:就空间占用而言:
    define预处理后占用代码段的空间

    const本质上还是数据,占用数据段的空间

       角度5: 从代码调试的方便程度而言:

        const常量可以进行调试的,define是不能进行调试的,因为在预编译阶段就已经替换掉了

        角度6: 从是否可以再定义的角度而言:
        const不足的地方,是与生俱来的,const不能重定义,而#define可以通过#undef取消某个符号的定义,再重新定义。

        角度7: 从某些特殊功能而言:
        define可以用来防止头文件重复引用,而const不能

        角度8:从某些复杂功能的实现的实现角度来看:
        使用define会使得代码看起来非常简单,而const无法实现该功能

问题27.面向对象的思想?面向对象的特性?有什么优点,比面向过程的语言好在哪?
问题28.函数式编程?

问题29.STL容器比较,以及底层结构?vector和map容器有什么特别好的地方?或者有什么不足之处?

选择顺序容器类型的一些准则:如果我们需要随机访问一个容器,则vector要比List好得多。如果我们一直要存储元素的个数,则vector又是一个比list好的选择。如果我们需要的不只是在容器两端插入和删除元素,则list显然比vector好。除非我们需要在容其首部插入和删除元素,否则vector要比deque好。

vector 底层数据结构为数组,内存中分配一块连续的内存空间进行存储。在末尾可以进行高效的插入删除,但是不能在头部进行插入删除。内部插入的效率很低,可以支持随机访问[],节省空间。vector将空间分为“容量(capacity)”和“大小(size)”两个部分,这是为了方便在堆上预先分配好空间。在增加新的元素时,如果大小即将超过当前的“容量”,那么“容量”就会扩充至原来的两倍,直到足够为止。

vectorpush_back()在内存里的详细操作过程

vector并不是简单在当前末尾的地址后面继续申请新的空间,而是要经过重新分配容量->全部元素复制到新分配的容量空间->释放原空间这三个步骤的,并且由于所有元素的堆地址可能都发生了改变,vector原有的迭代器都会失效。vector表示一段连续的内存区域,随机访问效率很高,因为每次访问离起始处的位移都是固定的,但是在随意位置插入删除元素效率很低,因为它需要将后面的元素复制一遍。
list,是一个双向链表,内部插入删除很方便,可以在两端进行插入和删除的操作,不能进行内部的随机访问,内存不连续。list表示非连续的内存区域,并通过一对指向首尾元素的指针双向链接起来,从而允许向前和向后两个方向进行遍历。list任意位置插入和删除元素的效率都很高:指针必须被重新赋值,但不需要用拷贝素来实现移动。他对随机访问支持不好,需要遍历中间的元素。每个元素有两个指针的额外空间开销。
deque底层数据结构为一个中央控制器和多个缓冲区,双端队列,类似vector,支持首尾(中间不能)快速增删,也支持随机访问,内部插入或者删除,但是占用内存多。deque(双端队列,发音为'deck')也表示一段连续的内存区域,但是他支持高效的在其首部插入和删除元素。

stack 底层一般用list,deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时一般定义的变量在栈里面,如果没有初始化随机值在变量后面,若初始化给了值内容存在常量区

queue 底层一般用list,deque实现,封闭头部即可,不用vector的原因应该是容量大小有限制,扩容耗时

stack、 queue 是适配器,而不叫容器,因为是对容器的再封装

priority_queue的底层数据结构一般为vector为底层容器,heap为处理规则来管理底层容器实现mallocnew,new放在堆里,申请需要释放

set   底层数据结构为红黑树,有序,不重复

multiset 底层数据结构为红黑树,有序,可重复 

map底层是用红黑树(二叉查找树,平衡树AVL),有序,不重复来实现的,查找时间复杂度为o(logn)

multimap底层数据结构为红黑树,有序,可重复

hash_set 底层数据结构为hash表,无序,不重复

hash_multiset 底层数据结构为hash表,无序,可重复 

hash_map   底层数据结构为hash表,无序,不重复

问题30.struct和class 区别:两个关键字都是进行类的定义。struct也可以定义类,和class定义的类唯一不同之处就在于默认的初始访问级别,struct在不声明public或者private的时候默认是public,class是private。
问题31.容器的迭代器:是一种检查容器内元素并遍历元素的数据类型 ,可以理解成指针。比如vector的迭代器:
vector<int>::iterator iter = ivec.begin()
//这就是定义了iter这个迭代器,指向ivec的第一个元素
*iter = 0//将ivec的第一个元素置零
for(vector<int>::iterator iter = ivec.begin(); iter!= ivec.end();++iter)
    *iter = 0;//将所有元素置零

如果关键字是const_iterator,那么就不允许进行赋值

问题32.s* 与 &: *是解引用操作符,用来获取指针所指向的对象,&是引用操作符,用来获取地址。

问题33.函数参数传递: 分为值传递,指针形参,引用形参。其中值传递是形参复制实参,调用函数里面不影响实参;指针传递是形参复制实参指针,调用函数并不影响实参指针,但是会影响指针指向的对象;引用传递会影响到实参。
有些时候,需要用到指针传递,比如一个简单的swap函数,swap(int *a,int *b),在调用的时候就是swap(&x,&y),这样在书写的时候比较麻烦。我们还可以用引用传递来代替指针传递,效果是一样的,swap函数定义为:swap(int &a, int &b),调用的时候比较简单:swap(x,y)。调用的过程中,发生了两个引用类型的变量定义:int &a = x,int &b = y. 即a,b分别是x,y的别名。

问题34.malloc 和 new 的区别

malloc与free是C++/C语言的标准库函数,new/delete是C++的运算符。
由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free; new 返回的是指定类型的指针,malloc返回的是void指针。

问题35.红黑树是怎么构造的

红黑树 B树 B+树 B-树

二叉查找树(BST):

二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:

l 若左子树不空,则左子树上所有结点的值均小于它的根结点的值。

l 若右子树不空,则右子树上所有结点的值均大于它的根结点的值。

l 左、右子树也分别为二叉排序树。

l 没有键值相等的节点(因此,插入的时候一定是叶子节点)。

删除算法

l 要删除节点是叶子节点。

l 要删除的节点只有一个孩子(左孩子或右孩子),这种情况比较简单,只需要将该孩子连接到当前节点的父节点即可。

l 要删除的节点有两个孩子,这个时候的算法就比较复杂(相比较于只有一个孩子的情况)。首先我们需要找到待删除节点的左子树上的最大值节点,或者右子树上的最小值节点,然后将该节点的参数值与待删除的节点参数值进行交换,最后删除该节点,这样需要删除的参数就从该二叉树中删除了。

问题36.内存对齐原则

一、结构体变量的首地址能够被其最宽基本类型成员大小与对齐基数中的较小者所整除

二、结构体每个成员相对于结构体首地址的偏移量(offset)都是该成员大小与对齐基数中的较小者的整数倍,如有需要编译器会在成员之间加上填充字节(internaladding);

三、结构体的总大小为结构体最宽基本类型成员大小与对齐基数中的较小者的整数倍,如有需要编译器会在最末一个成员之后加上填充字节(trailingpadding)。

关于字节对齐:

查阅资料后发现,字节对齐有助于加快计算机的取数速度,否则就得多花指令周期了。为此,编译器默认会对结构体进行处理(实际上其它地方的数据变量也是如此),让宽度为2的基本数据类型(short等)都位于能被2整除的地址上,让宽度为4的基本数据类型(int等)都位于能被4整除的地址上,以此类推。这样,两个数中间就可能需要加入填充字节,所以整个结构体的sizeof值就增长了。

内存对齐的主要作用是:

1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

2、性能原因:经过内存对齐后,CPU的内存访问速度大大提升。具体原因稍后解释。CPU把内存当成是一块一块的,块的大小可以是2,4,8,16字节大小,因此CPU在读取内存时是一块一块进行读取的。块大小成为memoryaccessgranularity(粒度)本人把它翻译为“内存读取粒度”。数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

 struct/class/union内存对齐原则有四个

1).数据成员对齐规则:结构(struct)(或联合(union))的数据成员,第一个数据成员放在offset为0的地方,以后每个数据成员存储的起始位置要从该成员大小或者成员的子成员大小(只要该成员有子成员,比如说是数组,结构体等)的整数倍开始(比如int在32位机为4字节, 则要从4的整数倍地址开始存储),基本类型不包括struct/class/uinon。

2).结构体作为成员:如果一个结构里有某些结构体成员,则结构体成员要从其内部"最宽基本类型成员"的整数倍地址开始存储.(struct a里存有struct b,b里有char,int ,double等元素,那b应该从8的整数倍开始存储.)。                     1  4  8

3).收尾工作:结构体的总大小,也就是sizeof的结果,.必须是其内部最大成员的"最宽基本类型成员"的整数倍.不足的要补齐.(基本类型不包括struct/class/uinon)。

4).sizeof(union),以结构里面size最大元素为union的size,因为在某一时刻,union只有一个成员真正存储于该地址。

角度8:

猜你喜欢

转载自blog.csdn.net/sinat_21026543/article/details/79825371