C语言 重嵌套定义 指针 以及 typedef 和引用参数(&)的探索

时间:2022-03-21 03:25:12

 

C语言 重嵌套定义 指针 以及 typedef 引用参数(&)的探索

一.怎么看懂c语言的多重嵌套定义?

我的看法是: 抓住其中的标识符,从标识符开始,从内向外,按照优先级,一层层往外看,同时心里想着,外层描述了内层,最终描述的都是标识符。

现在从简单的开始分析一下:

   int a; 标识符是a,向外是int, int 描述了a,a是一个整形变量。 同理  long a; char a;

   int *a; 标识符是a,向外是*, *描述了a, 即a是一个指针,这是内层; 外层还有一个int,这个int描述了内层的(指针a. 也是说 指针a是个整型指针。

        此时,a, *a, a+1,* a+1, *(a+1), *(a+1)+1  a[1] , &a[1], &a分别是什么意思呢?

   int *a[5] 标识符是a,外层用两个描述符,一个是*, 一个是[],显然[]的优先级高,所以先看 a[5],显然这是把a描述为一个大小为5的数组,至于数组元素是什么类型,目前还不知道,但是再往外看,发现是 *(a[5]), 这个*又把a[5]这个数组描述为 指针数组; 但是,是什么指针我们还不知道,于是再向外看,发现是 int,  这个int 又把前面的指针描述为 整型指针。  所以最后我们说: 这是一个数组元素为整形指针的大小为4的数组,数组名是a.  a[1]是一个整型指针。  而数组名a 本身也是一个地址。这里定义的是5个指针。

   int (*a)[5] 这个一看,a是个指针(括号的优先级最高)什么指针呢?或者说这个指针指向什么呢? [5]将其描述为一个数组,于是知道是一个数组指针,意思就是一个指向有四个元素的数组的指针,注意,a 只是一个指针,不是5个指针,此时 a+1又是什么意思?  同理int (*a)[5][5] 也知道是什么了吧,这可是一个指针,并不是5*5个指针。

int *a(int x); 这个是申明了一个函数。  按照上面的分析方法: 由标识符a开始, 括号优先级最高,所以第一步 先看  a(); ()a 描述成了函数。

   此时就要想,这个函数的参数和返回值是什么? 显然,参数是 int x ,把回值类型是 (int *)

   void (*a)(void) 这是一个指向函数的指针。为是说这是个指针不是个函数? 我是这么构造出来的。  首先呢是个指针,*a  什么指针呢,函数指针,所以成了这样  (*a)() 注意,()的优先级高,所以 *a 成了 (*a) ; 既然已经是个函数(指针)了,那是个什么函数(指针)呢,这就要考虑函数的属性了,参数是什么,返回值又是什么? 显然,在这里,参数是 void,返回值也是void 。 如果你愿意的话,写成这样也可以:  int *(*a)(int (*p)[4]);

void (*a[4])(int *x); 从标识符a开始。 看a[] [4]a定义为了一个数组。  什么数组呢? 看*a[4], *又把这个数组的元素定为了指针,也就是说是个指针数组,这个数组有4个元素,a[0], a[1], a[2], a[3], 每个元素都是一个指针。 至此,我们依然不知道这是些什么指针,也就问:这些指针指向什么呢?  那就再向外层看吧。 看到了(*a[4])();  最右边的()又把这个指针定义成了函数指针。    于是又有函数所必须考虑的问题 参数和返回值。  显然参数是 int *x. 返回值是:void. 当然你可能写成:  int *(*f[4])(int *(*b[4])(int (*p)[10])); 如果你喜欢的话,可以无限扩充下去。

 

 二. 指针++ 那些事儿

为什么有整形指针,有数组指针,有字符指针,有函数指针?

不都是指针?不都是放一个地址?地址不都一样??

他们的区别在于,这个指针除了放地址外,还保存了一个属性,这个属性是干什么用的呢?

就是在执行 指针++  ―― 用的。

内存都是线性的,不论几维数组,都是线性存放的。

例如:int *p;      如果p起初是0,那p++后,p变成了4. 因为一个整型变量占4个字节,也就是32位。

又如:char *p;  如果p起初是0,那p++后,p变成了1,因为一个字符型变量占1个字节,也就是8位。

如果你不告诉编译器p是个什么指针,那你执行 p++后,编译器就不知道是给p加多少,还是遇到p时是不是应该去这个地方执行程序。

看看这些啊。

int a=3;

int *p1=&a;

此时,显然p1==&a,  我们开辟了一个指针的空间,并起名为p1,并且把a的地址放入了这个空间,如果我们访问p1,则会取出p1的值,也就是a的地址; 如果我们访问 *p1,那就是访问a了。

但是,如果,访问&p1呢? 上面提到了一个指针空间,这个空间肯定和a代表的空间不一样了,所以这个空间就是一个新的地址了。所以p1==&a   p1!=&p1

 

再看这些:

   int b[6]={1,2,3,4,5,6};

int *p2=b;

   显然p2==b==&b[0];

*p2+100==101 因为*p2==1, *p2+1==1+100=101

*(p2+1)时,编译器发现p2是个整型指针,所以给p2的地址加了4个字节。这时的*(p2+4)就是a[1]了。

   那么b==&b吗?这个真相等。 那b这个活生生的字符呢?就这么不要了?没在内存中放的吗?事实是b不存在,编译器在遇到b的时候都换成了b的地址。 这个反汇编后可以发现。 用汇编写程序是不用定义变量的,你只要把你的东西放在一个指定的地址中就行了。现在起个名字都是为了人看起来方便。

 

int c[4][4]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};

  int (*p3)[4]=c;

