auto

基础

  • 当变量不是指针或者引用类型时,推导的结果中不会保留 constvolatile 关键字
  • 当变量是指针或者引用类型时,推导的结果中会保留 constvolatile 关键字
  • 很好理解,指针或者引用表示对内存那个位置的数操作,那么有 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 推导是在编译时进行

  1. 不能作为函数参数使用
// 错误
int func(auto a, auto b) {	
    cout << "a: " << a <<", b: " << b << endl;
}
// 这种错误就是因为只有传参的时候才知道传参的类型,才能推导,而传参是在运行时了,编译无法推导
// 因此要实现这种功能,就是函数模板的实现
  1. 不能用于类的非静态成员变量的初始化
class Test {
    // 因为只有创建了对象,才知道类型,那么编译这里代码时,无法知晓类型
    auto v1 = 0;         
    // 错误,类的静态非常量成员不允许在类内部直接初始化      
    static auto v2 = 0;  
    // 正确           
    static constexpr auto v3 = 10;      
}
  1. 不能使用 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 无法定义数组
}
  1. 无法使用 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;          // 输出这句话
}
  1. 表达式为普通变量或者普通表达式或者类表达式
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
// 类型和表达式的类型是一致的
  1. 表达式是函数调用
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
  1. 表达式是一个左值 (可以取地址),或者被括号 ( ) 包围
  • decltype 推导出的是表达式类型的引用(如果有 constvolatile 限定符不能忽略)
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/

只管努力,剩下的交给天意