一维数组

数组 - 一组相同类型元素的集合

数组的创建

数组的创建方式:

1
2
3
4
type_t arr_name[const_n];
//type_t 是指数组的元素类型
//arr_name 数组名
//const_n 是一个常量表达式,用来指定数组的大小

例子:

1
2
3
4
5
6
7
8
9
10
//数组创建
int main()
{
int arr[8];
char ch[5];
//错误写法,数组[]内要使用常量表达式,不能是变量
//int a = 8;
//int arr[a];
return 0;
}

注:在C99标准之前, [] 中只能是常量表达式,不能使用变量。而C99标准支持了变长数组的概念,数组的大小可以是变量

数组的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//数组初始化
int main()
{
//整型数组
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };//完全初始化
int arr2[10] = { 1,2,3,4,5 };//不完全初始化
int arr3[] = { 1,2,3,4,5 };//未指定数组大小,就根据初始化的内容来确定大小
//字符数组
char ch[10] = { 'h','e','l','l','o' };//剩余数组元素为\0
char ch2[] = { 'h','e','l','l','o' };//未指定数组大小,就根据初始化的内容来确定大小,没有\0
char ch3[10] = "hello";// h e l l o \0 0 0 0 0
char ch4[] = "hello";//未指定数组大小,就根据初始化的内容来确定大小,末尾会有 \0
return 0;
}

数组在创建的时候如果不指定数组的大小就得初始化。

数组的元素个数根据初始化的内容来确定。

一维数组的使用

[] - 下标引用操作符

数组是使用下标来访问的,下标是从0开始

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
int arr[10] = { 0 };//不完全初始化
//数组的大小可以通过计算得到
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for ( i = 0; i < sz; i++)
{
arr[i] = i;
}
for (i = 0; i < sz; i++)
{
printf("%d\n", arr[i]);
}

return 0;
}

一维数组在内存中的存储

1
2
3
4
5
6
7
8
9
10
11
int main()
{
int arr[10] = { 0 };//不完全初始化
int i = 0;
for (i = 0; i < 10; i++)
{
printf("&arr[%d] = %p\n",i, &arr[i]);
}

return 0;
}

内存地址为十六进制,0~9 a b c d e f

可以发现,相邻的元素的地址都相差了4,而一个元素四个字节,一个字节给一个地址

所以我们可以知道,一维数组在内存中是连续存放的,而随着数组下标的增长,地址是由低到高变化的

那么只要我们知道了数组的第一个元素,就可以找到整个数组的元素

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int* pa = arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *pa);
pa++;
}
return 0;
}

二维数组

二维数组的创建

1
2
3
4
5
6
int main()
{
int arr[3][4];
char arr[3][10];
return 0;
}

二维数组初始化

1
2
3
4
5
6
7
8
9
10
11
int main()
{
//前面[]为行,后面[]为列
int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};//根据行数,依次放入元素
int arr2[3][4] = { 1,2,3,4,5,6,7 };//不完全初始化,后面补 0
int arr3[3][4] = { {1,2},{3,4},{4,5} };//每个一维数组后面补 0
int arr4[][4] = { {1,2},{3,4},{4,5} };//行数可以省略。列不可以
//int arr4[][] = { {1,2},{3,4},{4,5} };//错误
//int arr4[3][] = { {1,2},{3,4},{4,5} };//错误
return 0;
}

二维数组的使用

我们可以通过循环下标拿到二维数组的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
int arr[3][4] = { {1,2},{3,4},{4,5} };
int i = 0;
int j = 0;
for ( i = 0; i < 3; i++)
{
for ( j = 0; j < 4; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
return 0;
}

二维数组在内存中的存储

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main()
{
int arr[3][4] = { {1,2},{3,4},{4,5} };
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("&arr[%d][%d] = %p\n",i,j, &arr[i][j]);
}
}
return 0;
}

我们可以看到,每个相邻数组元素的内存地址也是相差了4

说明二维数组在内存中也是连续存放的

一行内部元素连续后,换行一样也是连续的

那么只要我们知道了二维数组的第一个元素,也就可以找到整个二维数组的元素

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
int arr[3][4] = { {1,2},{3,4},{4,5} };
int i = 0;
int* p = &arr[0][0];
for (i = 0; i < 12; i++)
{
printf("%d ", *p);
p++;
}
return 0;
}

那么为什么行数可以省略,而列数不可以呢

1
int arr4[3][] = { {1,2},{3,4},{4,5} };//错误

就是因为二维数组在内存中是连续存放的

在一行在内存中时,要知道该什么时候结束,跳到下一行给它分配地址

如果没有设置列数,就无法知道什么时候换行

数组作为函数参数

数组名是什么

数组名在绝大部分情况下指的是数组首元素的地址

1
2
3
4
5
6
7
8
int main()
{
int arr[10] = { 0 };
printf("%p\n", &arr);
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}

打印输出后发现,他们的输出结果都是一样的,是同一个地址

所以数组名是数组首元素的地址

但是,有两个情况不一样

第一个:sizeof(数组名),计算整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组。

第二个:&数组名,取出的是数组的地址。&数组名,数组名表示整个数组。

1
2
int arr[10] = {0};
printf("%d\n", sizeof(arr));//40 整个数组的大小
1
2
3
4
5
6
7
8
9
int main()
{
int arr[10] = { 0 };
printf("%p\n", &arr);
printf("%p\n", &arr + 1);
printf("%p\n", arr);
printf("%p\n", arr + 1);
return 0;
}

首元素地址+1就是下一个元素的地址

而整个数组地址+1就是跳过了这个数组,拿到了数组后面的第一个地址

冒泡排序函数

在写代码的时候,有时会将数组作为参数传给函数

比如:将一个整形数组排序,实现一个冒泡排序函数

冒泡排序:两两相邻的元素进行比较,并且符合条件的话进行交换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void sort(int arr[])
{
//计算数组元素个数
int sz = sizeof(arr) / sizeof(arr[0]);
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[] = { 10,9,8,7,6,5,4,3,2,1,0 };
sort(arr);
return 0;
}

以上代码是否有问题?

有的,在计算数组元素个数时,出现了问题

因为数组在作为函数参数传参的时候,传过去的是数组首元素的地址

所以sizeof(arr)计算的是一个指针变量的大小,为 4

sizeof(arr[0])数组元素首元素的大小也是 4

就变成了4 / 4 = 1

所以我们需要把计算数组元素个数放到主函数里,并进行传参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void 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[] = { 10,9,8,7,6,5,4,3,2,1,0 };
//计算数组元素个数
int sz = sizeof(arr) / sizeof(arr[0]);
sort(arr,sz);
for (size_t i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}

打印输出:0 1 2 3 4 5 6 7 8 9 10

数组越界

数组的下标是有范围限制的

数组的下标规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1

数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问

1
2
3
4
5
6
7
8
9
10
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
for (i = 0; i <= 10; i++)//i = 10,越界
{
printf("%d\n", arr[i]);
}
return 0;
}

C语言本身是不做数组下标的越界检查,编译器也不一定报错(vs2019会报警)

所以在写代码时,最好自己做越界的检查