C++:表达式类型带来的困扰


📌 参考C++ primer 2.5

问题

在大型程序中,对象的类型往往十分复杂,例如,一个函数指针的类型,如下:

bool (*pf)(const string &, const int &, bool b &)

当作为一个整体放到函数中作为参数时,即冗长,不好理解,又容易出错,鉴于这种情况,我们需要一些解决办法。

类型别名 type alias

A type alias is a name that is a synonym for another type.

定义类型的别名,可以通过两种方式。

通过typedef定义

// 定义double的别名是wages
typedef double wages;
// 定义wages(double)的别名是base, 定义double *的别名是p;
// 相当于typedef wages base; typedef wages * p;
typedef wages base, *p;
wages i = 1.3;
p k = &i;
// 输出Pd, 代表pointer to double
cout << typeid(k).name() << endl;

通过using定义

The new standard introduced a second way to define a type alias, via an alias declaration.

  • An alias declaration starts with the keyword using followed by the alias name and an =.
  • The alias declaration defines the name on the left-hand side of the = as an alias for the type that appears on the right-hand side.
using SI = Sales_item;
SI si;

Pointer & const中使用的注意事项

当类型别名与复杂类型,如pointer等一起使用时,会出现一些难以理解的事情。

typedef char * pstring;  // 定义pstring为指向char的指针类型, pointer to char
const pstring cstr = 0; // cstr is a constant pointer to char 
const pstring *ps;     // ps is a pointer to a constant pointer to char

在我们的使用,如果要判断具体的类型,会倾向于将类型替换为原始类型,如:

const pstring cstr = 0;
// 可以变成, 容易误解
const char *cstr = 0; // cstr is a pointer to const char

但是这种替换,导致了错误的理解,这里cstr不是pointer to const char, 而是 const pointer to char.

typedef char * pstring;
const pstring cstr = 0;
// 能修改
*cstr = 1;
char a = 'a';
// 错误,不能修改
cstr = &a;
cout << typeid(pstring).name() << endl;
// 输出:Pc 代表pointer to char

上述代码输出

  • 这里对于 *cstr = 1,没有报错,说明并不是pointer to const char;
  • 对cstr = &a报错,说明cstr本身是const,即const pointer to char;

这里进行类型替换理解时,需要将pstring整体考虑,即全部都是类型,不能将char * 拆开来看。可以这样理解,pstring是一个指针类型,这是正确的,将pstring视为一个类似int等的简单类型,因此 const pstring就是const pointer,不是pointer to const.

auto

当类型很复杂时,判断表达式的类型有时很困难,容易出错,C++11提出auto来告诉编译器推断表达式的类型。

基本使用

// item为int
auto item = 100;

// ok, int i, int *p
auto i = 0, *p = &i;
// error, int sz, double pi
auto sz = 0, pi = 3.14;

当在一个声明中,出现多个初始化操作时,要保证所有变量的base type是一致的。

复杂类型中auto的奇异表现

对于复杂类型,比如,指针、引用等,auto的结果并不一定是基本数据类型中那么直接,而是编译器会调整。

  • 对于reference
    int a = 0, &b = a;
    // c 为int, 不是 int&
    auto c = b;
    // 输出: i
    cout << typeid(c).name() << endl;
    
  • 对于const
    • 忽略 top-level const
    • 保留 low-level const
    top-level const和low-level const是啥,可以参考这里.
    int i = 0;
    const int ci = i, &cr = ci;
    auto b = ci;
    auto c = cr;
    auto d = &i;
    auto e = &ci;
    
    cout << typeid(b).name() << endl;
    cout << typeid(c).name() << endl;
    cout << typeid(d).name() << endl;
    cout << typeid(e).name() << endl;
    
    // 输出
    i: int
    i: int
    Pi: pointer to int
    PKi: pointer to const int
    
    因为,auto忽略了top-level const,如果想要保证变量为const,需要显式说明:
    const auto f = ci;
    
  • 对于自动推导类型的reference: 此时,top-level const不再忽略了,而是保留下来。
    // g 为reference to auto - int, ci的const被保留,因此为const int &, 不是 int或int &
    auto &g = ci;
    // error, 是字面量,无法取引用
    auto &h = 42;
    // ok. j 为 const reference, 不是 reference to const int
    const auto &j = 42;
    
    这里,跟typedef一样,使用const修饰auto或者typedef定义的类型时,const得到的是const reference(技术上不存在这个概念)或者 const pointer。

decltype

基本使用

使用auto时, a variable that uses auto as its type specifier must have an initializer. 但是,并不是每个变量都要初始化,比如函数返回值,此时auto无法使用,可以使用decltype

// 不会调用f(), 而是使用返回值类型作为sum的类型
decltype(f()) sum = x;

复杂类型下的表现

  • decltype returns the type of that variable, including top-level const and references. 这一点与auto的表现不同。
    const int ci = 0, &cj = ci; 
    decltype(ci) x = 0; // x has type const int 
    decltype(cj) y = x; // y has type const int& and is bound to x 
    decltype(cj) z; // error: z is a reference and must be initialized
    
  • Generally speaking, decltype returns a reference type for expressions that yield objects that can stand on the left-hand side of the assignment. 即,decltype推断表达式的类型时,当该表达式可以作为左值时,返回的是它的引用,不是原始类型。
    int i = 42, * p = &i, &r = i; 
    // r + 0 不能作为左值
    decltype(r + 0) b; // ok: addition yields an int; b is an (uninitialized) int 
    // *p 可以作为左值,接受赋值
    decltype(*p) c; // error: c is int& and must be initialized
    
  • Another important difference between decltype and auto is that the deduction done by decltype depends on the form of its given expression. 在decltype中使用()将变量包起来,会影响类型的推断。
    // decltype of a parenthesized variable is always a reference 
    decltype((i)) d; // error: d is int& and must be initialized 
    decltype(i) e; // ok: e is an (uninitialized) int
    

    📌 Remember that decltype((variable)) (note, double parentheses) is always a reference type, but decltype(variable) is a reference type only if variable is a reference.


文章作者: alex Li
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 alex Li !
  目录