C语言 数组


数组: 一组相同类型元素的集合

一、一维数组

1.1 一维数组的声明

元素类型 数组名[元素个数]
说明:元素个数一般为常量表达式,c99标准支持可变长数组,即元素个数可以使用变量表示

int arr[10]; //声明一个整形数组,数组有10个元素,数组名为arr
/*
可变长数组
int n = 10;
int arr[n]; 
*/

1.2 一维数组的初始化

初始化:在数组声明时为数组赋值

int arr1[5] = {1,2,3};
int arr2[5] = {1,2,3,4,5};
int arr3[] = {1,2,3};
char arr4[] = "abc" //等同于 arr4[] = {'a','b','c','\0'};

说明:
1.在数组声明时为数组赋值,可省略数组元素个数,此时元素个数为初始值个数

int arr3[] = {1,2,3}; //元素个数为:3

2.如果初始值个数小于元素个数时,剩余数组元素值被初始化为0

int arr1[5] = {1,2,3}; // 等同于 int arr1[5] = {1,2,3,0,0};

3.只能在数组声明时为数组赋值,其他时候只能为数组元素赋值,不能为数组赋值

int arr1[5];
arr = {1,2,3,4,5}; //错误
int arr2[] = {1,2,3};
int arr3[3];
arr3 = arr2;//错误

1.3 引用数组元素

数组名[下标]
说明:
1.数组元素通过下标来访问
2.数组下标从0开始,下标范围为[0,size-1]
3.size = sizeof(arr) / sizeof(arr[0]); 函数内此公式不适用

#include <stdio.h>

//打印数组每个元素值
int main()
{
	int arr[5] = { 0 };
	int size = sizeof(arr) / sizeof(arr[0]); //size为数组元素个数
	int i = 0;
	for (i = 0; i < size; i++)
	{
		arr[i] = i; //对数组元素赋值
	}
	for (i = 0; i < size; i++)
	{
		printf("%d ",arr[i]);//打印数组元素
	}
	return 0;
	
}

输出

1 2 3 4 5

1.4 一维数组在内存中存储

一维数组在内存中是连续存放的,地址使用是从低地址到高地址

#include <stdio.h>
// 32位环境测试
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int size = sizeof(arr) / sizeof(arr[0]); //size为数组元素个数
	int i = 0;
	for (i = 0; i < size; i++)
	{
		printf("%p\n", &arr[i]); //打印对应元素在内存中地址 
	}
	return 0;
}

输出

010FFD80
010FFD84
010FFD88
010FFD8C
010FFD90

在这里插入图片描述

二、二维数组

2.1 二维数组声明

元素类型 数组名[行数][列数]
数组元素个数=行数*列数,在声明时行数可以省略(但必须初始化)

int arr[3][2]; //声明一个二维数组,数组名为arr,数组有3行2列,元素个数为3*2,元素类型为int

2.2 二维数组的初始化

初始化:在数组声明的时候为其赋值

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

说明:
1.初始值个数少于行数与列数乘积,剩余数组元素被初始化为0

int arr1[3][3] = {1,2,3}; // 等同于 int arr1[3][3] = { {1,2,3},{0,0,0},{0,0,0} };
//或等同于 int arr1[3][3] = {1,2,3,0,0,0,0,0,0};

2 只能在数组声明时为数组赋值,其他时候只能为数组元素赋值

int arr1[2][3] = { {1,2,3},{4,5,6} };
int arr2[2][3];
arr2 = arr1;//错误
arr2[0] = arr1[0] //错误
arr2[0][0] = arr1[1][1];//正确

2.3 引用数组元素

数组名[行下标][列下标]
行下标:范围为 [0,行号-1]
列下标:范围为 [0,列号-1]

#include <stdio.h>

int main()
{
	int arr[2][3] = { 0 };
	int i = 0;
	int j = 0;
	int flag = 0;
	for (i = 0; i < 2; i++) //控制行下标
	{
		for (j = 0; j < 3; j++) //控制列下标
		{
			arr[i][j] = flag; //对数组元素赋值
			flag++;
		}
	}
	for (i = 0; i < 2; i++) //控制行下标
	{
		for (j = 0; j < 3; j++) //控制列下标
		{
			printf("arr[%d][%d] = %d ", i, j, arr[i][j]); //打印对应数组元素值
		}
		printf("\n");//打印完一行数组就换行
	}
	return 0;
}

