C++快餐——C++11(1)

2023-10-28 19:36
文章标签 c++ 快餐

本文主要是介绍C++快餐——C++11(1),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

在这里插入图片描述

文章目录

  • 背景简介
  • 统一列表初始化
    • {}初始化
    • initializer_lists初始化
  • 关键字
    • auto
    • decltype
    • nullptr
  • 范围for
  • 右值引用和移动语义
    • 左值和右值
    • 左值引用和右值引用
    • 完美转发
  • 默认成员函数
  • 总结


背景简介

C++11,也被称为C++0x(在它被正式标准化之前的名字),C++11是C++标准的一个里程碑,引入了许多重要的语言特性和库,以适应当时的编程需求和技术发展。在C++98标准发布后的几年中,计算机硬件和软件技术取得了巨大的进步。多核处理器、内存管理和编程模型的变化,以及对更高效、更安全编程的需求,推动了C++的演进。

并且在C++标准化委员会外部,许多C++扩展和竞争性编程语言的发展都开始威胁到C++的地位。C++11的推出也是对这些竞争的回应,旨在使C++更具吸引力和竞争力。C++11引入了新的并发编程模型,以处理多核处理器的普及。标准库中引入了线程、原子操作、锁和条件变量等机制,以帮助开发人员编写多线程程序。C++11旨在提高代码的安全性,通过引入智能指针、类型安全枚举、初始化列表等功能,可以帮助减少内存泄漏和其他常见错误。C++11新的语言特性,如自动类型推导、范围基础的for循环、lambda表达式等,都提高了C++的表达力,减少了样板代码的编写。

不仅如此,C++11还引入了许多新的标准库组件,如std::thread、std::regex、std::array,以及对已有库的改进,如std::vector的移动语义。C++11是经过国际标准化组织ISO/IEC的正式认可的,这意味着它是一个全球范围内接受的标准,而不仅仅是一个特定厂商或社群的扩展。

总的来说,C++11的背景在于应对新的编程挑战、提高C++的现代性和竞争力,并为开发人员提供更多的工具来编写高效、安全的代码。它为C++语言带来了重大的改进,使其适应了当时和未来的编程需求。

统一列表初始化

{}初始化

C++11引入了统一的列表初始化语法,它允许你使用花括号 {} 来初始化各种类型的对象,包括数组、标准容器、结构体和类的成员变量等。这个特性有助于统一初始化语法,使代码更加清晰和一致,使用初始化列表时,可添加等号,也可不添加

//初始化数组:
int arr[] = {1, 2, 3, 4, 5};//初始化标准容器(例如std::vector)
std::vector<int> vec = {1, 2, 3, 4, 5};//初始化结构体
struct Point {int x;int y;
};
Point p = {10, 20};//初始化类的成员变量
class MyClass {
public:int x;double y;
};
MyClass obj = {42, 3.14};//初始化嵌套容器
std::vector<std::vector<int>> matrix = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};

initializer_lists初始化

std::initializer_list 是 C++11 引入的一个特性,它允许你以列表的形式传递一组值给函数或类的构造函数。这个特性通常用于自定义容器类或其他需要处理初始化的场景。如下一个简单的示例,示例中,printValues 函数接受一个 std::initializer_list 类型的参数,该参数可以接受一个整数列表,并将列表中的值打印出来。

#include <iostream>void printValues(std::initializer_list<int> values) 
{for (const auto& value : values) {std::cout << value << " ";}std::cout << std::endl;
}int main() 
{printValues({1, 2, 3, 4, 5});return 0;
}

在这里插入图片描述

关键字

auto

auto 是 C++11 引入的关键字,它的主要作用是用于自动类型推断。auto 允许你声明一个变量而无需明确指定其类型,而是让编译器根据初始化表达式来推断类型。这可以减少代码中的重复类型声明,使代码更加简洁。例如:

auto x = 42; // x 的类型将自动推断为 int
auto y = 3.14; // y 的类型将自动推断为 double
auto z = "Hello, World!"; // z 的类型将自动推断为 const char*

auto 也可以与 const 结合使用,以创建只读变量,在这种情况下,auto 推断的类型将是常量类型。例如:

const auto pi = 3.14159; // pi 的类型将自动推断为 const double

auto 还可以与引用结合使用时,可以保留引用性质,从而避免复制对象。在循环中与容器的迭代器一起使用,简化代码。在 Lambda 表达式中用于参数列表,允许你不需要指定参数的类型,而是由编译器自动推断。

