在 C 语言中,为了方便操作和管理数据,通常会将同一种类型的多个数据按照一定的形式和顺序组织成有序集合,这些有序数据集合被称为数组(Array)。每个数组都有一个名称,被称为数组名。数组中每个数据都有一个确定的编号,被称为数组下标(编号),通过数组名和下标就可以找到数组中指定的数据。
数组从维度上可以划分为一维数组和多维数组。在实际开发中,使用最多的是一维数组。
(一) 一维数组
一维数组是 C 语言中最简单、常用的一类数组,是一种线性数据结构。在系统中,编译器会为数组分配一段连续的内存空间用来存放数据。
1. 如何定义一维数组
一维数组定义的一般形式为:
数据类型 数组名[字面量表达式];
“数据类型”表示数组中每个元素的数据类型。“数组名”用来标识该数组。“字面量表达式”用来指定数组中元素的个数,也被称为数组长度。
例如:
int a[10];
上述程序表示,定义长度为 10 的一维数组 a,该数组中每个元素的类型都是 int。
【说明】
(1)数组名也是标识符,必须符合 C 语言标识符命名规范。
(2)数组长度可以是字面量、或字面量表达式,例如:int a[10]和 int a[5+5]都是合法的。
(3)数组长度不能是变量、变量表达式。例如 int a[n]和 int a[n+10]都是错误写法。
2. 一维数组初始化
一维数组初始化方式一般为:
数据类型 数组名[字面量表达式] = { 初始化列表 };
这种方式在定义数组时,使用初始化列表对数组进行初始化。
例如:
int a[10] = {1,2,3,4,5,6,7,8,9,10};
上述程序表示,定义 int 类型数组 a 长度为 10,并使用{ }中的数据进行数组进行初始化,{ }中各个数据之间使用英文逗号进行分割。
3. 引用一维数组元素
数组元素是数组的基本组成单元,对数组初始化本质上是对数组中各个元素进行初始化,数组中元素的个数在定义数组时确定。例如:int a[10],表示数组 a 由 10 个 int 类型数组元素组成。分别为:a[0]、a[1]、a[2]、a[3]、a[4]、a[5]、a[6]、a[7]、a[8]、a[9]。
【说明】
(1)需要注意的是,数组元素下标从 0 开始,0-9 一共是 10 个元素,不存在 a[10]元素。
(2)这 10 个数组元素可以等价看作 10 个 int 类型变量。
在 C 语言中引用数组的一般形式为:
数组名[下标]
“下标”一般为整数类型的字面量、变量、或表达式。
以 int a[5]为例:
a[0] //数组 a 第一个元素。
a[1] //数组 a 第二个元素。
a[2] //数组 a 第三个元素。
a[3] //数组 a 第四个元素。
a[4] //数组 a 的五个元素。
a[0]是数组 a 第一个元素,也被称为数组 a 首元素。
数组本质上是一片连续的内存空间,每个数组元素都对应一块独立的内存空间。
下面通过例子来了解一维数组元素的引用。
#include<stdio.h>
int main(void)
{
int a[5]={1,2,3,4,5};//定义数组并初始化
printf("%d ",a[0]);
printf("%d ",a[1]);
printf("%d ",a[2]);
printf("%d ",a[3]);
printf("%d ",a[4]);
getchar();
return 0;
}
除了引用方式比较特殊之外,数组元素完全可以当作变量使用,可以进行数学运算、赋值操作、自加、自减操作……等。
下面通过例子来进一步了解数组元素的使用。
数组元素使用方式
#include<stdio.h>
int main(void)
{
int a[3]={1,2,3};
a[0]=10;
a[1]=a[0]+1;
a[2]=a[1]+a[0];
printf("%d
",a[0]);
printf("%d
",a[1]);
printf("%d
",a[2]);
getchar();
return 0;
}
4. 一维数组其他初始化方式
(1)一维数组静态初始化
前面讲过一种数组初始化方式,定义数组时,使用初始化列表进行初始化。例如:
int a[3]={1,2,3};
这种方式被称为静态初始化,定义数组的同时完成初始化。
(2)一维数组动态初始化
除了静态初始化,C 语言还提供了另外一种初始化数组的方式,例如:
int a[3]; //定义数组,但未初始化
a[0] =1; //初始化 a[0]
a[1] =2; //初始化 a[1]
a[2] =3; //初始化 a[2]
这种方式被称为动态初始化,先定义数组,然后分别对每个元素进行初始化。
(3)一维数组部分初始化
数组部分初始化指的是只对数组一部分元素进行初始化,例如:
int a[5]={1,2,3};
在[ ]中指定数组长度为 5,但{ }中只对前 3 个元素初始化,系统会自动将剩下的元素初始化为 0。
(4) 数组一次性赋值为 0
在 C 语言中,如果想给数组全部赋值为 0,一般写法为:
int a[5]={0,0,0,0,0};
除了这种传统的写法,还可以写成:
int a[5]={0};
系统会将剩下的数组元素都赋值为 0,这种写法比较简洁。其实这种由“一维数组部分初始化”演变来的。
(5) 定义数组时,不指定数组长度
在 C 语言中,对数组进行静态初始化时,可以不指定数组长度,编译器会根据初始化列表计算出数组长度,例如:
int a[5]={1,2,3,4,5};
可以写为:
int a[ ]={1,2,3,4,5};
[ ]中虽然没有指定数组长度,但系统会根据{ }中数据的个数确定数组 a 长度为 5。
(二) 数组遍历与常见错误
1. 一维数组遍历
在 C 语言中,数组元素下标从 0 开始,依次递增 1,到数组长度减 1 停止。例如:int a[5],数组 a 有 5 个元素,元素下标依次为 0、1、2、3、4。由于数组下标是有规律的递增,因此,可以使用 for 循环对数组元素进行遍历。
下面通过例子来了解数组遍历。
#include<stdio.h>
int main(void)
{
int a[5]={1,2,3,4,5};
int i;
for(i=0;i<5;i++)
{
printf("%d ",a[i]);
}
getchar();
return 0;
}
2. sizeof 求数组长度
数组长度=数组总字节数 /单个元素字节数。
int length=sizeof(a)/sizeof(int);
也可以写成
int length=sizeof(a)/sizeof(a[0]);
sizeof(a[0])这样写的好处是:不用关心 a 数组是什么类型的。
遍历数组的时候使用 sizeof 计算数组长度,会比直接写死更好:
#include<stdio.h>
int main(void)
{
int a[5]={1,2,3,4,5};
int len = sizeif(a)/sizeof(int);
int i;
for(i=0;i< len;i++)
{
printf("%d ",a[i]);
}
getchar();
return 0;
}
3. 常见错误
错误程序:
#include<stdio.h>
int main()
{
int a[5]={1,2,3,4,5};
printf("%d
",a[5]); //错误写法
getchar();
return 0;
}
数组 a 定义时指定长度为 5,下标有效范围为 0、1、2、3、4 一共 5 个。第 5 行,a[5]元素下标为 5,表示访问数组 a 第 6 个元素,明显超过了数组 a 最大下标值,属于数组越界访问,输出结果将是未知的。
(三) 字符数组
在 C 语言中,字符串应用的非常广泛,但是却没有字符串类型。为了解决这个问题,C语言使用字符数组来存储字符串。
1. 字符串与字符串结束标志
在 C 语言中没有专门定义字符串类型,通常使用字符数组来存储字符串。由于字符串也采用字符数组进行存储,为了区分普通字符数组和字符串,C 语言规定以字符’ ’作为字符
串结束标志。例如:
char c[ ]={‘h’,’e’,’l’,’l’,’o’};
以上字符数组中没有’ ’表示是普通的字符数组。
char c[ ]= {‘h’,’e’,’l’,’l’,’o’,’ ’};
以上字符数组以字符’ ’结束表示是字符串。
分析程序中内存的分布,思考为什么需要' '作为结束标志。
除了以上初始化方式,也可以采用字符串对字符数组进行初始化,例如:
char c[ ]=“hello”;
使用字符串方式进行赋值时,系统会自动在字符串末尾添加’ ’。
通过对比可以发现,使用字符串初始化更加简洁、而且无需指定字符数组长度,系统会自动计算字符串长度并且添加’ ’。
2. ’ ’注意事项
在 C 语言中,采用’ ’作为字符串结束标志,当系统读取到’ ’时,认为字符串结束,’ ’之后的内容将不再读取。因此,通常将’ ’添加在字符串末尾,保证字符串完整输出。
不过需要注意的是,’ ’其实可以添加在字符串任意位置上,例如:
char c[ ]={'h','e',' ','l','l','o',' '};
或
char c[ ]=”he llo”;
在字符串中‘ ’可以省略单引号,直接写为 。
下面通过例子了解一下’ ’的作用。
#include<stdio.h>
int main()
{
char chs1[ ]={'h','e',' ','l','l','o',' '};
char chs2[ ]="he llo";
printf("%s
",chs1);
printf("%s
",chs2);
getchar();
return 0;
}
//输出结果
//he
//he
以%s 格式分别输出字符串,可以看到只输出了”he”。这是因为当系统读取到’e’后面的’ ’时,就认为字符串结束了,后面的内容不再读取,因此只输出”he”。
像 printf 等函数处理字符串的时候通常都是“不见 不死心”,因此使用字符串的时候一定不要忘了结尾的 。
下面的代码:
#include<stdio.h>
int main()
{
char c1[ ]={'a','b'};
printf("%s",c1);
getchar();
return 0;
}
可能就会输出:
因为 c1 没有以' '结尾,printf 就一直读,读到了其他的内存空间。
3. sizeof
由于字符串底层采用字符数组进行存储,因此计算字符串长度,等价于计算字符串转化为字符数组后字符数组的长度,例如:
char c[ ]="hello";
上述代码,经过编译后,会转化为以下字符数组形式:
char c[ ]={'h','e','l','l','o',' '};
前面讲过数组长度计算方式对于计算字符串长度同样适用,例如:
int length=sizoef(c)/sizeof(c[0]);
下面通过例子来了解一下。
#include<stdio.h>
int main()
{
char c1[ ]={'h','e','l','l','o',' '};
char c2[ ]="hello";
int length1=sizeof(c1)/sizeof(c1[0]);
int length2=sizeof(c2)/sizeof(c2[0]);
printf("%d
",length1);
printf("%d
",length2);
getchar();
return 0;
}
/* 运行结果
6
6
*/
4. strlen
在 C 语言中,字符串有效长度指的是字符串中’ ’之前的字符个数,不包括’ ’。例如:
char c1[ ]={'h','e','l','l','o',' '};
c1 字符串有效长度为 5,不包括最后的’ ’。
char c2[ ]="hello";
c2 字符串有效长度为 5,不包括系统自动添加的’ ’。
在实际开发中,为了快速计算字符串有效长度,可以使用 C 语言标准库提供的 strlen 函数。
//函数原型:
int strlen(char str[ ]);
//头文件(解释什么是 include):
#include<string.h>
//参数列表:
str:被计算的字符串。
//功能:
计算字符串有效长度。
//返回值:
返回字符串有效长度。
#include<stdio.h>
#include<string.h>
int main()
{
char c1[ ]={'h','e','l','l','o',' '};
char c2[ ]="hello";
int length1=strlen(c1);
int length2=strlen(c2);
printf("%d
",length1);
printf("%d
",length2);
getchar();
return 0;
}
/* 运行结果
5
5
*/
需要注意的是,strlen 函数内部只会计算字符串中’ ’之前的字符个数,’ ’及之后的字符将被忽略。例如:
char c1[ ]="he llo";
c1 有效字符串长度为 2,只包含 之前的字符个数。
下面通过例子来了解一下
#include<stdio.h>
#include<string.h>
int main()
{
char c1[ ]="he llo";
int len=strlen(c1);
printf("%d
",len);
getchar();
return 0;
}
/* 运行结果
2
*/
5. 中文字符串长度
在程序的默认设置中,1 个中文字符占 2 个字节。
char c[ ]=”与鹿逐秋”;
c 是中文字符串,有效长度为 4,4 个中文字符占 8 个字节,再加上’ ’占 1 个字节,c在内存中一共占 9 个字节。
#include
int main()
{
char c1[ ]="与鹿逐秋";
int size1=sizeof(c1); //计算 c1 总字节数
printf("size1=%d
",size1);
getchar();
return 0;
}
6. 字符串元素遍历
在 C 语言中,字符串本质上就是以’ ’结尾的字符数组。同理,字符数组也可以使用 for循环进行遍历。
由于字符’ ’是控制字符,无法在控制台中显示。但是为了让读者感受到字符串末尾确实有’ ’的存在,以下代码中,分别使用%d、%c 格式输出数组元素
示例代码如下:
#include<stdio.h>
int main()
{
char c[ ]="hello";
int i;
int length=sizeof(c)/sizeof(c[0]); //计算字符串长度
for (i=0;i< length;i++)
{
printf("%c ",c[i]);
printf("%d
",c[i]); //输出字符 ASCII 码
}
getchar();
return 0;
}
运行结果如下:
7. char* 方式引用字符串
在 C 语言中,字符串存储的方式只有一种,采用字符数组格式进行存储。但是引用的方式却有两种,除了前面介绍的字符数组方式,还可以定义 char*类型的变量进行引用。例如:
char* string=”rupeng”;
以上代码表示,定义 char*类型变量 string,并使用字符串进行初始化。这涉及到后续讲的“指针”的问题。
#include<stdio.h>
int main()
{
char* string="hello";
printf("%s",string);
getchar();
return 0;
}
但是注意用 char*方式引用字符串的时候不能用 sizeof 算字符串的长度。
(四) 字符串相关函数
1. atoi 字符串转整型函数
函数原型:
int atoi (const char * str);
头文件:
#include<stdlib.h>
参数列表:
str:被转换字符串。
功能:
将字符串转化为整数。
返回值:
返回转换后的整数。
示例代码如下:
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
char s[]="123";
int num=atoi(s); //将字符串”123”转化为整数 123
printf("%d
",num);
getchar();
return 0;
}
2. itoa 整型转字符串函数
函数原型
char *itoa( int value, char* string,int radix);
头文件
#include<stdlib.h>
参数列表
value:要转换的整数。
string:要写入转换结果的目标字符数组。
radix:转换的进制,可以是 10 进制、16 进制等。
返回值
写入转换结果的目标字符数组。
功能
将整数按照指定的进制转化为字符串。
示例代码如下:
#include<stdio.h>
#include<stdlib.h>
int main(void)
{
char s[4];
itoa(123,s,10); //将整数 123 转化为”123”,写入 s
printf("%s
",s);
getchar();
return 0;
}
为什么数组长度要为 4?因为要为最后的' '留地方。如果不知道整数多长,怎么办,那就把字符串声明为 10 进制的最大长度,比如在 32 位系统中最长的是-2147483648(包括负号),因此数组要声明为 12,当然声明更长一点,比如 32,容错性更强。
3. sprintf 字符串格式化函数
函数原型
int sprintf( char *buffer, const char *format, [ argument] … );
printf 是打印到屏幕上,sprintf 是存到字符串中。
头文件
#include<stdio.h>
参数列表:
buffer:保存格式化字符串的目标字符缓冲区。sprintf 会自动在最后加上' '
format:格式化后的字符串。printf 里可以用的占位符,sprintf 里都能用
[argument]...:参数列表。
功能:
将格式化后的字符串写入目标字符缓冲区。
示例代码如下:
#include<stdio.h>
int main(void)
{
char buffer[1024]={0};
sprintf(buffer,"name=%s,age=%d","hello",20);
printf("%s",buffer);
getchar();
return 0;
}
首先,第 4 行定义字符数组,用于保存格式化字符串。定义的数组的长度已经要足够长,不要不够用,特别是要记住结尾的' '也要占用一个长度。
接着,第 5 行将格式化后的字符串,写入字符缓冲区 buffer 中。
sprintf 函数执行流程为:
第 1 步,先将%d 使用数字 20 替换,将%s 使用”hello”替换,如图所示:
第 2 步 将替换后的字符串”name=rupeng,age=20”写入 buffer 中,如图所示: