主页
  • 主页
  • 分类
  • 热文
  • 教程
  • 面试
  • 标签
C++

C++ 基础

C++ 主页
C++ 概述
C++ 环境
C++ 基本语法
C++ 注释
C++ 数据类型
C++ Hello, World
C++ 省略命名空间
C++ 数值类型
C++ 字符类型
C++ 布尔类型
C++ 变量
C++ 作用域
C++ 多个变量
C++ 输入输出基础
C++ 常量/文字
C++ 修饰符类型
C++ 存储类别
C++ 操作符
C++ 决策结构
C++ 循环结构
C++ foreach 循环
C++ 数字
C++ 数组
C++ 指针
C++ 枚举类型
C++ 引用变量
C++ 日期与时间
C++ 结构体
C++ 联合类型

C++ 字符串

C++ 字符串
C++ 字符串长度
C++ 字符串连接

C++ 函数

C++ 函数
C++ 多参数函数
C++ 递归
C++ return 语句
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++ 环境
C++ 基本语法
C++ 注释
C++ 数据类型
C++ Hello, World
C++ 省略命名空间
C++ 数值类型
C++ 字符类型
C++ 布尔类型
C++ 变量
C++ 作用域
C++ 多个变量
C++ 输入输出基础
C++ 常量/文字
C++ 修饰符类型
C++ 存储类别
C++ 操作符
C++ 决策结构
C++ 循环结构
C++ foreach 循环
C++ 数字
C++ 数组
C++ 指针
C++ 枚举类型
C++ 引用变量
C++ 日期与时间
C++ 结构体
C++ 联合类型

字符串

C++ 字符串
C++ 字符串长度
C++ 字符串连接

函数

C++ 函数
C++ 多参数函数
C++ 递归
C++ return 语句
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 语言发展成为一个非常强大的现代编程工具。C++ 的版本始于 C++98,现在已经到了 C++20。自从 C++11 更新后,所有现代更新都被称为现代 C++。这些新模型包含了大量的新特性,使得语言更加用户友好并且具备更好的功能。这些新概念在其他一些较新的语言如 Ethereum、Ruby、Python 和 JavaScript 中已经存在,而在 C++ 中引入了这些概念之后,编程变得更加高效。

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

  • RAII(资源获取即初始化)
  • 野指针
  • 空指针
  • 内存泄漏
  • 智能指针
  • Lambda 表达式

随着 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();                  // 如果 f() 抛出异常,互斥锁将不会被释放
   if (!everything_ok())
      return;           // 提前返回,互斥锁将不会被释放
   m.unlock();           // 如果 bad() 执行到这里,互斥锁将被释放
}
 
void good(){
   std::lock_guard<std::mutex> lk(m);      // RAII 类:互斥锁的获取即初始化
   f();                                      // 如果 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;

   //此语句在编译时会抛出错误,因为它不能与 int 比较
   //运行上述行以说明

   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);
   // 使用 new() 来声明一个新的对象

   // 没有 delete() 操作
   return;
}

int main(){
   leak_func();

   return 0;
}

可以通过释放最初分配给 new() 对象的内存来避免这种情况。下面的程序说明了如何避免内存泄漏。

#include <iostream>
using namespace std;

void leak_func(){
   int* p = new int(10);
   // 使用 new() 来声明一个新的对象

   delete(p);
   return;
}

int main(){
   leak_func();

   return 0;
}

智能指针

随着 C++ 中 RAII 和面向对象概念的引入,还引入了包装类。其中一种包装类是智能指针,它有助于确保不会出现内存泄漏和错误。

#include <iostream>
using namespace std;

int main() {

   //int ptr=nullptr;

   //此语句在编译时会抛出错误,因为它不能与 int 比较

   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){
   // 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;
}
上一章
阅读号二维码

关注阅读号

联系二维码

联系我们

© 2024 Yoagoa. All rights reserved.

粤ICP备18007391号

站点地图