C++面试自己总结 - Go语言中文社区

C++面试自己总结



1.C和C++的区别?C++的特性?面向对象编程的好处?

答:c++在c的基础上增添类,C是一个结构化语言,它的重点在于算法和数据结构。C程序的设计首要考虑的是如何通过一个过程,对输入(或环境条件)进行运算处理得到输出(或实现过程(事务)控制),而对于C++,首要考虑的是如何构造一个对象模型,让这个模型能够契合与之对应的问题域,这样就可以通过获取对象的状态信息得到输出或实现过程(事务)控制。

好处:

1、维护简单:模块化是面向对象编程中的一个特征。实体被表示为类和同一名字空间中具有相同功能的类,我们可以在名字空间中添加一个类而不会影响该名字空间的其他成员。

2、可扩充性:面向对象编程从本质上支持扩充性。如果有一个具有某种功能的类,就可以很快地扩充这个类,创建一个具有扩充的功能的类。

3、代码重用:由于功能是被封装在类中的,并且类是作为一个独立实体而存在的,提供一个类库就非常简单。

C++特性:抽象,封装,继承,多态

2. const 有什么用途

      1:定义只读变量,即常量 

      2:修饰函数的参数和函数的返回值 

      3: const修饰函数的定义体,这里的函数为类的成员函数,承诺在本函数内部不会修改类内的数据成员,不会调用其它非 const 成员函数(也就是被const修饰函数定义体的函数)。( 类体外定义的 const 成员函数,在定义和声明处都需要 const 修饰符。);一个函数名字后有const,这个函数必定是类的成员函数,也就是说普通函数后面不能有const修饰;

const修饰函数的定义体--------类体外定义的 const 成员函数,在定义和声明处都需要 const 修饰符

      4. const 修饰类的成员变量,表示成员常量,不能被修改。

      5. 如果 const 构成函数重载,const 对象只能调用 const 函数,非 const 对象优先调用非 const 函数。

      6. const 函数只能调用 const 函数。非 const 函数可以调用 const 函数。

       7. const  类成员变量只可以初始化列表中初始化

3. 指针和引用的区别

   1. 指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用仅是个别名;

    2. 用只能在初始化时被赋值,其他时候值不能被改变,指针的值可以在任何时候被改变

    3. 引用不能为NULL,指针可以为NULL

    4. 引用变量内存单元保存的是被引用变量的地址,从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域(因为变量名本身是不占内存的,确切来说变量名在  运行期间  是不占内存的在  编译期间  是占内存的,而我们在这里指的是运行期

    5. “sizeof 引用" = 指向变量的大小 , "sizeof 指针"= 指针本身的大小

    6. 引用可以取地址操作,返回的是被引用变量本身所在的内存单元地址

    7. 引用使用在源代码级相当于普通的变量一样使用,做函数参数时,内部传递的实际是变量地址

   8. 引用没有 const,指针有 const;

  9. 指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)

(1)引用的底层实现:

         虽然从底层来说,引用的实质是指针(确切来说是常量指针),但是从高层语言级别来看,我们不能说引用就是指针,他们是两个完全不同的概念。因为从语言级别上,指针和引用没有关系,引用就是另一个变量的别名。对引用的任何操作等价于对被引用变量的操作。

(1)引用在内存中也会分配空间(引用变量的确存放的是被引用对象的地址,只不过,对于高级程序员来说是透明的,编译器屏蔽了引用和指针的差别),空间中存放的是被引用变量的地址,因此可以将引用看作为一个常量指针ptr;

(2) 对引用取地址操作,其实是对被引用变量取地址,编译器将对引用取地址解释为&(*ptr)取地址

(2)函数传值、传引用、传指针区别

传值:传值无非就是实参拷贝传递给形参,单向传递(实参->形参),赋值完毕后实参就和形参没有任何联系,对形参的修改就不会影响到实参。

传指针:传指针是把实参地址的拷贝传递给形参。还是一句话,传指针就是把实参的地址复制给形参。复制完毕后实参的地址和形参的地址没有任何联系,对实参形参地址的修改不会影响到实参, 但是对形参地址所指向对象的修改却直接反应在实参中,因为形参指向的对象就是形参的对象。

传引用:传引用本质没有任何实参的拷贝,一句话,就是让另外一个变量也执行该实参。就是两个变量指向同一个对象。这是对形参的修改,必然反映到实参上。

