1. C++ 中函数模版和类模版有什么区别?
两者的区别主要还是在于用法和实例化方式不同:
1. 定义和使用:
- 函数模板:用于创建可以接受不同类型参数的函数。我们定义一个一次性模板,然后生成多个函数版本。
cpp
template <typename T>
T max(T a, T b)
{
return (a > b) ? a : b;
}
在上面的例子中,max函数可以用于任何支持 ">" 运算符的类型。
- **类模板:**用于创建可以接受不同类型参数的类。通过创建一次模板类,我们可以生成多个不同类型的类实例。
cpp
template <typename T>
class Stack
{
private:
std::vector<T> elements;
public:
void push(T const& elem) { elements.push_back(elem); }
void pop() { elements.pop_back(); }
T top() const { return elements.back(); }
};
这个例子里,Stack类可以处理不同类型的堆栈元素。
2. 实例化方式:
- **函数模板:**编译器在实际调用函数时,根据传入参数的类型自动生成具体类型的函数。
cpp
int a = 10, b = 20;
std::cout << max(a, b); // 调用 max<int>(int a, int b)
- **类模版:**在使用类模版时,我们需要显示的声明模版类型。
cpp
Stack<int> intStack;
Stack<double> doubleStack;
2. 请介绍下 C++ 模板中的 SFINAE?它的原则是什么?
SFINAE是 Substitution Failure Is Not An Error的缩写,意思是在模板定义中,类型替换失败并不是一个编译错误。
通过SFINAE,可以实现某些代码只针对特定类型有效,而对其他类型无效,增强泛型编程的灵活性。
SFINAE的原则说白了就是:当替换模板参数失败时,编译器不会把这个失败当作一个错误,而是会继续尝试其他的模板匹配。如果找不到匹配的模板,才会报错。
3. C++ 的 strcpy 和 memcpy 有什么区别?
两者都用于复制数据,但使用场景略有不同:
-
strcpy用于复制字符串(null-terminated字符数组)。它会从源字符串复制字符到目标字符数组,直到遇:到终止符'\0'。 -
memcpy用于复制任意类型的数据块,不限于字符串。它会复制指定长度的内存块(以字节为单位),不会检查终止符。
4. C++ 中为什么要使用 std::array?他有什么优点?
std::array是 C++11 标准引入的新特性,它有很多优点:
-
固定大小:
std::array是一个固定大小的序列容器,一旦创建,大小就不能改变,它使用的是栈内存。它与:std::vector 不同,std::vector是动态大小的。 -
性能优势:
std::array在性能上很接近 C 风格的数组,因为它使用连续的栈内存布局。 -
类型安全: 与 C 风格数组相比,
std::array提供了类型安全的at()接口。 -
接口友好:
std::array提供了STL容器的标准接口,如size(),begin(),end()等,使用上也非常方便。 -
与现代C++特性结合: 作为STL的一部分,
std::array可以很自然地和其他标准库功能配合使用,比如范围for循环、算法函数等。
5. C++ 中堆内存和栈内存的区别是什么?
堆内存和栈内存的区别主要体现在分配方式、管理方式、生命周期和性能等方面:
1. 分配方式:
-
**栈内存:**由编译器在程序运行时自动分配和释放。典型的例子是局部变量的分配。
-
堆内存: 需要程序员手动分配和释放,使用
new和delete操作符。在C++11之后,也可以使用智能指针来管理堆内存。2. 管理方式: -
**栈内存:**由编译器自动管理,程序员无需担心内存泄漏,生命周期由作用域决定。
-
**堆内存:**由程序员手动管理,如果没有正确释放内存,会导致内存泄漏。
2. 生命周期
-
**栈内存:**变量在离开作用域之后自动销毁。
-
**堆内存:**只要不手动释放,内存会持续存在,直到程序终止。
3. 性能:
-
**栈内存:**内存分配和释放速度极快,性能上优于堆内存。
-
**堆内存:**涉及到复杂的内存管理和分配机制,性能上较慢。
6. C++ 的栈溢出是什么?
栈溢出 (Stack Overflow) 是在程序执行过程中,栈空间被耗尽的一种现象。
栈空间是操作系统为每个线程分配的有限内存,用于存储函数调用、局部变量等。当递归过深(例如无限递归) 或局部变量占用内存过大时,栈空间就会被用尽,导致栈溢
在 C++ 中,典型的栈溢出情况包括:
-
无限递归调用:函数不断调用自身,导致栈帧无限增长。
-
大局部变量:定义了过多或过大的局部变量,超过了栈内存的限制。
7. 什么是 C++ 的回调函数?为什么需要回调函数?
回调函数是一种通过函数指针或者函数对象(例如 std::function或 lambda 表达式)将一个函数作为参数传递给另一个函数的机制。
实际上,就是把函数的调用权从一个地方转移到另一个地方,这个调用会在未来某个时刻进行,而不是立即执行。之所以称为"回调",可以理解为某种倒叙执行:先安排好函数的调用,不立即执行,等到合适的时机再"回头"执行。
需要回调函数的主要原因包括:
-
**异步编程:**在异步操作中,比如网络请求、文件读取、事件处理等,可以在操作完成后调用回调函数,而主程序可以继续执行其它任务,避免等待操作完成。
-
**解耦代码:**回调函数有助于将代码模块化和解耦,允许我们创建更灵活和可复用的代码。例如,一个通用的排序算法可以接受一个比较函数,允许用户自定义排序逻辑。
-
**事件驱动编程:**在GUI或者其他事件驱动程序中,回调函数经常用于处理用户输入事件,如点击、鼠标移动、键盘输入等。
8. C++ 中为什么要使用 nullptr 而不是 NULL?
主要原因是 nullptr 有明确的类型,它是 std::nullptr_t 类型,可以避免代码中出现类型不一致的问题。
9. 什么是大端?什么是小端?
通俗点讲就是数据在内存中的存放顺序。
-
**大端序:**高字节存储在内存的低地址处,低字节存储在高地址处。例如,对于16进制数0x12345678,大端序在内存中的存储方式是:12 34 56 78。
-
**小端序:**低字节存储在内存的低地址处,高字节存储在高地址处。对于同样的16进制数0x12345678,小端序在内存中的存储方式是:78 56 34 12。
10. C++ 中 include<a.h> 和 include"a.h" 有什么区别?
两者都是用来包含头文件的指令,区别在于搜索头文件的路径。
-
#include<a.h>:编译器会在预定义的系统目录中搜索头文件,这种路径搜索方式适用于标准库和第三方库的头文件。 -
#include"a.h":编译器会在当前源文件所在目录和自定义目录中搜索头文件。
总结来说,#include<a.h>查找系统目录,而#include"a.h"查找当前目录和自定义目录。