【深度C++】之“typedef”

0. typedef

typedef关键字是用来定义类型别名的关键字。

使用typedef,可以让我们的代码条理清晰、易于阅读。例如:

// 可以表示二维网格的位置
typedef unsigned int ipos[2];

ipos pos = {2, 3};

除了使用typedef,还可以使用using,这是C++11新引入的方法:

using ipos = unsigned int[2];

实际就是你的大名叫起来有些拗口,领导通常喜欢叫你“小张”、“老王”。

使用typedef,需要注意如下几点:

  1. typedef使用方法
  2. typedef与复合数据类型

1. typedef使用方法

基本内置类型的typedef很简单:

typedef unsigned int idx;

idx的实际类型就是unsigned int

对于复合数据类型,理解typedef,我们首先考虑去掉typedef

unsigned int ipos[2];

这时声明了一个数组变量,名字叫做ipos。这时我们加上typedef,告诉编译器这句话不是声明变量,而是声明类型别名。

// 告诉编译器,我声明的不是变量
// 是类型别名
typedef unsigned int ipos[2];

此时编译器知道,ipos表示的就是包含2个元素类型为unsigned int的数组类型

2. typedef与复合数据类型

声明基本内置类型、自定义数据类型的类型别名比较简单:

typedef double wages;  // 用一个double存储工资wages

class MassachusettsInstituteOfTechnology;
typedef MassachusettsInstituteOfTechnology MIT;

复合类型的声明,以及使用别名的复合类型,就容易出错了。

2.1 数组

我们可以为数组定义了一个别名:

// 可以表示二维网格的位置
typedef unsigned int ipos[2];

使用ipos,若不赋初始值,则执行默认初始化,对于上例,是一个未定义的值。

ipos pos = {3, 4};
cout << pos[0] << ", " << pos[1];  // 输出3,4

也可以在声明类型别名的时候,不指定数组的长度:

// 可以表示二维网格的位置
// 也可以表示三维、四维
typedef unsigned int ipos[];

但是在使用ipos时,必须初始化,告诉编译器数组的长度,这是数组的要求:

ipos pos = {2, 3, 4};
// 此时是三维网格的坐标

注意:数组是不能赋值和拷贝的,不要使用另一个ipos初始化一个新的ipos,类型别名并不改变原类型的行为,只是别名,好比不能因为领导叫你“老王”你就必须做一些绿色的事情。所以上述定义ipos的方案并不是很好的方案,因为我们直观理解二维网格的位置的赋值应该是允许的。

可以定义数组别名的指针、数组别名的引用:

ipos pos = {3, 4};
ipos *p_ipos = &pos;

// unsigned int ipos[2] = {3, 4};
// unsigned int (*p_ipos)[2] = &ipos;

很明显使用类型别名更方便阅读。

2.2 引用

可以将引用类型声明为别名:

typedef MyClass &rmc;

MyClass my_class;
rmc mc = my_class;

此时注意,我们似乎可以定义引用的引用了。

rmc &ref_mc = mc;

请读者注意,某些编译器上述代码是可以编译通过的。C++标准虽然规定了不可以定义引用的引用,但是编译器有自己的实现。

而且上述代码,是通过一种间接的方式定义了引用的引用,从字面角度看,依旧是某种类型的引用,这个类型是程序员自己声明的类型,所以也不算是引用的引用。

2.3 指针

可以将指针类型声明为别名:

typedef char *pstring;

pstring ps = "hello world";

通常使用类型别名声明函数指针(前方高能):

typedef string name;
typedef bool (*pCompare)(const name &, const name &);

bool CompareLength(const name &s1, const name &s2) {
    return s1.size() < s2.size();
}

bool CompareAlpha(const name &s1, const name &s2) {
    return s1[0] < s2[0];
}

int main() {
    const string s1 = "Matt", s2 = "Jimmy";
    vector<pCompare> compares = {CompareLength, CompareAlpha};
    bool flag = false;
    for (const pCompare &pcom : compares) {
        flag = flag || pcom(s1, s2);
    }
    cout << (flag ? "true" : "false") << endl;

    return 0;
}

首先,将string取别名name,表示这个数据类型用来表示名字。

然后声明了一个函数指针pCompare类型,表示那些比较两个const name &,并返回bool类型的函数。

接下来定义的CompareLengthCompareAlpha就是这样的函数。

main函数中,我们定义了一个类型为vectorcompares容器,容纳所有比较操作。

遍历容器中的所有操作,将结果或||在flag上,输出flag

这样的代码,通过字面意义就明白程序在做什么。

这里有一个地方,在范围for循环中:

const pCompare &pcom : compares

仔细推敲一下pcom的类型,它是一个函数指针的常量引用

若我们不使用typedef,该如何定义呢?

bool (* const &rCom)(const name &, const name &) = &CompareAlpha;

理解上述声明,要使用机器思维:从内向外,从右向左。

从标识符rCom开始。

rCom向右是右括号,向左;看到&rCom是引用类型。什么样的引用?

②向左看到const,常量引用;向左看到*,指针的常量引用。什么样的指针?

③看到左括号,向外;看到函数相关的定义,函数指针的常量引用。什么样的函数?

④接下来观察一下函数的定义,就知道是指向什么样的函数的指针了。

因为是引用,所以必须初始化。

2.4 const限定符

使用const限定符修饰类型别名,容易出错的地方就是对指针的理解。

const + 引用,只有常量引用(实际上是指向常量的引用)一种情况。

const + 指针,有顶层const和底层const之分。

如示例:

typedef string *pname;
const pname n1 = new string("Matt");

请问上例const顶层cosnt还是底层const

答案:顶层const,即我们不能改变n1的值。

若把原类型带入,很可能得到:

const string *n1 = new string("Matt");
// 这是一个底层const

这是错误的理解

实际上,pname已经是一个独立的类型了,const修饰的目标是对给定的类型的修饰,就是表示n1不允许被改变。

但是:

const pname *p_n1 = &n1;
// 这是一个底层const

又回到了原貌。

3. 总结

typedef关键字是用来定义类型别名的关键字,使用typedef,可以让我们的代码条理清晰、易于阅读。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值