4. C++中有了malloc / free , 为什么还需要 new / delete     

  1,malloc与free是C++/C语言的标准库函数(  void *malloc(size_t size);  void free(void *ptr);  ),new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。

  2,对于非内部数据类型的对象而言,光用maloc/free  无法完成对动态对象的内存管理(因为内部数据类型对象没有构造与析构的过程)

    对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。

  3,因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。

5. 编写类String 的构造函数,析构函数,拷贝构造函数和赋值函数 (即自己实现一个string类(重点))

          是考验C++基础知识的好题。至少要能实现以下:构造函数,析构函数,拷贝构造函数(copy constructor),重载赋值操作符(copy assignment operator)。

重载 = 运算符
重载 + 运算符


6. 多态的实现

     多态用虚函数结合动态绑定来实现  

C++中有两种多态,称为动多态(运行期多态)和静多态(编译期多态),而静多态主要通过模板来实现,宏也是实现静多态的一种途径。动多态在C++中是通过虚函数实现的,即在基类中存在一些接口(一般为纯虚函数),子类必须重载这些接口。这样通过使用基类的指针或者引用指向子类的对象,就可以实现调用子类对应的函数的功能。动多态的函数调用机制是执行期才能进行确定,所以它是动态的。

包含虚函数的类才会有虚函数表, 同属于一个类的对象共享虚函数表, 但是有各自的_vptr.  虚函数表实质是一个指针数组,里面存的是虚函数的函数指针。(这样的基类和子类都有自己的虚函数表)

多继承

7. 单链表的逆置

看我自己的 单链表反转 - 简书

8. 堆和栈的区别   

(1)堆 栈空间分配

1.       管理方式不同

栈,由编译器自动管理,无需程序员手工控制;堆:产生和释放由程序员控制。

2.       空间大小不同

栈的空间有限;堆内存可以达到4G,。

3.       能否产生碎片不同

栈不会产生碎片,因为栈是种先进后出的队列。堆则容易产生碎片,多次的new/delete

会造成内存的不连续,从而造成大量的碎片。

4.       生长方向不同

堆的生长方式是向上的,也就是向着内存地址增加的方向,栈是向下的,也就是向着内存地址减小的方向。

5.       分配方式不同

堆是动态分配的。栈可以是静态分配和动态分配两种,静态分配是编译器完成的,比如局部变量的分配。动态分配由函数  alloca  进行分配(   int *p = (int *)alloca(sizeof(int)*10);   )。不过栈的动态分配和堆不同,他的动态分配是由编译器进行释放,无需我们手工实现

6.       分配效率不同

栈是机器系统提供的数据结构,计算机底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令。堆则是由C/C++函数库提供,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

l  堆和栈相比,由于大量new/delete的使用,容易造成大量的内存碎片;由于没有专门的系统支持,效率很低;由于可能引发用户态和核心态的切换,内存的申请,代价变得更加昂贵。所以栈在程序中是应用最广泛的,就算是函数的调用也利用栈去完成,函数调用过程中的参数,返回地址,EBP和局部变量都采用栈的方式存放。所以,我们推荐大家  尽量用栈,而不是用堆。

l  栈和堆相比不是那么灵活,有时候分配大量的内存空间,还是用堆好一些。

l  无论是堆还是栈,都要防止越界现象的发生。

9. 不调用C/C++ 的字符串库函数,编写strcpy

  char * strcpy(char * strDest,const char * strSrc) {

                if ((strDest==NULL)||strSrc==NULL))                   

                  return NULL;   

                char * strDestCopy=strDest;

                while ((*strDest++=*strSrc++)!='');    //先赋值给*strDest, 然后比较*strDest与''的是否相等,

               return strDestCopy;                                    //赋值表达式返回左   操作数,所以在赋值''后,循环停止。

        }

额外提一下:strcpy和memcpy主要有以下3方面的区别。

1)、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。

2)、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符""才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。

3)、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy

10. 关键字static的作用

   1.  函数体内 static 变量的作用范围为该函数体,不同于 auto 变量, static 变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值

    2.  在模块内的 static 全局变量可以被模块内所有函数访问,但不能被模块外其他函数访问(模块由头文件和实现文件组成)

    3.  在类的static 成员变量属于整个类所拥有,对类的所有对象只有一份拷贝

    4.  在类中的 static 成员函数属于整个类所拥有,这个函数不接收 this 指针,因而只能访问类的 static 成员变量,

     也就是说,static成员函数只能访问类中的static 成员变量,其他的成员变量不能访问。

    5.   static成员变量需要在类外进行初始化与定义,声明可以在类内部

