游戏开发工具

定义

数组是用来存储一系列相同类型数据的集合


静态数组

静态数组内存分配是连续的(即地址连续),最低的地址对应首元素,最高的地址对应末尾元素。在栈内存上分配了固定大小,在运行时这个大小不能改变。在函数执行完以后,系统自动销毁;

int a[5] = {1,2,3,4,5};
或 int a[] = {1,2,3,4,5};//定义时地址分配完毕,数组长度固定

定义一个静态数组实例

# include <stdio.h>
int main(void)
{
    int a[5] = {1, 2, 3, 4, 5};
    int i;
    for (i = 0; i < 5; i++ )
    {
        printf("%d\n", a[i]);
    }
    return 0;
}


动态数组

动态数组的内存空间是从堆(heap)上分配(即动态分配)的,当程序执行到分配内存语句时,才为其分配存储空间。运行结束后,程序员需要free自行销毁释放分配的空间。

定义一个动态数组实例

#include <stdio.h>
#include <stdlib.h> //使用malloc函数需要调用此头文件
int main()
{
    int Len;  //数组长度
    int *array;  //数组指针
    int i;  //数组下标
    //获取数组长度
    printf("请输入数组长度:");
    scanf("%d", &Len);
    //动态分配内存空间,如果失败就退出程序
    array = (int*)malloc( Len * sizeof(int) );
    if(array == null)
    {
        printf("内存分配失败!\n");
	exit(0); 
    }
    //向数组占用的内存写入数据
    for(i = 0; i < Len; i++ )
    {
        array[i] = i + 1;
    }
    // 遍历数组
    for(i = 0; i < Len; i++)
    {
        printf("%d  ", array[i]);
    }
    printf("\n");
    free(array); //释放资源
    return 0;
}


一维数组的定义和初始化

1、一维数组定义的一般形式:类型名  数组名[数组长度];

(1)类型名:指定数组中每个元素的类型

(2)数组名:数组变量的名称,是一个合法的标识符

(3)数组长度:一个整型常量表达式,设定数组的大小

如:

 int a[10];//定义了一个有10个整型元素的数组a
 float b[5];//定义了一个有5个单精度浮点型元素的数组b


2、一维数组的初始化:

(1)其一般形式为:类型名  数组名[数组长度] = {初值表};     如:int arr[10] = {1,2,3,4,5,6,7,8,9,10};

(2)自动计算数组的长度:如果声明中未给出数组长度,编译器会把数组长度设置为刚好能容纳初始值的长度。

    如:int arr[ ] = {1,2,3,4,5};等价于int arr[5] = {1,2,3,4,5};

(3)数组的初始化也可以只针对部分元素,如:int arr[5] = {1,2,3};那么数组的前三个元素分别为1,2,3,剩余的元素被初始化为0。

(4)如果初始化列表的项数>数组元素个数,编译器会视其为错误,如:int arr[5] = {1,2,3,4,5,6);//error

(5)指定初始化器:初始化指定元素。

#include <stdio.h>
int main()
{
    int days[8] = { 31, 28, [4] = 31, 30, 31, [1] = 29};
    int i;
    for(i = 0; i < 8; i++)
    {
        printf("%d %d\n",i + 1, days[i]);
    }
      return 0;
}

编译并运行该代码,输出如下:

1 31
2 29
3 0
4 0
5 31
6 30
7 31
8 0

3、指定初始化器的两个重要特性:

如果指定初始化器后面有更多的值,那么这些值将被初始化指定元素后面的元素,即days[4]被初始化为31后,days[5]和days[6]将分别被初始化为30,31

如果再次初始化指定元素,那么最后的初始化将会取代之前的初始化,即days[1]本来被初始化为28,但是days[1]又被后面指定初始化为29.

如果没有指定元素大小,编译器会把数组的大小设置为足够装得下初始化的值,如:int arr[ ] = {1,[6] = 23}; arr数组有7个元素。


一维数组的使用:

1.[ ]为下标引用操作符。数组元素的引用要指定下标,其形式为:数组名[下标]。注意:数组下标从0开始,其取值范围为[0,数组长度-1],下标不能越界。用于识别数组元素的数字被称为下标、索引或者偏移量。所以a[0]的意思就是数组a中的第1个元素(生活中都是从1开始计数的),但是其实在计算机中应该中描述其为第0个元素。

2.看以下代码:

#include <stdio.h>
int main()
{
    int arr[10] = {0};                  //数组的不完全初始化
    int i;                              //作为数组的下标
    int sz = sizeof(arr)/sizeof(arr[0]);//计算数组的元素个数
    //对数组内容进行赋值
    for(i=0; i<10; i++)
    {
        arr[i] = i;
    } 
   //输出数组的内容
   for(i=0; i<10; ++i)
   {
       printf("%d ", arr[i]);
   }
    return 0;
}

编译并运行该代码,输出如下:

0 1 2 3 4 5 6 7 8 9


(1)数组的大小是可以通过计算得到的。sizeof(arr)是整个数组的大小,sizeof(arr[0])是数组中一个元素的大小,两者相除得到的就是数组元素的个数。


