指针和数组

指针和数组

  • 一、数组概述
    • 1、一维数组
    • 2、二维数组
    • 3、多维数组
  • 二、指针表示法和数组
  • 三、用malloc创建一维数组
  • 四、用realloc调整数组长度
  • 五、传递一维数组
    • 1、用数组表示法
    • 2、用指针表示法
  • 六、一维指针数组
  • 七、指针和多维数组
  • 八、传递多维数组
  • 九、动态分配二维数组
    • 1、分配不连续内存
    • 1、分配不连续内存
  • 十、不规则数组和指针

导读:
本文概述数组,然后描述 数组表示法指针表示法 的异同。我们可以使用malloc类函数创建动态数组,这类函数提供了比传统数组声明更大的灵活性。我们也要了解如何用realloc函数调整数组的内存。

我们也要 了解在传递和返回数组时可能产生的问题 ,通常需要给函数传递数组长度以便函数能正确处理数组。我们还研究了如何在C中创建不规则数组。

一、数组概述

数组是C语言的内建的数据组织形式。数组为我们提供了连续存储数据或数据特征的一种手段。我们可以使用数组内部的索引方式来方便操作数据。
不同类型的数组形成不同的数组类型。

//连续的字符数组,来容纳字符串
char stringArray[30];

//连续的整数数组,容纳轻型数据
int array[30];

//连续的函数指针数组,用数组保存函数地址,以便调用
typedef void (*functionPtr)(int, char /*其他参数*/)

functionPtr array[30] = {NULL};

//容纳复杂数据的数组(数据复杂,数据存储开销大的时候,我们一般用数组存储数据的地址)
typedef struct 
{
	char infoOne;
	char infoTwo;
	char infoThree;
	//其他数据
	//........	
} OtherInfo;

typedef struct 
{
	int ID;
	char *UserName;
	char Sex;
	long PhoneNumber;
	char *Address;
	char *Email;
	OtherInfo Info;
} Item;

Item* itemArray[100];

1、一维数组

一维数组是线性结构,用一个索引访问成员。下面的代码声明了一个5个元素的整数数组:

int vector[5];

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

数组索引从0开始,到声明的长度减1结束。vector数组的索引从0开始,到4
结束。

  • C并没有强制规定边界,要靠我们自己确定边界。
  • 用无效的索引访问数组会造成不可预期的行为。
    指针和数组_第1张图片

2、二维数组

二维数组使用行和列来标识数组元素,这类数组需要映射为内存中的一维地址空间。在C中这是通过行-列顺序实现的。先将数组的第一行放进内存,接着是第二行、第三行,直到最后一行。
所以我们有时候看到这样的写法是对的:

//可以忽略行数,但是不可以忽略列数;因为列数是限制行数的。
int array[][6] 

看下图声明和内存示意图

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

指针和数组_第2张图片

3、多维数组

多维数组具有两个及两个以上维度。对于多维数组,需要多组括号来定义数组的类型和长度。下面的例子中,我们定义了一个具有3行、2列、4阶的三维数组。阶通常用来标识第三维元素。

//可以忽略第一维,但是不能忽略维度
//int arr3d[][2][4]={};

int arr3d[3][2][4]=
{
	{{1,2,3,4},{5,6,7,8}},
	{{9,10,11,12},{13,14,15,16}},
	{{17,18,19,20},{21,22,23,24}}
};

元素按照行-列-阶的顺序连续分配
指针和数组_第3张图片

二、指针表示法和数组

要学懂指针和数组;本质上还是要搞清楚他们原来最朴素的原理

  • 指针:是一个变量;存放的值很特殊:其他变量的地址;由地址可以改变变量的数据
  • 数组:数组名就是数组首地址(数组被设计的时候就为了节约内存开销,使用的是传递数组名【也就是传递数组首地址】)
int vector[5]={1,2,3,4,5}
int *pv=vector;

pv 是指向整数的指针变量,也是指向数组第一个元素不是指向数组本身的指针指向整数数组的指针和指向整数的指针不一样哦【指向数组本身的地址写法:&vector】给 pv赋值是把数组的第一个元素的地址赋给 pv。又因为数组是连续的,如果指针能够偏移就可以访问数组其他元素的内存了。
刚好 指针的运算:加法,减法,比较 就能实现 指针 移动指针类型长度的地址

下面这些写法都是输出数组首元素地址

printf("%p\n",vector);
printf("%p\n",&vector[0]);

我们可以运用数组的索引括号

int i;
(pv + i) == pv[i];
pv[i] == vector[i];

假设vector位于地址100,pv位于地址96,表4-1和图4-4说明了如何利用数组下标和指针算术运算分别从数组名字和指针得到不同的值。

