秋招总结------C++面试题总结三

1. 说说你对c和c++的看法,c和c++的区别?

  1. 第一点就应该想到C是面向过程的语言,而C++是面向对象的语言,一般简历上第一条都是熟悉C/C++基本语法,了解C++面向对象思想,那么,请问什么是面向对象
  2. C和C++动态管理内存的方法不一样,C是使用malloc/free函数,而C++除此之外还有new/delete关键字;(关于malooc/free与new/delete的不同又可以说一大堆,最后的扩展_1部分列出十大区别);
  3. 接下来就不得不谈到C中的struct和C++的类,C++的类是C所没有的,但是C中的struct是可以在C++中正常使用的,并且C++对struct进行了进一步的扩展,使struct在C++中可以和class一样当做类使用,而唯一和class不同的地方在于struct的成员默认访问修饰符是public,而class默认的是private;
  4. C++支持函数重载,而C不支持函数重载,而C++支持重载的依仗就在于C++的名字修饰与C不同,例如在C++中函数int fun(int ,int)经过名字修饰之后变为 _fun_int_int ,而C是 
    _fun,一般是这样的,所以C++才会支持不同的参数调用不同的函数;
  5. C++中有引用,而C没有;这样就不得不提一下引用和指针的区别(文后扩展_2);
  6. 当然还有C++全部变量的默认链接属性是外链接,而C是内连接;
  7. C 中用const修饰的变量不可以用在定义数组时的大小,但是C++用const修饰的变量可以(如果不进行&,解引用的操作的话,是存放在符号表的,不开辟内存);

2. 堆与栈的区别?

  1. 管理方式对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生memory leak。 
  2. 空间大小一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,例如,在VC6下面,默认的栈空间大小是1M(好像是,记不清楚了)。当然,我们可以修改: 打开工程,依次操作菜单如下:Project->Setting->Link,在Category 中选中Output,然后在Reserve中设定堆栈的最大值和commit。 注意:reserve最小值为4Byte;commit是保留在虚拟内存的页文件里面,它设置的较大会使栈开辟较大的值,可能增加内存的开销和启动时间。 
  3. 碎片问题对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出,详细的可以参考数据结构,这里我们就不再一一讨论了。 
  4. 生长方向对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。 
  5. 分配方式堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,无需我们手工实现。 
  6. 分配效率栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。

3. 野指针是什么?如何检测内存泄漏?

  1. 野指针:指向内存被释放的内存或者没有访问权限的内存的指针。
  • “野指针”的成因主要有3种:
  1. 指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如
    char *p = NULL;
    char *str = new char(100);
  2. 指针p被free或者delete之后,没有置为NULL
  3. 指针操作超越了变量的作用范围
  • 如何避免野指针:
  1. 对指针进行初始化

          ①将指针初始化为NULL。  char *   p  = NULL;

          ②用malloc分配内存。char * p = (char * )malloc(sizeof(char));

          ③用已有合法的可访问的内存地址对指针初始化。char num[ 30] = {0}; char *p = num;

     2.指针用完后释放内存,将指针赋NULL。

                delete(p);   p = NULL;

4. ​​​​​​​悬空指针和野指针有什么区别?

  1. 野指针:野指针指,访问一个已删除或访问受限的内存区域的指针,野指针不能判断是否为NULL来避免。指针没有初始化,释放后没有置空,越界
  2. 悬空指针:一个指针的指向对象已被删除,那么就成了悬空指针野指针是那些未初始化的指针。

5. ​​​​​​​内存泄漏

  1. 内存泄漏:内存泄漏是指由于疏忽错误造成了程序未能释放掉不再使用内存的情况。内存泄漏并非指内存在物理上消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制;
  2. 后果:只发生一次小的内存泄漏可能不被注意,但泄漏大量内存的程序将会出现各种证照:性能下降内存逐渐用完,导致另一个程序失败
  3. 如何排除​​​​​​​:
  • ​​​​​​​使用工具软件BoundsChecker,BoundsChecker是一个运行时错误检测工具,它主要定位程序运行时期发生的各种错误;调试运行DEBUG版程序,运用以下技术:CRT(C run-time libraries)、运行时函数调用堆栈、内存泄漏时提示的内存分配序号(集成开发环境OUTPUT窗口),综合分析内存泄漏的原因,排除内存泄漏。

​​​​​​​      解决办法:

  1. 智能指针。

     检查、定位内存泄漏

  1. 检查方法:在main函数最后面一行,加上一句_CrtDumpMemoryLeaks()。调试程序,自然关闭程序让其退出,查看输出:

    输出这样的格式{453}normal block at 0x02432CA8,868 bytes long

    被{}包围的453就是我们需要的内存泄漏定位值,868 bytes long就是说这个地方有868比特内存没有释放。

    定位代码位置

    在main函数第一行加上_CrtSetBreakAlloc(453);意思就是在申请453这块内存的位置中断。然后调试程序,程序中断了,查看调用堆栈。加上头文件#include

