文章目录
- 数组
- 数组的概念
- 一维数组的创建和初始化
- 一维数组的使用
- sizeof计算数组元素个数
- 一维数组在内存中的存储
- 二维数组的创建
- 二维数组的初始化
- 二维数组的使用
- 二维数组在内存中的存储
- C99中的变长数组
- 数组练习
数组
根据之前的知识,我们如果想要存放“1,2,3…8,9,10”这10个数字,需要敲10条:
int a=1;
int b=2;
int c=3;
.....
int j=10;
可以看到这样会非常的麻烦,而今天学习的数组可以方便存放一组数据。
数组的概念
数组是一组相同类型元素的集合
从这个概念中我们就可以发现2个有价值的信息:
- 数组中存放的是1个或者多个数据,但是数组元素个数不能为0。
- 数组中存放的多个数据,类型是相同的。
数组分为一维数组和多维数组,多维数组一般比较多见的是二维数组。
一维数组的创建和初始化
数组创建
一维数组创建的基本语法如下:
type arr_name[常量值];
放在数组的值被称为数组的元素,数组在创建的时候可以指定数组的大小和数组的元素类型。
- type 指的是数组中存放的数据的类型,可以是: char、short、int、float 等,也可以自
定义的类型。 - arr_name 指的是数组名的名字,这个名字根据实际情况,起的有意义就行。
- [] 中的常量值是用来指定数组的大小,这个数组的大小是根据实际的需求指定就行。
例如:
int main()
{
//⽐如:我们现在想存储某个班级的20⼈的数学成绩,那我们就可以创建⼀个数组,如下:
int math[20];
int data[10];//名字为data,大小为10个元素的整型数组
char ch[5];//名字为ch,大小为5个元素的字符型数组
double arr[8];//名字为arr,大小为8个元素的double类型数组
return 0;
}
数组的初始化
有时候,数组在创建的时候,我们需要给定一些初始值,这就称为初始化。
那数组如何初始化呢?
数组的初始化一般使用大括号,将数据放在大括号中。
初始化有两种
第一种:不完全初始化
int main()
{
//不完全初始化
int data1[10]={0};
//只给第一个元素初始化成0,剩下的元素默认给的是0
int data2[10]={1,2,3};
//只给前3个元素初始化为:1,2,3.剩下的都为0
return 0;
}
利用调试可清楚看到:
第二种:完全初始化
int data3[10]={1,2,3,4,5,6,7,8,9,10};
int data4[10]={1,2,3,4,5,6,7,8,9,10,11};//错误写法,因为多了一个元素
数组初始化的时候数组大小可以不写
例如:
int main()
{
int arr[10]={1,2,3,4,5,6,7,8,9,10};
}
调试后可以看到:
初始化时不写数组大小时:
int main()
{
int arr[]={1,2,3,4,5,6,7,8,9,10};
}
调试后的结果:
依然是10个元素,因为编译器会根据初始化的内容来确定它的个数。
注意:
如果不给数组初始化,那么就不能少了数组的大小:
int arr[];//错误,又无初始化,又无大小
int arr[10];//正确,无初始化的时候必须要有大小
int arr[]={1,2,3,4,5,6,7,8,9,10};
//正确,有初始化的时候可以不写数组大小,编译器会根据初始化内容自己来确定
数组的类型
数组也是有类型的,数组算是⼀种自定义类型(因为将type改一下,[常量子]改一下,那么数组就会变,所以叫自定义类型),去掉数组名剩下的就是数组的类型。
如下:
int arr[10];
int arr2[12];
char ch[5];
int arr[10];arr数组的类型是上面?
arr1数组的类型是 int [10],而int是arr数组的元素的类型
同理:
arr2数组的类型是 int [12]
ch 数组的类型是 char [5]
一维数组的使用
数组下标
C语言规定数组是有下标的,下标是从0开始的,假设数组有n个元素,最后一个元素的下标是n-1,下标就相当于数组元素的编号,如下:
int arr[10]={1,2,3,4,5,6,7,8,9,10};
在C语言中数组的访问提供了⼀个操作符 [] ,这个操作符叫:下标引用操作符。
有了下标访问操作符,我们就可以轻松的访问到数组的元素了
数组元素的打印
接下来,如果想通过下标打印整个数组的内容,那怎么办呢?
只要我们产生数组所有元素的下标就可以了,使用for循环产生0~9的下标,接下来使用下标访问。
数组的输入
当我们会了数组的打印,那么想要给数组输入值该怎么办呢?
现在有一个数组arr,其中有10个元素,全为0,我们该怎么输入值呢?
注意:有10个元素就要输入10次
sizeof计算数组元素个数
在遍历数组的时候,我们经常想知道数组的元素个数,那C语言中有办法使用程序计算数组元素个数吗?
答案是有的,可以使用sizeof
sizeof是C语言中的一个关键字,可以计算类型或变量大小的,其实sizeof也可以计算数组的大小(数组有几个元素)
我们可以用sizeof这个关键字求出数组总共的大小,然后除以一个元素字节的大小,就可以得到有几个元素
这样就求出来了数组元素的个数了
所以上面数组输入的代码可以这样改:
这样以来,当1地方的数组大小改变时,下面不需要做任何改动
就算是输入多了也不会多读取
一维数组在内存中的存储
有了前面的知识,我们使用数组基本没有什么障碍了,如果我们要深入了解数组,我们最好能了解一下数组在内存中的存储。
如果要研究一维数组在内存中的存储方式的话,就应该把每个元素的地址打印出来,这样才能更加容易理解它。
铺垫知识:
内存是一块大的空间,为了很好的管理和使用内存,我们把内存划分为一个一个的内存单元,一个内存单元的大小是一个字节。为了方便找到它们就给它们编个号。把给这些内存单元的编号叫做地址
如果想把每个元素的地址打印出来,也就是把内存单元的编号打印出来。
因为是x64环境所以地址有点长,观察起来不够方便,所以换成x86,这样我们好观察一点。
从输出的结果我们分析,数组随着下标的增长,地址是由小(低)到大(高)变化的,并且我们发现每两个相邻的元素地址之间相差4(因为一个整型是4个字节)。所以我们得出结论:数组在内存中是连续存放的。这就为后期我们使用指针访问数组奠定了基础。
二维数组的创建
二维数组的概念
前面学习的数组被称为一维数组,数组的元素都是内置类型的,如果我们把一维数组做为元素,这个数组就是二维数组(因为多个一维数组组成的二维数组有了“行”,“列”。所以叫二维数组),把二维数组作为元素的数组被称为三维数组,二维数组及以上的数组统称为多维数组。
二维数组的创建
那我们如果定义二维数组呢?语法如下:
type arr_name[常量值1][常量值2];
//type--元素类型
//arr_name--数组名
//例如:
int arr[3][5];
double data[2][8];
解释上述代码的信息:
- 3表示数组有3行
- 5表示每一行有5个元素(也就是有5列)
- int表示数组的每个元素是整型类型
- arr是数组名,可以根据自己的需求指定名字
同理,data数组意思基本一致
二维数组的初始化
在创建变量或者数组的时候,给定一些初始值,被称为初始化。
那二维数组如何初始化呢?像一维数组⼀样,也是使用大括号初始化的。
不完全初始化
int arr1[3][5]={1,2};
int arr2[3][5]={0};
例子:
int data[3][5]这个数组3行5列原本可以放15个整型的,但是初始化是{1,2,3},只放了3个元素。所以实际上只把前3个元素初始化了,剩下的元素并没有初始化,默认为0
完全初始化
完全初始化:创建元素的时候,给每个元素一个值
int arr3[3][5]={{1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
代码例子:
按照行来初始化
那么有没有办法给每行单独赋值呢?
因为int data[3][5]有3行5列,给每行的一维数组初始化的时候也需要大括号,所以分别给对应的3个大括号就行了:
初始化时省略行,但不能省略列
int arr5[][5]={1,2,3};
//编译器会根据初始化的内容来确定几行
//在5个元素中1,2,3连都一行都放不满,所以编译器会认为是1行
int arr6[][5]={1,2,3,4,5,6,7};
//同理,编译器会认为是2行
int arr7[][5]={{1,2},{3,4},{5,6}};
//这里给了3个大括号,所以就会有3行
二维数组的使用
二维数组的下标
二维数组访问也是使用下标,二维数组是有行和列的,只要锁定了行和列,就能唯一锁定数组中的一个元素。
C语言规定,二维数组的行是从0开始的,列也是从0开始的,如下所示:
int arr[3][5]={1,2,3,4,5,2,3,4,5,6,3,4,5,6,7};
图中最右侧绿色的数字表示行号,第一行蓝色的数字表示列号,都是从0开始的,比如,我们说:第2行,第4列,快速就能定位出7。
int main()
{
int arr[3][5] = {1,2,3,4,5, 2,3,4,5,6, 3,4,5,6,7};
printf("%d\n", arr[2][4]);
return 0;
}
这里同使用下标引用操作符:[ ],来找行号为1,列号为3的元素
输入的结果如下:
又例如:
可快速定位到5
二维数组的输入和输出
输出
访问二维数组的单个元素我们知道了,那如何访问整个二维数组(把整个二维数组的元素打印出来)呢?
输入+输出
完整代码:
int main()
{
int arr[3][5] = { 0 };
//输入值
int i = 0;//定义行
for (i = 0; i < 3; i++)
{
//列
int j = 0;
for (j = 0; j < 5; j++)
{
scanf("%d", &arr[i][j]);
}
}
//打印所有元素
for (i = 0; i < 3; i++)//行是从0开始的
{
//列
int j = 0;//定义列
for (j= 0; j < 5; j++)
{
printf("%d ",arr[i][j]);
}//这一个循环结束后,每行的5个元素就打印出来了
printf("\n");//每打印完一行就换个行
}
return 0;
}
另一种写法(先打印列,再打印行。这里只示例输入的代码):
每列打印完换一行。
二维数组在内存中的存储
像一维数组⼀样,我们如果想研究⼆维数组在内存中的存储方式,我们也是可以打印出数组所有元素的地址的。代码如下:
从输出的结果来看,每一行内部的每个元素地址之间相差4个字节,跨行位置处的两个元素(如:arr[0][4]和arr[1][0])之间也是差4个字节,所以虽然我们画图的时候分行画的,但是二维数组的每个元素在内存中的存储都是连续存放的。
如下图所示例:
第一行的数组名为:arr[0]
因为访问第一行数组时:
arr[0][ j ]
j:0~4
第二,三行数组名同理。
这个二维数组整个的数组名为arr,每行也都有数组名
C99中的变长数组
在C99标准之前,C语言在创建数组的时候,要么通过使用常量、常量表达式指定数组大小。要么利用初始化来省略写数组大小。
如:
int arr1[10];
int arr2[3+5];
int arr3[]={1,2,3};
int arr5[]={0};//此时只有一个元素0
int n=10;
int arr6[n];//不能使用变量
在C99中引入了变长数组的概念,这时创建数组的时候可以使用变量来指定大小,但是遗憾的是,vs中不支持变长数组。
所以接下来我们用**小熊猫C++**这个环境,因为它使用了gcc这个编译器,gcc是支持变长数组的
在这个代码里面,我们是用变量来指定的数组大小
变长数组方便之处在于:
int n=0;
scanf("%d",&n);
如果加上这两句代码,数组的大小是可以自己输入的。
例子:
运行结果:
当输入100时:
就可以将数组里放的0~99全部打印出来。
变长数组的根本特征,就是数组长度只有运行时才能确定,所以变长数组不能初始化。它的好处是程序员不必在开发时,随意为数组指定⼀个估计的长度,程序可以在运行时为数组分配精确的长度。
例如:
int n=0;
scanf("%d",&n);
int arr[n]={1,2,3};//这里的初始化是错误的,编译器会报错
并且:
变长数组的意思是:数组的大小可以使用变量来指定,数组的大小是根据程序运行时,变量的大小来决定的,而不是说数组的大小是可以变大的(被变长数组这个名字误导)。数组的大小一旦被确定就不能再变化了。
数组练习
练习1:多个字符从两端移动,向中间汇聚
意思就是,我们原本要打印的是:
welcome to China!!!
但是打印过程为:
- 先打印一行************************
- 接着两边出现一个字符:w**********************!
- 再出现一个字符:we********************!!
- 以此类推:wel******************!!!
- …
注意:我们要打印的那一串字符一定要与那行*号一样长。
代码例子:
但是此时只能打印一行,所以要加循环:
为什么while(left<=right)呢?
当打印的字符串个数为偶数时,left在小于right的时候,整个字符串就可以完整打印完。
当打印的字符串为奇数时,在还剩最中间的一个字符,那时left=right,左下标与右下标同时替换,将整个字符串打印完。
所以当left<=right时就可以将整个字符串逐渐替换,不需要left>right了。
完整代码:
#include <stdio.h>
#include <string.h>
#include <windows.h>//Sleep()需要包含的头文件
#include <stdlib.h>//system()需要包含的头文件
int main()
{
char arr1[] = "welcome to bit!!!!!!";
char arr2[] = "********************";
int left = 0;
int right = strlen(arr1) - 1;
while(left<=right)
{
arr2[left] = arr1[left];
arr2[right] = arr1[right];
printf("%s\n", arr2);
Sleep(1000);
//单位:毫秒。使上句打完后休眠1000毫秒,也就是1秒。此时程序可以一秒打印一句一秒打印一句
//也可以使上句被打印完后把它清理掉,下一句不受影响,因为下一句也是根据上一句往中间打印
system("cls");//cls:清理屏幕
//system可以执行系统命令,双引号把cls引起来,这样的话这个函数就可以帮忙调用cls这个命令
left++;
right--;
}
printf("%s\n", arr2);
//用了cls上句被清理掉了,所以重新打印一遍
return 0;
}
练习2:二分查找
当我买了一双鞋,你好奇问我多少钱,我说300元。你还是好奇,我就让你猜,你会怎么猜?你会1,2,3,4…这样猜吗?显然不会,一般都会猜中间数字,比如:150,然后看大了还是小了,这就是二分查找,也叫折半查找。
应用场景:
在有序的数据中查找某个数,就可以使用二分查找(折半查找)
一般的方法:整个数组遍历查找
但是这种方法效率很低,当是n个元素的时候,最坏的情况是找了n次!
二分查找
在{1,2,3,4,5,6,7,8,9,10}这组有序数组中找7。
- 利用下标找出数组中的中间元素
(第一个元素下标+最后一个元素下标)/2=中间元素的下标
(0+9)/2=4,下标是4的元素为5。
2. 5与要找的数比较。因为这组数组是有序的,5<7,那么5的前面不可能有7,所以去掉前面包含5的所有数据,在后面继续查找。
3.因为是前面一半被去掉了,所以起始下标要变,得+1,得到下一轮的起始下标,这样一来,被查找的范围缩小了一半,所以叫折半查找(二分查找)。
求第二轮的平均值:
(5+9)/2=7,下标为7的元素是8
- 8与要找的数比较。发现8>7,所以去掉8及8后面的所有数字。
- 因为是后面一半被去掉了,所以起始下标不变,末尾下标是元素8(上一轮中间元素的下标)的下标-1,为6。,
(5+6)/2=5,下标为5的元素是6
- 6与要找的数比较。6比要找的数小,说明6及6之前的数字不可能了.因为6之前的数字已经没有了,所以这时只用去掉6就行了。
- ,因为是前半段被去掉了,所以起始下标要变。上一轮中间元素的下标+1:5+1=6。让6作为新一轮的起始下标
(6+6)/2=6,下标为6的元素是7
- 拿7跟要找的数比较,发现相等。
注:如果剩最后一个元素都不能匹配的话,就无法找到要找那个数字。
所以:如果是有序数组,要在里面查找一个数,用二分查找是非常快的
写代码思路:
- 计算出中间元素的下标
左右下标------->中间元素下标 - 拿中间元素与之相比较。找到了就找到了,找不到就要确定新的范围。
- 有了新的范围后再通过左右下标找到中间元素,再比较…
- 以此类推
基本的定义:
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };//有序
int k = 7;//在数组中查找7
int i = 0;//数组元素下标
int sz = sizeof(arr) / sizeof(arr[0]);
//求的是数组元素的个数
int flag = 0;
int left = 0;//左下标
int right = sz-1;//右下标
//(sz-1)表示:元素个数-1=最右边元素的下标
int mid = (left+right)/2;//中间元素的下标
arr[mid];
//是中间下标所对应的元素,要拿这个元素跟k比
return 0;
}
只有一次的二分查找:
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };//有序
int k = 7;//在数组中查找7
int i = 0;//数组元素下标
int sz = sizeof(arr) / sizeof(arr[0]);
//求的是数组元素的个数
int flag = 0;
int left = 0;//左下标
int right = sz-1;//右下标
//.(sz-1)表示:元素个数-1=最右边元素的下标
//一次二分查找:
int mid = (left+right)/2;//中间元素的下标
if (arr[mid] < k)
//arr[mid]是中间下标所对应的元素,要拿这个元素跟k比
//中间元素要是比k小的话,中间元素及前面的数都不能要了,初始下标得变,变为中间元素下标+1
{
left = mid + 1;
}
else if (arr[mid] > k)
//中间元素要是比k大,那么中间元素之后的数都不能要,末尾下标得变,变为中间元素下标-1
{
right = mid - 1;
}
else//这种情况是arr[mid]=k的情况
{
printf("找到了 ,下标是%d\n",mid);
}
return 0;
}
当我们更新了left或right后又要产生了新范围的left和right,我们得根据新范围再进行二分查找,直到找到为止。所以要用到循环
我们在进行查找的时候范围逐渐缩小,左下标越来越大,右下标越来越小,直到左右下标相等,此时刚好可以把整个数组找完,所以while的表达式应该是(left<=right)。
注:只要left<=right,说明中间还有元素等着我们去查找。
完整代码:
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };//有序
int k = 7;//在数组中查找7
int i = 0;//数组元素下标
int sz = sizeof(arr) / sizeof(arr[0]);
//求的是数组元素的个数
int flag = 0;
int left = 0;//左下标
int right = sz-1;
//右下标.(sz-1)表示:元素个数-1=最右边元素的下标
while (left<=right)
{
int mid = (left + right) / 2;//中间元素的下标
if (arr[mid] < k)
//arr[mid]是中间下标所对应的元素,要拿这个元素跟k比
//中间元素要是比k小的话,中间元素及前面的数都不能要了,初始下标得变,变为中间元素下标+1
{
left = mid + 1;
}
else if (arr[mid] > k)
//中间元素要是比k大,那么中间元素之后的数都不能要,末尾下标得变,变为中间元素下标-1
{
right = mid - 1;
}
else//这种情况是arr[mid]=k的情况
{
flag = 1;
printf("找到了 ,下标是%d\n", mid);
break;
}
}
if (flag == 0)
printf("找不到了\n");
return 0;
}
运行结果:
当我们查找数改为17时:
注意:
上端代码中求平均值(中间元素小标)的写法不太合适
int mid = (left + right) / 2;//中间元素的下标
当left和right这两个值很大的时候,超过int类型可以装的范围,会被丢掉一些数据。此时我们有另一种写法:
int mid=left+(right-left)/2;
因为left本身就比right小,拿right比left多的部分除以2,它们此时一样大,这个值也是它们的平均值
所以我们的代码可以改为
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };//有序
int k = 7;//在数组中查找7
int i = 0;//数组元素下标
int sz = sizeof(arr) / sizeof(arr[0]);
//求的是数组元素的个数
int flag = 0;
int left = 0;//左下标
int right = sz-1;
//右下标.(sz-1)表示:元素个数-1=最右边元素的下标
while (left<=right)
{
//int mid = (left + right) / 2;
int mid = left + (right - left) / 2;//求中间元素的下标
if (arr[mid] < k)
//arr[mid]是中间下标所对应的元素,要拿这个元素跟k比
//中间元素要是比k小的话,中间元素及前面的数都不能要了,初始下标得变,变为中间元素下标+1
{
left = mid + 1;
}
else if (arr[mid] > k)
//中间元素要是比k大,那么中间元素之后的数都不能要,末尾下标得变,变为中间元素下标-1
{
right = mid - 1;
}
else//这种情况是arr[mid]=k的情况
{
flag = 1;
printf("找到了 ,下标是%d\n", mid);
break;
}
}
if (flag == 0)
printf("找不到了\n");
return 0;
}
标签:初始化,arr,下标,一维,int,元素,C语言,详解,数组
From: https://blog.csdn.net/2302_80244431/article/details/140871760