?
目录
前言:
一 、一维数组的创建和初始化
? ? ? ? 1.1 数组的创建
? ? ? ? 1.2 数组的初始化?
? ? ? ? 1.3 一维数组的使用
? ? ? ? 1.4?一维数组在内存中的存储
二、二维数组的创建和初始化
? ? ? ? 2.1 二维数组的创建
? ? ? ? 2.2 二维数组的初始化
? ? ? ? 2.3 二维数组的使用
? ? ? ? 2.4??二维数组在内存中的存储
三、数组的越界访问
四、数组作为函数参数
小结
前言:
? ? ? 数组是在程序设计中,为了处理方便,把具有相同类型的若干元素按有序的形式组织起来的一个形式。我们把相同类型元素的集合称之为数组。在C语言中,数组归属于构造数据类型。
? ? ? 按数组元素的类型不一样,数组又可分成数值数组、字符数组、指针数组、结构数组等各种类别。??
一 、一维数组的创建和初始化
1.1 数组的创建
? ?数组的创建方式:
type_t ? arr_name ? [const_n];
//type_t 是指数组的元素类型
//arr_name 是数组的名字
//const_n 是一个常量表达式,用来指定数组的大小
?单单像这样讲略显枯燥,下面我们用具体的代码好好学一学:
int arr[5];
//创建一个 int 类型的数组,数组名叫arr,数组里面有五个元素
char ch[10];
//创建一个 char 类型的数组,数组名叫ch,数组里面有十个元素
不过,我们要知道,创建数组的时候,[ ]里面的数字必须是常量。在C99标准之前是不支持使用变量的,只能是常量!在C99中增加了变长数组的概念,允许数组的大小是变量,而且要求编译器支持C99标准!但是我们常见的编译器对C99标准支持的不够好,比如说,VS系列编译器就是不咋的支持!
我用的就是VS2019,很明显它不是特别支持的。
对了,小声提一句,如果是变长数组的话就不可以初始化的;如上,用变量指定数组的大小,而变量是在代码运行起来的时候创建的,而n在初始化的时候,编译器不知道数组有多少个元素,所以初始化时没有意义的,这是语法限制死的。
1.2 数组的初始化?
我们在创建数组的同时给数组一些初始值叫初始化:
int arr1[10] = {1,2,3,4,5,6,7,8,9,10};
//完全初始化
int arr2[10] = {1,2,3};
//不完全初始化,剩余的元素默认为0
int arr3[10] = {0};
//不完全初始化,剩余的元素默认为0
int arr4[ ] = {1,2,3};
//数组会根据初始化的内容,默认[ ]的数字,如上默认为存放了3个元素
上面数组可以在编译器监事窗口看到:
不过,这里我们需要注意一个小细节,就是在用字符串作为数组的初始值时,稍微注意一下:
?如上所示,arr1有3个元素,数组的大小是3个字节,arr2有4个元素,数组的大小是4个字节。下面我们来测试一下:
注意:
1、strlen是一个库函数,计算的是字符串的长度,并且只能针对字符串;关注的字符串中是? ? ? ? 否有\0,计算的是\0之前的字符个数。
2、sizeof是一个操作符(运算符),sizeof使用来计算变量所占空间的大小的,任何类型都? ? ? ? ? 可以使用;只关注空间大小,不在乎内存中是否存在\0。
?在使用printf打印字符以及strlen求字符串长度时候,遇到’\0’才停止,没遇到’\0’之前不停止。
1.3 一维数组的使用
对于数组的使用我们之前介绍了一个操作符:[ ] 下标引用操作符。它其实就数组访问的操作符。
#include <stdio.h>
int main()
{
int arr[10] = { 0 };
int sz = sizeof(arr) / sizeof(arr[0]);//计算数组的元素个数
int i = 0;//数组是使用下标来访问的,下标从0开始。
for (i = 0; i < sz; ++i)//输入数组
{
scanf("%d",&arr[i]);
}
for (i = 0; i < sz; ++i)//输出数组的内容
{
printf("%d ", arr[i]);//arr[i]这个不是在创建数组,而是在访问数组的某一个元素
//访问的是下标,是可以用变量的
}
return 0;
}
?
1.4?一维数组在内存中的存储
要想知道一位数组是怎样在内存中存储的,我们可以先把它打印出来,然后再编译器里看:
为什么会相差4呢?——因为一个整型元素的大小是4个字节?
?
?总结:
1.一维数组在内存中是连续存放的。 2.随着数组下标的增长,地址是由低到高变化的。
数组地址连续存放有什么实际意义呢?
?我们可以很容易推测,p+i是数组中arr[i]的地址;
下面我们可以验证一下:
理解了这个以后,我们就可以不用下标的形式访问数组内容了:
?这个例子可以很好的说明,因为数组是连续存放的,通过数组首元素的地址往后找可以找到每一个数组对应的元素!
二、二维数组的创建和初始化
2.1 二维数组的创建
int arr1[3][4];
//3行4列的二维数组,里面的元素类型是 int类型
char arr2[3][4];
//3行4列的二维数组,里面的元素类型是 char类型
double arr3[4][5];
//4行5列的二维数组,里面的元素类型是 double类型
有了前面的一维数组的知识,我们就可以更容易的理解二维数组的概念,请看下面的例子:
?2.2 二维数组的初始化
第一种方式:
int arr1[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };//完全初始化
int arr2[3][4] = { 1,2,3,4,5,6,7 };//不完全初始化
int arr3[3][4] = { {1,2},{3,4},{5,6} };//不完全初始化
? ? ? ? ? ? ? ?
第二种方式:
//初始化的时候括号里面的数字是否可以省略
//行可以省略,列不可以省略
?看,这个就是一种错误的初始化方式。虽然我们想像一位数组一样,通过数组的内容来判断数组里面有几个元素。因为我们不知道到底有多少行多少列。是1行12列,2行6列,3行4列,4行3列......当行和列都没有指定的时候,这种写法肯定是错误的。
注意:二维数组在创建的时候行可以省略,列不能省略(第一个[ ]中的值可以省略,第二个[? ? ? ? ? ? ?]中的值必须要写出来)。
思考:那么为什么二维数组在创建的时候行可以省略,列不能省略呢?
在一维数组中,数组名[常量表达式]是它的定义方式;
而在二维数组中,可以看成是一维数组的数组,那么,arr[0]、arr[1]、arr[2]就可以看成是第一行、第二行、第三行,就是一个新的数组名;?
所以说,arr[ ][4]是这个样子的:
知道了列数,就知道第二行该从哪里出发,第三行该从哪里出发。
但是如果只知道了行数,那是没有办法知道有多少列的。
实在不理解的话,可以想一想一维数组的用法,把arr[0]、arr[1]、arr[2]看成新的数组名,类似于一维数组,又加了一个[ ]的用法。?
2.3 二维数组的使用
?二维数组的使用方式也是通过下标的方式。二维数组的行和列下标都是从0开始的:
让我们看一看这个例子:
?
从上面可以看出,下标是[2][3]的元素是12,结果果然就是12。
?打印二维数组的元素:
#include<stdio.h>
int main()
{
int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}
2.4??二维数组在内存中的存储
为了研究二维数组的存储,这里我们可以尝试将二维数组的每个元素都打印出来:
我们可以看出,第一行的每个元素之间间隔了4个字节,而第一行最后一个元素到第二行第一个元素也是间隔了4个字节,后面的也是这样。
就这样,虽然在我们的感觉中二维数组是多行多列的,实际上,在内存中的存储方式是这样的:
二维数组可以看成是一维数组的数组 。
结论:二维数组在内存中也是连续存储的。
三、数组的越界访问
?在内存中,我们只申请了 arr[0]到arr[9] 的空间,而当运行到i=10的时候,超出了申请的空间的边界,就叫做越界访问。
四、数组作为函数参数
我们在写代码的时候,往往会将数组作为参数传给函数,比如:我们要实现一个冒泡排序函数将一个整型数组排序。那我们将这样使用函数:?
冒泡排序的思想:两两相邻的元素进行比较,如果有可能的话需要交换。
一趟冒泡排序能搞定一个数字
让当前待排的数组中的一个元素来到最终应该出现的位置上
n个元素应该进行n-1趟的冒泡排序
?
下面先来展示一个错误的代码:
#include <stdio.h>
void bubble_sort(int arr[])//可以用数组来接收
{
//确定趟数
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz - 1; i++)
{
//一趟冒泡排序的过程
int j = 0;
//一趟冒泡排序比较的对数
for (j = 0; j <sz-1-i ; j++) //第一趟:10个数字待排序,9对比较
//第二趟:9个数字待排序,8对比较
//第三趟:8个数字待排序,7对比较
//……
//第九趟:2个数字待排序,1对比较
//sz-1是需要排序的趟数,sz-1-i是每一趟排序是两
//两相互比较的对数
{
if (arr[j] > arr[j + 1])
{
//交换
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
//数组传参
int arr[10] = { 1,4,6,3,2,5,8,9,7,0 };
//设计一个函数对arr数组进行排序——冒泡排序的算法
bubble_sort(arr);
//数组在传参的时候传 数组名 就可以了
//传arr[]是错的,传arr[10]是错的
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
在vs2019上运行的结果是:
经过调试,发现错误的根源在这里:
?sz=1 => sz-1=0 => for循环不执行。
本质上:
?既然传过去的是首元素的地址,那么必定为指针接收,指针的大小sizeof(arr)在32位机器上是4个字节,sizeof(arr[0]),整形第一个元素的大小是4个字节,所以sz=1。
既然sz在里头不好算出来,那么我们可以在外头算一下,那么传过去,接收的还要加一个sz:
#include <stdio.h>
void bubble_sort(int arr[],int sz )
{
int i = 0;
for (i = 0; i < sz - 1; i++)
{
int j = 0;
for (j = 0; j <sz-1-i ; j++)
{
if (arr[j] > arr[j + 1])
{
//交换
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[10] = { 1,4,6,3,2,5,8,9,7,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr,sz);
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
?此时运行的结果是:
?当然,因为传的是首地址,所以也可以这样定义:
void bubble_sort(int* arr, int sz)
?
思考:数组名的本质是什么?
我们经常会说数组名是首元素地址,那么这个说法对不对呢?这里我们可以验证一下。
?由此看来,数组名就是数组首元素的地址。
其实,这里还有两种特例:
1.sizeof(数组名)-- - 数组名表示整个数组-- - 计算的是整个数组的大小,单位是字节
?如果这种情况下,数组名是首元素的地址,那么,在32位机器下,程序运行出来的结果应该就是4,可是打印出来的是20;
2.&数组名-- - 数组名表示整个数组-- - 取出的是整个数组的地址。(这种情况下,明显是成立的,地址是一串编号,总不能说编号的编号吧)
除上述两种情况以外,其余情况数组名均表示首元素地址!
思考:数组地址和数组首元素地址有什么区别?
两者的地址值是一样的,但是含义和使用不同。
首元素的地址+1=>跳过一个元素(这个是整形,4个字节)
数组的地址+1 => 跳过一个数组(5个元素,每个大小是4个字节)
?
小结
这篇博客总结的是关于数组的原理的知识点 ,后面会总结出关于数组的应用实例。
有啥不足的地方,欢迎提出来一起共同进步哦!
如果喜欢这篇博客的话,欢迎铁汁们动动你们的小手,一键三连哦!
?
?
|