C语言数组

本文详细介绍了C语言中数组的创建、初始化、使用,包括一维数组和二维数组的存储方式,以及数组作为函数参数的传递。讲解了sizeof和strlen的区别,并通过实例展示了数组在内存中的存储方式。同时,文章通过冒泡排序的例子解释了如何有效地传递数组给函数,以及数组名的含义。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一维数组的创建和初始化

数组的创建

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

type_t arr_name [const_n]

type_t 是指数组的元素类型
const_n 是一个常量表达式,用来指定数组的大小

数组创建的实例

创建一个数组存放10个整型

正确

int arr[10];

错误

int n = 10;
char ch[n];

在这里插入图片描述
数组创建,[ ]中要给一个常量才可以,不能使用变量。

数组的初始化

数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。

type_t arr_name [const_n]={表达式1, 表达式2,...,表达式n}

上面是标准的数组的初始化格式,下面我们看几个例子

int arr1[10] = {1,2,3}; //注释1
int arr2[] = {1,2,3,4}; 
int arr3[5] = {1, 2, 3, 4, 5};
char arr4[3] = {'a', 98, 'c'}; //注释2
char arr5[] = {'a', 'b' , 'c'};
char arr6[] = "abcdef"; //注释3

注释1:这种初始化叫不完全初始化,剩下的元素会默认初始化为0
在这里插入图片描述
注释2:arr4中虽然第二个元素我们写的是98,但存入内存的依然为abc,98被解析为b,因为b的ASCII值是98。
在这里插入图片描述
注释3:双引号引起来的叫字符串,字符串是一种以‘\0’结尾的字符数组。arr6中放入数组有7个字符,因为最后还要加上一个’\0’。字符串可以通过字符型数组来存放。
在这里插入图片描述
数组在创建的时候如果不指定数组的确定大小就初始化,数组的元素个数会根据初始化的内容来确定。但是对于下面的代码要区分,在内存中如何分配。

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

像arr1这种将字符串"abc"存放在字符型数组的形式,等价于

char arr2[] = {'a','b','c','\0'};

补充:

sizeof与strlen的区别

char arr6[] = "abcdef"; 
printf("%d\n", sizeof(arr6));
printf("%d\n", strlen(arr6));

运行结果
在这里插入图片描述
解析:
sizeof是计算arr6所占空间的大小,字符串存了7个元素。strlen求字符串长度找到\0就停止,并且\0不算。

  1. sizeof和strlen没什么关联
  2. strlen是只能对字符串求长度,只跟字符串有关。
  3. sizeof计算变量、数组、类型的大小,单位字节。
  4. strlen是库函数,使用要引用头文件。
  5. sizeof是操作符,使用不用引用头文件。

不同存储方式sizeof和strlen的值

char arr1[] = "abc";
char arr2[] = {'a' , 'b' , 'c' };
printf("%d\n", sizeof(arr1));//arr1所占空间大小 abc\0
printf("%d\n", sizeof(arr2));//arr2所占空间大小 abc
printf("%d\n", strlen(arr1));//arr1长度
printf("%d\n", strlen(arr2));//输出随机值

运行结果
在这里插入图片描述

一维数组的使用

对于数组的使用我们之前介绍了一个操作符:[ ],下标引用操作符。它其实就是数组访问用到的操作符。我们来看代码:

//想要打印arr[3]
int main()
{
    char arr[] = "abcdef";//[a][b][c][d][e][f][\0]
    printf("%c\n",arr[3]);
    return 0;
}

运行结果
在这里插入图片描述

//想要打印整个数组
int main()
{
    char arr[] = "abcdef";//[a][b][c][d][e][f][\0]
    int i = 0;
    for (i = 0; i <(int)strlen(arr); i++) //strlen默认返回无符号数
    {
        printf("%c ", arr[i]);
    }
    return 0;
}

运行结果
在这里插入图片描述
strlen默认返回一个无符号数,如果编译器报警告在strlen前面加上一个强制类型转换(int)。

当然也可以改成下面的形式:

int main()
{
    char arr[] = "abcdef";//[a][b][c][d][e][f][\0]
    int i = 0;
    int len = strlen(arr)
    for (i = 0; i < len; i++)
    {
        printf("%c ", arr[i]);
    }
    return 0;
}

上面是一个字符串数组打印,那么整型数组如何打印呢?

int main()
{
   int arr[] = {1,2,3,4,5,6,7,8,9,0};
   //计算数组的元素个数
   int sz = sizeof(arr)/sizeof(arr[0]);
   //对数组内容赋值,数组是使用下标来访问的,下标从0开始,所以:
   int i = 0; //做下标
   //输出数组内容
   for(i=0; i<sz; i++)
   {
       printf("%d",arr[i]);
   }
   return 0;
}

运行结果
在这里插入图片描述

一维数组在内存中的存储

接下来我们探讨数组在内存中的存储。看代码:

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

运行结果
在这里插入图片描述
int型占4个字节,观察地址相邻元素地址相差4。说明数组在内存中是连续存放
在这里插入图片描述

二维数组的创建和初始化

二维数组的创建

//数组创建
int arr[3][4];//整型三行四列
char arr[3][5];//字符串型 三行五列
double arr[2][4];

二维数组的初始化

//数组初始化
int arr1[3][4] = {1,2,3,4,5}; //不完全初始化
int arr2[3][4] = {{1,2,3},{4,5}};
int arr3[][4] = {{2,3},{4,5}};//列绝对不能省略,行可以省略

arr1数组中1,2,3,4存储在第一行,5在第二行。
在这里插入图片描述
arr2数组中1,2,3存储在第一行,4 ,5在第二行。
在这里插入图片描述
arr3组中2,3存储在第一行,4 ,5在第二行。二维数组列绝对不能省略,行可以省略。
在这里插入图片描述

