C++面试知识点总结

1、谈谈你对命名空间的理解

namespace是一个关键字:随着工程量的增加,变量名上不可避免的会出现重名,防止名称冲突(在两个不同的命名空间中,即使2个变量名相同,也是2个不同的变量),在实际工作中,基本都使用标准命名空间。

命名空间只能全局范围内定义,不能在定义在函数内部。

命名空间内,可以存放变量、函数、结构体、类;也可也嵌套其他的命名空间。

命名空间可以匿名(但一般不这样使用),类似静态全局变量。

命名空间是可以起别名的。

2、谈谈指针和引用的区别

引用是给变量起别名,内部实现是指针常量(int* const ref = &a),其可以简单的理解为本体指针存放的是变量的地址。

引用的本质是指针常量,其指向不可修改,而指针可以改变指向。

引用创建的同时必须初始化,指针创建的时候可以不必初始化。

引用不能为空,指针可以为NULL。

”引用变量ref”的内存单元保存的是“被引用变量a"的地址sizeof(引用)=指向变量的大小sizeof(指针)= 指针本身的大小。

引用使用的时候无需解引用,指针需要解引用。

指针和引用的++,--运算意义不一样。

3、谈谈你对内联函数的理解

宏函数的缺陷1:需要将实现“加括号”,以保证优先级的完整性。

宏函数的缺陷2:即使加了括号 有些情况依然有缺陷。

内联函数本身是一个真正的函数,但宏函数不是函数。

内联函数具有普通函数的所有行为唯一不同之处在于:内联函数会在适当的地方像定义宏一样展开,可以以空间换时间,因此内联函数既可以避免宏函数的缺陷,也可以避免普通函数入栈的时间开销。

在普通函数前面加上inline关键字使之成为内联函数。

如果有函数声明,函数本身和声明必须同时加inline关键字,否则视为普通函数。

任何在类内部定义的函数都会自动成为内联函数。

下列情况,普通函数即使指定为内联函数,编译器也可能考虑不按内联编译

1.存在任何形式的循环语句

2.存在过多的条件判断语句

3.函数体过于庞大

4.对函数进行取址操作

使用方式建议:(1)内联仅仅只是给编译器一个建议,编译器不一定会接受这种建议(2)如果你没有将函数声明为内联函数,那么编译器也可能将此函数做内联编译(一个好的编译器会将内联小的、简单的函数)因此,不用刻意使用内联函数,可以交给编译器去自行处理。

4、谈谈函数的重载条件

函数重载:在C语言中,函数名必须是唯一的,程序中不允许出现同名的函数。在c++中是允许出现同名的函数,即在同一作用域内,具有相同函数名,不同参数列表的一组函数,称为函数重载。

函数重载实现的原理:

编译器为了实现函数重载,也是默认为我们做了一些幕后的工作,编译器用不同的参数类型来修饰不同的函数名,比如void func();编译器可能会将函数名修饰成func,当编译器碰到void func(int x),编译器可能将函数名修饰为func_int,当编译器碰到void func(int x,char c),编译器可能会将函数名修饰为_func_int_char我这里使用“可能”这个字眼是因为编译器是如何修饰重载函数名称并没有一个统一的标准,所以不同的编译器可能产生不同的内部名。

函数重载实现的条件:

同一个作用域、参数的个数不同、参数类型不同、参数的顺序不同。

5、谈谈C与C++中struct的不同点

C语言中的struct只有数据,c++中的struct不止有数据还有函数

6、如何理解C++的封装性

封装特性包含两个方面,一个是属性和变量合成一个整体,一个是给属性和函数增加访问权限。

7、谈谈你对C++构造与析构的理解

对象的初始化和清理也是两个非常重要的安全问题,一个对象或者变量没有初始时,对其使用后果是未知的,同样的使用完一个变量,没有及时清理,也会造成一定的安全问题。C++为了给我们提供这种问题的解决方案,构造函数和析构函数,这两个函数将会被编译器自动调用,完成对象初始化和对象清理工作。无论你是否喜欢,对象的初始化和清理工作是编译器强制要我们做的事情,即使你不提供初始化操作和清理操作,编译器也会给你增加默认的操作,只是这个默认初始化操作不会做任何事,所以编写类就应该顺便提供初始化函数。

构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。

析构函数:主要用于对象销毁前系统自动调用,执行一些清理工作。

8、构造函数的分类

