数组:一组具有相同数据类型变量的集合

数组名:表示这组数据的名字
索引:标识数组中数据的位置
数据元素:构成数组的每个数据项,数组名+索引 如:a[i]表示数组a的第i个元素

一维数组

定义一维数组格式:数据类型 数组名[正整数] 例:float score[10];
数组的初始化是在声明后面紧跟一个初始化表达式列表,列表用花括号括起来,各初始化表达式之间通过逗号分隔
如:int days[]={31,28,31,30,31,30,31,31,30,31,30,31};
定义了一个由12个对象组成的集合,对象存储在相邻的内存区域中
定义数组时可对数组进行初始化,存在几种不同情况,例:                以按行存储为准
    int a[5]={12,34,56,78,9};    标准形式,全部初始化
    int a[5]={0};                初始化表达式的个数比数组元素数少,则没有初始化表达式的外部变量、静态变量和自动变量元素将被初始化为0
    int a[]={11,22,33,44,55};    不指定的数组长度,则数组空间的长度为初始化的数据个数
    int a[5]={11};               部分初始化,第一个数据是11,其余为0
    int a[5];                    未初始化,其中数据是杂乱无章的
如果初始化表达式的个数比数组元素数多,则是错误的

不能跳过前面的数组元素而直接初始化后面的数组元素

一维数组使用:数组名+数组下标

数组下标(index):从0开始,数组元素的索引
数组下标可以为常量,整型表达式,并且允许快速随机访问 如:score[i] 可以像普通变量一样使用
一维数组的存储:系统分配一块连续的存储空间,大小为 数据类型大小*数组元素个数

数组名表示数组的首地址(相当于整个数组的地址),值等于首个元素的地址
例:int a[10];  假设a的首地址为1000,int占4个字节空间,则 &a[n]为1000+4*n 如 &a[5]=1020
因为是连续的地址空间,所以数组中元素的访问速度是非常快的

数组名表示数组的首地址,其值不可改变(常量)

定义两个数组int a[4]={1,2,3,4},b[4]; b=a; 是错误的,因为b的值不可改变
因此,如果想令b的值等于a,则需依次对b中的元素进行修改,如:a[0]=b[0],a[1]=b[1],a[2]=b[2],a[3]=b[3];
                                              或:for(i=0,;i<4;i++)b[i]=a[j];
数组的长度在编译时已经完全确定,它等于数组项的长度乘以项数

因此,可得出项数为:keytab的长度/struct key的长度

c提供了一个编译时(compile-time)一元运算符sizeof,它可以用来计算任一对象的长度

表达式:sizeof 对象 以及 sizeof(类型名) 将返回一个整型值,其数值等于对象或类型占用的存储空间字节数

(严格地说,sizeof的返回值是无符号整型,其类型为size_t,该类型定义在头文件<stddef.h>中定义)

其中对象可以是变量、数组或结构;类型可以是基本类型如int、double、也可以是派生类型如结构类型或指针类型
如:#define NKEYS (sizeof keytab/sizeof(struct key))
 或#define NKEYS (sizeof keytab/sizeof keytab[0]) 使用这种方法即使类型变了也不需要改动程序
一维数组示例程序:显示键盘输入的月份拥有的天数,不包括闰年的天数
#include<stdio.h>
int main()
{
    int month,i;
    int days[12]={31,28,31,30,31,30,31,31,30,31,30,31};
    scanf("%d",&month);
    printf("%d月有%d天",month,days[month-1]);
}
一维数组示例2:输入10个学生成绩,输出其中最高分和其在数组中的位置
#include<stdio.h>
int main()
{
    int score[10];
    int num,i,j;
    int highest,topstudent;
    for(i=0;i<10;i++)
    {
        scanf("%d",&num);
        score[i]=num;              &score[i]与score+i相同都表示score中第i个元素的地址
        for(j=0;j<i;j++)
    	{
   	    if(num<=score[j])break;
	    else if(j==i-1)highest=num,topstudent=i;
        }
    }
    printf("%d\n%d",highest,topstudent);
}

二维数组

c提供了类似于矩阵的多维数组,但实际上这种数组的应用不如指针数组广泛(空间浪费过于严重)

二维数组:在c中二维数组就是一种特殊的一维数组,一个二维数组的元素本身是一个一维数组

同理可推 n维数组:一个一维数组的元素本身是一个n-1维数组
n维数组定义:数据类型 数组名[元素个数1][元素个数2]...[元素个数n]
例:int two[10][20];two[2][8]=1;
   int three[4][5][3];three[i][j][k]=3;
   错误的写法:two[10,20];
除表示方式的差别外,c中二维数组的使用与其他语言一样

数组元素按行存储,因此当按存储顺序访问数组时,最右边的数组下标(列)变化的最快

二维数组的逻辑结构:
例:两行三列数组a的逻辑结构
             第一列   第二列   第三列       行数为第一个括号中的数,列数为第二个括号中的数
    第一行   a[0][0] a[0][1] a[0][2]      行数实际指定了二维数组的长度,列数指定元素的长度
    第二行   a[1][0] a[1][1] a[1][2]
二维数组的存储空间大小为 数据类型大小*一维数组个数*一维数组元素数量

