一、一维数组
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;
}
输出结果:
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;
}
输出的结果:
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;
}
输入个输出结果:
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;
}
输出结果我们看看:
从输出的结结果我们分析,数组随着下标的增⻓,地址是由小到大变化的,并且我们发现每两个相邻的元素之间相4(因为一个整型是4个字节)。
所以我们得出结论:数组在内存中是连续存放的,随着数组下标的增⻓,地址是由低到高变化的。
数组元素在内存中是连续存放的。
二、二维数组
1.二维数组的创建与初始化
1.1二维数组的概念
我们前面学习的数组被称为一维数组,数组的元素都是内置类型的,如果我们把一维数组做为数组的 元素,这时候就是二维数组,二维数组作为数组元素的数组被称为三维数组,二维数组以上的数组统 称为多维数组。
1.2二维数组的创建
int arr[3][4];
char arr[3][4];
double arr [3][4];
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}};
为什么列不可省略:省略列后电脑不知道数组中的元素该如何放。
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;
}
结果:
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;
}
从输出的结果来看,每一行内部的每个元素都是相邻的,地址之间相差4个字节,跨行位置处的两个元 素(如:arr[0] [4]和arr[1] [0])之间也是差4个字节,所以二维数组中的每个元素都是连续存放的。 如下图所示:
二维数组的每一行在内存中连续存放,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;
}
结果:
首先,它并不会造成编译错误!就是说,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;
}
二维数组的行和列也可能存在越界。
四、数组作为函数参数
数组在传参时给个数组名就可以 数组传参时,形参有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 结果均是:
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;
}
结果:
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;
}
结果:
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;
}
结果:
三个打印结果相同,有什么不同呢?
#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;
}
结果:
分析:
由此可以看出,虽然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]的地址相同,即二维数组的数组名也表示首元素地址,取的是第一行所有的地址。
#include<stdio.h>
int main()
{
int arr[3][4] = { 0 };
printf("%p\n", arr);
printf("%p\n", arr + 1);
return 0;
}
结果:
分析:
打印二维数组的行和列
#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;
}
结果:
分析:
二维数组中
&arr也表示取出全部元素的地址
代码如下:
#include<stdio.h>
int main()
{
int arr[3][4] = { 0 };
printf("%p\n", &arr);
printf("%p\n", &arr + 1);
return 0;
}
一个数组12个元素,占12*4=48个字节,
&arr+1跳过了整个数组的地址