C语言数组的讲解(一)
谨记
每个人心中都有一片海,自己不扬帆,没人帮您启航,久了就是一片死海。人生,就是一场自己与自己的较量:让积极打败消极,让快乐打败忧郁,让勤奋打败懒惰,让坚强打败脆弱。在每一个充满希望的清晨,告诉自己:努力,就总能遇见更好的自己。
今天这篇文章将为读者介绍C语言中一个非常重要的知识点————数组,相信的知识已经不能满足我们实际开发的需要,所以,为了开发更快捷、更方便,那么今天开始我们就走进C语言的重点知识点。
通过本篇文章,你将学到如下知识点:
- 一维数组的概念
- 一维数组的定义方式
- 一维数组的内存分配
- 一维数组的排序
- 二维数组的介绍
- 二维数组的初始化
- 二维数组的深入
一、一维数组
1、数组的定义
数组就是具有一定顺序关系的若干变量的一个集合,我们简称数组,其中每一个变量我们称为数组的元素,数组的几个关键点:
- 1、组成数组的元素都是互不相干的独立的变量
- 2、这些变量(数组元素)的数据类型必须是相同的
- 3、变量之间有一定的顺序关系。
例如:int [10];
在前面的文章中已经提到过,数组是C语言的构造数据类型,一个数组可以分解成多个数组元素,这些数组元素可以是基本数据类型或者构造数据类型,因此,如果按照数组元素的类型来进行一个划分,那么数组可以分为数值数组、字符数组、指针数组、结构体数组等。
在C语言中,如果程序员要想使用数组,那么首先必须的去定义。数组的代表就是一维数组。
一维数组就是只有一个下标的数组,我们称为一维数组。定义格式:
<存储类型> <数据类型> <数组名>[<常量表达式>]
存储类型指的是auto, register, static, extern。若省略,相当于auto。
数据类型可以是任一种基本数据类型或构造数据类型。
数组名是用户定义的数组标识符。
方括号中的常量表达式表示数据元素的个数,也称为数组的长度。
对于数组的定义,需要注意一下几点:
- 1、数组的数据类型,其实就是指的数组元素的取值数据类型,对于同一个数组,数组元素的数据类型都是一样的。
- 2、数组名应当符合标识符的命名规定,即由字母、数字和下划线组成,但不能以数字开头。
- 3、在同一作用域中,数组名不能和其他变量的名称一样,
- 4、方括号中常量表达式表示数组元素的个数,如a[6]表示数组a有6个元素,它需要在数组定义时就确定下来,不能随着程序的运行动态更改。它的下标从0开始计算,因此6个元素分别为a[0]、a[1]、a[2]、a[3]、a[4]、a[5]。
- 5、不能用变量来表示数组的长度,只能是常量表达式或者常量
- 6、允许多个相同数据类型的数组和变量声明和定义。
int num[3];//定义了一个名称叫做num的数组,数组中可以存储3个int类型的数据
// num = 15;//会报错,系统不知道应该给谁赋值
// 只要定义一个C语言的数组, 系统就自动会给数组中的每一块小得存储空间一个编号
//这个编号从0开始,依次递增
//数组中系统自动绑定的编号,我们称为索引
num[0] = 10;
num[1] = 12;
num[2] = 20;
注意点:C语言中规定了数组必须逐个元素引用,而不能整体引用。数组的引用实际上就是数组元素的引用。数组元素的一般表示方法为: 数组名[下标]
若数组在定义时指定有n个元素,则数组的下标范围为0~(n-1)。如果数组的下标越界,则有可能导致以下几种结果:
① 若越界访问的内存空间是空闲的,程序可能不受影响,仍能正确运行。
② 若越界访问的空间已经被占用,且写了很重要的数据。在这种情况下,若程序执行了非法写操作,则程序可能会异常终止或崩溃。
③ 若越界访问的空间是空闲的,程序只是进行了读操作,则程序能继续运行,但无法得出正确的结果。
④ 其他情况。
2、一维数组的初始化
一维数组初始化严格上来说有7种。
1、局部数组初始化
对于普通局部数组,若定义时,没有初始化,则数组中元素的值,是不确定的。
2、static数组不初始化
对于static修饰的数组,若定义时,没有初始化,则数组中元素的值默认为0
3、全局数组不初始化
对于全局数组,若定义时,没有初始化,则数组中元素的值默认也为0。
4、全部初始化
与变量在定义时初始化一样,数组也可以在定义时进行初始化,如对整型数组进行初始化。
int a[10]={1, 2, 9, 21, 8, 10, 7, 24, 0, 20};
此处还是要注意,数组只能通过下标逐个引用元素。定义数组时,对数组元素的初始化,只能写成一行,不能换行写。
5、部分初始化:数组在定义时可以对其中的部分数据进行初始化。当“{}”中值的个数少于元素个数时,只给前面部分元素赋值。例如,如下定义就是对数组的前5个数据初始化,而后5个数据自动赋0。
比如:int a[10] = {1,2,3,5} 前面4个有数据初始化,后面6个数据会自动赋0
6、数组全部赋值
若想要对数组中的元素全部赋值,则可以省略数组下标中的常量。编译器会根据初始化列表自动计算数组元素的个数,如下所示。
int a[]={1, 2, 5, 23, 8, 10, 7, 24, 0, 22};
7、数组全部初始化为0
特殊写法: int a[10] = {0};
当然给数组清零,还有其他写法,比如可以使用库函数memset,把数组用0来填充,需要引入头文件string.h。
int main(int argc, const char * argv[]) {
int a[10];
memset(a, 0, sizeof(a));
return 0;
}
可以使用库函数bzero,把数组清0,需要引入头文件strings.h。
int main(int argc, const char * argv[]) {
int a[10];
bzero(a, sizeof(a));
return 0;
}
3、一维数组内存分配
在内存中,数组中的元素占用连续的存储空间,并且根据每个元素所占存储空间来进行内存分配。数组名代表数组的起始地址,是地址常量,对数组名求sizeof(),可以得出数组在内存空间中所占用的总空间。类似的道理,可以很容易利用下面的表达式来计算出数组的元素个数:
数组的元素个数 = sizeof(数组名) / sizeof(数据类型)
#include <stdio.h>
int main(int argc, const char * argv[]) {
//定义一个5个元素的数组
int a[5] = {1,2,3,4,5};
int sizea = sizeof(a);//求数组所占用的空间大小
printf("%d\n",sizea);
printf("%p\n",a);
printf("数组起始地址%p\n",&a[0]);
int n = sizeof(a) / sizeof(int);//求数组元素个数
for (int i = 0; i < n; i++) {
printf("a[%d]的地址%p\n",i,&a[i]);
}
return 0;
}
运行结果:
20
0x7fff5fbff830
数组起始地址0x7fff5fbff830
a[0]的地址0x7fff5fbff830
a[1]的地址0x7fff5fbff834
a[2]的地址0x7fff5fbff838
a[3]的地址0x7fff5fbff83c
a[4]的地址0x7fff5fbff840
Program ended with exit code: 0
通过上面的程序例子,我们可以看出,对数组名求地址,其实就是数组的第一个元素的地址,所以我们可以变像的说数组名就是一个指针,只不过是一个静态的指针,一个数组内存空间是连续的,从运行结果也可以看出来。
4、数组的排序
A、冒泡排序法
对于冒泡排序法,过程如下:
(1)比较第一个数与第二个数,若为逆序a[0]>a[1],则交换;然后比较第二个数与第三个数;依次类推,直至第n-1个数和第n个数比较为止——第一趟冒泡排序,最终,最大的数被安置在最后一个元素位置上。
(2)对前n-1个数进行第二趟冒泡排序,最终,使次大的数被安置在第n-1个元素位置。
(3)重复上述过程,共经过n-1次冒泡排序后,排序结束。
(省略了头文件和主函数,直接展示的核心代码)
//任意输入10个元素的数组排序
第一种:
int a[N], i, j, k;
for (i = 0; i < N; i++) {
scanf("%d",&a[i]);
}
for (i = 0; i < N - 1; i++) {
for (j = 0; j < N - 1 - i; j++) {
if (a[j] > a[j+1]) {
k = a[j];
a[j] = a[j+1];
a[j+1] = k;
}
}
}
printf("\n");
for (i = 0; i < N; i++) {
printf("%5d", a[i]);
}
第二种:
//利用sizeof
int a[] = {1,23,45,6,58,29},i,j,k,n;
n = sizeof(a) / sizeof(a[0]);
for (i = 0; i < n - 1; i++) {
for (j = 0; j < n - 1 - i; j++) {
if (a[j] > a[j+1]) {
k = a[j];
a[j] = a[j+1];
a[j+1] = k;
}
}
}
for (i = 0; i < n; i++) {
printf("%d\n",a[i]);
}
第三种,循环剥离
int a[] = {1,23,45,6,58,29},i,j,k,n,index;
n = sizeof(a) / sizeof(a[0]);
for (i = 0; i < n - 1; i++) {
index = 0;
for (j = 0; j < n - 1 - i; j++) {
if (a[index] < a[j+1]) {
index = j+1;//下标替换
}
}
k = a[index];//赋值
a[index] = a[n - 1 -i];
a[n - 1 -i] = k;
}
for (i = 0; i < n; i++) {
printf("%d\n",a[i]);
}
B、选择排序法
选择排序的排序过程如下。
(1)首先通过n-1次比较,从n个数中找出最小的, 将它与第一个数交换——第一次选择排序,结果最小的数被安置在第一个元素位置上。
(2)再通过n-2次比较,从剩余的n-1个数中找出关键字次小的记录,将它与第二个数交换——第二次选择排序。
(3)重复上述过程,共经过n-1次排序后,排序结束。
(省略了头文件和主函数体)
int a[N], i, j, r, t;//定义数组和变量
printf("Please input %d numbers\n",N);
for (i = 0; i < N; i++)//进行循环输入数组元素
scanf("%d",&a[i]);
for (i = 0; i < N-1; i++)//循环变量
{
r = i;
for (j = i+1; j < N; j++)
if (a[j] < a[r])
r = j;
if(r != i)//判断
{
t = a[r];
a[r] = a[i];
a[i] = t;
}
}
printf ("the array after sort:\n");
for (i = 0; i < N; i++)//打印
printf ("%5d",a[i]);
printf ("\n");
多维数组(二维数组)
1、多维数组定义
前面提到一维数组只有一个下标。那么具有两个或两个以上下标的数组,就称为多维数组。多维数组元素有多个下标,以标识它在数组中的位置。多维数组的说明与一维数组的说明基本类似,其说明的一般形式如下:
<存储类型><数据类型><数组名><常量表达式1><常量表达式2>…<常量表达式n>
可以看出,多维数组与一维数组的说明相比,只是增加了多个下标,其他特性基本与一维数组相同。例如:int b[2][3][4]。
在这里重点介绍二维数组,多维数组的用法可由二维数组类推而得到。
2、二维数组
二维数组定义的一般形式是:
<存储类型><数据类型><数组名>[常量表达式1][常量表达式2]
其中常量表达式1表示第一维下标的长度,即行数,常量表达式2 表示第二维下标的长度,即列数。例如:int a[2][3]。
说明了一个二行三列的数组,数组名为a,其下标变量的类型为整型。该数组的下标变量共有2×3个,即:
a[0][0],a[0][1],a[0][2]
a[1][0],a[1][1],a[1][2]
3、二维数组的初始化
==============二维数组============
格式:<存储类型><数据类型><数组名>[常量表达式1][常量表达式2]
其中常量表达式1表示第一维下标的长度,即行数,常量表达式2表示第二维下标的长度。即列数
初始化
① 降维给二维数组赋初值,即按行初始化。每一组的初始值都用{ }括起来。
int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
//按降维给a数组元素全部初始化
int a[3][3] = {{1}, {4}};
//只初始化了部分数组元素,其他元素为0。第一行为1 0 0,第二行为4 0 0
② 按线性存储的形式给二维数组赋初值。
int a[2][3] = {1, 2, 3, 4, 5, 6};
//按线性存储形式给二维数组全部初始化
int a[3][3] = {1, 2 };
//只初始化了部分数组元素,其他元素为0。第一行为1 0 0,第二行为2 0 0,第三行为0 0 0。
③ 可以省略左边下标范围的方式,给二维数组赋初值。
int a[][3] = {{1, 2, 3}, {4, 5, 6}};
//省略左边下标范围,给数组所有元素初始化
特别要注意的是,第一维的长度可以省略,但是,第二维的长度不能省,比如下面的写法:
int a[2][] = {{1, 2, 3}, {4, 5, 6}};
编译程序时,会有语法错误。
error: array type has incomplete element type
结论:二维数组第二维的长度不能省略。
// int a[2][] = {{1,2,3},{4,5,6}};//报错
4、二维数组内存分配
二维数组在概念上是二维的,也就是说其下标在两个方向上变化,有行和列的说法。下标变量在数组中的位置也处于一个平面之中,而不是象一维数组只是一个向量。但是内存却是连续编址的,是按一维线性排列的。如何在一维存储器中存放二维数组,
在C语言中,二维数组采取了和一维数组类似的存储方式,按行优先存,存储了第一行的元素,即存第二行的,依次类推。
int main(int argc, const char * argv[]) {
int a[2][3] = {{8, 2, 6}, {1, 7, 9}}, i, j;;
for (i = 0; i < 2; i++)
for (j = 0; j < 3; j++)
printf ("%d ", a[i][j]);
printf ("\n");
for (i = 0; i < 2; i++)
for (j = 0; j < 3; j++)
printf ("%p ", &a[i][j]);
printf ("\n");
return 0;
}
运行结果:
8 2 6 1 7 9
0x7fff5fbff810
0x7fff5fbff814
0x7fff5fbff818
0x7fff5fbff81c
0x7fff5fbff820
0x7fff5fbff824
Program ended with exit code: 0
深入理解二维数组,在数组定义形式你可以知道,其实一个二维数组只是比一维数组多了一个下标,那么,从内存分配的角度上来看的话,我们可以把一个二维数组看作是有很多个一维数组构成。例如:int a[3][4]。可以理解成二维数组含有三个元素:a[0],a[1],a[2]。每个元素a[i]由包含四个元素的一维数组组成。举一个程序代码例子
#include <stdio.h>
#define DEBUG 0
int main(int argc, const char * argv[]) {
/*
我们也可以把二维数组,看成由多个一维数组组成。例如:int a[3][4]。可以理解成二维数组含有三个元素:a[0],a[1],a[2]。每个元素a[i]由包含四个元素的一维数组组成。
*/
int a[3][4] = {{8, 2, 6, 4}, {1, 4, 7, 9}};
//预编译指令
#if DEBUG
a++;
a[0]++;
a[1]++;
a[2]++;
a[3]++;
#endif
printf("a :%p a+1 :%p\n\n", a, a+1);
printf("a[0]:%p a[0]+1:%p &a[0][1]=%p\n", a[0], a[0]+1, &a[0][1]);
printf("a[1]:%p a[1]+1:%p &a[1][1]=%p\n", a[1], a[1]+1, &a[1][1]);
printf("a[2]:%p a[2]+1:%p &a[2][1]=%p\n", a[2], a[2]+1, &a[2][1]);
printf("a[3]:%p a[3]+1:%p &a[3][1]=%p\n", a[3], a[3]+1, &a[3][1]);
//有一个3×4的矩阵,要求输出其中值最大的元素的值,以及它的行号和列号
int max, i, j, r = 0, c = 0;
int b[3][4] ={{24, 89, 2, 41}, {3, 11, 9, 1}};
max = b[0][0];
for (i = 0; i < 3; i++)
for (j = 0; j < 4; j++)
if (b[i][j] > max)
{
max = b[i][j];
r = i;
c = j;
}
printf("Max=%d, row=%d, column=%d\n", max, r, c);
return 0;
}
运行结果:
a :0x7fff5fbff7e0 a+1 :0x7fff5fbff7f0
a[0]:0x7fff5fbff7e0 a[0]+1:0x7fff5fbff7e4 &a[0][1]=0x7fff5fbff7e4
a[1]:0x7fff5fbff7f0 a[1]+1:0x7fff5fbff7f4 &a[1][1]=0x7fff5fbff7f4
a[2]:0x7fff5fbff800 a[2]+1:0x7fff5fbff804 &a[2][1]=0x7fff5fbff804
a[3]:0x7fff5fbff810 a[3]+1:0x7fff5fbff814 &a[3][1]=0x7fff5fbff814
Max=89, row=0, column=1
Program ended with exit code: 0
通过运行结果我们可以得出结论:
1、 a是二维数组名,是地址常量。
2、 a[0],a[1], a[2], a[3]实际上都是一维数组名,代表一维数组的起始地址,也都是地址常量。
3、 a+1和a的地址差16个字节,相当于4个数组元素。因此,可以看出a代表第1行的地址,a+1代表第2行的地址。
4、 a[0]+1和a[0]的地址差4个字节,相当于1个数组元素。因此,a[0]+1相当于元素&a[0][1], a[1]+1相当于元素&a[1][1],a[2]+1相当于元素&a[2][1]。
如果读者还想继续深入研究二维数组,可以自行查阅一些相关资料,当然,后面的文字中,我也会单独的讲解。
总结
希望读者认真学习,一步一个脚印,踏踏实实的把基础落实好。这篇文章主要介绍了C语言的构造数据类型数组,重点掌握一维数组,了解和熟悉二维数组,望读者掌握好。
结尾
最后,希望读者在读文章的时候发现有错误或者不好的地方,欢迎留言,我会及时更改,感谢你的阅读和评论已经点赞收藏。