数组

一、数组的定义
数组(Array)是有序的元素序列。 若将有限个类型相同的变量的集合命名,那么这个名称为数组名。组成数组的各个变量称为数组的分量,也称为数组的元素,有时也称为下标变量。用于区分数组的各个元素的数字编号称为下标。数组是在程序设计中,为了处理方便, 把具有相同类型的若干元素按有序的形式组织起来的一种形式。这些有序排列的同类数据元素的集合称为数组。
二、数组的初始化
数组的初始化是指在创建数组的同时给数组的内容一些合理初始值(初始化)。

int arr1[10] = {1,2,3};
int arr2[] = {1,2,3,4};
int arr3[5] = {1,2,3,4,5};
char arr4[3] = {'a',98, 'c'};
char arr5[] = {'a','b','c'};
char arr6[] = "abcdef";

数组在创建的时候如果想不指定数组的确定大小就得初始化。数组的元素个数根据初始化的内容来确定。
注:char型数据是将一个字符常量放到一个字符变量中,并不是把该字符本身放到内存单元中去,而是将该字符的相应的ASCII代码放到存储单元中。因为b的ASCII码值是98,所以arr4和arr5所存储的内容是完全一致的。

对于下面的代码要区分,内存中是如何分配的呢

char arr1[] = "abc";
char arr2[3] = {'a','b','c'};

arr1以字符串的方式初始化,在数组的结尾会自动添加上’\0’,而arr2的方式则不会,只能自己在初始化的结尾加上’\0’才能完成初始化操作。
三、给数组元素赋值
声明数组后,可以借助数组下标(或索引)给数组元素赋值。例如,下面的程序段给数组的所有元素赋值:

/给数组的元素赋值/
#include<stdio.h>
#define SIZE 50
int main(void)
{
int counter,evens[SIZE];
for(counter=0; counter<SIZE; counter++)*
evens[counter]=2counter;
...
}

这段代码中使用循环给数组的元素依次赋值。注意:
(1)C不允许把数组作为一个单元赋给另一个数组。
(2)除初始化以外不允许使用花括号列表的形式赋值。
下面的代码段演示了一些错误的赋值形式:

/一些无效的数组赋值/
#define SIZE 5
int main(void)
{
int oxen[SIZE]={5,3,2,8};/初始化没问题/
int yaks[SIZE];
yaks=oxen;/不允许/
yaks[SIZE]=oxen[SIZE];/数组下标越界/**

四、数组边界
在使用数组时,要防止数组下标超出边界,也就是说,必须确保下标是有效的值。
下面的程序创建了一个内含四个元素的数组,然后错误的使用了-1~6的的下标:

#include 
#define SIZE 4
int main(void)
{
    int value1 = 44;
    int arr[SIZE];
    int value2 = 88;
    int i;
    printf("value1 = %d, value2 = %d\n", value1, value2);
    for (i = -1; i < SIZE; i++)
        arr[i] = 2 * i + 1;
       for (i = -1; i < 7; i++)
        printf("%2d %d\n", i, arr[i]);
    printf("value1 = %d, value2 = %d\n", value1, value2);
    printf("address of arr[-1] : %p\n", &arr[-1]);
    printf("address of arr[4] : %p\n", &arr[4]);
    printf("address of value1 : %p\n", &value1);
    printf("address of value2 : %p\n", &value2);
    return 0;
   }

编译器不会检查数组下标是否使用得当。在C标准中,使用越界下标的
结果是未定义的。这意味着程序看上去可以运行,但是运行结果很奇怪,或
异常中止。下面是使用GCC的输出示例:

value1=44,value2=88

-1       -1
 1        3
 2        5
 3        7
 4        9
 5        1624678494
 6        32767
value1=9, value2=-1
address of arr[-1]: 0x7fff5fbff8cc
address of arr[4]: 0x7fff5fbff8e0
address of value1: 0x7fff5fbff8e0
address of value2: 0x7fff5fbff8cc

注意,该编译器似乎把value2储存在数组的前一个位置,把value1储存在数组的后一个位置(其他编译器在内存中储存数据的顺序可能不同)。在上面的输出中,arr[-1]与value2对应的内存地址相同,arr[4]和value1对应的内存地址相同。因此,使用越界的数组下标会导致程序改变其他变量的值。
不同的编译器运行该程序的结果可能不同,有些会导致程序异常中止。
五、指定数组的大小
使用整型常量来声明数组:

#define SIze 4
intmain(void)
{
intarr[SIZE];//整数符号常量
doublelots[144];//整数字面常量
...

在C99标准之前,声明数组时只能在方括号中使用整型常量表达式。所
谓整型常量表达式,是由整型常量构成的表达式。sizeof表达式被视为整型
常量,但是(与C++不同)const值不是。另外,表达式的值必须大于0:

上面的

int n=5
int m=8;
float a1[5];//可以
float a2[5*2+1];//可以
float a3[sizeof(int)+1];//可以
float a4[-4];//不可以,数组大小必须大于0
float a5[0];//不可以,数组大小必须大于0
float a6[2.5];//不可以,数组大小必须是整数
float a7[(int)2.5];//可以,已被强制转换为整型常量
float a8[n];//C99之前不允许
float a9[m];//C99之前不允许

注释表明,以前支持C90标准的编译器不允许后两种声明方式。
而C99标准允许这样声明,这创建了一种新型数组,称为变长数组
(variable-lengtharray)或简称VLA(C11放弃了这一创新的举措,把VLA设定为可选,而不是语言必备的特性)。
C99引入变长数组主要是为了让C成为更好的数值计算语言。例如,VLA简化了把FORTRAN现有的数值计算例程库转换为C代码的过程。VLA有一些限制,例如,声明VLA时不能进行初始化。在充分了解经典的C数组后,我们再详细介绍VLA。
六、一维数组
1.数组的创建
(1)数组是一组相同类型元素的集合。 数组的创建方式:

type_t arr_name [const_n];
//type_t 是指数组的元素类型
//const_n 是一个常量表达式,用来指定数组的大小

(2)数组创建的实例:

//代码1
int arr1[10];
//代码2
int count = 10;
int arr2[count];//数组时候可以正常创建?
//代码3
char arr3[10];
float arr4[1];
double arr5[20];

注:上面的代码2,在用 gcc 去编译这段程序时,是没有任何问题的,但在vs2019环境下会报以下的错误:-1

是因为在 ISO/IEC9899 标准的 6.7.5.2 Array declarators 中明确说明了数组的长度可以为变量的,称为变长数组(VLA,variable length array)。(注:这里的变长指的是数组的长度是在运行时才能决定,但一旦决定在数组的生命周期内就不会再变。)
在 GCC 标准规范的 6.19 Arrays of Variable Length 中指出,作为编译器扩展,GCC 在 C90 模式和 C++ 编译器下遵守 ISO C99 关于变长数组的规范。

注意:在我们学习的vs2019环境下,数组创建, [] 中要给一个常量才可以,不能使用变量。
2.一维数组的使用
对于数组的使用我们之前介绍了一个操作符: [] ,下标引用操作符。它其实就数组访问的操作符。 我们来看代码:

#include <stdio.h>
int main()
{
    int arr[10] = {0};
    //计算数组的元素个数
    int sz = sizeof(arr)/sizeof(arr[0]);
    //对数组内容赋值,数组是使用下标来访问的,下标从0开始。
    int i = 0;//做下标
    for(i=0; i<10; i++)
    {
        arr[i] = i;
    } 
    //输出数组的内容
    for(i=0; i<10; ++i)
    {
        printf("%d ", arr[i]);
    }
    return 0;
}

总结:
(1)数组是使用下标来访问的,下标是从0开始。
(2)数组的大小可以通过sizeof操作符计算得到。
int arr[10];
int size =sizeof(arr)/sizeof(arr[0]);
3.一维数组在内存中的存储
接下来我们探讨数组在内存中的存储。 看代码:

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

输出结果如下:

&arr[0]= 007EFE04
&arr[1]= 007EFE08
&arr[2]= 007EFE0C
&arr[3]= 007EFE10
&arr[4]= 007EFE14
&arr[5]= 007EFE18
&arr[6]= 007EFE1C
&arr[7]= 007EFE20
&arr[8]= 007EFE24
&arr[9]= 007EFE28

仔细观察输出的结果,我们知道,随着数组下标的增长,元素的地址,也在有规律的递增。 由此可以得出结论:数组在内存中是连续存放的。
七、指定初始化器(C99)
C99增加了一个新特性:指定初始化器(designatedinitializer)。利用
该特性可以初始化指定的数组元素。例如,只初始化数组中的最后一个元
素。对于传统的C初始化语法,必须初始化最后一个元素之前的所有元素,
才能初始化它:
intarr[6]={0,0,0,0,0,212};//传统的语法
而C99规定,可以在初始化列表中使用带方括号的下标指明待初始化的
元素:
intarr[6]={[5]=212};//把arr[5]初始化为212
对于一般的初始化,在初始化一个元素后,未初始化的元素都会被设置
为0。程序清单中的初始化比较复杂。
程序清单designate.c程序

//designate.c--使用指定初始化器
#include<stdio.h>
#defineMONTHS12
intmain(void)
{
intdays[MONTHS]={31,28,[4]=31,30,31,[1]
=29};
inti;
for(i=0;i<MONTHS;i++)
printf("%2d%d\n",i+1,days[i]);
return0;
}

该程序在支持C99的编译器中输出如下:

1   31
2   29
3   0
4   0
5   31
6   30
7   31
8    0
9    0
10  0
11  0
12  0

以上输出揭示了指定初始化器的两个重要特性。第一,如果指定初始化器后面有更多的值,如该例中的初始化列表中的片段:[4]=31,30,31,那么
后面这些值将被用于初始化指定元素后面的元素。也就是说,在days[4]被初始化为31后,days[5]和days[6]将分别被初始化为30和31。第二,如果再次初始化指定的元素,那么最后的初始化将会取代之前的初始化。例如程序清单开始时把days[1]初始化为28,但是days[1]又被后面的指定初始化[1]=29初始化为29。

八、二维数组
1.导入
我们要在气象分析程序中用到这个二维数组。该程序的目标是,计算每年的总降水量、年平均降水量和月平均降水量。要计算年总降水量,必须对一行数据求和;要计算某月份的平均降水量,必须对一列数据求和。

2.二维数组的创建
//数组创建

int arr3;
char arr3;
double arr2;
/rain.c--计算每年的总降水量、年平均降水量和5年中每月的平均降水量/
#include <stdio.h>
#define MONTHS 12//一年的月份数
#define YEARS 5//年数
int main(void)
{
//用2010~2014年的降水量数据初始化数组
const float rainYEARS=
{
{4.3,4.3,4.3,3.0,2.0,1.2,0.2,0.2,0.4,2.4,3.5,
6.6},
{8.5,8.2,1.2,1.6,2.4,0.0,5.2,0.9,0.3,0.9,1.4,
7.3},
{9.1,8.5,6.7,4.3,2.1,0.8,0.2,0.2,1.1,2.3,6.1,
8.4},
{7.2,9.9,8.4,3.3,1.2,0.8,0.4,0.0,0.6,1.7,4.3,
6.2},
{7.6,5.6,3.8,2.8,3.8,0.2,0.0,0.0,0.0,1.3,2.6,
5.2}
};
int year, month;
float subtot,total;
printf("YEAR RAINFALL(inches)\n");
for(year=0, total=0; year<YEARS;year++)
{//每一年,各月的降水量总和
for(month=0, subtotal = 0 ; month < MONTHS ; month++)
subtotal += rainyear;
printf("%5d%15.1f\n",2010+year,subtot);
total+=subtot;//5年的总降水量
}
printf("\n The yearly average is%.1 finches.\n\n",total/YEARS);
printf("MONTHLYAVERAGES:\n\n");
printf("Jan Feb Mar Apr May Jun Jul Aug Sep Oct");
printf("Nov Dec\n");
for(month=0;month<MONTHS;month++)
{//每个月,5年的总降水量
for(year=0,subtot=0;year<YEARS;year++)
subtot+=rainyear;
printf("%4.1f",subtot/YEARS);
}
printf("\n");
return0;
}

下面是该程序的输出:

YEAR  RAINFALL(inches)
2010   32.4
2011   37.9
2012   49.8
2013   44.0
2014   32.9
They early average is 39.4 inches.
MONTHL YAVERAGES:
Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
7.3 7.3 4.9 3.0 2.3 0.6 1.2 0.3 0.5 1.7 3.6 6.7

学习该程序的重点是数组初始化和计算方案。初始化二维数组比较复杂,我们先来看较为简单的计算部分。
程序使用了两个嵌套for循环。第1个嵌套for循环的内层循环,在year不变的情况下,遍历month计算某年的总降水量;而外层循环,改变year的值,重复遍历month,计算5年的总降水量。这种嵌套循环结构常用于处理二维数组,一个循环处理数组的第1个下标,另一个循环处理数组的第2个下标:
for(year=0,total=0;year<YEARS;year++)
4.二维数组的初始化
float rain5;//内含5个数组元素的数组,每个数组元素内含12个float类型的元素
理解该声明的一种方法是,先查看中间部分(粗体部分):
float rain5;//rain是一个内含5个元素的数组
这说明数组rain有5个元素,至于每个元素的情况,要查看声明的其余部分(粗体部分):
float rain[5][12];//一个内含12个float类型元素的数组
这说明每个元素的类型是float[12],也就是说,rain的每个元素本身都是一个内含12个float类型值的数组。
根据以上分析可知,rain的首元素rain[0]是一个内含12个float类型值的数组。所以,rain[1]、rain[2]等也是如此。如果rain[0]是一个数组,那么它的首元素就是rain0,第2个元素是rain0,以此类推。简而言之,数组rain有5个元素,每个元素都是内含12个float类型元素的数组,rain[0]是内含12个float值的数组,rain0是一个float类型的值

上一篇:web3 writeup


下一篇:以太坊Geth通过私钥导入新地址到钱包步骤(3种方法)