auto
基础
- 当变量不是指针或者引用类型时,推导的结果中不会保留
const
、volatile
关键字 - 当变量是指针或者引用类型时,推导的结果中会保留
const
、volatile
关键字 - 很好理解,指针或者引用表示对内存那个位置的数操作,那么有
const
表示对那块区域的内存值不能修改,那么推导后const
关键字就该保留
auto b; // 错误,需要初始化
int temp = 110;
auto *a = &temp; // a 为 int* auto ---> int
auto b = &temp; // b 为 int* auto ---> int*
auto &c = temp; // c 为 int auto ---> int
auto d = temp; // d 为 int auto ---> int
int tmp = 250;
const auto a1 = tmp; // a1 为 const int
auto a2 = a1; // a2 为 int,虽然 a1 为 const int,但是这里不保留 const
const auto &a3 = tmp; // a3 为 const int&
auto &a4 = a3; // a4 为 const int&
auto *a5 = &a1; // a5 为 const int*
auto 的限制
只需记住一个点,
auto
推导是在编译时进行
- 不能作为函数参数使用
// 错误
int func(auto a, auto b) {
cout << "a: " << a <<", b: " << b << endl;
}
// 这种错误就是因为只有传参的时候才知道传参的类型,才能推导,而传参是在运行时了,编译无法推导
// 因此要实现这种功能,就是函数模板的实现
- 不能用于类的非静态成员变量的初始化
class Test {
// 因为只有创建了对象,才知道类型,那么编译这里代码时,无法知晓类型
auto v1 = 0;
// 错误,类的静态非常量成员不允许在类内部直接初始化
static auto v2 = 0;
// 正确
static constexpr auto v3 = 10;
}
- 不能使用
auto
关键字定义数组
int func() {
int array[] = {1,2,3,4,5}; // 定义数组
auto t1 = array; // ok, t1 被推导为 int* 类型
auto t2[] = array; // error, auto 无法定义数组
auto t3[] = {1,2,3,4,5};; // error, auto 无法定义数组
}
- 无法使用
auto
推导出模板参数
template <typename T>
struct Test {};
int func() {
Test<double> t;
Test<auto> t1 = t; // error, 无法推导出模板类型
return 0;
}
运用场景
// STL
map<int, string> mp;
mp[1] = "zhangsan";
mp[2] = "lisi";
// 一般方法
for(map<int, string>::iterator it = mp.begin(); it != mp.end(); it++) {
cout << it->first << " " << it->second << endl;
}
// 使用 auto
for(auto t : mp) {
cout << t.first << " " << t.second << endl;
}
// 泛型编程
class T1 {
public:
static int get() {
return 10;
}
};
class T2 {
public:
static string get() {
return "hello, world";
}
};
// 未使用 auto
template <class T, typename P>
void func() {
P val = T::get();
cout << "val: " << val << endl;
}
int main() {
func<T1, int>();
func<T2, string>();
return 0;
}
// 使用 auto
template <class T>
void func() {
auto val = T::get();
cout << "val: " << val << endl;
}
int main() {
func<T1>();
func<T2>();
return 0;
}
decltype
基础
编译时推导 根据表达式推导类型,但表达式不需要计算,只做类型推导
// 对于类型推导不确定的,检测
decltype(1.0) x = 1;
if (is_same<decltype(x), double>::value) {
cout << "x is double" << endl; // 输出这句话
}
- 表达式为普通变量或者普通表达式或者类表达式
int x = 99;
const int &y = x;
decltype(x) a = x; // int
decltype(y) b = x; // const int &
decltype(2) c = x; // 2 为 int c ---> int
const int z = 1;
decltype(z) d = 2; // const int
// 类型和表达式的类型是一致的
- 表达式是函数调用
class Test{...};
//函数声明
int func1();
int& func2();
int&& func3();
const int func4();
const int& func5();
const int&& func6();
const Test func7();
//decltype类型推导
int n = 100;
decltype(func1()) a = 0; // int
decltype(func2()) b = n; // int &
decltype(func3()) c = 0; // int &&
decltype(func4()) d = 0; // int
decltype(func5()) e = n; // const int &
decltype(func6()) f = 0; // const int &&
decltype(func7()) g = Test(); // Test
func4()
返回的是一个纯右值(在表达式执行结束后不再存在的数据,也就是临时性的数据)- 对于纯右值而言,只有
类类型可以携带 const、volatile
限定符 - 除此之外需要忽略掉这两个限定符,因此推导出的变量
d
的类型为int
而不是const int
// 类类型的
class Test {
public:
const Test func() {return Test() };
};
Test t1, t2;
decltype(t1.func()) p = t2; // const Test
- 表达式是一个左值 (可以取地址),或者被括号 ( ) 包围
- 用
decltype
推导出的是表达式类型的引用(如果有const
、volatile
限定符不能忽略)
class Test {
public:
int num;
};
int main() {
const Test obj;
decltype(obj.num) a = 0; // int
decltype((obj.num)) b = a; // const int &
int n = 0, m = 0;
decltype(n + m) c = 0; // int
decltype(n = n + m) d = n; // int &
return 0;
}
// 第一个不加 const 原因和上面一致
// 第二个加 const 是因为 const Test obj,引用是根据规则,它有括号
// 疑惑点第三个和第一个也是左值,为啥不加引用
// 是因为规则的先后,它们更是一个普通变量类型,优先规则一
// 如果进行了加减乘除操作还赋值,就用第三个规则,如第四个案例
运用场景
// 泛型编程
// 未使用 decltypr
template <class T>
class Container {
public:
void func(T& c) {
for (m_it = c.begin(); m_it != c.end(); ++m_it) {
cout << *m_it << " ";
}
cout << endl;
}
private:
typename T::iterator m_it;
};
int main() {
list<int> lst;
Container<list<int>> obj;
obj.func(lst);
return 0;
}
// 首先上述有个易错点,就是必须加 typename,目前告诉编辑器这 T::iterator 是类型
// 为啥编辑器不能识别,原因是存在以下情况
class A {
public:
int iterator;
};
class B {
public:
typedef int iterator;
};
// 对于 A,A::iterator 是一个成员变量
// 对于 B,B::iterator 是一个类型
// 而且这里还有不足之处,就是迭代器存在 iterator 和 const_iterator,无法确定
// 使用 decltype 推导
// 使用 decltype
template <class T>
class Container {
public:
void func(T& c) {
for (m_it = c.begin(); m_it != c.end(); ++m_it) {
cout << *m_it << " ";
}
}
private:
decltype(T().begin()) m_it; // T() 是创建了个匿名对象
};
int main()
{
const list<int> lst{ 1,2,3,4,5,6,7,8,9 };
Container<const list<int>> obj;
obj.func(lst);
return 0;
}
// 虽然这个例子中使用 auto 更简单,这只是示例它的用法
返回值类型后置
template <typename R, typename T, typename U>
R add(T t, U u) {
return t + u;
}
int main()
{
int x = 520;
double y = 13.14;
// auto z = add<decltype(x + y), int, double>(x, y);
// 简化之后的写法
// 因为可以自动化推导出 x, y类型
auto z = add<decltype(x + y)>(x, y);
cout << "z: " << z << endl;
return 0;
}
// 但是实际有问题,因为有可能这个函数模板提前封装起来了,不方便知道具体的返回值表达式
// 也就不能进行在外部进行 decltype 类型推导
// 在函数模板封装的时候就确定,外部用 auto 即可
// 语法:
// auto func(参数1, 参数2, ...) -> decltype(参数表达式)
template <typename T, typename U>
// 返回类型后置语法
auto add(T t, U u) -> decltype(t+u) {
return t + u;
}
int main()
{
int x = 520;
double y = 13.14;
// auto z = add<int, double>(x, y);
auto z = add(x, y); // 简化之后的写法
cout << "z: " << z << endl;
return 0;
}
// 这样即可实现,具体的 decltype 内的表达式看模板的具体逻辑
final
final 修饰函数
// 修饰虚函数,阻止子类重写
class Base {
public:
virtual void test() {
cout << "Base class...";
}
};
class Child : public Base {
public:
void test() final {
cout << "Child class...";
}
};
class GrandChild : public Child {
public:
// 语法错误, 不允许重写
void test() {
cout << "GrandChild class...";
}
};
- 上述就是存在的一种使用情况
- 因为父类是虚函数,继承下来的就都是虚函数,那么后代的后代仍可实现多态,进行重写方法
- 但是有些场景就只是需要第一个子类继承,并重写方法,而不想让子类的派生类有重写权限,此时的
final
就发挥作用
final 修饰类
// 修饰类,表示该类不允许继承
class Base{
public:
virtual void test() {
cout << "Base class...";
}
};
class Child final: public Base { // 这里使用 final
public:
void test() {
cout << "Child class...";
}
};
// error, 语法错误
class GrandChild : public Child {
public:
};
override
// overrode ---> 重写
class Base {
public:
virtual void test() {
cout << "Base class...";
}
};
class Child : public Base {
public:
void test() override {
cout << "Child class...";
}
};
class GrandChild : public Child {
public:
void test() override {
cout << "Child class...";
}
};
- 使用了
override
关键字之后,假设在重写过程中因为误操作,写错了函数名或者函数参数或者返回值编译器都会提示语法错误 - 提高了程序的正确性,降低了出错的概率
说明:参考 https://subingwen.cn/