2.4 二维数组在内存中存储

二维数组在内存中是连续存放的,地址使用是从低地址到高地址

#include <stdio.h>

int main()
{
	int arr[2][3] = { 0 };
	int i = 0;
	int j = 0;
	for (i = 0; i < 2; i++) //控制行下标
	{
		for (j = 0; j < 3; j++) //控制列下标
		{
			printf("&arr[%d][%d] = %p\n",i,j,&arr[i][j]);
		}
	}

	return 0;
}

输出

&arr[0][0] = 00B8FE6C
&arr[0][1] = 00B8FE70
&arr[0][2] = 00B8FE74
&arr[1][0] = 00B8FE78
&arr[1][1] = 00B8FE7C
&arr[1][2] = 00B8FE80

在这里插入图片描述

三、多维数组

多维数组:维数大于1个称为多维数组,如:二维数组、三维数组、四位数组等

3.1 多维数组声明

int arr[3][4][5];//声明1个三维数组,数组名为arr,数组为3排、4行、5列,元素个数=3*4*5,元素类型为int

3.2 多维数组初始化

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

说明:多维数组中,只有第一维才能根据初始化列表缺省的提供,剩余的几维必修显式地写出

int arr1[][2][2 = { { {1,2},{3,4} },{ {5,6},{7,8} } }; //第一维度省略不写,通过计算子数组的个数,得到第一维度为2

四、数组下标越界

数组的下标是有范围的,下标范围为[0,元素个数-1],当下标不在此范围时就超出数组合法空间的访问,即数组下标越界。C语言标准并不规定数组下标越界检查,下标越界检查涉及开销比想象的多(涉及到指针指向空间是否在数组空间内),所以大部分编译器不做下标越界检查

4.1 下标越界访问

#include <stdio.h>

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	printf("arr[%d] = %d\n", -1, arr[-1]); //访问数组之前内存空间
	printf("arr[%d] = %d\n", 5, arr[5]); //访问数组之后内存空间
	return 0;
}

输出

arr[-1] = -858993460
arr[5] = -858993460

大部分编译器对越界访问只会报警告,程序仍旧可以运行

4.2 下标越界修改

#include <stdio.h>

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	arr[-1] = 2; //修改数组之前内存空间存放的值
	arr[5] = 3; //修改数组之后内存空间存放的值
	return 0;
}

程序错误,Run-Time Check Failure #2 - Stack around the variable ‘arr’ was corrupted.

结论:我们要自己对数组下标是否越界做检查,不要指望编译器

五、数组与指针

5.1 数组名

5.1.1数组名是什么

在C中,几乎所有使用数组名的表达式,数组名是数组首元素地址,是一个指针常量

例1

#include <stdio.h>

int main()
{
	int arr[5] = { 1,2,3,4,5 };
	printf("p = %p\n", arr);
	printf("&arr[0] = %p\n", &arr[0]);
	//arr++; 此代码错误,因为arr是数组首元素地址,是一个指针常量,常量不能更改
	return 0;
}

输出

p = 008FFE64
&arr[0] = 008FFE64

例2

#include <stdio.h>

int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5];
	arr2 = arr1;//此代码错误,因为arr2是arr2数组首元素地址,一个指针常量,不能被修改
	return 0;
}

5.1.2 数组名两个例外

只有在两种情况下,数组名不是数组首元素地址
例外1:sizeof(数组名)

#include <stdio.h>
//32位环境测试
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	/*
	在sizeof内部单独放一个数组名,数组名表示整个数组大小,int类型大小为4,元素个数为5,数组大小为20字节
	而不是当作一个地址大小计算(32位平台地址大小为4字节)
	*/
	printf("%d\n", sizeof(arr));
	return 0;
}

输出

20

例外2:&数组名

