C++面试干货---带你梳理常考的面试题(一)-LMLPHP

顾得泉:个人主页

个人专栏:《Linux操作系统》 《C++从入门到精通》  《LeedCode刷题》

键盘敲烂,年薪百万!


1.C和C++的区别

       1.语法和特性:C是一种过程式编程语言,而C++是一种面向对象编程语言。C++在C的基础上增加了许多新的语法和特性,如类、对象、继承、多态等,使得代码更加模块化和可重用。

       2.扩展性:C++可以使用C的代码,因为C++是C的超集,但C不能使用C++的代码。C++还提供了许多新的库和功能,使得开发更加方便和高效。

       3.内存管理:C++引入了自动内存管理的概念,通过构造函数和析构函数来管理对象的生命周期。而C需要手动管理内存,使用malloc和free等函数进行内存分配和释放。

       4.异常处理:C++支持异常处理机制,可以捕获和处理程序运行过程中的异常情况。而C没有内置的异常处理机制,需要通过返回错误码或者使用全局变量来处理异常情况。

       5.标准库:C++标准库提供了丰富的功能和数据结构,如容器、算法、字符串处理等。而C的标准库相对较小,主要包含了一些基本的输入输出函数和数学函数。


2.指针和引用的区别

       1.定义方式:指针是一个变量,存储的是另一个变量的内存地址,通过使用"*“来声明和操作指针。引用是一个别名,它直接绑定到另一个变量,通过使用”&"来声明和操作引用。

       2.空值:指针可以为空,即指向空地址或者未初始化的指针。而引用必须在定义时初始化,并且不能为空。

       3.内存操作:指针可以通过解引用操作符"*"来访问所指向的内存地址中的值。而引用则直接访问所绑定变量的值,无需解引用。

       4.重新赋值:指针可以被重新赋值,可以指向不同的内存地址。而引用一旦绑定到一个变量,就不能再绑定到其他变量。

       5.空间占用:指针需要占用额外的内存空间来存储地址信息。而引用不需要额外的内存空间,它只是原变量的别名。

       5.函数参数传递:指针可以作为函数参数传递,通过传递地址来实现对实参的修改。而引用也可以作为函数参数传递,通过引用传递来实现对实参的修改。


3.malloc和new的区别

共同点

       都是从堆上申请空间,并且需要用户手动释放

不同点

       1.malloc是函数,new是操作符

       2.malloc申请的空间不会初始化,new可以初始化

       3.malloc申请空间时,需要手动计算空间大小并传递,new只需在其后跟上空间的类型即可,如果是多个对象,[]中指定对象个数即可

       4.malloc的返回值为void*, 在使用时必须强转,new不需要,因为new后跟的是空间的类型

       5.malloc申请空间失败时,返回的是NULL,因此使用时必须判空,new不需要,但是new需要捕获异常

       6.申请自定义类型对象时,malloc/free只会开辟空间,不会调用构造函数与析构函数,而new在申请空间后会调用构造函数完成对象的初始化,delete在释放空间前会调用析构函数完成空间中资源的清理


4.delete 和 delete[] 的区别

       1.delete用于释放通过new运算分配的单个对象的内存,而delete[]用于释放通过new[]运算符分配的数组对象的内存。

       2.delete只能释放通过new运算符分配的单个对象的内存,而不能释放通过new[]运算符分配的数组对象的内存。同样,delete[]只能释放通过new[]运算符分配的数组对象的内存,而不能释放通过new运算符分配的单个对象的内存。

       3.delete会调用被删除对象的析构函数,以确保对象被正确地清理和销毁。而delete[]会调用数组中每个元素的析构函数,然后再释放整个数组的内存。

       4.如果使用delete来释放通过new[]分配的数组对象的内存,会导致未定义行为。同样,如果使用delete[]来释放通过new分配的单个对象的内存,也会导致未定义行为。


5.八大排序的时间复杂度

冒泡排序:

        最好情况时间复杂度:O(n),当输入数据已经有序时。

        最坏情况时间复杂度:O(n^2),当输入数据逆序时。

        平均情况时间复杂度:O(n^2)。

选择排序:

        最好情况时间复杂度:O(n^2)。

        最坏情况时间复杂度:O(n^2)。

        平均情况时间复杂度:O(n^2)。

插入排序:

        最好情况时间复杂度:O(n),当输入数据已经有序时。

        最坏情况时间复杂度:O(n^2),当输入数据逆序时。

        平均情况时间复杂度:O(n^2)。

希尔排序:

        最好情况时间复杂度:O(n log n)。

        最坏情况时间复杂度:取决于步长序列的选择,最坏情况下为O(n^2)。

        平均情况时间复杂度:取决于步长序列的选择,一般为O(n log n)。

归并排序:

        最好情况时间复杂度:O(n log n)。

        最坏情况时间复杂度:O(n log n)。

        平均情况时间复杂度:O(n log n)。

快速排序:

        最好情况时间复杂度:O(n log n),当每次划分都能均匀地将数组分成两部分时。

        最坏情况时间复杂度:O(n^2),当输入数据已经有序时。

        平均情况时间复杂度:O(n log n)。

堆排序:

        最好情况时间复杂度:O(n log n)。

        最坏情况时间复杂度:O(n log n)。

        平均情况时间复杂度:O(n log n)。

计数排序:

        最好情况时间复杂度:O(n + k),其中k是输入数据的范围。

        最坏情况时间复杂度:O(n + k)。

        平均情况时间复杂度:O(n + k)。


6.const用法

       1.声明常量:可以使用const关键字声明一个常量,一旦被声明为常量,其值就不能再被修改。

