第二章 指针与声明

本章节所说的声明不同于函数声明,它指所有类型的变量表达式的声明和定义。而本章节的主要内容是指针的各种声明的解读和理解。

C语言设计哲学要求对象的声明形式与它的使用形式尽可能相似。(例如,int p[3];表示一个int型指针的数组,而用p[i]表示指针所指向的int型数据。)这种方式的优点是操作符的优先级在“声明”和“使用”时是一样的。但,由于C语言的操作符的优先级过于复杂,导致程序员需要记住特殊的规则才能理解声明。

在说明如何解读声明之前,先说下一般声明所包含的各个元素。首先是类型说明符修饰,一般由类型说明符(void、char、short、int、long、signed、unsigned、float、double、struct、enum、union)+存储类型(extern、static、register)+类型限定符(const、volatile)组成(并非所有组合都合法)。然后出现的是指针修饰符(可有可无,也可以是多个表示多维指针)。后面紧跟的是声明器(所谓的声明器是指标识符以及与它组合在一起的任何指针、函数括号、数组下标等)。如果是函数则括号里的是函数的形参类型声明。如果有初始值,则后面紧跟着 = 初始值。如果同时声明多个变量表达式,则用逗号(,)隔开。最后,声明用分号(;)结束。

2.1 声明的规则

声明只是类型的一种声明,它必须要符合一定的规则。比如说:

1.类型说明符有且只能出现一个。(不包括函数的形参类型)
  例如:int *float*p;     非法
2.函数的返回值不能是一个函数。
  例如:int pfoo()();     非法
3.函数的返回值不能是一个数组。(参见函数与数组章节)
  例如:int pfoo()[];     非法
4.数组里不能有函数。(因为数组元素的大小要相同,而不同函数的代码长短不一)
  例如:int pfoo[]();   
5.其他

2.2 声明的优先级

正如前文所述程序员必须记住一些特殊的规则才能理解声明,这些特殊的规则称为理解C语言声明的优先级规则。C语言声明的优先级规则如下:

A.声明从它的名字开始读,然后按照优先级顺序依次读取。
B.优先级从高到低依次是:
  B1 声明中被括号括起来的部分
  B2 后缀操作符:
    圆括号()表示这是一个函数
    方括号[]表示这是一个数组
  B3 前缀操作符:
    星号*表示 "指向……的指针"
C 如果const、volatile关键字的后面紧跟类型说明符(如int,long等),那么它作用于类型说明符。
  在其他情况下,const、volatile关键字作用于它紧邻的指针星号。

2.3 实例分析

  1. 普通类型
int f;               // int型变量
(int) f;             // 将f强制转换成int型
int *f;              // int型指针
int **f;             // int型二维指针
(int*)*f;            // 将f所指向的地址的值强制转换成int型指针
int *f,g;            // f是int型指针,g是int型变量
int* f,g;            // f是int型指针,g是int型变量
(int*) f,g;          // 错误定义,但是逗号表达式。(int*)f表示将f强制转换为int型指针,g是前面定义的变量,整个表达式的值是g
(int*)f;             // 将f强制转换为int型指针

1.int* ...和int *....的意思一样
2.(int*)是强制类型转换
3.(int)强制类型转换
  • 函数
int f();             // f是个函数,返回值是int型
int *f();            // f是个函数,返回值是int型指针
(int*) f();          // 将f函数的返回值强制转换成int型指针
int (*f)();          // f是个指针,指向一个函数,该函数的返回值是int型,无参数
(int) (*f)();        // 将函数指针f所指向的函数的返回值强制转换成int型
int *(*f)();         // f是个指针,指向一个函数,该函数的返回值是int型指针,无参数
(int *)(*f)();       // 将函数指针f所指向的函数的返回值强制转换成int型指针

1.将标识符用()括起来的括号内所有的语句表示对标识符的修饰
2.标识符左边的圆括号()表示强制类型转换
3.标识符右边的圆括号()表示函数,括号里表示参数
  • 数组
int f[N];            // f是int型数组
int *f[N];           // f是数组,数组中的每个元素都是int型指针
int f()[];           // f是函数,函数的返回值是int型数组。非法
int f[N]();          // f是数组,数组中的每个元素是返回值为int型的无参函数。非法
int (*f[N])();       // f是数组,数组中的每个元素是个指针,指针指向返回值为int型的无参函数。
int *(*f[N])();      // f是数组,数组中的每个元素是个指针,指针指向返回值为int型指针的无参函数。
int *(*f[N])(),p;    // p仍然是int型变量。
int (*f)(int , float);      // f是个指针,指向返回值是int型,两个参数分别是int和float型的函数
int *(*f[N])(int , float);  // f是个数组,数组中的每个元素都是指针,指针指向返回值是int型指针,参数分别是int和float型的函数

1.逗号表达式后面的表达式只继承前面的类型说明符。
2.匹配规则:
    第一步,寻找标识符及将标识符括起来的括号。
      (*f[N]),根据优先级规则,[]>*,f[N]则表示其是个数组元素为N的数组,
      *f[N]则表示数组中的每个元素都是指针。
    第二步,根据优先级规则,先看右边,再看左边。
      由(int, float)中的()知,该指针指向一个函数,该函数的两个参数分别是int和float,
      再由int *知该函数的返回值是int型指针。
3.char *const *(*nest)();   
    // nest是个指针,指针指向返回值是char *const *的无参函数。
    // 该函数的返回值是个指针,该指针指向一个只读的指向char的指针。

注:返回值类型的解析参见指针与类型限定符一章。

2.4 typedef简化声明

typedef是为一种类型引入新的名字,而不会为该类型变量分配空间,它类似于函数的声明。它和普通的声明不同的地方在于,普通的声明表示“这个名字是一个指定类型的变量”,而typedef则是宣称“这个名字是指定类型的同义词”。

Unix系统信号机制中提供了一个名为signal的信号函数,其函数声明如下:

void (*signal(int signo,  void (*func) (int)))(int);

分析如下:

首先,从左到右寻找到标识符signal,根据优先级规则,知道signal是个函数。
该函数有两个参数。
由( *signal() )知signal函数的返回值是个指针,
再由void  (*signal())(int);知该指针是个指向B函数的指针,而B函数的返回值是void型,参数是int型。

接着,再看看signal函数的参数。
它第一个参数是int型的变量,第二个参数是void (*func) (int)。
由(*func )知道它是个指针,由(*func )(int)知道这个指针指向某个函数且函数的参数是int型的变量,
由void (*func) (int)知该函数的返回值是void型。

综上,signal函数是:一个函数,该函数的返回值是一个指向返回值为void型、参数为int型的函数的函数指针。
该函数的第一个参数是int型,该函数的第二个参数是一个指向返回值为void型、参数为int型的函数的函数指针。

为了巩固前面关于声明部分的知识,现在简要的讨论下:

void  *signal(int signo,  void (*func) (int)) (int);

分析如下:

由signal(…)知道signal是个函数,再由signal(…)(int),知道该函数的返回值是个函数。
由于函数的返回值不能是函数,所以这个声明是非法的。

现在我们可以用typedef来简化声明了,关于这个函数,通常有以下两种简化声明的形式。

方式一:

typedef void(*ptr_to_func)(int);
说明:声明ptr_to_func是一个指针,该指针指向一个返回值为void型、参数为int型的函数。

ptr_to_func signal(int , ptr_to_func);
说明:声明函数signal,该函数有两个参数。
第一个参数是int型的,第二个函数是ptr_to_func型的,返回值是ptr_to_func型的。
所以,signal函数就是:一个函数,该函数的返回值是一个指向返回值为void型、参数为int型的函数的函数指针。
该函数的第一个参数是int型,该函数的第二个参数是一个指向返回值为void型,参数为int型的函数的函数指针。

方式二:

typedef void ptr_to_func(int);
说明:ptr_to_func是一个函数,该函数的参数是int型,返回值是void型。

ptr_to_func* signal(int , ptr_to_func*);
说明:声明函数signal,该函数有两个参数。
第一个参数是int型的,第二个函数是指向ptr_to_func的指针,返回值也是指向ptr_to_func的指针。
所以,signal函数就是:一个函数,该函数的返回值是一个指向返回值为void型、参数为int型的函数的函数指针。
该函数的第一个参数是int型,该函数的第二个参数是一个指向返回值为void型,参数为int型的函数的函数指针。

typedef和 #define不一样。typedef是类型声明,表示某种变量是什么类型的,我们可以用这个变量名参照其他变量声明规则重新声明新的类型变量。而#define只是宏定义,会进行宏替换,它会在任何出现该宏替换的地方进行替换。

例如:
    typedef int* AA; AA b,c; 相当于 int *b,*c;
而,
    #define AA int*; AA b,c; 则相当于int *b,c;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,423评论 3 44
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,505评论 1 51
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young阅读 3,775评论 1 10
  • 回到老家不到一年的时间,在工作上一直没有多大起色,感觉老家城市太小,似乎没有什么发展空间,于是陷入迷茫。跟我差不多...
    清怡海风阅读 270评论 0 1
  • 一直用键盘敲击下滴滴点点,来记录我们过往的历历曾经。 本以为这样的日子很是享受,只是如今没有了你的旁观。好怕这种坚...
    语子书阅读 234评论 0 0