数组的理解
数组的含义
在C语言中,用于存储多个相同类型的元素。它可以被简单地定义为包含多个元素的容器。数组中每个元素都可以通过索引来访问,索引从零开始递增。 C语言中的数组可以包含任何基本数据类型,例如整数、字符、浮点数等。要定义一个数组,需要指定数组的类型和元素的数量。
数组的创建
type_t arr_name [const_n];
type_t 是指数组的元素类型
const_n 是一个常量表达式,用来指定数组的大小
例如:
int a[5]; char c[2]; double* ptr[10];
数组创建的其他几种实例情况
#include<stdio.h> #define COUNT 10 enum Count { Count = 10, }; int main() { //写法一 int count = 10; int arr[count]; //写法二 const int count = 10; int arr[count]; //写法三 int arr[COUNT]; //写法四 int arr[Count]; return 0; }
以上代码中包含了4种不同的数组定义方式,分别如下:
1. 使用变量定义数组大小:
```
int count = 10;
int arr[count];
```
此种方式定义数组的长度使用的是一个变量,变量count的值为10,因此定义了一个包含10个元素的数组。
但需要注意的是,在博主的vs编译器中,采用的是C90的标准(数组大小只能是常量表达式),并不支持C99的标准(引入变长数组的概念,使得数组在创建的时候可以使用变量,但是不可以被初始化),所以这样的变量长度的数组会被编译器判定错误。
而在gcc编译器环境下,它是支持C99标准的特性,就允许这样的写法。
2. 使用常量定义数组大小:
```
const int count = 10;
int arr[count];
```
此种写法只是将变量count改为了常量count,但是count还是常量,只不过它拥有了常量属性,本质还是变量,原因和第一种方法相同。
3. 使用宏定义定义数组大小:
```
#define COUNT 10
int arr[COUNT];
```
通过宏定义,我们将数组大小定义为一个常量,因此在整个程序中都可以使用宏定义的值。此种方式定义的数组大小,与使用常量定义的数组大小基本相同。
4. 使用枚举定义数组大小:
```
enum Count
{
Count = 10,
};
int arr[Count];
```
使用枚举定义数组大小与使用宏定义相似,只不过使用的是枚举类型。其中,我们定义了一个名为Count,值为10的枚举常量,然后通过Count来定义数组大小。最终,以上四种方式定义的数组类型都是相同的,都定义了一个包含10个元素的数组。
总结:数组创建,在C99标准之前,[ ]([ ]内的值必须是整型)中要给一个常量、宏定义或者枚举常量才可以,不能使用变量。在C99标准支持了变长数组的概念,数组的大小可以使用变量指定,但是数组不能初始化。
数组的初始化
数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。
int arr1[5] = {1,2,3,4,5};//完全初始化 int arr2[10] = {1,2,3};//不完全初始化,剩余的元素都是0 int arr3[] = {1,2,3,4};//省略数组的大小,数组会根据初始化的内容来确定 int arr4[3] = {0};//省略数组的内容,就必须要指定数组的大小 //下面解释 char arr5[] = {'a','b','c'}; char arr6[] = "abc";
数组在创建的时候如果想不指定数组的确定的大小就得初始化。数组的元素个数根据初始化的内容来确定。
现在我们来看数组中字符数组在创建为什么有两种形式(char arr5[] 和 char arr6[ ] ),以及在内存是怎么分配的。
```
char arr5[] = {'a','b','c'};
```
这种方式就是简单的使用{ }进行初始化 。
```
char arr6[] = "abc";
```
这种创建char数组的方式叫做字符串字面值初始化,可以简化char数组的定义和初始化。
由两张图我们可以得出:
第二种写法等价于:
```
char arr6[] = {'a', 'b', 'b', '\0'};
```
注意:两种写法有区别,第二种写法比第一种写法多了'\0',我们以后创建使用的时候不能混淆了。
一维数组的使用
数组访问的操作符: [ ]
下标从'0'开始
#include <stdio.h> int main() { int arr[10] = {0};//数组的不完全初始化 //计算数组的元素个数 int sz = sizeof(arr)/sizeof(arr[0]); //对数组内容赋值,数组是使用下标来访问的,下标从0开始。所以: int i = 0;//做下标 for(i=0; i<10; i++)//这里写10,好不好? - 可以清晰的看到元素的总个数 { arr[i] = i; } //输出数组的内容 for(i=0; i<10; ++i) { printf("%d ", arr[i]); } return 0; }
总结:
1. 数组是使用下标来访问的,下标是从0开始。
2. 数组的大小可以sizeof关键字通过计算得到。
一维数组在内存中的存储
上面我们打印的是数组每个元素的内容,现在我们来打印一下数组每个元素的地址
//输出数组每个元素的地址 for (i = 0; i < 10; ++i) { printf("[%d]==%p\n", arr[i], &arr[i]); }
总结:数组在内存中是连续存储的。
二维数组的含义
二维数组是一种数组类型,它可以存储具有相同数据类型的元素值,但是以二维网格的形式存储。
例如:
二维数组由行和列组成,其中每个元素可以通过其行和列的索引来访问。通常,我们使用两个下标来访问二维数组的元素,第一个下标表示要访问的行数,第二个下标表示要访问的列数。
二维数组的创建
int arr[3][4];
char arr[3][5];
double arr[2][4]
二维数组的初始化
int arr[3][4] = {1,2,3,4};
int arr[3][4] = {{1,2},{4,5}};
int arr[][4] = {{2,3},{4,5}};//二维数组如果有初始化,行可以省略,列不能省略
二维数组的使用
二维数组的使用也是通过下标的方式,行下标和列下标都从0开始。
#include <stdio.h> int main() { int arr[3][4] = { 0 }; int i = 0; for (i = 0; i < 3; i++)//行下标 { int j = 0; for (j = 0; j < 4; j++)//列下标 { arr[i][j] = i * 4 + j; } } for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 4; j++) { printf("%d ", arr[i][j]); } } return 0; }
二维数组在内存中的存储
//输出数组每个元素的地址 for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 4; j++) { printf("&arr[%d][%d] = %p\n", i,j, &arr[i][j]); } }
二维数组初始化为什么可以省略行,不能省略列
二维数组是由多个一维数组按照行排列而成的,每一个一维数组被称为一行,它们在内存中是连续存放的,行与行之间是相互独立的。当我们初始化一个二维数组时,需要指定其行数和列数。
对于指定行数,我们可以采用以下两种方式:
1. 显示指定行数
```
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
```
这种方式明确指定了行数为2。
2. 隐式指定行数
```
int arr[ ][3] = {{1, 2, 3}, {4, 5, 6}};
```
这种方式没有指定行数,但是编译器会根据初始化中元素的个数来计算出行数,这里由于有两个一维数组,每个一维数组包含3个元素,所以行数为2。
对于指定列数,我们需要显式指定数组列数,因为在分配内存空间时,需要先知道每一行应该分配多少个元素。如果省略了列数,则编译器无法确定每一行应该分配多少个元素。因此,初始化二维数组时可以省略行数,但必须指定列数。
数组越界
数组的下标是有范围限制的。 数组的下规定是从0开始的,如果数组有n个元素,最后一个元素的下标就是n-1。 所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。 C语言本身是不做数组下标的越界检查,编译器也不一定报错,但是编译器不报错,并不意味着程序就 是正确的, 所以我们在写代码时,最好自己做越界的检查。
#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\n", arr[i]);//当i等于10的时候,越界访问了 } return 0; }
这段代码定义了一个长度为10的整型数组,并将其初始化为1到10的连续数字。然后通过循环遍历数组,依次输出数组中的元素,但是需要注意的是,在循环中判断的条件是 `i<=10`,这意味着循环将会执行11次,而数组下标从0开始,最大只能取到9。
因此,在循环的最后一次迭代中,`i`等于10,此时访问了数组下标为10的元素,即超出了数组的范围,这个错误我们称之为数组越界访问。
越界访问是一种常见的编程错误,它可能导致程序崩溃、产生不可预知的结果或安全隐患等问题。为了避免越界访问,我们需要在访问数组元素时,保证数组下标的合法性,即数组下标不能小于0,且不能大于等于数组长度。所以代码中应该把循环判断条件修改为 `i<10`。
数组作为函数参数
在C语言中,我们可以将数组作为函数的参数来传递。当我们将一个数组作为参数传递给函数时,实际上传递的是数组的地址。在函数中,我们可以通过传递进来的地址来访问数组中的元素,对数组进行读取、修改等操作。
数组名怎么理解
数组名通常情况下就是数组的首元素的地址。
但是有两个例外:
1.sizeof(数组名),数组名单独放在sizeof()内部,这里的数组名表示整个数组,计算的是整个数组的大小。
2.&数组名,这里的数组名表示整个数组,这里取出的是整个数组的地址。
我们来用函数作为参数写一个冒泡排序实现整形数组排序。
#include <stdio.h> void bubble_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[] = { 3,1,7,5,8,9,0,2,4,6 }; bubble_sort(arr);//是否可以正常排序? int i = 0; for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("%d ", arr[i]); } return 0; }
在这段代码中,定义了一个名为 `bubble_sort` 的函数,它接受一个整型数组 `arr` 作为参数。在函数内部,使用双重循环实现了冒泡排序算法,将传入的数组进行排序。
在 `main` 函数中,定义并初始化了一个整型数组 `arr`,然后将其作为参数传递给 `bubble_sort` 函数进行排序。
然而,这段代码存在一个问题:在 `bubble_sort` 函数中,计算数组长度的方式不确。 在 C 语言中,数组作为函数参数时,我们只能传递数组的地址,也就是指向数组第一个元素的指针。因此,当 `bubble_sort` 函数被调用时,传递给它的是数组的地址,而不是数组本身的大小。因此,`sizeof(arr)` 将会返回数组指针的大小,而不是整个数组的大小。我们试着打印一下发现结果确实是4,并且程序也提示警告。
为了解决这个问题,我们可以在调用函数时将数组的长度一起传递给函数,或者在函数内部使用另一个参数来表示数组的长度。在这段代码中,我们可以在定义函数时加上一个表示数组长度的参数 `int len`,然后在调用函数时将数组的长度传递给它。
修改后的冒泡排序算法:
#include <stdio.h> //void bubble_sort(int *arr,int sz) void bubble_sort(int arr[],int sz) { int i = 0; for (i = 0; i < sz - 1; i++)//比较的趟数 { int j = 0; int flag = 1;//表示有序 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; } } int main() { int arr[] = { 3,1,7,5,8,9,0,2,4,6 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr,sz); int i = 0; for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("%d ", arr[i]); } return 0; }
在代码中,用一个flag变量来表示当前的数组是否已经有序了。在每一轮比较之前,将flag值赋为1,表示当前的数组已经有序。如果在比较的过程中发现有元素需要进行交换,那么就将flag值改为0,表示当前的数组无序。 在每一轮比较结束之后,检查flag的值。如果flag值还是1,说明这一轮比较过程中没有进行任何元素的交换,也就是整个数组已经有序了。此时就可以直接退出排序循环,不再进行无用的比较,从而提高排序的效率。这个优化方法能够有效地减少排序循环的次数,尤其在处理大数据量时效果更为明显。因为如果数组已经有序,那么不必再进行多余的比较操作,这样可以大大减少算法的时间复杂度。