例如:

const int MAX_VALUE = 100; 

       在这个例子中,MAX_VALUE被声明为一个常量,其值为100,不能再被修改。

       2.修饰函数参数:const可以用来修饰函数的参数,表示该参数在函数内部不会被修改。这样做可以增加代码的可读性,并且可以防止意外修改参数的值。

       例如:

void print(const int num);

        在这个例子中,print函数的参数num被声明为const,表示在函数内部不会修改num的值。

       3.修饰成员函数:const可以用来修饰类的成员函数,表示该成员函数不会修改类的成员变量。这样做可以使得对象在调用该成员函数时不会被修改。

       例如:

 class MyClass 
{ 
    int num; 
public: 
    int getNum() const; 
};

       在这个例子中,getNum函数被声明为const,表示该函数不会修改类的成员变量num。

       4.修饰指针:const可以用来修饰指针,表示指针所指向的值不能被修改。

有两种情况:

        const修饰指针本身:表示指针本身的值不能被修改。例如:const int* p;

        const修饰指针所指向的值:表示指针所指向的值不能被修改。例如:int* const p;


7.什么是野指针

       野指针是指指向无效内存地址的指针。一个指针被赋值为一个未初始化的变量、已经释放的内存地址或者超出作用域的局部变量的地址时,就会产生野指针。野指针的存在可能导致程序崩溃、数据损坏或者安全漏洞。

野指针通常出现在以下情况下:

       1.指针未初始化:在定义指针变量后,没有为其分配有效的内存空间。

       2.指针指向已释放的内存:在释放了某个内存块后,仍然保留了指向该内存块的指针。

       3.指针超出作用域:在函数或代码块结束后,指针仍然存在并被使用。

为了避免野指针的问题,我们可以采取以下几种措施:

       1.初始化指针:在定义指针变量时,将其初始化为nullptr或者有效的内存地址。

       2.及时释放内存:在使用完动态分配的内存后,及时使用delete或者free函数释放内存,并将指针置为nullptr。

       3.避免指针超出作用域:确保指针的生命周期与其所指向的对象的生命周期相匹配。


8.sizeof 和 strlen 的区别

       sizeof是一个运算符,用于计算数据类型或变量所占用的字节数。它可以用于任何数据类型,包括基本数据类型(如int、float等)和自定义数据类型(如结构体、数组等)。sizeof返回的是一个无符号整数值,表示所占用的字节数。

       strlen是一个函数,用于计算字符串的长度,即字符串中字符的个数(不包括字符串末尾的空字符’\0’)。它只能用于字符串类型(即以’\0’结尾的字符数组),不能用于其他数据类型。

区别:

       参数类型:sizeof可以接受任何数据类型或变量作为参数,而strlen只能接受字符串类型的参数。

       返回值类型:sizeof返回的是一个无符号整数值,表示所占用的字节数;strlen返回的是一个整数值,表示字符串中字符的个数。

       计算方式:sizeof在编译时进行计算,而strlen在运行时遍历字符串来计算长度。

       适用范围:sizeof适用于计算任何数据类型或变量的大小;strlen适用于计算以’\0’结尾的字符串的长度。


9.什么叫做结构体对齐

       结构体对齐是指在内存中如何排列结构体的成员变量,以便于提高访问效率和节省内存空间。在结构体中,成员变量的排列顺序和对齐方式是由编译器决定的。

       结构体对齐的原则是,结构体的起始地址必须是其最宽基本类型成员大小的整数倍。这样可以保证结构体的每个成员变量都能够被正确地访问到,而不会出现对齐错误导致的访问异常或性能下降。

对于结构体的对齐方式,一般有两种常见的方式:

       1.默认对齐方式:按照成员变量的定义顺序依次排列,每个成员变量按照其自身大小进行对齐。

       2.指定对齐方式:可以使用编译器提供的特定语法来指定结构体的对齐方式,例如使用#pragma pack(n)来指定对齐字节数。

       需要注意的是,结构体对齐可能会导致内存空间的浪费。因为为了满足对齐要求,编译器可能会在结构体成员之间插入一些填充字节,以保证对齐。为了减少内存浪费,可以通过调整结构体成员的顺序或者使用特定的对齐方式来优化结构体对齐


10.strcpy和memcpy的区别

功能不同:

       strcpy函数用于将一个字符串(以’\0’结尾)从源地址复制到目标地址,包括字符串的结束符。

       memcpy函数用于将一段内存块从源地址复制到目标地址,不关心内存块的内容是否是字符串。

参数不同:

       strcpy函数接受两个参数,分别是目标地址和源地址的指针。

       memcpy函数接受三个参数,分别是目标地址、源地址和要复制的字节数。

安全性不同:

       strcpy函数在复制字符串时不会检查目标地址的空间是否足够,容易导致缓冲区溢出的安全问题。

       memcpy函数需要指定要复制的字节数,可以确保不会发生缓冲区溢出。

返回值不同:

       strcpy函数返回目标地址的指针,即复制后的字符串的起始地址。

       memcpy函数没有返回值。

适用场景不同:

       strcpy函数适用于字符串的复制操作,常用于处理以’\0’结尾的字符串。

       memcpy函数适用于任意内存块的复制操作,可以复制任意类型的数据。


 结语:关于本次常见面试题的梳理到这里就结束了,希望本篇文章的分享会对大家的面试带来些许帮助,如果大家有什么问题,欢迎大家在评论区留言,最后祝愿每位伙伴都能找到心意的工作。

03-02 11:42