[一篇读懂]C语言四讲:一维数组与字符数组
1. 一维数组
1 数组的定义
- 人难以同时记住多个元素,可以借助数组,通过一个符号来访问多个元素。
如存放鞋子,将衣柜的最下面一层分为10个连续的格子,拿取最下面一层第三个格子的鞋子就很方便。
- 数据特点:(1)具有相同的数据类型。(2)使用过程中需要保留原始数据
如学生的成绩,用于排名时需要原始数据。
- 一维数组的定义格式为:
//类型说明符 - 数组名 - [常量表达式]
int a[10];
- 声明数组时遵循以下规则:
- 遵循标识符命名规则,数组名命名规则与变量名相同——由数字、字母和下划线组成,且只能由数字和下划线开头。
- 定义数组时需要制定数组中的元素个数,由方括号中的常量表达式表示,即数组长度。
- 常量表达式中可以包含常量和符号常量,在旧的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 一维数组在内存中的存储
(1)定义一个大小为a[10]的数组:
int a[10] = { 0,1,2,3,4,5,6,7,8,9 };
- 数组元素的引用方式是“数组名[下标]”,其可以访问的值为a[0]到a[9]。
注意,其中没有元素a[10],因为数组元素是从0开始编号的。
- 每个元素都是整型元素,占用4字节。
- 从低地址向高地址存放
- 初始化不能写成:
int a[10];
a[10]={ 0,1,2,3,4,5,6,7,8,9 };//Error
(2)可以只给一部分元素赋值。例如,
int a[10] = { 0,1,2,3,4 };
定义a数组有10个元素,但花括号内只提供5个初值,这表示只给前5个元素赋初值,后5个元素的值为0。
(3)如果要使一个数组中全部元素的值为0,那么可以写为
int a[10] = { 0 };//后面没有赋值的默认都是0
最好不要写成
int a[10] = { 0,0,0,0,0,0,0,0,0,0 };//浪费时间,显得学艺不精
(4)在对全部数组元素赋初值时,由于数据的个数已经确定,因此可以不指定数组的长度。
例如:
int a[] = { 1,2,3,4,5 };//初试中不建议使用,不便于阅读
这种写法不便于 人类 阅读数组长度。
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发生改变 - i=7
return 0;
}
i i i在程序中没有变动,但是 i i i的值发生了改变。
这是因为操作a[6]时,将会把变量
i
i
i放置的内存位置覆盖。
所以此处
i
i
i并没有赋值,但是值却变化了。
通常在for循环中没有控制好边界,导致访问越界。
编译器并不检查程序对数组下标的引用是否在数组的合法范围内。
2 数组的传递
重要
下列代码:
#include <stdio.h>
void print(int a[])//[]内最好不写长度 - 没有意义
{
int i;
for (i = 0; i < sizeof(a) / sizeof(int); i++)
{
printf("%d\n", a[i]);
}
}
int main()
{
int a[6] = { 1,2,3,4,5,6 };
print(a);
return 0;
}
运行结果:
发现结果只输出了1、2。
这是因为一维数组在传递时,数组传递到子函数后,子函数的形参接收到的是数组的起始地址。
因此不能够把数组的长度传递给子函数。
注意,在子函数a[]的方括号中填写任何数字都是没有意义的,最好
不写
。
所以我们通过额外定义一个变量length来传递数组中的元素个数。
#include <stdio.h>
void print(int a[], int length)
{
int i;
for (i = 0; i < length; i++)
{
printf("%d\n", a[i]);
}
}
int main()
{
int a[6] = { 1,2,3,4,5,6 };
print(a, 6);
return 0;
}
可以正常输出数组中的所有元素了。
另外,在子函数中修改数组元素,当函数执行结束时,数组中的元素也会得到修改。
#include <stdio.h>
void print(int a[], int length)
{
int i;
for (i = 0; i < length; i++)
{
printf("%3d\n", a[i]);
}
a[3] = 20;//在子函数中修改a[3]
printf("\n");
}
int main()
{
int a[6] = { 1,2,3,4,5,6 };
print(a, 6);
printf("a[3] = %d\n", a[3]);//此处a[3]已经得到修改
return 0;
}
运行结果:
3. 字符数组与scanf读取字符串
对字符数组进行额外的讲解,其与整形数组和浮点型数组不一样的地方。
1 字符数组初始化及传递
- 字符数组的定义方法与前面介绍的数组类似:
char c[10];
- 字符数组初始化的方式:
char c[10] = "Iamhappy";
- 字符串结束标志为’/0’,所以字符数组存储的字符串长度必须比字符数组少1字节。
例如,char c[10]最长存储9个字符,剩余的1个字符用来存储’/0’
- 使用%s来输出一个字符串
printf("%s\n",c);//直接把数组名放过来
- 输出字符串乱码时,要去查看字符数组中是否存储了结束符
注意,没有结束符会输出乱码,直到找到’\0’结束。
char c[5] = { 'h','e','l','l','o' }; printf("%s\n",c);
输出乱码:
- 通过子函数模拟printf(“%s”,c)操作
#include <stdio.h>
void print(char d[])//不需要输入数组长度
{
int i = 0;
while (d[i])//当循环到结束符时,循环结束
{
printf("%c", d[i]);
i++;
}
printf("\n");
}
int main()
{
char c[5] = "how";
printf(c);
return 0;
}
当c[i]为’\0’时,其值是0,循环结束,也可以写为 c[i] != ‘\0’.
2 scanf读取字符串
- scanf读取字符串时使用%s
scanf("%s", c);//不需要取地址
注意,字符数组名c中存储了数组的起始地址,因此不需要取地址。
scanf读取字符串操作,会自动往字符数组中放结束符。
- scanf在使用%s读取字符串时,会忽略空格和回车(这一点与%d和%f类似)。
输入多个字符串,对c和d分别输入“how”和“are”(中间加一个空格):
#include <stdio.h>
int main()
{
char c[10];
char d[10];
scanf("%s%s", c, d);
printf("c = %s, d = %s\n", c,d);
return 0;
}
运行结果:
结果中没有输出空格,这是因为空格被忽略了。
- scanf怎么一次读取一行?
用gets函数实现。
4. gets与puts讲解,strlen_strcmp_strcpy讲解
1 gets函数与puts函数
- gets中放入我们字符数组的数组名即可
gets(c);
gets函数可以一次读一行,其会读取换行’\n’之前的所有内容,并将’\n’翻译为空字符’\0’。
- puts等价于printf(“%s\n”, c);
puts(c);//等价于printf("%s\n", c);
puts内放的是字符数组名。
2 str系列字符串操作函数
- strlen用于统计字符串长度
#include <stdio.h>
#include <string.h>
int main()
{
int len;
char c[20];
gets(c);
puts(c);
len = strlen(c);
printf("len = %d\n", len);
return 0;
}
运行结果:
注意,'\0’不统计在字符串长度之内
- 自己写一个mystrlen看看
int mystrlen(char c[])
{
int i = 0;
while(c[i])//找到结束符后,循环结束,从而得出字符串长度
{
i++;
}
return i;//返回字符串的长度
}
运行结果:
- strcat用于把d中的字符串拼接到c中
int main()
{
char c[20];
char d[100] = "world";
gets(c);
strcat(c, d);//把d中的字符串拼接到c中
puts(c);
return 0;
}
运行结果:
- strcpy用于把c中的字符串复制到e中
strcpy(e, c);//把c中的字符串复制到e中
puts(e);
运行结果:
- strcmp用于比较两个字符串的大小
gets(c);
printf("c?d %d\n", strcmp(c, "how"));
当输入hello时,使用strcmp比较字符串hello和字符串how的大小。
注意,strcmp比较的是两个字符串的ASCII码值。
e比o的ASCII码值小。
如果前一个字符串小于后一个字符串,返回值为负值;
如果前一个字符串大于后一个字符串,返回值为正值;
两个字符串相等时,返回值为0。
- strcat(c,d)和strcpy(e,f)中c和e的位置必须是字符组名字(空间),不可以放字符串常量。
总结
1.1
- 数组名遵循标识符命名规则。
- 数组长度由方括号中的常量表达式表示。
1.2
- 数组元素的引用方式是“数组名[下标]”。
- 初始化不能写成:
int a[10];
a[10]={ 0,1,2,3,4,5,6,7,8,9 };//Error
- 可以只给一部分元素赋值。
- 如果要使一个数组中全部元素的值为0,那么可以写为:
int a[10] = { 0 };//后面没有赋值的默认都是0
2.1
- 数组下标取值越界会覆盖其他变量。
2.2
- 数组长度无法传递到子函数中。
- 子函数中可以改变数组中的元素。
3.1
- 字符串结束标志为’/0’,所以字符数组存储的字符串长度必须比字符数组少1字节。
- 使用%s来输出一个字符串
- 输出字符串乱码时,要去查看字符数组中是否存储了结束符
3.2
- scanf在使用%s读取字符串时,会忽略空格和回车(这一点与%d和%f类似)。
4.1
- gets中放入我们字符数组的数组名即可
- puts等价于printf(“%s\n”, c);
4.2
- strlen用于统计字符串长度
- strcat用于把d中的字符串拼接到c中
- strcpy用于把c中的字符串复制到e中
- strcmp用于比较两个字符串的大小
- strcat(c,d)和strcpy(e,f)中c和e的位置必须是字符组名字(空间),不可以放字符串常量。