C语言之数组(一维数组和二维数组)

328 阅读13分钟

一、一维数组

1.数组的概念

数组是一组相同类型元素的集合。 从这个概念中我们就可以发现2个有价值的信息:

  • 数组中存放的是1个或者多个数据
  • 数组中存放的数据,类型是相同的

2..一维数组的创建与初始化

2.1数组的创建

数组创建的基本语法如下:

type arr_name[常量值];

存放在数组的值被称为数组的元素,数组在创建的时候可以指定数组的大小和数组的元素类型。

  • type指定的是数组中存放数据的类型,可以是:char、short、int、float等,也可以自定义的类型存放在数组的值被称为数组的元素,数组在创建的时候可以指定数组的大小和数组的元素类型。

  • arr_name指的是数组名的名字,这个名字根据实际情况,起的有意义就行。

  • [] 中的常量值是用来指定数组的大小的,这个数组的大小是根据实际的需求指定就行。

我们现在想存储某个班级的20人的数学成绩,那我们就可以创建一个数组,如下: 比如:

int math[20];

当然我们也可以根据需要创建其他类型和大小的数组:

char ch[8];
double score[10];

2.2数组的初始化

有时候,数组在创建的时候,我们需要给定一些初始值值,这种就称为初始化的。 那数组如何初始化呢?数组的初始化一般使用大括号,将数据放在大括号中。

//完全初始化
int arr[5] = {1,2,3,4,5};
//不完全初始化
int arr2[6] = {1};
//第一个元素初始化为1,剩余的元素默认初始化为0
//错误的初始化 - 初始化项太多
int arr3[3] = {1, 2, 3, 4};
//错误的初始化 - 初始化不是使用()
int arr4[] = (1,2,3,4);

2.3数组的类型

数组也是有类型的,数组算是一种自定义类型,去掉数组名留下的就是数组的类型。

int arr1 [10];
int arr2 [12];
ch char[5];

arr1 数组的类型是 int [10] arr2 数组的类型是 int[12] ch 数组的类型是 char [5]

3.一维数组的使用

一维数组可以存放数据,存放数组的目的是对数据的操作,那我们如何使用一维数组呢?

1.数组下标

C语言中规定数组是有下标的,下标是从0开始的,假设数组有n个元素,最后一个元素的下标是n-1, 下标就相当于数组元素的编号,如下:

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

数组元素和下标 在C语言中数组的访问提供了一个操作符[],这个操作符叫:下标引用操作符。

有了下标访问操作符,我们就可以轻松的访问到数组的元素了,比如我们访问下标为7的元素,我们就可以使用 arr\[7] ,想要访问下标是3的元素,就可以使用 arr\[3] ,如下代码:

#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%d\n", arr[7]);
//8
printf("%d\n", arr[3]);
//4
return 0;
}

输出结果:

屏幕截图 2024-11-06 162918.png

2.数组元素的打印

接下来,如果想要访问整个数组的内容,那怎么办呢?

只要我们产生数组所有元素的下标就可以了,那我们使用for循环产生0~9的下标,接下来使用下标访问就行了。

如下代码:

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

输出的结果:

屏幕截图 2024-11-06 163149.png

3.数组的输入

明白了数组的访问,当然我们也根据需求,自己给数组输入想要的数据,如下:

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

输入个输出结果:

屏幕截图 2024-11-06 163454.png

4.一维数组在内存中的储存

打印数组元素的地址

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

输出结果我们看看:

屏幕截图 2024-11-06 163806.png 从输出的结结果我们分析,数组随着下标的增⻓,地址是由小到大变化的,并且我们发现每两个相邻的元素之间相4(因为一个整型是4个字节)。

所以我们得出结论:数组在内存中是连续存放的,随着数组下标的增⻓,地址是由低到高变化的。

微信图片_20241106165730.jpg

数组元素在内存中是连续存放的。

二、二维数组

1.二维数组的创建与初始化

1.1二维数组的概念

我们前面学习的数组被称为一维数组,数组的元素都是内置类型的,如果我们把一维数组做为数组的 元素,这时候就是二维数组,二维数组作为数组元素的数组被称为三维数组,二维数组以上的数组统 称为多维数组。

微信图片_20241106170107.jpg

1.2二维数组的创建

int arr[3][4];
char arr[3][4];
double arr [3][4];

屏幕截图 2024-11-04 215358.png

1.3二位欸数组的初始化