二维数组的初始化

数组可以用花括号括起来的初值进行初始化,二维数组每一行由相应的子列表进行初始化

声明二维数组时必须指明数组的列数,而数组行数没有太大的关系

这是由于二维数组在用于函数参数传递等用途时,函数调用的是一个指针,对二维数组而言指针指向“行向量”构成的一维数组

而这个一维数组的长度是需要声明的,否则指针的类型无法确定(指针算术运算时也无法确定应该移动几个连续存储单元)

如:f(int daytab[2][13]){}、f(int daytab[][13])、f(int (*daytab)[13])

一般而言,除数组的第一维可以不指定大小外,其余各维都必须明确指定大小
二维数组的初始化的几种情况,例:
    int a[3][3]={1,2,3,4,5,6,7,8,9};
    按元素初始化,第1-3个数据为第0行数据,第4-6个是第1行数据,第7-9个是第2行数据
    int a[3][3]={{1,2,3},{4,5,6},{7,8,9}};
    按行初始化,第1个一维数组内为第0行数据,第2个一维数组内是第1行数据,第3个一维数组内是第2行数据
    int a[][3]={{1,2,3},{4,5,6},{7,8,9}};
    省略行数的按行初始化,根据一维数组数确定行数为3,然后按行初始化即可,编译时将对初值个数进行统计并将这一准确数字填入数组长度
    int a[3][3]={1,2};
    部分初始化,行列数已确定,只初始化前两个元素即第0行第1、2个元素,其他用0填充
    int a[3][3]={{1,2},{4},{7,8,9}};
    按行部分初始化,行列数已确定,将给定行的前几个数据初始化,其他用0填充
    int a[][3]={{1},{4,5},{7,8,9}};
    省略行数的按行部分初始化,根据一维数组数确定行数为3,然后将给定行的前几个数据初始化,其他用0填充
    int a[][3]={1,2,3,4,5,6,7}
    省略行数的按元素部分初始化,列数已确定,给定数据依次为第0,1行和第2行第1个数据,其他用0填充
    例:int a[][3]={{1,2,3},{4,5},{6},{0}};
    printf("%d,%d,%d",a[1][1],a[2][1],a[3][1]);   输出结果:5,0,0 

字符数组

字符数组是存放字符的数组,每个元素为一个字符

一个一维字符数组可以存放一个字符串

一个二维数组可以存放多个字符串

字符数组声明与初始化
例:char str[10];                       一般必须指定长度,因为不指定长度char类型默认为1个字符
    字符数组中因为不一定包含字符串结束符'\0',所以不一定是字符串
    与正常的数组一样,可以逐个元素赋值,也可在定义时初始化,例:
    char str[10]={'c','h','i','n','a'}; 用字符进行逐个元素的初始化,前5个元素初始化,不足的用0填充
    char s1[20]={"How do you do"};      直接以字符串进行初始化,字符串常量初始化后,自动在结尾加入结束标志\0(或者数值0)
    char s2[20]="How do you do"         花括号可以省略,此时s2[20]本身就代表一个字符串常量,可以直接按字符串形式输出
    通过输入输出操作进行初始化,例:
    单个字符输入输出
    char str[10];
    for(i=0;i<5;i++)scanf("%c",&str[i]);     注意:visual studio中对字符要求使用安全输入函数scanf_s,需要在参数中指定字符长度
    for(j=4;j>=0;j--)printf("%c",str[j]);

字符串常量

字符串常量是一个字符数组(但字符数组不一定是字符串)

如:"I am a string"在字符串的内部表示中,字符数组以空字符'\0'结尾

因此程序可以通过检查空字符找到字符串结尾,字符串常量占据的存储单元数也因此比双引号内的字符数大1

字符串常量的主要用法也许就是作为函数参数,如:printf("hello,world\n");

在上面的语句中,当类似的字符串出现在程序中是通过字符指针访问字符串,实际上printf接受的就是一个指向字符数组的第一个字符的字符指针

因此字符串常量也可以通过指向其第一个元素的指针访问
除作为函数参数外,字符串常量还可以对字符指针和字符数组进行赋值,如:char *pmessage="now is the time"
该赋值过程没有进行字符串的拷贝,而只涉及指针的操作,实际上此时*pmessage='n'(首元素地址存储的值)而不是整个字符串

同样的,c没有提供将整个字符串作为整体进行处理的运算符
字符数组示例程序:寻找姓名中含有James的校友
#include<stdio.h>
#include<string.h>
int main()
{
    const int n=20,m=6;
    char name[][n]={"Kate.Wate","James.Tan","Bull.Ben","James.Tide","James.Ting","Lebron.James"};
    char James[]="James";
    int i,j,k,l,len=strlen("James");
    for(i=0;i<m;i++)
    {
        for(j=0;j<len;j++)
        {
            if(name[i][j]!=James[j])break;
        }
        if(j==len)printf("%s\n",name[i]);
        for(k=strlen(name[i])-len,l=0;k<strlen(name[i])&&l<len;k++,l++)
        {
            if(name[i][k]!=James[l])break;
	}
	if(l==len)printf("%s\n",name[i]);
    }
}