C语言基础之数组

上一篇讲述了C语言函数的使用,本文讲述数组的相关概念,通过一维数组、二维数组、数组越界等详细讲解数组相关的具体内容,以辅助读者了解并掌握数组相关概念。

一维数组

一维数组的定义与创建

若无数组,我们要存储一堆类型相同的数值,就要创建一系列类型相同的变量,这样带来了代码复杂度。因此C语言给出了数组类型。C语言中数组的定义是:数组是一组相同类型元素的集合。

一维数组的创建:type_t arr_name [const_n]; 其中type_t 是指数组的元素类型,const_n 是一个常量表达式,用来指定数组的大小。

注:对于数组创建,在C99标准之前, [] 中要给一个常量才可以,不能使用变量。但在C99标准中支持了变长数组的概念,即数组的大小可以使用变量指定,但是这样的数组不能初始化。

一维数组的初始化和使用

数组的初始化 是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。

当然数组在创建的时候如果想不指定数组的确定的大小就必须得初始化,数组的元素个数根据初始化的内容来确定

cpp 复制代码
#include<stdio.h>
void print_arr(int* arr, int len)
{
	for (int i = 0; i < len; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}
int main()
{
	int arr1[10] = { 1,2,3,4,5 };//不完全初始化,剩余元素都是0
	int arr2[10] = { 0 };//不完全初始化,剩余元素都是0
	int arr3[5] = { 2,3,4,5,6 };//完全初始化

	printf("print arr 1:->");
	print_arr(arr1, 10);
	printf("print arr 2:->");
	print_arr(arr2, 10);
	printf("print arr 3:->");
	print_arr(arr3, 5);

	char str[] = "hello world";
	return 0;
}

由上述代码可知不完全初始化,指的是局部初始化,即对数组按从前往后(也可以说是,从低地址到高地址)对元素依次赋初值,剩余的元素则默认为0。所以对于 int arr2[10] = { 0 }; 这种,不要认为是将全部元素的初值附为0,实则是只将第一个元素赋初值为0,其余元素默认为0。

而完全初始化,则更容易理解,即将数组中的元素全部赋初值。

定义数组时,[]中可以省略数组长度,C语言会自动计算数组长度。

数组的使用: 对于数组的使用又一个操作符 [],即下标引用操作符。有了下标访问操作符,我们就可以轻松的访问到数组的元素了。

举个例子:

总结:

1.数组是通过下标进行访问的,下标是从0开始。

2.数组的长度是可以通过计算得到的。

一维数组的输入

明⽩了数组的访问,当然我们也根据需求,⾃⼰给数组输⼊想要的数据,如下:

cpp 复制代码
#include<stdio.h>
int main()
{
	int arr[10];
	for (int i = 0; i < 10; i++)
	{
		scanf("%d", &arr[i]);
	}
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

一维数组在内存中的存储

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

cpp 复制代码
#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	for (int i = 0; i < 10; i++)
	{
		printf("&arr[%d] = %p\n", i, &arr[i]);
	}
	return 0;
}

小Tips

1.%p------用来打印数据存放的地址,输出地址的形式一般是以16进制输出的

2.十六进制对应十进制与二进制如下表

|------------|------|------|------|------|------|------|------|------|
| 十六进制数字 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 十进制值 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 二进制值 | 0000 | 0001 | 0010 | 0011 | 0100 | 0101 | 0110 | 0111 |
| 十六进制数字 | 8 | 9 | A | B | C | D | E | F |
| 十进制值 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 二进制值 | 1000 | 1001 | 1010 | 1011 | 1100 | 1101 | 1110 | 1111 |

从输出的结果分析,数组随着下标的增长,地址从小到大变化,并且每两个相邻的元素之间相差4所以我们得出结论:数组在内存中是连续存放的。这就 为后期我们使⽤指针访问数组奠定了基础(在讲指针的时候我们在再讲,这⾥暂且记住就⾏)。

二维数组

二维数组的创建和初始化

二维数组的创建和一维数组类似,只不过多了行列之分。创建语法如下:
type arr_name[常量值1][常量值2];
在创建变量或者数组的时候,给定⼀些初始值,被称为初始化。
那⼆维数组如何初始化呢?像⼀维数组⼀样,也是使⽤⼤括号初始化的。话不多说,直接上代码:

cpp 复制代码
#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,6} };

	return 0;
}

