导读:
本文概述数组,然后描述 数组表示法 和 指针表示法 的异同。我们可以使用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];
一维数组是线性结构,用一个索引访问成员。下面的代码声明了一个5个元素的整数数组:
int vector[5];
printf("%d\n",sizeof(vector)/sizeof(int));
数组索引从0开始,到声明的长度减1结束。vector数组的索引从0开始,到4
结束。
二维数组使用行和列来标识数组元素,这类数组需要映射为内存中的一维地址空间。在C中这是通过行-列顺序实现的。先将数组的第一行放进内存,接着是第二行、第三行,直到最后一行。
所以我们有时候看到这样的写法是对的:
//可以忽略行数,但是不可以忽略列数;因为列数是限制行数的。
int array[][6]
看下图声明和内存示意图
int matrix[2][3]={{1,2,3},{4,5,6}};
多维数组具有两个及两个以上维度。对于多维数组,需要多组括号来定义数组的类型和长度。下面的例子中,我们定义了一个具有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}}
};
要学懂指针和数组;本质上还是要搞清楚他们原来最朴素的原理
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 |
创建很简单;我一般使用堆区数组的时候、我一般使用 方式二,这提醒我用的是地址索引。
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;
realloc函数主要就是在现有的基础上(基础可以是0字节内存也是传ptr为NULL)扩容,可以实现动态扩容
//函数声明:
void *realloc( void *ptr, size_t new_size );
// 参数说明:
// ptr - 指向需要重新分配的内存区域的指针 (也就是就内存区的首地址)
// new_size - 数组的新大小(字节数)
// 返回值
//成功时,返回指向新分配内存的指针。返回的指针必须用 free() 或 realloc() 归还。
//失败时,返回空指针。原指针 ptr 保持有效,并需要通过 free() 或 realloc() 归还。
注意要点:
重新分配按以下二者之一执行:
a) 可能的话,扩张或收缩 ptr 所指向的已存在内存。内容在新旧大小中的较小者范围内保持不变。若扩张范围,则数组新增部分的内容是未定义的。
b) 分配一个大小为 new_size 字节的新内存块,并复制大小等于新旧大小中较小者的内存区域,然后释放旧内存块。
因为数组是确定数量的数据块,不管是数组表示法还是指针表示法都是得传递数组的个数,当然我们也可以学习字符串的那种方法,约定数组结尾是特殊值也能不需要传数组长度
直接传入的数组名,本质上还是数组首地址
//传入数组名、可以指针数组长度, 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);
声明函数的数组参数不一定要用方括号表示法,也可以用指针表示法,如下所示:
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[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、可以将多维数组的一部分看做子数组。 比如说,二维数组的每一行都可以当做一维数组。我们使用这样的一层层的解释数组,就可以把高维数组看成比它低一维的数组的一维数组。
我们就可以理解这种写法:
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
这是指针和数组的内存指向图
同样的,这里可以使用数组的 [ ] 索引符
数组索引计算如下:
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这样的函数创建二维数组时,在内存分配上会有几种选择。由于我们可以将二维数组当做数组的数组,因而“内层”的数组没有理由一定要是连续的。如果对这种数组使用下标,数组的不连续对程序员是透明的。
下面就是进行堆上数组内存分配
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));
}
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;
}
其实不陌生;比如常用的就是二维不规则数组就是字符串组,每一行字符串长度都不同
这个不太常用,一般不怎么用,到时候看自己需要
我们用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<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