按参数类型:分为无参构造函数和有参构造函数 按类型分类:普通构造函数和拷贝构造函数(复制构造函数)。

9、构造函数的调用规则

默认情况下,C++编译器至少为我们写的类增加3个函数。 1.默认构造函数(无参,函数体为空)2.默认析构函数(无参,函数体为空)3.默认拷贝构造函数,对类中非静态成员属性简单值拷贝 如果用户定义拷贝构造函数,C++不会再提供任何默认构造函数,如果用户定义了普通构造函数(非拷贝构造),C++不在提供默认的无参构造函数,但是会提供默认拷贝构造函数。

10、谈谈你对浅拷贝与深拷贝的区别

浅拷贝

同一类型的对象之间可以赋值,使得两个对象的成员变量的值相同,两个对象仍然是独立的两个对象,这种情况被称为浅拷贝。一般情况下,浅拷贝没有任何副作用,但是当类中有指针,并且指针指向动态分配的内存空间,析构函数做了动态内存释放的处理,会导致内存问题。

深拷贝

当类中有指针,并且此指针有动态分配空间,析构函数做了释放处理,往往需要自定义拷贝构造函数,自行给指针动态分配空间,这种情况被称为深拷贝。

11、谈谈啥叫对象成员以及对象成员的构造函数调用方式

在类中定义的数据成员一般都是基本的数据类型。但是类中的成员也可以对象,叫做对象成员。

C++中对对象的初始化是非常重要的操作,当创建一个对象的时候,C++编译器必须确保调用了所有子对象的构造函数。如果所有的子对象有默认构造函数,编译器可以自动调用他们。但是如果子对象没有默认的构造函数,或者想指定调用某个构造函数怎么办?

那么是否可以在类的构造函数直接调用子类的属性完成初始化呢?但是如果子类的成员属性是私有的,我们是没有办法访问并完成初始化的。

解决办法非常简单:对于子类调用构造函数,C++为此提供了专门的语法,即构造函数初始化列表。当调用构造函数时,首先按各对象成员在类定义中的顺序(和参数列表的顺序无关)依次调用它们的构造函数,对这些对象初始化,最后再调用本身的函数体。也就是说,先调用对象成员的构造函数,再调用本身的构造函数。析构函数和构造函数调用顺序相反,先构造,后析构。

12、谈谈你对explicit的理解

C++提供了关键字explicit,禁止通过构造函数进行的隐式转换。声明为explicit的构造函数不能在隐式转换中使用。

13、谈谈C中malloc free与C++中的new delete

C++中的malloc free的问题:

1、程序员必须确定对象的长度。

2、malloc返回一个void指针,C++不允许将void赋值给其他任何指针,必须强转。

3、malloc可能申请内存失败,所以必须判断返回值来确保内存分配成功。

4、用户在使用对象之前必须记住对他初始化,构造函数不能显式调用初始化(构造函数是由编译器调用),用户有可能忘记调用初始化函数。

总结:C的动态内存分配函数太复杂,容易令人混淆,是不可接受的,C++中我们推荐使用new和delete

new操作符能确定在调用构造函数初始化之前内存分配是成功的,所以不用显式确定调用是否成功

delete表达式先调用析构函数,然后释放内存。

14、谈谈你对static静态成员变量的理解

在一个类中,若将一个成员变量声明为static,这种成员称为静态成员变量。与一般的数据成员不同,无论建立了多少个对象,都只有一个静态数据的拷贝。静态成员变量,属于某个类,所有对象共有。静态变量,是在编译阶段就分配空间,对象还没创建时,就已经分配空间。

注意:

1、静态成员变量必须在类中声明,在类外定义。2、静态数据成员不属于某个类,在为对象分配空间中不包括静态成员所占空间。3、静态数据成员可以通过类名或者对象名来引用。

15、谈谈你对static静态成员函数的理解

在类定义中,前面又static说明的成员函数称为静态成员函数。静态成员函数使用方式和静态变量一样,同样在对象没有创建前,即可通过类名调用。静态成员函数主要为了访问静态变量,但是不能访问普通成员变量。

静态成员函数的一样,不在于信息共享,数据沟通,而在于管理静态数据成员,完成对静态数据成员的封装。

1、静态成员函数只能访问静态变量,不能访问普通成员变量2、静态成员函数的使用和静态成员变量一样3、静态成员函数也有访问权限4、普通成员函数可访问静态成员变量、以可以访问非静态成员变量。

16、谈谈你对this的理解