int *p4=c[0];

定义了这些之后:

看看p3吧,p3是什么呢?由上面的分析知道,p3是一个指针,不是4个指针, 假设p4=0,如果是整型指针,那么p4++,p4==4.

向后移动了4个字节。而现在,p4不是整型指针,他是指向了一个一维数组的指针,这就是[4]的意义。相当于你告诉告诉编译器,这个p不得了啊,如果谁执行了 p++, 那我就把这个地址加上 4*4个字节。4个整型啊。

由此可知 p+1==c[1];

   但是,如果你执行了 *(p+1)+1

     这个*号把p给降级了,现在 *(p+1) 是一个指向整型变量的指针了。

你可以这样: int *q;   q=*(p+1);   可见*(p+1)依然是个指针。

如果(*(p+1))++  那就是再向后移动一个整型变量。如果*(p+1)=16

那*(p+1)+1==20==16+4 也就是 c[1][1].

三、 引用的值传递与地址传递

引用应该是C++里的吧。引用和指针相似啊。

void fun(int *a,int (&x)[6],int *(&p));

看上面这个函数声明和下面这个函数调用。

int main()

{

int b[6]={1,2,3,4,5,6};

int *p2=b;

fun(b,b,p2);

}

最后两个参数是什么意思呢?

中间那个:显然,x是个引用,是个数组引用。这样在fun中用x,和在main()中用b,是一样的,会改变b数组元素的值。

显然用int *a,效果也一样。那么到底有什么区别呢?

想想sizeof(a)和sizeof(x)的值一样吗?

事实上 sizeof(a)==4 而sizeof(x)==24, sizeof(x)==sizeof(b)

显然,sizeof(a)是求一个指针占空间,是4个字节,而sizeof(x),相当于sizeof(b),显然是算b数组占多少字节,由int b[6],知 b数组占了4*6个字节。

引用保存了被引用者的所有信息。 显然上边的b和x 是一个东西,

在编译器看来,是同一个地址和他拥有的6个元素。

   对于第三个参数,int *(&p),这里的p 和p2是一个东西。

在fun()函数中改变p,主函数中的p2也会随之改变。

   如果不用引用的话,可以用指向指针的指针,也能达到这一效果。

