C语言基础(6)之数组

本文详细讲解了一维数组的定义、创建、初始化和使用,以及二维数组的创建、初始化、内存存储和数组越界的概念。还讨论了数组作为函数参数和数组名的理解,强调了C语言中数组下标的规则和边界检查的重要性。
摘要由CSDN通过智能技术生成

        上一篇文章我们讲到函数,讲到了如何快速学习库函数等知识。本文通过一维数组、二维数组、数组越界等来详细讲解数组相关的具体内容,帮助大家快速了解并掌握数组这一部分的知识。

1. 一维数组

1.1 一维数组的定义和创建

        在学习数组之前,我们如果要存储一堆类型相同的数值,就要创建一堆类型相同的变量,这样极大的造成了代码复杂度。因此C语言给出了数组类型。C语言中数组的定义是:数组是一组相同类型元素的集合。
        一维数组的创建:type_t  arr_name [const_n];  其中type_t 是指数组的元素类型,const_n 是一个常量表达式,用来指定数组的大小。
        注:对于数组创建,在C99标准之前, [] 中要给一个常量才可以,不能使用变量。但在C99标准中支持了变长数组的概念,即数组的大小可以使用变量指定,但是这样的数组不能初始化
        VS2019、VS2022 这样的IDE不支持C99 中的变长数组,而Dev-C++编译器支持C99中的变长数组。(如下图)

1.2 一维数组的初始化和使用

        数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。
        当然数组在创建的时候如果想不指定数组的确定的大小就必须得初始化,数组的元素个数根据初始化的内容来确定。(看如下一些数组的初始化情况)

#include<stdio.h>
int main() {

	int arr1[10] = { 1,2,3 }; // 不完全初识化,剩余的元素默认都是0
	int arr2[10] = { 0 }; // 不完全初识化,剩余的元素默认都是0
	int arr3[5] = { 1,2,3,4,5 }; // 完全初始化
	// 省略数组的大小,数组必须初始化,数组的大小是根据初始化的内容来确定的
	int arr4[] = { 1,2,3,4 }; 
	int arr5[] = { 0 };
	//int arr6[]; // error

	char arr7[] = "abc"; // abc\0
	char arr8[] = { 'a','b','c' }; // abc
	char arr9[] = { 'a',98,'c' };  // abc

	return 0;
}

        所谓不完全初始化,指的是局部初始化,即对数组按从前往后(也可以说是,从低地址到高地址)对元素依次赋初值,剩余的元素则默认为0。所以对于 int arr2[10] = { 0 }; 这种,不要认为是将全部元素的初值附为0,实则是只将第一个元素赋初值为0,其余元素默认为0。(见如下调试arr1数组中的元素值)
        而完全初始化,则更容易理解,即将数组中的元素全部赋初值。(见如下调试arr3数组中的元素值)

        数组的使用:对于数组的使用我们之前介绍了一个操作符: [] ,即下标引用操作符。它其实就数组访问的操作符。

#include<stdio.h>
int main() {
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	//              0 1 2 3 4 5 6 7 8 9
	printf("%d\n", arr[1]); // [] 下标引用操作符
	int i = 0;
	// 计算数组元素的大小
	int size = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < size; i++) {
		printf("%d ", arr[i]);
	}

	return 0;
}

总结:
        1. 数组是使用下标来访问的,下标是从0开始。
        2. 数组的大小可以通过计算得到。  int sz = sizeof(arr)/sizeof(arr[0]);

1.3 一维数组在内存中的存储

数组在内存中是怎样存储的呢?是离散的?还是连续的?答案是连续存储的。那一起来看看下面这段代码中数组元素在内存中的存放地址吧。

这里先来简单的讲一下几个额外的小知识:
        1. %p  ——  是专门用来打印地址的,输出地址的形式一般是以16进制输出的。
        2. 16进制,即0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,再下一位则进1为10,.... 1F,20,.... 2F。(就好比我们常用的10进制是0,1,2,3,4,5,6,7,8,9,再下一位则要进1为10,.... 19,20,... 29)
        之前讲过整型的大小是4个字节的,看上述结果中arr[0]地址的最后两位为88,arr[1]地址的最后两位为8C,两者中间相隔了88,89,8A,8B,4个字节长度(正好是一个整型的大小),而arr[1]和arr[2]之间相隔了8C,8D,8E,8F,也是4个字节的长度,继续观察计算可知数组中相邻元素的地址都只相差一个整型的长度,因此说明数组中的元素是连续存储的,且是从低地址到高地址进行存储。

2. 二维数组

2.1 二维数组的创建和初始化

        二维数组的创建和一维数组类似,只不过多了行列之分。二维数组的创建:type_t arr_name[行][列];  其中type_t 是指数组的元素类型,const_n 是一个常量表达式,用来指定数组的大小。

对于二维数组的初始化,我们直接上代码演示讲解。