(2)这段代码使用了一个循环给数组赋值,注意for循环中的第二条表达式(test)中不能写成i <= 10,这样会造成数组下标越界。C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的,所以程序员写代码时,最好自己做越界的检查。


(3)另外,C不允许把数组作为一个单元赋给另一个数组,如:

#include <stdio.h>
int main()
{
    int oxen[5] = {1, 2, 3, 4, 5};
    int yaks[5];
    yaks = oxen;//error
    return 0;
}

一维数组在内存中的存储

看以下代码:

#include <stdio.h>
int main()
{
    int arr[5] = {0};
    int i;
    for(i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
    {
       printf("&arr[%d] = %p\n", i, &arr[i]);
    }
    return 0;
}

编译并运行该代码,输出如下:

1.png

随着数组下标的增长,元素的地址,也在有规律的递增。因为arr数组的类型为int,所以每个地址之间相差4个字节。由此可以得出结论:数组在内存中是连续存放的。


二维数组的定义和初始化

C语言支持多维数组,最常见的多维数组为二维数组。二维数组本质上是以数组作为数组元素的数组,即“数组的数组”。二维数组的定义形式为:类型名 数组名[行长度][列长度];如:int arr[3] [2];//定义了一个二维数组,3行2列,共6个元素。

二维数组的初始化

(1)分行赋初值:一般形式为:类型名 数组名[行长度][列长度]={{初值表0},{初值表1},...,{初值表n}};

如:

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

此时arr数组中各元素为:

1  2
3  4

二维数组的初始化,也可以只针对部分元素,如:int arr[2][2] = { {1, 2},{} };此时arr数组的第0行的元素的值分别为1,2;而第一行的元素的初值默认为0。

(2)顺序赋初值:一般形式为:类型名 数组名[行长度][列长度]={初值表};

如:

int arr[2][2] = { 1, 2, 3, 4 };等价于int arr[2][2] = { { 1,  2} , {3, 4} };
int arr[2][2] = { 1, 2, 0, 0 };等价于int arr[2][2] = { {1, 2},{} };

(3)注意:二维数组初始化时,如果对全部元素都赋了初值,或分行赋初值时,在初值表中列出了全部行,就可以省略行长度。

如:

int arr[ ][3] = {1,2,3,4,5,6,7,8,9};等价于int arr[3][3] = {{1,2,3},{4,5,6},{7,8,9}};

不可以省略列的长度!!!

二维数组的使用

1.引用二维数组的元素要指定两个下标,即行下标和列下标,形式为:数组名[行下标][列下标]。行下标的合理取值范围为[0,行长度-1],列下标的合理取值范围为[0,列长度-1]。比如a[0][0]的意思是第1行第1列的元素(生活中从1开始计数),但是其实在计算机中应该中描述其为第0行第0列的元素。

用两重循环来给二维数组赋值:

#include <stdio.h>
int main()
{
    int arr[2][2] = {0};
    int i, j;
    for(i = 0; i < 2; i++)
    {
       for(j = 0; j < 2; j++)
       {
          arr[i][j] = i * 4 + j;
       }
    }
    for(i = 0; i < 2; i++)
    {
       for(j = 0; j < 2; j++)
       {
           printf("%d ", arr[i][j]);
       }
    }
    return 0; 
}

编译并运行代码,输出如下:

0 1 4 5


二维数组在内存中的存储

看以下代码:

#include <stdio.h>
int main()
{
    int arr[2][2];
    int i, j;
    for(i = 0; i < 2; i++)
    {
       for(j = 0; j < 2; j++)
       {
           printf("&arr[%d][%d] = %p\n", i, j,&arr[i][j]);
       }
    }
    return 0; 
}

编译并运行该代码,输出如下:

2.png

由此,我们可以分析,二维数组在内存中也是连续存储的。所以,二维数组可以整体看成一个一维数组,如图:

3.png

数组作为函数参数

1.数组名是数组首元素的地址,数组名是一个地址常量。

2.两个例外:

(1)sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。

(2)&数组名,取出的是数组的地址。&数组名,数组名表示整个数组


除了这 两种情况之外,其它所有的数组名都表示数组首元素的地址,如:*数组名等等

看以下代码以及结果:

4.png

test()中打印的是长度为4,更加印证了数组作为函数参数时,没有把整个数组都传过去,而是把数组中首元素的地址传过去了,此处的地址是用十六进制表示的整型常量,所以大小为4byte。

main()中打印的长度为40,是因为sizeof(数组名)计算的是整个数组的大小,数组中有10个int型的元素,所以10*4=40

函数定义头中的形参int arr[]里面的[],它的里面可以什么都不填,也可以填一个任意数,因为它没有意义,比如说形参写成int arr[20]也是可以的,但是为了代码的可读性更强,我们不建议这样写。

 其实,test()的形参还可以这样写:

void test(int* arr)
{
    printf("%d\n",sizeof(arr));
}

因为数组名是数组首元素的地址,我们用指针来接收arr的首元素地址更好。注意:只有在函数原型或函数定义头中,才可以用int arr[]代替int* arr。

在函数原型中可以省略参数名,所以下面这4种写法等效:

void test(int* arr);
void test(int arr[]);
void test(int*);
void test(int []);