1、数组的概念
数组是一组相同类型元素的集合;从中我们可以得知两点:
- 数组中存放着1个或者多个数据,但是数组元素个数不能为0。
- 数组存放这多个数据,类型是相同的。
数组分为一维数组和多维数组,多维数组中最常使用的就是二维数组。
2、一维数组的创建和初始化
2.1 数组创建
一维数组创建的基本语法如下:
type arr_name[常量值];
存放在数组的值被称为数组的元素,数组在创建的时候可以指定数组的大小和数组的元素类型。
- type表示数组中存放数据的类型,可以是char、short、int、double等...
- arr_name表示数组名
- [ ]中的常量值是用来指定数组的大小的,这个数组的大小是根据实际需求指定就行
int arr;//整型类型变量
int arr[10];//整型类型数组
比如:我们现在想存储某个班级20个同学的数学成绩,我们就可以创建一个数组,如下:
int math[20];
当然我们也可以根据需要创建其他类型和大小的数组:
char ch[8];
double score[10];
2.2 数组初始化
有时候,数组在创建的时候我们要给数组一个初始值,这就称为数组的初始化。
那数组是如何初始化的呢?数组的初始化一般使用大括号,将数据放在大括号中。
//完全初始化
int arr[5] = {1,2,3,4,5};
//不完全初始化
int arr[5] = {1,2,3};//剩余的元素默认初始化为0
//错误的初始化
int arr[3] = {1,2,3,4};//初始化元素个数大于所分配空间个数
注意还有一种初始化,也是最为常用的初始化方式,如下:
int arr[] = {1,2,3,4,5};
//数组的大小,是编译器根据初始化的内容(元素个数)确定的。
这种初始化方式的空间是系统自动开辟的,不需要我们手动输入来让数组开辟空间,不需要在[ ]
内填写任何值来开辟空间大小,编译器可以根据你初始化有几个元素自动开辟对应的空间大小来存放你给的初始值。但是这种方式仅限于初始化,如果只是想先创建一个数组,不给它初始值,就需要在[ ]内填写常量,来表示你要给这个数组开辟多少元素的空间,后期才可以使用此数组。
重点:在刚创建一个数组不给初始值时,要给数组手动输入需要开辟元素的空间个数。如果只是创建数组不给初始值也不在[ ]内输入要开辟的元素个数,那这个数组基本上就不能为我么使用,解引用赋值不能再赋值,并且数组名不是指针(指针后期笔记讲解),只是一个地址,也不能直接给数组名赋值一块空间的地址。比如有一个int a; 用来取地址&a,并且给&a赋值可以吗?那肯定是不行啊,地址就是地址,而不是一块空间。
注:数组名是首元素的地址
所以建议创建数组时要么初始化,要么给他输入一个元素大小让编译器开辟空间,既不给数组初始化有不说要创建多大空间的数组这种数组就没有办法去使用了。
2.3 数组的类型
数组也是有类型的,数组算是一种自定义类型,去掉数组名留下的就是数组的类型。
如下:
char ch[10]; //数组类型:char [10];
int arr[12]; //数组类型:int [12];
double score[5];//数组类型:double [5];
为什么数组是自定义类型呢?比如char ch[10]的类型是char [10],如果我将char ch[10]的[ ]括号内的值改为11,此时数组ch类型就是char [11];char [10]和char [11]就是两种不同的类型,所以数组可以被称为自定义类型。
注:上面的是数组的类型,那数组类型前面的char、int、double就是数组元素的类型。
3、一维数组的使用
知道了一维数组的基本语法,一维数组可以存放数据,存放数据的目的是对数据的操作,那我们如何使用一维数组呢?
3.1 数组的下标
C语言规定数组是有下标的,下标是从0开始的,每个下标对应一个元素,下标相当于每个元素的编号,如下:
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
注:数组就是通过下标去访问对应的元素的
在C语言中数组的访问提供了一个操作符[ ],这个操作符叫:下标引用操作符。
有了下标引用操作符,我们就可以轻松地访问到数组的元素了,比如我们访问下标为7的元素,我们就可以使用arr[7](注意这里是下标的访问,而不是元素的个数)来访问下标7对应数组中的元素8,也就是说arr[7] == 8; 可以使用arr[3]来访问数组中下标为3的元素4,arr[3] == 4;如下代码:
#include <stdio.h>
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
输出结果:
引言:
不知道大家有没有通过上面这些代码注意到为什么在创建数组时[ ]内只能填写常量值来表示元素个数呢?为什么就不能是一个变量替代呢?如下代码:
int main() { int arr[10] = {0};//允许的 int n = 10; int arr2[n]; //error }
这是因为在C99标准之前数组只能使用常量来表示数组元素个数,C99标准之后就可以使用变量来表示元素个数了,这被称为变长数组,后面会介绍变长数组。
注:在创建数组时的[ ]内是不能使用变量,但是不代表arr[ i ]不能使用变量,这里的[ ]是下标引用操作符,所以是可以使用变量来替代的。
3.2 数组的输入
明白了数组的访问,我们可以给数组输入想要的数据。
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int i = 0;
for (i = 0; i < 10; i++)
{
scanf("%d", &arr[i]);
}
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
输出结果:
我们除了通过下标访问输出数据,我们也可以通过下标访问输入数据,通过下标访问输入的数据会替换数组下标原来的值。
4、一维数组在内存中的存储
有了前面的知识,我们使用数组就基本上没有什么障碍了,如果我们要深入了解数组,我们最好也能了解一下数组在内存中是如何存储的。
int main()
{
int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int i = 0;
for (i = 0; i < 10; i++)
{
printf("arr[%d]=%p\n", i, &arr[i]);
}
return 0;
}
输出结果:
可以看的出来,每个地址之间相差4个字节空间的地址,因为每个字节的内存块都有一个自己的地址,数组的每个元素是int类型,所以需要4个内存块存储数据,每个元素的地址表示就是内存中低地址的字节空间地址,当解引用访问地址时就会从当前地址再向后访问3个地址的内存块取出最后拿出这4个字节的元素。所以当一个int元素地址要到下一个int元素的地址需要跳过4个字节。
- 在内存中,&数组元素或是&变量是取该元素或是变量所占的内存空间范围内最接近低地址的那块内存块的地址来表示它的地址
- 连续存放的数组元素类型大小是多少,在内存中需要占用的内存块就是多少。
- 每个字节的内存都有地址,每个地址指向的是一个字节的内存块。
- 数组的元素在内存中连续存储的。
- 随着下标的增长,地址也是由低到高的。
5、sizeof计算数组元素个数
在遍历数组的时候,我们经常想知道数组的元素个数,那C语言中有办法计算数组元素的个数吗?
答案是有的,可以使用sizeof。
sizeof在C语言中是一个关键字,是可以计算类型或者变量大小的,其实sizeof也可以计算数组的大小。
比如:
#include <stdio.h>
int main()
{
int arr[10];
int sz = sizeof(arr);
printf("%d\n",sz);//结果为40
return 0;
}
sizeof返回结果是size_t类型的,size_t是无符号整型。
如果数组元素个数变化时,该怎么精准计算到数组的元素个数呢?
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr)/sizeof(arr[0]);//计算数组元素个数
int i = 0;
for (i = 0; i < sz; i++)
{
scanf("%d", &arr[i]);
}
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
sz为什么能计算出数组元素个数呢?sizoef(arr)这里的arr求的不是数组首元素地址的大小,而是整个数组的大小,单位是字节,sizeof(arr[0])求的是元素的大小,然后整个数组的大小 / 一个元素的大小就是元素的个数,例如上面代码中的arr数组大小是40个字节,一个元素是4字节,40/4结果就是10,sz=10;使用这个方法可以随时求数组的元素个数,比较方便。
6、二维数组的创建
6.1 二维数组的概念
前面学习的数组叫做一维数组,数组的元素都是内置类型的,如果我们把一维数组作为数组元素,就是二维数组,二维数组作为数组元素就是三维数组,二维数组以上都可以称之为多维数组。
注:存放一维数组的数组叫做二维数组,二维数组的每个元素就是一维数组
6.2 二维数组的创建
那我们如何定义二维数组呢?语法如下:
type arr_name[常量值1][常量值2];
例如:
int arr[3][5]; //第一个[]内表示行,第二个[]内的值表示列,表示此数组有3行5列,也可以理解为3个元素个数为5的一维数组
double data[2][8];//2行8列,可以理解为2个元素个数为8的一维数组
解释:上述代码中出现的信息
- 3表示数组有3行
- 5表示数组有5列
- int表示每个元素是int类型
- arr是数组名,可以根据自己的需求指定名字
7、二维数组的初始化
首先看一下这三种二维数组初始化的语法:
int main()
{
int arr1[3][5] = { 1, 2, 3, 4, 5, 6, 7 };//不完全初始化
int arr3[3][5] = { { 1, 2, 3, 4, 5 }, { 2, 3, 4, 5, 6 }, { 3, 4, 5, 6, 7 } };//完全初始化
int arr2[2][4] = { { 1, 2 }, { 2, 3 } };//按照行初始化
return 0;
}
7.1 不完全初始化
arr1的不完全初始化里数组的结果
int arr1[3][5] = { 1, 2, 3, 4, 5, 6, 7 };//不完全初始化
7.2 完全初始化
arr3的完全初始化数组的结果
int arr3[3][5] = { { 1, 2, 3, 4, 5 }, { 2, 3, 4, 5, 6 }, { 3, 4, 5, 6, 7 } };//完全初始化
7.3按照行初始化
arr2的按照行初始化数组的结果
int arr2[2][4] = { { 1, 2 }, { 2, 3 } };//按照行初始化
7.4 初始化省略行,但是不能省略列
int arr1[][4] = {1,2,3}; //一行1,2,3,0,0
int arr2[][5] = {1,2,3,4,5,6,7}; //两行,第一行:1,2,3,4,5 第二行:6,7,0,0,0
int arr3[][5] = {{1,2},{3,4},{5,6}};//三行,第一行:1,2,0,0,0 第二行:3,4,0,0,0 第三行:5,6,0,0,0
可以将行和列理解为有行个元素大小为列的一维数组,比如一个二维数组的行为3,列为5。就可以理解为3(行)个二维数组元素,每个元素是5(列)个一维数组元素的一维数组。所以你可以将行省略,不知道要分配几个二维数组元素,但是有列就行,知道每个元素大小是多少。但是有行省略列,就会导致知道有几个二维数组元素,却不知道每个元素的大小是多少,不知道如何分配。
8、二维数组的使用
8.1 二维数组的下标
我们掌握了二维数组的创建和初始化,那我们怎么使用二维数组呢?
其实二维数组访问也是使用下标形式的,二维数组是有行和列的,只要锁定了行和列就能唯一锁定数组中的一个元素。
C语言规定,二维数组的行是从0开始的,列也是从0开始的,如下所示:
int arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
比如我要随机找一个元素5,那就可以通过行和列来锁定,通过下标先找到第二个数组元素arr[1],此时二维数组arr拿到了第二个元素,arr[1]就是一个一维数组,再通过当前的一维数组找到5,就使用列的下标再访问一次数组元素也就是arr[1][4],arr[1][4]此时就是元素5。
注:每一行列的下标,都是从0开始的
8.2 二维数组的输入输出
#include <stdio.h>
int main()
{
int arr[3][5] = { { 1, 2, 3, 4, 5 }, { 2, 3, 4, 5, 6 }, { 3, 4, 5, 6, 7 } };
int i, j;
for (i = 0; i < 3; i++) //表示行
{
for (j = 0; j < 5; j++) //表示列
{
scanf("%d", &arr[i][j]); //通过行和列找到当前空间的地址并输入值
}
}
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);//通过行和列找到元素进行打印
}
printf("\n");
}
return 0;
}
输出结果:
9、二维数组在内存中存储
那二维数组在内存中是怎么存储的呢?是不是我们想象的每一行元素一维数组的地址不连续呢?看下面代码:
#include <stdio.h>
int main()
{
int arr[3][5] = { { 1, 2, 3, 4, 5 }, { 2, 3, 4, 5, 6 }, { 3, 4, 5, 6, 7 } };
int i, j;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("arr[%d][%d] = %p\n", i, j, &arr[i][j]);
}
}
return 0;
}
输出结果:
我们可以看出和一维数组一样,二维数组中的所有元素也是由低到高依次存储的,是连续的存储,地址也在下标的增长中由低到高的变化。
二维数组每一行都是一个一维数组名,arr[0]、arr[1]、arr[2]可以理解为三个一维数组的数组名,再通过这个数组名下标引用就可以找到一维数组的元素,例如:arr[0][3]。
二维数组解析:
数组名是首元素地址,二维数组的数组名也是,二维数组的数组元素是一维数组,所以二维数组数组名表示的是一维数组整个数组的地址,+1就跳过一维数组大小的字节地址,解引用二维数组的数组名后变成一维数组,此时+1就跳过一个一维数组元素大小的字节地址,再解引用就可以拿到值(后期指针讲解)。
了解清楚二维数组在内存中的布局,有利于我们后期使用指针来访问学习。
10、变长数组
C99标准之前创建数组的方式,数组大小是使用常量、常量表达式指定的
int arr1[10];
int arr2[3 + 5];
int arr3[] = { 1, 2, 3, 4 };
这样的语法限制,让我们创建数组就不够灵活,有时候数组大了就浪费空间,数组小了不够用。
在C99中,引入了变长数组(variable-length array,简称VLA)的新特性,允许数组的大小是变量
请看下面代码:
int main()
{
//C99中,引入了变长数组的概念,允许数组的大小是变量
int n = 0;
scanf("%d", &n);
int arr[n];
return 0;
}
上面示例中,数组arr就是变长数组,因为它的长度取决于变量n的值,编译器没法事先确定,只有运行时才能知道n是多少。
注:变长数组的根本特性,就是数组长度只有运行时才能确定,所以变长数组不能初始化。
11、数组代码练习
练习1:两边字符向中间汇聚
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int main()
{
char str1[] = "hello world";
char str2[] = "***********";
int left = 0;
int right = strlen(str1)-1;
while (left <= right)
{
str2[left] = str1[left];
str2[right] = str1[right];
printf("%s", str2);
Sleep(1000); //睡眠时间(1秒循环一次)
system("cls");//执行系统屏幕清理指令
left++;
right--;
}
printf("%s\n", str2);
return 0;
}
运行时打印流程:
库函数介绍:
上面又使用两个库函数分别是system和Sleep,system是指令库函数,是执行系统命令的,可以用来输入控制台指令,比如"cls"就是清理屏幕指令,该库函数所包含头文件是#include<stdlib.h>。Sleep是windows所提供的库函数,是睡眠多少时间,参数1000为1秒,该库函数所包含头文件是#include<windows.h>
练习2:二分查找 (折半查找)
小明买了一双鞋,说在100-200元之间,让你猜一下,你从100、101、102一个一个往后问就很慢了,如果先猜150,小明说小了,再猜175,小明说大了,然后162这样不停的找中间值,不停的折半,最后很快就找到了要找的数。
#include <stdio.h>
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int sz = sizeof(arr) / sizeof(arr[0]);
int k = 7;
int left = 0;
int right = sz - 1;
while (left <= right)
{
int z = left + (right - left) / 2;//把right多出left的数求出来折个半加给left是更准确的中间值
if (arr[z] < k)
left = z + 1;
else if (arr[z]>k)
right = z - 1;
else
{
printf("找到了,下标为:>%d\n", z);
break;
}
}
if (left > right)
{
printf("找不到\n");
}
return 0;
}
输出结果:
C语言笔记第4篇结束了,欢迎大家在评论区留言,有什么事情也可以私信作者,再见