#include<stdio.h>
int main() {
	// 二维数组的初始化
	int arr1[4][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7},{4,5,6,7,8} };
	int arr2[4][5] = { 1,2,3,4,5, 2,3,4,5,6, 3,4,5,7, 4,5,6,8 }; // 不完全初始化,填满才换行
	int arr3[4][5] = { { 1,2,3 }, 2, 3, 4, 5, 6, 3, 4, 5, 6, 7, 4, 5, 6, 7, 8}; 
	int arr4[4][5] = { {1,2,3,},{2,3,4,5},{3,4,5,6,7},{4,5} };
	
	int arr[][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7},{4,5,6,7,8} };	

	return 0;
}

        看如上代码,arr1数组是最标准的初始化,对于arr1数组中每行元素的初始化,都再用{}括起来,使代码更清晰明了。
        对于arr2数组的初始化,又称为不完全初始化,即对每行元素赋初值时不用{}括起来,那这时元素的赋值情况是怎样的呢?这些值会从数组第一行元素开始进行填充,当第一行的元素填满后再继续给下一行元素赋值,如果最后赋值完还有元素未被初始化,则这些元素默认为0。(见如下调试arr2数组中的元素值)

        再看看最为特殊的arr数组的初始化,这是因为二维数组即使初始化了,行是可以省略的,但列不能省略。我们借助下图可以这样去理解:将arr[5]理解为一个新的一维数组名,二维数组的每一行抽象理解为新一维数组的每个元素,这样对于一维数组来说,我们在初始化时可以无需知道它有多少个元素(即不需要知道二维数组有多少行),但需要知道一维数组每个元素的大小(即需要知道二维数组的列数以确定一行有多大)。

2.2 二维数组的使用

二维数组的使用和一维数组一样,也是通过下标来访问数组中的元素的,并且行和列的下标都是从0开始。

#include<stdio.h>
int main() {
	// 二维数组的使用
	int arr[4][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7},{4,5,6,7,8} };
	// 二维数组的行和列都是从0开始的
	printf("%d\n", arr[2][3]);
	int i = 0;
	int j = 0;
	for (i = 0; i < 4; i++) {
		for (j = 0; j < 5; j++) {
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	
	return 0;
}

2.3 二维数组在内存中的存储

        也许大家看到上面画的数组arr[4][5]的图,是否就认为二维数组在内存中的存储是:一行内部的元素是连续存储的,而行与行之间的存储是离散的。当然不是,二维数组在内存中的存储和一维数组一样,无论是行内部还是行之间都是连续存储的,一起来看看下面的代码吧。

#include<stdio.h>
int main() {
	// 二维数组在内存中的存储
	int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6} };
	
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++) {
		for (j = 0; j < 4; j++) {
			printf("&arr[%d][%d] = %p\n",i, j, &arr[i][j]);
		}
		printf("\n");
	}

	return 0;
}

        仔细观察可知一行内部的元素是连续存储的。我们重点观察一行末尾元素与下一行首元素之间的地址间隔:arr[0][3]地址的最后两位为64,arr[1][0]地址的最后两位为68,两者之间的地址大小相差4个字节,而arr[1][3]与arr[2][0]的地址大小也只相差4个字节,因此二维数组中,行与行之间也是连续存储的,也是从低地址向高地址存储。(如下图)

3. 数组越界

        1. 数组的下标是有范围限制的。
        2. 数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。
        3. 所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。
        4. C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的,所以我们在写代码时,最好自己做越界的检查。

4. 数组作为函数参数

        往往我们在写代码时,有时会将数组作为参数传给函数,这时候我们就能在函数内对数组进行操作。

#include<stdio.h>
void test(int arr[10])
{
	arr[5] = 20;
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

int main() 
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	test(arr);

	return 0;
}

5. 数组名是什么(数组名的理解)

我们这里来拓展一个小知识点,来讲讲对于数组名的理解,让大家对数组名有一个更深刻的理解。那么数组名是指整个数组地址,还是指数组首元素的地址呢?
        数组名通常情况下是数组首元素的地址。
        但是有2个例外:
                1. sizeof(数组名),数组名单独放在sizeof()内部,这里的数组名表示整个数组,计算的是整个数组的大小。
                2. &数组名,这里的数组也表示整个数组,这里取的是整个数组的地址。
        除此之外,遇到的所有数组名都表示数组首元素的地址。
关于第一点,我们在之前计算数组大小的时候已经写过代码了,这里就不再仔细的讲述了。重点看看第二点。

        代码如上,如果我们取首元素的地址&arr[0],再对&arr[0]+1,会发现两者之间的地址相差4,即只跳过了一个整型元素的大小,而假如我们对数组名取地址(&arr),再进行&arr+1,地址的最后两位由98变成了C0,经计算两个相差40个字节,即整个arr数组的大小。故&数组名,取的是整个数组的地址。

        不知不觉又到结尾了,那我们下篇文章再见吧!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值