1ms Faster
Home
  • Program

    • Lean in C++
  • Perfromance Engineering

    • [>>>>>]
  • Reading Note

    • [>>>>>]
  • ComputeArch

    • [_]
  • Compiler

    • [_]
  • System

    • [_]
Authoring
  • Categories
  • Tags
GitHub (opens new window)

Quincy Jet

We are.
Home
  • Program

    • Lean in C++
  • Perfromance Engineering

    • [>>>>>]
  • Reading Note

    • [>>>>>]
  • ComputeArch

    • [_]
  • Compiler

    • [_]
  • System

    • [_]
Authoring
  • Categories
  • Tags
GitHub (opens new window)
  • Lean in c++

    • Lean in C++
    • Phase_0:Glimpse

      • C++ Glimpse
      • type cast
      • typedef
        • 定义一种类型的别名
          • typedef 定义基础类型,int, long
          • typedef 定义复合类型,如指针和数组。
          • typedef定义struct类型
          • typedef 有const的情况
          • typedef 与 #define的区别
        • 简化复杂的声明
        • 跨平台类型定义
        • 注意事项:
          • typedef是一个存储类的关键字
      • class vs. typename
      • const
      • extern
      • pointer
      • static
      • volatile
      • inline
      • assert
      • void
      • __global__
  • Performance Engineering

  • Misc

  • Reading Notes

  • Wiki
  • Lean in c++
  • Phase_0:Glimpse
Quincy Jet
2021-08-05
Content

typedef

一些大程序里面, 很多时候上来先用typedef定义了很多struct, enum或者union类型, 我们先看看typedef这个关键字

# 定义一种类型的别名

# typedef 定义基础类型,int, long

typedef 使用最多的地方是创建易于记忆的类型名,例如:

typedef int size;
typedef long byte_4;  // 给已知数据类型long起个新名字,叫byte_4。 
void measure(size * psz); 
size array[4];
size len = file.getlength();
std::vector<size> vs; 
1
2
3
4
5
6

这里定义了一个 int 的同义字,名为 size, typedef 并不创建新的类型。它仅为现有类型添加一个同义字。你可以在需要 int 的地方使用 size:

# typedef 定义复合类型,如指针和数组。

例如,像下面这样重复定义有 81 个字符元素的数组:

char line[81];
char text[81];
1
2

可以使用typedef定义相同类型和大小的数组,可以这样:

typedef char Line[81]; 
Line text, secondline;
getline(text);
1
2
3

也可以用typedef定义一个指针类型:

char* pa, pb;         // 这是不对的,它只声明了一个指向字符变量的指针, 和一个字符变量;
typedef char * pstr;  // 可以这样
pstr pa, pb;
int mystrcmp(pstr, pstr);
1
2
3
4

但是这里也有一些陷阱。标准函数 strcmp()有两个‘const char *’类型的参数。因此,它可能会误导我们象下面这样声明 mystrcmp():

int mystrcmp(const pstr, const pstr); 
1

这是错误的,按照顺序,‘const pstr’被解释为‘char * const’(一个指向 char 的常量指针),而不是‘const char *’(指向常量 char 的指针)。这个问题很容易解决:

typedef const char * cpstr; 
int mystrcmp(cpstr, cpstr); // 现在是正确的
1
2

记住: 不管什么时候,只要为指针声明 typedef,那么都要在 typedef 名称中加一个 const,以使得该指针所指数据为常量量(常量指针),而指针本身为常量(指针常量)。

# typedef定义struct类型

typedef struct tagMyStruct 
{ 
    int iNum; 
    long lLength; 
} MyStruct; 
1
2
3
4
5

这语句实际上完成以下两个操作:

1)定义一个新的struct

struct tagMyStruct 
{ 
    int iNum; 
    long lLength; 
}; 
1
2
3
4
5

分析:tagMyStruct称为“tag”,即“标签”,实际上是一个临时名字,struct 关键字和tagMyStruct一起,构成了这个结构类型,不论是否有typedef,这个结构都存在。

