chap06:数组(array)
数组是一种派生类型的数据。
定义: 是一组具有相同类型、相同名称的有有序数据(变量)的集合。
- 相同类型:数组里的数据具有相同数据类型;
- 相同名称:个体通过下标(索引)来区分;
- 有序:在内存中按顺序连续存放,最低的地址对应第一个元素,最高的地址对应最后一个元素。;
数组有一维数组与多维(二维、三维……)数组,我们先从一维数组开始介绍。
一、一维数组:
1、一维数组的声明(定义)
type arrayName [arraySize];
- type: 可以是任意有效的 C 数据类型;
- arrayName: 数组名(符合标识符的命名规则);
- arraySize: 必须是一个大于零的整型常量;
例:
int score[5]; //定义了一个整型数组score,含有5个数组元素。
double balance[10]; //定义了一个浮点型数组balance,含有10个数组元素。
2、访问数组元素
数组元素可以通过数组名称加下标(索引)进行访问。元素的索引是放在方括号内,跟在数组名称的后边。
-
下标(索引): 从0开始(基索引)到arraySize-1;可以看成是从第一个数组元素的偏移量。数组元素在内存中按顺序连续存放,最低的地址对应第一个元素,最高的地址对应最后一个元素。;
- 数组长度arraySize可以使用 sizeof 运算符来获取数组的长度,例如:
int numbers[5]; int array_size = sizeof(numbers) / sizeof(numbers[0]);
- 数组名
在 C 语言中,数组名表示数组的地址,即数组首元素的地址。下标就是偏移量。
-
数组元素可以像普通变量一样使用(赋值、参与运算……),以称为下标变量。
例: 上例中定义的score数组包含有5个数组元素,分别为:score[0],score[1],score[2],score[3],score[4]
int score[5]; //定义了一个整型数组score,含有5个数组元素。
score[0]=10;
score[1]=score[0]*2;
score[2]=score[0]+score[1];
printf("%d,%d,%d",score[0],score[1],score[2]);
3、初始化数组
在 C 中,您可以逐个为数组元素赋值(很麻烦),也可以使用一个初始化语句,如下所示:
int score[5]={10,20,30,40,50};
如果您省略掉了数组的大小,数组的大小则为初始化时元素的个数。因此,如果:
int score[]={10,20,30,40,50};
它与前一个实例中所创建的数组是完全相同的。
只有在初始化时,才能省略数组的大小。如以下定义是错误的。
int score[]; //错误的
如果初始化的值的数量小于数组的大小(如下所示),后面的数组元素自动初始化为零值。
int score[5]={10,20}; //score[0],score[1]分别初始化为10,20,score[2],score[3],score[4]为0
### 4、通过循环访问数组元素
下面的程序是不是很繁琐!
#include <stdio.h>
int main ()
{
int score[5];
//输入5个值分别给5个数组元素
scanf("%d",&score[0]);
scanf("%d",&score[1]);
scanf("%d",&score[2]);
scanf("%d",&score[3]);
scanf("%d",&score[4]);
//输出5个数组元素的值
printf("%d,%d,%d,%d,%d\n",score[0],score[1],score[2],score[3],score[4]);
}
如果是数组大小是50个呢?这样写就是一个灾难!
可以用循环实现:
#include <stdio.h>
int main ()
{
int score[5];
//输入5个值分别给5个数组元素
for(i=0;i<5;i++){
scanf("%d",&score[i]);
}
//输出5个数组元素的值
for(i=0;i<5;i++){
printf("%d ",score[i]);
}
}
非常重要: 仔细体会下面程序。
#include<stdio.h>
#include<stdlib.h> //要使用随机函数,必须要包含这个库
#define N 10 //宏定义:定义一个符号常量N来表示数组大小,这样便于修改
int main (){
int score[N],i,sum=0;
srand(time(0)); //初始化随机数发生器,要不然产生的随机数是伪随机数
// 随机生成N个数给相应数组元素
for(i=0;i<N;i++)
score[i]=rand()%101; // rand()%101 产生0~100之间的随机数
//求所有数组元素的和
for(i=0;i<N;i++)
sum += score[i];
//输出所有数组元素的值,每个占一行
for(i=0;i<N;i++){
printf("%d\n",score[i]);
}
printf("总和:%d,平均数:%.2f\n",sum,(float)sum/N);
// 这样写:(float)sum/N /两边都是整数时,是整除,要强制转换
}
注: 产生 a~b之间的随机整数(包括a,b): a + rand()%(b-a+1)
二、用一维字符数组存储处理字符串
1、一些基本概念
C语言中只有字符串常量的概念。
字符串字面值或常量:是括在双引号 “” 中的。一个字符串包含类似于字符常量的字符:普通的字符、转义序列和通用的字符。如:“Hello” , “Don\'t give up and don\'t give in.”
C语言里没有字符串变量的概念(后面我们学习其它语言有),但我们可以用一维字符数组来处理字符串,因此我们可以将一维字符数组看成字符串变量。
但要注意字符数组不是字符串变量,只是可以用来存储字符串。
注意:(非常重要)字符串在内存中存储时,会自动添加一个null 字符,即’\0’。如字符串"Hello"在内在中存储时如下图
占6个字节的空间。
因此:
char s1[]={'H','e','l','l','o'};
char s2[]={"Hello"};
这两个字符数组是不一样的!s1里存的就不是字符串,s2里存的就是字符串。它们在内在中所占的字节数是不一样的(数组长度是不一样的)。
如:输出它们所占的字节数。
printf("S1占%d个字节,S2占%d个字节。\n",sizeof(s1),sizeof(s2));
输出:S1占5个字节,S2占6个字节。
2、字符串的输入与输出
字符串的输出
在C语言中,有两个函数可以在控制台(显示器)上输出字符串,它们分别是:
- puts():输出字符串并自动换行,该函数只能输出字符串。
- printf():通过格式控制符
%s
输出字符串,不能自动换行。除了字符串,printf() 还能输出其他类型的数据。
#include <stdio.h>
int main(){
char str[] = "I am a student.";
printf("%s", str); //通过字符串名字输出
printf("%s", "I am a student."); //直接输出
printf("\n")
puts(str); //通过字符串名字输出
puts("I am a student."); //直接输出
}
运行结果:
I am a student. I am a student.
I am a student.
I am a student.
注意,输出字符串时只需要给出名字,不能带后边的[ ]
,例如,下面的两种写法都是错误的:
printf(“%s\n”, str[]);
puts(str[10]);
字符串的输入:
在C语言中,有两个函数可以让用户从键盘上输入字符串,它们分别是:
- scanf():通过格式控制符
%s
输入字符串。除了字符串,scanf() 还能输入其他类型的数据。 - gets():直接输入字符串,并且只能输入字符串。
但是,scanf() 和 gets() 是有区别的:
- scanf() 读取字符串时以空格为分隔,遇到空格就认为当前字符串结束了,所以无法读取含有空格的字符串。
- gets() 认为空格也是字符串的一部分,只有遇到回车键时才认为字符串输入结束,所以,不管输入了多少个空格,只要不按下回车键,对 gets() 来说就是一个完整的字符串。换句话说,gets() 用来读取一整行字符串。
请看下面的例子:
#include <stdio.h>
int main(){
char str1[30] = {0};
char str2[30] = {0};
char str3[30] = {0};
//gets() 用法
printf("Input a string: ");
gets(str1);
//scanf() 用法
printf("Input a string: ");
scanf("%s", str2);
scanf("%s", str3);
printf("\nstr1: %s\n", str1);
printf("str2: %s\n", str2);
printf("str3: %s\n", str3);
return 0;
}
运行结果:
Input a string: This is a flower.↙
Input a string: This is a flower.↙
str1: This is a flower.
str2: This
str3: is
第一次输入的字符串被 gets() 全部读取,并存入 str1 中。第二次输入的字符串,前半部分被第一个 scanf() 读取并存入 str2 中,后半部分被第二个 scanf() 读取并存入 str3 中。
注意,scanf() 在读取数据时需要的是数据的地址,这一点是恒定不变的,所以对于 int、char、float 等类型的变量都要在前边添加&
以获取它们的地址。但是在本段代码中,我们只给出了字符数组的名字,却没有在前边添加&
,这是为什么呢?因为数组名字就是数组的首地址,所以再添加&
就是多此一举,会导致错误了。
3、字符串处理函数
C语言提供了丰富的字符串处理函数,可以对字符串进行输入、输出、合并、修改、比较、转换、复制、搜索等操作,使用这些现成的函数可以大大减轻我们的编程负担。
用于输入输出的字符串函数,例如printf
、puts
、scanf
、gets
等,使用时要包含头文件stdio.h
,而使用其它字符串函数要包含头文件string.h
。
本节只能讲解几个常用的。
1)字符串连接函数 strcat()
strcat 是 string catenate 的缩写,意思是把两个字符串拼接在一起,语法格式为:
strcat(arrayName1, arrayName2);
arrayName1、arrayName2 为需要拼接的字符串。
strcat() 将把 arrayName2 连接到 arrayName1 后面,并删除原来 arrayName1 最后的结束标志'\0'
。这意味着,arrayName1 必须足够长,要能够同时容纳 arrayName1 和 arrayName2,否则会越界(超出范围)。
strcat() 的返回值为 arrayName1 的地址。
下面是一个简单的演示:
#include <stdio.h>
#include <string.h>
int main(){
char str1[100]="The URL is ";
char str2[60];
printf("Input a URL: ");
gets(str2);
strcat(str1, str2);
puts(str1);
return 0;
}
2)字符串复制函数 strcpy()
strcpy 是 string copy 的缩写,意思是字符串复制,也即将字符串从一个地方复制到另外一个地方,语法格式为:
strcpy(arrayName1, arrayName2);
strcpy() 会把 arrayName2 中的字符串拷贝到 arrayName1 中,字符串结束标志'\0'
也一同拷贝。请看下面的例子:
#include <stdio.h>
#include <string.h>
int main(){
char str1[50] = "《C语言趣味编程》";
char str2[50] = "http://c.biancheng.net/cpp/u/jiaocheng/";
strcpy(str1, str2);
printf("str1: %s\n", str1);
return 0;
}
你会看到,将 str2 复制到 str1 后,str1 中原来的内容就被覆盖了。
另外,strcpy() 要求 arrayName1 要有足够的长度,否则不能全部装入所拷贝的字符串。
3)字符串比较函数 strcmp()
strcmp 是 string compare 的缩写,意思是字符串比较,语法格式为:
strcmp(arrayName1, arrayName2);
arrayName1 和 arrayName2 是需要比较的两个字符串。
字符本身没有大小之分,strcmp() 以各个字符对应的 ASCII 码值进行比较。strcmp() 从两个字符串的第 0 个字符开始比较,如果它们相等,就继续比较下一个字符,直到遇见不同的字符,或者到字符串的末尾。
返回值: 若 arrayName1 和 arrayName2 相同,则返回0;若 arrayName1 大于 arrayName2,则返回大于 0 的值;若 arrayName1 小于 arrayName2,则返回小于0 的值。
对4组字符串进行比较:
#include <stdio.h>
#include <string.h>
int main(){
char a[] = "aBcDeF";
char b[] = "AbCdEf";
char c[] = "aacdef";
char d[] = "aBcDeF";
printf("a比较b: %d\n", strcmp(a, b));
printf("a比较c: %d\n", strcmp(a, c));
printf("a比较d: %d\n", strcmp(a, d));
return 0;
}
运行结果:
a 比较 b: 32
a 比较 c: -31
a 比较 d: 0
三、二维数组
在实际问题中有很多数据是二维的或多维的,因此C语言允许构造多维数组。多维数组元素有多个下标,以确定它在数组中的位置。本节只介绍二维数组,多维数组可由二维数组类推而得到。
1、二维数组的定义
二维数组定义的一般形式是:
dataType arrayName[length1][length2];
其中,dataType 为数据类型,arrayName 为数组名,length1 为第一维下标的长度,length2 为第二维下标的长度。
我们可以将二维数组看做一个 Excel 表格,有行有列,length1 表示行数,length2 表示列数,要在二维数组中定位某个元素,必须同时指明行和列。例如:
int a[3][4];
定义了一个 3 行 4 列的二维数组,共有 3×4=12 个元素,数组名为 a,即:
a[0][0], a[0][1], a[0][2], a[0][3]
a[1][0], a[1][1], a[1][2], a[1][3]
a[2][0], a[2][1], a[2][2], a[2][3]
如果想表示第 2 行第 1 列的元素,应该写作 a[2][1]
。
也可以将二维数组看成一个坐标系,有 x 轴和 y 轴,要想在一个平面中确定一个点,必须同时知道 x 轴和 y 轴。
二维数组在概念上是二维的,但在内存中是连续存放的;换句话说,二维数组的各个元素是相互挨着的,彼此之间没有缝隙。那么,如何在线性内存中存放二维数组呢?有两种方式:
- 一种是按行排列, 即放完一行之后再放入第二行;
- 另一种是按列排列, 即放完一列之后再放入第二列。
在C语言中,二维数组是按行排列的。也就是先存放 a[0] 行,再存放 a[1] 行,最后存放 a[2] 行;每行中的 4 个元素也是依次存放。数组 a 为 int 类型,每个元素占用 4 个字节,整个数组共占用 4×(3×4)=48 个字节。
你可以这样认为,二维数组是由多个长度相同的一维数组构成的。
2、二维数组的初始化(赋值)
二维数组的初始化可以按行分段赋值,也可按行连续赋值。
例如,对于数组 a[5][3]
,按行分段赋值应该写作:
int a[5][3]={ {80,75,92}, {61,65,71}, {59,63,70}, {85,87,90}, {76,77,85} };
按行连续赋值应该写作:
int a[5][3]={80, 75, 92, 61, 65, 71, 59, 63, 70, 85, 87, 90, 76, 77, 85};
这两种赋初值的结果是完全相同的。
3、对于二维数组的初始化还要注意以下几点:
- 可以只对部分元素赋值,未赋值的元素自动取“零”值。例如:
int a[3][3] = {{1}, {2}, {3}};
是对每一行的第一列元素赋值,未赋值的元素的值为 0。赋值后各元素的值为:
1 0 0
2 0 0
3 0 0
再如:
int a[3][3] = {{0,1}, {0,0,2}, {3}};
赋值后各元素的值为:
0 1 0
0 0 2
3 0 0
- 如果对全部元素赋值,那么第一维的长度可以不给出。例如:
int a[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
可以写为:
int a[][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
4、二维数组可以看作是由一维数组嵌套而成
如果一个数组的每个元素又是一个数组,那么它就是二维数组。当然,前提是各个元素的类型必须相同。根据这样的分析,一个二维数组也可以分解为多个一维数组,C语言允许这种分解。
例如,二维数组a[3][4]
可分解为三个一维数组,它们的数组名分别为 a[0]、a[1]、a[2]。
这三个一维数组可以直接拿来使用。这三个一维数组都有 4 个元素,比如,一维数组 a[0] 的元素为 a[0][0]
、a[0][1]
、a[0][2]
、a[0][3]
。
【实例】 一个学习小组有 5 个人,每个人有 3 门课程的考试成绩,求该小组各科的平均分和总平均分。
– | Math | C | English |
---|---|---|---|
张三 | 80 | 75 | 92 |
61 | 65 | 71 | |
59 | 63 | 70 | |
85 | 87 | 90 | |
76 | 77 | 85 |
对于该题目,可以定义一个二维数组 a[5][3]
存放 5 个人 3 门课的成绩,定义一个一维数组 v[3] 存放各科平均分,再定义一个变量 average 存放总平均分。最终编程如下:
#include <stdio.h>
int main(){
int i, j; //二维数组下标
int sum = 0; //当前科目的总成绩
int average; //总平均分
int v[3]; //各科平均分
int a[5][3]; //用来保存每个同学各科成绩的二维数组
printf("Input score:\n");
for(i=0; i<3; i++){
for(j=0; j<5; j++){
scanf("%d", &a[j][i]); //输入每个同学的各科成绩
sum += a[j][i]; //计算当前科目的总成绩
}
v[i]=sum/5; // 当前科目的平均分
sum=0;
}
average = (v[0] + v[1] + v[2]) / 3;
printf("Math: %d\nC Languag: %d\nEnglish: %d\n", v[0], v[1], v[2]);
printf("Total: %d\n", average);
return 0;
}