#include <stdio.h>
//32位环境测试
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	printf("&arr = %p\n", &arr); // &数组名,取出的是数组地址
	printf("&arr[0] = %p\n", &arr[0]); // &arr[0],取出数组首元素地址
	printf("arr = %p\n", arr); // 数组名表示首元素地址,取出数组首元素地址
	printf("---------------\n");
	printf("&arr+1 = %p\n", &arr+1);
	printf("&arr[0]+1 = %p\n", &arr[0]+1);
	printf("arr+1 = %p\n", arr+1);
	return 0;
}

输出

&arr = 004FF924
&arr[0] = 004FF924
arr = 004FF924
---------------
&arr+1 = 004FF938
&arr[0]+1 = 004FF928
arr+1 = 004FF928

在这里插入图片描述

5.2 下标引用与指针表达式

我们之前访问(修改)数组元素时是使用下标引用,但我们知道数组名一般情况下就是数组首元素的地址,是一个指针常量,所以本质上下标引用是指针表达式的伪装

array[subscript ] 等同于 *(array + subscript )

#include <stdio.h>
//32位环境测试
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int i = 0;
	printf("使用下标引用方式打印元素值\n");
	for (i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);
	}
	int* p = arr; //整形指针变量p存放arr数组首元素地址
	printf("\n使用指针表达式方式打印元素值\n");
	for (i = 0; i < 5; i++)
	{
		printf("%d ", *(p+i)); // *(p+i) 等同于 *(arr+i)
	}

	return 0;
}

输出

使用下标引用方式打印元素值
1 2 3 4 5
使用指针表达式方式打印元素值
1 2 3 4 5

当指针在表达式左边时,修改指针指向空间存放的值
当指针在表达式右边时,访问指针指向空间存放的值

#include <stdio.h>
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int* p = arr;
	*p = 10; //指针在表达式左边,修改p指向空间存放值,等同于 arr[0] = 10;
	int i = *p;  //指针在表达式右边,访问p指向空间存放值,等同于 int i = arr[0]
	printf("*p = %d\n", *p);
	printf("i = %d\n", i);
	printf("arr[0] = %d\n", arr[0]);
	return 0;
}

输出

*p = 10
i = 10
arr[0] = 10

说明:
1.下标引用相对于指针表达式可读性更强
2.下标引用绝不会比指针更有效率,但指针有时会比下标引用更有效率

5.3 数组与指针区别

数组和指针并不是相等的

  1. 声明一个数组时,编译器根据声明所指定的元素个数为数组分配内存空间,然后再创建数组名,它的值是一个常量,指向这片空间的起始位置。
  2. 声明一个指针变量时,编译器只为指针本身分配内存空间,如果指针变量是一个全局变量则默认初始化为NULL,如果是局部变量不会被初始化,存放的是一个随机地址值

在这里插入图片描述

六、数组与函数

6.1 一维数组传参的函数设计

一位数组数组名是数组首元素地址
当在一个函数内部使用外部某个一维数组时,需要将该一维数组传入函数内,我们之前一般设计为数组形式

例1:

#include <stdio.h>
//32位环境测试

void print1(int arr[5]) //形式参数设计为数组形式
{
	int i = 0;
	for (i = 0; i < 5; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	print1(arr);
	return 0;
}

例2 :
我们发现例1设计时,函数写死了,数组元素个数只能为5,当外部数组元素个数不是5的时候,函数需要修改,有些人会想到在函数内部通过 sizeof(arr) / sizeof(arr[0])求元素个数,这是一种错误设计方法

#include <stdio.h>
//32位环境测试
/*
传入实参arr是数组名,本质是数组首元素地址,是一个指针类型,形参应该设计为int* p,但也可以写成int arr[]
[]内元素个数可以不指定,因为编译器都会当作指针来处理
*/
void print2(int arr[]) 
{
	int i = 0;
	/*
	arr是一个指针,sizeof(arr)是求指针大小,32位指针大小为4字节,64位为8字节,本机是32位环境
	所以sizeof(arr) = 4。
	sizeof(arr[0]) 由于arr[0]是int类型,所以sizeof(arr[0])等同于sizeof(int) = 4。
	即sizeof(arr) / sizeof(arr[0]) 等同于 4 /4 =1,即 int size = 1;
	*/
	int size = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < size; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	print2(arr);//传入实参
	return 0;
}

例3:
通过例2我们知道,如果在函数内部需要知道外部数组元素个数,可通过一个显式的参数传递给函数,指明数组元素个数

#include <stdio.h>
//32位环境测试

void print3(int arr[],int size) //形式参数设计为数组形式,size为数组元素个数
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int size = sizeof(arr) / sizeof(arr[0]);
	print3(arr,size);
	return 0;
}