我们可以用struct tagMyStruct varName来定义变量,但要注意,使用tagMyStruct varName来定义变量是不对的,因为struct 和tagMyStruct合在一起才能表示一个结构类型。

2)typedef为这个新的结构起了一个名字,叫MyStruct。

typedef struct tagMyStruct MyStruct; 
1

因此,MyStruct实际上相当于struct tagMyStruct,我们可以使用MyStruct varName来定义变量。

3)规范做法:

struct tagNode 
{ 
    char *pItem; 
    struct tagNode *pNext; 
}; 
typedef struct tagNode *pNode; 
1
2
3
4
5
6

除了对struct, 在一些代码中经常看到对enum, union来进行typedef, 来形成整个程序的类型系统。

# typedef 有const的情况

typedef char* PSTR;
int mystrcmp(const PSTR, const PSTR);
1
2

const PSTR实际上相当于const char吗?不是的,它实际上相当于char const。

原因在于const给予了整个指针本身以常量性,也就是形成了常量指针char* const。

简单来说,当const和typedef一起出现时,typedef不再是简单的字符串替换。

下面的代码中编译器会报一个错误

typedef char * pStr; 
char string[4] = "abc"; 
const char *p1 = string; 
const pStr p2 = string; 
p1++; 
p2++;  // error!  const pStr p2并不等于const char * p2。
1
2
3
4
5
6

上面代码中const pStr p2和const long x本质上没有区别,都是对变量进行只读限制,只不过此处变量p2的数据类型是我们自己定义的而不是系统固有类型而已。const pStr p2的真正含义是:限定数据类型为char *的变量p2为只读,因此p2++错误。

# typedef 与 #define的区别

char* pa, pb;         // 这是不对的,它只声明了一个指向字符变量的指针, 和一个字符变量;
// #define char* pstr // 如果是这样则是不对的, 因为#define只是等价替换祖父穿, 对于第二个pb则仍然为char类型
typedef char * pstr;  // 可以这样
pstr pa, pb;
int mystrcmp(pstr, pstr);
1
2
3
4
5

#define只是在预处理的时候进行简单的字符串替换, typedef是重新在整个程序中定义一个新的类型

不仅仅如此:

  1. #define宏定义有一个特别的长处:可以使用 #ifdef ,#ifndef等来进行逻辑判断,还可以使用#undef来取消定义。
  2. typedef也有一个特别的长处:它符合范围规则,使用typedef定义的变量类型其作用范围限制在所定义的函数或者文件内(取决于此变量定义的位置),而宏定义则没有这种特性。

# 简化复杂的声明

在原来的声明里逐步用别名替换一部分复杂声明,如此循环,把带变量名的部分留到最后替换,得到的就是原声明的最简化版。举例:

  1. 原声明:
int *(*a[5])(int, char*);  // 含有5个函数指针组成的数组
1

​ 变量名为a,也可以直接用一个新别名 pFun 替换a就可以了:

typedef int *(*pFun)(int, char*); 
pFun a[5]; //  原声明使用pFun表示
1
2
  1. 原声明:
void (*b[10]) (void (*)());
1

​ 变量名为b,先替换右边部分括号里的,pFunParam为别名一:

typedef void (*pFunParam)();
1

​ 再替换左边的变量b,pFunx为别名二:

 typedef void (*pFunx)(pFunParam);
1

​ 原声明的最简化版:

pFunx b[10];
1
  1. 原声明:
doube(*)() (*e)[9]; 
1

​ 变量名为e,先替换左边部分,pFuny为别名一:

typedef double(*pFuny)();
1

​ 再替换右边的变量e,pFunParamy为别名二

typedef pFuny (*pFunParamy)[9];
1

​ 原声明的最简化版:

pFunParamy e;
1
  1. 原声明:
   double(* (*pa)[9] )();      // 此蓝色部分为个人理解,未找到原文出处 

 typedef double(*pFun)();        //首先为上面表达式蓝色部分声明一个新类型 
 typedef pFun (*pFunParam)[9];  //整体声明一个新类型 
 pFunParam pa;       //使用定义的新类型来声明对象,等价于double(*(*pa)[9])(); 