地址值 表达式 数组名偏移等价 指针描述数组元素地址等价 指针描述地址等价
92 &vector[-2] vector - 2 &pv[-2] pv - 2
100 vector vector + 0 &pv[0] pv
100 &vector[0] vector + 0 &pv[0] pv
104 &vector[1] vector + 1 &pv[1] pv + 1
140 &vector[10] vector + 10 &pv[10] pv + 10

其内存是示意图:
指针和数组_第4张图片

三、用malloc创建一维数组

创建很简单;我一般使用堆区数组的时候、我一般使用 方式二,这提醒我用的是地址索引。

int*pv=(int*)malloc(5*sizeof(int));

//数组访问方式一
for(int i=0;i < 5;i++)
{
	pv[i]=i+1;
}

//数组访问方式二
for(int i=0; i < 5; i++)
{
	*(pv+i)=i+1;
}

//记得手动释放 free(pv); 并释放pv的引用:pv = NULL;

说明了本例的内存分配。
指针和数组_第5张图片

四、用realloc调整数组长度

realloc函数主要就是在现有的基础上(基础可以是0字节内存也是传ptr为NULL)扩容,可以实现动态扩容

//函数声明:
void *realloc( void *ptr, size_t new_size );

// 参数说明:
// ptr - 指向需要重新分配的内存区域的指针 (也就是就内存区的首地址)
// new_size - 数组的新大小(字节数) 

// 返回值
//成功时,返回指向新分配内存的指针。返回的指针必须用 free() 或 realloc() 归还。
//失败时,返回空指针。原指针 ptr 保持有效,并需要通过 free() 或 realloc() 归还。

注意要点:

  • 重新分配给定的内存区域。它必须是之前为 malloc() 、 calloc() 或 realloc() 所分配,并且仍未被 free 或 realloc 的调用所释放,否则会出错。
  • 若无足够内存,则不释放旧内存块,并返回空指针。
  • 若 ptr 为 NULL ,则行为与调用 malloc(new_size) 相同。
  • 如果realloc分配成功,我们不需要释放旧的buffer,因为realloc会把原来的缓冲区复制到新的缓冲区中,再把旧的释放。 如果试图释放buffer,十有八九程序会终止,因为我们试图重复释放同一块内存。

重新分配按以下二者之一执行:

  • a) 可能的话,扩张或收缩 ptr 所指向的已存在内存。内容在新旧大小中的较小者范围内保持不变。若扩张范围,则数组新增部分的内容是未定义的。

  • b) 分配一个大小为 new_size 字节的新内存块,并复制大小等于新旧大小中较小者的内存区域,然后释放旧内存块。

五、传递一维数组

因为数组是确定数量的数据块,不管是数组表示法还是指针表示法都是得传递数组的个数,当然我们也可以学习字符串的那种方法,约定数组结尾是特殊值也能不需要传数组长度

1、用数组表示法

直接传入的数组名,本质上还是数组首地址

//传入数组名、可以指针数组长度, arr[5]也可以不用:arr[]
void displayArray (int arr [] , int size) 
{
	for(inti=0;i<size;i++)
	{
		printf("%d\n", arr[i]);
	}
}

int vector[5]={1,2,3,4,5};
displayArray(vector,5);

2、用指针表示法

声明函数的数组参数不一定要用方括号表示法,也可以用指针表示法,如下所示:

void displayArray(int*arr,int size)
{
	for (int i = 0; i < size; i++) 
	{
		printf("%d\n",arr[i]);
	}
}

在函数内部我们仍然使用数组表示法,如果有需要,也可以用指针表示法:

void displayArray(int*arr,int size)
{
	for(inti=0;i<size;i++)
	{
		printf("%d\n",*(arr+i));
	}
}

如果在声明函数时用了数组表示法,在函数体内还是可以用指针表示法:

void displayArray(int arr[], int size)
{
	for(inti=0;i<size;i++)
	{
		printf("%d\n",*(arr+i));
	}
}

总结
1、数组和指针本质上是有区别的
2、数组和指针都可以通过地址访问;两种可以互相访问
3、数组的 [ ] 符号可以看成相对于首地址位置偏移的元素接引

  • arr[1] = *(arr + 1)

六、一维指针数组

整数数组 :数组元素是一个个整数的集合

int array[5] = {0, 1, 2, 3, 4};

指针数组:数组元素是一个个指针的集合

int array[5] = {0, 1, 2, 3, 4};
int *ptrVector[5] =  {array, array+1, array+2, array+3, array+4}

完整实例:

#include 