int x = 42;
auto& ref_x = x; // ref_x 的类型将自动推断为 int&std::vector<int> numbers = {1, 2, 3, 4, 5};
for (auto it = numbers.begin(); it != numbers.end(); ++it) {std::cout << *it << " ";
}auto add = [](auto a, auto b) { return a + b; };
int result = add(10, 20); // a 和 b 的类型将根据参数类型自动推断

decltype

decltype 用于获取表达式或变量的类型。它的主要作用是在编译时获取表达式的类型,而不执行实际的表达式,这对于泛型编程和编写模板代码非常有用。decltype 允许你声明一个变量并使用一个表达式,而该变量的类型将根据该表达式的类型来自动推断。这在需要动态确定变量类型的情况下非常有用。例如

int x = 42;
decltype(x) y = x; // y 的类型将自动推断为 intint getValue() { return 42; }
decltype(getValue()) result; // result 的类型将自动推断为 int,但不会调用 getValue()

decltype 与引用结合使用时,会保留表达式的引用性质。也可以用于嵌套表达式,以获取复杂表达式的类型。

int x = 42;
int& ref_x = x;
decltype(ref_x) y = x; // y 的类型将自动推断为 int&int x = 42;
decltype(x + 3.14) y; // y 的类型将自动推断为 double

当然它的最大用处还是在泛型编程中,可以用于定义泛型模板函数或类,以处理各种数据类型。

template <typename T, typename U>
auto add(T a, U b) -> decltype(a + b) 
{return a + b;
}

总之,decltype 是一个强大的工具,用于在编译时获得表达式或变量的准确类型信息,特别适用于泛型编程、模板元编程和元编程中,以提高代码的灵活性和可维护性。

nullptr

nullptr 是 C++11 引入的关键字,用于表示空指针。它是为了解决在 C++98 中使用 NULL 或 0 时可能导致的二义性和类型不匹配问题而引入的。在 C++11 之前,通常使用 NULL 或 0 表示空指针,但这会引发一些问题,因为它们实际上是整数值,可能导致类型不匹配或二义性。而nullptr 是一个特定的空指针常量,没有特定的类型,因此可以用于任何指针类型。也因为它没有特定的类型,不能隐式地转换为其他指针类型,从而减少了类型错误的风险。并且nullptr 在函数重载时非常有用,因为它可以明确表示空指针,而不会与整数重载发生二义性。但是在 C++98 中,使用 0 或 NULL 可能会导致二义性,因为它们是整数值。

总之,nullptr 是一项有助于提高类型安全、减少二义性和更好表示空指针的 C++11 特性。在现代 C++ 编程中,推荐使用 nullptr 来表示空指针,而不是使用 NULL 或 0。

范围for

C++中的范围for循环是一种语法糖,用于遍历容器(如数组、向量、列表等)中的元素或者其他支持迭代器的数据结构。它提供了一种简洁的方式来遍历容器,无需手动管理迭代器或索引。范围for的语法形式如下:

for (element_declaration : range_expression) {// 循环体
}

其中,element_declaration是用于声明循环变量的语句。它定义了一个新的变量,该变量将在每次迭代中代表容器中的当前元素。range_expression是一个表示要遍历的范围的表达式,可以是容器、数组、初始化列表等。并且范围for循环还支持自定义类型的容器,只要该容器提供了迭代器支持。范围for循环的优势在于它简化了遍历容器的过程,并且避免了手动处理迭代器或索引的复杂性。它提供了一种更具可读性和易用性的方式来处理序列中的元素。如下代码:

#include <iostream>
#include <vector>
using namespace std;int main()
{int arr[] = { 1, 2, 3, 4, 5, 6, 7 };for (int e : arr)cout << e << ' ';cout << endl;vector<int> vec = { 7, 6, 5, 4, 3, 2, 1 };for (int e : vec)cout << e << ' ';cout << endl;return 0;
}

对于内置数组、标准库容器(如std::vector、std::list、std::set等)以及其他支持迭代器的容器,范围for循环是直接支持的,无需额外的工作。

右值引用和移动语义

左值和右值

在C++中,左值和右值是表达式的两种不同类型。左值是指表达式后得到的持久对象,具有内存地址。左值可以出现在赋值语句的左侧或右侧,并且可以被取址(获取地址)操作符 & 所获取。通常,变量、函数返回的变量、数组元素等都是左值。右值是指表达式后得到的临时对象或将要销毁的对象,它们没有持久的内存地址。右值只能出现在赋值语句的右侧,并且不能被取址操作符 & 所获取。通常,字面量、临时变量、表达式的结果等都是右值。

