C语言 重嵌套定义 指针 以及 typedef 和引用参数(&)的探索
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;
}
运行结果: