C++11是C++语言的一个重要标准,于2011年发布。它引入了许多新特性和改进,旨在提高开发效率和代码质量。
相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言, C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能提升程序员的开发效率。
1.统一的列表初始化
1.1 {}初始化
在C++98中,标准允许使用花括号{}对数组或者结构体元素进行统一的列表初始值设定。
struct Point {
int _x;
int _y;
};
int main() {
int array1[] = {1, 2, 3, 4, 5};
int array2[5] = {0};
Point p = {1, 2};
Point p{ 1,2 };
return 0;
}
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自定义的类型,使用初始化列表时,可添加等号(=),也可不添加。下面是一些示例:
struct Point {
int _x;
int _y;
};
int main() {
int num1{10}; // 初始化整型变量
double pi{3.14159}; // 初始化浮点型变量
char ch{'A'}; // 初始化字符型变量
bool flag{true}; // 初始化布尔型变量
std::string name{"John"};// 初始化字符串对象
int array1[]{1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int array2[5]{0};
Point p{1, 2};
std::vector<int> numbers{1, 2, 3, 4};// 初始化容器对象
std::list<int> lt{1, 2, 3, 4};
std::pair<int, double> p{42, 3.14};// 初始化pair对象
//C++11中列表初始化也可以适用于new表达式中
int *pa = new int[4]{0};
return 0;
}
创建对象时也可以使用列表初始化方式调用构造函数初始化
class Date {
public:
Date(int year, int month, int day)
: _year(year), _month(month), _day(day) {
cout << "Date(int year, int month, int day)" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1(2022, 1, 1);// old style()
// C++11支持的列表初始化,这里会调用构造函数初始化
Date d2{2022, 1, 2};//new style{}
Date d3 = {2022, 1, 3};
return 0;
}
列表初始化在初始化时,如果出现类型截断,是会报警告或者错误的
#include <iostream>
using namespace std;
int main() {
short c = 65535;
short d{65535};
return 0;
}
//narrowing conversion of '65535' from 'int' to 'short int' inside { } [-Wnarrowing]
1.2 std::initializer_list
std::initializer_list
是C++11标准库中引入的一个类模板,用于简化初始化列表的处理。它允许在函数或构造函数中以初始化列表的形式传递一组值,并在接收端以统一的方式访问这些值。
std::initializer_list
通过重载构造函数和迭代器接口,提供了一种简单的方式来访问初始化列表中的元素。它可以被用于定义函数参数、构造函数参数、以及其他接受初始化列表的容器类。
std::initializer_list是什么类型
void test() {
auto i1 = { 10, 20, 30, 40, 50, 60, 70 };
auto i2 = { 10, 20, 30 };
cout << typeid(i1).name() << endl; //class std::initializer_list<int>
cout << typeid(i2).name() << endl; //class std::initializer_list<int>
}
std::initializer_list特点和用法
1.构造函数和成员函数: std::initializer_list
是一个模板类,它接受任意类型的元素,并提供了构造函数和成员函数来处理初始化列表。这些函数包括:
-
initializer_list()
:默认构造函数,创建一个空的初始化列表。 -
initializer_list(size_type count, const T& value)
:构造函数,创建包含指定数量的元素,每个元素都是给定值的初始化列表。 -
const T* begin() const
:返回指向初始化列表中第一个元素的指针。 -
const T* end() const
:返回指向初始化列表中最后一个元素的下一个位置的指针。 -
size_type size() const
:返回初始化列表中元素的数量。
2.语法使用: 使用 std::initializer_list
的语法类似于使用大括号 {}
来初始化一个列表。例如:
std::initializer_list<int> myList = {1, 2, 3, 4, 5};
3.函数参数: 可以将 std::initializer_list
用作函数的参数,以便在函数调用时传递一组值。例如:
void myFunction(std::initializer_list<int> values) {
// 使用初始化列表中的值
for (const auto& value : values) {
// 处理每个值
}
}
// 调用函数
myFunction({1, 2, 3, 4, 5});
4.构造函数参数: 类的构造函数可以接受 std::initializer_list
参数,以便在创建对象时使用初始化列表进行初始化。例如:
class MyClass {
public:
MyClass(std::initializer_list<int> values) {
// 使用初始化列表中的值
for (const auto& value : values) {
// 处理每个值
}
}
};
// 创建对象时使用初始化列表
MyClass obj = {1, 2, 3, 4, 5};
std::initializer_list使用场景
std::initializer_list
一般是作为构造函数的参数,C++11对STL中的不少容器就增加了std::initializer_list
作为参数的构造函数,这样初始化容器对象就更方便了。也可以作为operator= 的参数,这样就可以用大括号赋值
void test() {
auto i1 = { 10, 20, 30, 40, 50, 60, 70 };
auto i2 = { 10, 20, 30 };
initializer_list<int>::iterator it1 = i1.begin();
initializer_list<int>::iterator it2 = i2.begin();
cout << it1 << endl; //00000053083EF1F8
cout << it2 << endl; //00000053083EF268
initializer_list<int> i3 = { 10,20,30,40,50,60,70,80 };
initializer_list<int>::iterator it3 = i3.begin();
cout << it3 << endl; //00000053083EF308
}
void test2() {
Date d1(2023,5,20);
Date d2(2023,5,21);
//initializer_list<Date> 从C++11开始常量数组会被识别成initializer_list
vector<Date> v1 = { d1,d2 };
vector<Date> v2 = { Date(2023,5,20),Date(2023,5,21) };
vector<Date> v3 = { {2023,5,20},{2023,5,21} };
map<string, string> dict = { {"sort","排序"},{"string","字符串"},{"Date","日期"}};
pair<string, string> kv1 = { "Date","日期" };
pair<string, string> kv2{ "Date","日期" };
}
std::initializer_list
的引入使得在C++中处理初始化列表变得更加方便和一致。它在函数参数和构造函数参数中的使用为处理一组值提供了一种简洁而灵活的方式。
让模拟实现的vector也支持{}初始化和赋值
template<class T>
class vector {
public:
typedef T* iterator;
vector(initializer_list<T> l)
{
_start = new T[l.size()];
_finish = _start + l.size();
_endofstorage = _start + l.size();
iterator vit = _start;
//内嵌类型需要加上typename
typename initializer_list<T>::iterator lit = l.begin();
while (lit != l.end())
{
*vit++ = *lit++;
}
//for (auto e : l)
// *vit++ = e;
}
vector<T>& operator=(initializer_list<T> l) {
vector<T> tmp(l);
std::swap(_start, tmp._start);
std::swap(_finish, tmp._finish);
std::swap(_endofstorage, tmp._endofstorage);
return *this;
}
private:
iterator _start;
iterator _finish;
iterator _endofstorage;
};
2.声明
2.1 auto
在C++11以前(同C语言),auto
是一个存储类说明符,用于指示变量具有自动存储期。当在函数内部声明变量时,如果没有显式指定存储类说明符,则默认为 auto
存储类。
auto
存储类意味着变量在进入其作用域时自动创建,在离开作用域时自动销毁。这是C语言中变量的默认行为,因此在C中使用 auto
关键字并不常见。
但是局部域中定义局部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。
当你使用 auto 关键字时,编译器会分析赋值运算符(=)右侧的初始化表达式,并确定变量的适当类型。然后,该类型被推导并赋值给变量。
下面是一个示例,说明了 auto 的用法:
void test(){
auto x = 42; // x 被推导为 int 类型
auto y = 3.14; // y 被推导为 double 类型
auto z = "Hello, World!"; // z 被推导为 const char* 类型
// 你也可以在更复杂的类型中使用 auto,比如迭代器:
std::vector<int> numbers = {1, 2, 3, 4, 5};
auto it = numbers.begin(); // it 被推导为 std::vector<int>::iterator 类型
int i = 10;
auto p = &i;
auto pf = strcpy;
cout << typeid(p).name() << endl; //int * __ptr64
cout << typeid(pf).name() << endl; //char * __ptr64 (__cdecl*)(char * __ptr64,char const * __ptr64)
}
2.2 decltype
decltype
是 C++11 引入的一种类型推导机制。它用于从表达式中推导出变量或表达式的类型。在编译时计算表达式的类型,并将其作为变量或函数返回类型的一部分使用。
decltype
的语法如下所示:
decltype(expression)
其中,expression
是一个表达式,可以是变量名、函数调用、类型转换等。
decltype
的工作方式如下:
- 如果
expression
是一个标识符(变量名),decltype(expression)
将推导出该标识符的类型。 - 如果
expression
是一个函数调用,decltype(expression)
将推导出该函数调用的返回类型。 - 如果
expression
是一个表达式,decltype(expression)
将推导出该表达式的类型。
下面是一些示例来说明 decltype
的用法:
void test(){
int x = 5;
decltype(x) y = 10; // 推导出 y 的类型为 int
int foo();
decltype(foo()) result = foo(); // 推导出 result 的类型为 foo() 函数的返回类型
int a = 1;
int b = 2;
decltype(a + b) c = a + b; // 推导出 c 的类型为 int,表达式 a + b 的类型为 int
}
decltype
的一个常见用途是在模板编程中,可以用于从函数返回值或表达式中推导出类型,并将其用作其他类型的模板参数或变量类型。
需要注意的是,decltype
并不会执行表达式,它仅仅用于推导表达式的类型。此外,decltype
推导出的类型可能会保留引用和 cv 限定符(const 和 volatile)。
decltype的一些使用场景
template<class T1, class T2>
void F(T1 t1, T2 t2) {
decltype(t1 * t2) ret;
cout << typeid(ret).name() << endl; //int
}
int main() {
const int x = 1;
double y = 2.2;
decltype(x * y) ret;// ret的类型是double
decltype(&x) p; // p的类型是int*
cout << typeid(ret).name() << endl; //double
cout << typeid(p).name() << endl; //int const * __ptr64
F(1, 'a');
// vector存储的类型跟x*y表达式返回值类型一致
// decltype推导表达式类型,用这个类型实例化模板参数或者定义对象
vector<decltype(x* y)> v;
return 0;
}
2.3 nullptr
nullptr
是一个空指针常量,用于表示一个空指针。它是C++11标准引入的一个新特性,旨在解决C++中NULL的一些模糊和二义性问题。
在C++中,通常使用指针来指向内存中的对象或函数。而空指针表示指针不指向任何有效的对象或函数。在C++之前的版本中,通常使用NULL
宏来表示空指针,它通常被定义为0或者(void*)0
。然而,这样的定义在某些情况下可能引起二义性,因为0可能被用于表示整数类型的零值。
为了解决这个问题,C++11引入了nullptr
关键字,它是一个明确的空指针常量。使用nullptr
可以明确地表示一个指针为空,而不会与整数0混淆。这样可以避免一些编程错误和歧义。
使用nullptr
的语法非常简单,只需将其直接赋值给指针变量即可,例如:
int* ptr = nullptr; // 整型指针指向空
nullptr
还可以用于与其他指针进行比较操作。与其他指针相比较时,nullptr
的结果将为真(即指针为空)。例如:
int* ptr = nullptr;
if (ptr == nullptr) {
// 指针为空
}
nullptr
是C++11引入的一个空指针常量,用于明确表示空指针而不引起歧义。它提供了更好的类型安全性和代码清晰性,是现代C++编程中推荐使用的空指针表示方式。
3.范围for循环
3.1 范围for的语法
在C++98中如果要遍历一个数组,可以按照以下方式进行:
void TestFor() {
int array[] = {1, 2, 3, 4, 5};
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
array[i] *= 2;
for (int *p = array; p < array + sizeof(array) / sizeof(array[0]); ++p)
cout << *p << endl;
}
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因 此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
void TestFor() {
int array[] = {1, 2, 3, 4, 5};
for (auto &e: array)
e *= 2;
for (auto e: array)
cout << e << " ";
}
注意:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。
3.2 范围for的使用条件
1.for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围; 对于类而言,应该提供 begin和end的方法,begin和end就是for循环迭代的范围。
注意:以下代码就有问题,因为for的范围不确定
void TestFor(int array[]) {
for (auto &e: array)
cout << e << endl;
}
2.迭代的对象要实现++和==的操作。
4.STL中的变化
C++11引入了一些重要的改进和新增的特性,对STL(Standard Template Library)也有一些变化和增强。以下是C++11后STL的主要变化:
1.移动语义(Move Semantics):C++11引入了右值引用(Rvalue references)和移动构造函数(Move constructors)以及移动赋值运算符(Move assignment operators)。这些特性使得STL容器和算法能够更高效地管理资源,并提供了对可移动类型(如std::unique_ptr和std::shared_ptr)的更好支持。
2.新的容器类型:C++11引入了两个新的容器类型:
- std::array:是一个固定大小的数组容器,与C风格数组相比,提供了更安全和更方便的接口。
- std::unordered_XXX:引入了哈希容器,如std::unordered_map和std::unordered_set,用于提供基于哈希的快速查找和插入操作。
3.新的算法:C++11引入了一些新的算法,丰富了STL的功能:
- std::move、std::move_backward:用于移动元素。
- std::copy_if、std::move_if:根据条件复制或移动元素。
- std::find_if_not:查找第一个不满足给定条件的元素。
- std::is_sorted、std::is_partitioned:用于检查序列是否已排序或已分区。
- std::all_of、std::any_of、std::none_of:用于检查序列中的元素是否满足特定条件。
4.lambda表达式:C++11引入了lambda表达式,它允许在STL算法中以一种更紧凑和便捷的方式编写匿名函数。使用lambda表达式,可以更容易地编写自定义的谓词(predicate)和其他函数对象。
5.并发编程支持:C++11引入了std::thread和std::mutex等并发编程的原语,使得STL在多线程环境下更容易使用。此外,还引入了std::atomic和std::atomic_flag等原子操作类型,用于实现无锁算法。
6.新的标准库组件:C++11引入了一些新的标准库组件,例如:
- std::tuple:用于表示固定数量的异构值的元组。
- std::chrono:提供了时间和日期的类型和操作。
- std::regex:支持正则表达式的库。
这些变化和增强使得C++11后的STL更强大、更高效,并且提供了更多的工具和选项来处理现代C++编程的需求。