01.4.1 一维数组
1 数组的定义
为了存放鞋子,假设你把衣柜最下面的一层分成了 10个连续的格子。此时,让他人帮你拿鞋子就会很方便,例如你可直接告诉他拿衣柜最下面一层第三个格子中的鞋子。同样假设现在我们有 10 个整数存储在内存中,为方便存取,我们可以借助C语言提供的数组,通过一个符号来访问多个元素。
某班学生的学习成绩、一行文字、一个矩阵等数据的特点如下:
(1)具有相同的数据类型。
(2)使用过程中需要保留原始数据。
C语言为了方便操作这些数据,提供了一种构造数据类型--数组。所谓数组,是指一组具有相同数据类型的数据的有序集合。
一维数组的定义格式为
类型说明符 数组名[常量表达式];
例如
int a[10]; //定义一个整型数组,数组名为 a,它有 10 个元素
声明数组时要遵循以下规则:
(1)数组名的命名规则和变量名的相同,即遵循标识符命名规则。
(2)在定义数组时,需要指定数组中元素的个数,方括号中的常量表达式用来表示元素的个数,即数组长度。
(3)常量表达式中可以包含常量和符号常量,但不能包含变量,也就是说,C语言不允许对数组的大小做动态定义,即数组的大小不依赖于程序运行过程中变量的值。
以下是错误的声明示例(最新的C标准支持,但是最好不要这么写):
int n;
scanf("%d",&n); //在程序中临时输入数组的大小
int a[n];
数组声明的其他常见错误:
(1)float a[0]; // 数组大小为0没有意义
(2)int b(2)(3); //不能使用圆括号
(3)int k = 3, a[k]; //不能用变量说明数组大小
2 一维数组在内存中的存储
语句 int mark[100];定义的一维数组 mark 在内存中的存放情况如下图所示,每个元素都是整型元素,占用4字节,数组元素的引用方式是“数组名[下标]”,所以访问数组 mark 中的元素的方式是 mark[0],mark[1],...,mark[99]。注意,没有元素 mark[100],因为数组元素是从0开
始编号的。
一维数组的初始化方法:
(1)在定义数组时对数组元素赋初值。例如
int a[10] = {1,2,3,4,5,6,7,8,9,10};
不能写成
int a[10];
a[10] = {1,2,3,4,5,6,7,8,9,10};
(2)可以只给一部分元素赋值。例如
int a[10] = {1,2,3,4,5};
定义a数组有10个元素,但花括号内只提供只提供5个初值,这表示只给前5个元素赋初值,后5个元素的值为0。
(3)如果要使一个数组中全部元素的值为0,那么可以写为
int a[10] = {0,0,0,0,0,0,0,0,0,0}; //不建议这么写
或
int a[10] = 10;
(4)在对全部数组元素赋初值时,由于数据的个数已经确定,因此可以不指定数组的长度。例如
int a[] = {1,2,3,4,5}; //机试可以,初试不建议
01.4.2 数组访问越界与数组传递
1 数组访问越界
下面借助一个数组的实例来掌握数组元素的赋值、访问越界。
【例】一维数组的存储及访问越界
#include <stdio.h>
//数组越界
int main() {
int a[5]={1,2,3,4,5}; //定义数组时,数组长度必须固定
int j=20;
int i=10;
a[5]=6; //访问越界
a[6]=7; //访问越界会造成数据异常
printf("i=%d\n",i); //i并没有赋值,但是值却变化了
return 0;
}
输出结果
注意:编译器并不检查程序对数组下标的引用是否在数组的合法范围内。这种不加检查的行为有好处也有坏处,好处是不需要浪费时间对有些已知正确的数组下标进行检查,坏处是这样做将无法检测出无效的下标引用。
一个良好的经验法则:如果下标值是通过那些已知正确的值计算得来的,那么就无须检查;如果下标值是由用户输入的数据产生的,那么在使用它们之前就必须进行检查,以确保它们位于有效范围内。
2 数组传递
#include <stdio.h>
//子函数把某一个常用功能,封装起来的作用
//数组名传递到子函数后,子函数的形参接收到是数组的起始地址,因此不能把数组的长度传递给子函数
void print(int a[],int length)
{
int i;
for(i=0;i<length;i++)
{
printf("%3d",a[i]);
}
a[3]=20;
printf("\n");
}
//main函数就是主函数
int main() {
int a[5]={1,2,3,4,5};
print(a,5); //数组在传递给子函数时,它的长度传递不过去
printf("a[3]=%d\n",a[3]);
return 0;
}
输出结果
一维数组在传递时,其长度是传递不过去的,所以我们通过 len来传递数组中的元素个数。实际数组名中存储的是数组的首地址,在调用函数传递时,是将数组的首地址给了变量b,在b[]的方括号中填写任何数字都是没有意义的。这时在 print 函数内修改元素 b[4]=20,可以看到数组b的起始地址和 main 函数中数组a的起始地址相同,即二者在内存中位于同一位置,当函数执行结束时,数组a中的元素 a[4]就得到了修改。
01.4.3 字符数组与scanf读取字符串
1 字符数组初始化及传递
字符数组的定义方法与前面介绍的一维数组类似。例如
char c[10];
字符数组的初始化可以采用以下方式。
(1)对每个字符单独赋值进行初始化。例如
c[0] = 'I';
c[1] = ' ';
c[2] = 'a';
c[3] = 'm';
c[4] = ' ';
c[5] = 'h';
c[6] = 'a';
c[7] = 'p';
c[8] = 'p';
c[9] = 'y';
(2)对整个数组进行初始化。例如
char c[10] = {'I',' ','a','m',' ','h','a','p','p','y'};
但工作中一般不用以上两种初始化方式,因为字符数组一般用来存取字符串。通常采用的初始化方式是
char c[10] = "hello";
因为C语言规定字符串的结束标志为0,而系统会对字符串常量自动加一个'\0',为了保证处理方法一致,一般会人为地在字符数组中添加'\0',所以字符数组存储的字符串长度必须比字符数组少1字节。
例如,char c[10]最长存储9个字符,剩余的 1个字符用来存储'\0'。
【例】字符数组初始化及传递
#include <stdio.h>
//模拟printf("%s",c)操作
void print(char d[])
{
int i=0;
while(d[i]) //当走到结束符时,循环结束
{
printf("%c",d[i]);
i++;
}
printf("\n");
}
//如何初始化字符数组,字符串如何输出
//输出字符串乱码时,要去查看字符数组中是否存储了结束符'\0'
int main() {
char c[6]= "hello"; //使用这种方式初始化字符数组
char d[5]="how";
printf("%s\n",c); //使用%s来输出一个字符串,直接把字符数组名放到printf后面位置
print(d);
return 0;
}
输出结果
2 scanf读取字符串
【例】scanf读取字符串
#include <stdio.h>
//scanf读取字符串操作,会自动往字符数组中放结束符
int main() {
char c[10];
char d[10];
// scanf("%s",c); //字符数组名c中存储了数组的起始地址,因此不需要取地址
// printf("%s\n",c);
scanf("%s%s",c,d);
printf("c=%s,d=%s\n",c,d);
return 0;
}
输出结果
01.4.4 gets函数与puts函数
gets 函数类似于 scanf 函数,用于读取标准输入。前面我们已经知道 scanf 函数在读取字符串时遇到空格就认为读取结束,所以当输人的字符串存在空格时,我们需要使用 gets 函数进行读取。
gets 所数的格式如下:
char *gets(char *str);
gets 函数从STDIN(标准输入)读取字符并把它们加载到str(字符串)中,直到遇到换行符(\n)。如下例所示,执行后,输入"how are you",共 11 个字符,可以看到 gets 会读取空格,同时可以看到并未给数组进行初始化赋值,但是最后有'\0',这是因为 gets 遇到\n 后,不会存储\n,而是将其翻译为空字符'\0'。
puts 函数类似于 printf 函数,用于输出标准输出。
puts 函数的格式如下
int puts(char *str);
函数 puts 把 str(字符串)写人STDOU(标准输出)。puts 会将数组c中存储的"how are you"字符串打印到屏幕上,同时打印换行,相对于 printf 函数,puts 只能用于输出字符串,同时多打印一个换行符,等价于 printf(“%s\n”,c)。
#include <stdio.h>
int main() {
char c[20];
gets(c); //gets中放入我们字符数组的数组名即可
puts(c); //puts等价于printf("%s\n",c); puts内放的参数是字符数组名
return 0;
}
输出结果
01.4.5 str系列字符串操作函数(strlen-strcmp-str)(初试没那么重要,对于机试更重要一些)
str 系列字符串操作数主要包括 strlen、strcpy、strcmp、strcat 等。
strlen函数用于统计字符串长度;strcpy函数用于将某个字符串复制到字符数组中;strcmp 函数用于比较两个字符串的大小;strcat 函数用于将两个字符串连接到一起。
各个函数的具体格式如下:
#include <string.h>
size_t strlen(char *str); //统计字符串长度
char *strcpy(char *to,const char *from) //将某个字符串复制到字符数组中
int strcmp(const char *str1,const char *str2); //比较两个字符串的大小
char *strcat(char *str1, const char *str2); //将两个字符串连接到一起
对于传参类型char*,直接放入字符数组的数组名即可。
【例】str系列字符串操作函数的使用
#include <stdio.h>
#include <string.h>
int mystrlen(char c[])
{
int i=0;
while(c[i]) //找到结束符后,循环结束,从而得出字符串长度
{
i++;
}
return i;
}
int main() {
int len;
char c[20];
char d[100]="world";
char e[100];
gets(c);
puts(c);
len=strlen(c); //统计字符串的长度
printf("len=%d\n",len);
len= mystrlen(c);
printf("my len=%d\n",len);
strcat(c,d); //把d中的字符串拼接到c中
puts(c);
strcpy(e,c); //把c中的字符串复制到e中
puts(e);
//c大于“how",返回值是正值,相等是0,c小于”how",返回负值
printf("c?d=%d\n",strcmp(c,"how"));
return 0;
}
输出结果
练习题
1.int mark[100]; 我们可以做 mark[100]=3;
A.正确 B.错误
答案:B
解释:数组下标从零开始,定义int mark[100],只能 mark[0]到 mark[99]。
2.访问越界是非常危险的,因为C和 C++语言没有针对访问越界进行检测。
A.正确 B.错误
答案:A
解释:访问越界会改变其他变量的值,因此非常危险。
3.puts(c)等价于 printf("%s\n",c)
A.正确 B.错误
答案:A
解释:如果输出字符串,使用 puts 更加方便,注意 puts 只能用于输出字符串,不能输出其
他类型。
4.如果字符串没有结束符'\0',也可以使用 stren 正确统计长度。
A.正确 B.错误
答案:B
解释:strlen 是通过结束符'\0'来判断字符串长度的,如果没有结束符,无法统计字符串长度。