1. typedef 的基本使用' K8 c8 X V2 {4 j @( q
1.1 typedef与结构体的结合使用typedef 是 C 语言的一个关键字,用来给某个类型起个别名,也就是给C语言中已经存在的一个类型起一个新名字。大家在阅读代码的过程中,会经常见到 typedef 与结构体、联合体、枚举、函数指针声明结合使用。比如下面结构体类型的声明和使用: - % C N6 q% l( Y& Z8 T4 k# g, g! w
- struct student
4 q* g7 t3 B3 f% o1 s - {
* q4 I2 d. T7 K6 V" T: U, _ - char name[20];" {5 H1 U) C( f( J
- int age;$ X% y( L1 G5 u1 {( Y T/ W+ U
- float score;
: B* e& A' ?* M' s - };
1 H7 k9 c/ k( b* E" \$ V - struct student stu = {"wit", 20, 99};
复制代码
# } R1 R$ J# r' S* K6 j在C语言中定义一个结构体变量,我们通常的写法是: - 3 G: h' G$ S, ~- L, j+ K3 [8 {
- struct 结构体名 变量名;
复制代码
- P* h* ^( ~1 Q' j, p前面必须有一个struct关键字打前缀,编译器才会理解你要定义的对象是一个结构体变量。而在C++语言中,则不需要这么做,直接使用:结构体名 变量名就可以了
0 m, \, Z( s0 i2 i) p! m q: r- struct student 3 c* @! [# ]3 H1 Q% d
- {3 `3 Z# l% l( P) i
- char name[20];
$ b8 z* b3 m* ` - int age;* r# |1 K( V8 C2 U
- float score;
( |/ a- { k0 L+ j - };( X) ?4 ^, S4 S1 K
- int main (void)9 }$ |' A. i. M- r0 h
- {
, D9 e& O" g4 d/ V& b2 \+ g9 m - student stu = {"wit", 20, 99};) x# t7 }1 `) g
- return 0;( _5 |- @8 d$ o1 t/ R+ j$ R
- }
复制代码
* Q9 _$ @0 @& Y7 E如果我们使用typedef,就可以给student声明一个别名student_t和一个结构体指针类型student_ptr,然后就可以直接使用student_t类型去定义一个结构体变量,不用再写struct,这样会显得代码更加简洁。
! D! M- ^. _; R- #include <stdio.h>
- l3 r D' E1 N# ?% ? V; `! F - typedef struct student" j$ g, j9 J, o" W" g% C
- {" `7 I. x) c8 O
- char name[20];8 y- g$ l4 ~* n
- int age;
" |0 X0 b1 V9 o! E1 ], l; R3 v/ k - float score; I% Z- N/ v6 P, l
- }student_t, *student_ptr;
- Z, s" t$ Y3 k {) t: w1 W% _- H4 d
6 R8 b5 q2 C) f. B, P- int main (void)& L* l9 U- Q& o7 n9 L5 I
- {
# U+ g: I9 L) n - student_t stu = {"wit", 20, 99};
( @8 x! Y7 m. s* s" w - student_t *p1 = &stu;
0 x9 R3 y4 U+ b4 Y2 L H4 i2 e - student_ptr p2 = &stu;
+ r4 R: B9 [& ~. w' ` - printf ("name: %s\n", p1->name);& X Q" I9 ] n' R$ j5 n/ ~
- printf ("name: %s\n", p2->name);
) e. g( a, N3 g& S - return 0;
" |$ j& @+ U8 m2 t& D - }4 }* j4 r- D, L; P9 _7 A
- 程序运行结果:
+ Y, y) ?1 a4 K. H6 U0 E: l/ t - wit
7 S* z) \0 X3 V7 N - wit6 v- K; F2 e" D' j: E. U5 `3 d8 H1 e+ i
复制代码 1. 2 typedef 与数组的结合使用typedef除了与结构体结合使用外,还可以与数组结合使用。定义一个数组,通常我们使用int array[10];即可。我们也可以使用typedef先声明一个数组类型,然后再使用这个类型去定义一个数组。 - : X3 N5 B2 p) ?
- typedef int array_t[10]; 3 u* r! O- }3 c9 Y$ l6 X- M! V+ G
- array_t array;
+ o, H& o2 U0 ?; k6 T- S - int main (void)& g: K* u: O1 z: M# d* a% t& H
- {; y# \& g7 {* X" H7 w+ Z3 [
- array[9] = 100;: l `) o: a* K7 N8 ~' }! l
- printf ("array[9] = %d\n", array[9]);
3 e* N! V3 I) o - return 0;+ }4 W1 p8 {! Y- W/ k
- }
复制代码 6 s' c) u# V& T: d9 x
在上面的demo程序中,我们声明了一个数组类型array_t,然后再使用该类型定义一个数组array,这个array效果其实就相当于:int array[10]。 / s! f- A+ F8 l/ p o8 L7 d5 q" T
1.3 typedef 与指针的结合使用
' M. Q; F7 B6 C" K- u+ g- <font face="Tahoma"><font size="3"><font color="black">
& G3 f- P) n( e" }/ D - typedef char * PCHAR;
4 w; ?5 D7 J: U9 m+ g7 X( O, X - int main (void)1 y- e% i8 X k" w9 S4 q* C3 p, E- p
- {2 n* j5 d7 x( d+ o( E& z. B
- //char * str = "学嵌入式,到宅学部落";( h+ ~/ x- X( ]& E8 t/ c, [0 _* P
- PCHAR str = "学嵌入式,到宅学部落";
# {; X4 y7 y$ p E5 f" F3 U6 ]% S - printf ("str: %s\n", str);( q& ?# g$ X, \0 c, `3 ~* G
- return 0;
- Y+ A4 K+ ^# [$ O i- @! _3 z5 B9 D - }</font></font></font>
% E* q' w# p0 P2 L/ }
复制代码
! U# e$ V1 c; l4 W! i0 S ?在上面的demo程序中,PCHAR 的类型是 char *,我们使用PCHAR类型去定义一个变量str,其实就是一个char *类型的指针。 1.4 typedef与函数指针的结合使用定义一个函数指针,我们通常采用下面的形式: - ) L+ P: G7 T: y0 e, I/ k
- int (*func)(int a, int b);
复制代码
" I$ ]( I$ k; d6 m! \$ x我们同样可以使用typedef声明一个函数指针类型:func_t - * T2 N: ^; }5 a- z
- typedef int (*func_t)(int a, int b);" D4 v: r/ h6 b3 C* z
- func_t fp; // 定义一个函数指针变量
复制代码 $ [. z) t9 L7 I7 ?5 W4 g. [
写个简单的程序测试一下,运行OK:
/ ?; V) Q0 Y3 j4 K# g6 E: ?( I3 }- typedef int (*func_t)(int a, int b);
5 H* S$ a8 M- ?/ C - int sum (int a, int b)
/ t; a; r/ S6 D; u& J - {- T% C3 X) r$ j! x! F0 |+ I K
- return a + b;$ j D$ g& u/ Z! B9 P9 X4 s5 r
- }
$ ?3 [) g- s5 k g4 N/ V - int main (void)
0 Z* w4 q/ d- t. q - {
3 j/ l. P$ g+ B - func_t fp = sum;0 W9 i9 n% H+ p3 Q* a! g# i- p
- printf ("%d\n", fp(1,2));# \3 s% u( }- k% a2 t* ?- h
- return 0;6 ` X7 k7 H% W( s, @# S
- }
复制代码
, ?8 B J4 J1 q7 R G为了增加程序的可读性,我们经常在代码中看到下面的声明形式: - 5 t+ V8 \6 R. k! a
- typedef int (func_t)(int a, int b);
/ I, S( Y9 G+ ~6 n0 }* w - func_t *fp = sum;
复制代码 5 z- {1 R/ Y0 c! L4 y
函数都是有类型的,我们使用typedef给函数类型声明一个新名称:func_t。这样声明的好处是:即使你没有看到func_t的定义,也能够清楚地知道fp是一个函数指针,代码的可读性比上面的好。 - x/ E/ N d2 y& V/ _
1.5 typedef与枚举的结合使用$ ~2 W% T+ U; \; c% ]+ Z2 _
6 L/ s3 K' o) `- typedef enum color
- S' l( z+ K ^5 O7 h6 P. P - {
( D9 f' n* j% A6 ~/ [/ D - red,
# s9 c6 h, S3 k - white,8 U4 T5 i: Q' y( n$ C! Y: V
- black,
. F" C/ E; N' N4 \ - green,
/ }+ A$ j! H% t9 \ - color_num,
' E- F' j J, ` - } color_t;' W$ ^0 U3 i6 ]/ @ ?4 ]
0 _/ N; a9 l, ?) s, L- int main (void)' Z6 k% Y# n1 }" ?4 Z! B
- {
/ y! u2 S4 i9 B4 S$ h8 P2 o# d - enum color color1 = red;
2 e7 I" C- B- @3 m6 { - color_t color2 = red;
1 b7 T, J6 {7 h+ O( |! _; W$ D, D - color_t color_number = color_num;0 V3 P" S5 D! C) B# w2 `( d
- printf ("color1: %d\n", color1);
* L% l0 l( {2 G+ R! a& c" O H: ~ - printf ("color2: %d\n", color2);! X& @, A6 a& f- h. w6 U# L- ?4 L
- printf ("color num: %d\n", color_number);! g8 S; r* t& R
- return 0;
1 a% o- {# l) C - }
复制代码 ! h2 C2 z+ @% w. v
! u/ k4 `7 |# a' \! ~. F
枚举与typedef的结合使用方法跟结构体类似:可以使用typedef给枚举类型color声明一个新名称color_t,然后使用这个类型就可以直接定义一个枚举变量。
) }& k# A: K$ _9 }! ` 2. 使用typedef的优势不同的项目,有不同的代码风格,也有不同的代码“癖好”。看得代码多了,你会发现:有的代码喜欢用宏,有的代码喜欢使用typedef。那么,使用typedef到底有哪些好处呢?为什么很多人喜欢用它呢? 2.1 可以让代码更加清晰简洁, X" |* `6 s2 N1 T- G
4 a+ w: }- ^6 `* j- typedef struct student( y: S& g+ `! A" c1 [. S0 [! R
- {
' G2 ?+ B1 e8 U0 W% m# z' ~ - char name[20];/ ^3 w3 k) c. M1 p' [
- int age;$ n3 \5 H: j `# o
- float score;
9 q) Q/ k! A* M: R! a - }student_t, *student_ptr;
6 i( F O I* a% k$ Q - 7 }8 R5 l* o/ H' x8 o0 b9 L! H3 X
- student_t stu = {"wit", 20, 99};5 \$ Q( j% O* l' I, B0 b" K
- student_t *p1 = &stu;
. l$ o& F! x) n9 A Z, n4 F2 ]+ v - student_ptr p2 = &stu;
! F. U& w, O2 K' d" ~0 d: R
复制代码
' z* ?. }1 ?/ d. O/ P4 }如示例代码所示,使用typedef,我们可以在定义一个结构体、联合、枚举变量时,省去关键字struct,让代码更加简洁。 2.2 增加代码的可移植性C语言的int类型,我们知道,在不同的编译器和平台下,所分配的存储字长不一样:可能是2个字节,可能是4个字节,也有可能是8个字节。如果我们在代码中想定义一个固定长度的数据类型,此时使用int,在不同的平台环境下运行可能会出现问题。为了应付各种不同“脾气”的编译器,最好的办法就是使用自定义数据类型,而不是使用C语言的内置类型。
6 l/ \9 Y" h9 ~# h/ [' K5 ^- #ifdef PIC_16
4 m' y1 p% y8 ?6 a - typedef unsigned long U322 Z2 B3 F" y/ t( X' @$ H. u
- #else$ m# {9 s7 _& M2 b. ]
- typedef unsigned int U32
. y& w- @/ O* f% O - #endif
复制代码
; Z+ a; K3 g* ?% S8 H" U- d在16位的 PIC 单片机中,int一般占2个字节,long占4个字节,而在32位的ARM环境下,int和long一般都是占4个字节。如果我们在代码中想使用一个32位的固定长度的无符号类型,可以使用上面方式声明一个U32的数据类型,在代码中你可以放心大胆地使用U32。将代码移植到不同的平台时,直接修改这个声明就可以了。 在Linux内核、驱动、BSP 等跟底层架构平台密切相关的源码中,我们会经常看到这样的数据类型,如size_t、U8、U16、U32。在一些网络协议、网卡驱动等对字节宽度、大小端比较关注的地方,也会经常看到typedef使用得很频繁。 2.3 比宏定义更好用C语言的预处理指令#define用来定义一个宏,而typedef则用来声明一种类型的别名。typedef跟宏相比,不仅仅是简单的字符串替换,可以使用该类型同时定义多个同类型对象。 - - H3 X; K" C) a
- typedef char* PCHAR1;' r! e8 x8 P! D* k/ I. c
- #define PCHAR2 char *
0 A" {2 b6 u+ \8 B& L, A+ z7 S - 6 A1 i; g$ @& m ?! V( x7 O
- int main (void)! h, e) o' f$ s) q
- {! K+ |# j% |5 A- X, @1 }0 l
- PCHAR1 pch1, pch2;1 Q! y3 y1 w/ h
- PCHAR2 pch3, pch4;- U$ u5 H# P$ a( t h5 p" k; a
- printf ("sizeof pch1: %d\n", sizeof(pch1));# n0 Y( C+ U2 Q9 y. ^' p9 Z
- printf ("sizeof pch2: %d\n", sizeof(pch2));
/ m+ L. x4 C/ r - printf ("sizeof pch3: %d\n", sizeof(pch3));# m% R+ g4 `4 c X) ^% @% s
- printf ("sizeof pch4: %d\n", sizeof(pch4));
# r' F0 Y9 O0 V! V) {) Q M r - return 0;# y( S$ w2 D# p2 s. e0 \2 q1 @
- }
复制代码
2 q9 \9 r3 Q8 b1 d在上面的示例代码中,我们想定义4个指向char类型的指针变量,然而运行结果却是:
1 w8 V2 b7 f/ t9 @ f- O9 G- sizeof pch1: 43 _6 W- ~3 I8 x- w5 L
- sizeof pch2: 4
3 G8 ]3 C& m" L1 t+ | - sizeof pch3: 4
% p9 W: J7 v2 k! n, D - sizeof pch4: 1
复制代码
6 z, N, D" a% n+ `7 i j本来我们想定义4个指向char类型的指针,但是 pch4 经过预处理宏展开后,就变成成了一个字符型变量,而不是一个指针变量。而 PCHAR1 作为一种数据类型,在语法上其实就等价于相同类型的类型说明符关键字,因此可以在一行代码中同时定义多个变量。上面的代码其实就等价于: - 4 c6 O3 Z5 ^2 T5 Z) `* ^
- char *pch1, *pch2;
% @ y0 {; _1 N: v - char *pch3, pch4;, _# G7 ^' W* |
复制代码 2.4 让复杂的指针声明更加简洁一些复杂的指针声明,如:函数指针、数组指针、指针数组的声明,往往很复杂,可读性差。比如下面函数指针数组的定义: - * j1 s# R, ~2 S7 @
- int *(*array[10])(int *p, int len, char name[]);
复制代码
2 k& \& y+ s M上面的指针数组定义,很多人一瞅估计就懵逼了。我们可以使用typedef优化一下:先声明一个函数指针类型func_ptr_t,接着再定义一个数组,就会更加清晰简洁,可读性就增加了不少:
' N- d5 Q+ `" O- e0 f- typedef int *(*func_ptr_t)(int *p, int len, char name[]);
5 `) b2 j' A% ^9 a. u; D - func_ptr_t array[10];: S* A) |. u9 d6 T' ~7 N
复制代码 3. 使用typedef需要注意的地方通过上面的示例代码,我们可以看到,使用typedef可以让我们的代码更加简洁、可读性更强一些。但是typedef也有很多坑,稍微不注意就可能翻车。下面分享一些使用typedef需要注意的一些细节。 3.1 typedef在语法上等价于关键字我们使用typedef给已知的类型声明一个别名,其在语法上其实就等价于该类型的类型说明符关键字,而不是像宏一样,仅仅是简单的字符串替换。举一个例子大家就明白了,比如const和类型的混合使用:当const和常见的类型(如:int、char) 一同修饰一个变量时,const和类型的位置可以互换。但是如果类型为指针,则const和指针类型不能互换,否则其修饰的变量类型就发生了变化,如常见的指针常量和常量指针: - 0 Q6 K- Q& o i7 S4 Q
- char b = 10;
' V! u4 s7 k( ~$ d/ B; ] - char c = 20;
* E1 Z: D0 r) p: B2 _ - int main (void)
. o( _9 I( _" k- } - { / Y2 o9 W4 Y% o1 o- ]
- char const *p1 = &b; //常量指针:*p1不可变,p1可变
6 @( ^) |- g2 Y - char *const p2 = &b; //指针常量:*p2可变,p2不可变 7 f6 K/ d$ p6 j) E6 p1 {
- p1 = &c; //编译正常
6 A5 z% g1 N+ }" A/ b$ h3 F - *p1 = 20; //error: assignment of read-only location / I! D" w- k) @+ \$ p6 s5 E2 V4 U
- p2 = &c; //error: assignment of read-only variable`p2'
' f/ P% ]- B" I2 m3 K% I1 m# B - *p2 = 20; //编译正常- ]0 H& T6 y c% {( B
- return 0;
! N. `- `5 M) V& N4 Y - }
复制代码 3 ^5 |1 C- W" L
当typedef 和 const一起去修饰一个指针类型时,与宏定义的指针类型进行比较: - , ?: H1 J/ @: u2 X5 @% \4 G, G
- typedef char* PCHAR2;9 f7 U- G! Z4 W6 b. C5 [
- #define PCHAR1 char * / G/ I9 ^% B Z) I
- char b = 10;3 \0 C7 I+ N$ _# |! X
- char c = 20;: e1 l. ]5 N" A* C8 u) _
- int main (void)
$ D2 E* G$ a6 {9 @ - { 1 S6 b4 _- h+ \8 M- J- }/ b
- const PCHAR1 p1 = &b;+ S9 _6 @& o5 {% U
- const PCHAR2 p2 = &b;
$ y5 M6 j2 x; d5 e% @$ Z - p1 = &c; //编译正常
; x7 V1 I+ f6 w1 q1 A* x/ a - *p1 = 20; //error: assignment of read-only location $ t/ Z* f7 |& r5 K
- p2 = &c; //error: assignment of read-only variable`p2'
6 `" m, A# p& b/ f& R/ E - *p2 = 20; //编译正常
6 ~. Z+ h. L% Q$ [- Q - return 0;
( h3 I3 d! m$ z0 j7 r' v) U H - }
复制代码 3 r1 V9 r5 k+ g: `9 D: A2 r9 h$ t
运行程序,你会发现跟上面的示例代码遇到相同的编译错误,原因在于宏展开仅仅是简单的字符串替换:
: _5 n, T' R! e3 e% D- const PCHAR1 p1 = &b; //宏展开后是一个常量指针1 z9 m' M2 ^; l" X4 a
- const char * p1 = &b; //其中const与类型char的位置可以互换
复制代码 1 s9 c' ~1 a6 t2 s/ m6 l8 J
而在使用PCHAR2定义的变量p2中,PCHAR2作为一个类型,位置可与const互换,const修饰的是指针变量p2的值,p2的值不能改变,是一个指针常量,但是*p2的值可以改变。 - : _$ H k: g# r9 ^7 Y8 f/ @6 z- p; p
- const PCHAR2 p2 = &b; //PCHAR2此时作为一个类型,与const可互换位置9 a7 n5 T7 e7 g, Q, Y) h- L# [
- PCHAR2 const p2 = &b; //该语句等价于上条语句
" |; O- G2 d1 H( P - char * const p2 = &b; //const和PCHAR2一同修饰变量p2,const修饰的是p2!
复制代码 1 {) u' C% f. f$ o, {8 M3 D. M- s
3.2 typedef是一个存储类关键字没想到吧,typedef在语法上是一个存储类关键字!跟常见的存储类关键字(如:auto、register、static、extern)一样,在修饰一个变量时,不能同时使用一个以上的存储类关键字,否则编译会报错:
$ f6 w1 f8 P/ S! d9 `- typedef static char * PCHAR;( {9 x l' ^4 T
- //error: multiple storage classes in declaration of `PCHAR'
4 t( r; v7 Y) [0 p+ Z% m- y$ F
复制代码 3.3 typedef 的作用域跟宏的全局性相比,typedef作为一个存储类关键字,是有作用域的。使用typedef声明的类型跟普通变量一样遵循作用域规则:包括代码块作用域、文件作用域等。 - + v0 m v7 O& b2 H7 @
- typedef char CHAR;
1 G6 T$ y/ p3 W9 K0 F o - 9 M( }. a# M9 N# Z- ^
- void func (void), m. m/ A! t* O3 m
- {7 q/ p' Y0 K6 U! h @6 O `
- #define PI 3.14
, A5 X! k) E! S% r) i - typedef short CHAR;+ p% n! }) q. E' g: R
- printf("sizeof CHAR in func: %d\n",sizeof(CHAR));
1 W2 T# K; a: ]; f - }
. p3 p$ J5 N! T! X; R - ) d* P- I0 v) a% q3 {
- int main (void)
" y8 a1 E; A0 ?5 P5 z0 {9 A- v! u - { ' S4 N6 `" ^! p" J8 n( A
- printf("sizeof CHAR in main: %d\n",sizeof(CHAR));
! H$ m% {5 H+ g$ s% W - func();
+ d+ c2 B/ ? M, N4 A/ C) {5 ^ - typedef int CHAR;4 f+ N# @, W9 W# B
- printf("sizeof CHAR in main: %d\n",sizeof(CHAR));" ~4 U5 f7 ? U: n) a+ w
- printf("PI:%f\n", PI); * a, F. o; _' i6 E( \
- return 0;- l! f. v: L9 E) I4 ~- W/ \, q
- }
复制代码
* B' j' f5 R# `" W8 e1 C宏定义在预处理阶段就已经替换完毕,是全局性的,只要保证引用它的地方在定义之后就可以了。而使用typedef声明的类型则跟普通变量一样遵循作用域规则。上面代码的运行结果为:
3 b- {% p" n% F) N9 z3 S- sizeof CHAR in main: 1
0 E4 M2 W7 ^$ e8 h4 {* ~8 d. R - sizeof CHAR in func: 2+ p% Y5 z+ {9 Y0 E
- sizeof CHAR in main: 4. r. t2 M' m6 z4 h/ ~, ^" F7 K
- PI:3.140000% u- f' J( d' p( _2 _
复制代码* K4 e w- F+ _. v G
4 如何避免typedef的滥用?通过上面的学习我们可以看到:使用typedef可以让我们的代码更加简洁、可读性更好。在实际的编程中,越来越多的人也开始尝试使用typedef,甚至到了“过犹不及”的滥用地步:但凡遇到结构体、联合、枚举都要用个typedef封装一下,不用就显得你low、你菜、你的代码没水平。 其实typedef也有副作用,不一定非得处处都用它。比如上面我们封装的STUDENT类型,当你定义一个变量时:
0 q* b, {+ g i: b( }9 ^, B- STUDENT stu;( }8 L+ A: y! g
复制代码不看STUDENT的声明,你知道stu的含义吗?未必吧。而如果我们直接使用struct定义一个变量,则会更加清晰,让你一下子就知道stu是个结构体类型的变量:
: Z; }( O6 n; O. E' k& @- struct student stu;/ H$ B/ u9 R8 X
复制代码一般来讲,当遇到以下情形时,使用typedef可能会有用,否则可能会适得其反: 在阅读Linux内核源码过程中,你会发现大量使用了typedef,哪怕是简单的int、long都使用了typedef。这是因为:Linux内核源码发展到今天,已经支持了太多的平台和CPU架构,为了保证数据的跨平台性和可移植性,所以很多时候不得已使用了typedef,对一些数据指定固定长度:如U8/U16/U32等。但是内核也不是到处到滥用,什么时候该用,什么不该用,也是有一定的规则要遵循的,具体大家可以看kernel Document中的 CodingStyle 中关于typedef的使用建议。 ; ~; V1 g6 K" {- \
|