c的基础知识:数组详解


一 数组

什么是数组?

int a;
int b[10];

我们常将变量a称作一个标量,因为它是一个单一的值,而第二行的定义,b[10]此时代表的一些值的集合,也就是我们所说的数组。

数组就是指存储一个固定大小相同类型元素的顺序集合。

固定大小是指一个数组在定义声明好之后大小就是固定的,如上代码,我们定义的b[10]就是一个具有十个元素的存储空间的数组,这一大小在定义好这个数组之后是无法进行修改的。

相同类型就是指我们的数组在定义好之后,它所能存储的数据类型都是一致的,如我们上述代码中,定义的int b[10]就是指我们定义的这个数组中所有的元素都是int型的。

顺序则是指数组所申请的空间都是连续的,也就是说,我们数组中逻辑相邻的元素在存储空间中存储的位置也是相邻的。

最后,不知道你是否还有一个疑问,正如我们上述所说,b[10]所有元素的类型都是int型,那么b的类型是什么?b所表示的是什么?

我们可能第一反应认为b代表这个数组,那么必然是int型啊!其实不然,在c中,数组名的值是一个指针常量(指针就是一种特殊的用于存放地址的变量),也就是数组中第一个元素的地址。它的类型取决于数组元素的类型,如我们上述的代码int b[10],此时b就是指向int的常量指针

也就是说数组名是一个指针常量,但在两种情况下,数组名并不能作为一个指针常量来表示:

  • 数组名作为sizeof操作符的操作数时;
  • 数组名作为单目操作符&的操作数时;

我们使用如下代码验证一下:

#include <stdio.h>

int main(){
    int a[10];
	printf("sizeof(a) = %d\n",sizeof(a));
	printf("a = %d\n",a);
	printf("&a[0] = %d\n",&a[0]);
	printf("&a[0] = %d\n",&a[0]); 
}

它的输出为:
在这里插入图片描述

我们来分析一下这个代码,sizeof(a)此时并不是求a作为指针常量的大小而是此时a所代表的数组的大小,int型是4个字节,这个数组中有10个int型数据,大小就是40个字节。

a&a[0]的值的对比中我们知道,a的值其实就是数组中第一个元素的地址,但是当我们&a时最终得到的结果还是数组第一个元素的地址,此时的a并不是作为一个指针常量,而是代表一个数组。

1. 数组的初始化

1. 定义时就进行初始化

    int a[10] = {1,2,3};
    int b[10] = {1,2,3,4,5,6,7,8,9,10};
    int c[] = {1,2,3,4,5};

第一种方式只进行了部分初始化,未初始化的部分元素值为0(这和编译器有关),最终得到的a[10] = {1,2,3,0,0,0,0,0,0,0}。第二种数组中的梅格元素都进行了初始化,最终得到的b[10] = {1,2,3,4,5,6,7,8,9,10}。第三种初始化的方式未指定大小,它的大小由初始化的元素个数决定,如我们的定义c[ ]的大小为5,最终的结果为c[5] = {1,2,3,4,5}

2. 由输入设备输入的元素进行初始化

在实际应用中,并不是每个变量在申请定义的时候都要同时进行初始化,在更多的时候我们是先定义了数组,最终由输入设备(键盘)输入,对数组进行初始化。

如下代码:

#include <stdio.h>

int main(){
    int n;
    scanf("%d",&n);
    int a[n];
    for(int i=0;i<n;i++){
	    scanf("%d",&a[i]);
	}
	for(int i=0;i<n;i++){
	    printf("%d ",a[i]);
	}
}

运行时如下:
在这里插入图片描述

这里需要注意的是,由于定义数组之后就是固定长度,所以我们在定义之前就需要确定数组的长度,在我们的代码中首先由用户输入一个数字对n进行初始化,也就是确定了数组的大小。

2. 访问数组元素

1. 使用数组名进行访问

定义好了一个数组并进行了初始化之后,我们该如何访问数组中的某一个元素呢?

代码如下(访问a[5])

#include <stdio.h>

int main(){
    int a[10] = {1,2,3,4,5,6,7,8,9,10};
    printf("a[5] = %d",a[5]);
}

在数组中我们可以直接借用它的下标来访问数组中的元素。但是需要注意的是数组的下标是从0开始的,如我们的a[10]
在这里插入图片描述

2. 使用解引用的方式进行访问

我们知道我们的数组名是一个指针常量,而且是我们数组中的第一个元素的地址,那么自然我们可以想到以解引用的方式来访问数组。

代码如下(访问a[5]):

#include <stdio.h>

int main(){
    int a[10] = {1,2,3,4,5,6,7,8,9,10};
    printf("*(a+5) = %d\n",*(a+5));
}

这里我们需要再次强调,在进行指针的算数运算时,它们会对数字进行调整,比如说*(a+5),此时它的意思其实是访问从a开始向后的第5个元素,也就是a[5]。

具体这里使用解引用的方式如果不是很明白,可以看指针这里的算数运算的部分:https://blog.csdn.net/weixin_50941083/article/details/120854946?spm=1001.2014.3001.5501

3. 遍历数组

遍历数组的意思就是指将数组中的元素从第一个访问到最后一个。所以我们遍历数组的方式也有两种,一种使用数组名进行遍历,一种使用解引用的方式进行遍历。这里主要用到的就是for循环了,需要注意的是我们的数组下标从0开始,最后一个元素的下标为数组长度减去1。

1. 使用数组名遍历数组

#include <stdio.h>

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

2. 使用解引用的方式遍历数组