6.​​​​​​​​​​​​​​ new和malloc的区别?

  1. 秋招总结------C++面试题总结三_第1张图片

7. ​​​​​​​​​​​​​​delete p;与delete[]p,allocator

  1. 动态数组管理new一个数组时,[]中必须是一个整数,但是不一定是常量整数,普通数组必须是一个常量整数;
  2. new动态数组返回的并不是数组类型,而是一个元素类型的指针
  3. delete[]时,数组中的元素按逆序的顺序进行销毁;
  4. new在内存分配上面有一些局限性,new的机制是将内存分配和对象构造组合在一起,同样的,delete也是将对象析构和内存释放组合在一起的。allocator将这两部分分开进行,allocator申请一部分内存,不进行初始化对象,只有当需要的时候才进行初始化操作。

8. ​​​​​​​​​​​​​​malloc申请的存储空间能用delete释放吗

  1. 不能,malloc /free主要为了兼容C,new和delete 完全可以取代malloc /free的。malloc /free的操作对象都是必须明确大小的。而且不能用在动态类上。new 和delete会自动进行类型检查和大小malloc/free不能执行构造函数与析构函数,所以动态对象它是不行的。当然从理论上说使用malloc申请的内存是可以通过delete释放的。不过一般不这样写的。而且也不能保证每个C++的运行时都能正常。

9. ​​​​​​​ malloc、realloc、calloc的区别

  1. malloc函数
  • void* malloc(unsigned int num_size);
  • int *p = malloc(20*sizeof(int));申请20个int类型的空间;

     2. calloc函数

  • void* calloc(size_t n,size_t size);

    int *p = calloc(20, sizeof(int));

    省去了人为空间计算;malloc申请的空间的值是随机初始化的,calloc申请的空间的值是初始化为0的;

    3.realloc函数

  • void realloc(void *p, size_t new_size); 给动态分配的空间分配额外的空间,用于扩充容量。

10. ​​​​​​​ 使用智能指针管理内存资源,RAII

  1. RAII全称是“Resource Acquisition is Initialization”,直译过来是“资源获取即初始化,也就是说在构造函数中申请分配资源,在析构函数中释放资源。因为C++的语言机制保证了,当一个对象创建的时候,自动调用构造函数,当对象超出作用域的时候会自动调用析构函数。所以,在RAII的指导下,我们应该使用类来管理资源,将资源和对象的生命周期绑定。
  2. 智能指针(std::shared_ptr和std::unique_ptr)即RAII最具代表的实现,使用智能指针,可以实现自动的内存管理,再也不需要担心忘记delete造成的内存泄漏。毫不夸张的来讲,有了智能指针,代码中几乎不需要再出现delete了。

11.​​​​​​​ 手写实现智能指针类

  1. 智能指针是一个数据类型,一般用模板实现,模拟指针行为的同时还提供自动垃圾回收机制。它会自动记录SmartPointer对象的引用计数,一旦T类型对象的引用计数为0,就释放该对象。除了指针对象外,我们还需要一个引用计数的指针设定对象的值,并将引用计数计为1,需要一个构造函数。新增对象还需要一个构造函数,析构函数负责引用计数减少和释放内存。通过覆写赋值运算符,才能将一个旧的智能指针赋值给另一个指针,同时旧的引用计数减1,新的引用计数加1
  2. 一个构造函数、拷贝构造函数、复制构造函数、析构函数、移走函数;

12. ​​​​​​​ 内存对齐?位域?

  1. 分配内存的顺序是按照声明的顺序
  2.  每个变量相对于起始位置的偏移量必须是该变量类型大小的整数倍,不是整数倍空出内存,直到偏移量是整数倍为止
  3.  最后整个结构体的大小必须是里面变量类型最大值的整数倍

     添加了#pragma pack(n)后规则就变成了下面这样:

  1. 偏移量要是n和当前变量大小中较小值的整数倍
  2. 整体大小要是n和最大变量大小中较小值的整数倍
  3. n值必须为1,2,4,8…,为其他值时就按照默认的分配规则

13. ​​​​​​​结构体变量比较是否相等

  1. 重载了 “==” 操作符
  2. struct foo {

        int a;

        int b;

        bool operator==(const foo& rhs) // 操作运算符重载

        {

            return( a == rhs.a) && (b == rhs.b);

     

        }

    };

  3. 元素的话,一个个比;
  4. 指针直接比较,如果保存的是同一个实例地址,则(p1==p2)为真;

