一乐电子

一乐电子百科

 找回密码
 请使用微信账号登录和注册会员

QQ登录

只需一步,快速开始

快捷登录

手机号码,快捷登录

搜索
查看: 3533|回复: 20
收起左侧

C 中复杂的函数声明

[复制链接]
发表于 2015-6-14 07:56 | 显示全部楼层 |阅读模式
概述8 L$ c  W3 z' y. u4 O

* d( O8 }, L, H9 \2 o  在很多情况下,尤其是读别人所写代码的时候,对C语言声明的理解能力变得非常重要,而C语言本身的凝练简约也使得C语言的声明常常会令人感到非常困惑,因此,在这里我用一篇的内容来集中阐述一下这个问题。
" D$ r  U0 L! s! D2 G; L8 y
+ M9 R: d5 N! H9 S3 C  问题:声明与函数
+ P$ `6 e6 k  `! t' ^7 F+ R9 h! |
0 i! ]( I8 ^# ?$ O) {6 k  有一段程序存储在起始地址为0的一段内存上,如果我们想要调用这段程序,请问该如何去做?
5 a& o; J6 }6 W( A( ]* Y: A, O! x( S  W' D' {0 w- W3 r3 |! m
  答案
7 P& U$ I% B. ?: x1 r1 S+ _
. O1 ?* H" l( |) t( J  答案是(*(void (*)( ) )0)( )。看起来确实令人头大,那好,让我们知难而上,从两个不同的途径来详细分析这个问题。
1 H( f; I5 M# z1 u6 d+ o
( ~, J+ d6 d0 u$ v6 p) D  答案分析:从尾到头
/ [& i( y3 ^7 g+ j% c5 Z' G4 ?& T( y1 V% Q( |' O
  首先,最基本的函数声明:void function (paramList);- p0 r( E# x6 v* z
1 M- V& t/ Z5 m6 T
  最基本的函数调用:function(paramList);
4 b1 P. E# P  v- p( ~; Q+ y7 @8 _! J  s; @  V. E
  鉴于问题中的函数没有参数,函数调用可简化为 function();
# |/ `0 H. h1 h1 h: }6 r. Z3 L$ \3 F/ ~* W. L1 }
  其次,根据问题描述,可以知道0是这个函数的入口地址,也就是说,0是一个函数的指针。使用函数指针的函数声明形式是:void (*pFunction)(),相应的调用形式是: (*pFunction)(),则问题中的函数调用可以写作:(*0)( )。7 b; g7 A+ c. j5 R+ I2 K
8 b' v+ A. i; K) ^7 z4 e* W2 O
  第三,大家知道,函数指针变量不能是一个常数,因此上式中的0必须要被转化为函数指针。) ?, h& T& J- w; T2 j1 B
0 Q+ x" O. z: ~$ V) F( z- x
  我们先来研究一下,对于使用函数指针的函数:比如void (*pFunction)( ),函数指针变量的原型是什么?这个问题很简单,pFunction函数指针原型是( void (*)( ) ),即去掉变量名,清晰起见,整个加上()号。
4 B  ^" s1 {/ a8 S: |, L6 F& T+ B9 D) P
  所以将0强制转换为一个返回值为void,参数为空的函数指针如下:( void (*)( ) )。
/ X! s3 E7 L% r
1 v" F  t: j; r7 ~5 M) ]% k1 r  OK,结合2)和3)的分析,结果出来了,那就是:(*(void (*)( ) )0)( ) 。
3 g0 N8 A) B; Z9 B* R% U9 y( k8 Z
% J0 j$ O$ L( q! A  答案分析:从头到尾理解答案0 |2 F) q  S# u
5 r( k, I# Q, B1 R+ g5 g
  (void (*)( )) ,是一个返回值为void,参数为空的函数指针原型。
" j4 L8 w1 h! x& B* B1 \0 ~2 U  (void (*)( ))0,把0转变成一个返回值为void,参数为空的函数指针,指针指向的地址为0.% r1 Y5 n) C1 a
  *(void (*)( ))0,前面加上*表示整个是一个返回值为void的函数的名字9 n0 L+ Y* z3 F* k+ m4 k
  (*(void (*)( ))0)( ),这当然就是一个函数了。
  R* C& `( A& h2 ]- c) a
7 @5 O/ m, K9 T' U9 y! t  我们可以使用typedef清晰声明如下:* K' u+ |5 H% H; {+ i$ V% u

& ]* U% n: f7 t  typedef void (*pFun)( );" s9 S0 [8 S  h+ f

6 t8 @/ Q2 }% ?6 U  r+ {  这样函数变为 (*(pFun)0 )( );0 b5 \1 @' U0 y* L3 d
  Q6 k3 v- M+ N$ _! w6 B# r* F! _
  问题:三个声明的分析' n  T; n6 m  {# _& I
  z* W' B1 I  k0 C; U7 N
  对声明进行分析,最根本的方法还是类比替换法,从那些最基本的声明上进行类比,简化,从而进行理解,下面通过分析三个例子,来具体阐述如何使用这种方法。
" o- c8 q: i2 r8 V3 {* X8 Y* m0 o7 ]1 U. D) n/ x. z
#1:int* (*a[5])(int, char*);
3 j# ?9 c2 g4 O- i) Y# U5 f4 q6 m6 \0 b
  首先看到标识符名a,“[]”优先级大于“*”,a与“[5]”先结合。所以a是一个数组,这个数组有5个元素,每一个元素都是一个指针,指针指向“(int, char*)”,很明显,指向的是一个函数,这个函数参数是“int, char*”,返回值是“int*”。OK,结束了一个。:)
  a6 l: ~' c: G' a/ w2 Z+ {; t5 R# t- }6 q
#2:void (*b[10]) (void (*)());2 n6 V3 m# b! ]- `. r

8 i2 a! m$ Q! \/ O# h+ J/ ~" s   b是一个数组,这个数组有10个元素,每一个元素都是一个指针,指针指向一个函数,函数参数是“void (*)()”【注10】,返回值是“void”。完毕!
% X* K: }6 T( e6 a, J, ]0 P- b- r; R
  注意:这个参数又是一个指针,指向一个函数,函数参数为空,返回值是“void”。  y! A  l9 y8 ^+ m# w

# t0 L! o, V0 x1 E#3. doube(*)() (*pa)[9];$ d2 g3 h) k2 L. ^" I' A( o1 x  ^

: T6 e3 s+ K: _  @   pa是一个指针,指针指向一个数组,这个数组有9个元素,每一个元素都是“doube(*)()”(也即一个函数指针,指向一个函数,这个函数的参数为空,返回值是“double”)。
8 Y9 z3 ^% f; r- a- y  typedef用法小结- -1 `5 {2 a' |7 m6 M
  在C语言的情况下,与C++稍有出入。% V) g1 v" {! N9 u! {  _
  这两天在看程序的时候,发现很多地方都用到typedef,在结构体定义,还有一些数组等地方都大量的用到.但是有些地方还不是很清楚,今天下午,就想好好研究一下.上网搜了一下,有不少资料.归纳一下:
- {$ V+ u' e4 v9 H- C2 W2 V5 n/ r  来源一:Using typedef to Curb Miscreant Code- ^4 K( t5 I( O+ H
  Typedef 声明有助于创建平台无关类型,甚至能隐藏复杂和难以理解的语法。不管怎样,使用 typedef 能为代码带来意想不到的好处,通过本文你可以学习用 typedef 避免缺欠,从而使代码更健壮。0 n4 g& K$ O6 I! m6 _- Z
  typedef 声明,简称 typedef,为现有类型创建一个新的名字。比如人们常常使用 typedef 来编写更美观和可读的代码。所谓美观,意指 typedef 能隐藏笨拙的语法构造以及平台相关的数据类型,从而增强可移植性和以及未来的可维护性。本文下面将竭尽全力来揭示 typedef 强大功能以及如何避免一些常见的陷阱。
2 a7 [# V7 e! q, v# K+ h  如何创建平台无关的数据类型,隐藏笨拙且难以理解的语法?- l# H( r+ u% h- V: p# x
  使用 typedefs 为现有类型创建同义字。
" j2 r  I, p* U' x4 v4 a* T  定义易于记忆的类型名, {( d% M; a# o
  typedef 使用最多的地方是创建易于记忆的类型名,用它来归档程序员的意图。类型出现在所声明的变量名字中,位于 ''typedef'' 关键字右边。例如:  h1 u) w* i4 S! D/ i, C  ?
  typedef int size;; B" K. x# o1 {; B
  此声明定义了一个 int 的同义字,名字为 size。注意 typedef 并不创建新的类型。它仅仅为现有类型添加一个同义字。你可以在任何需要 int 的上下文中使用 size:3 D5 k3 Z; D$ p/ n6 M
  void measure(size * psz);% ^( s6 Z% e1 ]& S
  size array[4];
( d$ G0 J% |/ m  size len = file.getlength();
: p) G! `2 w! d# p* T) a  std::vector vs;
9 k; K1 }" y/ s- E! A  typedef 还可以掩饰符合类型,如指针和数组。例如,你不用象下面这样重复定义有 81 个字符元素的数组:! B5 g, t- [  c
  char line[81];
1 _+ d! y2 ?/ h2 ~) t  char text[81];& K) M+ B5 V& d; [7 P. x7 B' F+ x" z
  定义一个 typedef,每当要用到相同类型和大小的数组时,可以这样:
6 i: L% ?% w0 N7 l  typedef char Line[81];
' J; B9 Z$ w2 ~/ g8 S% t1 T  Line text, secondline;
9 T0 x/ r- K% O4 U; s& {  getline(text);. }) G& x3 K/ C" K: ^
  同样,可以象下面这样隐藏指针语法:
9 B# |8 w, D. ]+ O  typedef char * pstr;
6 i+ |4 E; T7 \  int mystrcmp(pstr, pstr);5 P) R0 c% m& L0 P4 |' x6 M
  这里将带我们到达第一个 typedef 陷阱。标准函数 strcmp()有两个‘const char *'类型的参数。因此,它可能会误导人们象下面这样声明 mystrcmp():% L9 z; ^! t; \# B
  int mystrcmp(const pstr, const pstr);
0 W5 Y0 \! E+ q; Y6 Y; N  这是错误的,按照顺序,‘const pstr'被解释为‘char * const'(一个指向 char 的常量指针),而不是‘const char *'(指向常量 char 的指针)。这个问题很容易解决:
- _1 y7 D4 Z) g7 k6 ?  typedef const char * cpstr;
/ ^- h1 \4 z5 Q0 i; v  int mystrcmp(cpstr, cpstr); // 现在是正确的7 q' l* I0 P; f
  记住:不管什么时候,只要为指针声明 typedef,那么都要在最终的 typedef 名称中加一个 const,以使得该指针本身是常量,而不是对象。: @" ?  `  ?0 [0 i7 c- `; z0 V
  代码简化2 E+ @  X, n  Q& |5 Y  c. E
  上面讨论的 typedef 行为有点像 #define 宏,用其实际类型替代同义字。不同点是 typedef 在编译时被解释,因此让编译器来应付超越预处理器能力的文本替换。例如:
  k, B! {! A3 F" d2 M+ V  typedef int (*PF) (const char *, const char *);/ q! @* y9 ^" ?
  这个声明引入了 PF 类型作为函数指针的同义字,该函数有两个 const char * 类型的参数以及一个 int 类型的返回值。如果要使用下列形式的函数声明,那么上述这个 typedef 是不可或缺的:$ q2 p: q# v+ e- X
  PF Register(PF pf);
" F9 W) i* s* U( F  Register() 的参数是一个 PF 类型的回调函数,返回某个函数的地址,其署名与先前注册的名字相同。做一次深呼吸。下面我展示一下如果不用 typedef,我们是如何实现这个声明的:
+ y0 V3 ~. t+ N5 r3 \- q0 z  int (*Register (int (*pf)(const char *, const char *)))0 W% b$ L/ d. P& t
  (const char *, const char *);) |2 A7 i0 H+ y
  很少有程序员理解它是什么意思,更不用说这种费解的代码所带来的出错风险了。显然,这里使用 typedef 不是一种特权,而是一种必需。持怀疑态度的人可能会问:"OK,有人还会写这样的代码吗?",快速浏览一下揭示 signal()函数的头文件,一个有同样接口的函数。8 F5 j- z  P5 U2 H# G5 t0 C
  typedef 和存储类关键字(storage class specifier)0 k. p8 G6 {$ K* U
  这种说法是不是有点令人惊讶,typedef 就像 auto,extern,mutable,static,和 register 一样,是一个存储类关键字。这并是说 typedef 会真正影响对象的存储特性;它只是说在语句构成上,typedef 声明看起来象 static,extern 等类型的变量声明。下面将带到第二个陷阱:! r) A* O7 |, F- {( y& H
  typedef register int FAST_COUNTER; // 错误0 t4 Z! {& \1 S! K, a
  编译通不过。问题出在你不能在声明中有多个存储类关键字。因为符号 typedef 已经占据了存储类关键字的位置,在 typedef 声明中不能用 register(或任何其它存储类关键字)。
1 g- m3 [& O' V2 r. D: C' c! x  促进跨平台开发" w5 j7 i! l7 b& d( r
  typedef 有另外一个重要的用途,那就是定义机器无关的类型,例如,你可以定义一个叫 REAL 的浮点类型,在目标机器上它可以i获得最高的精度:
5 \! w! P! Z4 N  L) F6 U2 Q7 i  typedef long double REAL;* s8 M: L6 _2 G" j8 N9 c2 @2 F4 P1 ]
  在不支持 long double 的机器上,该 typedef 看起来会是下面这样:
& r' v6 }( g, o( ]4 h6 ^  typedef double REAL;# ~( n* S. i/ }. t9 y# W
  并且,在连 double 都不支持的机器上,该 typedef 看起来会是这样:、
1 F5 K8 d: o- P" f  Z  typedef float REAL;  i. Y- i; F% U* }- v0 {0 C( E
  你不用对源代码做任何修改,便可以在每一种平台上编译这个使用 REAL 类型的应用程序。唯一要改的是 typedef 本身。在大多数情况下,甚至这个微小的变动完全都可以通过奇妙的条件编译来自动实现。不是吗? 标准库广泛地使用 typedef 来创建这样的平台无关类型:size_t,ptrdiff 和 fpos_t 就是其中的例子。此外,象 std::string 和 std::ofstream 这样的 typedef 还隐藏了长长的,难以理解的模板特化语法,例如:basic_string,allocator> 和 basic_ofstream>。9 [) [2 V, D7 B
  作者简介
& k" O5 {, ~8 V  Danny Kalev 是一名通过认证的系统分析师,专攻 C++ 和形式语言理论的软件工程师。1997 年到 2000 年期间,他是 C++ 标准委员会成员。最近他以优异成绩完成了他在普通语言学研究方面的硕士论文。业余时间他喜欢听古典音乐,阅读维多利亚时期的文学作品,研究 Hittite、Basque 和 Irish Gaelic 这样的自然语言。其它兴趣包括考古和地理。Danny 时常到一些 C++ 论坛并定期为不同的 C++ 网站和杂志撰写文章。他还在教育机构讲授程序设计语言和应用语言课程。* r+ a$ Z4 P. N4 E3 F/ M
  来源二:(http://www.ccfans.net/bbs/dispbbs.asp?boardid=30&id=4455)5 q1 i8 u6 c- Z
  C语言中typedef用法
( |8 t& _& f! x) N  1. 基本解释
, g/ S/ X* }" r  typedef为C语言的关键字,作用是为一种数据类型定义一个新名字。这里的数据类型包括内部数据类型(int,char等)和自定义的数据类型(struct等)。
8 Y4 b7 y3 c! ]/ K* `2 O/ c  在编程中使用typedef目的一般有两个,一个是给变量一个易记且意义明确的新名字,另一个是简化一些比较复杂的类型声明。
7 h* g, T/ M. G& @$ ?' m, S  至于typedef有什么微妙之处,请你接着看下面对几个问题的具体阐述。1 s4 P: c* m6 P+ d9 U& w, F" s
  2. typedef & 结构的问题
2 G$ |% n  w2 t% x1 d6 D$ j# Z  当用下面的代码定义一个结构时,编译器报了一个错误,为什么呢?莫非C语言不允许在结构中包含指向它自己的指针吗?请你先猜想一下,然后看下文说明:
& ~0 ^  d1 a0 r  typedef struct tagNode" m! R* f" i* {
  {
% m% R/ {3 N" Q0 F' @" d  char *pItem;* t3 g) [4 w- N' ^. f; T
  pNode pNext;( k; F- d9 U9 x5 I. y1 g
  } *pNode;# D  j& |2 }% K
  答案与分析:
* _2 Z1 L6 v: W  1、typedef的最简单使用
  ^2 v  z6 U' ~/ V, t# ~  |/ y) A" ]  typedef long byte_4;3 U1 T$ z2 F4 L! K* G+ D4 Y* r( K
  给已知数据类型long起个新名字,叫byte_4。+ z6 F+ M7 C  r4 u: i% R0 \
  2、 typedef与结构结合使用# K4 s1 e% e2 @+ ?& P  h! p/ Z
  typedef struct tagMyStruct2 l# L+ ^% V% F0 |3 ^
  {# S- {) ^" n% G/ k- ?& }* M& g8 Q
  int iNum;* h1 M, Z. S  V  M  N
  long lLength;2 P$ b8 H8 k2 E' F5 ~) e
  } MyStruct;% d' k: v# ~9 s+ R$ [/ I7 r6 g
  这语句实际上完成两个操作:
) e7 \+ ~. G3 U3 O# Y  1) 定义一个新的结构类型. e1 N5 X. X& `. T' a) N
  struct tagMyStruct
% t! ]+ ?" x. I% {" G3 L1 r  {
3 M' ~, x2 L5 }6 _4 o  int iNum;
2 e/ I" T. }7 K$ u8 ]! b$ p  long lLength;
, v5 o- m7 r7 g8 n) c3 [  };0 Q5 M1 {, _3 g4 [& K
  分析:tagMyStruct称为“tag”,即“标签”,实际上是一个临时名字,struct 关键字和tagMyStruct一起,构成了这个结构类型,不论是否有typedef,这个结构都存在。
! p$ _3 k+ w* y! ?1 Z3 h  n  我们可以用struct tagMyStruct varName来定义变量,但要注意,使用tagMyStruct varName来定义变量是不对的,因为struct 和tagMyStruct合在一起才能表示一个结构类型。1 G: u. Z: |# K  I
  2) typedef为这个新的结构起了一个名字,叫MyStruct。3 o4 h. n) M  J5 I/ C- b( G6 c
  typedef struct tagMyStruct MyStruct;$ a" y% K7 h8 s; X. K. ^: p6 \  M5 s
  因此,MyStruct实际上相当于struct tagMyStruct,我们可以使用MyStruct varName来定义变量。
* s& D9 P) t/ D; t9 F% `" G0 q+ }  答案与分析
6 @1 _: {* l; D- k& F8 C2 Z4 E  C语言当然允许在结构中包含指向它自己的指针,我们可以在建立链表等数据结构的实现上看到无数这样的例子,上述代码的根本问题在于typedef的应用。6 h/ C; S9 q. ^- R
  根据我们上面的阐述可以知道:新结构建立的过程中遇到了pNext域的声明,类型是pNode,要知道pNode表示的是类型的新名字,那么在类型本身还没有建立完成的时候,这个类型的新名字也还不存在,也就是说这个时候编译器根本不认识pNode。+ ?# ?) ^9 A3 t8 p# \& s9 ]# a6 \
  解决这个问题的方法有多种:
9 D8 ^3 n0 @8 G. j. ^* j- v  1)、' y$ I- I6 M" [3 c5 o8 M5 |$ _
  typedef struct tagNode
# U2 {- y  U' R' I  {  X# Y, t9 {- Z, D
  char *pItem;& U0 L/ a$ I9 V
  struct tagNode *pNext;% v: n7 D8 U6 H3 c
  } *pNode;
) B' n3 w9 e* l; ?8 D" X6 }  2)、, |, W7 h0 u: y. H
  typedef struct tagNode *pNode;8 B6 D1 m, }- q. X% D
  struct tagNode
7 z% G! c  J; w" k) @  {
5 d/ e9 @7 Q+ j. p: n  char *pItem;
8 Y/ o( h  d4 b/ Q  pNode pNext;
' x* c* \& f/ z/ w' e+ x% y+ M6 n% H  };- I6 Q1 U% W! |# E
  注意:在这个例子中,你用typedef给一个还未完全声明的类型起新名字。C语言编译器支持这种做法。- Y3 N9 O7 M$ c- W) L$ |
  3)、规范做法:
; ~$ S% ~7 h; Z. T  struct tagNode# D' G6 f  D# R$ G+ t* c/ Y
  {: g5 ~7 R* I$ R+ s  g
  char *pItem;
; K$ c$ u2 j* L/ D  struct tagNode *pNext;
; l* l' t2 ]+ d5 ~, c/ y% e  z! m  };1 }4 d8 U0 P5 s7 `6 s3 D
  typedef struct tagNode *pNode;
' o; x8 D4 U2 a. |: G# `  3. typedef & #define的问题
! g) o$ S. ]  `* m: X/ T$ c% o! C  有下面两种定义pStr数据类型的方法,两者有什么不同?哪一种更好一点?% f9 i2 f/ K3 p
  typedef char *pStr;
+ }8 o5 [8 X( |. t4 ]! |6 A- y9 }  #define pStr char *;
( Q& Q6 b. H0 N  答案与分析:& Q/ m" P: |/ x" ]5 |4 v( J; Q# y, V
  通常讲,typedef要比#define要好,特别是在有指针的场合。请看例子:3 a' Y1 z5 v) g* z0 f
  typedef char *pStr1;
- S0 ~$ U% r9 @7 b" E5 p! t  #define pStr2 char *;
- z( _; u- }- U0 r  pStr1 s1, s2;
  U, b5 w% t8 @# ^. S$ D5 n! F  pStr2 s3, s4;0 }2 |- i% i6 v+ N' c$ \) A
  在上述的变量定义中,s1、s2、s3都被定义为char *,而s4则定义成了char,不是我们所预期的指针变量,根本原因就在于#define只是简单的字符串替换而typedef则是为一个类型起新名字。; ]' e( E/ C7 B+ T
  #define用法例子:
% k7 ?: D& [7 z6 Q  #define f(x) x*x
5 p$ E+ E' b6 n$ f, l  main( )
. P* m) r: V( v6 @, n6 ^, r) T) b  {
8 l: q7 [* @6 ~7 [* D3 X5 M  int a=6,b=2,c;
5 Q4 \. r! Q% E+ }7 R$ h' k* r! e  c=f(a) / f(b);
4 [4 W9 w) |) F, Z  printf("%d \\n",c);
7 l, t* G/ ^5 w% i1 x- l  }1 Q" R& G' }, N8 d, I) O$ [
  以下程序的输出结果是: 36。
7 @# s1 z2 }' r& i, G# u- }* B  因为如此原因,在许多C语言编程规范中提到使用#define定义时,如果定义中包含表达式,必须使用括号,则上述定义应该如下定义才对:2 Y8 w5 \9 v1 O/ g
  #define f(x) (x*x), R6 x4 K" M0 g
  当然,如果你使用typedef就没有这样的问题。( a* m$ p) L$ P$ k" p2 b
  4. typedef & #define的另一例+ E, y1 R2 ^1 Z3 T7 c; S
  下面的代码中编译器会报一个错误,你知道是哪个语句错了吗?
/ Q5 v6 F5 n1 [, X9 r  typedef char * pStr;, K% P2 b6 I2 s+ b2 V: J: ^6 S
  char string[4] = "abc";
$ w. X7 f, E5 M) c* ?+ z  H$ Q  const char *p1 = string;- R4 ], F, T& m9 g3 u
  const pStr p2 = string;2 X, t, f4 q  x% n2 }" P6 |- R
  p1++;8 [3 D# t: p+ v- i
  p2++;
8 t& N4 l! a. f9 l1 I  答案与分析:
/ ?" X! F' J8 T4 r# N; ?  是p2++出错了。这个问题再一次提醒我们:typedef和#define不同,它不是简单的文本替换。上述代码中const pStr p2并不等于const char * p2。const pStr p2和const long x本质上没有区别,都是对变量进行只读限制,只不过此处变量p2的数据类型是我们自己定义的而不是系统固有类型而已。因此,const pStr p2的含义是:限定数据类型为char *的变量p2为只读,因此p2++错误。
. b; X$ Y, \& B8 X6 M2 p7 e  #define与typedef引申谈
; h; t: M$ M6 f' e+ D* f  R3 \: k  1) #define宏定义有一个特别的长处:可以使用 #ifdef ,#ifndef等来进行逻辑判断,还可以使用#undef来取消定义。
4 t+ v- g" ^! P, M: r; r8 J  2) typedef也有一个特别的长处:它符合范围规则,使用typedef定义的变量类型其作用范围限制在所定义的函数或者文件内(取决于此变量定义的位置),而宏定义则没有这种特性。& n" [/ r9 ]; z$ B3 C! Z' \
  5. typedef & 复杂的变量声明0 V; g- y# }( R1 o$ U
  在编程实践中,尤其是看别人代码的时候,常常会遇到比较复杂的变量声明,使用typedef作简化自有其价值,比如:; B, ~% V0 b+ g1 R3 @* W6 l; W* j
  下面是三个变量的声明,我想使用typdef分别给它们定义一个别名,请问该如何做?7 `; N9 }$ P% M
  >1:int *(*a[5])(int, char*);  p' p" R5 k; P0 z2 O; t( ~# k* Q: U
  >2:void (*b[10]) (void (*)());! I  ^+ w" n7 P0 C+ ]+ D3 k
  >3. doube(*)() (*pa)[9];  U5 f: X6 a, y1 m( T. i
  答案与分析:4 n/ Q4 m- N( q& L7 @- G6 c: a
  对复杂变量建立一个类型别名的方法很简单,你只要在传统的变量声明表达式里用类型名替代变量名,然后把关键字typedef加在该语句的开头就行了。6 E" o  C9 k$ U: m" K1 ~* n% B
  >1:int *(*a[5])(int, char*);
# C- K. }- R7 n0 I! I  //pFun是我们建的一个类型别名; B- p; c9 b' L, S4 ^
  typedef int *(*pFun)(int, char*);
) D6 C+ L6 o4 q1 f, W  //使用定义的新类型来声明对象,等价于int* (*a[5])(int, char*);
6 A4 i6 l" Z" T2 C, c  pFun a[5];1 ^- ]- [3 g7 M0 V6 _
  >2:void (*b[10]) (void (*)());
) ]4 ~& f! X) W$ [3 Z% G6 O  //首先为上面表达式蓝色部分声明一个新类型) M; N( V; }. U
  typedef void (*pFunParam)();7 D- d  U/ P0 b; B" u
  //整体声明一个新类型
4 o$ {+ r7 t  X- O& w  typedef void (*pFun)(pFunParam);
* q9 F1 f2 n& g8 X  //使用定义的新类型来声明对象,等价于void (*b[10]) (void (*)());0 c7 z) r# \7 r) F; e
  pFun b[10];
- b3 c0 u8 W4 O$ D( s% q  >3. doube(*)() (*pa)[9];
/ J1 A  B2 {$ m5 N  //首先为上面表达式蓝色部分声明一个新类型
6 c8 a% a; W, Q7 K( |  typedef double(*pFun)();
7 ]# C' e) N& O7 H: }! L& o# L  |- R  //整体声明一个新类型
) `) d: A$ ~# E5 ], M3 R  typedef pFun (*pFunParam)[9];1 ]' G+ b2 G1 s6 C0 F
  //使用定义的新类型来声明对象,等价于doube(*)() (*pa)[9];( e' ?5 `, o5 V" K0 W+ A7 j
  pFunParam pa;
发表于 2015-6-14 08:16 | 显示全部楼层
这个对我这种没有比较深厚的c语言功底的人要理解透比较困难,做个记号,到一定时间时,再仔细研究研究。谢谢楼主哈。
 楼主| 发表于 2015-6-14 09:48 | 显示全部楼层
fxhfxh 发表于 2015-6-14 08:16+ w6 [/ T6 f3 P1 W
这个对我这种没有比较深厚的c语言功底的人要理解透比较困难,做个记号,到一定时间时,再仔细研究研究。谢 ...
9 F; \1 x- ~, e# A3 w: y/ o
共同努力。# g* f# |" `0 H; @& w
 楼主| 发表于 2015-6-14 10:15 | 显示全部楼层
宏有好多没见过的给大家学习一下,代码从别的网抄过来的
$ f) h1 ], [# s& s2 v3 Z% [
( W" b. z8 x) @  S( |* e' C#define XMODEM_CFG(__MODE, __BUFFER)        \5 Q0 I0 \6 v, `0 N1 E8 p. B
    do {                                    \. f6 d; p0 A  R2 U3 o
        xmodem_t tCFG = {                   \
+ w3 J' y3 X/ r1 f, [            .pchBuffer = __BUFFER,          \+ o# [8 f# q# V* o
            .tMode = __MODE                 \
4 b' h+ P% O7 [2 }+ Y        };                                  \) G1 n5 Z. ?6 R+ ^, y2 S) g
        xomdem_init(&tCFG);                 \4 ]( V1 I. H$ n
    } while (0)1 ?0 {# @+ ]* R) {! Z: A
# n" f+ Q0 }- b
/*============================ TYPES =========================================*/) p6 ?9 v( r7 O2 c
//! \name xmodem receive configuration1 J8 \1 r$ H1 u% T  [% N$ B
//! @{
6 r2 T0 [0 C' v8 D0 c) y& M& y0 Ttypedef struct {5 R0 L- h/ U$ q* D( m; y
    uint8_t *pchBuffer;         //!< receive buffer
3 t. O3 v! t4 z' T) s- y, m    enum {
) l+ X5 z! n- q, ~) Z' o9 W9 r, B        XMODEM_BLOCK_SZ_256  = 0,   g" n! D: w  l6 f, s
        XMODEM_BLOCK_SZ_1K,+ q# }; P- G. Y& S  ?
    } tMode;3 H0 V3 S. T" @! @" H" j4 X5 W6 i
} xmodem_t;
 楼主| 发表于 2015-6-14 10:19 | 显示全部楼层
这个类拟
5 ?9 `" X0 G- A- |: s( i#define MACRO(arg1, arg2) do { \- _2 ?9 m/ M) M# j# o6 y- `
/* declarations */ \
, a* u& r: Q$ @4 Zstmt1; \
' p0 |  R' A7 \! y& pstmt2; \& G" {! O1 }0 W  m! N
/* ... */ \
( _: s  |3 a* p; Q" E7 n* T} while(0) /* (no trailing ; ) */
 楼主| 发表于 2015-6-14 10:30 | 显示全部楼层
#define 的使用方法7 y/ c, l/ L# Q; z
http://www.cocoachina.com/industry/20140122/7738.html
发表于 2015-6-14 11:15 | 显示全部楼层
太专业了,先收藏在看.....
 楼主| 发表于 2015-6-14 11:26 | 显示全部楼层
再来一个
! a) r6 c, g. p1 Y) H- Z. F% o/ T3 |. j; e: ~
char (*(*p[3])( int ))[5] 等等一系列' R% @9 M+ @5 o4 c* W* W: ?- I1 k

0 h7 `$ [. @' _4 S7 z+ D+ [C指针声明解读之左右法则% u, w' p! O5 ?  L# c
C语言所有复杂的指针声明,都是由各种声明嵌套构成的。如何解读复杂指针声明呢?右左法则是一个既著名又常用的方法。不过,右左法则其实并不是C标准里面的内容,它是从C标准的声明规定中归纳出来的方法。C标准的声明规则,是用来解决如何创建声明的,而右左法则是用来解决如何辩识一个声明的,两者可以说是相反的。右左法则的英文原文是这样说的:: X) r1 y; Z6 j3 n
  The right-left rule: Start reading the declaration from the innermost parentheses, go right, and then go left. When you encounter parentheses, the direction should be reversed. Once everything in the parentheses has been parsed, jump out of it. Continue till the whole declaration has been parsed.  
" P# _4 _  D% _
5 C* i. {  L6 ]( Y这段英文的翻译如下:
8 b1 o5 R! R2 f1 C1 ]7 p2 l9 ~
6 ]& e- m( M' g: B0 }  右左法则:首先从最里面的圆括号内未定义的标识符开始阅读看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。4 j. u% E, v! ~  P/ t/ a
& A0 Q* \' G( D" I
  总之对声明进行分析,最根本的方法还是按优先级和结合性来类比替换,从那些最基本的声明进行类比,简化,从而进行理解。下面分析几个例子,来具体阐述如何使用这种方法。
; L" ?# g: h  }/ ^" x* c* x: M( o
6 \) P' a8 `% S2 c, k3 f#1:int* (*a[5])(int, char*);$ p; _9 m. F2 L% I0 H( ?- e$ H, A) m

9 B3 o2 }  N7 S7 K% K  首先看到标识符名a,"[]"优先级大于"*",a与"[5]"先结合。所以a是一个数组,这个数组有5个元素,每一个元素都是一个指针,指针指向"int* (int, char*)",很明显,指向的是一个函数,这个函数参数是"int, char*",返回值是"int*"。OK,结束了一个。:)( ?9 Q" {3 q3 D+ M6 Z# P' j
, j. z( {% U- s1 s+ T
#2:void (*b[10]) (void (*)());$ I- b* Y6 d( b7 X& D* p/ ?

1 ~% p4 ?: ]( }' {. Q" a) A6 |, W" \  s  b是一个数组,这个数组有10个元素,每一个元素都是一个指针,指针指向一个函数,函数参数是"void (*)()"【注:这也是一个函数指针, 参数为空,返回为void】,返回值是"void"。完毕!
, D( \( s5 C0 N9 k! j3 s: _0 Y
9 A* p8 Z; f7 F! B+ M9 i0 e' x( Y#3:int(*)() (*c)[9];
9 e! C8 O/ p" x4 E6 z
% T' R. J7 q. `7 e  K# c" |   c是一个指针,指针指向一个数组,这个数组有9个元素,每一个元素都是"int(*)()"(也即一个函数指针,指向一个函数,这个函数的参数为空,返回值是int型)。
! ?5 E8 T4 z& z" [8 D! O' r( T' E% H8 \1 H9 ~

' n8 p* c/ r4 f/ s6 Y  D+ A" v#4:int (*(*d)[5])(int *);
, X4 g  e- i  p5 l, u4 `
" @: Y; p4 G6 \6 T  (*d)------指针;, A. C4 O. W$ Y2 e
  (*d)[5]------这个指针指向一个数组;. u8 Y  v9 i4 p
  *(*d)[5]------这个数组中每个元素都是指针类型;5 j7 B3 {3 W+ v% j" j5 d
  int (int *)------ 什么类型的指针?这个类型的。1 o; {/ k7 l2 O  l5 {
    . V, x6 V0 m% X! u& |6 P- {
    ' G, d3 F5 u1 x
#5:int (*(*e)(int *))[5];  4 F) `" O) K! U( Q
  *e-----向右遇到括号,向左遇到*,说明e是个指针,啥指针呢?- U; ]1 w: O! ~6 ^1 m
  (*e)(int *)------跳出括号向右遇到(int *),说明这个指针是个函数指针,形参为int*, 返回值为何?且听下回分解:);
' s# R0 ^! ~1 ~$ M9 @! g  *(*e)(int *)------返回值为何?向右遇到括号,再向左,喔,遇到*了,那就是返回了一个指针了。啥指针呢? 同样地,下回分解;
; l2 S1 M- d/ R2 x8 F  (*(*e)(int *))[5]-------向右遇到[],说明那是个指向数组的指针,是啥数组呢?不急,慢慢来;) m, @) Y" [$ P
  int (*(*e)(int *))[5]-------向左遇到int,喔,明白了,就是个简单的整型数组。OVER( C6 F4 R" S* R
. _0 U- M+ m1 }. j# O& X* W: U- _1 U
   
