PART3:原有语法的实用性增强

特性1:类型推导autodecltype

auto关键字

auto
1、背景

在以前的C++中,auto关键字用于指明变量的存储类型,和static关键字是相对的。auto表明变量是自动存储的,这也是编译器的默认规则,因此写不写系统都会自动加上。例如int a=10;// auto int a=10;表示a自动存储

而在C++11后为了顺应c# JAVAScript PHP等语言的自动推导变量的特性对auto重新赋予新的含义。

2、使用方法

auto关键字的基本使用语法包含有定义一个变量、连续定义多个变量。案例如下:

案例1:定义一个变量

1
2
3
4
5
6
7
8
// 案例一:定义一个变量
auto valueName=value; // VlaueName是变量名称,value是变量的值

// 具体实例
auto n = 10; // n为int
auto f = 12.8; // f浮点型,默认为double
auto p = &n; // p为int*的指针
auto url = "http://c.biancheng.net/cplus/"; // url为const char*

案例2:连续定义多个变量

1
2
3
// 案例二:连续定义多个变量
int n = 20;
auto *p = &n, m = 99; // 这里推导出auto为int,后面的m也是int型,若写成12.5则是错误的为12.5是double型和int冲突

注意:在连续定义多个变量时不能存在二义性

image-20220410151057950

auto关键字的高级使用语法包含有混合类型推导、与const关键字配合使用。案例如下:

案例1:混合类型推导

auto除了单独使用还可以与某些具体类型混合使用,因此auto充当半个类型。

1
2
3
4
5
int  x = 0;
auto *p1 = &x; //p1 为 int *,auto 推导为 int
auto p2 = &x; //p2 为 int*,auto 推导为 int*
auto &r1 = x; //r1 为 int&,auto 推导为 int
auto r2 = r1; //r2 为 int,auto 推导为 int 重点!!!

第五行需要特别==注意==,当auto推导出=右边为引用类型时,auto会自动丢弃引用,直接推导出原始类型

案例2:const关键字配合使用

1
2
3
4
5
int  x = 0;
const auto n = x; // n为const int类型,因此auto为int型
auto f = n; // f为const int,auto 推导为int(const属性被丢弃)
const auto &r1 = x; // r1为const int &,auto推到位int
auto &r2 = r1; // r2为const int &,auto推导为const int

第3行中nconst int类型,但是被推导为int类型,这说明等号==右边为const属性时==,auto不会使用const类型,而是==直接推导出non-const类型==。

第5行中的r2const int&auto推导为cosnt int,这说明当==const和引用结合时==,auto的推导将==保留表达式的const类型==。

对于autoconst总结如下:

  • 类型不为引用时,auto推导结果将不保留表达式的const属性。
  • 当类型为引用时,auto推导结果将保留表达式的cosnt属性。
3、使用限制

1)auto使用时必须初始化

2)auto不能作为函数的参数(因为函数在定义时只是指明了参数的类型,而值是在调用时才会赋值,auto需要根据值推导类型,在定义时就需要赋值这和函数参数的使用矛盾)。

3)不能定义数组,例如下面的案例就是错误的

1
2
char url[] = "http://c.biancheng.net/";
auto str[] = url; //arr 为数组,所以不能使用 auto

4)auto不能作为模版参数

1
2
3
4
5
6
7
8
9
template <typename T>
class A{
//TODO:
};
int main(){
A<int> C1;
A<auto> C2 = C1; //错误
return 0;
}
4、应用场景

1)STL定义迭代器:在使用STL容器时,需要定义迭代器来遍历容器中的元素,不同的迭代器有不同的类型,在定义迭代器时必须指明,因此可以使用auto简写。案例如下:

1
2
3
vector<vector<int>> v;
vector<vector<int>>::iterator ite=v.begin(); // 直接定义太复杂
auto ite=v.begin(); // 使用auto关键字

2)泛型编程:在不知道什么类型或者不希望指定类型时使用。案例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class T1
{
public:
int getvalue()
{
return 10;
}
};
class T2
{
public:
const char* getvalue()
{
return "hello c++";
}
};
template<TypeName t>
void func()
{
auto val=t::getvalue(); // 这里泛型编程根据结果去自动推导
cout<<val<<endl;
}

如果没有auto关键字时应该使用如下方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class T1
{
public:
int getvalue()
{
return 10;
}
};
class T2
{
public:
const char* getvalue()
{
return "hello c++";
}
};
template<TypeName t,TypeName t2>
void func()
{
t2 val=t::getvalue(); // 没有使用auto关键字需要携带类型
cout<<val<<endl;
}

