数组
数组
什么是数组,数组有什么用,在写代码过程中我们为什么要建立数组…
数组的概念
- 数组是一种数据结构,它是一组相同类型的元素按照一定顺序排列的集合
- 数组中可以存放一个或多个数据,但元素个数不能为零
- 数组存放的多个数据,类型是相同的
- 数组通常储存多个相同的类型数据,在内存中是连续存放多个数据元素的
- 数组通常分为一维数组和多维数组,其中多维数组比较常见:
- 那我们为什么要创建数组呢?C语言中建立数组的主要原因包括:
1.有利于存储和访问一系列相同类型的数据。提供了一种方便且高效的方式来处理数据,我们可以通过索引快速定位和操作数组中的元素。
2.节省内存空间。数组在内存中是连续存储的,相邻元素之间没有额外的空间开销,故可以有效地利用内存空间。
3.方便进行数据的遍历和处理。使用循环结构可以方便地遍历数组中的所有元素,以至于我们进行统一的处理操作。
4.支持多维数组。多维的数组,可以方便表示和高效处理多个维度的数据结构。
1. 一维数组的创建和初始化
1.1数组的创建
- 创建一维数组的基本语法:
type arr_name[常量值];
type
是指数组中存放的数据类型,可能是int
,char
,short
,float
等类型,当然也可以是自定义类型;arr_name
指的是数组的名字,在命名时我们尽量要起的有意义;[]
中的常量值是用来指定数组大小的,具体大小需要我们根据实际来指定;
例如;
- 我们创建一个数组来储存我们班的语文成绩:
int Chinese[41] ;
- 创建一个字符类型的数组:
char wwm[10];
- 创建一个整型数组:
int wwm[41];
- 双精度浮点型数组:存储双精度浮点数值。
double doubleArray[3]; // 声明一个包含3个双精度浮点数的数组
- 结构体数组:存储结构体类型的值。
typedef struct {
int id;
char name[50];
} Student;
Student studentArray[10]; // 声明一个包含10个Student结构体的数组
… … … …
1.2数组的初始化
- 在数组创建以后,我们通常会给数组一个初始值,这些初始值我们常用大括号
{}
将其括起来;
1.完全初始化
#include <stdio.h>
int main() {
// 初始化一个包含5个学生成绩的数组
int scores[5] = {90, 85, 78, 92, 88}; //**完全初始化**
// 打印数组中的每个成绩
for (int i = 0; i < 5; i++) {
printf("学生%d的成绩是:%d\n", i + 1, scores[i]);
}
return 0;
}
2.部分初始化(不完全初始化)
int arr[5] = {6};//不完全初始化;
//将第一个元素初始化为6,其它剩下的5个元素默认初始化值为0
3.错误初始化
int arrr[2] = {1,2,3,4,5,6};//初始化元素超过了数组定义的元素个数
2.数组的类型
- 数组的类型是自定义类型,去掉数组名剩下的就是数组的类型
例如:
int arr1[100];
arr1
的数组类型就是:int [100]
```c
int arr2[200];
arr2
的数组类型是:int [200]
int arr3[300];
arr3
的数据类型是:int [300]
3.一维数组的具体使用
- 一维数组可以存放数据,存放数据是为了方便对其操作,那我们具体该如何使用呢?
3.1数组下标
C语言中数组的下标是从0
开始,假如有数组里有n
个元素,那么最一个元素的下标就是n-1
,下标就是元素在数组中的编号,我们调用索引它的时候就是根据这个下标来访问的。
例如:
int arr[10] = {1,2,3,4,5,6,7,8,9,10;
3.2 下标引用操作符[]
- 我们知道了数组的访问是通过下标编号来访问的,那具体怎么操作呢?没错,这时候就要用到下标引用操作符
[]
比如:
我们访问下标为6的元素,那我们就可以结合数组名来访问:arr[6]
再者,访问下标为8的元素:arr[8]
#include <stdio.h>
int main(){
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%d\n",arr[6]);
printf("%d\n",arr[8]);
return 0;
}
3.3数组元素的打印
- 学会了如何访问数组元素,现在我们就可以同过循环的方式来遍历输出数组中的所有元素
- 我们知道访问数组只要知道数组的下标就可以,那我们要遍历输出数组中的所有元素,只需要通过循环来产生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.4数组的输入
- 学习了数组的访问和遍历输出,那么根据实际需求我们也需要输入自己想要的数据,所以我们同样可以将
scanf
和for
循环结合起来使用输入我们需要的数据:
#include <stdio.h>
int main(){
int arr[10] = {0};//数组的初始化
int i = 0;
for(i = 0;i < 10;i++){
scanf("%d",&arr[i]);//for循环遍历每一个数组空间,
//依次让scanf记录我们的数据并赋值到数组中
}
for(i = 0;i < 10;i++){
printf("%d ",arr[i]);//遍历输出所有元素
}
return 0;
}
4.一维数组在内存中的储存
- 学习了数组的访问和遍历输出,那么我们对于数组的使用基本没有问题了,那么要深入来哦姐数组,我们就需要来了解它在内存中是如何储存的;
- 因此我们首先需要依次打印数组元素的地址:
#include <stdio.h>
int main()
{
int arr[] = { 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个字节);
- 故我们得出结论:数组在内存中是连续存放的;
5.sizeof
计算数组元素的个数
- 遍历数组的时候,我们需要知道数组有多少个元素才能更好的输出,那么C语言中我们可以通过
sizeof
来确定; sizeof
是C语言中的关键字,是用来计算数据类型或对象在内存中的大小(以字节为单位),当然同样也可以计算数组的大小
#include <stdio.h>
int main (){
int arr[] = {1,2,3,4,5,6,7,8,9,10};
printf("%d\n",sizeof(arr));
return 0;
}
- 数组所占内存总大小,输出为40,单位字节**(一个整型是4个字节,10个元素就是10 * 4 = 40)**
- 数组中的元素的类型是相同的,所以我i们只需要计算出一个元素的所占字节大小,通过数组字节总大小与一个元素字节大小相除即可求出元素个数
#include <stdio.h>
int main() {
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", sizeof(arr[0]));//计算一个元素的大小,单位字节
return 0;
}
- 一个元素大小是4个字节
#include <stdio.h>
int main() {
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d\n", sizeof(arr)/sizeof(arr[0]));
return 0;
}
- 得出的结果是10,说明数组有10个元素;
- 所以在代码中我们就可以不用把数组的元素个数固定写死,运用
sizeof
计算数组的元素个数,不管数组如何变化,数组元素个数的计算同样也会随之变化
6.二维数组的创建
6.1二维数组的概念
- 前面学习的一维数组的数组元素类型是内置类型,假如我们把一维数组作为数组元素,这是就变成了二维数组,那如果把二维数组作为数组元素的数组就变成了三维数组,以此类推。但是通常我们将二维以上的数组称为多维数组
- 二维数组是一个可以存储多个一维数组的数组,或者说是一个矩阵。在二维数组中,数据以行和列的形式组织,每个元素都有一个行索引和一个列索引。
6.2 二维数组的创建
- 二维数组的基本语法:
type arr_name[常量值1][常量值2];
例如:
int arr[5][2];
char data[3][4];
- 数组有5行,每行有2个元素,
int
表示数组的每个元素是整型,arr
是数组名;
7.二维数组的初始化
- 在创建变量时我们通常会给变量赋初值,这个过程叫做初始化;
7.1不完全初始化
- 当使用二维数组时,我们选择不完全初始化它,这意味着只为数组中的一部分元素提供初始值,而其余元素将自动使用默认值(对于基本数据类型,通常是0)
例如:
int arr[3][5] = {2,3};
int arr[2][3] = {0};
#include <stdio.h>
int main() {
int array[3][3] = {
{1, 2}, // 第一行初始化了两个元素,第三个元素使用默认值0
{3}, // 第二行只初始化了一个元素,其余两个使用默认值0
{4, 5, 6}// 第三行初始化了所有元素
};
// 打印数组内容
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", array[i][j]);
}
printf("\n");
}
return 0;
}
- 使用花括号省略来初始化部分行:
#include <stdio.h>
int main() {
int array[3][3] = {
{1, 2}, // 第一行初始化了两个元素
{3}, // 第二行只初始化了一个元素
// 第三行没有初始化任何元素,所有元素都将使用默认值0
};
// 打印数组内容
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", array[i][j]);
}
printf("\n");
}
return 0;
}
7.2完全初始化
int arr[3][6] = {1,2,3,4,5,6,2,3,4,5,6,7,3,4,5,6,7,8}
#include <stdio.h>
int main() {
int array[2][3] = {
{1, 2, 3}, // 第一行完全初始化
{4, 5, 6} // 第二行完全初始化
};
// 打印数组内容
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", array[i][j]);
}
printf("\n");
}
return 0;
}
7.3 按照行初始化
int arr[4][3] = {{1,2},{2,3},{3},{4}}
- 初始化过程中可以省略行,但不能省略列
int arr1[][6] = {1,2,3,4}
int arr2[][6] = {1,2,3,4,5,6,7}
int arr3[][6] = {{1,2},{2,3},{3,4}}
8.二维数组的使用
8.1 二维数组的下标
- 根据C语言的规定,二维数组的下标的**行是从0开始,列也是从0开始**;
- 二维数组的访问同样是通过下标来访问,但是有行和列之分,只要我们确定了行和列就能锁定二维数组中的具体位置的元素;
例如:
int arr[3][5] = {1,2,3,4,5,2,3,4,5,6,3,4,5,6,7}
- 如果我们要找2行3列的元素,那么根据图中的下标就能快速找到元素,即2行3列的元素是:6
#include <stdio.h>
int main() {
int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
printf("%d",arr[2][3]);
return 0;
}
8.2 二维数组的输入和输出
- 同样的我们只需要按照一定的规律产生行和列,通过
for
和scanf
获取行和列的范围,即可对二维数组进行数据的输入和输出
#include <stdio.h>
int main() {
int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
int i = 0;//遍历行
//输入
for (i = 0; i <= 2; i++) {
int j = 0;
for (j = 0; j <= 4; j++) {
scanf("%d",&arr[i][j]);
}
}
//输出
for (i = 0; i <= 2; i++)
{
int j = 0;//遍历列
for (j = 0; j <= 4; j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
return 0;
}
8.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个字节,故我们可以得出结论二维数组中的每个元素都是连续存放的
9.C99中的变长数组
C99标准之前:
- C语⾔在创建数组的时候,数组大小的指定只能使用常量、常量表达式,或者如果我们初始化数据的话,可以省略数组大小
- 缺点:创建数组不够灵活,数组大了浪费空间,数组又小了不够用
- 如:
int arr1[10];
int arr2[6+6];
int arr3[];
C99中:
- 提供了一个变长数组(variable-length array,简称VLA)
- 允许我们可以使用变量指定数组大小。
- 如:
int n = a + b;
int arr[n];
上面的数组arr
就是变长数组,因为它的元素个数取决于n
的大小,编译器 无法提前得知,只有运行时才知道n
的大小;
- 变长数组的根本特征在于,无法事先知道数组长度,只有运行时才能确定,所以变长数组不能初始化,好处在于,程序员在写代码时不必随意指定数组长度,在运行时才为数组分配精确的长度;
- 值得注意的是:变长数组是根据变量大小来指定元素个数,而不是说数组的大小时可变的。数组的大小可以根据变量来指定,但是数组的大小一旦创建就无法再改变了。
- 下面是在
gcc
编译器上测试的结果:
#include <stdio.h>
int main()
{
int n = 0;
scanf("%d", &n);//根据输⼊数值确定数组的⼤⼩
int arr[n];
int i = 0;
for (i = 0; i < n; i++)
{
scanf("%d", &arr[i]);
}
for (i = 0; i < n; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
10.数组练习
10.1 多个字符从两端移动,向中间汇聚
#include <stdio.h>
#include <stdlib.h>
#include <Windows.h>
int main()
{
char arr1[] = "Welcome to wuahan !!!!";
char arr2[] = "**********************";
int left = 0;
int right = strlen(arr1) - 1;
while (left <= right)
{
Sleep(1000);
arr2[left] = arr1[left];
arr2[right] = arr1[right];
/*system("cls");*/
left++;
right--;
printf("%s\n", arr2);
}
return 0;
}
10.2 二分查找(折半查找)
- 二分查找的前提是有序;
#include <stdio.h>
int main()
{
// 定义一个已排序的整数数组
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
// 定义左右指针,初始时左指针指向数组的第一个元素,右指针指向数组的最后一个元素
int left = 0;
int right = sizeof(arr) / sizeof(arr[0]) - 1; // 注意这里减去1,因为数组索引是从0开始的
// 定义要查找的关键字
int key = 6;
// 定义标志变量,用来记录是否找到关键字
int flag = 0;
// 计算中间位置,这里使用了一个技巧来防止在left和right值很大时,left + right可能溢出
int mid = left + (right - left) / 2;
// 当左指针不大于右指针时,继续查找
while (left <= right) {
// 如果中间元素大于关键字,则关键字在左半部分
if (arr[mid] > key)
{
right = mid - 1;
}
// 如果中间元素小于关键字,则关键字在右半部分
else if (arr[mid] < key)
{
left = mid + 1;
}
// 如果中间元素等于关键字,则找到关键字
else
{
flag = 1; // 设置找到标志
printf("找到了,下标是:%d\n",mid); // 输出找到的关键字下标
break; // 跳出循环
}
// 重新计算中间位置
mid = left + (right - left) / 2;
}
// 如果没有找到关键字
if (flag == 0) {
printf("找不到!\n");
}
return 0;
}
- 注意:
1.sizeof(arr) / sizeof(arr[0]) - 1
用于计算数组的长度,并减去1得到右指针的初始值。因为数组索引是从0
开始的,所以最后一个元素的索引是数组长度 - 1
。
2.left + (right - left) / 2
是计算中间索引的公式,这种写法可以避免left
和right
的和过大时溢出的问题。
3.flag
是一个标志变量,用来标记是否找到了关键字。如果找到了,就输出其下标,并跳出循环。
4.如果循环结束后flag
仍然为0
,则表示关键字不在数组中,输出“找不到!”。