介绍它很重要的一条:隐藏(static函数,static变量均可) 

当同时编译多个文件时,所有未加static前缀的全局变量和函数都具有全局可见性。

举例来说明。同时编译两个源文件,一个是a.c,另一个是main.c。

      为什么在a.c中定义的全局变量a和函数msg能在main.c中使用?

      前面说过,所有未加static前缀的全局变量和函数都具有全局可见性,其它的源文件也能访问。此例中,a是全局变量,msg是函数,并且都没有加static前缀,

        因此对于另外的源文件main.c是可见的。

     如果加了static,就会对其它源文件隐藏。例如在a和msg的定义前加上static,main.c就看不到它们了。

     利用这一特性可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲突。static可以用作函数和变量的前缀,对于函数来讲,static的作用仅限于隐藏

11. 在c++程序中调用被C编译器编译后的函数,为什么要加extern“C”

     C++语言支持函数重载,C语言不支持函数重载,函数被C++编译器编译后在库中的名字与C语言的不同,

      假设某个函数原型为:

          void foo(int x, inty);

    该函数被C编译器编译后在库中的名字为:  _foo

而C++编译器则会产生像: _foo_int_int   之类的名字。

为了解决此类名字匹配的问题,C++提供了C链接交换指定符号 extern "C"。

12. 头文件中的ifndef/define/endif 是干什么用的

   防止头文件被重复包含  

13. 什么时候要用虚析构函数?什么时候要用拷贝构造函数?

     一.  什么时候用虚析构函数

         通过基类的指针来删除派生类的对象时,基类的析构函数应该是虚的。否则其删除效果将无法实现。

      一般情况下,这样的删除只能够删除基类对象,而不能删除子类对象,形成了删除一半形象,从而造成内存泄漏。

      原因:在公有继承中,基类对派生类及其对象的操作,只能影响到那些从基类继承下来的成员。

如果想要用基类对非继承成员进行操作,则要把基类的这个操作(函数)定义为虚函数。

那么,析构函数自然也应该如此:如果它想析构子类中的重新定义或新的成员及对象,当然也应该声明为虚的。 调用析构函数后,该对象的虚函数指针,会指向基类的虚函数表

      注意:  如果不需要基类对派生类及对象进行操作,则不能定义虚函数(包括虚析构函数),因为这样会增加内存开销。

     二.  什么时候要用拷贝构造函数

拷贝构造函数也是一种构造函数,它的功能是使用传入对象的值生成一个新的对象的实例。调用的是拷贝构造函数还是赋值运算符,主要是看是否有新的对象实例产生

       (1)将新对象初始化为一个同类对象时   (2)将对象以值传递的方式传给函数时   (3)对象作为函数的返回值,以值的方式从函数返回时   (4)编译器生成临时对象时

14. c++怎样让返回对象的函数不调用拷贝构造函数

    拷贝构造函数前加 “explicit” 关键字;这样拷贝构造函数就不会进行自动类型转换,也就不会实现拷贝构造函数的调用了。

    explicit主要是用来修饰类的构造函数,被修饰的构造函数的类,不能发生相应的隐式类型转换,只能以显示的方式进行类型转换。类构造函数默认情况下声明为隐式的即implicit。

    explicit关键字只能用于类内部的构造函数声明上,而不能用在类外部的函数定义(函数实现)上,它的作用是不能进行隐式转换;explicit关键字作用于单个参数的构造函数,如果构造函数有多个参数,但是从第二个参数开始,如果各参数均有默认赋值,也可以应用explicit关键字。

      可以由单个实参来调用的构造函数定义了一个从形参类型到该类类型的隐式转换。编译器在试图编译某一条语句时,如果某一函数的参数类型不匹配,编译器就会尝试进行隐式转换,如果隐式转换后能正确编译,编译器就会继续执行编译过程,否则报错。

15.请用简单的语言告诉我C++ 是什么?

答:C++是在C语言的基础上开发的一种面向对象编程语言,应用广泛。C++支持多种编程范式 --面向对象编程、泛型编程和过程化编程。 其编程领域众广,常用于系统开发,引擎开发等应用领域,是最受广大程序员受用的最强大编程语言之一,支持类:类、封装、重载等特性!