14. ​​​​​​​为什么内存对齐

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

    2. ​​​​​​​性能原因

  • 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
  • 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

15. ​​​​​​​怎样判断两个浮点数是否相等?

  1. 对两个浮点数判断大小和是否相等不能直接用==来判断,会出错!明明相等的两个数比较反而是不相等!
  2. 对于两个浮点数比较只能通过相减并与预先设定的精度比较,记得要取绝对值!浮点数与0的比较也应该注意。与浮点数的表示方式有关。

16. ​​​​​​ define、const、typedef、inline使用方法?

  • ​​​​​​​const与#define的区别:
  1. const定义的常量是变量带类型,而#define定义的只是个常数不带类型
  2. define只在预处理阶段起作用,简单的文本替换,而const在编译、链接过程中起作用;
  3. define只是简单的字符串替换没有类型检查。而const是有数据类型的,是要进行判断的,可以避免一些低级错误;
  4. define预处理后,占用代码段空间,const占用数据段空间
  5. const不能重定义,而define可以通过#undef取消某个符号的定义,进行重定义;
  6. define独特功能,比如可以用来防止文件重复引用。
  • #define和别名typedef的区别
  1. ​​​​​​​执行时间不同,typedef在编译阶段有效,typedef有类型检查的功能;#define是宏定义,发生在预处理阶段,不进行类型检查;
  2. 功能差异,typedef用来定义类型的别名,定义与平台无关的数据类型,与struct的结合使用等。#define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。
  3. 作用域不同,#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用。而typedef有自己的作用域。
  • defineinline的区别
  1. #define关键字inline是函数
  2. 宏定义在预处理阶段进行文本替换,inline函数在编译阶段进行替换;
  3. inline函数有类型检查,相比宏定义比较安全;

17. ​​​​​​​#include 的顺序以及尖叫括号和双引号的区别

  1. 表示编译器只在系统默认目录或尖括号内的工作目录下搜索头文件,并不去用户的工作目录下寻找,所以一般尖括号用于包含标准库文件;

  2. 表示编译器先在用户的工作目录下搜索头文件,如果搜索不到则到系统默认目录下去寻找,所以双引号一般用于包含用户自己编写的头文件。

18. lambda 函数

 

[capture] (parameters) mutable ->return-type {statement};

  1. 利用lambda表达式可以编写内嵌的匿名函数,用以替换独立函数或者函数对象;

  2. 每当你定义一个lambda表达式后,编译器会自动生成一个匿名类(这个类当然重载了()运算符),我们称为闭包类型(closure type)。那么在运行时,这个lambda表达式就会返回一个匿名的闭包实例,其实一个右值。所以,我们上面的lambda表达式的结果就是一个个闭包。闭包的一个强大之处是其可以通过传值或者引用的方式捕捉其封装作用域内的变量,前面的方括号就是用来定义捕捉模式以及变量,我们又将其称为lambda捕捉块。
  3. lambda表达式的语法定义如下:
  4. lambda必须使用尾置返回来指定返回类型,可以忽略参数列表和返回值,但必须永远包含捕获列表和函数体

19. ​​​​​​​模板类和模板函数的区别是什么?

  1. 函数模板实例化是由编译程序在处理函数调用时自动完成的,而类模板的实例化必须由程序员在程序中显式地指定。即函数模板允许隐式调用和显式调用而类模板只能显示调用。在使用类模板必须加,而函数模板不必

20. ​​​​​​​为什么模板类一般都是放在一个h文件中

  1. 模板定义很特殊。由template<…>处理的任何东西都意味着编译器在当时不为它分配存储空间,它一直处于等待状态直到被一个模板实例告知。在编译器和连接器的某一处,有一机制能去掉指定模板的多重定义。所以为了容易使用,几乎总是在头文件中放置全部的模板声明和定义。
  2. 分离式编译的环境下,编译器编译某一个.cpp文件时并不知道另一个.cpp文件的存在,也不会去查找(当遇到未决符号时它会寄希望于连接器)。这种模式在没有模板的情况下运行良好,但遇到模板时就傻眼了,因为模板仅在需要的时候才会实例化出来,所以,当编译器只看到模板的声明时,它不能实例化该模板,只能创建一个具有外部连接的符号并期待连接器能够将符号的地址决议出来。然而当实现该模板的.cpp文件中没有用到模板的实例时,编译器懒得去实例化,所以,整个工程的.obj中就找不到一行模板实例的二进制代码,于是连接器也黔驴技穷了。

 

你可能感兴趣的:(面试问题,C++)