一、数组的定义
在C语言中,可以使用以下方式定义数组:
- 1.定义数组并指定长度
//定义一个长度为5的整数数组
int arr[5];
之后可逐个赋值:
arr[0]=1;
arr[1]=2;
```
arr[4]=5;
- 2.定义数组并初始化
int num[] = {1,2,3};
char chars[] = {'a','b','c'};
char str[] = "Hello";
- 3.指定数组长度但不完全初始化元素
//初始化部分元素,其余元素默认为0;
int arr[5]={1,2};
//定义字符数组时,可以用ASII码值代替字符:
char arr[5] = {'a',98}; == char arr[5] = {'a','b'};
- 4.定义多维数组
//定义一个二维整数数组
int mat[3][3];
//使用嵌套大括号
int array[3][5] = {
{1, 2, 3, 4, 5},
{6, 7, 8, 9, 10},
{11, 12, 13, 14, 15}
};
- 5.动态分配内存
type *arrayName = malloc(sizeof(type) * elements);
- type 指数组元素的类型,如 int、char 等
- arrayName 是数组的指针名称
- malloc() 分配 sizeof(type) * elements 个字节的内存,elements 为元素个数
例如:
int *numbers = malloc(sizeof(int) * 5); // 分配 5 个 int 型元素的内存
char *chars = malloc(sizeof(char) * 26); // 分配 26 个 char 型元素的内存
```
这实际上就是为数组分配内存,并用指针 arrayName 来访问数组元素。
如使用 numbers 访问数组:
numbers[0] = 1;
numbers[1] = 2;
...
numbers[4] = 5;
- 使用完数组后需要使用 free()释放数组内存:
free(numbers);
free(chars);
- 这个做法的优点是:
- 可在运行时指定数组大小
- 不需要在编译时事先知道数组大小
- 数组大小可动态调整
- 缺点是:
- 需要手动 free() 释放内存,否则会内存泄漏
- 分配失败需要检查 malloc() 的返回值
总的来说,用malloc() 分配数组内存可以在运行时指定数组大小。需要谨慎释放内存,否则会内存泄漏。
定义数组时不能既不初始化值也不规定数组大小,必须至少满足以下两个条件之一:
- 提供初始值来初始化数组,让编译器推断数组大小
int arr[] = {1,2,3}; //数组大小推断为3
- 直接指定数组大小
int arr[5]; //数组大小为5
注意:
-
需要注意的是,数组的索引从0开始,因此访问数组元素时,索引范围应为0到数组长度减1。
另外,C语言中的数组是一块连续的内存区域,用于存储相同类型的数据。可以通过索引访问和修改数组中的元素。
二、数组的显隐式初始化
-
隐式初始化
指在声明变量时,使用默认的初始化规则(赋值为0)为变量赋予初始值,而不显式地指定初始值。
-
隐式初始化
指在声明变量时明确指定初始值的操作。
三、数组的存储
数组在内存中是连续存放的。
在C语言中,数组的内存分配取决于数组的声明方式。
以下举两个例子:
- char arr1[] = "abc";
编译器会根据字符串的长度为数组分配足够的内存空间,并在末尾自动添加一个空字符'\0',表示字符串的结束符。
因为字符串"abc"包含3个字符,所以编译器会为`arr1`分配4个字节的内存空间(3个字符+1个空字符)。内存布局如下所示:
+------+------+------+------+
| 'a' | 'b' | 'c' | '\0' |
+------+------+------+------+
| arr1[0] | arr1[1] | arr1[2] | arr1[3] |
+------+------+------+------+
- 2.char arr2[3] = {'a','b','c'};
-
我们明确指定了数组的大小为3。编译器会为`arr2`分配3个字节的内存空间,并将指定的字符分别存储在数组的对应位置。
没有自动添加空字符'\0',因为我们使用了显式初始化。内存布局如下所示:
+------+------+------+
| 'a' | 'b' | 'c' |
+------+------+------+
| arr2[0] | arr2[1] | arr2[2] |
+------+------+------+
需要注意的是,由于数组的索引从0开始,所以`arr1`和`arr2`的元素索引范围都是0到2。
-
那么,什么情况下,编译器会在数组末尾自动添加空字符呢?
-
1.定义字符串字面值初始化的字符数组
-
char greeting[] = "Hello";
// 等效于
char greeting[] = {'H', 'e', 'l', 'l', 'o', '\0'};
-
2.使用scanf()读取字符串到字符数组
-
char str[100];
scanf("%s", str); //%s—按字符串格式读取
-
scanf()需要’\0‘作为字符串的结束标志,才知道停止读取输入。
即使 str 数组有 100 个元素,使用 scanf("%s", str) 读取字符串时,实际最多只能读取 99 个字符。
scanf()读取到第99个数组时,会自动添加一个\0作为字符串的结束。
3.使用strcpy()/strcat()将字符串复制到字符数组中
strcpy(str, "Hello");
strcpy() 会这样做:
- 复制字符串中的前 99 个字符到 str 数组中
- 再在第 100 个位置(即下标为 99)添加空字符 \0
对比:
- scanf() 是读入字符串的过程中动态地添加'\0'
- strcpy() 是复制完字符串后,再附加'\0' 加到数组末尾
- 都只能识别到前99个元素
4.将字符串作为函数参数传递给函数
void printStr(char str[]) {
}
printStr("Hello");
// 等效于
printStr("Hello\0");
```
传递字符串字面值时,编译器会自动添加'\0'。
总结:
这些情况下,编译器自动在字符数组末尾添加空字符'\0',是为了表示字符串的 ending(结束)。
'\0' 使得字符串函数如strlen()、strcpy()等能正确处理字符串。
所以,只有定义存储字符串的数据时,编译器才会自动在末尾添加'\0'。
定义普通字符数组时,不会添加'\0':
如:char chars[] = {'h', 'e', 'l'}; // 正常字符数组,无'\0'
```
四、 二维数组
【二维数组的存放也是连续的】
为了便于理解,我们可以将二维数组看作一维数组。
比如: int arr[3][4]; //三行四列
-
二维数组的初始化
- 规则:第一维可以省略,第二维不可以省略。(即行可以省略,但是列必须初始化)
- 当 int arr[3][4] ={1,2,3,4,5};
此数组是三行四列,第五个元素会自动填补到第二行。
- 当 int arr[3][4]={{1,2,3,},{4,5,6,7}};
当第一行的元素不够时,自动补0。
打印二维数组
【通过下标访问】
打印每一个元素的地址:
printf("&arr[%d][%d] = %p\n", i , j , &arr[i][j]);
五、数组作为函数参数
往往写代码时,会将数组作为参数传给函数,比如:想实现一个冒泡排序函数将一个整型数组排序。
- 冒泡排序
它通过多次迭代比较相邻的元素,并依次交换它们的位置,使得较大(或较小)的元素逐渐向数组的一端移动,从而达到排序的目的。
如果有10个元素相比较,则需要9趟冒泡排序。(n-1次)
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
void boubble_sort(int arr[],int sz)
{
//确定冒泡排序的趟数
int i = 0;
//为了避免已经有序的数据重复排序浪费资源,使用flag优化程序
for (i = 0; i < sz - 1; i++)
{
int flag = 1;//假设这一趟排序的数据已经有序
//每一趟冒泡排序
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;
flag = 0;//本次排序不完全有序
}
}
if (flag == 1)
{
break;//flag 保持为1,说明整个数组已经有序
//break总是跳出离它最近的内层循环。这里跳出的是外层i for循环
}
}
}
int main()
{
int arr[] = { 9,8,7,12,40,32,3,2,1,0 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
//对arr经行排序,排成升序
boubble_sort(arr,sz);//冒泡排序函数
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
其中,使用了flag对程序进行优化,避免浪费资源。
六、数组名的含义和用法
数组名就是数组首元素地址(有两个例外);
例外:
- sizeof(数组名)—数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
- &数组名 ,数组名代表整个数组, &数组名取出的是整个数组的地址
除了这两种情况,所有的数组名都表示数组首元素的地址。
- int arr[]={1,2,3,4,5,6,7};
arr -- 首元素地址
&arr[0] -- 首元素地址
&arr -- 整个数组的地址
注意: arr == &arr[0]