C语言指针和数组的关系

本文深入探讨了数组和指针的基本概念及应用,包括一维、二维和多维数组的创建与使用,以及如何利用指针操作数组。特别强调了数组与指针之间的联系与区别,并介绍了动态内存分配的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 概念

数组是能用索引访问的同质元素连续集合,连续是指数组的元素在内存中是相邻的,中间不存在空隙,同质是指元素都是同一类型的,数组声明用的是方括号的集合,可以拥有多个维度。
数组和指针不是完全可以互换的,尽管数组名字有时候可以当做指针来用,但是数组的名字不是指针。数组的内存是连续的,数组内部是不带有长度信息的。支持变长数组的技术是用realloc函数实现的。
1)一维数组
一维数组是线性结构,用一个索引访问成员。数组索引从0开始,到声明的长度减1结束。无效的索引访问数组会造成不可预期的行为。数组名字只是引用了一块内存,对数组做sizeof操作符会得到为该数组分配的字节数,数组长度 = sizeof(arr) / sizeof(int);
2)二维数组
二维数组使用行和列来标识数组元素,这类数组需要映射为内存中的一维地址空间。C中是通过行-列顺序连续分配实现的,先将数组的第一行放进内存,接着是第二行、第三行,直到最后一行。
二维数组当做数组的数组,若只用一个下标访问数组,得到的是对应行的指针。
3)多维数组
需要多组括号来定义数组的类型和长度,元素按照行-列-阶的顺序连续分配。

2 指针表示法和数组

可以使用指针指向已有的数组,也可以从堆上分配内存然后把这块内存当做一个数组使用。数组表示法和指针表示法在某种意义下可以互换,但是他们并不完全相同。单独使用数组名字时会返回数组的地址,我们可以把地址赋给指针。

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
// p指针变量指向数组第一个元素而不是指向数组本身的指针,给p赋值就是把数组的第一个元素的地址赋给p。

注意:
1)一维数组的数组名代表第一个元素的地址,而不是整个数组的地址;
2)要是使用p = &arr,那么就是获取整个数组的地址,返回的指针也是整个数组的指针;
3)p[i] 等价于 *(p + i);
4)给指针加上一个整数会把它持有的地址增加这各整数和数据类型长度的乘积; 5)数组表示法可以理解为“指针并解引”操作。

数组和指针的差别(重要!!):
例如:

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;

上面arr[i]生成的代码和*(arr+i)生成的不一样,arr[i]表示法生成的机器码从位置arr开始,移动i个位置,取出内容;而*(arr+i)表示法生成的机器码则是从arr开始,在地址上增加i,然后取出内容。尽管结果一样,生成的机器码却不一样。
sizeof操作符对数组和同一个数组的指针操作也是不同的:
sizeof(arr)返回20,为数组分配的字节数sizeof§返回4,为指针的长度
注意:p是一个左值,左值表示赋值操作符左边的符号,左值必须能修改,但是arr这样的数组名字不是左值,它不能被修改。我们不能改变数组所持有的地址,但可以给指针赋值一个新值从而引用不同的内存段。举例如下:

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
p = p + 1;
arr = arr + 1; // 语法错误,arr不是左值所以不能被修改,只能修改它的内容
p = arr + 1;   // 正确,arr + 1表达式本身没问题	

3 一维数组创建

用malloc创建一维数组,从堆上分配内存并把地址赋给一个指针,那就可以对指针使用数组下标并把这块内存当做成一个数组。例如:

int *p = (int *)malloc(5 * sizeof(int)); // 相当于栈区申请数组int p[5];

用malloc创建的已有数组的长度可以通过realloc函数来调整。变长的数组只能在函数内部声明,如果数组需要的生命周期比函数长,那也只能用realloc。
若超出了缓冲区的大小,realloc函数会分配一块新的内存,这块内存比旧的内存大,realloc函数不一定会让已有的内存保持在原来的位置,所以必须用它的返回指针来确定调整过大小的内存块的位置。realloc会把原来的缓冲区复制在新的缓冲区中,再把旧的释放。
realloc函数也可以用来减少指针指向的内存。若比当前分配的小,那么多余的内存会还给堆,不能保证多余的内存会被清空。

将一维数组传递作为参数传递给函数实际是通过值来传递数组的地址。因为不用在传递整个数组,从而也就不用再栈上分配内存。通常意味着要传递数组长度,否则函数只有数组地址而不知道其长度。

在函数声明中声明数组的方法:数组表示法和指针表示法。
1)数组表示法

Void diaplayArray(int arr[], int size)