例4
我们知道一维数组的数组名为数组首元素地址,所以函数参数设计时应该用指针类型

#include <stdio.h>
//32位环境测试

void print4(int* p,int size) //形式参数设计为指针形式
{
	int i = 0;
	for (i = 0; i < size; i++)
	{
		printf("%d ", *(p+i));
	}
}
int main()
{
	int arr[5] = { 1,2,3,4,5 };
	int size = sizeof(arr) / sizeof(arr[0]);
	print4(arr,size);
	return 0;
}

结论:
1.一维数组传参传递的是数组首元素地址,本质是指针
2.我们推荐使用例3、例4的方式进行函数设计,尤其是例4

6.2 二维数组传参的函数设计

二维数组的数组名是首元素地址,首元素是一个一维数组,即数组指针

int arr[3][4];
int (*p)[4] = arr; //p存放数组arr首元素地址 
//  下标引用运算符优先级高于间接访问运算符,但是由于小括号存在,p首先与*匹配,即p是指针。接下来与
//  下标引用运算符匹配,表明p指向某种类型的数组,最后与int匹配,所以p是一个指向数组的指针,数组有10个
//  元素,每个元素为int类型

当在一个函数内部使用外部某个二维数组时,需要将该二维数组传入函数内,我们之前一般设计为数组形式
例1:

#include <stdio.h>
//32位环境测试

void print1(int arr[2][2]) //数组形式
{
	int i = 0;
	int j = 0;
	for (i = 0; i < 2; i++)
	{
		for (j = 0; j < 2; j++)
		{
			printf("arr[%d][%d] = %d ", i, j, arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[2][2] = { {1,2},{3,4} };
	print1(arr);
	return 0;
}

例2:
我们发现例1设计时,函数写死了,数组的行和列只能为2,其他情况函数需要修改。可通过2个显式的参数传递给函数,指明行和列

#include <stdio.h>
//32位环境测试

/*
二维数组的数组名是一维数组的地址,本质是一个指针,使用数组形式传参,列的大小无法省略,这一点只能写死
*/
void print2(int arr[][2],int row,int col) //数组形式
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("arr[%d][%d] = %d ", i, j, arr[i][j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[2][2] = { {1,2},{3,4} };
	int row = sizeof(arr) / sizeof(arr[0]); //算出行
	int col = sizeof(arr[0]) / sizeof(arr[0][0]); //算出列
	print2(arr,row,col);
	return 0;
}

例3
我们知道二维数组的数组名为数组首元素地址,即1个一维数组的地址,所以函数参数设计时应该用指针类型

#include <stdio.h>
//32位环境测试


void print3(int (*p)[2], int row, int col) //指针形式
{
	int i = 0;
	int j = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			printf("arr[%d][%d] = %d ", i, j, *(*(p+i)+j)); 
		}
		printf("\n");
	}
}
int main()
{
	int arr[2][2] = { {1,2},{3,4} };
	int row = sizeof(arr) / sizeof(arr[0]); //算出行
	int col = sizeof(arr[0]) / sizeof(arr[0][0]); //算出列
	print3(arr,row,col);
	return 0;
}

例4
前三种方式列的个数只能为2,在设计这个函数时候我们可能并不知道这个二维数组的行数,只有调用者最清楚该二维数组行数,由于多维数组每个元素在内存中是连续存储,我们可以将二维数组作为一个一维数组使用

void print4(int* p,int row,int col)
{
    for(int i = 0;i<row;i++)
    {
        for(int j = 0;j<col;j++)
        {
            printf("%d ",p[i*row+j]);
        }
        printf("\n");
    }
}
int main()
{
    int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
    int row = sizeof(arr) / sizeof(arr[0]); //算出行
	int col = sizeof(arr[0]) / sizeof(arr[0][0]); //算出列
    print4((int*)arr,3,4);
    
    return 0;
}
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值