成员函数通过this指针即可知道操作的是哪个对象的数据。this指针是一种隐含指针,它隐含于每个类的非静态成员函数中。this指针无需定义,直接使用即可。

注意:静态成员函数内部没有this指针,静态成员函数不能操作非静态成员变量。

17、谈谈你对友元的理解

类的主要特点之一是数据隐藏,即类的私有成员无法在类的外部(作用域之外)访问。但是,有时候需要在类的外部访问私有成员,怎么办?解决办法是使用友元函数,友元函数是一种特权函数,C++允许这个特权函数访问私有成员。

1、friend关键字只出现在声明处2、其他类、类成员函数、全局函数都可声明为友元3、友元函数不是类的成员。不带this指针4、友元函数可访问对象任意成员属性,也包括私有属性。

友元的注意事项

1、友元关系不能被继承。2、友元关系是单向的,类A是类B的朋友,但类B不一定是类A的朋友。

3、友元关系不具有传递性。类B是类A的朋友,类C是类B的朋友,但类C不一定是类A 的朋友。

18、谈谈你对继承的理解

C++最重要的特征是代码重用,通过继承机制可以利用已有的数据类型来定义新的数据类型,新的类不仅拥有旧类的成员,还拥有新定义的成员。一个类B继承于A类,或称从类A派生类B。这样的话,类A成为基类(父类),类B成为派生类 (子类)。派生类中的成员,包含两大部分:1、一类是从基类继承过来的,一类是自己增加的成员。2、从基类继承过来的表现其共性,而新增的成员体现了其个性。

19、谈谈继承中的构造与析构的顺序

1、子类对象在创建时会首先调用父类的构造函数 2、父类构造函数执行完毕后,才会调用子类的构造函数 3、当父类构造函数有参数时,需要在子类初始化列表(参数列表)中显式调用父类构造函数 析构函数调用顺序和构造函数相反。

20、继承中同名成员的处理方法

1、当子类成员和父类成员同名时,子类依然从父类继承同名成员 2、如果子类有成员和父类同名,子类访问其成员默认访问子类的成员(本作用域,就近原则) 3、在子类通过作用域::进行同名成员区分(在派生类中使用基类的同名成员,显示使用类名限定符)

21、那些函数是无法继承的

是所有的函数都能自动从基类继承到派生类中。构造函数和析构函数用来处理对象的创建和析构操作。构造和析构函数只知道对他们的特定层次的对象做什么,也就是说构造函数和析构函数不能被继承,必须为每一个特定的派生类分别创建。

22、静态多态与动态多态的区别

静态多态和动态多态的区别就是函数地址是早绑定(静态联编)还是晚绑定(动态联编)。如果函数的调用,在编译阶段就可以确定函数的调用地址,并产生代码,就是静态多态(编译时多态),就是说地址是早绑定的。而如果函数的调用地址不能在编译期间确定,而需要在允许时才能决定,这就是属于晚绑定(动态多态,运行时多态)

23、C++的动态捆绑机制是怎么样的?

首先、我们看看编译器如何处理虚函数。当编译器发现我们的类中有虚函数的时候,编译器会创建一张虚函数表,把虚函数的函数入口地址放到虚函数表中,并且在类中秘密增加一个指针,这个指针就是vpointer,这个指针是指向对象的虚函数表。在多态调用的时候,根据Vptr指针,找到虚函数表来实现动态绑定。

24、多态成立的条件

1、有继承 2、子类重写父类虚函数 (a)返回值,函数名字,函数参数,必须和父类完全一致(析构函数除外)(b)子类中virtual关键字可写可不写,建议写 3、类型兼容,父类指针,父类引用指向子类对象。

25、虚析构的作用

虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。

26、纯虚析构与非纯虚析构的区别

纯虚析构函数在C++中是合法的,但是在使用的时候有一个额外的限制:必须为纯虚析构函数提供一个函数体。

纯虚析构函数和非纯虚析构函数之间唯一的不同之处在于纯虚析构函数使得基类是抽象类,不能创建基类的对象。

注意:如果类的目的不是为了实现多态,作为基类来使用,就不要声明虚析构函数,反之,则应该为类声明虚析构函数。

27、谈谈重载、重写、重定义的概念