上述代码,arr1数组是最标准的初始化,对于arr1数组中每行元素的初始化,都再用{}括起来,使代码更清晰明了。

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

arr数组的初始化,是非常特殊的初始化方式。行是可以省略的,但列不能省略。该初始化方式可以借助下图理解。

对于一维数组来说,在初始化时无需知道它有多少个元素。但对于二维数组而言,不需要知道二维数组有多少行,但需要知道二维数组的列数以确定一行有多大。

二维数组的使用

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

cpp 复制代码
#include<stdio.h>
int main()
{
	int arr[][3] = { 1,2,3,4,5,6,7,8,9 };
	for (int j = 0; j < 3; j++)
	{
		for (int i = 0; i < 3; i++)
		{
			printf("%d ", arr[j][i]);
		}
		printf("\n");
	}
	printf("arrp[1][1] = %d\n", arr[1][1]);
	return 0;
}

二维数组在内存中的存储

二维数组在内存中的存储是怎么样的呢?你是不是认为一行内部的元素是连续存储的,而行与行之间的存储是离散的。然而实际是,**二维数组在内存中的存储和一维数组一样,无论是行内部还是行之间都是连续存储的。**看看下面这段代码吧。

cpp 复制代码
#include<stdio.h>
int main()
{
	int arr[][3] = { 1,2,3,4,5,6,7,8,9 };
	for (int j = 0; j < 3; j++)
	{
		for (int i = 0; i < 3; i++)
		{
			printf("&arr[%d][%d] = %p\n", j,i,&arr[j][i]);
		}
	}
	return 0;
}

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

数组越界

  1. 数组的下标是有范围限制的。

  2. 数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。

  3. 所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。

  4. C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就是正确的,所以我们在写代码时,最好自己做越界的检查。

看一个经典例子

cpp 复制代码
#include <stdio.h>
int main()
{
    int i = 0;
    int arr[] = {1,2,3,4,5,6,7,8,9,10};
    for(i=0; i<=12; i++)
    {
        arr[i] = 0;
        printf("hello bit\n");
    }
    return 0;
}

运行这段代码,会发现它死循环。下面解释一下这段代码为什么会发生死循环。

数组名的理解

数组名通常情况下是数组首元素的地址。但是有2个例外:

  1. sizeof(数组名),数组名单独放在sizeof()内部,这里的数组名表示整个数组,计算的是整个数组的大小。

  2. &数组名,这里的数组也表示整个数组,这里取的是整个数组的地址。

除此之外,遇到的所有数组名都表示数组首元素的地址。

cpp 复制代码
#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6 };
	//数组首元素地址
	printf("%p\n", arr);
	printf("%p\n", arr + 1);
	printf("\n");

	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0] + 1);
	printf("\n");

	//数组的地址
	printf("%p\n", &arr);
	printf("%p\n", &arr + 1);
	printf("\n");

	//整个数组的大小
	printf("%zd\n", sizeof(arr));
	return 0;
}

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

本文就介绍到这里啦,我们下期再见!

相关推荐
码农小苏2411 分钟前
力扣刷题--476. 数字的补数【简单】
数据结构·算法·leetcode
Swxctx12 分钟前
Go版数据结构 -【4.1 二叉树】
开发语言·数据结构·golang·go版数据结构
流星白龙34 分钟前
【C++算法】6.双指针_有效三角形的个数
开发语言·c++·算法
九离十38 分钟前
初识C语言(四)
c语言·开发语言
是小比特40 分钟前
二叉搜索树(c++版)
数据结构·算法
DdddJMs__1351 小时前
C语言 | Leetcode C语言题解之第443题压缩字符串
c语言·leetcode·题解
nuo5342021 小时前
数学期望专题
c语言·c++·算法·概率论
霍金的微笑1 小时前
LeetCode
算法·leetcode·职场和发展
菜鸟求带飞_1 小时前
算法打卡:第十一章 图论part10
java·数据结构·算法·图论
窜天遁地大吗喽1 小时前
分层图 的尝试学习 1.0
学习·算法·图论