二维数组的使用

二维数组的使用也是通过下标的方式。看代码:

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

运行结果:
在这里插入图片描述

二维数组在内存中的存储

像一维数组一样,这样我们尝试打印二维数组的每一个元素地址。

int main()
{
    int arr[3][4] = { {1,2,3},{4,5} };
    int i = 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]);
        }
    }
    return 0;
}

运行结果
在这里插入图片描述
观察二位数组地址可知在地址二维数组在内存中是连续的。
在这里插入图片描述
可以把二位数组想象成由一维数组组成,上图可以把arr[3][4]想象成三个一维数组。

数组作为函数参数

往往我们在写代码的时候,会将数组作为参数传给函数,比如:我要实现一个冒泡排序(这里要讲算法思想)将一个整型数组排序。通过冒泡排序来学习数组作为函数参数是怎样传递的。

冒泡排序的思路

假设数组有n个元素,采用冒泡排序法对该数组元素进行排序。从下标为0的元素开始,比较相邻的元素开始,比较相邻的两个元素大小,每次比较如果前面的元素大于后面的元素,则交换这个两个元素的值。

第一轮,从下标0的元素到下标为n-1元素,依此比较相邻两个数组元素的大小。比较n-1次后,n个数中最大的一个数被交换到最后一个数的位置上,这样大的数“沉底”,小的数“浮起”。

第二轮,仍然从下标为0的元素开始,到下标为n-2的数组元素为止,对余下的n-1个数重复上述过程,比较n-2次后,将n个数中第二大的数交换到下标为n-2的倒数第二个位置上。

依此类推,重复以上过程n-1次,分别将n个数中最大的数到第n-1大的数“沉底”到相应位置上,则n个数全部排序完毕。
在这里插入图片描述
下面我们开始写代码

void bubble_sort(int arr[],int sz)
{
	int i = 0; //确定冒泡排序的趟数
	//int sz = sizeof(arr) / sizeof(arr[0]); //因为传的是数组首地址,没法得到元素个数,不要在冒泡函数里面写。应该在主函数里算出再传进来
	for (i = 0; i < sz - 1; i++)
	{
		int flag = 1; //假设这一趟要排序的数据已经有序
		//每一趟冒泡排序
		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;
				flag = 0;
			}
		}
		if (flag == 1)
		{
			break;
		}
	}
int main()
{
	int arr[] = {9,8,7,6,5,4,3,2,1,0};
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);//sz要直接在主函数里求出,传进冒泡函数
	//arr是数组,我们对数组arr进行传参,实际上是数组首元素的地址
	//计算机对数组传参不是把数组整个传到函数里,如果数组有一万个参数直接传是会造成浪费的
	bubble_sort(arr,sz); //调用冒泡排序函数
	for (i=0;i<sz ;i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

我们之前学过实参传给形参,形参是实参的一份拷贝,在主函数中调用冒泡排序函数将arr数组传给形参,现在arr数组有10个元素,按照我们之前学的,void bubble_sort(int arr[ ])就会复制10个元素,产生一倍多的浪费。如果元素个数是10000个呢?

其实数组传参不会这么傻的,我们对数组arr进行传参,实际上传的是数组首元素的地址。所以sz要在主函数里求出然后传到冒泡排序函数里,不要认为在冒泡函数里用int sz = sizeof(arr) / sizeof(arr[0])这个公式能求出来。

为了使冒泡排序函数减少不必要的运行。最好在每一趟冒泡排序前加一个flag,将flag设为1,假设这一趟要排序的数据已经有序。如果无序flag会被赋值为0,如果有序if (arr[j] > arr[j + 1])这句判断语句一次都不会被执行,flag不会被赋值为0,这一趟for循环结束,函数运行到if (flag == 1)直接运行break,退出大循环。输出结果。

总结:

每一趟冒泡排序都是从数组开头(也就是数组下标为0)开始比较大小。

每一趟比较如果过程中有两个元素恰巧顺序,两者不交换位置。我下面放个动图仔细观察一下。

数组大小一定要在主函数里求出再作为参数传进冒泡排序函数。
在这里插入图片描述

数组名是什么?

#include<stdio.h>
int main()
{
   int arr[ ] = {1,2,3,4,5,6,7};
   printf("%p\n", arr);
   printf("%p\n", &arr[0]);
   printf("%d\n", *arr);
   return 0;
}

运行结果
在这里插入图片描述
结论:

数组名使数组首元素的地址。(有两个例外)

如果数组名是元素地址,那么:

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

运行结果
在这里插入图片描述
为什么输出的结果是:40?

int main()
{
	int arr [ ] = { 1,2,3,4,5,6,7 };
	printf("%p\n", arr);
	printf("%p\n", &arr[0]); //数组首元素地址
	printf("%p\n", &arr); //数组的地址
}

运行结果
在这里插入图片描述
printf("%p\n", arr);确实取出了第一个元素的地址。

printf("%p\n", &arr[0]);也确实取出第一个元素的地址。

printf("%p\n", &arr);显示是第一个元素的地址,但这个地址代表整个数组地址从这个地址开始。这种我们叫他数组的地址。

第三种和前两种完全不同,我们不妨为arr后面加上一个1。

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

	printf("%p\n", &arr[0]);
	printf("%p\n", &arr[0]+1);

	printf("%p\n", &arr);
	printf("%p\n", &arr+ 1);

	return 0;
}

运行结果
在这里插入图片描述
观察结果前两种arr+1地址都加了4,而第三种地址直接增加了28(7个int型元素就是28)。

总结:

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

除这两种情况外,所有的数组名都表示数组首元素的地址。

下一节操作符详解

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值