16.什么是面向对象(OOP)?

答:面向对象是一种对现实世界理解和抽象的方法、是通过将需求要素转化为对象进行问题处理的一种思想。面向对象的三个基本特征是:封装、继承、多态。

(1)封装:封装是实现面向对象程序设计的第一步,封装就是将数据或函数等集合在一个个的单元中(我们称之为类)。封装的意义在于保护或者防止代码(数据)被我们无意中破坏。

(2)继承:继承主要实现重用代码,节省开发时间。子类可以继承父类的一些东西。

17.什么是多态?

答:有了虚函数,基类指针指向基类对象时就使用基类的成员(包括成员函数和成员变量),指向派生类对象时就使用派生类的成员。换句话说,基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,我们将这种现象称为多态(Polymorphism)。

18.设计模式举个例子(单例模式,工厂模式)  设计模式 - CSDN博客

答:设计模式(Design pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。

比如   单例模式,是一种常用的软件设计模式,在它的核心结构中值包含一个被称为单例的特殊类。一个类只有一个实例,即一个类只有一个对象实例,并提供一个访问它的全局访问点。

单例模式可以分为  懒汉式  和  饿汉式

    (1)懒汉式单例模式:在类加载时不初始化。

    (2)饿汉式单例模式:在类加载时就完成了初始化,所以类加载比较慢,但获取对象的速度快。

适用于:当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时;当这个唯一实例应该是通过子类化可扩展的,并且客户应该无需更改代码就能使用一个扩展的实例时。

比如工厂模式,定义一个用于创建对象的接口,让子类决定实例化哪一个类。Factory Method 使一个类的实例化延迟到其子类。

适用于:当一个类不知道它所必须创建的对象的类的时候;当一个类希望由它的子类来指定它所创建的对象的时候;当类将创建对象的职责委托给多个帮助子类中的某一个,并且你希望将哪一个帮助子类是代理者这一信息局部化的时候。

19.STL库用过吗?常见的STL容器有哪些?

答:STL包括6部分内容:C++ STL 一般总结 - as_ - 博客园

(1)容器(containers):是一种数据结构容器,使用类模板的方式提供,我们可以方便的进行数据的存储操作。

(2)适配器(adapters):以序列式容器为基础,提供的栈,队列和优先级队列的这种容器。

(3)迭代器(iterators):类似于指针,用来操作容器的对象。

(4)算法(algorithm):包含一系列的常见算法。

(5)空间配置器(allocator):其中主要工作包括两部分:1,对象的创建与销毁。2,内存的创建与释放。

(6)仿函数(functor):仿函数又称为函数对象,其实就是重载了()操作符的struct,没有什么特别的地方。

容器,即存放数据的地方。比如array等。

在STL中,容器分为两类:顺序容器和关联式容器。

顺序容器,其中的元素不一定有序,但都可以被排序。如:vector、list、deque、stack、queue、heap、priority_queue、slist;

关联式容器,内部结构基本上是一颗平衡二叉树。所谓关联,指每个元素都有一个键值和一个实值,元素按照一定的规则存放。如:RB-tree、set、map、multiset、multimap、hashtable、hash_set、hash_map、hash_multiset、hash_multimap。

vector:它是一个动态分配存储空间的容器。区别于c++中的array,array分配的空间是静态的,分配之后不能被改变,而vector会自动重分配(扩展)空间。

set:其内部元素会根据元素的键值自动被排序。区别于map,它的键值就是实值,而map可以同时拥有不同的键值和实值。

算法,如排序,复制……以及个容器特定的算法。这点不用过多介绍,主要看下面迭代器的内容。

迭代器是STL的精髓,我们这样描述它:迭代器提供了一种方法,使它能够按照顺序访问某个容器所含的各个元素,但无需暴露该容器的内部结构。它将容器和算法分开,好让这二者独立设计。

20. 类的static变量在什么时候初始化?函数的static变量在什么时候初始化?

答:类的静态成员变量在类实例化之前就已经存在了,并且分配了内存。函数的static变量在执行此函数时进行初始化。

21.什么是内存泄漏?面对内存泄漏和指针越界,你有哪些方法?你通常采用哪些方法来避免和减少这类错误?

答:用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元即为内存泄露。

使用的时候要记得指针的长度。

malloc的时候得确定在那里free.  new完之后一定要delete ,如果是数组的话还得要 delete[ ]

对指针赋值的时候应该注意被赋值指针需要不需要释放.

动态分配内存的指针最好不要再次赋值.

可以使用智能指针来避免内存泄漏的问题

22. new和malloc的区别?

答:1、malloc与free是C++/C语言的标准库函数new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。

2、对于非内部数据类型的对象而言,光用maloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。

3、由于malloc/free是库函数而不是运算符,不在编译器控制权限之内不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以一个能完成清理与释放内存工作的运算符delete。注意new/delete不是库函数。

4、C++程序经常要调用C函数,而C程序只能用malloc/free管理动态内存。

5、new可以认为是malloc加构造函数的执行。new出来的指针是直接带类型信息的。而malloc返回的都是void指针。

备注:new的时候,其实做了两件事,一是:new调用了operator new(size_t size)分配内存,相当于malloc分配所需内存的功能;  二是:调用构造函数。

           delete的时候,也是做了两件事,一是:调用析构函数,二是:调用 operator delete(void * mem)释放内存。

23. 解释C++中静态函数和静态变量?

答:(1)类静态数据成员在编译时创建并初始化:在该类的任何对象建立之前就存在,不属于任何对象,而非静态类成员变量则是属于对象所有的类静态数据成员只有一个拷贝,为所有此类的对象所共享。

(2)类静态成员函数属于整个类,不属于某个对象,由该类所有对象共享。

1、static 成员变量实现了同类对象间信息共享。

2、static 成员类外存储,求类大小,并不包含在内。

3、static 成员是命名空间属于类的全局变量,存储在 data 区的rw段。

4、static 成员只能类外初始化,但是可以在类内部声明。

5、可以通过类名访问(无对象生成时亦可),也可以通过对象访问。

静态成员函数

1、静态成员函数不能使用this指针,只能访问静态成员变量,静态成员函数中是不能调用非静态成员的,包括 非静态成员函数 和 非静态成员变量。 如果要访问非静态成员变量的话,只能访问某一个对象的非静态成员变量和静态成员函数,可以传一个对象的指针,引用等参数给这个静态成员函数。)