1
2
3
4
5

理解复杂声明可用的“右左法则”:

从变量名看起,先往右,再往左,碰到一个圆括号就调转阅读的方向;括号内分析完就跳出括号,还是按先右后左的顺序,如此循环,直到整个声明分析完。举例:

int (*func)(int *p);
1

首先找到变量名func,外面有一对圆括号,而且左边是一个*号,*这说明func是一个指针;然后跳出这个圆括号,先看右边,又遇到圆括号,这说明 (func)是一个函数,所以func是一个指向这类函数的指针,即函数指针,这类函数具有int类型的形参,返回值类型是int。

int (*func[5])(int *);
1

func 右边是一个[]运算符,说明func是具有5个元素的数组;func的左边有一个*,说明func的元素是指针(注意这里的不是修饰func,而是修饰 func[5]的,原因是[]运算符优先级比高,func先跟[]结合)。跳出这个括号,看右边,又遇到圆括号,说明func数组的元素是函数类型的指 针,它指向的函数具有int*类型的形参,返回值类型为int。

也可以归纳为2个固定的模式:

type (*)(....) //函数指针 
type (*)[]     //数组指针
1
2

上面讨论的 typedef 行为有点像 #define 宏,用其实际类型替代同义字。不同点是 typedef 在编译时被解释,因此让编译器来应付超越预处理器能力的文本替换。例如:

typedef int (*PF) (const char *, const char *);
1

这个声明引入了 PF 类型作为函数指针的同义字,该函数有两个 const char * 类型的参数以及一个 int 类型的返回值。如果要使用下列形式的函数声明,那么上述这个 typedef 是不可或缺的:

PF Register(PF pf);
1

Register() 的参数是一个 PF 类型的回调函数,返回某个函数的地址,其署名与先前注册的名字相同。做一次深呼吸。下面我展示一下如果不用 typedef,我们是如何实现这个声明的:

int (*Register (int (*pf)(const char *, const char *))) (const char *, const char *); 
1

很少有程序员理解它是什么意思,更不用说这种费解的代码所带来的出错风险了。显然,这里使用 typedef 不是一种特权,而是一种必需。持怀疑态度的人可能会问:“OK,有人还会写这样的代码吗?”,快速浏览一下揭示 signal()函数的头文件 ,一个有同样接口的函数。

# 跨平台类型定义

定义一个叫 REAL 的浮点类型,为:

typedef long double REAL;  // 在目标平台一上,让它表示最高精度的类型
typedef double REAL; 	   // 在不支持 long double 的平台二上
typedef float REAL;  	   // 在连 double 都不支持的平台三上,可以这样定义
1
2
3

当在不同的平台时,只要修改 typedef 本身就行,不用对代码其他部分作修改。这个微小的变动可以通过条件编译来自动实现。

标准库广泛使用了这个技巧,比如size_t。

此外,因为typedef是定义了一种类型的别名,不是简单的字符串替换(#define),所以它比宏更稳健(虽然用宏有时也可以完成以上的用途)。

# 注意事项:

# typedef是一个存储类的关键字

同auto、extern、mutable、static、register等一样,他们在语法上是一个存储类的关键字, 虽然它并不真正影响对象的存储特性,如:

typedef static int INT2;  // error
typedef register int FAST_COUNTER; // error
1
2

编译将失败,会提示“指定了一个以上的存储类”。。因为符号 typedef 已经占据了存储类关键字的位置,在 typedef 声明中不能用 register(或任何其它存储类关键字)。s

Extra Reference: Using typedef to Curb Miscreant Code (opens new window) [使用 typedef 抑制劣质代码]

#C++
Last updated: 2023/03/11, 03:13:10
type cast
class vs. typename

← type cast class vs. typename→

Copyright © 2017-2023 Quincy Jet | MIT License
  • Auto
  • Light
  • Dark
  • Read