1 u+ o' N  n' e7 H  当然实际当中,当需要声明一个复杂指针时,如果把整个声明写成上面所示的形式,对程序可读性将是一个巨大损害。谁要是写出这样BT的指针声明,那就真是丢rp了,估计会被骂死!。+ z% m% c( k9 B3 [" Z6 S  R
  还是用typedef来对声明逐层分解替换下吧,增强可读性。
3 s5 U% [7 m8 `5 n% }8 W  A2 o" `9 Y9 H0 P+ A
  例如对于上面的声明:int (*(*func)(int *))[5]; 可以这样分解:+ H( F' A* ?8 p9 F1 n" z' o
  typedef int (*pArr)[5];  & _) @. X# V2 h
  typedef pArr (*func)(int *);  
% I- T, U4 t9 e1 |, `$ z  这样就容易读得多了啊!1 N  O  [" F( V" r
/ V: N/ V% b& ~+ r- }, k

) }  J) V, N7 ~8 B, B  再看看这个啥意思? typedef int (* (* (*FUNC)(int *) )[5] )(int *); ---- 晕了吧。
9 i4 Q7 Y$ [, Z8 ?7 C, v( C+ p" M' ?, t
  其实typedef int (* (* (*FUNC)(int *) )[5] )(int *);  
* ]2 i7 ]% N- m  等价与下面的:)9 Y. G! G' T- D0 a$ H% d7 L
& g  U- \+ O0 v* Z- b
  typedef int (*PF)(int *);: z6 i/ ^! z  }

; P, L; i( t& x5 S. U  typedef PF (*PARRAY)[5];
, P3 y1 A. r+ ]" m. p' {+ _* v5 m* D2 W2 Z9 E% `$ t/ b
  typedef PARRAY (*FUNC)(int *);0 \) Y6 s0 U! h& L+ {
9 O$ P( f. V9 C; ?! }8 e6 Q  Q" x$ F
  (*(void (*)())0)();------->这个呢?3 S. b7 T( P8 N7 [) R2 t
  按左右法则:( N! S5 [* T9 a( W) ]9 u5 H/ Q4 o
  (void (*)()) -----是一个返回值为void,参数为空的函数指针原型。, d7 T- `4 G* M- w- h
  (void (*)())0-----把0强转成一个返回值为void,参数为空的函数指针,指针指向的地址为0.
+ h6 H6 z/ T" i# d% j  *(void (*)())0-----前面加上*表示整个是一个返回值为void的函数的名字3 ]5 h$ z$ r' `) f. \$ u
  (*(void (*)())0)()------这当然就是一个函数调用了。
: P# K' r4 V) E" B; I% ?5 Q- G; V: D4 F' @+ T& i
  再typedef化简下:
% I5 a1 M4 {7 Q# O! ?6 h. ^7 g  typedef void (*pf)();
0 p1 K  y0 u: y. B( @. T6 b0 N( `  (*(pf)0)();
发表于 2015-6-14 14:25 来自手机 | 显示全部楼层
谢谢楼主分享,写得很好,使之易懂。来自: iPhone客户端
发表于 2015-6-17 13:53 | 显示全部楼层
太专业了,先收藏在看.....

本版积分规则

QQ|一淘宝店|手机版|商店|电子DIY套件|一乐电子 ( 粤ICP备09076165号 ) 公安备案粤公网安备 44522102000183号

GMT+8, 2024-5-10 20:08 , Processed in 0.076749 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表