2.在非静态成员函数中是可以调用静态成员函数的,因为静态成员函数属于类本身,在类的对象产生之前就已经存在了

原因:非静态成员函数,在调用时 this指针时被当作参数传进。而静态成员函数属于类,而不属于对象,不能使用 this 指针。

3、静态成员函数的意义,不在于信息共享,数据沟通,而在于管理静态数据成员,完成对静态数据成员的封装


24. 说下你对内存的了解?

1. 栈 -  由编译器自动分配释放

2. 堆 -  一般由程序员分配释放,若程序员不释放,程序结束时可能由OS回收

3. 全局区(静态区),全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,(C的话未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,C++是不区分的)- 程序结束释放

4.常量存储区:存储常量,内容不允许更改, 程序结束释放。

5 自由存储区:  C++中由new创建,由deletel释放的动态内存单元。

在函数体中定义的变量通常是在栈上,用malloc, calloc, realloc等分配内存的函数分配得到的就是在堆上。在所有函数体外定义的是全局量,加了static修饰符后不管在哪里都存放在全局区(静态区),在所有函数体外定义的static变量表示在该文件中有效,不能extern到别的文件用,在函数体内定义的static表示只在该函数体内有效。另外,函数中的"adgfdf"这样的字符串存放在常量区。

25.  C++ 字符串操作函数(数组和字符串的函数是最常问的,非常多,一定不要记混了)

c/c++中sizeof()、strlen()、length()、size()详解和区别 - CSDN博客

string str;

(1)c++中的size()和length()没有区别;  字符串长度     len = str.length() 和  len = str.size()  都不计算字符串末尾的 '/0' ;  

  sizeof()是运算符,只能用char*做参数, 计算数组末尾的 '/0' ;strlen是函数,不计算字符数组末尾的 '/0' ;

strlen()只能用char*做参数,且必须是以''''结尾的
strlen(char *);     strlen()只能用char*做参数,且必须是以''''结尾的。


(2)字符串比较     可以直接比较,     也可以:    str1.compare(str2);

  str1.compare(str2);


(3).附加

   str1 += str2;   或    str1.append(str2);     str1.append(str2.pos2,len2);

