【Amazing! C】数组

一、一维数组

1.1  一维数组的创建与初始化

1.1.1 数组的创建

        数组是一组相同类型元素的集合。数组的创建形式为:

type_t arr_name [const_n]
//type_t 数组的元素类型
//const_n常量表达式,指定数组的大小

        基于此,我们可以创建相关数组:

//形式1
int arr1[3];
int arr2[2+4]
char arr3[5];

int arr[];//err 没有指定数组的大小,换句话说,数组怎么能不指定大小呢?
//在创建数组的时候,括号内必须指定大小,除非进行初始化。
//此外,在数组传参时,作为形参也可以不指定,但在形参部分,已经不再是数组,而是指针。

//形式2
#define X 4
int arr4[X];

        针对形式2,如果我们不采用宏定义,而是采用赋值的方法,在函数中以局部变量或全局变量的形式令int X = 4,该方法是否正确呢?

        C99之前,数组只能是常量指定大小;
        C99之后,引入了变长数组的概念,数组的大小是可以用变量指定的;
        但是VS2022、2019是不支持C99的变长数组的。

        当我们在使用变长数组时,需要注意,变长数组是不能初始化的。

#include<stdio.h>

int main()
{
	int n = 0;
	scanf("%d", &n);
	int arr[n];//不能设置为int arr[n]={0};
	int i = 0;
	for (i = 0; i < n; i++)
	{
		scanf("%d", &arr[i]);
	}
	for (i = 0; i < n; i++)
	{
		printf("%d ",arr[i]);
	}

	return 0;
}

1.1.2 数组的初始化

(1)完全初始化

        定义数组时,所有元素都被赋初值。

int arr1[5] = { 1,2,3,4,5 };

(2)不完全初始化

int arr2[5] = { 1,2,3 };

        值得注意的是,“不完全初始化”和“不初始化”,以上式为例,不完全初始化表示arr[0]、arr[1]和arr[2]分别被初始化为1,2,3,而后边七个元素没有被初始化,未被初始化的元素自动赋值为0;不初始化,即仅定义为“int arr[10];”,数组的各个元素就不再是0。

(3)不指定数组的长度

        如果数组中所有元素均被初始化,那么就可以不指定数组的长度,此时,数组的长度就是元素的个数。

int arr3[] = { 1,2,3 };

(4)字符数组

char arr1[] = { 'a','b','c' };
char arr2[] = "abc";
char arr3[3] = "abc";
char arr4[10] = "abc";

        由图可知,系统为arr1和arr2分别分配了3个和4个空间,arr2相较于arr1,字符串最后是有“\0”的。同样的,arr3的字符串中本应该是有“\0”的,但是由于已经指定了arr3的长度,因此“\0”被丢掉了。我们采用strlen函数计算上述四个字符数组的长度。

1.2 一维数组的使用

        [ ],下标引用操作符,用来访问数组的元素。例如:

#include<stdio.h>

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

sizeof用来计算数据类型和表达式长度的计算符,返回一个变量或类型的大小,单位是byte。

sizeof(arr)计算的是整个数组的大小;sizeof(arr[0])计算的是首元素的大小。

1.3 一维数组在内存中的储存

        数组在内存中是连续存放的。我们可以通过代码证明:

#include<stdio.h>

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

        数组在内存中是连续存放的;
        随着下标的增长,地址是由低到高变化的;
        这就意味着,只要拿到首元素地址,就可以通过地址偏移找到其他元素,为指针访问数组提供很大便利。

二、二维数组

2.1 二维数组的创建与初始化

        对于二维数组,如果初始化了,对于行数是可以省略的,但是列不能省略。

int main()
{
	int arr[3][5] = { 0 };//3行5列
	int arr2[4][10] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };//没有大括号,肯定是把一行放满,再去存放下一行
	int arr2[4][10] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	int arr3[4][10] = { {1,2},{2,3,4},{5,5,5} };//第四行全是0
	int arr4[][10] = { {1,2},{2,3,4},{5,5,5} };//行省略,只有3行

	return 0;
}

2.2 二维数组的使用

        与一维数组相同,二维数组也是通过下标的形式进行访问。证明代码如下:

#include<stdio.h>

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

2.3 二维数组在内存中的储存