2)用指针表示法

Void diaplayArray(int *arr, int size)

而使用指针的一维数组,例如:int *arr[5];// 指针数组

4 指针和多维数组动态分配

二维数组的每一行都可以当做一维数组。数组按行-列顺序存储,就是将第一行按顺序存入内存,后面紧接着第二行。例如:

int arr[5] = {1, 2, 3, 4, 5};
int  (*parr)[5] = arr;// 数组指针
// parr定义为一个指向二维数组的指针,该二维数组元素类型是整数,每行有5个元素。
printf(%p”,parr);// 100  parr是数组首行地址。
printf(%p”,parr+1);// 120  注意!!!sizeof(parr[0])->20

(parr[0]+1):表达式parr[0]返回数组第一行第一个元素的地址,这个地址是整数数组的地址,给它加1实际加上的是一个整数的长度,得到的是第二个元素地址,括号外面的是解引,得到第二个元素值。

传递多维数组时,要决定在函数签名中使用数组表示法还是指针表示法。要想在函数内部使用数组表示法,必须指定数组的形态,否则编译器就无法使用下标。例如:

void doubleArr(int arr[][5], int rows);// 二维数组
void doubleArr(int (*arr)[5], int rows);// 数组指针//改为int *arr[5]错误(指针数组)

注意:两种写法都指明了数组的列数,这很重要,因为编译器需要知道每行有几个元素,若没有传递这个信息,编译器就无法计算arr[0][3]这样的表达式。第一种写法中arr[]是数组指针的一个隐式声明,第二种写法中的(*arr)表达式则是指针的一个显式声明。(重要!!!)

上面函数在对二维数组进行调用时不会对形参数组arr进行分配内存,传递的只是地址。还可以这样声明函数:

//声明
void doubleArr(int *arr, int rows, int cols);// 接受的参数是一个指针和行列数
{
	for(int I = 0; I < rows; i++) {
		for(int j = 0; j < cols; j++) {
			printf(%d”,*(arr+(i*cols)+j) );// 注意!!!
		}
	}
}
//调用
doubleArr(&arr[0][0], 2, 5); // 注意!!!

注意:在函数内我们无法使用printf(“%d”,arr[i][j]);这样的下标!!!原因是没有将指针声明为二维数组,不能使用两个下标是因为编译器不知道一维的长度。但是可以使用printf(“%d”,*(arr+(i*cols)+j));这样的数组表示法,这样可以解释为数组内部的偏移量。在函数调用的时候传递的是&arr[0][0]而不是arr,尽管用arr也能运行,但是会产生编译警告,原因是指针类型不兼容。&arr[0][0]表达式是一个整数指针,而arr则是一个整数数组的指针。

传递二维以上的数组时,除了第一维以外,需要指定其他维度的长度。

//声明一个三维数组的函数
void doubleArr(int (*arr)[2][4], int rows);//int arr[3][2][4];
//调用
doubleArr(arr, 3);

二维数组动态分配内存涉及的问题有:数组元素是否连续,数组是否规则。当使用malloc函数创建二维数组时,在内存分配上会有几种选择。由于我们可以将二维数组当做数组的数组,因而“内层”的数组没有理由一定要是连续的,如果对这种数组使用下标,数组的不连续对程序员是透明的。
例如声明如下的二维数组所分配的内存是连续的!

void arr[2][5] ={{1,2,3,4,5},{6,7,8,9,10}};

1)分配可能不连续的内存
例如1:

int **arr = (int **)malloc(rows*sizeof(int *));
for(int i = 0; i < rows; i++) {
	 arr[i] = (int *)malloc(columns*sizeof(int));//分别使用了malloc,所以内存不一定连续
}

2)分配连续的内存
例如2:

int **arr = (int **)malloc(rows*sizeof(int *));
arr[0] = (int *)malloc(rows* columns *sizeof(int));//分配连续内存,数组所需内存一次性分配
for(int i = 1; i < rows; i++) {										      
	arr[i] = arr[0] + i * columns; // arr[0]是数组第一行第一个元素的地址
}

例如3:

int *arr = (int *)malloc(rows* columns *sizeof(int));
//后面的代码用到这个数组时不能使用下标,必须手动计算索引。

注意:不能使用下标是因为我们丢失了允许编译器使用下标所需的“形态”信息。 例子2中第一个malloc分配了一个整数指针的数组,第一个元素用来存储一行的指针,第二个malloc在地址600处为所有的元素分配内存。在for循环中将第二个malloc所分配的内存的一部分赋值给数组的每个元素。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值