高级 C++ 概念
C++ 是现代编程的基础语言之一。它从基础的 C 语言发展成为现代编程中非常强大的工具。C++ 的版本始于 C++ 98,现已更新至 C++ 20。自 C++ 11 更新以来,所有现代更新都统称为现代 C++。这些新版本拥有大量新功能,使该语言更加用户友好,功能更加丰富。其中一些新概念早已融入其他新语言,例如 Ethereum、Ruby、Python 和 Javascript。随着这些概念在 C++ 中的引入,如今的编程效率更高了。
以下是我们将要详细了解的高级 C++ 主题列表 -
随着 C++ 20 版本的推出,其他功能也已可用,这些功能稍微高级一些,将在本文的后续部分介绍。上面提到的特性也都是非常高级的概念,但本文提供的解释足以帮助读者深入了解现代 C++ 语言。
RAII(资源获取即初始化)

资源获取即初始化,通常缩写为 RAII,是一种用于内存管理的 C++ 技术。虽然 RAII 与 C++ 的关联通常是其研究的重点,但它的应用范围远不止语言限制。
简单来说,RAII 的定义是:以构造函数的形式为对象分配内存,然后使用析构函数释放分配的内存。因此,它属于面向对象编程 (OOP) 概念的一部分,该概念已在之前的专题中介绍过。
现在,您一定很想知道 RAII 究竟解决了哪些问题? RAII 的工作原理多种多样,其中一些如下:
本节前面部分已经讨论过其中一些主题,本文后面部分将讨论一些新概念。
那么,在编程中,尤其是在面向对象编程 (OOPS) 方面,资源 究竟是什么?
资源是在程序或程序序列编译或执行过程中可能需要的实体。资源示例包括 Stack、Heap、Memory、Files、Sockets(在套接字编程中)、Locks 和信号量等。这些资源对于程序的平稳运行至关重要。程序通过请求获取这些资源,例如调用 mutex() 方法获取互斥锁。
在使用 C 语言的传统编程中,我们使用 new() 和 delete() 的概念来创建实体,然后释放内存。这种传统概念虽然在 C++ 等面向对象编程语言中仍然可以接受,但不鼓励使用。在 C++ 中,RAII 的概念使得在作用域内分配和释放资源变得更容易。
新对象的使用期限就是对象的使用期限,构造函数可以创建并分配内存给对象,而析构函数可以在完成后自动释放内存。这使得 C++ 成为一种非常高效且用户友好的语言。让我们通过一个简单的例子来理解这一点。
示例
#include <bits/stdc++.h> using namespace std; mutex m; void bad() { m.lock(); // 获取互斥锁 f(); // 如果 f() 抛出异常,则互斥锁永远不会被释放 if (!everything_ok()) return; // 提前返回,则互斥锁永远不会被释放 m.unlock(); // 如果 bad() 执行到此语句,则互斥锁会被释放 } void good(){ lock_guard<mutex> lk(m); // RAII 类:获取互斥锁即初始化 f(); // 如果 f() 抛出异常,则释放互斥锁 if (!everything_ok()) return; // 提前返回,则释放互斥锁 } int main(){ good(); bad(); return 0; }
C++ 中的野指针
如果一个指针随机指向内存中的任意地址,则称为野指针。这种情况发生在程序中声明指针,但未初始化为指向某个地址值的情况。野指针与普通指针不同,它们也存储内存地址,但指向的是未分配的内存或已释放的数据值。
这些指针可能导致内存泄漏,本文后面将对此进行讨论。
示例
#include <bits/stdc++.h> using namespace std; int main() { int *ptr; //此指针已声明但尚未初始化 //因此,它是一个野指针 cout<<*ptr<<endl; int a=11; ptr=&a; cout<<*ptr<<endl<<ptr<<endl; //一旦声明了一个值,它就变成了一个普通的指针 *ptr=10; cout<<*ptr<<endl<<ptr; return 0; }
输出
-660944088 11 0x7ffcfb77825c 10 0x7ffcfb77825c
C++ 中的空指针
在早期版本的 C++ 中,NULL 被定义为指向非内存的 void 元素。允许将 NULL 转换为 int 或类似数据类型,但如果函数发生 重载,NULL 指针会抛出错误。
自 C++ 11 出现以来,NULL 被重新定义为 nullptr,这是一种特殊的数据类型,只能用作指向内存中不存在的地址的指针。
因此,在重新定义指针变量时,它可以充当指向任何位置的指针。与 NULL 不同,它不能隐式转换为整数类型,也不能与整数类型(例如 int 或 char)进行比较。因此,它必然会解决 NULL 的问题。
另外,在较新版本的 C++ 中,可以比较空指针,因此可以理解为指针可以与 bool 数据类型进行比较。
示例
#include <bits/stdc++.h> using namespace std; int main() { //int ptr=nullptr; //这会引发编译器错误,因为它无法与 int 进行比较 //运行上面这行代码进行演示 int *ptr=nullptr; if(ptr==nullptr) cout<<"true"; else cout<<"false"; return 0; }
输出
true
C++ 中的内存泄漏
内存泄漏是许多计算设备中的一个主要问题,因为程序中编译器提供的栈和堆内存有限且成本高昂。当声明、使用新对象且未将其从内存中清除时,就会发生内存泄漏。如果程序员忘记使用删除操作,或者使用不当,就会发生这种情况。
内存泄漏会带来巨大的弊端,因为随着每个进程请求的到来,空间会呈指数级增长,而且必须为新进程分配新的内存空间,而不是清除不需要的内存。
给定的程序说明了使用 C++ 的程序中如何发生内存泄漏。
示例
#include <bits/stdc++.h> using namespace std; void leak_func(){ int* p = new int(10); //使用 new() 声明一个新对象 //没有 delete() 操作 return; } int main(){ leak_func(); return 0; }
可以通过释放最初分配给 new() 对象的内存来避免这种情况。以下程序演示了如何避免内存泄漏。
示例
#include <bits/stdc++.h> using namespace std; void leak_func(){ int* p = new int(10); //使用 new() 声明一个新对象 delete(p); return; } int main(){ leak_func(); return 0; }
C++ 中的智能指针
随着 C++ 中 RAII 和 OOP 概念的引入,包装器类也被引入到 C++ 中。智能指针就是其中一个包装器类,它有助于确保不会出现内存泄漏和错误。
示例
#include <bits/stdc++.h> using namespace std; int main() { //int ptr=nullptr; //这会引发编译器错误,因为它与 int 不可比 int *ptr=nullptr; if(ptr==nullptr) cout<<"true"; else cout<<"false"; return 0; }
输出
true
C++ 中的 Lambda 表达式
自 C++ 11 起,C++ 中允许使用 Lambda 表达式来解析内联函数,这些函数用于编写小段代码,无需指定函数名称和作用域。
语法
[ capture clause ] (parameters) -> return-type{ definition of method }
这里,返回类型由编译器自行解析,无需指定函数的返回类型。但是,对于复杂的语句,需要指定返回类型才能使编译器正常运行。
外部变量可以通过以下方式捕获 -
- 通过引用捕获
- 通过值捕获
- 通过两者捕获(混合捕获)
捕获变量的语法如下 -
- [&] :通过引用捕获所有外部变量
- [=] :通过值捕获所有外部变量
- [a, &b] :通过值捕获 a,通过引用捕获 b
示例
#include <bits/stdc++.h> using namespace std; void printvector(vector<int> &v){ // 打印向量的 lambda 表达式 for_each(v.begin(), v.end(), [](int i){ std::cout << i << " "; }); cout << endl; } int main(){ vector<int> v; v.push_back(10); v.push_back(11); v.push_back(12); printvector(v); return 0; }
输出
10 11 12