#include<stdio.h>

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

         由此可知:

        二位数组在内存中也是连续存放的;
        二维数组其实是一维数组的数组;
        arr[0]是第一组数组的数组名,arr[1]、arr[2]是第二、三组数组的数组名。

三、数组越界

        由1.1.2可知,数组首元素的下标为0,并且,数组的下标规定了数组的大小。所以数组的下标如果小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。例如:

#include<stdio.h>

int main()
{
	int arr[10] = { 1,2,3,4,5 };//0~9
	int i = 0;
	for (i = 0; i <= 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

        因此,数组越界时,编译器没发现不报错,但并不代表代码是正确的,因此要自行检查下标有没有越界。

四、数组作为函数参数

        数组也会作为参数传递给函数。例如,冒泡排序就是对一个数组进行多次的循环与遍历,从而实现数组内各个元素的有效排序。

        代码如下:

#include<stdio.h>

void sort(int arr[])//形参和实参格式要一样
{
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[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[9] = { 9,8,7,6,5,4,3,2,1 };
	//写个函数对arr进行排序,排序为升序
	sort(arr);//数组传参

	//冒泡排序
	//两两相邻的元素进行比较
	
	int i = 0;
	for (i = 0; i < 9; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

        运行代码我们发现,数组并没有完成排序工作。可是我们确实是按照逻辑思路,一步一步的向下写代码的呀。

        我们需要对照成程序代码进行逐步调试。经过调试发现,sz的值是1。导致了程序进入sort函数后并没有继续进入循环语句。难道我们在数组传参时没有把整个数组都传递过去?因此,我们需要知道,数组传参,传递的是什么?

         数组名就是地址,所以之前就经常讲,数组名就是地址,不需要对它取地址。
         数组名是地址,那数组名是谁的地址?
         ——通常说,是数组首元素的地址。

        原来,数组传参传递的时首元素的地址,所以在sort函数中计算sizeof(arr),其实是计算指针的大小,而不是整个数组的大小。这里,sizeof(arr)的大小为4或者8个字节。因此就有了sz=1。

        综上,我们可以总结:所有遇到的数组名,都是数组首元素的地址,但有两个情况例外。

  1. sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
  2. &数组名,这里的数组名表示整个数组,取出的是整个数组的地址

         对此,我们通过代码进一步验证如下:

#include<stdio.h>

int main()
{
	int arr[10] = { 0 };
	printf("%p\n", arr);//数组名
	printf("%p\n", &arr[0]);//数组首元素	//两个一样
	printf("%p\n", &arr);

	printf("%d\n", sizeof(arr));//40

	printf("%p\n", arr+1);
	printf("%p\n", &arr[0]+1);
	printf("%p\n", &arr+1);//70变成了98,相当于加了16进制的28,8*16的0次方+2*16的1次方=40
	//所以,&arr[0]+1只是跳过1个元素,&arr+1是跳过整个数组
	return 0;
}

        所以,我们再次回到冒泡排序,arr这里不是特殊的2种情况,就是数组首元素的地址,那么传过去的就不是数组,代码修正如下:

#include<stdio.h>

void sort(int arr[], int sz)
{
    int tmp = 0;
    int i = 0;
    int j = 0;
    for(i = 0; i < sz - 1; i++)
    {
        for(j = 0; j < sz - 1 - i; j++)
        {
            if(arr[j] > arr[j+1])
            {
                tmp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = tmp;
            }
        }
    }
}

int main()
{
    int arr[9] = { 9,8,7,6,5,4,3,2,1 };
    int sz = 0;
    sz = sizeof(arr)/sizeof(arr[0]);
    sort(arr, sz);
    
    int i = 0;
    for(i = 0; i < sz; i++)
    {
        printf("%d ", arr[i]);
    }

    return 0;
}

         通过运行,验证了代码的正确性。

        最后还有一个小尾巴,我们看这里:void sort(int arr[], int sz)

        既然我们说传递过去的是地址,那么sort函数为什么要用int arr[]来接收呢?

        原来,这里看着像数组,但本质上是指针。那此处为什么要写成数组?

        因为为了方便理解,数组传参,拿数组接收,传什么形参就写什么。当了解数组就是指针后,改为int*arr也是正确的。即 void sort(int *arr, int sz)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值