1、重载,同一作用域的同名函数 a、同一个作用域 b、参数个数,参数顺序,参数类型不同 c、和函数返回值没有关系 d、const也可作为重载条件 //do(const teacher &t){} do(teacher &t) 2、重定义(隐藏) a、有继承 b、子类(派生类)重新定义父类(基类)的同名成员(非virtual函数) 3、重写(覆盖) a、有继承 b、子类(派生类)重写父类(基类)的virtual函数 c、函数返回值,函数名字,参数类型,必须和基类中的虚函数一致。

28、谈谈你对模板的理解

所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来代表。这个通用函数就成为函数模板。

C++提供两种模板机制:函数模板和类模板

模板把函数或类要处理的数据类型参数化,表现为参数的多态性,成为类属。

模板用于表达逻辑结构相同,但具体数据元素类型不同的数据对象的通用行为。

29、函数模板和普通函数的区别

函数模板不允许自动类型转换。普通函数可以自动实现类型转换。

30、函数模板和普通函数同时出现时的调用机制

1、C++编译器优先考虑普通函数 2、可以通过空模板实参列表的语法限定编译器只能通过模板匹配 3、函数模板可以像普通函数那样可以被重载 4、如果函数模板可以产生一个更好的匹配,那么选择模板

32、谈谈静态转换、动态转换、常量转换、重新解析转换的区别

静态转换(static_cast)

用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。1、进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;2、进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所有是不安全的。3、用于基本数据类型之间的转换,如把int转换成char,把char转换成int。这种转换的安全性也要开发人员来保证。

动态转换(dynamic_cast)

1、dynamic_cast主要用于类层次间的上行转换和下行转换;2、在类层次间进行上行转换时,dynamic_cast和static_cast的效果时一样的;3、在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

常量转换(const_cast)

1、该运算符用来修改类型的const属性。2、常量指针被转化成非常量指针,并且仍然指向原来的对象;3、常量引用被转换成非常量引用,并且仍然指向原来的对象;

注意:不能直接对非指针和非引用的变量使用const_cast操作符去直接移除他的cosnt.

重新解析转换(reinterpret_cast)

这是最不安全的一种转换机制,最有可能出问题。主要用于将一种数据类型从一种类型转换为另一种类型。它可以将一个指针转换成一个整数,也可以将一个整数转换成一个指针。

32、谈谈你对异常的理解

异常处理就是处理程序中的错误。所谓错误是指在程序运行过程中发生的一些异常事件(如:除0溢出,数组下标越界,所要读取的文件不存在,空指针,内存不足等等。)

33、谈谈C++的异常机制相比C语言的异常处理的优势

1、函数的返回值可以忽略,但异常不可忽略。如果程序出现异常,但是没有被捕获,程序就会终止,这多少会促使程序员开发出来的程序更健壮一点。而如果使用C语言的error宏或者函数返回值,调用者都有可能忘记检查,从而没有对错误进行处理,结构造成程序莫名奇妙的终止或出现错误的结果。

2、整型返回值没有任何语义信息。而异常却包含语义信息,有时你从类名就能够体现出来。

3、整型返回值缺乏相关的上下文信息。异常作为一个类,可以拥有自己的成员,这些成员就可以传递足够的信息。

4、异常处理可以在调用跳级。这是一个代码编写时的问题:假设在有多个函数的调用栈中出现了某个错误,使用整型返回码要求你在每一级函数中都要进行处理。而使用异常处理的栈展开机制,只需要在一处进行处理就可以了,而不需要每级函数都处理。

STL模板

1、谈谈STL的六大组件

容器:各种数据结构,如vector、list、deque、set、map等,用来存放数据,从实现角度来看,STL容器是一种class template.

算法:各种常用的算法,如sort、find、copy、fo_each。从实现的角度来看,STL算法是一种function template。

迭代器:扮演了容器与算法之间的胶合剂,共有5种类型,从实现角度来看,迭代器是一种将operator* ,operator->,operator++,operator--等指针相关操作予以重载的class template。所有STL容器都附带有自己专属的迭代器,只有容器的设计者才知道如何遍历自己的元素。原生指针(native pointer)也是一种迭代器。

仿函数:行为类似函数,可作为算法的某种策略。从实现角度来看,仿函数是一种重载了operator()的class或者class template

适配器:一种用来修饰容器或者仿函数或迭代器接口的东西。

空间配置:负责空间的配置与管理。从实现角度看,配置器是一个实现了动态空间配置、空间管理、空间释放的class template。

2、谈谈STL六大组件的关系

STL六大组件的交互关系,容器通过空间配置器取得数据存储空间,算法通过迭代器存储容器中的内容,仿函数可以协助算法完成不同的策略的变化,适配器可以修饰仿函数。

