C++ 是现代编程的基础语言之一。它从基本的 C 语言发展成为一个非常强大的现代编程工具。C++ 的版本始于 C++98,现在已经到了 C++20。自从 C++11 更新后,所有现代更新都被称为现代 C++。这些新模型包含了大量的新特性,使得语言更加用户友好并且具备更好的功能。这些新概念在其他一些较新的语言如 Ethereum、Ruby、Python 和 JavaScript 中已经存在,而在 C++ 中引入了这些概念之后,编程变得更加高效。
以下是我们将要详细了解的不同高级 C++ 主题列表:
随着 C++20 版本的推出,还有更多稍微先进的特性,将在本文档的后续部分进行介绍。上面提到的特性本身也是非常先进的概念,但是本文提供的解释应该足以让读者深入了解现代 C++ 语言。
RAII(资源获取即初始化)
RAII,即资源获取即初始化,是一种 C++ 技术,常用于内存管理。虽然它与 C++ 的联系最为紧密,但是 RAII 的应用范围超出了语言限制。
简单来说,RAII 是指通过构造函数为对象分配内存,并通过析构函数释放已分配的内存。因此,它是面向对象编程概念的一部分。
现在,你可能想知道 RAII 解决了什么问题?RAII 通过多种方式工作,包括但不限于:
资源是在编译或执行程序或一系列程序时所需的实体。例如,资源可以是栈、堆、内存、文件、套接字(在套接字编程中)、锁和信号量等。这些资源对于程序的正常运行至关重要。这些资源通常是通过请求来获得的,例如通过调用 mutex() 方法来获取互斥锁。
在传统的 C 编程中,我们使用 new() 和 delete() 概念来创建实体然后释放内存。尽管这种传统概念在像 C++ 这样的面向对象语言中仍然适用,但并不提倡使用。在 C++ 中,RAII 概念使得在一个作用域内分配和释放资源变得容易。
新对象的生命周期就是该对象的生命周期,而构造函数可以创建并分配内存给对象,析构函数可以在完成后自动释放内存。这使得 C++ 成为一种非常高效且用户友好的语言。下面是一个简单的示例:
#include <mutex>
#include <thread>
std::mutex m;
void bad() {
m.lock();
f();
if (!everything_ok())
return;
m.unlock();
}
void good(){
std::lock_guard<std::mutex> lk(m);
f();
if (!everything_ok())
return;
}
int main(){
good();
bad();
return 0;
}
野指针
在 C++ 中,如果一个指针随机指向内存中的任何地址,则称为野指针。当指针在程序中声明但未初始化指向某个地址值时会发生这种情况。野指针不同于普通指针,它们也存储内存地址,但指向的是未分配的内存或已被释放的数据值。
这些指针可能导致内存泄漏,这一主题将在本文档的后续部分讨论。
#include <iostream>
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;
}
空指针
在早期版本的 C++ 中,NULL 定义为指向无内存位置的空元素。允许将 NULL 转换为 int 或类似的数据类型,但在函数重载的情况下,NULL 指针会引发错误。
自从 C++11 出现以来,NULL 被重新定义为 nullptr,这是一种特殊的数据类型,只能作为指针来指向内存中不可用的位置。
因此,它可以重新定义指针变量后作为任何位置的指针。与 NULL 不同,它不是隐式可转换或可比较于整型数据类型,如 int 或 char。因此,它解决了 NULL 的问题。
在 C++ 的新版本中,空指针之间的比较是可能的,因此可以理解为指针与布尔数据类型是可以比较的。
#include <iostream>
using namespace std;
int main() {
int *ptr=nullptr;
if(ptr==nullptr) cout<<"true";
else cout<<"false";
return 0;
}
内存泄漏
内存泄漏是许多计算设备中的一个主要问题,因为程序中的堆栈和堆内存是有限的并且非常昂贵。当新对象被声明、使用且未从内存中清除时就会发生内存泄漏。这可能是由于程序员忘记了使用 delete 操作或错误地使用了它。
内存泄漏有很大的缺点,因为随着每个新进程请求空间会指数级增加,而新的进程必须分配新的内存空间而不是清除不需要的内存。
下面的程序说明了如何在使用 C++ 的程序中发生内存泄漏。
#include <iostream>
using namespace std;
void leak_func(){
int* p = new int(10);
return;
}
int main(){
leak_func();
return 0;
}
可以通过释放最初分配给 new() 对象的内存来避免这种情况。下面的程序说明了如何避免内存泄漏。
#include <iostream>
using namespace std;
void leak_func(){
int* p = new int(10);
delete(p);
return;
}
int main(){
leak_func();
return 0;
}
智能指针
随着 C++ 中 RAII 和面向对象概念的引入,还引入了包装类。其中一种包装类是智能指针,它有助于确保不会出现内存泄漏和错误。
#include <iostream>
using namespace std;
int main() {
int *ptr=nullptr;
if(ptr==nullptr) cout<<"true";
else cout<<"false";
return 0;
}
Lambda 表达式
自从 C++11 引入以来,允许在 C++ 中使用 Lambda 表达式来解决内联函数的问题,这些函数用于少量的代码行而不必命名函数和定义作用域。
语法:
[捕获子句] (参数) -> 返回类型{
方法定义
}
在此处,返回类型由编译器自行解决,无需指定函数的返回类型。然而,在复杂语句的情况下,为了编译器正确执行,需要指定返回类型。
外部变量可以通过以下方式捕获:
- 按引用捕获
- 按值捕获
- 混合捕获(两者结合)
用于捕获变量的语法如下:
- [&] :按引用捕获所有外部变量
- [=] :按值捕获所有外部变量
- [a, &b] :按值捕获 a,按引用捕获 b
```cpp
#include <iostream>
#include <vector>
using namespace std;
void printvector(vector<int> &v){
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;
}