int main()
{
	int array[5] = {0, 1, 2, 3, 4};
	int *ptrVector[5] = {array, array+1, array+2, array+3, array+4};
	
	//遍历整数数组
	printf("遍历整数数组: \n");
	for(int i=0; i < 5; i++ )
	{
		printf("%d ", *(array+i));
	}
	
	//遍历指针数组
	printf("\n遍历指针数组:\n");	
	for(int i=0; i < 5; i++ )
	{
		printf("%d ", *(*ptrVector+i));
	}
   return 0;
}

//运行结果:
遍历整数数组: 
0 1 2 3 4 
遍历指针数组:
0 1 2 3 4 

部分变量表

变量名 地址值 内容值
ptrVector 0x200 0x100
*ptrVector - 0x100
  • (*ptrVector) | - | 0

内存示意图:
指针和数组_第6张图片
注意这里可以使用 第五条的表示方法

*(ptrVector[i])  == 0
ptrVector[0][0] == 0

完整实例:

#include 

int main()
{
	int array[5] = {0, 1, 2, 3, 4};
	int *ptrVector[5] =  {array, array+1, array+2, array+3, array+4};
	
	//遍历整数数组
	printf("遍历整数数组: \n");
	for(int i=0; i < 5; i++ )
	{
		printf("%d ", *(array+i));
	}
	
	//遍历指针数组
	printf("\n遍历指针数组:\n");
	for(int i=0; i < 5; i++ )
	{
		// printf("%d ", *(*ptrVector + i));
		// printf("%d ", *(ptrVector[i]));
		printf("%d ", ptrVector[i][0]);
		//这第二个索引一维数组时必须是0
	}
   return 0;
}
//输出结果:
遍历整数数组: 
0 1 2 3 4 
遍历指针数组:
0 1 2 3 4 

七、指针和多维数组

1、数组是按行-列顺序存储,也就是说,将第一行按顺序存入内存,后面紧接着第二行。多维数组也是如此扩展的:如三维数组按照 行-列-秩 扩展。行可以忽略(也即是第一维可忽略值):

int array[] = {1,2,3};
int array_two[][2] = {{1,2}, {12,321}};
int array_three[][2][3] = { };

2、可以将多维数组的一部分看做子数组。 比如说,二维数组的每一行都可以当做一维数组。我们使用这样的一层层的解释数组,就可以把高维数组看成比它低一维的数组的一维数组。
我们就可以理解这种写法:

  • array[0] : 二维数组中第一个节点数组
  • array[0][1]: 二维数组中第一个节点数组的第二个子元素
int array[3][2]= {{1, 2}, {3, 4}, {5, 6}};
int number = array[0][1];
//这里的 number == 2

我们可以声明一个指针处理二维数组,如下所示:

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

//这是数组指针
int(*pmatrix)[2]=matrix;

//这是指针数组
 int * ptrmatrix[2]={matrix, matrix + sizeof(int) * 5};

*(pmatrix)表达式声明了一个数组指针,上面的整条声明语句将pmatrix定义为
一个指向二维数组的指针,该二维数组的元素类型是整数,每列有2个元素。如果
我们把括号去掉就声明了2个元素的数组,数组元素的类型是整数指针。

如果要用指针表示法访问第二个元素(就是2),下面的代码看似合理:

//指针数组
printf("addr: %p , value: %d\n", matrix, **matrix);
printf("addr: %p , value: %d\n", matrix+1, *(*matrix+1));

//数组指针
printf("addr: %p , value: %d\n", ptrmatrix, **ptrmatrix);
printf("addr: %p , value: %d\n", ptrmatrix+1, *(*ptrmatrix+1));

完整代码

#include 

int main()
{
   int matrix[2][5] = {{1,2,3,4,5},{6,7,8,9,10}};

	//这是数组指针
	int(*pmatrix)[2]=matrix;

	//这是指针数组
	int * ptrmatrix[2]={matrix, matrix + sizeof(int) * 4};
	 
	//指针数组
	printf("addr: %p , value: %d\n", pmatrix, **pmatrix);
	printf("addr: %p , value: %d\n", pmatrix+1, *(*pmatrix+1));

	//数组指针
	printf("addr: %p , value: %d\n", ptrmatrix, **ptrmatrix);
	printf("addr: %p , value: %d\n", ptrmatrix+1, *(*ptrmatrix+1));
	
   return 0;
}

