目录
1、auto
1.1、解释
自动类型推导:即在使用auto关键字以后,编译器子啊编译期间自动推导出变量的类型。
1.2、语法
auto name = value;
name:变量的名称,value:变量的初始值
1.3、注意事项
1、auto仅仅是一个占位符,编译期间它会被真正的类型所替代。或者说,c++中的变量必须是有明确类型的,知识这个类型是编译器自己推导出来的。
2、推导的时候不能有二义性。
3、推导时,如果 = 右边的表达式是一个引用类型,auto会把引用抛弃,直接推导出它原始的类型。
1.4、实例
int main() {
auto a = 10;
auto b = 10.1;
auto c = &a;
auto d = "abcdefghij";
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
system("pause");
}
1.5、auto和const的结合
用法:
1、当类型不为引用时,auto的推导结果不保留const属性。
2、当类型是引用时,auto的推导结果将保留const属性。
1.6、auto的限制
1、auto必须对变量进行初始化
2、auto不能在函数的参数中使用
3、auto不能作用于类的非静态成员变量(没有被static关键字修饰的成员变量)中
4、auto不能用于定义数组
5、auto不能作用于模版参数
1.7、auto的应用
1、使用auto定义迭代器
2、auto 用于泛型编程
#include <iostream>
using namespace std;
class A{
public:
static int get(void){
return 100;
}
};
class B{
public:
static const char* get(void){
return "http://c.biancheng.net/cplus/";
}
};
template <typename T>
void func(void){
auto val = T::get();
cout << val << endl;
}
int main(void){
func<A>();
func<B>();
return 0;
}
本例中的模板函数 func() 会调用所有类的静态函数 get(),并对它的返回值做统一处理,但是 get() 的返回值类型并不一样,而且不能自动转换。这种要求在以前的 C++ 版本中实现起来非常的麻烦,需要额外增加一个模板参数,并在调用时手动给该模板参数赋值,用以指明变量 val 的类型。
但是有了 auto 类型自动推导,编译器就根据 get() 的返回值自己推导出 val 变量的类型,就不用再增加一个模板参数了。
2、decltype
2.1、解释
decltype 是 C++11 新增的一个关键字,它和 auto 的功能一样,都用来在编译时期进行自动类型推导。但它们的用法是有区别。
2.2、语法
对比auto:
auto varname = value;
decltype(exp) varname = value;
decltype(exp) varname;
varname
表示变量名,value
表示赋给变量的值,exp
表示一个表达式。
auto 根据=右边的初始值 value 推导出变量的类型,而 decltype 根据 exp 表达式推导出变量的类型,跟=右边的 value 没有关系。
2.3、注意事项
1、exp是一个普通的表达式,它可以是任意复杂的形式,但必须保证exp的结果必须是有类型的,不能是void
2.4、实例
int a = 0;
decltype(a) b = 1; //b 被推导成了 int
decltype(10.8) x = 5.5; //x 被推导成了 double
decltype(x + 100) y; //y 被推导成了 double
2.5、decltype推导规则
1、如果 exp 是一个不被括号( )包围的表达式,或者是一个类成员访问表达式,或者是一个单独的变量,那么 decltype(exp) 的类型就和 exp 一致,这是最普遍最常见的情况。
2、如果 exp 是函数调用,那么 decltype(exp) 的类型就和函数返回值的类型一致。
3、如果 exp 是一个左值,或者被括号( )包围,那么 decltype(exp) 的类型就是 exp 的引用;假设 exp 的类型为 T,那么 decltype(exp) 的类型就是 T&。
2.6、decltype实际运用
auto 的语法格式比 decltype 简单,所以在一般的类型推导中,使用 auto 比使用 decltype 更加方便,但auto 只能用于类的静态成员,不能用于类的非静态成员(普通成员),如果我们想推导非静态成员的类型,这个时候就必须使用 decltype 了
template <typename T>
class Base {
public:
void func(T& container) {
m_it = container.begin();
}
private:
decltype(T().begin()) m_it; //注意这里
};
注意:有些低版本的编译器可能不支持T().begin()这种写法。
T(),匿名对象。可以如下理解:
3、返回值类型后置
3.1、解释
C++11 中增加了返回类型后置(trailing-return-type,又称跟踪返回类型)语法,可以将 decltype 和 auto 结合起来完成返回值类型的推导。
3.2、实例
template <class T,class U>
auto add(T&a,U&b) ->decltype(a+b){
return a + b;
}
int foo(int& a) {
return a;
}
double foo(double& b) {
return b;
}
template <class T>
auto func(T&val) ->decltype(foo(val)) {
return foo(val);
}
似乎只使用 auto 也可以完成自动推导?
3.3、注意事项
1、返回值类型后置语法,是为了解决函数返回值类型依赖于参数而导致难以确定返回值类型的问题。有了这种语法以后,对返回值类型的推导就可以用清晰的方式(直接通过参数做运算)描述出来。
2、但这份工作,似乎可以直接是用auto 作为返回值代替?
4、左值右值(引用)
4.1、左值和右值
左值:在内存有确定的存储地址,有变量名,表达式结束后依旧存在的值,简单来说,左值就是非临时对象。
右值:在内存没有确定的地址,没有变量名,表达式结束就会销毁的值,简单来说,右值就是临时对象。
左值可以分为:
1、常量左值
2、非常量左值
int a=10; // a 为非常量左值(有确定存储地址,也有变量名)
const int a1=10; //a1 为常量左值(有确定存储地址,也有变量名)
const int a2=20; //a2 为常量左值(有确定存储地址,也有变量名)
右值可以分为:
1、常量右值
2、非常量右值
int a=10; // 10 为非常量右值
const int a1=10;
const int a2=20;
a1+a2 // (a1+a2) 为常量右值
判断左右值方法:
1、可位于赋值号(=)左侧的表达式就是左值;反之,只能位于赋值号右侧的表达式就是右值。
2、有名称的、可以获取到存储地址的表达式即为左值;反之则是右值。
4.2、左值引用和右值引用
左值引用:绑定到左值的引用,通过&来获得左值引用。
总结:
1、非常量左值引用只能绑定到非常量左值上;
2、常量左值引用可以绑定到非常量左值、常量左值、非常量右值、常量右值等所有的值类型。
右值引用:绑定到右值的引用,通过&&来获得右值引用。
在c++14中,“常量右值 不能绑定到 非常量右值引用”这一规则被修改了。可以绑定!
其实,C++11 标准中对右值做了更细致的划分,分别称为纯右值(Pure value,简称 pvalue)和将亡值(eXpiring value,简称 xvalue )。其中纯右值就是 C++98/03 标准中的右值,而将亡值则指的是和右值引用相关的表达式(比如某函数返回的 T && 类型的表达式)。对于纯右值和将亡值,都属于右值,读者知道即可,不必深究。
4.3、移动语义和完美转发
右值引用主要用于移动语义和完美转发:
移动语义:允许我们在语言级别上将资源从一个对象“移动”到另一个对象,而不是复制它。
完美转发:允许我们以一种类型安全的方式将参数转发给其他函数,并保留其值类别(左值或右值)。
5、tuple元组
5.1、解释
tuple,直译为为元组:实例化的对象可以存储任意数量,任意类型的数据。是一种类模版,类似于vector
5.2、tuple对象创建
头文件: #include<tuple>
1、使用构造方法创建
2、make_tuple()
make_tuple()函数的功能是,创建一个tuple右值对象(或临时对象)
5.3、tuple常用函数
1、tup1.swap(tup2) || swap(tup1,tup2)
功能描述:
tup1 和 tup2 表示类型相同的两个 tuple 对象,tuple 模板类中定义有一个 swap() 成员函数,<tuple> 头文件还提供了一个同名的 swap() 全局函数。
功能是,交换两个tuple对象存储的内容。
实例:
2、get<num>(tup)
功能描述:
tup 表示某个 tuple 对象,num 是一个整数,get() 是 <tuple> 头文件提供的全局函数。
功能是,返回 tup 对象中第 num+1 个元素。
实例:
3、tuple_size<type>::value
功能描述:
tuple_size 是定义在 <tuple> 头文件的类模板,它只有一个成员变量 value,type 为该tuple 对象的类型。
功能是,获取某个 tuple 对象中元素的个数。
实例:
4、tuple_element<I, type>::type
功能描述:
tuple_element 是定义在 <tuple> 头文件的类模板,它只有一个成员变量 type。<I,type>中,I 是位置,索引从0开始,type是元组对象类型
功能是,获取某个 tuple 对象第 I+1 个元素的类型。
实例:
5、forward_as_tuple<args...>
功能描述:
args... 表示 tuple 对象存储的多个元素,
功能是,创建一个 tuple 对象,内部存储的 args... 元素都是右值引用形式的。
实例:
6、tie(args...) = tup
功能描述:
tup 表示某个 tuple 对象,tie() 是 <tuple> 头文件提供的。
功能是,将 tup 内存储的元素逐一赋值给 args... 指定的左值变量。
实例:
7、tuple_cat(args...)
功能描述:
args... 表示多个 tuple 对象,该函数是 <tuple> 头文件提供的,
功能是,创建一个 tuple 对象,此对象包含 args... 指定的所有 tuple 对象内的元素。
实例:
表格:
函数或类模板 | 描 述 |
tup1.swap(tup2)swap(tup1, tup2) | tup1 和 tup2 表示类型相同的两个 tuple 对象,tuple 模板类中定义有一个 swap() 成员函数,<tuple> 头文件还提供了一个同名的 swap() 全局函数。 |
get<num>(tup) | tup 表示某个 tuple 对象,num 是一个整数,get() 是 <tuple> 头文件提供的全局函数,功能是返回 tup 对象中第 num+1 个元素。 |
tuple_size<type>::value | tuple_size 是定义在 <tuple> 头文件的类模板,它只有一个成员变量 value,功能是获取某个 tuple 对象中元素的个数,type 为该tuple 对象的类型。 |
tuple_element<I, type>::type | tuple_element 是定义在 <tuple> 头文件的类模板,它只有一个成员变量 type,功能是获取某个 tuple 对象第 I+1 个元素的类型。 |
forward_as_tuple<args...> | args... 表示 tuple 对象存储的多个元素,该函数的功能是创建一个 tuple 对象,内部存储的 args... 元素都是右值引用形式的。 |
tie(args...) = tup | tup 表示某个 tuple 对象,tie() 是 <tuple> 头文件提供的,功能是将 tup 内存储的元素逐一赋值给 args... 指定的左值变量。 |
tuple_cat(args...) | args... 表示多个 tuple 对象,该函数是 <tuple> 头文件提供的,功能是创建一个 tuple 对象,此对象包含 args... 指定的所有 tuple 对象内的元素。 |
5.4、注意事项
tuple 模板类对赋值运算符 = 进行了重载,使得同类型的 tuple 对象可以直接赋值。此外,tuple 模板类还重载了 ==、!=、<、>、>=、<= 这几个比较运算符,同类型的 tuple 对象可以相互比较(逐个比较各个元素)。
6、lambda匿名函数
6.1、匿名函数的定义
语法格式:
[外部变量访问方式说明符] (参数) mutable noexcept/throw() -> 返回值类型
{
函数体;
};
说明:
1、[外部变量的访问方式说明符]
[ ] 方空号向编译器表明这是一个lambda表达式,不能被省略。方括号内部,注明当前lambda函数的函数体可以使用那些“外部变量"。
外部变量:指和当前lambda表达式位于同一作用域的所有局部变量。
2、(参数)
匿名函数所接受的传递参数。如果不需要参数,"( )",也可以省略 。
3、mutable
此关键字可以省略,当使用时,"( )"则不可省略,但参数个数可以为0,。lambda表达式,默认情况下是不可以修改以值传递过来的外部变量的(可以理解为这些变量为const),如果需要修改外部变量的值,就需要使用mutable关键字。
注意:对于值传递的外部变量,lambda表达式修改的是拷贝的那一份,并不会影响真正的外部变量。
4、noexcept/throw()
可以省略,如果使用,“()”则必须存在(参数的个数可以为0),默认情况下匿名函数可以抛出任何类型的异常,如果标注noexcept关键字,则表示不会抛出任何异常。throw()可以指定lambda函数内部可以抛出的异常类型。
5、->返回值类型
指明lambda表达式的返回值类型。如果 lambda 函数体内只有一个 return 语句,或者该函数返回 void,则编译器可以自行推断出返回值类型,此情况下可以直接省略-> 返回值类型。
6、函数体
和普通函数一样,lambda 匿名函数包含的内部代码都放置在函数体中。该函数体内除了可以使用指定传递进来的参数之外,还可以使用指定的外部变量以及全局范围内的所有全局变量。可以省略函数体。
注意事项:
外部变量会受到以值传递还是以引用传递方式引入的影响,而全局变量则不会。换句话说,在 lambda 表达式内可以使用任意一个全局变量,必要时还可以直接修改它们的值。
最简单的lambda表达式:[] {}
这是一个没有任何功能的lambda匿名函数。
6.2、匿名函数的外部变量
定义格式:
外部变量格式 | 功能 |
[] | 空方括号表示当前 lambda 匿名函数中不导入任何外部变量。 |
[=] | 只有一个 = 等号,表示以值传递的方式导入所有外部变量; |
[&] | 只有一个 & 符号,表示以引用传递的方式导入所有外部变量; |
[val1,val2,...] | 表示以值传递的方式导入 val1、val2 等指定的外部变量,同时多个变量之间没有先后次序; |
[&val1,&val2,...] | 表示以引用传递的方式导入 val1、val2等指定的外部变量,多个变量之间没有前后次序; |
[val,&val2,...] | 以上 2 种方式还可以混合使用,变量之间没有前后次序。 |
[=,&val1,...] | 表示除 val1 以引用传递的方式导入外,其它外部变量都以值传递的方式导入。 |
[this] | 表示以值传递的方式导入当前的 this 指针。 |
注意:单个外部变量不允许以相同的传递方式导入多次。例如 [=,val1] 中,val1 先后被以值传递的方式导入了 2 次,这是非法的。
6.3、实例
//匿名函数作谓词,实现STL中的sort()排序算法
void my_sort() {
vector<int> v{0,4,7,9,8,6,3,2,1};
cout << "排序前:";
for_each(v.begin(), v.end(), [](int x) {cout << x << " "; });
cout << endl;
// x > y 降序排序
sort(v.begin(), v.end(), [](int x, int y) {return x > y; });
cout << "排序后:";
for_each(v.begin(), v.end(), [](int x) {cout << x << " "; });
cout << endl;
}
7、基于范围的for循环
7.1、语法格式
for (declaration : expression){
//循环体
}
说明:
1、declaration
表示这里要定义一个变量,该变量的类型为要遍历序列中存储元素的类型。
注意事项:C++ 11 标准中,declaration参数处定义的变量类型可以用 auto 关键字表示,该关键字可以使编译器自行推导该变量的数据类型。
2、expression
表示要遍历的序列,常见的可以为事先定义好的普通数组或容器,还可以是用{}大括号初始化的序列。
7.2、同c++98中for循环的区别
同 C++ 98/03 中 for 循环的语法格式相比较,此格式并没有明确限定 for 循环的遍历范围,这是它们最大的区别,即旧格式的 for 循环可以指定循环的范围,而C++11 标准增加的 for 循环,只会逐个遍历 expression 参数处指定序列中的每个元素。
7.3、实例
//基于范围的for
void test01() {
char a[] = "hello world";
string str = "c++yyds";
vector<int> v{9,8,7,6,5,1,2,3,4};
for (auto i:a) {
cout << i;
}
cout <<"!"<< endl;
for (auto i : str) {
cout << i;
}
cout << "!" << endl;
for (auto i : v) {
cout << i << " ";
}
cout << "!" << endl;
for (auto i : {1,2,3,4,5,9,8,7,6}) {
cout << i << " ";
}
cout << "!" << endl;
}
7.4、注意事项
1、新格式的 for 循环在遍历字符串序列时,不只是遍历到最后一个字符,还会遍历位于该字符串末尾的 '\0'(字符串的结束标志)
2、对于for(auto ch : myvector){};
而言,遍历容器时,定义的ch 不是迭代器类型,而是会被推导成所存储元素的类型,表示的是 myvector 容器中存储的每个元素。
3、在使用新语法格式的 for 循环遍历某个序列时,如果需要遍历的同时修改序列中元素的值,实现方案是在 declaration 参数处定义引用形式的变量。
4、declaration 参数既可以定义普通形式的变量,也可以定义引用形式的变量,应该如何选择呢?其实很简单,如果需要在遍历序列的过程中修改器内部元素的值,就必须定义引用形式的变量;反之,建议定义const &(常引用)形式的变量(避免了底层复制变量的过程,效率更高),也可以定义普通变量。
8、constexpr
8.1、解释
常量表达式:
由多个(≥1)常量组成的表达式。换句话说,如果表达式中的成员都是常量,那么该表达式就是一个常量表达式。这也意味着,常量表达式一旦确定,其值将无法修改。
扩展:非常量表达式只能在程序运行阶段计算出结果;而常量表达式的计算往往发生在程序的编译阶段。
constexpr 关键字功能:使指定的非常量表达式获得在程序编译阶段计算出结果的能力,而不必等到程序运行阶段。
8.2、constexpr修饰普通变量
constexpr 修饰变量,使该变量获得在编译阶段即可计算出结果的能力。
注意事项:使用 constexpr 修改普通变量时,变量必须经过初始化且初始值必须是一个常量表达式。
实例:
8.3、constexpr修饰函数
constexpr修饰函数的返回值,这样的函数又称为“常量表达式函数”。
成为常量表达式函数的条件:
1、 整个函数的函数体中,除了可以包含 using 指令、typedef 语句以及 static_assert 断言外,只能包含一条 return 返回语句。
2、该函数必须有返回值,即函数的返回值类型不能是 void。
3、常量表达式函数在使用前,必须要有该函数的定义。
4、return 返回的表达式必须是常量表达式
不过第1,2条规则在c++14中都被修改了,不满足也可以是常量表达式。
9、move()函数
9.1、解释
功能:将某个左值强制转化为右值,常用于实现移动语义。
语法:move( arg )
其中,arg 表示指定的左值对象。该函数会返回 arg 对象的右值形式。
10、nullptr
10.1、解释
nullptr 是 nullptr_t 类型的右值常量,专用于初始化空类型指针。
nullptr_t 是 C++11 新增加的数据类型,可称为“指针空值类型”。也就是说,nullpter 仅是该类型的一个实例对象(已经定义好,可以直接使用),nullptr 可以被隐式转换成任意的指针类型。
10.2、实例
在 C++11 标准下,相比 NULL 和 0,使用 nullptr 初始化空指针可以令我们编写的程序更加健壮。
11、shared_ptr(共享指针)
11.1、解释
C++ 智能指针底层是采用引用计数的方式实现的。简单的理解,智能指针在申请堆内存空间的同时,会为其配备一个整形值(初始值为 1),每当有新对象使用此堆内存时,该整形值 +1;反之,每当使用此堆内存的对象被释放时,该整形值减 1。当堆空间对应的整形值为 0 时,即表明不再有对象使用它,该堆空间就会被释放掉。
多个 shared_ptr 智能指针可以共同使用同一块堆内存。
11.2、shared_ptr的创建
注意:空的shared_ptr指针,其初始引用计数为0,不是1。
赋初值:
调用构造函数:
sp2 是左值,因此会调用拷贝构造函数。
move(sp2)将 sp2 转换成对应的右值,因此初始化 sp5 调用的是移动构造函数。与调用拷贝构造函数不同,用 move(sp2) 初始化 sp5,会使得 sp5 拥有了 sp2的堆内存,而 sp2则变成了空智能指针。
11.3、自定义释放规则
对于申请的动态数组来说,shared_ptr 指针默认的释放规则是不支持释放数组的,只能自定义对应的释放规则,才能正确地释放申请的堆内存。
对于申请的动态数组,释放规则可以使用 C++11 标准中提供的 default_delete<T> 模板类,我们也可以自定义释放规则:
11.4、shared_ptr<T>成员方法
成员方法名 | 功 能 |
operator=() | 重载赋值号,使得同一类型的 shared_ptr 智能指针可以相互赋值。 |
operator*() | 重载 * 号,获取当前 shared_ptr 智能指针对象指向的数据。 |
operator->() | 重载 -> 号,当智能指针指向的数据类型为自定义的结构体时,通过 -> 运算符可以获取其内部的指定成员。 |
swap() | 交换 2 个相同类型 shared_ptr 智能指针的内容。 |
reset() | 当函数没有实参时,该函数会使当前 shared_ptr 所指堆内存的引用计数减 1,同时将当前对象重置为一个空指针;当为函数传递一个新申请的堆内存时,则调用该函数的 shared_ptr 对象会获得该存储空间的所有权,并且引用计数的初始值为 1。 |
get() | 获得 shared_ptr 对象内部包含的普通指针。 |
use_count() | 返回同当前 shared_ptr 对象(包括它)指向相同的所有 shared_ptr 对象的数量。 |
unique() | 判断当前 shared_ptr 对象指向的堆内存,是否不再有其它 shared_ptr 对象再指向它。 |
operator bool() | 判断当前 shared_ptr 对象是否为空智能指针,如果是空指针,返回 false;反之,返回 true。 |
除此之外,C++11 标准还支持同一类型的 shared_ptr 对象,或者 shared_ptr 和 nullptr 之间,进行 ==,!=,<,<=,>,>= 运算。
实例:
12、unique_ptr(唯一指针)
12.1、解释
unique_ptr指针只能拥有一个指向对象的所有权,不能进行复制或赋值操作,因此它是不可共享的。unique_ptr 的实现基于移动语义,可以允许移动拥有权,而不是进行复制或赋值操作。这种方式大大提高了程序的效率。
12.2、unique_ptr创建
12.3、unique_ptr成员函数
unique_ptr智能指针的成员函数与shared_ptr相似。
1、get:返回指向被管理对象的指针
2、reset:替换被管理的对象,释放原来管理的资源,管理新的资源
3、release:释放被管理对象的所有权,即解除智能指针和资源的管理关系
4、swap:交换被管理对象的所有权
5、operator bool() const:检查是否有关联的被管理对象
6、重载operator*,operator->,访问被管理的对象
实例:
13、weak_ptr(弱指针)
13.1、解释
weak_ptr 是 shared_ptr 的一种辅助类型,用于解决 shared_ptr 循环引用问题。在使用 shared_ptr 时,容易出现循环引用的问题,即两个对象互相引用,导致它们的计数器无法降为 0,从而导致内存泄漏。
weak_ptr 可以解决这个问题,它不会增加引用计数,仅仅是一个指向 shared_ptr 对象的弱引用。
13.2、weak_ptr指针创建
weak_ptr的创建和通常和shared_ptr , unique_ptr配套使用