int x = 10;  // x 是左值,具有持久内存地址int* p = &x;  // &x 获取 x 的地址,合法操作
// int* p = &10;  // 错误!&10 获取右值 10 的地址,不合法操作int result = x + y;  // x 和 y 都是左值,可以进行加法操作
int result = 5 + 7;  // 5 和 7 都是右值,可以进行加法操作

左值引用和右值引用

左值引用和右值引用是C++中的两种引用类型,它们具有不同的绑定规则和语义。左值引用使用单个 & 符号来声明,例如 T&,其中 T 是类型。左值引用只能绑定到左值(具有持久内存地址的对象)。它用于传递参数、进行别名引用和对象修改等场景。左值引用在函数传参中是常见的,允许通过引用修改传递的对象。

右值引用使用双个 && 符号来声明,例如 T&&。右值引用只能绑定到右值(临时对象、将要销毁的对象)。它引入了移动语义和完美转发的概念,允许高效地处理临时对象,并实现资源的转移和移动构造/移动赋值操作。通过右值引用,我们可以区分出临时对象和持久对象,并对它们进行不同的处理。

在C++11之前,大多数操作符和函数接受的参数都是左值。但是C++11引入了右值引用和移动语义,使得右值能够被有效地管理和利用,从而提高代码性能。右值引用的引入使得我们能够区分左值和右值,并对它们进行不同的处理。需要注意的是,有些左值也可以被转换为右值引用,以便进行移动语义的操作。这可以通过使用 std::move 函数来实现。std::move 将左值强制转换为右值引用,用于表示对其的资源转移操作。

无论是左值引用还是右值引用,都是给对象取别名。

void foo(int& x) 
{  // 左值引用std::cout << "foo - lvalue reference: " << x << std::endl;
}void bar(int&& x) 
{  // 右值引用std::cout << "bar - rvalue reference: " << x << std::endl;
}int main() 
{int a = 10;  // a 是左值foo(a);     // 传递左值给左值引用int b = 20;  // b 是左值foo(b);     // 传递左值给左值引用//foo(5);     // 错误,不能将右值绑定到左值引用bar(5);     // 传递右值给右值引用//bar(a);     // 错误,不能将左值绑定到右值引用return 0;
}

在这里插入图片描述

我们都知道右值是不能取地址的,但是如果将右值用右值引用绑定的话,就可以取地址了,并且还可以对其进行修改,是不是感觉很诧异?如下:

int main() 
{int&& rvalue = 5;int* ptr = &rvalue;cout << ptr << " : " << *ptr << endl;*ptr = 90;cout << ptr << " : " << *ptr << endl;
}

在这里插入图片描述

这是因为当我们用右值引用绑定到右值时,它会延长右值的生命周期,使其变得持久。这时,我们可以对右值引用进行取地址操作,并且可以通过该地址修改右值引用所绑定的对象。但是需要注意的是,取地址并修改右值引用所绑定的对象并不是一个常见的用法,因为右值引用通常用于实现移动语义和完美转发。对于临时对象或将要销毁的对象,直接修改其值可能会导致意外的行为。因此,在实际应用中,我们通常会将右值引用用于移动语义和转发操作,而不是对其进行直接的取地址和修改。

左值引用加const来引用右值也是类似道理。

完美转发

完美转发是一种在函数模板中保持参数类型完整性的技术,它允许将参数按原样转发给另一个函数,包括参数的值类别(左值或右值)和常量性。完美转发通常用于实现泛型函数或类模板中的转发机制,以便将参数传递给其他函数,同时保留原始参数的类型和特性。在 C++ 中,完美转发通常与引用折叠和转发引用相关联。转发引用是一种特殊的引用类型,使用 && 语法声明,可以接受任意值类别的参数。

void func(int&& args)
{cout << "void func(int&& args)" << endl;
}void func(int& args)
{cout << "void func(int& args)" << endl;
}template <typename T>
void forwardFunction(T&& arg)
{func(std::forward<T>(arg));
}int main()
{int x = 10;forwardFunction(2);  //右值forwardFunction(x);  //左值return 0;
}

在这里插入图片描述
模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值,模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是在后续使用中都退化成了左值,使用完美转发就可以在传递过程中保持它的左值或者右值的属性。

默认成员函数

默认成员函数是在 C++ 类中自动生成的成员函数,当用户没有显式定义该成员函数时,编译器会自动生成默认的实现。在C++11之前存在以下几种默认成员函数:

