上一节中,我们学习了循环结构语句的作用及使用方法。但在一般解题过程中,常常会出现需要多组数据同时考虑的情况。这是,对一组大量数据一一声明并操作,显然不具有可行性,这时候,就需要使用数组来存放一系列数据。下面,我们将了解数组的概念,数组声明与使用,以及数组内存的操作(初始化,数组复制等)。后面,我们将顺带了解字符串相关内容。
数组概念
请大家先考虑以下数学中的数列,它们使用下标来区分其中元素。数组中元素的存储方式与数列相同,数组中的每一个元素都具有自己的唯一索引(即下标)。比如,n个元素的数组,索引为0
~n-1
。通过这个索引,可以定位到该元素,并对其进行读写操作。
(本段原理内容仅供有兴趣的读者了解,不要求一定掌握)C语言中,数组的存储形式为一段连续的内存。数组声明时,系统将为这个数组分配一段连续的内存空间,大小为元素个数乘以元素数据类型,元素按照顺序存储在其中。实际上,数组本身(不加任何索引,仅数组名称)也是一个变量,它是一个指向数组的首部的指针(有关指针的内容将在后几节给出)。在调用一个数组元素时,系统通过数组下标和元素大小计算地址偏移量,再根据数组首部指针计算出数组元素的真实地址,就可以对这个地址上的数据进行操作。
数组操作
数组声明
**与其他变量相同,数组在使用前需要进行声明。**声明语法:
type name[size];
其中,
type
: 数组中元素的类型,一个数组中元素的类型只能有一个name
: 数组的名称,命名规则与变量相同size
: 数组的大小,注意:数组大小非数组最后一个元素的索引,实际索引为[0]到[size-1]
数组使用
语法示例:
int a[100];
a[0] = 1;
使用时,name[index]
就可以指定一个数组中的一个元素,并能像变量一样操作。索引可以是一个变量或者一个表达式,其他用法与单个变量完全相同,这里就不赘述。
注意,如果想要将一个数组a赋值给另一个数组b,不能直接赋值,由于数组本身是一个指针,如果这样赋值,则会使数组b指针指向a,a和b将会是同一个数组,改变数组a元素,b也会改变。如果想要进行数组按值赋值,请见下文“内存赋值”
下面用一个样例展示数组与循环语句结合的用法,a[i]
为从1加到i
的和,
int a[100];
memset(a, 0, sizeof(a));
for (int i = 1; i < 100; i++)
a[i] = a[i - 1] + i;
其中的memset
语句将在下面的数组内存操作中讲到
多维数组
某些情况下,我们可能需要一个表来存储数据,这就用到了二维数组。通过数组声明,我们可以使用二维、三维甚至多维的数组。
使用多个方括号就可以声明和使用多维数组,下面用一个样例说明:
int a[100][100];
a[0][1] = 1;
printf("%d\n", a[0][1]);
数组内存操作
对数组内存的操作需要引用头文件string.h
内存赋值 memset
新声明一个数组后,系统并不会自动清空这个位置上的原有数据,这时,不过不加初始化就读取,极有可能遇到意外情况(读取到一个无意义数据)(参考烫烫烫和屯屯屯等)。这就需要将整个数组进行初始化(一般为置零),memset
语句就提供了整块内存赋值的功能。
memset(array_name, value, size);
其中:
array_name
: 数组名称,也即数组指针value
: 要赋的值,一般为0,若不是,则在多字节变量中会遇到问题(下面会说到)size
: 要赋值的字节数,注意:是字节数而不是元素个数,字节数为元素个数和元素类型字节数的乘积
以下为示例,
int c[100];
memset(c, 0, sizeof(c));
其中,sizeof(c)
语句获取了整个c
数组的大小(已经乘过元素类型字节数)
注意:内存赋值时,是将所指定的每一字节都赋值为value
,如果value
不是0,则单字节变量不受影响,而多字节元素则会导致一个元素中的值不为value
,假设value
=1,赋值给双字节变量a[0]
,则有a[0]
=0b0000000100000001=257.
内存复制 memcpy
如果想要将一个数组的值复制给另一个数组,不能直接将两个数组指针变量进行赋值,那样只会导致地址传递,最后两个数组成为一个。需要使用下面要说的memcpy
语句,
memcpy(Dst, Src, size);
其中:
Dst
: 赋值的目标数组指针Src
: 赋值源数组指针size
: 需要复制的字节数(注意:是字节数)
下面展示一个内存复制的样例
#include <stdio.h>
#include <string.h>
int main()
{
int a[100], b[100];
for (int i = 0; i < 100; i++)
a[i] = i;
memcpy(b, a, sizeof(a));
for (int i = 0; i < 10; i++)
printf("%d\n", b[i]);
a[0] = 100;
printf("a[0] is: %d\n", a[0]);
printf("b[0] is: %d\n", b[0]);
return 0;
}
从输出结果可以看出,数组a的所有值传给了数组b,并且,数组a中值的改变不会对b造成任何影响。
字符串
实际上,字符串也是一个字符(char
)类型的数组,字符串的每一个字符按位存储在数组中。字符串有效值的最后,需要有一个值为0的元素(结束符)来标志数组的结束。所以,字符串数组所能存储的最大字符数为数组长度减一。
字符串的声明和使用
字符串的声明和使用与普通数组完全相同,不过赋值时,可以直接给它赋一个用双引号包含的常量字符串,下面的示例将使用它,
char string[100];
memset(string, 0, sizeof(string));
string = "Hello World!\n";
printf("%s", string);
也可以使用一个字符串常量来对一个字符串变量进行初始化,这时候可以不指定数组长度,默认常量长度+1(结束标志)。
char string[] = "Hello";
printf("%d\n", sizeof(string));
字符串的输出和输出
字符串IO主要有两种方式:scanf/printf
和gets/puts
对于scanf/printf
格式化输入输出:
语法示例:
printf("%s\n", string);
scanf("%s", string); //没有&
printf
输出直接使用即可,转换说明为%s
scanf
转换说明也是%s
,但需要注意:- 由于数组变量就是一个指针,在
scanf
的参数位置不需要再加提领标识符&
,示例:scanf("%s", string)
scanf
仅能读取一段连续的字符串,遇到空字符串会停止,如"Hello world",只能读入"Hello"
- 由于数组变量就是一个指针,在
对于gets/puts
字符串输入输出
语法示例:
gets(string);
puts(string);
gets
能够读取一整行内容,读到换行符或者EOF
停止puts
能够输出一个字符串,最后将会附带一个\n
换行符
字符串长度函数strlen
char string[] = "Hello World";
printf("%d\n", strlen(string));
strlen
函数将返回字符串变量中真实字符串的长度(不包含结束符)。它将逐个搜索变量,直到找到一个值为0
(字符串结束符)的位置,停止计数并返回。