int arr[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};//完全初始化
int arr[3][4]={1,2,3,4,5,6,7}//元素不够时默认为0
int arr[3][4]={{1,2},{3,4}};//不完全初始化
int arr[][4]={{1,2,3,4},{5,6}}//二维数组如果有初始化,行可省,列不可省
如果 int arr [][4]={{1,2,3,4},{2,3}};

微信图片_20241106170114.jpg

为什么列不可省略:省略列后电脑不知道数组中的元素该如何放。

2.二维数组的使用

二维数组的使用也是通过下标的方式

赋值+访问

#include<stdio.h>
int main()
{
	int arr[3][4];
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 4; j++)
		{
			scanf_s("%d", &arr[i][j]);
	    }
    }
    //访问全部元素
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 4; j++)
		{
			printf("%d", arr[i][j]);
		}
		printf(" \n");
	}
    printf("%d", arr[0][3]);//访问单个元素
	return 0;
}

结果:

屏幕截图 2024-11-04 211621.png

3.二维数组在内存中的储存

像一维数组一样,我们如果想研究二维数组在内存中的存储方式,我们也是可以打印出数组所有元素 的地址的。代码如下:

#include <stdio.h>
int main()
{
	int arr[3][5] = { 0 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 5; j++)
		{
			printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]);
		}
	}
	return 0;
}

屏幕截图 2024-11-05 145215.png

从输出的结果来看,每一行内部的每个元素都是相邻的,地址之间相差4个字节,跨行位置处的两个元 素(如:arr[0] [4]和arr[1] [0])之间也是差4个字节,所以二维数组中的每个元素都是连续存放的。 如下图所示:

数组.png 二维数组的每一行在内存中连续存放,arr [3] [4] 与arr [12]在内存中的布局方式是一样的,只是访问形式有所差异 了解清楚二维数组在内存中的布局,有利于我们后期使用指针来访问数组的学习。

三、数组越界访问

1.什么是数组越界访问?

所谓数组越界,就是指数组下标变量的取值超过了初始定义时的大小,导致对数组元素的访问出现在数组的范围之外。

我们通过数组的下标来得到数组内指定索引的元素,这称作对数组的访问。

如果一个数组定义为有n个元素,那么,对这n个元素(下标为0 到n-1的元素)的访问都合法,如果对这n个元素之外的访问,就是非法的,称为“越界。

数组占用了一段连续的内存空间。然后,我们可以通过指定数组下标来访问这块内存里的不同位置。因此,当你的下标过大时,访问到的内存,就不再是这个数组“份内”的内存。你访问的,将是其它变量的内存了。

2.访问越界会出现什么样的结果?

