一些实用的 C++ 11、14、17 特性总结
For定义自定义类的迭代器;// 这里的迭代器是广义的迭代器,指针也属于该范畴。该类型拥有begin()和end()成员方法,返回值为迭代器(或者重载全局的begin()和end()函数也可以)。自定义迭代器的!比较操作。自定义迭代器的++前置自增操作,显然该操作要是迭代器对象指向该容器的下一个元素。自定义迭代器解引用操作,显然解引用操作必须容器对应元素的引用,否则引用遍历时将会出错。案例1pub
C++ 19
C++ 17
内联变量
C++17前只有内联函数,现在有了内联变量,我们印象中C++类的静态成员变量在头文件中是不能初始化的,但是有了内联变量,就可以达到此目的:
折叠表达式
从C++17开始,可以使用二元操作符对形参包中的参数进行计算,这一特性主要针对可变参数模板进行提升,可以分为左折叠和右折叠。
输出 : 15
if 语句初始化
约束作用域,简洁代码
namespace 嵌套
C++ 14
函数返回值类型推导
C++14对函数返回类型推导规则做了优化
函数内如果有多个return语句,它们必须返回相同的类型,否则编译失败。
如果return语句返回初始化列表,返回值类型推导也会失败
如果函数是虚函数,不能使用返回值类型推导
返回类型推导可以用在递归函数中,但是递归调用必须以至少一个返回语句作为先导,以便编译器推导出返回类型。
deprecated
C++14中增加了deprecated标记,修饰类、变、函数等,当程序中使用到了被其修饰的代码时,编译时被产生警告,用户提示开发者该标记修饰的内容将来可能会被丢弃,尽量不要使用。
二进制字面量
C++14引入了二进制字面量,也引入了分隔符
a = 15
b = 100000000
C++ 11
重要关键字
auto
auto name = value;
auto
只是一个占位符,在编译器期间它会被真正的类型所替代。或者说,C++ 中的变量必须是有明确类型的,只是这个类型是由编译器自己推导出来的。
使用 auto
类型推导的变量必须马上初始化。因为 auto
在 C++11 中只是“占位符”,并非如 int 一样的真正的类型声明。
当value类型不为引用时,auto
的推导结果将不保留表达式的 const
属性。
当value类型为引用时,auto
的推导结果将保留表达式的 const
属性。
auto
不能在函数的参数中使用,因为我们在定义函数的时候只是对参数进行了声明,指明了参数的类型,但并没有给它赋值。
auto
不能作用于类的非静态成员变量(也就是没有 static
关键字修饰的成员变量)中。
auto
关键字不能定义数组。
decltype
decltype(exp) name = value;
decltype
根据 exp
表达式推导出变量的类型,跟 =
号右边的 value
没有关系!!!
exp
是一个普通的表达式,它可以是任意复杂的形式,但我们必须保证 exp
的结果是有类型的,不能是 void
。
- 推导规则1:
exp
是标识符、类访问表达式,decltype(exp)
和exp
的类型一致。 - 推导规则2:
exp
是函数调用,decltype(exp)
和返回值的类型一致。 - 推导规则3:其他情况,若
exp
是一个左值,则decltype(exp)
是exp
类型的左值引 用,否则和exp
类型一致。
using 与 typedef
using 与 typedef 区别:
- using 的别名语法覆盖了 typedef 的全部功能。
- using 别名语法比 typedef 更加清晰
lambda匿名函数
一个完整的lambda匿名函数表达式如下:
在C++14中,对此进行优化,lambda表达式参数可以直接是 auto
constexpr
常量表达式,指的就是由多个常量组成的表达式。也就是说如果表达式中的成员都是常量,那么该表达式就是一个常量表达式。这也意味着,常量表达式一旦确定,其值将无法修改。
- 修饰普通变量时
- 在程序编译阶段确定结果
- 常量表达式中包含浮点数时,考虑到程序编译和运行所在的系统环境可能不同,常量表达式在编译阶段和运行阶段计算出的结果精度很可能会受到影响, C++11 标准规定,浮点常量表达式在编译阶段计算的精度要至少等于(或者高于)运行阶段计算出的精度。
- 修饰函数时
- 整个函数的函数体中,除了可以包含 using 指令、typedef 语句以及 static_assert 断言外,只能包含一条 return 返回语句。
- 该函数必须有返回值,即函数的返回值类型不能是 void。
- 函数在使用之前,必须有对应的定义语句。我们知道,函数的使用分为“声明”和“定义”两部分,普通的函数调用只需要提前写好该函数的声明部分即可(函数的定义部分可以放在调用位置之后甚至其它文件中),但常量表达式函数在使用前,必须要有该函数的定义。
- return 返回的表达式必须是常量表达式
- 修饰自定义类型(用class或者struct实现):通过修饰类的构造函数
- constexpr 修饰类的构造函数时,要求该构造函数的函数体必须为空,且采用初始化列表的方式为各个成员赋值时,必须使用常量表达式。
C++14 修改
C++14相较于C++11对constexpr减少了一些限制
- C++11中constexpr函数可以使用递归,在C++14中可以使用局部变量和循环
- C++11中constexpr函数必须必须把所有东西都放在一个单独的return语句中,而C++14中constexpr则无此限制
指针
nullptr
C++11 标准中引入一个新关键字,即 nullptr。nullptr 是 nullptr_t 类型的右值常量,专用于初始化空类型指针。nullptr_t 是 C++11 新增加的数据类型,可称为“指针空值类型”。也就是说,nullptr 仅是该类型的一个实例对象(已经定义好,可以直接使用). NULL 并不是 C++ 的关键字,它是 C++ 为我们事先定义好的一个宏,并且它的值往往就是字面量 0(#define NULL 0)
智能指针
智能指针,可以从字面上理解为“智能”的指针。具体来讲,智能指针和普通指针的用法是相似的,不同之处在于,智能指针可以在适当时机自动释放分配的内存。也就是说,使用智能指针可以很好地避免“忘记释放内存而导致内存泄漏”问题出现。
std::shared_ptr智能指针
shared_ptr 智能指针可以共同使用同一块堆内存。并且,由于该类型智能指针在实现上采用的是引用计数机制,即便有一个 shared_ptr 指针放弃了堆内存的“使用权”(引用计数减 1),也不会影响其他指向同一堆内存的 shared_ptr 指针(只有引用计数为 0 时,堆内存才会被自动释放)。
初始化 shared_ptr 智能指针时,还可以自定义所指堆内存的释放规则,这样当堆内存的引用计数为 0 时,会优先调用我们自定义的释放规则。
std::unique_ptr
unique_ptr“唯一”拥有其所指对象,同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。相比与原始指针unique_ptr用于其RAII的特性,使得在出现异常的情况下,动态资源能得到释放。unique_ptr指针本身的生命周期:从unique_ptr指针创建时开始,直到离开作用域。离开作用域时,若其指向对象,则将其所指对象销毁(默认使用delete操作符,用户可指定其他操作)。unique_ptr指针与其所指对象的关系:在智能指针生命周期内,可以改变智能指针所指对象,如创建智能指针时通过构造函数指定、通过reset方法重新指定、通过release方法释放所有权、通过移动语义转移所有权。
右值引用
左值的英文简写为“lvalue”,右值的英文简写为“rvalue”。很多人认为它们分别是"left value"、“right value” 的缩写。
其实不然,lvalue
是“loactor value”的缩写,可意为存储在内存中、有明确存储地址(可寻址)的数据,而 rvalue
译为 “read value”,指的是那些可以提供数据值的数据(不一定可以寻址,例如存储于寄存器中的数据)。
Range-Based-For
C++11中引入了基于范围的For(Range-Based-For)。但是, 这个是只读的!!!
std::vector<int> vec {1,2,3,4,5,6,7,8,9,10};
for (auto n :vec)
std::cout << n;
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
for (auto n : arr)
std::cout << n;
在循环迭代时,不可以修改容器!!!
如下代码,在运行时会出现程序崩溃的情况。
vector<int> vec = { 1, 2, 3, 4, 5, 6 };
int main() {
for (auto &n : vec)
{
cout << n << endl;
vec.push_back(7);
}
}
基于范围的For循环的内部实现机制还是依赖于迭代器的相关实现。
遍历时修改
std::vector<int> vec {1,2,3,4,5,6,7,8,9,10};
cout << "修改前" << endl;
for (auto n :vec)
std::cout << n++;
cout << endl;
cout << "修改后" << endl;
for (auto j : vec)
std::cout << j;
输出:
修改前
12345678910
修改后
12345678910
如果要修改的话:::将声明的遍历 n
从 auto
声明为 auto &
,类型声明必须为 引用类型!!!
std::vector<int> vec {1,2,3,4,5,6,7,8,9,10};
cout << "修改前" << endl;
for (auto& n :vec)
std::cout << n++;
cout << endl;
cout << "修改后" << endl;
for (auto j : vec)
std::cout << j;
输出:
修改前
12345678910
修改后
234567891011
关于 set 的细节
使用基于范围的 for
循环还要注意一些容器类本身的约束,比如 set
的容器内的元素本身有容器的特性就决定了其元素是只读的,哪怕的使用了引用类型来遍历 set
元素,也是不能修改器元素的。
set<int> ss = { 1, 2, 3, 4, 5, 6 };
for (auto& n : ss)
cout << n++ << endl;
关于 map 的细节
std::map<string, int> map = { { "a", 1 }, { "b", 2 }, { "c", 3 } };
for (auto &val : map)
cout << val.first << "->" << val.second << endl;
为什么是使用 val.first
val.second
而不是直接输出 value
呢?
在遍历容器的时候,auto
自动推导的类型是容器的 value_type
类型,而不是迭代器,而 map
中的 value_type
是 std::pair
,也就是说 val
的类型是 std::pair
类型的,因此需要使用 val.first,val.second
来访问数据。
与 set
类似,对于 map
中的 first
元素也是不能进行修改的。
冒号后为函数调用
如果冒号后面的表达式是一个函数调用时,函数仅会被调用一次!!!
set<int> ss = { 1, 2, 3, 4, 5, 6 };
const set<int>& getSet() {
cout << "GetSet" << endl;
return ss;
}
int main() {
for (auto &n : getSet())
cout << n << endl;
}
GetSet
1
2
3
4
5
6
请按任意键继续. . .
自定义类实现基于范围的 For
- 定义自定义类的迭代器 ; // 这里的迭代器是广义的迭代器,指针也属于该范畴。
- 该类型拥有
begin()
和end()
成员方法,返回值为迭代器(或者重载全局的begin()
和end()
函数也可以)。 - 自定义迭代器的
!=
比较操作 。 - 自定义迭代器的
++
前置自增操作,显然该操作要是迭代器对象指向该容器的下一个元素 。 - 自定义迭代器
*
解引用操作,显然解引用操作必须容器对应元素的引用,否则引用遍历时将会出错。
案例1
class Myiterator {
public:
Myiterator(int val) :value_(val){}
bool operator!=(const Myiterator& other) const {
return this->value_ != other.value_;
}
const int & operator*() {
return value_;
}
int& operator++() {
return ++value_;
}
private:
int value_;
};
class Range {
public:
Range(int begin, int end) :begin_(begin), end_(end){}
Myiterator& begin() {
return Myiterator(begin_);
}
Myiterator& end() {
return Myiterator(end_);
}
private:
int begin_;
int end_;
};
int main() {
for (auto i : Range(1, 10))
cout << i << " ";
}
如果要实现 Range(1,10,2)
也就是带步长的 Range
的话只需要将其迭代器的 ++
操作符改写,value_+=2
即可。
模板类型案例2:
template <typename T>
class Myiterator {
public:
Myiterator(T val) :value_(val){}
bool operator!=(const Myiterator<T>& other) const {
return this->value_ != other.value_;
}
const T & operator*() {
return value_;
}
T operator++() {
return ++value_;
}
private:
T value_;
};
template<typename T>
class Range {
public:
Range(T begin, T end) :begin_(begin), end_(end){}
Myiterator<T>& begin() {
return Myiterator<T>(begin_);
}
Myiterator<T>& end() {
return Myiterator<T>(end_);
}
private:
T begin_;
T end_;
};
int main() {
for (auto i : Range<int>(1, 10))
cout << i << " ";
}
新增容器
forward_list
forward_list
是 C++ 11 新添加的一类容器,其底层实现和 list
容器一样,采用的也是链表结构,只不过 forward_list
使用的是单链表,而 list
使用的是双向链表。
H 表示链表的表头。
使用链表存储数据最大的特点在于,其并不会将数据进行集中存储(向数组那样),即,链表中数据的存储位置是分散的、随机的,整个链表中数据的线性关系通过指针来维持。
forward_list
容器具有和 list
容器相同的特性,即擅长在序列的任何位置进行插入元素或删除元素的操作,但对于访问存储的元素,没有其它容器(如 array
、vector
)的效率高。
单链表只能从前向后遍历,而不支持反向遍历,因此 forward_list
容器只提供前向迭代器,而不是双向迭代器。这意味着,forward_list
容器不具有 rbegin()
、rend()
之类的成员函数。
forward_list 容器具有和 list 容器相同的特性,list 容器还可以提供更多的功能函数;但是 forward_list
容器底层使用单链表,存储相同个数的同类型元素,单链表耗用的内存空间更少,空间利用率更高,并且对于实现某些操作单链表的执行效率也更高。
#include <forward_list>
using namespace std;
// 创建一个没有任何元素的空 forward_list 容器:
forward_list<int> values;
// 创建一个包含 n 个元素的 forward_list 容器:
forward_list<int> values(10);
// 通过此方式创建 values 容器,其中包含 10 个元素,每个元素的值都为相应类型的默认值(int类型的默认值为 0)。
// 创建一个包含 n 个元素的 forward_list 容器,并为每个元素指定初始值。例如:
forward_list<int> values(10, 5);
// 如此就创建了一个包含 10 个元素并且值都为 5 个 values 容器。
// 在已有 forward_list 容器的情况下,通过拷贝该容器可以创建新的 forward_list 容器。例如:
forward_list<int> value1(10);
forward_list<int> value2(value1);
// 注意,采用此方式,必须保证新旧容器存储的元素类型一致。
// 通过拷贝其他类型容器(或者普通数组)中指定区域内的元素,可以创建新的 forward_list 容器。例如:
// 拷贝普通数组,创建 forward_list 容器
int a[] = { 1,2,3,4,5 };
forward_list<int> values(a, a+5);
// 拷贝其它类型的容器,创建forward_list容器
array<int, 5>arr{ 11,12,13,14,15 };
forward_list<int>values(arr.begin()+2, arr.end());
// 拷贝 arr 容器中的 {13,14,15}
#include <iostream>
#include <forward_list>
// forward_list 容器中是不提供 size() 函数的,
// 但如果想要获取 forward_list 容器中存储元素的个数,可以使用头文件 <iterator> 中的 distance() 函数
#include <iterator>
using namespace std;
int main()
{
std::forward_list<int> values{1,2,3};
values.emplace_front(4);//{4,1,2,3}
values.emplace_after(values.before_begin(), 5); //{5,4,1,2,3}
values.reverse();//{3,2,1,4,5}
for (auto it = values.begin(); it != values.end(); ++it) {
cout << *it << " "; // 3 2 1 4 5
}
std::forward_list<int> my_words{1,2,3,4};
int count = std::distance(std::begin(my_words), std::end(my_words));
cout << count; // 4
std::forward_list<int> values{1,2,3,4};
auto it = values.begin();
advance(it, 2);
while (it!=values.end()) {
cout << *it << " ";
++it;
}
return 0;
}
更多推荐
所有评论(0)