您现在的位置是:首页 >技术教程 >C learning_11 (数组和在内存存储的理解、数组越界、数组作为形参)网站首页技术教程

C learning_11 (数组和在内存存储的理解、数组越界、数组作为形参)

笨笨胡小巴 2024-05-30 13:36:12
简介C learning_11 (数组和在内存存储的理解、数组越界、数组作为形参)

目录

         数组的理解

数组越界

数组作为函数参数


数组的理解

数组的含义

        在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'开始

#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
", 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
", 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
", 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,说明这一轮比较过程中没有进行任何元素的交换,也就是整个数组已经有序了。此时就可以直接退出排序循环,不再进行无用的比较,从而提高排序的效率。这个优化方法能够有效地减少排序循环的次数,尤其在处理大数据量时效果更为明显。因为如果数组已经有序,那么不必再进行多余的比较操作,这样可以大大减少算法的时间复杂度

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。