(4). 字符串提取

   str2 = str1.substr();    str2 = str1.substr(pos1);    str2 = str1.substr(pos1,len1);

  假设:string s = "0123456789";

  string sub1 =s.substr(5);//只有一个数字5表示从下标为5开始一直到结尾:sub1 = "56789"

   string sub2 =s.substr(5, 3);//从下标为5开始截取长度为3位:sub2 = "567"

(5). 字符串搜索

   int where = str1.find(str2);

 int  where = str1.find(str2,pos1); pos1是从str1的第几位开始,即数组下标

 int  where = str1.rfind(str2); 从后往前搜。

(6). 插入字符串

   不是赋值语句。

   str1.insert(pos1,str2);

   str1.insert(pos1,str2,pos2,len2);

   str1.insert(pos1,numchar,char);    numchar是插入次数,char是要插入的字符。

(7). 替换字符串

   str1.replace(pos1,str2);

   str1.replace(pos1,str2,pos2,len2);

(8). 删除字符串

   str.erase(pos,len);       str.clear();

(9). 交换字符串

   swap(str1,str2);

(10). C<==> C++

   char *cstr = "Hello";  string str1;   cstr = cstr;   string str2(cstr);   string.c_str();   to_string(int(a))

(11)字符串复制:

strcpy:将由source指针指示的C 字符串(包括结尾字符)复制到destination指针指示的区域中。该函数不允许source和destination的区域有重叠,当src指针指向为‘’时将会停止字符串的复制,同时,为了避免溢出,destination区域应该至少和source区域一样大。 strcpy只用于字符串复制,并且它不仅复制字符串内容之外,还会复制字符串的结束符;(是个深复制)

最终答案输出是 "ABC";而不是"ABCef"

strncpy:复制source的前num字符到destination。如果遇到null字符(’’),且还没有到num个字符时,就用(num - n)(n是遇到null字符前已经有的非null字符个数)个null字符附加到destination。注意:并不是添加到destination的最后,而是紧跟着由source中复制而来的字符后面。下面举例说明:

memcpy:将source区域的前num个字符复制到destination中。该函数不检查null字符(即将null字符当作普通字符处理),意味着将复制num个字符才结束。该函数不会额外地引入null字符,即如果num个字符中没有null字符,那么destination中相应字符序列中也没有null字符。同strcpy的区别:允许将source中null字符后面的字符也复制到destination中,而strcpy和strncpy则不可以。

26.  重写和重载区别   

重载(Overloading):函数名相同,函数的参数个数、参数类型或参数顺序三者中必须至少有一种不同。函数返回值的类型可以相同,也可以不相同。发生在一个类内部。

重写(override):也叫做覆盖,一般发生在子类和父类继承关系之间。子类重新定义父类中有相同名称和参数的 虚函数。

重定义:也叫做隐藏,子类重新定义父类中有相同名称的非虚函数 (参数列表可以不同 ) ,指派生类的函数屏蔽了与其同名的基类函数。发生在继承中。

重写需要注意:

1、 被重写的函数不能是static的。必须是virtual的

2 、重写函数必须与被重写函数有相同的类型,名称和参数列表,返回的类型,否则不能称其为重写而是重载。

3 、重写函数的访问修饰符可以不同。尽管virtual是private的,派生类中重写改写为public,protected也是可以的

访问修饰符的限制一定要大于被重写方法的访问修饰符(public>protected>default>private)

重载的规则:

1)、必须具有不同的参数列表;

2)、可以有不同的返回类型,只要参数列表不同就可以了;

3)、可以有不同的访问修饰符;

4)、可以抛出不同的异常;

重定义规则如下:

 a 、如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏。

   b 、如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有vitual关键字,此时,基类的函数被隐藏(如果相同有Virtual就是重写覆盖了)。

27. C++类中成员变量的初始化总结

(1)普通的变量: 一般不考虑啥效率的情况下 可以在构造函数中进行赋值。考虑一下效率的可以再构造函数的初始化列表中进行。 

(2)static 静态变量: 在类内部声明,但是必须 在类的外部进行定义和初始化  static变量属于类所有,而不属于类的对象,因此不管类被实例化了多少个对象,该变量都只有一个。

(3)const常量:常量在类内部声明,但是定义和初始化只能在构造函数的初始化列表进行const常量需要在声明的时候即初始化) 

static 静态变量,const常量的初始化