#include <stdio.h>

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

3. 使用指针遍历数组

因为数组名本质上是一个指针常量,那么我们理所当然的可以将其放入一个指针中,再利用这个指针来对它进行遍历。

#include <stdio.h>

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

4. 数组间的赋值

我们都知道变量之间的赋值可以直接使用a=b来进行,那么属于数组之间的赋值该如何来进行呢?可以使用数组名来直接进行赋值吗?

是不可以的,我们必须要单个元素来一一进行赋值,代码如下:

#include <stdio.h>

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

二、数组和函数

函数的出现,使得我们的程序模块化,降低了耦合,同时使得代码的重复量降低,为我们带来了很多好处。那么如果我们在写一个函数的功能的时候,需要将一个数组传递给它,那么我们该如何传参呢?难道要将数组中的每一个值都传递过去吗?当这个函数处理过我们这个数组之后,我们该如何返回我们的数组呢?

在这里,我们分析以下将数组中的元素进行逆序的程序。具体代码如下:

#include <stdio.h>

int * reverse(int a[],int n){
	int b[n];
	for(int i=0;i<n;i++){
		b[i] = a[i];
	}
	printf("%d\n",b);
    for(int i=0;i<10;i++){
    	printf("%d ",b[i]);
	}
	printf("\n");
	return b;
}

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

最终得到的结果是:
在这里插入图片描述

1. 传递数组给函数

在分析数组如何传参之前,我们首先要知道,程序在运行时,函数会重新建立一个栈来存储函数中的一些值,当函数运行完毕后,这些函数运行中的数据也会随之在内存中释放。其次函数只能有一个返回值。

所以如果我们需要将数组传递给函数的时候,往往是将数组名(也就是一个指针常量)传递给数组,而非将每一个数组中的元素传递过去。

这里需要注意的是,数组名作为一个指针常量,函数接受这个参数的时候,其实是初始化了一个指针来接收这个数组名,从而借助指针来操作这个数组。可能就有同学问了,我们的函数在接收传参的时候明明是int a[],怎么就是一个指针了,其实这只是为了让初学者容易理解罢了,他在本质上其实还是个指针。

我们可以用sizeof来验证一下,如果它是一个数组,sizeof的结果应该是这个数组的大小。代码如下:

#include <stdio.h>

void Test1(int a[]){
    printf("sizeof(a):%d\n",sizeof(a));
}
void Test2(int* p){
    printf("sizeof(p):%d\n",sizeof(p));
}
int main(){
    int arr[10];
    printf("sizeof(arr):%d\n",sizeof(arr));
    Test1(arr);
    Test2(arr);
}

最终结果为:
在这里插入图片描述
从这个代码上我们就可以明白其实在将数组传参的时候就是将数组名(一个指针常量,数组第一个元素的首地址)给了函数。

2. 从函数返回一个数组

我们知道函数只能有一个返回值,那么必然我们是没办法一一返回数组中的每个元素的。而在我们上述实例的代码中我们也看到了,即是我们在函数中重新定义一个数组,将传参过去的数组的元素一一进行赋值,最终函数运行完毕,释放内存之后,主函数得到的也只是一个地址罢了。

故而在上述代码中,函数中定义的b[10]的确完成了初始化,但是当我们将b返回给主函数时,主函数其实只是得到了b的地址罢了,此时的b并不是一个数组名,只是一个地址罢了。

那么我们该如何返回函数操作后的数组呢,我们来看这一段代码:
例:函数功能,将一个数组自小到大排序(使用冒泡排序)

#include <stdio.h>

void bubbling_sort(int *a,int n){
	int b[n];
	for(int i=n;i>=0;i--){
		for(int j=1;j<i;j++){
			if(a[j]<a[j-1]){
				int temp = a[j-1];
				a[j-1] = a[j];
				a[j] = temp;
			}
		}
	}
}

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

函数虽然并没有特意return这个数组,但是当我们执行这个程序的时候,发现此时抓函数的数组中的元素已经按从小到大的顺序2排列了。

这是因为函数所接受的参数实际上是原参数的一份拷贝,所以函数可以直接对其进行操作而不改变它原来的值。但是当我们将数组传递给函数作为参数时,对指针参数执行间接访问操作允许函数修改原先的数组元素。数组形参既可以声明为数组也可以声明为指针,这两种声明方式只有当他们作为函数形参时才是相等的。

总结:当我们想要在函数中操作一个数组时,往往是直接将数组名传递给数组,这时函数直接在该数组上进行操作,而函数也不需要将这个数组作为返回值给主函数,因为此时函数通过执行间接访问对该数组做了操作。

三、指针数组

就像我们可以创建一个整数型数组一样,我们也可以创建一个指针数组,里面存放的是地址,具体声明如下。

int *parr[10]

下标引用高于间接访问,所以在这个表达式中首先执行下标引用,因此parr首先是一个数组,在区的一个数组元素之后,随即执行间接访问,所以它的结果最终是int型。而parr中存放的就是指向整型的指针。

#include <stdio.h>

int main(){
    int a = 1;
    int b = 2;
    int c = 3;
    int* p[] = {&a,&b,&c};
    for(int i=0;i<3;++i){
        printf("%d\n",*p[i]);
    }
    for(int i=0;i<3;++i){
        printf("%d\n",**(p+i));
    }
}

指针数组出现的意义是,当我们的某个矩阵中的元素很紧凑时,当然就没有我们指针数组的用武之地,但是当矩阵中的元素很稀疏时,这是使用指针数组就可以大大的节省空间。

示意图如下:
在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值