【C语言初阶学习笔记】数组(超详细内容总结)

 

目录

前言:

一 、一维数组的创建和初始化

        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个字节)

 

小结

这篇博客总结的是关于数组的原理的知识点 ,后面会总结出关于数组的应用实例。

有啥不足的地方,欢迎提出来一起共同进步哦!

如果喜欢这篇博客的话,欢迎铁汁们动动你们的小手,一键三连哦!

 

 

  • 25
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 17
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

哎呀是小张啊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值