//程序输出
addr: 0x7ffc261735f0 , value: 1
addr: 0x7ffc26173604 , value: 2
addr: 0x7ffc261735e0 , value: 1
addr: 0x7ffc261735e8 , value: 2
  • 这里的 matrix+1 与 matrix的地址差是 0x7ffc26173604 - 0x7ffc261735f0 == 20
    也就是一个一行元素的 5 x 4 字节;是每一行的地址,跨度是列的字节总数
    printf(“%d\n”,sizeof(matrix[0])); // 显示20
  • 第二种用的数组指针,我们是手动定位到数组的首行以及第二行的首地址,这里的数组地址和第一个元素的地址有区别的。

这是指针和数组的内存指向图
在这里插入图片描述
同样的,这里可以使用数组的 [ ] 索引符

  • *matrix[0] == 1
  • *(matrix[0] + 1) == 2
  • *(*matrix+1))== 2

数组索引计算如下:
arr[i][j] == address of arr + [ (i * size of row) + (j " size of element) ]

数组元素地址 == 数组首地址+相对偏移量

八、传递多维数组

要想在函数内部使用数组表示法,必须指定数组的形态,否则,编译器就无法使用下标。

//要传递数组matrix,可以这么写:
void display2DArray(int arr[][5], int rows);

//或者这么写:
void display2DArray(int(*arr)[5],int rows);

这两种写法都指明了数组的列数,这很有必要,因为编译器需要知道每行有几个元
素。如果没有传递这个信息,编译器就无法计算上面的数组索引计算
{ arr[i][j] == address of arr + [ (i * size of row) + (j " size of element) ] }这样的表达式。

注意: 数组的下标 [ ] 符号在这种情况下是无法使用的:
void display2DArray(int*arr[2], int rows)
因为这是传递整数地址 而不是数组地址

九、动态分配二维数组

为二维数组动态分配内存涉及几个问题:

  • 数组元素是否需要连续;
  • 数组是否规则。

一个声明如下的二维数组所分配的内存是连续的(栈上连续):

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

不过,当我们用malloc这样的函数创建二维数组时,在内存分配上会有几种选择。由于我们可以将二维数组当做数组的数组,因而“内层”的数组没有理由一定要是连续的。如果对这种数组使用下标,数组的不连续对程序员是透明的。

下面就是进行堆上数组内存分配

1、分配不连续内存

int rows=2;
int columns = 5;
int**matrix=(int**)malloc(rows*sizeof(int*));
for(inti=0;i<rows;i++)
{
	matrix[i]=(int*)malloc(columns*sizeof(int));
}

内存示意图:
指针和数组_第7张图片

1、分配不连续内存

int rows = 2;
int columns = 5;
int**matrix=(int**)malloc(rows*sizeof(int*));
matrix [ 0 ] = ( int * ) malloc ( rows * columns * sizeof ( int ) ) ;
for (int i = 1 ; i < rows ; i + + )
{
	matrix[i]=matrix[0]+i*columns;
}

内存示意图:
指针和数组_第8张图片

十、不规则数组和指针

其实不陌生;比如常用的就是二维不规则数组就是字符串组,每一行字符串长度都不同
这个不太常用,一般不怎么用,到时候看自己需要
我们用3个复合字面量声明不规则数组,然后从0开始按行-列顺序初始化数组元素。下面的代码片段会打印数组来验证创建是否正确,因为每行的列数不同,所以需要3个for循环:

int(*(arr2[1))=
{
	(int[]){0,1,2,3},
	(int[]){4,5},
	(int[]){6,7,8}
};

int row = 0;
for(int i=0;i<4;i++)
{
	printf("Layer1[%d][%d] Address:%p Value:%d\n",	row, i, &arr2[row][i], arr2[row][i]);
}
printf("\n");
row=1;
for(int i=0;i<2;i++)
{
	printf("Layer1[%d][%d] Address:%p Value:%d\n", row,i,&arr2[row][i],arr2[row][i]);
}
printf("\n");
row=2;
for(int i=0;i&lt;3;i++)
{
	printf("Layer1[%d][%d] Address:%p Value:%d\n", row,i,&arr2[row][i],arr2[row][i]);
}
printf("\n");

//输出
arr2[0][0] Address:0x000100 Value:0
arr2[0][1] Address: 0x000104 Value: 1
arr2[0][2] Address: 0x000108 Value: 2
arr2[0][3] Address: 0x000112 Value: 3
arr2[1][0] Address: 0x000116 Value: 4
arr2[1][1] Address: 0x000120 Value: 5
arr2[2][0] Address: 0x000124 Value: 6
arr2[2][1] Address: 0x000128 Value: 7
arr2[2][2] Address: 0x000132 Value: 8

内存布局:
指针和数组_第9张图片

你可能感兴趣的:(#,▶,C指针,c语言,数组,数组与指针,指针,指针与数组)