decltype关键字

decltype

1、背景

decltype的全称是declare type的缩写,表示“声明类型”。decltype也是C++11中新增的一个关键字,和auto的功能一样,主要原因是因为auto在有些特殊情况下使用不方便甚至无法使用。

2、使用方法

推导的形式如下所示:需要根据exp来推导变量的类型。而exp是一个普通的表达式,也可以是任意复杂的形式,但必须保证exp的结果是有类型的,不能是void的,例如当exp调用一个返回类型为void的函数时,exp的结果也是void类型,此时会导致编译错误。

1
decltype(exp) varname; // decltype主要是根据exp来推导变量的类型。

decltype的推导规则包含以下三条:

  • exp是一个不被()包围的表达式,或者是一个类成员访问表达式,或者是一个单独的变量,则decltype(exp)的类型和exp一样。
  • exp为一个函数调用,则decltype(exp)的类型和函数返回值的类型一致
  • exp是一个左值,或者被括号()包围,那么decltype(exp)的类型就是exp的引用;假设exp的类型为T,那么decltype(exp)的类型就是T&

案例1:exp是一个普通表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <string>
using namespace std;
class Student{
public:
static int total;
string name;
int age;
float scores;
};
int Student::total = 0;
int main(){
int n = 0;
const int &r = n;
Student stu;
decltype(n) a = n; // int型
decltype(r) b = n; // cosnt int& 型
decltype(Student::total) c = 0; // int 型
decltype(stu.name) url = "http://c.biancheng.net/cplus/"; // string型
return 0;
}

案例2:exp为函数调用

1
2
3
4
5
6
7
8
9
10
11
12
13
//函数声明
int& func_int_r(int, char);
int&& func_int_rr(void);
int func_int(double);
const int& fun_cint_r(int, int, int);
const int&& func_cint_rr(void);
//decltype类型推导
int n = 100;
decltype(func_int_r(100, 'A')) a = n; // int&型
decltype(func_int_rr()) b = 0; // int&&型
decltype(func_int(10.5)) c = 0; // int型
decltype(fun_cint_r(1,2,3)) x = n; // const int&
decltype(func_cint_rr()) y = 0; // cont int&&

案例3:exp为左值,或被()包围

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using namespace std;
class Base{
public:
int x;
};
int main(){
const Base obj;
decltype(obj.x) a = 0; // int
decltype((obj.x)) b = a; // int&
int n = 0, m = 0;
decltype(n + m) c = 0; // int
decltype(n = n + m) d = c; //int & n为一个左值
return 0;
}

注意:decltype(n = n + m) d = c;其中n代表一个左值所以会采用int&型。

左值:在表达式执行结束后依然存在的数据,右值:表达式执行结束不存在的,是一个临时的。

3、实际应用

1)auto只能用于类的静态成员,不能用于类的非静态成员(普通成员),因此如果想要推导类的非静态成员必须使用decltype

2)解决泛型编程中不能推导const类型迭代器的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <vector>
using namespace std;
template <typename T>
class Base {
public:
void func(T& container) {
m_it = container.begin();
}
private:
typename T::iterator m_it; //注意这里
};
int main()
{
const vector<int> v;
Base<const vector<int>> obj;
obj.func(v); // 由于这里传入的是const类型便令因此应该使用const_iterator,而不应该是用iterator读写类型的迭代器。
return 0;
}

在使用decltype之后

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <vector>
using namespace std;
template <typename T>
class Base {
public:
void func(T& container) {
m_it = container.begin();
}
private:
decltype(T().begin()) m_it; // 注意这里采用decltype类型推导
};
int main()
{
const vector<int> v;
Base<const vector<int>> obj;
obj.func(v); // 由于这里传入的是const类型便令因此应该使用const_iterator,而不应该是用iterator读写类型的迭代器。
return 0;
}

总结

1、使用方法auto的推导规则为:auto valueType=value;根据value推导出valuetypedecltype(exp) valueType;是根据exp推导出类型和=右边无关。

2、对CV(const、Volatile)关键字的处理:如果表达式不是指针或引用auto自动丢弃constvolatile属性。如果是指针或引用保留cosntvolatile属性。而decltype会保留constvolatile属性。

3、对引用的处理:当表达式为引用时,auto丢弃引用类型,直接推导出原始类型;decltype会保存引用类型。