函数说明
默认构造函数如果用户没有提供任何构造函数的定义,编译器会自动生成一个默认构造函数。默认构造函数没有参数,用于创建对象的实例。它执行默认的初始化操作,例如将数据成员初始化为默认值。
默认析构函数如果用户没有提供析构函数的定义,编译器会自动生成一个默认析构函数。默认析构函数用于销毁对象时进行清理操作,例如释放资源或执行必要的清理动作。
默认拷贝构造函数如果用户没有提供拷贝构造函数的定义,编译器会自动生成一个默认的拷贝构造函数。默认拷贝构造函数用于创建一个新对象,并将原始对象的值复制到新对象中。
默认拷贝赋值运算符如果用户没有提供拷贝赋值运算符的定义,编译器会自动生成一个默认的拷贝赋值运算符。默认拷贝赋值运算符用于将一个对象的值赋给另一个对象。

取地址重载和const取地址重载没多大作用就不谈了,有需要请跳转6大默认成员函数。

在C++11 后新增了两个:移动构造函数和移动赋值运算符重载。

函数说明
默认移动构造函数如果用户没有提供移动构造函数的定义且没有实现析构函数 、拷贝构造和拷贝赋值,编译器会自动生成一个默认移动构造函数。默认移动构造函数用于将右值对象的值转移到新创建的对象中,以实现高效的移动语义。
默认移动赋值运算符如果用户没有提供移动赋值运算符的定义且没有实现析构函数 、拷贝构造和拷贝赋值重载,编译器会自动生成一个默认移动赋值运算符。默认移动赋值运算符用于将右值对象的值移动到已存在的对象中,以实现高效的移动语义。

默认成员函数的生成规则和行为是由 C++ 标准定义的,编译器会根据需要自动创建这些函数。它们可以在类的声明内部进行声明,或者可以通过使用 = default 或 = delete 进行显式标记和控制。

总结

文章介绍了C++11中新添加的一些常用的特性以及语法,并且重点介绍了右值引用和移动语义。码文不易,如果文章对你有帮助的话就点一个👍呗,感谢支持!🌹🌹🌹🌹

在这里插入图片描述

这篇关于C++快餐——C++11(1)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/295472

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

c++中std::placeholders的使用方法

《c++中std::placeholders的使用方法》std::placeholders是C++标准库中的一个工具,用于在函数对象绑定时创建占位符,本文就来详细的介绍一下,具有一定的参考价值,感兴... 目录1. 基本概念2. 使用场景3. 示例示例 1:部分参数绑定示例 2:参数重排序4. 注意事项5.

使用C++将处理后的信号保存为PNG和TIFF格式

《使用C++将处理后的信号保存为PNG和TIFF格式》在信号处理领域,我们常常需要将处理结果以图像的形式保存下来,方便后续分析和展示,C++提供了多种库来处理图像数据,本文将介绍如何使用stb_ima... 目录1. PNG格式保存使用stb_imagephp_write库1.1 安装和包含库1.2 代码解

C++实现封装的顺序表的操作与实践

《C++实现封装的顺序表的操作与实践》在程序设计中,顺序表是一种常见的线性数据结构,通常用于存储具有固定顺序的元素,与链表不同,顺序表中的元素是连续存储的,因此访问速度较快,但插入和删除操作的效率可能... 目录一、顺序表的基本概念二、顺序表类的设计1. 顺序表类的成员变量2. 构造函数和析构函数三、顺序表

使用C++实现单链表的操作与实践

《使用C++实现单链表的操作与实践》在程序设计中,链表是一种常见的数据结构,特别是在动态数据管理、频繁插入和删除元素的场景中,链表相比于数组,具有更高的灵活性和高效性,尤其是在需要频繁修改数据结构的应... 目录一、单链表的基本概念二、单链表类的设计1. 节点的定义2. 链表的类定义三、单链表的操作实现四、

使用C/C++调用libcurl调试消息的方式

《使用C/C++调用libcurl调试消息的方式》在使用C/C++调用libcurl进行HTTP请求时,有时我们需要查看请求的/应答消息的内容(包括请求头和请求体)以方便调试,libcurl提供了多种... 目录1. libcurl 调试工具简介2. 输出请求消息使用 CURLOPT_VERBOSE使用 C

C++实现获取本机MAC地址与IP地址

《C++实现获取本机MAC地址与IP地址》这篇文章主要为大家详细介绍了C++实现获取本机MAC地址与IP地址的两种方式,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 实际工作中,项目上常常需要获取本机的IP地址和MAC地址,在此使用两种方案获取1.MFC中获取IP和MAC地址获取