数组
在程序设计中,为了处理方便,把具有相同类型的若干变量按有序的形式组织起来。这些按序排列的同类数据元素的集合称为数组。
在C语言中,数组属于构造数据类型。一个数组可以分解为多个数组元素,这些数组元素可以是基本数据类型或是构造类型。因此按数组元素的类型不同,数组又可分为数值数组、字符数组 、指针数组、结构数组等各种类别。
一维数组
数组可以看作是一行连续的数据,只有一个下标,称为一维数组。
一维数组的定义
我们知道,要想把数据放入内存,必须先要分配内存空间。放入4个整数,就得分配4个int类型的内存空间:
int a[4];
这样,就在内存中分配了4个int类型的内存空间,共 4×4=16 个字节,并为它们起了一个名字,叫a。
我们把这样的一组数据的集合称为数组(Array),所包含的每一个数据叫做数组元素(Element),所包含的数据的个数称为数组长度(Length),数组中的每个元素都有一个序号,这个序号从0开始,而不是从我们熟悉的1开始,称为下标(Index)。
例如int a[4];就定义了一个长度为4的整型数组,名字是a。a[index]使用数组元素时,指明下标即可,a为数组名称,index 为下标。 接下来我们就把第一行的4个整数放入数组:
a[0]=2021; a[1]=803; a[2]=13; a[3]=43;
这里的0、1、2、3就是数组下标,a[0]、a[1]、a[2]、a[3] 就是数组元素。
在学习过程中,我们经常会使用循环结构将数据放入数组中(也就是为数组元素逐个赋值),然后再使用循环结构输出(也就是依次读取数组元素的值),下面我们就来演示一下如何将 1~10 这十个数字放入数组中:
#include <stdio.h> int main(){ int nums[10]; int i; //将1~10放入数组中 for(i=0; i<10; i++){ nums[i] = (i+1); } //依次输出数组元素 for(i=0; i<10; i++){ printf("%d ", nums[i]); } return 0; }
运行结果
1 2 3 4 5 6 7 8 9 10
变量 i 既是数组下标,也是循环条件;将数组下标作为循环条件,达到最后一个元素时就结束循环。数组 nums 的最大下标是 9,也就是不能超过 10,所以我们规定循环的条件是 i<10,一旦 i 达到 10 就得结束循环。
最后我们来总结一下数组的定义方式:
dataType arrayName[length];
dataType 为数据类型,arrayName 为数组名称,length 为数组长度。例如:
float m[12]; //定义一个长度为 12 的浮点型数组 char ch[9]; //定义一个长度为 9 的字符型数组
需要注意的是:
- 1.数组中每个元素的数据类型必须相同,对于int a[4];,每个元素都必须为 int。
- 2.数组长度 length 最好是整数或者常量表达式,例如 10、20+4 等,这样在所有编译器下都能运行通过;如果 length 中包含了变量,例如 n、4+m 等,在某些编译器下就会报错。
- 3.访问数组元素时,下标的取值范围为 0 ≤ index < length,过大或过小都会越界,导致数组溢出,发生不可预测的情况,请大家务必要引起注意。
数组内存是连续的
数组是一个整体,它的内存是连续的;也就是说,数组元素之间是相互挨着的,彼此之间没有一点点缝隙。下图演示了int a[4];在内存中的存储情形:
数组内存是连续的这一点很重要,所以我使用了一个大标题来强调。连续的内存为指针操作(通过指针来访问数组元素)和内存处理(整块内存的复制、写入等)提供了便利,这使得数组可以作为缓存(临时存储数据的一块内存)使用。大家暂时可能不理解这句话是什么意思,等后边学了指针和内存自然就明白了。
数组的初始化
上面的代码是先定义数组再给数组赋值,我们也可以在定义数组的同时赋值,例如:
int a[4] = {20, 345, 700, 22};
数组元素的值由{}包围,各个值之间以,分隔。
对于数组的初始化需要注意以下几点:
- 1.可以只给部分元素赋值。当{}中值的个数少于元素个数时,只给前面部分元素赋值。例如:
int a[10]={12, 19, 22 , 993, 344};
表示只给 a[0]~a[4] 5个元素赋值,而后面 5 个元素自动初始化为 0。
当赋值的元素少于数组总体元素的时候,剩余的元素自动初始化为 0:
1、对于short、int、long,就是整数 0;
2、对于char,就是字符 '\0';
3、 对于float、double,就是小数 0.0。
我们可以通过下面的形式将数组的所有元素初始化为 0:
int nums[10] = {0}; char str[10] = {0}; float scores[10] = {0.0};
由于剩余的元素会自动初始化为 0,所以只需要给第 0 个元素赋值为 0 即可。
2.只能给元素逐个赋值,不能给数组整体赋值。例如给 10 个元素全部赋值为 1,只能写作:
int a[10] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
而不能写成:
int a[10] = 1;
3.如给全部元素赋值,那么在定义数组时可以不给出数组长度。例如:
int a[] = {1, 2, 3, 4, 5};
相当于
int a[5] = {1, 2, 3, 4, 5};
二维数组
在实际问题中有很多数据是二维的或多维的,因此C语言允许构造多维数组。多维数组元素有多个下标,以确定它在数组中的位置。多维数组可由二维数组类推而得到。
二维数组的定义
二维数组定义的一般形式是:
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]。
二维数组在概念上是二维的,但在内存中是连续存放的;换句话说,二维数组的各个元素是相互挨着的,彼此之间没有缝隙。那么,如何在线性内存中存放二维数组呢?有两种方式:
- 一种是按行排列, 即放完一行之后再放入第二行;
- 另一种是按列排列, 即放完一列之后再放入第二列。
在C语言中,二维数组是按行排列的。 也就是先存放 a[0] 行,再存放 a[1] 行,最后存放 a[2] 行;每行中的 4 个元素也是依次存放。数组 a 为 int 类型,每个元素占用 4 个字节,整个数组共占用 4×(3×4)=48 个字节。
你可以这样认为,二维数组是由多个长度相同的一维数组构成的。
二维数组的初始化
二维数组的初始化可以按行分段赋值,也可按行连续赋值。
例如,对于数组 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};
这两种赋初值的结果是完全相同的。
对于二维数组的初始化还要注意以下几点:
- 1.可以只对部分元素赋值,未赋值的元素自动取零值。例如:
int a[3][3] = {{0,1}, {0,0,2}, {3}};
是对每一行的第一列元素赋值,未赋值的元素的值为 0。赋值后各元素的值为:
0 1 0 0 0 2 3 0 0
- 2.如果对全部元素赋值,那么第一维的长度可以不给出。例如:
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};
- 3.二维数组可以看作是由一维数组嵌套而成的;如果一个数组的每个元素又是一个数组,那么它就是二维数组。当然,前提是各个元素的类型必须相同。根据这样的分析,一个二维数组也可以分解为多个一维数组,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]。
数组小结
- 数组用来保存大量相同类型的数据。
- 数组是相同数据类型的变量的排列。
- 构成数组的小格子的个数称为数组元素数。
- 二维数组多用于存储多行多列的二维表格数据。
- 二维数组如同多层楼房,每层有相同数量的房间。
- 数组的数据类型就是它里面存储的数据的数据类型。
- 二维数组的行编号和列编号都是从 0 开始的序列号。
- 数组和变量一样,在使用前必须先定义(声明数组)。
- 数组元素用元素编号(从 0 开始的序列号)进行管理。
- 二维数组数组元素的引用格式:数组名 [行编号] [列编号]。
- 元素编号(下标)可以标识指定的数组元素:数组名 [下标]。
- 二维数组用行编号和列编号两个下标来指定和引用数组元素。
字符数组和字符串
用来存放字符的数组称为字符数组,例如:
char a[3]; //一维字符数组 char b[3][4]; //二维字符数组 char c[20]={'s', 'h', 'u', 'a', 'i', 'c', 'i'}; // 给部分数组元素赋值 char d[]={'s', 'h', 'u', 'a', 'i', 'c', 'i'}; //对全体元素赋值时可以省去长度
字符数组实际上是一系列字符的集合,也就是字符串(String)。在C语言中,没有专门的字符串变量,没有string类型,通常就用一个字符数组来存放一个字符串。
C语言规定,可以将字符串直接赋值给字符数组,例如:
char str[30] = {"shuaici.blog.csdn.net"}; char str[30] = "shuaici.blog.csdn.net"; //这种形式更加简洁,实际开发中常用
数组第 0 个元素为's',第 1 个元素为'h',第 2 个元素为'u',后面的元素以此类推。
为了方便,你也可以不指定数组长度,从而写作:
char str[] = {"shuaici.blog.csdn.net"}; char str[] = "shuaici.blog.csdn.net"; //这种形式更加简洁,实际开发中常用
给字符数组赋值时,我们通常使用这种写法,将字符串一次性地赋值(可以指明数组长度,也可以不指明),而不是一个字符一个字符地赋值,那样做太麻烦了。
这里需要留意一个坑,字符数组只有在定义时才能将整个字符串一次性地赋值给它,一旦定义完了,就只能一个字符一个字符地赋值了。请看下面的例子:
char str[7]; str = "abc123"; //错误 //正确 str[0] = 'a'; str[1] = 'b'; str[2] = 'c'; str[3] = '1'; str[4] = '2'; str[5] = '3';
字符串结束标志(划重点)
字符串是一系列连续的字符的组合,要想在内存中定位一个字符串,除了要知道它的开头,还要知道它的结尾。找到字符串的开头很容易,知道它的名字(字符数组名或者字符串名)就可以;然而,如何找到字符串的结尾呢?C语言的解决方案有点奇妙,或者说有点奇葩。
在C语言中,字符串总是以 '\0' 作为结尾,所以 '\0' 也被称为字符串结束标志,或者字符串结束符。
'\0'是 ASCII 码表中的第 0 个字符,英文称为 NUL,中文称为空字符。该字符既不能显示,也没有控制功能,输出该字符不会有任何效果,它在C语言中唯一的作用就是作为字符串结束标志。
C语言在处理字符串时,会从前往后逐个扫描字符,一旦遇到'\0'就认为到达了字符串的末尾,就结束处理。'\0'至关重要,没有'\0'就意味着永远也到达不了字符串的结尾。
由" "包围的字符串会自动在末尾添加'\0'。例如,"abc123"从表面看起来只包含了 6 个字符,其实不然,C语言会在最后隐式地添加一个'\0',这个过程是在后台默默地进行的,所以我们感受不到。
需要注意的是,逐个字符地给数组赋值并不会自动添加'\0',例如:
char str[] = {'a', 'b', 'c'};
数组 str 的长度为 3,而不是 4,因为最后没有'\0'。
当用字符数组存储字符串时,要特别注意'\0',要为'\0'留个位置;这意味着,字符数组的长度至少要比字符串的长度大 1。请看下面的例子:
char str[7] = "abc123";
abc123看起来只包含了 6 个字符,我们却将 str 的长度定义为 7,就是为了能够容纳最后的'\0'。如果将 str 的长度定义为 6,它就无法容纳'\0'了。
当字符串长度大于数组长度时,有些较老或者不严格的编译器并不会报错,甚至连警告都没有,这就为以后的错误埋下了伏笔,读者自己要多多注意。
字符串长度
所谓字符串长度,就是字符串包含了多少个字符(不包括最后的结束符'\0')。例如"abc"的长度是 3,而不是 4。
在C语言中,我们使用string.h头文件中的 strlen() 函数来求字符串的长度,它的用法为:
length strlen(strname);
strname 是字符串的名字,或者字符数组的名字;length 是使用 strlen() 后得到的字符串长度,是一个整数。
实例
#include <stdio.h> #include <string.h> //记得引入该头文件 int main(){ char str[] = "https://shuaici.blog.csdn.net/"; long len = strlen(str); printf("Length: %ld.\n", len); return 0; }
运行结果:
Length: 30.
数组元素的查询
在实际开发中,经常需要查询数组中的元素。不幸的是,C语言标准库没有提供与数组查询相关的函数,所以我们只能自己编写代码。
对无序数组的查询
所谓无序数组,就是数组元素的排列没有规律。无序数组元素查询的思路也很简单,就是用循环遍历数组中的每个元素,把要查询的值挨个比较一遍。请看下面的代码:
#include <stdio.h> int main(){ int nums[10] = {15, 10, 4, 22, 109, 522, 803, 44, 68, 999}; int i, num, thisindex = -1; num = 522; for(i=0; i<10; i++){ if(nums[i] == num){ thisindex = i; break; } } if(thisindex < 0){ printf("数组不存在元素 %d .\n", num); }else{ printf("数组存在元素 %d , 所在位置 %d.\n", num, thisindex); } return 0; }
运行结果
数组存在元素 522 , 所在位置 5.
注意:数组下标的取值范围是非负数,当 thisindex >= 0 时,该数字在数组中,当 thisindex < 0 时,该数字不在数组中,所以在定义 thisindex 变量时,必须将其初始化为一个负数。
对有序数组的查询
查询无序数组需要遍历数组中的所有元素,而查询有序数组只需要遍历其中一部分元素。例如有一个长度为 10 的整型数组,它所包含的元素按照从小到大的顺序(升序)排列,假设比较到第 4 个元素时发现它的值大于输入的数字,那么剩下的 5 个元素就没必要再比较了,肯定也大于输入的数字,这样就减少了循环的次数,提高了执行效率。
请看下面的代码:
#include <stdio.h> int main(){ int nums[10] = {4, 10, 15, 22, 44, 68, 109, 522, 803, 999}; int i, num, thisindex = -1; num = 522; for(i=0; i<10; i++){ if(nums[i] == num){ thisindex = i; break; }else if(nums[i] > num){ break; } } if(thisindex < 0){ printf("数组不存在元素 %d .\n", num); }else{ printf("数组存在元素 %d , 所在位置 %d.\n", num, thisindex); } return 0; }
与前面的代码相比,这段代码的改动很小,只增加了一个判断语句。因为数组元素是升序排列的,所以当 nums[i] > num 时,i 后边的元素也都大于 num 了,num 肯定不在数组中了,就没有必要再继续比较了,终止循环即可。
字符串处理函数
C语言提供了丰富的字符串处理函数,可以对字符串进行输入、输出、合并、修改、比较、转换、复制、搜索等操作,使用这些现成的函数可以大大减轻我们的编程负担。
string.h是一个专门用来处理字符串的头文件,它包含了很多字符串处理函数。
字符串连接函数 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]="https://shuaici.blog.csdn.net/"; strcat(str1, str2); puts(str1); return 0; }
运行结果
The URL is https://shuaici.blog.csdn.net/
字符串复制函数 strcpy()
strcpy 是 string copy 的缩写,意思是字符串复制,也即将字符串从一个地方复制到另外一个地方,语法格式为:
strcpy(arrayName1, arrayName2);
strcpy() 会把 arrayName2 中的字符串拷贝到 arrayName1 中,字符串结束标志'\0'也一同拷贝。
请看下面的例子:
#include <stdio.h> #include <string.h> int main(){ char str1[100]="The URL is "; char str2[60]="https://shuaici.blog.csdn.net/"; strcpy(str1, str2); printf("str1: %s\n", str1); return 0; }
运行结果
str1: https://shuaici.blog.csdn.net/
将 str2 复制到 str1 后,str1 中原来的内容就被覆盖了。 注意: strcat() 要求 arrayName1 要有足够的长度,否则不能全部装入所拷贝的字符串。
字符串比较函数 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 VS b: %d\n", strcmp(a, b)); printf("a VS c: %d\n", strcmp(a, c)); printf("a VS d: %d\n", strcmp(a, d)); return 0; }
运行结果
a VS b: 32 a VS c: -31 a VS d: 0
如有错误,烦请斧正