目录
先补个概念:数组是什么
数组是一组具有相同类型的数据的集合,在一个数组中构成该数组的成员称为数组元素。
一、一维数组
一维数组是由一个下标的数组元素组成的数组。它的定义格式如下:
类型说明符 数组名[常量表达式]
int arr1[10];
char arr2[10];
float arr3[10];
double arr4[10];
//下面的写法不可行
int n=5;
int arr5[n];//即使你预先赋值,它仍然不是常量表达式
常量表达式 可以是整型常量、符号常量;但不能是0、负数、浮点数、变量。常量表达式的值就是数组的元素个数。
1、一维数组的初始化
数组在创建的时候我们要给定一些纸进行初始化。注意观察等号后面是数字,字符,字符串三种情况格式的区别哦。
int arr1[10]={1,2,3};//不完全初始化,默认剩下七个元素的值为0
char arr2[10]={'a','b','c'};//不完全初始化,默认剩下七个元素的值为0
//char arr2[10]={'a',98,'c'};这里面的98其实还是'b'(ASCII码表)
char arr3[10]="abc";//不完全初始化,默认剩下6个元素的值为0,别忘了第四个字符是'\0'哦
char arr4[]="abcdef";//这种情况可以省略常量表达式,电脑知道[]里面放的是7
测测之前的知识:
char arr4[]="abcdef";
printf("%d\n",sizeof(arr4));
printf("%d\n",strlen(arr4));
两个分别输出什么呢?
答案:7 6
为什么呢?
sizeof是计算数组arr4所占空间的大小,里面包含'\0'。
strlen是求字符串的长度,遇到'\0'停止,'\0'不算在字符串里面(也就是说是求'\0'之前的字符个数)
两者的区别:
strlen:只能针对字符串求长度;是库函数;需要引用头文件
sizeof:计算变量、数组、类型的大小,单位是字节;是关键字
看个例子加深印象:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
char arr1[] = "abcd";
char arr2[] = { 'a','b','c','d' };
printf("%d\n", sizeof(arr1));
printf("%d\n", sizeof(arr2));
printf("%d\n", strlen(arr1));
printf("%d\n", strlen(arr2));
return 0;
}
答案:
5
4
4
随机值
前三个我们不奇怪,但是第四个咋是个随机值呢。我们刚刚说过strlen遇到'\0'停止,但问题是你arr2 里面没有'\0'啊,所以继续在内存里面找,什么时候找到'\0'什么时候停止,所以是随机值。
2、一维数组的引用
每一个数组元素都是一个变量,他们可以被赋值也可以被使用。前面那么多的代码,不知道你们有没有发现有一个操作符[ ],这是下标引用操作符(其实就是数组访问操作符)。
注意下标从0开始的,也就是说加入一个数组有10个元素,那么它的下标只到9。
讲了那么多了我们有必要深入了解一下一维数组是怎么存储的。
一维数组在内存中的存储:
我们先看看下面程序数组每个元素的地址:
这些地址以16进制的形式打印(为什么16进制看我指针文章),回顾一下16进制:
1 2 3 4 5 6 7 8 9 a(10) b(11) c(12) d(13) e(14) f(15)
我们观察上面的地址,它们每一个地址之间正好差4字节,而4字节正好是一个整型元素的大小。
说了那么多只想告诉你一句话:数组在内存中是连续存放的。
二、二维数组
什么是二维数组?
当数组中的每个元素都带有两个下标时,这样的数组就称为二维数组。
二维数组的创建:
int arr1[3][4];//三行四列
char arr2[3][4];
double arr3[3][4];
float arr4[3][4];
1、二维数组的初始化
int arr1[3][4]={1,2,3,4,5};
首先看这样一个二维数组,它是怎么存放数据的呢?
打开VS监视器:
我们发现它是这样排列的:
1 2 3 4
5 0 0 0
0 0 0 0
显而易见这也是一个未完全初始化的数组。
观察得知:数据是一行一行排列,一行放不完才会放到下一行里面。(行优先)
可以理解为每一行都是一个一维数组。
那有没有办法把某些指定数据放在第一行,而另一些数据放在其他行呢?
我们可以这样来写:
int arr1[3][4]={{1,2},{3},{4,5}};
打开监视器:
我们发现它是这样排列的:
1 2 0 0
3 0 0 0
4 5 0 0
前面我们学过:
int arr1[]={1,2,3,4];
这种情况可以省略[ ]的值,那二维数组能不能也这样呢?
这种情况是错误的,因为你没有告诉编译器数组是几行几列,给这么5个数让我怎么排?
前面在二维数组,我们把某些值再用[ ]括起来,达到把某些值按行分配的效果。
那么这里可不可以呢?
也是不行的!
那到底有没有可以省略的呢?
有,列的表达式不可以省略
好吧,这个{ }里面有3个{ },实际上告诉你我有三行了,那我们看看下面这张图:
这张图我们没有告诉编译器有几行,但是我告诉有几列。
这个几列实际上就是告诉你一行有几个元素,上图告诉我们一行三个元素,现在有五个元素,先把第一行的三个元素填进去。
多了两个元素?
那就再来一行,为了与上一行的元素个数保持一致,补个0就完事了。
2、二维数组的应用
二维数组的使用也是通过下标:
每一行、每一列的下标都是从0开始的。
实操一下,把二维数组的所有元素打印出来:
二维数组在内存中的存储:
先打印出每个元素的地址:
和一维数组一样,每个地址之间相差4字节,也就是说二维数组在内存中也是连续存放的。
可别想象成这样排列的:
1 2 3 4
5 0 0 0
0 0 0 0
咱们在书写二维数组的时候这样写,但是他在内存中可是连续排列的。
三、数组作为函数参数传参
我们在写代码的时候会将数组作为参数传给函数。(在我的C语言笔记函数-函数参数一节也有涉猎)
实践出真知,我们先写一个程序练练手:
将一个整型数组的元素进行升序排序(也就是冒泡排序)
冒泡排序的原理就是从首元素开始相邻两个元素比较大小,进行排序。
也就是说先第一个元素和第二个元素比较大小,第一个大的话和第二个元素交换位置。
然后第二个和第三个元素比较大小,第二个大的话,第二个元素和第三个元素交换位置.......
这一轮结束后,最大的元素能被放到最右边。
然后是下一轮继续交换,这一轮结束后,第二大的数能放在从右往左第二个位置。
继续交换.....
............
直到所有的数字被升序完成。
交换轮数和元素个数有关为n-1次
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
void bubble_sort(int arr[], int m)
{
int i;
for (i = 0; i < m - 1; i++)
{//每一轮冒泡排序
int j = 0;
for (j = 0; j < m -1-i; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main()
{
int arr1[] = { 3,5,2,7,4,9,8,1 };
int i = 0;
//对arr1进行升序排列
int sz = sizeof(arr1) / sizeof(arr1[0]);//确定元素个数,sz-1即为冒泡排序的轮次
bubble_sort(arr1,sz);
for (i = 0; i < sz; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
上面的代码挺长的,且看我慢慢分解
int sz = sizeof(arr1) / sizeof(arr1[0]);//确定元素个数,sz-1即为冒泡排序的轮次
//注意确定元素个数的语句要放在主函数里面计算,不能放在调用函数里面
bubble_sort(arr1,sz);
//数组作为函数参数,传递的是首元素的地址
void bubble_sort(int arr[], int m)
//int arr[]和int* arr两者都可
for (i = 0; i < m - 1; i++)
//用循环实现多轮排序,排序的轮数比元素个数减一
for (j = 0; j < m -1-i; j++)//在每一轮内部进行比大小,换位置
{//j < m -1-i是因为每一轮结束后,下一轮中比大小的次数比上一轮少一次
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
这段代码的不足之处在于,如果给了一个有序数组,它还要一轮一轮的判断,所以我们优化一下
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
void bubble_sort(int arr[], int m)
{
int i;
for (i = 0; i < m - 1; i++)
{//每一轮冒泡排序
int order = 1;//order=1时有序,
int j = 0;
for (j = 0; j < m -1-i; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
order = 0;//说明还不完全有序
}
}
if (order == 1)
{
break;
}
}
}
int main()
{
int arr1[] = { 3,5,2,7,4,9,8,1 };
int i = 0;
//对arr1进行升序排列
int sz = sizeof(arr1) / sizeof(arr1[0]);//确定元素个数,sz-1即为冒泡排序的轮次
bubble_sort(arr1,sz);
for (i = 0; i < sz; i++)
{
printf("%d ", arr1[i]);
}
return 0;
}
感觉这个博主讲的详细:冒泡排序算法(超级详细)
四、 关于数组名
我们常常将数组名是首元素地址,我们打印他们的地址看看:
但是呢,凡是皆有例外:
有两种例外:
- sizeof(数组名),这种时候数组名表示整个数组,计算的是整个数组的大小----字节
- &数组名,代表取出整个数组的地址
对于第二种例外,我们打印出来看看地址和首元素地址的区别:
嚯!看来这整个数组的地址咋和首元素地址一样啊?
真的一样吗?
前两种打印的是首元素的地址,而第三种打印的是数组的起始地址!
假如在它们的地址上面+1,我们看看效果:
前两个都是:08->0C
首元素地址+1,1是整型占4字节,8+4=12(c)。
最后一个是:08->1C
计算可知两者相差20字节(16+4),正好是5个整数,对应数组里面的4个数,加上1。
(数组里面4个数是因为&arr的起始地址是首元素地址,不计入了)