Fun(int **P);

调用时应该是:Fun(&p2);

此时,p里放的是p2的地址,*p就是p2里放的地址,也就是b.

**p 就是b[0]; 

这里的p,与int *(&p)里的p又有什么区别呢?在这里如果想改变主函数里p2的值又该怎么做呢?

四.神奇而强大的typedef

typedef int T1;

T1 a;           相当于int a;

于是有人说,typedef int T1, 相当于 #define T1 int

这个是有问题的。

那 typedef  int  T2[5] 呢?

typedef  int *(*T3[4])(); 这个呢?

其实不论是上面说的各种嵌套定义,还是现在在的typedef, 这些都是为了描述标识符的。

typedef  int T2[5],并不是把int 换成 T2[5].

T2 x; 相当于  int x[5].   是把标识符T2 换成了x.

typedef strut{

             int a,b;

} T4,*T5;

把struct{}  当成 上边的int,就不难理解T4 和 T5有什么作用了。

下面段代码是验证了typedef.

#include<stdio.h>

int * fun()

{

   int a;

   return &a;

}

int main()

{

   typedef int T1;

   T1 a;

   a=3;

   printf("a=%d\n",a);

   typedef   int  T2[5];

   T2 b;

   b[3]=2;

   printf("%d\n",b[3]);

   typedef  int *(*T3[4])();

   T3 c;

   c[0]=fun;

   return 0;

}

 

 

以下是代码以论证上边所说:

#include<stdio.h>

void fun(int *a,int (&b)[6],int *(&p))

{

       printf("a=%x, b=%x, %x\n",&a,&b,a);

       printf("%d, %d\n",sizeof(a),sizeof(b));

       a++;

      

       //b++;   error C2105: “++”需要左值

       p++;

}

int main()

{

       printf("%d %d\n\n",sizeof(int),sizeof(char));

       int a=3;

       int b[6]={1,2,3,4,5,6};

       int c[4][4]={1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16};

       int *p1=&a;

       int *p2=b;

       //int *p3=c; 这个编译失败了 error C2440: “初始化”: 无法从“int [4][4]”转换为“int *”

       int (*p3)[4]=c;

       int *p4=c[0];

       //int **p5=c;1>C指针探索.cpp(21): error C2440: “初始化”: 无法从“int [4][4]”转换为“int **”

       //int (*p)[4]=b; 这个编译失败了  error C2440: “初始化”: 无法从“int [4]”转换为“int (*)[4]”

       printf("b=%x &b=%x p2=%x  &p2=%x\n\n",b,&b,p2,&p2);

       printf(" *p1=%d\n *p2=%d\n p2[0]=%d\n *p2+1=%d\n *(p2+1)=%x\n\n",*p1,*p2,p2[0],*p2+100,*(p2+1));

       printf("&a=%x &p1=%x p1=%x\n\n",&a,&p1,p1);

       printf("c[0][0]=%d  c[0][1]=%d  c[1][0]=%d  c[1][1]=%d\n",c[0][0],c[0][1],c[1][0],c[1][1]);

       printf("**p3=%d  *(*p3+1)=%d  **(p3+1)=%d  *(*(p3+1)+1)=%d\n\n",**p3,*(*p3+1),**(p3+1),*(*(p3+1)+1));

       printf("c=%x  c[1]=%x  &c[1][1]=%x\n",c,c[1],&c[1][1]);

       printf("p3=%x  p3+1=%x  *(p3+1)+1=%x\n\n",p3,p3+1,*(p3+1)+1);

 

       printf("执行fun()前:p1=%x *p1=%d p2=%x *p2=%d\n",p1,*p1,p2,*p2);

       fun(b,b,p2);

       printf("执行fun()后:p1=%x *p1=%d p2=%x *p2=%d\n",p1,*p1,p2,*p2);

       return 0;

}

运行结果:

                       C语言 重嵌套定义 指针 以及 typedef 和引用参数(&)的探索