(4)Reference 引用型变量(也就是&): 引用型变量和const变量类似,需要在创建的时候即进行初始化。也是在初始化列表中进行。但需要注意用Reference类型。但是可以在类内部初始化的。

(5)const static integral 变量:    对于既是const又是static 而且还是整形变量,C++是给予特权的(但是不同的编译器可能有会有不同的支持,VC 6好像就不支持)。可以直接在类的定义中初始化。short可以,但float的不可以哦。

 总结起来,可以初始化的情况有如下四个地方:

1、在类的定义中进行的,只有const 且 static 且(int, short) 的变量。

2、在类的构造函数初始化列表中, 包括const对象和Reference对象。

3、在类的定义之外初始化的,包括static变量。因为它是属于类的唯一变量。

4、普通的变量可以在构造函数的内部,通过赋值方式进行。当然这样效率不高。 

28.(1)为什么不能在构造函数中调用虚函数(2)构造函数为什么不能是虚函数

     (1)   基类部分在派生类部分之前被构造,当基类构造函数执行时派生类中的数据成员还没被初始化。用一个基类指针指向一个子类对象,子类对象会先调用基类的构造函数,如果此时基类构造函数中的虚函数调用被解析成调用派生类的虚函数,而派生类的虚函数中又访问到未初始化的派生类数据,将导致程序出现一些未定义行为和bug。(同理析构函数中也是不能调用虚函数的,例如基类的析构函数中调用虚函数,派生类的析构函数在函数退出时先被调用,此时派生类已经没有内存资源了,再去调用基类的析构函数,此时如果析构函数中的虚函数被解析成派生类中的函数,也是不存在的。)

(2)虚函数对应一个指向vtable虚函数表的指针,这大家都知道,可是这个指向vtable的指针其实是存储在对象的内存空间的。问题出来了,如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。

总结起来就是: 如果构造函数是虚函数,那么:      先要有虚函数表----->才能有虚构造函数;  但是问题在于????   对象必须靠构造函数才能实现实例化------>实例化后才能有内存去存虚函数表; 这样实现的顺序完全相反了!所以不行!!!

构造函数为什么不能是虚函数 - lizezheng - 博客园    

另外个原因是:虚函数的作用在于通过父类的指针调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针去调用,因此就规定构造函数不能是虚函数。

29. 指针函数和函数指针

指针函数:本质是一个函数。函数返回类型是某一类型的指针

格式:  类型标识符*函数名(参数表)               int * f (x,y);

函数指针:是指向函数的指针变量,即本质是一个指针变量。

格式:类型说明符(*函数名)(参数)                 int (*f) (int x); 


30.C++函数模板底层实现原理

函数模板:实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。 凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。

使用函数模板能减少不必要的重复


不使用的话会重复定义函数

函数模板和普通函数的区别:函数模板是不允许自动类型转换的,而普通函数允许自动类型转换

