天天看点

c语言数组名和指针的区别&&多维数组

之前就知道数组名和指针不同,因为运算符sizeof对数组名和指向数组的指针的运算结果是不同的---sizeof数组名得到的是数组空间的大小,而sizeof指向数组的指针获得的是指针自身占据空间大小。而如果是对指针进行解引用,即对象是指针指向的,那么sizeof得到的是这个对象的大小。

举例:

char  aszFoo_Name[4];
char *pcFoo_Name = aszFoo_Name;
sizeof(aszFoo_Name) = 4;
sizeof(pcFoo_Name) = 8;
sizeof(*pcFoo_Name) = 1;
           

今天恰好看到关于linux(注意可移植性)里面对于0数量成员的数组的一些解释,举例:

typedef struct tagFoo_xxx
{
    char cT1;
    char acT2[0];
}Foo_xxx;
           

acT2就是我说的这种成员数量为0的数组,sizeof(Foo_xxx)得到的大小是1,可以验证acT2这种数组是不占空间的,引用别人博客的一段话:

char a[1]里面的a和char *b的b相同吗?《 Programming Abstractions in C》(Roberts,

E. S.,机械工业出版社,2004.6)82页里面说:“arr is defined to be identical to

&arr[0]”。也就是说,char a[1]里面的a实际是一个常量,等于&a[0]。而char *b是有一个实实在在的指针变量b存在。

所以,a=b是不允许的,而b=a是允许的。

上面这种东西的用法大致如下,也是从别人的博客搞来的:

#include <stdlib.h>
#include <string.h>
struct line {
   int length;   
   char contents[0]; // C99的玩法是:char contents[]; 没有指定数组长度     
};    
int main(){
   int this_length=10;
   struct line *thisline = (struct line *)malloc (sizeof (struct line) + this_length);
   thisline->length = this_length;
   memset(thisline->contents, 'a', this_length); 	
   return 0;
}
           

上面这段代码的意思是:我想分配一个不定长的数组,于是我有一个结构体,其中有两个成员,一个是

length,代表数组的长度,一个是contents,代码数组的内容。后面代码里的this_length(长度是10)代表是我想分配的数据的长度。(这看上去是不是像一个C++的类?)这种玩法英文叫:FlexibleArray,中文翻译叫:柔性数组。

需要注意的是后面根的缓冲区不能自己释放,所以要手动释放,防止内存泄露。。。

我感觉可以结合一下理解下数组名是什么东西。

而且对数组名取地址和数组名的值是相同的,那么 数组名指向的是首个元素的地址,数组名取地址指向的是数组的地址,所以这两者的值是相同的。结合数组指针来理解一下:

char acTest[3]={1,2,3};
    char (*pacTest1)[3]=&acTest;
    char *pacTest2= acTest;
    printf("%d",acTest); 
    printf("%d",&acTest); 
    printf("%d",*acTest); 
    printf("%d",*(&acTest)); 
           

数组指针pacTest1是指向数组的一个地址,所以将数组名的取地址的值赋给它,同样char指针代表指向一个char的地址,这里用数组名赋值。而四个printf的输出结果,一二四是相同的,都是同一地址,而第三输出为1.这样就可以佐证上面的说法是正确的。

那么二维数组又是怎样的呢:

char acTest[2][3]={1,2,3,4,5,6};
        char (*pacTest1)[3]=acTest;
        char (*pacTest2)[2][3]=&acTest;
        printf("%d",acTest); 
        printf("%d",&acTest); 
        printf("%d",*acTest); 
        printf("%d",*(&acTest)); 
           

这次四个表达式的值是相同的。二维数组可分解为多个一维数组,即以一维数组为成员,如二维数组acTest可分解为拥有三个成员:acTest[0],acTest[1],acTest[2]的数组,所以数组名直接指向的是其成员:一维数组,而对其取地址得到的才是二维数组地址,这和上面的结论相同。那么四个值相同,acTest指向一维数组acTest[0],其为一维数组的地址。&acTest指向二维数组本身。*acTest即acTest[0],也就是第一个一维数组的第一个成员的地址。最后一个与第一个相同。

数组名取地址的类型是一个数组指针类型,但它不是数组指针;而数组名的类型为这个数组的成员的类型的指针的类型,它同样不是指针(好绕。。)因为指针的sizeof都是指针自身空间的大小~~~这个也可以结合数组下标访问的语法糖来理解,a[i]=*(a+i),a[i][j]=*(a+i)[j]=*(*(a+i)+j)所以a也必定是指向其成员的指针的类型。这里gdb里看一下对应的类型:

char a[2][3]={1,2,3,4,5,6};
(gdb) ptype &&a
A syntax error in expression,near '&&a'.
(gdb) ptype &a
char (*)[2][3]
(gdb) ptype a
char [2][3]
(gdb) ptype &a[0]
char (*)[3]
(gdb) ptype &a[0][0]
char *
           

&a是二维数组指针类型,a是一维数组指针类型,&a[0]是一维数组指针类型,&a[0][0]是指向基本数组元素的指针类型。还有一点,在第一行取值的时候,对数组名取两次地址,显示原型错误,这应该是代表数组名取两次地址没有意义,所以不给打印。假设有一个n维数组,a[b1][b2]…[bn],其中bi为第i维数组的长度(i=1,2,…n),那么元素a[j1][j2]…[jn]的数值为一递归函数:

f(n)=*(f(n-1)+jn)
其中f(0)=a,n>=1
           

或者另一中方式, a[j1][j2]…[jn]的位置为:

LOC(a[j1][j2]...[jn]) = LOC(a)+(b2*b3*...bn*j1+b3*b4*...bn*j2+...+jn)L
其中LOC(x)代表x的位置,L为数组基本元素的长度
           

今天又看到两种模拟二维数组的方法,也可以拿过来理解一下:

一、利用一个二级指针来实现

#include<stdio.h>
#include<malloc.h>
 
int main()
{
	//5行2列的数组
	int **p = (int **)malloc(sizeof(int *) * 5);
	for (int i = 0; i < 5; ++i)
	{
		p[i] = (int *)malloc(sizeof(int) * 2);
	}
	for (int i = 0; i < 5; ++i)
	{
		for (int j = 0; j < 2; ++j)
		{
			//输出数组每个元素地址
			printf("%p\n", &p[i][j]);
		}
	}
	for (int i = 0; i < 5; ++i)
		free(p[i]);
	free(p);
    return 0;
}
           

二级指针指向了一块五个int的空间,每个int空间里面储存了两个Int空间的地址。

当使用语法糖访问时,p[i][j]先通过i定位到第几个int空间,p[i]就是一个Int,而后j则可以在指向的空间内移动,每次移动sizeof(int)大小。

二、利用数组指针来实现

#include<stdio.h>
#include<malloc.h>
int main()
{
 
	//申请一个5行2列的整型数组
	int(*p)[2] = (int(*)[2])malloc(sizeof(int) * 5 * 2);
	for (int i = 0; i < 5; ++i)
	{
		for (int j = 0; j < 2; ++j)
		{
			//输出数组每个元素地址
			printf("%p\n", &p[i][j]);
		}
	}
	free(p);
    return 0;
           

一维数组指针是一个指向一维数组的指针,当p+i,每次移动整个数组所占空间大小,p[i]即相当于一维数组地址,也是指向第一个成员的地址,而j可以确定在数组中成员的位置

继续阅读