C语言中变量定义/声明深入分析

就算是非常有经验的C程序员,也对那些比简单数组/指针更复杂一些的声明感到头疼。比如说,下面这个是一个指针的数组,还是一个数组的指针?

int *a[10];
下面这货到底是什么?

int (*(*vtable)[])();
当然了,这货是一个指针,指向一个数组,这个数组的每个元素是一个指针,指向一个函数,函数的返回值类型是int :)

这篇短文希望能够教会你一个非常简单地读懂复杂声明的方法。我99%肯定我在80年代读过这篇,但是不记得具体是在什么地方读到的了。我怀疑是我自己发现这个的(尽管我总会被计算机语言结构和神秘的事物搞得很兴奋)。然而我的确记得,能够写出一个程序,将任何声明转换成英语。

== 黄金法则 ==

这个法则是这样说的:
引用

从标识符开始(或者最内层的结构,如果不存在标识符的话,通常出现于函数指针),首先向右看,直到遇到 ) 括号或者结束,看到什么就说出来;然后向左看,直到遇到 ( 括号或者回到行首,看到什么就说出来。跳出一层括号,重复上述过程:右看看,说出来;左看看,说出来。直到你说出变量的类型或者返回值(针对函数指针),也就表示你把声明都读完了。

最简单的情况是这样的:

int i;
从 i 开始,你向右看,啥都没看到;然后就向左看,看到了int,说出来:i是一个int。

然后看个复杂一点的:

int *a[3];
从 a 开始:向右看,说“是一个包含3个元素的数组”;向左看,说“数组的每个元素是指针”;向右看,啥都没;向左看,说“指针指向int”。综合起来就是: a 是一个包含3个元素的数组,每个元素是一个指针,指向int。

加上一对括号让它看起来更怪异点儿:

int (*a)[3];
像在普通表达式中一样,括号改变了阅读/计算的顺序。从 a 开始:向右看,遇到括号了,往回;向左看,说“是一个指针”,遇到(括号,跳出来;向右看,[3],说“指向一个包含3个元素的数组”;向左看,int,说“数组的每个元素是int”。综合起来:a是一个指针,指向一个包含3个元素的数组,数组的每个元素是一个int。

好,再来看看这个:

extern int *foo();
赞,你说:foo是一个函数,返回一个指针,指向int。

接下来跳一步:就像我们可以定义一个指向int的指针,我们也可以定义一个指向函数的指针。在这种情况下,不需要extern了(因为不是函数的前向引用声明),而是一个变量的定义。这是一个基本的函数指针:

int (*foo)();
从foo开始:向右看,遇到括号,往回;向左看,*,说“是一个指针”,遇到左括号,跳出来;向右看,(),说“指向一个函数”;向左看,int,说“函数返回int”。综合起来:foo是一个指针,指向一个函数,函数返回int。

下面是一个数组,每个元素是一个指针,指向函数,函数返回int:

int (*Object_vtable[])();
你还需要最后一个,诡异的难以置信的声明:

int (*(*vtable)[])();

后再附些声明例子

举例说明:

例子1

int a;
a 的右边什么都没有,向左看int,说明a是一个int型变量。

例子2

char *a;
向右看,什么都没有; 向左看是*, 说明a是一个指针; 再向右看,什么都没有; 再向左看是char,说明a是一个指向char的指针。

例子3

int *a[];
向右看,[]说明a是一个数组;向左看,*说明数组的每个元素是个指针;再向右看,什么都没有; 再向左看,int,每个指针指向一个整数。综合来看,a是一个数组,数组每个元素是指向整数的指针。

例子4

int * const a;
向a右边看,什么都没有。一直向左看,先是const,说明a是不可修改的,然后是*,说明a是一个指针;然后是int,说明指针指向整数。综合来看,a是一个不可修改的指针,它指向整数。

例子5

void (*checkout)();
向右看,遇见 ) 返回,再向左看是*,说明checkout是个指针。再向右看,是(),说明这个指针指向函数; 向左看是void,说明函数返回void。总的来看, checkout是一个指向返回void的函数的指针。

例子6

void (*checkout[])();
向右看,[]说明checkout是一个数组;向左看, * 说明数组每个元素是个指针。向右看,遇见)返回;再向左看,遇见(返回。再向右看,是(),说明数组内每个指针指向函数; 向左看是void,说明每个函数返回void。
总的来看, checkout是一个数组,数组内都是指向返回void的函数的指针。

例子7

void (*(*checkout)[])();
向右看,遇见)返回; 向左看,*说明checkout是个指针,遇到(跳出一层()。向右看,[]说明checkout指向的是一个数组;向左看, *说明数组每个元素是个指针。向右看遇见),向左看遇见(,跳出一层()。向右看,()说明数组每个元素指向一个函数;再向左看,void说明每个数组元素指向的函数返回void。
总结:checkout是一个指针,指向一个数组,每个数组元素都是一个指向返回void函数的指针。
这是一个指针,指向一个数组,数组的每个元素是个指针,指向一个函数,函数的返回值是int。发现了吗?这货就是上面那个object_vtable的指针,也就是你定义的每一个对象需要的虚函数表(vtable)的指针。

这个指向vtable的指针是一个vtable的地址,例如,&Truck_vtable (就是某个Truck类的实例虚函数表的指针)。

== 总结 ==

接下来的例子总结了所有C++为了实现多态性所建造的虚函数表需要的所有情形(就像最初的C Front - C++转C翻译器)。

int *ptr_to_int;
int *func_returning_ptr_to_int();
int (*ptr_to_func_returning_int)();
int (*array_of_ptr_to_func_returning_int[])();
int (*(*ptr_to_an_array_of_ptr_to_func_returning_int)[])();
这篇文章写的非常好,很适合我这种一直忘记数组指针指针数组的人,总结下来,也就是右左环顾,括号优先。

举例说明:

例子1

int a;
a 的右边什么都没有,向左看int,说明a是一个int型变量。

例子2

char *a;
向右看,什么都没有; 向左看是*, 说明a是一个指针; 再向右看,什么都没有; 再向左看是char,说明a是一个指向char的指针。

例子3

int *a[];
向右看,[]说明a是一个数组;向左看,*说明数组的每个元素是个指针;再向右看,什么都没有; 再向左看,int,每个指针指向一个整数。综合来看,a是一个数组,数组每个元素是指向整数的指针。

例子4

int * const a;
向a右边看,什么都没有。一直向左看,先是const,说明a是不可修改的,然后是*,说明a是一个指针;然后是int,说明指针指向整数。综合来看,a是一个不可修改的指针,它指向整数。

例子5

void (*checkout)();
向右看,遇见 ) 返回,再向左看是*,说明checkout是个指针。再向右看,是(),说明这个指针指向函数; 向左看是void,说明函数返回void。总的来看, checkout是一个指向返回void的函数的指针。