#include <stdio.h>
int main()
{
	int arr[]= {1,2,3,5,6,7};
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

结果:

屏幕截图 2024-11-05 153016.png 首先,它并不会造成编译错误!就是说,C,C++的编译器并不判断和指出你的代码“访问越界”了,编译器==可能不会报错==。一个明明是错误的东西,就这样“顺利”地通过了编译。

数组访问越界在运行时,它的表现是不定的,有时似乎什么事也没有,程序一直运行(当然,某些错误结果已造成);有时,则是程序一下子崩溃。因此在使用数组时,一定要在编程中判断是否越界以保证程序的正确性。

常见的错误就是数组的size值和下标访问值弄错,数组的下表是从0开始的,最大的访问值是size-1。

3.解决方案

可以算出元素个数

#include <stdio.h>
int main()
{
	int arr[]= {1,2,3,5,6,7};
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

屏幕截图 2024-11-05 153343.png

二维数组的行和列也可能存在越界。

四、数组作为函数参数

数组在传参时给个数组名就可以 数组传参时,形参有2种写法:

  • 1.数组
  • 2.指针

1.数组传参

1.1 用数组接收

数组传参C 语言中,数组传参时可以选择使用指针接收,也可以选择不使用指针。以下是一个不使用指针接收数组参数的例子:

#include <stdio.h>

void printArray(int arr[], int size) 
{
    for (int i = 0; i < size; i++) 
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main()
{
    int arr[] = {1, 2, 3, 4, 5};
    int size = sizeof(arr) / sizeof(arr[0]);

    printArray(arr, size);
    
    return 0;

}

在这个例子中,我们定义了一个名为 printArray 的函数,它接收一个数组和数组长度作为参数。在函数内部,我们使用循环遍历数组并打印数组的元素。

在 main 函数中,我们定义了一个名为 arr 的数组,并使用 sizeof 运算符计算数组的长度。然后我们调用 printArray 函数,将 arr 数组和数组长度作为参数传递。

这个例子展示了如何在 C 语言中不使用指针接收数组参数。但是,需要注意的是,在这种情况下,数组会被复制,因此可能会影响程序的性能。如果数组非常大,使用指针接收数组参数可能会更加高效。

1.2 用指针接收

在 C 语言中,数组传参时可以选择使用指针接收,也可以选择不使用指针。以下是一个同时使用指针和数组符号接收数组参数的例子:

#include <stdio.h>

void printArray(int* arr, int size)
{
    for (int i = 0; i < size; i++) 
    {
        printf("%d ", *arr++);
    }
    printf("\n");
}

int main()
{
    int arr[] = {1, 2, 3, 4, 5};
    int size = sizeof(arr) / sizeof(arr[0]);

    printArray(arr, size);
    10
    return 0;

}

1.1 1.2 结果均是:

屏幕截图 2024-11-06 122038.png

2.冒泡排序

冒泡排序:两个相邻元素进行比较

冒泡排序错误设计:

//把数组的数据排成升序
#include <stdio.h>
//参数写成数组形式,本质上还是指针
void bubble_sort(int arr[])
{
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[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[] = { 9,6,7,4,8,5,1,4,3,2,0 };
	bubble_sort(arr);
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		printf("%d", arr[i]);
     }
	return 0;
}

结果:不报错,但无法得出正确答案

原因:数组作为函数参数,是把数组首元素的地址传过去,在函数内部不可求元素个数(sz)。

我们发现在函数内部是没有正确获得数组的元素个数。

数组名是数组首元素的地址;

那么在数组传参的时候,传递的是数组名,也就是说本质上数组传参本质上传递的是数组首元素的地址。

所以函数形参的部分理论上应该使用指针变量来接收首元素的地址。

那么在函数内部我们写 sizeof(arr) 计算的是一个地址的大小(单位字节)而不是数组的大小(单位字节)。 正是因为函数的参数部分是本质是指针,所以在函数内部是没办法求的数组元素个数的。

正确代码:

#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[] = { 9,6,7,4,8,5,1,4,3,2,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr,sz);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d", arr[i]);
	}
	return 0;
}

结果: 屏幕截图 2024-11-05 203736.png

3.数组名是什么?

数组名是数组首元素的的地址,但有两个例外:

  • 1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
  • 2.&数组名,这里取出的是整个数组的地址。

3.1一维数组数组名的理解

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	int n = sizeof(arr);
	printf("%d\n", n);
	return 0;
}

结果: 屏幕截图 2024-11-06 100844.png

arr&arr[0]表示相同为首元素的地址,sizeof(arr)这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节,该数组内有10个元素,每个元素占4个字节,所以数组大小为40

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	printf("%p\n", &arr);
	return 0;
}

结果:

屏幕截图 2024-11-06 100801.png

三个打印结果相同,有什么不同呢?

#include<stdio.h>
int main()
{
	int arr[10] = { 0 };
	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);
	return 0;
}

结果:

屏幕截图 2024-11-06 101842.png

分析:

微信图片_20241106165910.jpg 由此可以看出,虽然arr,&arr[0],&arr打印结果相同,但其意义却不相同!!!

3.2二维数组数组名的理解

二维数组的数组名也表示首元素地址,取的是第一行所有的地址。

#include<stdio.h>
int main()
{
	int arr[3][4] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]);
	return 0;
}

结果:arr的地址与arr[0]的地址相同,即二维数组的数组名也表示首元素地址,取的是第一行所有的地址。

屏幕截图 2024-11-06 143844.png

#include<stdio.h>
int main()
{
	int arr[3][4] = { 0 };
	printf("%p\n", arr);
	printf("%p\n", arr + 1);
	return 0;
}

结果:

屏幕截图 2024-11-06 105047.png 分析:

微信图片_20241106165901.jpg 打印二维数组的行和列

#include<stdio.h>
int main()
{
	int arr[3][4] = { 0 };
	printf("%d\n", sizeof(arr) / sizeof(arr[0]));// 整个数组的大小/第一行元素的大小 = 行数

	printf("%d\n", sizeof(arr[0]) / sizeof(arr[0][0]));// 第一行的元素大小 / 第一行第一个元素大小 = 列数
	return 0;
}

结果:

屏幕截图 2024-11-06 110739.png 分析:

屏幕截图 2024-11-06 111111.png 二维数组中&arr也表示取出全部元素的地址

代码如下:

#include<stdio.h>
int main()
{
	int arr[3][4] = { 0 };
	printf("%p\n", &arr);
	printf("%p\n", &arr + 1);
	return 0;
}

屏幕截图 2024-11-06 112625.png 一个数组12个元素,占12*4=48个字节,&arr+1跳过了整个数组的地址