一、auto
使用auto时,必须对auto进行初始化,只有进行初始化才能推导出类型
auto a = 10; //正确
auto b;//错误,没有进行初始化
简单示例:
int temp = 10;//temp 是int类型
auto a = temp;//a 是int类型, auto 自动推断为int类型
auto* b = & temp;//b是int*类型, auto 自动推断为int类型
auto c = &temp;//b 是int*类型, auto 自动推断为int*类型
复杂一点的情况:
int temp = 10; //temp 为int类型
const auto a1 = temp; //a1 为const int 类型,auto 自动推导为int类型
auto a2 = a1;// a2为int 类型,auto自动推导为int类型
const auto &a3 = temp; //a3 为const int& 类型,auto自动推导为int类型
auto &a4 = a3;//a4为const int& 类型,auto自动推导为int类型
auto* a5 = &a3; //a5为const int* 类型,auto自动推导为int类型
通过上面我们会发现
- 当变量是指针或者引用类型时,推导的结果中会保留const
- 当变量不是指针或者引用类型时,推导的结果中不会保留const
应用场景
auto的应用场景不只是上面那些简单的推导,也没必要对上面那些使用auto,它更常用于以下场景:
- 迭代器和范围基于的循环
map<int,string> m;
m.insert(make_pair(1, "nihao"));
m.insert(make_pair(2, "hello"));
m.insert(make_pair(3, "world"));
//auto关键字的使用
auto it = m.begin();
//如果不使用auto关键字,就会显得很麻烦
//map<int,string>::iterator it = m.begin();
for (it; it != m.end(); it++)
{
cout << it->first << " " << it->second << endl;
}
- 返回值类型后置(了解decltype后再介绍)
auto的限制:
- 不能作为函数参数使用
int test(auto a, auto b)//错误
{
cout << "nihao" << endl;
}
//应使用模板
template<typename T1, typename T2>
void test(T1 a, T2 b) {
std::cout << "你好" << std::endl;
}
- 不能用于类的非静态成员变量的初始化
class MyClass {
static auto myStaticVar = 42; // 合法,因为是静态成员变量
// auto myVar = 42; // 非法,不能用于非静态成员变量
};
- 无法使用auto推导出模板参数
template<typename T>
class MyClass {
static auto myStaticVar = 42; // 合法,因为是静态成员变量
// auto myVar = 42; // 非法,不能用于非静态成员变量
};
MyClass<int> t;
MyClass<auto> m = t;//错误
二、decltype
我觉得decltype是auto的补充,在解决auto的限制后做进一步的提高
decltype (表达式),通过表达式推导出变量的类型
int a = 5;
decltype(a) b = 10; // b的类型会被推导为int
std::cout << "b的值为: " << b << std::endl;
const int& c = a;
decltype(c) d = 15; // d的类型会被推导为const int&
std::cout << "d的值为: " << d << std::endl;
现在还看不出来decltype的作用
decltype应用
-返回类型后置
auto func(参数1, 参数2, ...) -> decltype(参数表达式)
通过decltype对auto进行初始化刚开始我觉得很奇怪,既然我能通过表达式来推导出变量类型,为什么不直接前置而使用后置,这不是多次一举吗,后面发现并不是这样,想的还是太浅了,因为我觉得每个人都会想到这一点,不然decltype就没有存在的必要了
template<typename T,typename U>
auto add(T t, U u) -> decltype(t + u) {
return t + u;
}
int main(){
cout << add(1, 2) << endl;
cout << add(1, 2.1) << endl;
return 0;
}
这里我们能够明显的感受到,虽然我们不知道传入参数的类型,但通过decltype我们能有效的减少函数重载,如果我们不使用decltype,我们可能需要为不同的类型组合编写多个重载多个不同返回值的版本(例如,整数和整数、浮点数和浮点数、整数和浮点数等)。而通过使用 decltype,一个泛型函数就能覆盖所有这些情况,
极大地减少了必要的重载数量
同样的,我觉得lambda表达式也属于decltype的范畴
auto compare = [](int a, int b) -> bool {//指定返回值类型为bool类型
return a < b;
};
//我们试图传入一个容器并遍历它
template<typename T>
class Container {
public:
void print(T it) {
for (_it = it.begin(); _it != it.end(); _it++) {
cout << *_it << endl;
}
}
private:
decltype(T().begin()) _it;//获得容器迭代器的类型
//T::iterator _it 我发现这种写法在我的电脑上能够通过编译,但是在别人的电脑上不能通过编译,不能通过编译的原因主要是在模板类中直接使用 T::iterator 作为成员变量类型时,编译器需要在编译时刻就确定这个类型。但是,在模板类本身编译阶段,T 是一个未具体化的模板参数,其具体类型(以及是否有 iterator 嵌套类型)尚未知晓,这导致编译器无法解析 T::iterator。可能是我c++使用的版本较高,后面优化了吧,我们一般使用第一种写法,即decltype进行推导
};
int main()
{
list<int> lt{ 1,2,3,4,5 };
Container <list<int>> c;
c.print(lt);
return 0;
}
这里我们能够发现,虽然decltype不能够对成员变量进行初始化,但能够对成员变量的类型进行推导,这是auto无法实现的,因为auto无法在类中对成员变量进行初始化,自然无法推导变量类型,我觉得这是decltype对auto的补充