例子6

void (*checkout[])();
向右看,[]说明checkout是一个数组;向左看, * 说明数组每个元素是个指针。向右看,遇见)返回;再向左看,遇见(返回。再向右看,是(),说明数组内每个指针指向函数; 向左看是void,说明每个函数返回void。
总的来看, checkout是一个数组,数组内都是指向返回void的函数的指针。

例子7

void (*(*checkout)[])();
向右看,遇见)返回; 向左看,*说明checkout是个指针,遇到(跳出一层()。向右看,[]说明checkout指向的是一个数组;向左看, *说明数组每个元素是个指针。向右看遇见),向左看遇见(,跳出一层()。向右看,()说明数组每个元素指向一个函数;再向左看,void说明每个数组元素指向的函数返回void。
总结:checkout是一个指针,指向一个数组,每个数组元素都是一个指向返回void函数的指针。

时间: 2024-10-12 06:49:53

C语言中变量定义/声明深入分析的相关文章

Go语言中常量定义方法实例分析_Golang

本文实例讲述了Go语言中常量定义方法.分享给大家供大家参考.具体分析如下: 常量的定义与变量类似,只不过使用 const 关键字. 常量可以是字符.字符串.布尔或数字类型的值. 复制代码 代码如下: package main import "fmt" const Pi = 3.14 func main() {     const World = "世界"     fmt.Println("Hello", World)     fmt.Printl

c语言-C语言中变量名称里面可以存在空格吗

问题描述 C语言中变量名称里面可以存在空格吗 解决方案 不可以.但是这里的空格不是变量之间的空格,而是变量和变量类型之间的空格. 好比 static int a; 解决方案二: 变量名 只能由 字母,数字,下划线组成 而且不能以数字开头哦~ 解决方案三: 不可以的,可以用下划线分割单词.图中的情况是类型和变量名直接,当然有空格了,那些类型其实就是用typedef或者宏定义的 解决方案四: 不可以!!!!!!!!!!!!! 解决方案五: 文件名可以用空格,变量名为什么不可以?因为编译器中空格就是分

c语言-C语言中变量类型的范围会因为CPU的位数变化而不同吗?

问题描述 C语言中变量类型的范围会因为CPU的位数变化而不同吗? C语言中变量类型(short.int.long.char.float.double)的范围会因为CPU的位数变化而不同吗?这里的位数指的是什么?和操作系统的位数是同样的东西吗? 解决方案 会的. 在c语言中int型数据的长度就是CPU的位数.

C++编程中变量的声明和定义以及预处理命令解析_C 语言

关于C++变量的声明和定义 我们已经知道,一个函数一般由两部分组成:声明部分和执行语句. 声明部分的作用是对有关的标识符(如变量?函数?结构体?共用体等)的属性进行说明.对于函数,声明和定义的区别是明显的,前边已说明,函数的声明是函数的原型,而函数的定义是函数功能的确立.对函数的声明是可以放在声明部分中的,而函数的定义显然不在函数的声明部分范围内,它是一个文件中的独立模块. 对变量而言,声明与定义的关系稍微复杂一些.在声明部分出现的变量有两种情况:一种是需要建立存储空间的(如int a;):另一

c/c++中变量的声明和定义深入解析_C 语言

不管是函数还是变量的声明 ,都是为了告诉编译器我要使用这个变量或者函数了,用于类型检查.在定义 的时候编译器是不会分配任何内存的, 比如下面的函数: 复制代码 代码如下: void func() {      int a ;      int b = 0 ;      a = 0 ;} 当函数执行到int a ;的时候,这是一个声明,编译器不会为其分配内存空间 .当执行到a = 0; 这是一个定义,编译器才会为其分配内存空间.因此声明不一定是定义,定义一定就是定义,还可以包含声明.但是下面的这种

C语言中函数的声明、定义及使用的入门教程_C 语言

对函数的"定义"和"声明"不是一回事.函数的定义是指对函数功能的确立,包括指定函数名,函数值类型.形参及其类型以及函数体等,它是一个完整的.独立的函数单位.而函数的声明的作用则是把函数的名字,函数类型以及形参的类型.个数和顺序通知编译系统,以便在调用该函数时进行对照检查(例如,函数名是否正确,实参与形参的类型和个数是否一致),它不包括函数体.--谭浩强 ,<C程序设计>(第四版),清华大学出版社,2010年6月,p182 这段论述包含了许多概念性错误,这

C语言中变量与其内存地址对应的入门知识简单讲解_C 语言

先来理解理解内存空间吧.请看下图: 如上图所示,内存只不过是一个存放数据的空间,就好像我的看电影时的电影院中的座位一样.电影院中的每个座位都要编号,而我们的内存要存放各种各样的数据,当然我们要知道我们的这些数据存放在什么位置吧.所以内存也要象座位一样进行编号了,这就是我们所说的内存编址.座位可以是遵循"一个座位对应一个号码"的原则,从"第1号"开始编号.而内存则是按一个字节接着一个字节的次序进行编址,如上图所示.每个字节都有个编号,我们称之为内存地址.好了,我说了这

从汇编看c++中变量类型的深入分析_C 语言

全局变量的生命期和可见性是整个程序的运行期间,下面就来用汇编来看一下实际情况: c++源码: 复制代码 代码如下: int i = 2;//全局变量 int main() {    int j = i;} 下面是汇编代码: 复制代码 代码如下: PUBLIC    ?i@@3HA                        ; i_DATA    SEGMENT?i@@3HA    DD    02H                    ; 全局变量i内存空间_DATA    ENDSPUB

C#语言中变量的使用和注意事项

变量 转载 c#编程篇: c#编程我不怎么熟悉,既然教材里面有,就顺着它的思路学一下. 开始就讲什么变量.内存拉,看看就可以. 一.变量声明的问题: 1.习惯了vb,大小写是一个十分棘手的问题,又不得不这样做. 2.声明的时候可以同时赋值,也可以同时声明多个变量,为了写的程序好读,约定声明与赋值分离. 3.要想使用一个变量,必须提前声明. 4.给字符串变量提供值,必须使用双引号:给数字型变量提供值,就不需要使用任何引号.asp.net的语法中有双引号中的均被解释成文本,不在双引号中的均被解释成变