1.一维数组的创建和初始化
1.1 数组的创建
数组是一组相同类型元素的集合
数组的创建方式:
type_t arr_name [const_n];//type_t数组元素类型 arr_name数组名 const_n常量表达式或常量,用来指定数组的大小 [ ]基本语法形式
int arr[20];
char ch[5];
double date1[20];
double date2[15+5];//常量表达式指定数组的大小
//在支持C99标准的编译器上,指定数组大小也可以用变量
//在C99标准之前,数组的大小必须是常量或者常量表达式
//在C99之后,数组大小可以是变量,为了支持变长数组(数组的大小通过变量来指定)
//下面代码只能在支持C99标准的编译器上编译
int n=10;
int arr1[n];//变长数组不能初始化
变长数组:用变量指定数组的大小
1.2 数组的初始化
在创建数组的同时给它一些合理的值
//不完全初始化 剩余元素默认初始化为0
int arr[10] = {1,2,3};//不止一个元素初始化用{}
//完全初始化
int arr1[10] = {1,2,3,4,5,6,7,8,9,0};
//字符型不完全初始化 剩余元素默认初始化为0(0就是\0,\0的ASCII值是0)
char ch1[10] = {'a','b','c'};
// a b c 0000000
//用双引号引起来的字符串初始化
char ch2[10] = "abc";
//a b c \0 000000
//虽然这两个初始化结果是一样的,但是性质不一样,第一个只放入了abc,默认补了7个0
//第二个放入了abc\0 默认补了6个0
//不指定数组大小,只进行初始化,不指定元素个数
//数组大小根据初始化的内容确定
int arr2[] = {1,2,3};//数组大小为3
char ch3[] = {'a','b','c'};//3
char ch4[] = "abc";//4 多了一个'\0'
1.3 一维数组的使用
[ ] 下标引用操作符,专门用来访问数组元素的操作符
#include <stdio.h>
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9,10};
//局部的数组;在内存的栈区中找了一块连续的空间去存储数据
//数组的每个元素对应一个编号,编号从0开始
//这里的编号就是数组的下标
//知道编号(下标)就可以定位到该元素
//[] 下标引用操作符
arr[4];//访问下标为4的元素 即 5
//打印数组中所有元素
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);//数组元素个数
for( i = 0;i < sz;i++)
{
printf("%d " ,arr[i]); //访问数组元素可以用变量,创建数组大小在不支持C99的编译器下不可以用变量
}
for( i=sz-1;i>=0;i--)//倒序打印
{
printf("%d " ,arr[i]);
}
return 0;
}
注意:
- 数组是使用下标来访问,下标从0开始
- 数组大小可以通过计算得到
1.4 一维数组在内存中的存储
//打印数组每个元素的地址
for(i=0;i<sz;i++)
{
printf("&arr[%d]=%p\n",i,&arr[i])
}
每个元素的地址之间相差4(一个整型元素占4个字节)随数组下标的不断增长,元素地址也在有规律增长。由此可知:数组在内存中是连续存放的
2.二维数组的创建和初始化
2.1 二维数组的创建
二维数组能够存放的数据更多一些,相同类型的数据又出现了好多组,解决多组数据的存储
int arr[3][4];//3行4列 一维数组是一行,二维数组是多行
char arr1[5][10];//5行10列 5行,每一行上有10个元素
2.2 二维数组的初始化
//完全初始化
int arr1[3][4]={1,2,3,4,2,3,4,5,3,4,5,6};
//没有分组直接放入全部数据 每4个分一组放到一行上
//不完全初始化
int arr1[3][4]={1,2,3,4,2,3,4,5,3,4};
//没有给全部数据 前面还是按4个一组分配到每一行,剩下不够的补0
//依次往里放,不够后面默认初始化为0
int arr1[3][4]={{1,2},{3,4},{5,6}};
//用{}分组
//不能省略二维数组列的大小,只能省略二维数组行的大小
int arr2[][4]={{1,2,3,4},{2,3}};
int arr2[][4]={{1,2,3,4,2,3};
//可以不进行分组,每4个一组,剩下不够的补0
//三维数组只能省略第一维,第二维和第三维不能省略
arr1[3][4]={1,2,3,4,2,3,4,5,3,4,5,6};
arr1[3][4]={1,2,3,4,2,3,4,5,3,4};
arr1[3][4]={{1,2},{3,4},{5,6}};
arr2[][4]={{1,2,3,4},{2,3}}; arr2[][4]={{1,2,3,4,2,3};
2.3 二维数组的使用
通过下标方式定位某元素 行/列下标(编号)从0开始
#include <stdio.h>
int main()
{ //先确定行0~2再确定列0~3
int arr[3][4] = {1, 2, 3, 4, 2, 3, 4, 5, 3, 4, 5, 6};
printf("%d\n",arr[2][0]); //打印某个元素
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 4; j++)
{
scanf("%d",&arr[i][j]); //给每个元素重新赋值
//printf("%d ", arr[i][j]);//打印二维数组每一行
}
printf("\n");
}
return 0;
}
注意:
- 访问一个元素或者所有元素都可以通过下标方式(坐标)定位元素
- 计算二维数组的行和列:二维数组看作一维数组时,每一行看作一个元素,每个元素(每一行)都是一个一维数组,所以可以把二维数组理解为一维数组的数组
- 访问第一行arr[0][j]: arr[0]看作第一行的一维数组的数组名(j:0~3);访问第二行arr[1][j] :arr[1]看作第二行的一维数组的数组名 ;访问第三行arr[2][j]: arr[2]看作第三行的一维数组的数组名(类比一维数组:int arr[10]={0}; 访问数组每个元素时采用:arri数组名[下标]对于第一行来说arr[0]就是数组名)
2.4 二维数组在内存中的存储
每两个元素的地址之间相差4,跨行处元素也是相差4个字节,所以二维数组在内存中也是连续存放的
arr[0]为第一行一维数组的数组名
可以将这个二维数组arr[3][4]理解为连续的有12个元素的一维数组arr[12], 这两个数组在内存中的布局方式是一样的,只是访问形式不一样
为什么必须要输入列(列不能省略)?
int arr[][4]={1,2,3,4,5,6};省略了列完全不知道怎么存放,所以不能省略
3.数组越界
数组的下标是有范围限制的,下标从0开始,n个元素中最后一个元素下标为n-1, 使用了小于0或者大于n-1的下标就造成了数组的越界访问。
越界访问编译器不一定报错,并不代表程序是对的
二维数组:行和列也存在越界(在自己范围内越界/在外面范围越界)
4.数组作为函数参数
4.1冒泡排序函数
冒泡排序的核心思想:
两个相邻元素进行比较,如果比较的大小不满足顺序,需要调整顺序。
例: 9 8 7 6 5 4 3 2 1 0 排列成升序
一趟冒泡排序让一个数据来到它最终应该出现的位置上
10个元素需要9趟冒泡排序:采用交换形式排序,9个已经交换到最终应该出现的位置上,剩下的一个已经不要交换了,已经在它最终应该出现的位置上
N个元素需要N-1趟冒泡排序,每一趟冒泡排序内部都是在进行两个相邻元素的比较
错误示范:
#include <stdio.h>
void bubble_sort(int arr[])
//数组传参时,形参有两种写法:
// 1.数组形式 int arr[]直观易理解 并不代表重新创建一个数组
// []里面可以写数字但是写了也用不上所以一般不写
// arr看似是数组本质上是一个指针变量 算sizeof(arr) / sizeof(arr[0])
// 32位 4/4=1
// 2.指针形式 int* arr
{
//趟数
int sz = sizeof(arr) / sizeof(arr[0]); //元素个数
// sz=1 下面循环根本就没有进去所以输出还是未排序的数据
//所以不能在这里求元素个数
int i = 0;
for (i = 0; i < sz - 1; i++)
{ //一趟冒泡排序 相邻两个元素比较
//第一趟10个元素--9次相邻比较 第二趟 9个元素--8次相邻比较
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, 8, 7, 6, 5, 4, 3, 2, 1, 0}; //把数组的数据排列成升序
//将数据内容改变:0123456789 冒泡排序算法进行排序
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;
}
正确示范:
#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, 8, 7, 6, 5, 4, 3, 2, 1, 0};
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]); //元素个数
bubble_sort(arr, sz);
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
4.2数组名是什么?
之前我们总说,数组名就是首元素地址,但是这句话有一点不准确
下面来探讨一下数组名:
#include <stdio.h>
int main()
{
int arr[10];
printf("%p\n", arr); //数组名
printf("%p\n", &arr[0]); //首元素地址
printf("%p\n", &arr);
int n = sizeof(arr);//数组名是首元素地址sizeof(首元素地址)=4/8
int m =sizeof(&arr[0]);
printf("%d\n",n);//输出40 矛盾了
printf("%d\n",m);
return 0;
}
发现:数组名和首元素地址确实是一样的
数组名确实能表示首元素地址,但是有两个例外,1.sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节 2.&数组名。这里的数组名表示整个数组,取出的是整个数组的地址(还是从首元素开始),除了这两个例外,其他的所有的数组名都表示首元素地址。
发现:数组名,首元素地址和&数组名三个都是一样的
那么这三个有什么区别?
#include <stdio.h>
int main()
{
int arr[10];
printf("%p\n", arr); //这里数组名就是首元素地址
printf("%p\n", arr+1);
printf("-----------------\n");
printf("%p\n", &arr[0]); //首元素地址
printf("%p\n", &arr[0]+1);//首元素地址+1跳过4个字节
printf("-----------------\n");
printf("%p\n", &arr); //&数组名:数组的地址
printf("%p\n", &arr+1); //数组的地址+1跳过整个数组去了
//差值是0x28 8*1+2*16=40个字节
return 0;
}
值看起来一样,但是意义不一样
二维数组的数组名的理解
int arr[3][4];
int sz=sizeof(arr);
//sizeof(数组名)中数组名表示整个数组 计算整个二维数组的大小
//&数组名:这里数组名也是表示整个数组 二维数组的地址
printf("%d\n",sz); //48
arr;
//除以上两种特例外,其他情况下二维数组的数组名也表示首元素地址
//二维数组的首元素地址:是&arr[0][0]? 不是
//用第一行的地址表示首元素地址,这里将二维数组看成数组元素是一维数组的数组。一行就是一个元素
//二维数组的首元素的地址:第一行数组的地址
//第一行一维数组的地址,即&arr[0]
//虽然第一行数组地址与第一行数组首元素地址数值相同,但二者意义是不一样的
#include <stdio.h>
int main()
{ //二维数组的数组名:
int arr[3][4];
printf("%p\n", &arr[0][0]); //二维数组中元素arr[0][0]的地址
printf("%p\n", &arr[0][0] + 1); //+1跳到下一个元素(地址相差4byte 一个整型的大小)
printf("--------------------------\n");
printf("%p\n", &arr[0]); //第一行数组的地址 arr[0]是第一行数组的数组名
printf("%p\n", &arr[0] + 1); //+1跳到第二行 (地址相差16byte 4个整型的大小)
printf("--------------------------\n");
printf("%p\n", arr); //二维数组首元素的地址:第一行数组的地址
printf("%p\n", arr + 1); //+1跳到第二行 (地址相差16byte 4个整型的大小)
printf("--------------------------\n");
printf("%p\n", &arr); //二维数组的地址
printf("%p\n", &arr + 1); //+1跳出整个二维数组(地址相差48byte 12个整型的大小)
printf("--------------------------\n");
printf("%d\n", sizeof(arr)); //二维数组的大小 4*12=48
printf("%d\n", sizeof(arr[0])); //第一行数组的大小 4*4=16
printf("%d\n", sizeof(arr[0][0])); // arr[0][0]元素的大小 4
printf("--------------------------\n");
printf("%d\n", sizeof(arr) / sizeof(arr[0])); //行数 3
printf("%d\n", sizeof(arr[0]) / sizeof(arr[0][0])); //列数 4
return 0;
}
计算二维数组的行和列?
行的计算:总数组的大小/一行的大小 sizeof(arr)/sizeof(arr[0]) (在这里的arr[0]就是第一行的数组名,第一行数组名放到sizeof内部计算第一行整个数组的大小)
列的计算:一行的大小/一个元素的大小 sizeof(arr[0])/sizeof(arr[0][0])
5.数组应用实例
- 三子棋
- 扫雷游戏