3、vertor容器相比于普通的数组array有啥优点

vector的数据安排以及操作方式,与array非常相似,两者的唯一差别在于空间的运用的灵活性。Array是静态空间,一旦配置了就不能改变,要换大一点或者小一点的空间,可以,一切琐碎得由自己来,首先配置一块新的空间,然后将旧的空间的数据搬完新空间,再释放原来的空间。vector是动态空间,随着元素的加入,它的内部机制会自动扩充空间以容纳新元素。因此vector的运用对于内存的合理利用与运用的灵活性有很大的帮助,我们再也不必害怕空间不足而一开始就要求一个大块头的array了

vector的实现技术,关键在于其对大小的控制以及重新配置时的数据移动效率,一旦vector旧空间满了,如果客户每新增一个元素,vector内部只是扩充一个元素的空间,实为不智,因为所谓的扩充空间(不论多大),一如刚所说,是“配置新空间-数据移动-释放旧空间”的大工程,时间成本很高,应该加入某种未雨绸缪的考虑.

4、deque容器相对于vector容器的区别

deque容器和vector容器最大的差异

一、在于deque允许使用常数项时间对头端进行元素的插入和删除操作。

二、在于deque没有容量的概念,因为它是动态的以分段连续空间组合而成,随时可以增加一段新的空间并链接起来,换句话说,像vector那样,“旧空间不足而重新配置一块更大空间,然后复制元素,再释放旧空间”这样的事情在deque身上是不会发生的。也因此,deque没有必须要提供所谓的空间保留(reserve)功能

5、谈谈stack容器的概念

stack所有元素的进出都必须符合“先进后出”的条件,只有stack顶端的元素,才有机会被外界取用。stack不提供遍历功能,也不提供迭代器。

6、谈谈queue容器的概念

queue是一种先进先出的数据结构,它有两个出口,queue容器允许从一端新增元素,从另一端移除元素。

queue所有元素的进出都必须符合“先进先出”的条件,只有queue的顶端元素,才有机会被外界取用。queue不提供遍历的功能,也不提供迭代器。

7、谈谈list容器的概念

相较于vector的连续性空间,list就显得负责许多,它的好处是每次插入或者删除一个元素,就是配置或者释放一个元素的空间。因此,list对于空间的运用有绝对的精准,一点也不浪费。而且,对于任何位置的元素插入或元素移除,list永远是常数时间

1、采用动态存储分配,不会造成内存浪费和溢出 2、链表执行插入和删除操作十分方便,修改指针即可,可不需要移动大量元素3、链表灵活,但是空间和时间额外耗费较大。

8、谈谈set/multiset容器的概念

Set的特性是,所有元素都会根据元素的键值自动被排序。set的元素不像map那样可以同时拥有实值和键值,set的元素既是键值又是实值。set不允许两个元素有相同的键值。

multiset特性及用法和set完全相同,唯一的差别在于它允许键值重复。set和multiset的底层实现是红黑树,红黑树为平衡二叉树的一种

9、谈谈map/multimap容器的概念

map的特性是,所有元素都会根据键值自动排序。map所有的元素都是pair,同时拥有实值和键值,pair的第一个元素被视为键值,第二个元素被视为实值,map不允许两个元素有相同的键值。

multimap和map的操作类似,唯一区别multimap键值可重复。

map和multimap都是以红黑树为底层实现机制。

10、谈谈什么是函数对象

重载函数调用操作符的类,其对象常称为函数对象,即他们是行为类似函数的对象,也叫仿函数,其实就是重载“()”操作符,使得类对象可以像函数那样调用

注意:

1、函数对象(仿函数)是一个类,不是一个函数。2、函数对象(仿函数)重载了“()”操作符使得它可以像函数一样调用

总结:

1、函数对象通常不定义构造函数和析构函数,所以在构造和析构时不会发生任何问题,避免了函数调用的运行时问题。2、函数对象超出普通函数的概念,函数对象可以有自己的状态 3、函数对象可内联编译,性能好。用函数指针几乎不可能。 4、模板函数对象使函数对象具有通用性,这也是它的优势之一。

11、谈谈什么是谓词

谓词是指普通函数或重载的operator()返回值是bool类型的函数对象(仿函数)。如果operator接受一个参数,那么叫做一元谓词,如果接受两个参数,那么叫做二元谓词,谓词可作为一个判断式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值