C++特性和新特性
C++11
C++11是C++编程语言的一个重要标准版本,是C++98标准发布后13年来的第一次重大修正,它引入了许多新特性和改进,极大地增强了C++语言的表达能力和开发效率。
C++11是C++编程语言的一个重要标准版本,由国际标准化组织(ISO)和国际电工委员会(IEC)旗下的C++标准委员会(ISO/IEC JTC1/SC22/WG21)于2011年8月12日公布,并于2011年9月正式出版,其标准文件号为ISO/IEC 14882:2011。C++11是C++98标准发布后13年来的第一次重大修正,它引入了许多新特性和改进,极大地增强了C++语言的表达能力和开发效率。以下是对C++11的一些主要特性和改进的归纳:
核心语言的新机能
自动类型推导(auto):
C++11引入了auto关键字,用于自动推导变量的类型,使得代码更加简洁,特别是在处理复杂类型或模板类型时。
#include <iostream>
#include <vector>
int main() {
// 使用auto推导vector<int>类型
auto myVector = std::vector<int>{1, 2, 3, 4, 5};
// 遍历vector
for(auto it = myVector.begin(); it != myVector.end(); ++it) {
std::cout << *it << std::endl;
}
// 推导函数返回类型
auto result = myVector.size(); // result的类型是std::vector<int>::size_type,通常是unsigned int
return 0;
}
decltype:
与auto类似,但decltype用于推导表达式的类型,适用于auto无法使用的场景。
#include <iostream>
int main() {
int x = 5;
// 使用decltype获取x的类型,并声明同类型的变量y
decltype(x) y = x;
// 也可以用于复杂表达式
int a = 1, b = 2;
decltype(a + b) c = a + b; // c的类型是int
// 引用类型
int& ref = x;
decltype(ref) anotherRef = y; // anotherRef也是int&类型
// 注意,decltype(表达式)的结果取决于表达式的形式
decltype((x)) wholeX = x; // wholeX是int&,因为表达式(x)是左值
return 0;
}
右值引用和移动语义:
引入了右值引用(T&&)和移动语义,允许临时对象(右值)的资源被“窃取”以进行高效的资源转移,避免了不必要的拷贝操作。
- 右值引用:右值引用是C++11中引入的一个关键特性,它允许程序员显式地将一个表达式标记为右值,从而可以利用移动语义进行优化。在C++中,每个表达式都可以被分类为左值或右值。左值是指那些可以取地址的表达式,如变量、数组元素等,而右值则是指那些不能取地址的表达式,如字面量、临时变量、表达式求值结果等。右值引用就是用来引用这些右值的类型,其语法是在变量名前添加两个连续的“&”符号,如“int&&”。
int&& rvalueRef = 10; // 正确:10是右值,可以被右值引用绑定
int x = 10;
int&& rvalueRef2 = std::move(x); // 正确:使用std::move将x转换为右值
- 移动语义:移动语义是C++11中引入的一种新的语言特性,旨在提高程序的性能和资源管理效率。其核心概念在于允许对象间资源的转移,而非传统的拷贝操作。移动语义通过右值引用和移动构造函数(以及移动赋值运算符)实现了资源的所有权从一个对象到另一个对象的转移,从而避免了不必要的复制操作。
- 移动构造函数:接收一个右值引用参数,并将其资源“移动”到新的对象中,而不是复制这些资源。移动构造函数的定义形式为
ClassName(ClassName&& other);
。 - 移动赋值函数:用于将一个对象的资源转移给另一个已经存在的对象,其定义形式通常为
ClassName& operator=(ClassName&& other);
。
class MyClass {
public:
MyClass(int* data) : ptr(data) {}
MyClass(MyClass&& other) noexcept : ptr(other.ptr) {
other.ptr = nullptr; // 确保原对象不再拥有资源
}
MyClass& operator=(MyClass&& other) noexcept {
if (this != &other) {
delete[] ptr; // 释放原资源
ptr = other.ptr;
other.ptr = nullptr; // 确保原对象不再拥有资源
}
return *this;
}
~MyClass() { delete[] ptr; }
private:
int* ptr;
};
MyClass createMyClass() {
return MyClass(new int[100]); // 返回临时对象,将触发移动语义
}
MyClass obj = createMyClass(); // 这里将调用移动构造函数
统一的初始化:
C++11引入了统一的初始化语法({}),使得所有类型的对象都可以使用相同的初始化方式。通过大括号{}
和std::initializer_list
提供了更加灵活、安全、直观的初始化方法。
- 使用大括号
{}
直接初始化- 对于基本数据类型:
int a{10};
- 对于数组:
int arr[3]{1, 2, 3};
或者int arr[3] = {1, 2, 3};
(注意,这里=
并不是传统意义上的拷贝初始化,而是C++允许的一种简写形式,仍然属于统一初始化) - 对于结构体和类对象:
struct Point{int x, y;} p{1, 2};
- 对于容器:
std::vector<int> vec{1, 2, 3};
- 对于基本数据类型:
- 使用
std::initializer_list
- 在C++11中,许多容器(如
std::vector
、std::map
等)都增加了接受std::initializer_list
作为参数的构造函数,使得容器初始化更加方便。 std::initializer_list
是一个轻量级的、可以容纳固定数量元素的容器,它在初始化时自动生成,并在初始化结束后销毁。
- 在C++11中,许多容器(如
Lambda表达式:
提供了一种定义匿名函数对象的方式,使得编写回调函数等更加简洁方便。Lambda 表达式特别适用于需要函数对象但又不想正式命名一个函数的场景,比如作为算法(如 std::sort
)的参数,或者在需要回调函数的地方。
[capture](parameters) mutable -> return_type {
// 函数体
}
- 捕获列表(
[capture]
):指定哪些外部变量在 lambda 表达式内部是可见的。如果省略捕获列表,则 lambda 表达式不能访问任何外部变量。捕获列表可以是值捕获(通过拷贝)或引用捕获(通过引用)。 - 参数列表(
(parameters)
):与普通函数的参数列表相同,定义了 lambda 表达式的参数。如果 lambda 表达式不接受任何参数,则可以省略参数列表。 - mutable 关键字(可选):允许在 lambda 表达式体内修改被捕获的变量的值(如果它们是通过值捕获的)。默认情况下,这些变量在 lambda 表达式内是不可变的。
- 返回类型(
-> return_type
):指定 lambda 表达式的返回类型。如果 lambda 表达式体只包含一个返回语句,并且编译器可以推导出返回类型,则可以省略返回类型。 - 函数体:定义了 lambda 表达式的操作。
- 一个计算两数之和的简单例子:
#include <iostream>
#include <algorithm>
#include <vector>
int main() {
int a = 10, b = 20;
// 定义一个 lambda 表达式,计算两个整数的和
auto sum = [](int x, int y) { return x + y; };
// 使用 lambda 表达式
std::cout << "The sum is " << sum(a, b) << std::endl;
// 使用 lambda 表达式作为 std::sort 的比较函数
std::vector<int> vec = {4, 1, 3, 5, 2};
std::sort(vec.begin(), vec.end(), [](int x, int y) { return x < y; });
for (int n : vec) {
std::cout << n << ' ';
}
std::cout << std::endl;
return 0;
}
标准库的扩展
智能指针:
C++11标准库增加了shared_ptr、unique_ptr和weak_ptr等智能指针,用于自动管理动态分配的内存,减少内存泄漏的风险。
- unique_ptr
- 独占式智能指针,确保只有一个指针可以指向资源。
- 通过
std::move()
函数可以转移资源的所有权。
#include <iostream>
#include <memory>
class MyClass {
public:
MyClass(int value) : value_(value) {}
void print() const { std::cout << "Value: " << value_ << std::endl; }
private:
int value_;
};
int main() {
// 创建一个 unique_ptr 指向 MyClass 的实例
std::unique_ptr<MyClass> ptr = std::make_unique<MyClass>(10);
// 使用 ptr
ptr->print();
// 当 ptr 离开作用域时,它指向的 MyClass 实例将被自动销毁
return 0;
}
- shared_ptr
- 共享式智能指针,允许多个指针共享同一个资源。
- 采用引用计数机制,当所有shared_ptr对象都不再需要该资源时,资源会自动被销毁。
//Myclass同上
int main() {
// 创建两个 shared_ptr 指向同一个 MyClass 实例
std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>(20);
std::shared_ptr<MyClass> ptr2 = ptr1; // ptr2 和 ptr1 共享所有权
// 使用 ptr1 和 ptr2
ptr1->print();
ptr2->print();
// 当 ptr1 和 ptr2 都离开作用域时,MyClass 实例将被销毁
return 0;
}
- weak_ptr
- 弱引用智能指针,用于辅助shared_ptr工作,不增加资源的引用计数。
- 当所有的shared_ptr对象都不再需要该资源时,weak_ptr对象会自动失效。
//Myclass同上
int main() {
// 创建一个 shared_ptr 和一个 weak_ptr
std::shared_ptr<MyClass> ptr = std::make_shared<MyClass>(30);
std::weak_ptr<MyClass> weakPtr = ptr;
// 使用 ptr
ptr->print();
// 尝试通过 weakPtr 访问资源,需要先锁定 weakPtr
if (auto lockedPtr = weakPtr.lock()) {
lockedPtr->print();
} else {
std::cout << "weak_ptr is expired!" << std::endl;
}
// 当 ptr 离开作用域时,MyClass 实例将被销毁
// weakPtr 将自动变为过期状态
return 0;
}
无序容器:
新增了unordered_map和unordered_set等基于哈希表的容器,提供了比原有map和set更高的查找效率。点击了解更多容器知识:STL标准模板库容器操作集合-CSDN博客
std::unordered_map
:存储键值对,其中每个键都是唯一的。键和值可以是任何可复制且可赋值的类型。std::unordered_multimap
:与unordered_map
类似,但允许键重复。std::unordered_set
:只存储键的集合,键是唯一的。std::unordered_multiset
:与unordered_set
类似,但允许键重复。
正则表达式:
标准库增加了对正则表达式的支持,使得字符串处理更加灵活和强大。从C++11开始,C++标准库引入了<regex>
头文件,提供了对正则表达式的支持。C++中的正则表达式类和相关函数主要包括:
- std::regex:定义包含正则表达式的对象。
- std::smatch和std::cmatch:定义保存匹配结果的对象,分别用于string类型和char*类型的字符串。
- 常用正则匹配函数
- std::regex_match:判断整个目标字符串是否与正则表达式完全匹配。
- std::regex_search:在目标字符串中搜索与正则表达式匹配的第一个子字符串。
- std::regex_replace:用指定的字符串替换与正则表达式匹配的部分。
#include <iostream>
#include <regex>
#include <string>
int main() {
std::string s = "hello, world!";
std::regex e("\\bhello\\b"); // 匹配单词"hello"
if (std::regex_search(s, e)) {
std::cout << "Match found!" << std::endl;
} else {
std::cout << "No match." << std::endl;
}
// 替换匹配到的内容
std::string replaced = std::regex_replace(s, e, "Hi");
std::cout << replaced << std::endl; // 输出: "Hi, world!"
return 0;
}
线程支持:
C++11首次在标准库中引入了线程支持,包括线程(std::thread)、互斥锁(std::mutex)、条件变量(std::condition_variable)等,使得C++能够更方便地进行多线程编程。以下是C++11中线程支持的主要特点:
- std::thread类
std::thread
是C++11中用于表示线程的类。通过创建std::thread
的实例并传递给它一个可调用对象(如函数、lambda表达式、函数对象等),可以启动一个新的线程来执行该可调用对象。std::thread
的构造函数有多种重载形式,允许传递不同数量和类型的参数给线程函数。
- 线程控制
- join():等待线程结束。调用线程(通常是主线程)会阻塞,直到被join的线程执行完毕。join操作是线程同步的一种方式。
- detach():分离线程,使其独立于主线程运行。一旦线程被分离,就不能再对其执行join操作。分离后的线程在结束时会自动释放资源。
- joinable():检查线程是否可以被join。如果线程已经被join或detach,或者线程对象从未与任何执行线程关联,则joinable()返回false。
- 线程ID
- 每个
std::thread
对象都有一个唯一的标识符,可以通过调用get_id()
成员函数来获取。这个ID可以用于区分不同的线程。
- 每个
- 线程互斥与同步
- C++11还引入了
<mutex>
、<condition_variable>
等头文件,提供了互斥锁、条件变量等同步机制,用于解决多线程中的数据竞争和同步问题。
- C++11还引入了
#include <iostream>
#include <thread>
//在这个示例中,我们创建了两个线程t1和t2,它们分别执行threadFunction函数,并传递不同的参数。然后,主线程通过调用join()函数等待这两个线程结束。
void threadFunction(int n) {
for (int i = 0; i < 5; ++i) {
std::cout << "Thread: " << n << ", Count: " << i << std::endl;
}
}
int main() {
std::thread t1(threadFunction, 1);
std::thread t2(threadFunction, 2);
// 等待两个线程结束
t1.join();
t2.join();
return 0;
}
其他重要特性
nullptr:
引入了nullptr作为空指针的字面量,替代了原来的NULL宏,提高了代码的安全性和可读性。
nullptr
是一个特殊的关键字,其类型是std::nullptr_t
。这个类型只能被隐式转换为指针类型,而不能被转换为整数类型,从而避免了类型不匹配的问题。- 相比之下,
NULL
通常被定义为0
或((void*)0)
,可以隐式地转换为任何指针类型或整数类型,这可能导致意外的类型转换错误。
基于范围的for循环:
提供了一种更简洁的遍历容器或数组的方式,使得代码更加简洁易读。
for (declaration : expression) {
// 循环体
}
- 元素类型:在
declaration
中声明的类型应该与容器中元素的类型相匹配,或者至少是容器中元素类型的可隐式转换类型。 - 修改元素:如果你需要在循环中修改元素的值,并且这个修改对容器是可见的,你应该使用元素的引用(通过
&
)来声明变量。例如:
for (int& num : nums) {
num *= 2; // 将会修改容器中的元素
}
例子
#include <iostream>
#include <vector>
int main() {
std::vector<int> nums = {1, 2, 3, 4, 5};
// 使用基于范围的for循环遍历vector
for (int num : nums) {
std::cout << num << " ";
}
return 0;
}
变长参数模板:
允许模板参数的数量在编译时确定,为泛型编程提供了更强大的能力。(没看懂,以后再说)
constexpr:
允许在编译时计算表达式的值,并用于常量表达式的定义,提高了程序的运行效率。使用 constexpr
可以提高程序的性能,因为它允许编译器在编译时进行更多的优化,而不是在运行时计算表达式的值。
- 变量:当用于变量时,
constexpr
变量必须在声明时初始化,并且其值必须是编译时常量。这意味着它不能依赖于运行时才能确定的值,如用户输入或文件读取。
constexpr int max_value = 100; // 正确:编译时常量
// constexpr int x = get_value_from_user(); // 错误:不是编译时常量
// 可以在编译时计算
constexpr int square(int x) {
return x * x;
}
constexpr int result = square(5); // 正确:result 的值是 25,在编译时确定
- 函数:当用于函数时,
constexpr
函数表示该函数可以在编译时求值,但并非所有constexpr
函数都必须在编译时调用。如果一个constexpr
函数在编译时没有被用于需要常量表达式的上下文中,它也可以像普通函数一样在运行时被调用。
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int value = factorial(5); // 在编译时计算
std::cout << "Factorial of 5 is " << factorial(10) << std::endl; // 在运行时计算
return 0;
}
标签:std,int,特性,线程,C++,ptr,表达式
From: https://blog.csdn.net/longer_net/article/details/140123550