当函数模板和普通函数在一起时,调用规则如下:

    (1)函数模板可以像普通函数一样被重载

    (2)c++编译器优先考虑普通函数

    (3)如果函数模板可以产生一个更好的匹配,那么选择模板

    (4)可以通过空模板实参列表的语法,限定编译器只通过模板匹配(max<>(a,b); //显示使用函数模板方法,则使用<>空的类型参数化列表

    (5)函数模板不提供隐式的类型转换,必须是严格的匹配。

函数模板底层的实现模式:

              编译器并不是把函数模板处理成能够处理任意类的函数;编译器从函数模板通过具体类型产生不同的函数;编译器会对函数模板进行两次编译,在声明的地方对模板代码本身进行编译,在调用的地方对参数替换后的代码进行编译。


31. 智能指针:智能指针详解 - CSDN博客  (推荐)

C++11中智能指针的原理、使用、实现 - wxquare - 博客园

如果 func 函数能顺利执行到 delete 处当然是最理想的。如果由于某种原因导致函数在中途返回了,或者在还没执行到 delete 语句时发生了异常,那么就悲剧了,为指针pt分配的内存得不到释放,产生了一个经典的内存泄漏。如果 func 函数是一个执行频率较高的函数,那么就尴尬了…为了消除传统的内存分配方式上存在的隐患,C++提供了一些强大的智能指针模版类,

智能指针的核心思想是将堆对象的生存期用栈对象(这个栈对象就是智能指针)来管理,当new一个堆对象的时候,立刻用智能指针来接管;具体做法是:在构造函数中对资源初始化(用一个指针指向堆对象),在析构函数中调用delete对资源进行释放。由于智能指针本身就是一个栈对象,它的作用域结束的时候,自动调用析构函数,从而调用了delete释放了堆对象。-------->这样一个堆对象的生存期就由栈对象来管理了。

智能指针:就是智能/自动化的管理指针所指向的动态资源的释放。它是一个类,有类似指针的功能。 

从较浅的层面看,智能指针是利用了一种叫做RAII(资源获取即初始化)的技术对普通的指针进行封装,这使得智能指针实质是一个对象,行为表现的却像一个指针。智能指针的作用是防止忘记调用delete释放内存和程序异常的进入catch块忘记释放内存。另外指针的释放时机也是非常有考究的,多次释放同一个指针会造成程序崩溃,这些都可以通过智能指针来解决

智能指针典型的有着这四种: auto_ptr、 scoped_ptr、unique_ptr、share_ptr、 weak_ptr

1.  auto_ptr类 (独占所有权,转移所有权 ,C++98引入的,带有很大缺陷不建议使用)

  (1)auto_ptr没有使用引用计数,如果多个auto_ptr指向同一个对象,就会造成对象被删除一次以上的错误。因此一个对象只能由一个auto_ptr所拥有,在给其他auto_ptr赋值的时候,会转移这种拥有关系。所以,在赋值、参数传递的时候会转移所有权,因此不要轻易进行此类操作。

(2)auto_ptr的析构函数内部释放资源时调用的是delete而不是delete[],因此不要让auto_ptr托管数组

2. scoped_ptr  (独占所有权,防拷贝)

 scoped_ptr的实现原理是防止对象间的拷贝与赋值。具体实现是将拷贝构造函数和赋值运算符重载函数设置为保护或私有,并且只声明不实现,并将标志设置为保护或私有,防止他人在类外拷贝,简单粗暴,但是也提高了代码的安全性。

3.unique_ptr (独占所有权)

unique_ptr “唯一 ” 拥有其所指对象,同一时刻只能有一个unique_ptr实例指向给定对象(通过禁止拷贝、只有移动所有权move()函数来实现)。在出现异常的情况下,动态资源能得到释放。

unique_ptr本身的生命周期从unique_ptr实例对象创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。

转移所有权
创建unque_ptr和转移所有权

4.shared_ptr(共享所有权,引用计数)

shared_ptr允许多个指针指向相同的对象。shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。每使用他一次,内部的引用计数加1,每析构一次,内部的引用计数减1,减为0时,自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。

 shared_ptr 的实现原理是通过引用计数来实现,智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象指针指向同一对象,拷贝或赋值时将引用计数加1,析构时只有当引用计数减到0才释放空间,否则只需将引用计数减1即可.

shared_ptr的通用实现技术使用引用计数(reference count)。智能指针类将一个计数器与类实例指向的对象相关联引用计数跟踪该类有多少个对象的指针指向同一对象。每次创建类的新对象时,初始化指针就将引用计数置为1;当对象作为另一对象的副本而创建时,拷贝构造函数拷贝指针并增加与之相应的引用计数;对一个对象进行赋值时,赋值操作符减少左操作数所指对象的引用计数(如果引用计数为减至0,则删除对象),并增加右操作数所指对象的引用计数;调用析构函数时,析构函数减少引用计数(如果引用计数减至0,则删除基础对象)。

5. weak_ptr     关于shared_ptr与weak_ptr的使用 - CSDN博客

        weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个引用的 shared_ptr智能指针对象, weak_ptr只是提供了对管理对象的一个访问手段.

        weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 其实它的出现是伴随shared_ptr而来,尤其是解决了一个引用计数导致的问题:存在循环引用的时候会出现内存泄漏

存在循环引用的时候会出现内存泄漏。

它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少.

智能指针应用在多线程的时候要注意事项:

在多线程环境下,引用计数的更新存在安全隐患-------我们可以在改变引用计数的时候加上一把互斥锁,防止多线程带来的隐患


32.   保护继承和私有继承,虚继承

A继承B
版权声明:本文来源简书,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://www.jianshu.com/p/eee39a710205
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-01-08 22:11:45
  • 阅读 ( 1201 )
  • 分类:面试题

0 条评论

请先 登录 后评论

官方社群

GO教程

推荐文章

猜你喜欢