C++ 基础

C++ 主页 C++ 概述 C++ 环境设置 C++ 基本语法 C++ 注释 C++ Hello World C++ 省略命名空间 C++ 标记 C++ 常量/字面量 C++ 关键字 C++ 标识符 C++ 数据类型 C++ 数字数据类型 C++ 字符数据类型 C++ 布尔数据类型 C++ 变量类型 C++ 变量作用域 C++ 多变量 C++ 基本输入/输出 C++ 修饰符类型 C++ 存储类 C++ 数字 C++ 枚举 C++ 枚举类 C++ 引用 C++ 日期和时间

C++ 运算符

C++ 运算符 C++ 算术运算符 C++ 关系运算符 C++ 逻辑运算符 C++ 位运算符 C++ 赋值运算符 C++ sizeof 运算符 C++ 条件运算符 C++ 逗号运算符 C++ 成员运算符 C++ 强制类型转换运算符 C++ 指针运算符 C++ 运算符优先级 C++ 一元运算符

C++ 控制语句

C++ 决策语句 C++ if 语句 C++ if else 语句 C++ 嵌套 if 语句 C++ switch 语句 C++ 嵌套 switch语句 C++ 循环类型 C++ while 循环 C++ for 循环 C++ do while 循环 C++ Foreach 循环 C++ 嵌套循环 C++ break 语句 C++ continue 语句 C++ goto 语句

C++ 字符串

C++ 字符串 C++ 循环遍历字符串 C++ 字符串长度 C++ 字符串连接 C++ 字符串比较

C++ 函数

C++ 函数 C++ 多函数参数 C++ 递归函数 C++ 返回值 C++ 函数重载 C++ 函数重写 C++ 默认参数

C++ 数组

C++ 数组 C++ 多维数组 C++ 指向数组的指针 C++ 将数组传递给函数 C++ 从函数返回数组

C++ 结构 &联合

C++ 结构 C++ 联合

C++ 指针

C++ 指针 C++ 解引用 C++ 修改指针

C++ 类和对象

C++ 面向对象 C++ 类 &对象 C++ 类成员函数 C++ 类访问修饰符 C++ 静态类成员 C++ 静态数据成员 C++ 静态成员函数 C++ 内联函数 C++ this 指针 C++ 友元函数 C++ 指向类的指针

C++ 构造函数

C++ 构造函数 &析构函数 C++ 默认构造函数 C++ 参数化构造函数 C++ 复制构造函数 C++ 构造函数重载 C++ 带默认参数的构造函数 C++ 委托构造函数 C++ 构造函数初始化列表 C++ 使用构造函数动态初始化

C++ 继承

C++ 继承 C++ 多重继承 C++ 多级继承

C++ 面向对象

C++ 重载 C++ 多态性 C++ 抽象 C++ 封装 C++ 接口 C++ 虚函数 C++ 纯虚函数与抽象类

C++ 文件处理

C++ 文件和流 C++ 文件读取

C++ 进阶

C++ 异常处理 C++ 动态内存 C++ 命名空间 C++ 模板 C++ 预处理器 C++ 信号处理 C++ 多线程 C++ Web 编程 C++ 套接字编程 C++ 并发 C++ 高级概念 C++ Lambda 表达式 C++ unordered_multiset

C++ 实用资源

C++ 问答 C++ 快速指南 C++ 速查表 C++ STL 教程 C++ 标准库 C++ 实用资源 C++ 讨论


高级 C++ 概念

C++ 是现代编程的基础语言之一。它从基础的 C 语言发展成为现代编程中非常强大的工具。C++ 的版本始于 C++ 98,现已更新至 C++ 20。自 C++ 11 更新以来,所有现代更新都统称为现代 C++。这些新版本拥有大量新功能,使该语言更加用户友好,功能更加丰富。其中一些新概念早已融入其他新语言,例如 EthereumRubyPythonJavascript。随着这些概念在 C++ 中的引入,如今的编程效率更高了。

以下是我们将要详细了解的高级 C++ 主题列表 -

随着 C++ 20 版本的推出,其他功能也已可用,这些功能稍微高级一些,将在本文的后续部分介绍。上面提到的特性也都是非常高级的概念,但本文提供的解释足以帮助读者深入了解现代 C++ 语言

RAII(资源获取即初始化)

RAII

资源获取即初始化,通常缩写为 RAII,是一种用于内存管理的 C++ 技术。虽然 RAII 与 C++ 的关联通常是其研究的重点,但它的应用范围远不止语言限制。

简单来说,RAII 的定义是:以构造函数的形式为对象分配内存,然后使用析构函数释放分配的内存。因此,它属于面向对象编程 (OOP) 概念的一部分,该概念已在之前的专题中介绍过。

现在,您一定很想知道 RAII 究竟解决了哪些问题? RAII 的工作原理多种多样,其中一些如下:

本节前面部分已经讨论过其中一些主题,本文后面部分将讨论一些新概念。

那么,在编程中,尤其是在面向对象编程 (OOPS) 方面,资源 究竟是什么?

资源是在程序或程序序列编译或执行过程中可能需要的实体。资源示例包括 StackHeapMemoryFilesSockets(在套接字编程中)、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 不同,它不能隐式转换为整数类型,也不能与整数类型(例如 intchar)进行比较。因此,它必然会解决 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