7.数组、指针和数组的关系

本文采用了《C Primer Plus》、《C陷阱与缺陷》以及比特科技的教学视频。

对C语言数组、指针和数组的关系进行了详细讲解;并将初学者在使用数组时遇到的问题进行了总结,希望对各位读者有所帮助。

目录

1.一维数组

一维数组的创建

一维数组的初始化

(一)传统方法

(二)指定初始化器(C99)

一维数组的使用

数组边界

指定数组的大小

一维数组在内存中的存储

2.二维数组

二维数组的创建

二维数组的初始化

二维数组的使用

二维数组在内存中的存储

3.指针和数组

数组名是什么?

通过地址对数组进行操作

数组参数、指针参数


1.一维数组

一维数组的创建

数组就是由数据类型相同的一系列元素组成,创建普通变量时的数据类型,在创建数组中均适用。

一维数组创建的一般格式:元素类型 数组名 [常量表达式];

示例1:

#include<stdio.h>
int main()
{
    int arr1[10];//10个int类型元素的数组
	char arr2[20];//10个char类型元素的数组
	double arr3[30];//10个double类型元素的数组
	return 0;
}

这里arr1、arr2、arr3都是数组名,[]中的10、20、30均是常量,很好理解。

示例2:

#include<stdio.h>

#define n 10

int main()
{
	int arr1[n];//10个int类型元素的数组
	return 0;
}

通过用define定义常量,也可以实现数组的创建。

示例3:

#include<stdio.h>

int main()
{
	int n = 10;
	char a[n];
	return 0;
}

以上方法为C99标准所支持的变长数组(VLA)的概念,在C99标准之前[]中必须赋予一个常量。

值得注意的是:并不是所有编译器都支持变长数组(VS编译器就不支持),并且变长数组存在很多弊端,不建议使用。

数组的创建和使用都是静态的,无论用那种方式创建数组,在使用中不可将其空间大小改变。若是想动态的改变空间存放数据,则需要学到动态规划的知识。

一维数组的初始化

(一)传统方法

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

int arr1[5] = {1,2,3,4,5};//完全初始化

int arr2[10] = {1,2,3};//不完全初始化

int arr3[] = {1,2,3,4};//编译器按输入内容自行定义数组大小
  • 完全初始化:为数组内的每一元素都赋予其值。
  • 不完全初始化:未赋值的元素,编译器自动初始化为0。
  • 省略[]中的常量:编译器会自动根据输入的内容来合理分配空间。

需要区分以下两种初始化形式

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

字符串的末尾默认为‘\0’,以字符串形式初始化不用输入‘\0’;若是以单个字符输入时,需要在结尾单独输入‘\0’,并且‘\0’占一个字节的空间。

如果不进行初始化会怎样?

#include<stdio.h>
int main()
{
	char arr1[10];
	printf("%s\n", arr1);
}

如上述代码所示,创建了arr1数组,其中包含10个char类型的数据,在创建之初并没有对其进行初始化,打印arr1数组。

这是因为没有对数组进行初始化,数组中存放的都是垃圾信息,并且arr1数组是char类型的,编译器找不到终止符‘\0’,打印时会一直打印,超出数组的大小限制。

(二)指定初始化器(C99)

传统方法中,若是想初始化一个数组中的最后一个元素,必须将该元素以及之前的元素全部初始化。而在C99标准中,支持针对某一个元素进行初始化,而其他未被初始化的元素默认初始化为0。

int arr1[5]={0,0,0,0,1};//将第五个元素初始化为1
int arr2[5] = {[4]=1};//数组下标从0开始,4表示第五个元素

arr1和arr2的初始化结果是一样的。

示例1:

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

通过以上示例可以明白指定初始化器的两个特性:

  • 如果初始化器后面有更多的值:[4]=2,3,4,那么这些值将赋予指定初始化元素后面的元素。arr[4]被初始化为2后,紧接着arr[5]被初始化为3,arr[6]被初始化为4。
  • 如果再次初始化指定元素,那么最后一次初始化将会取代前一次初始化。例如[2]=1,最开始将1的值赋给arr1[2],但是arr1[2]又被后来的[2]=10初始化为10。

不指定数组大小会怎样?

int main()
{
	int arr1[] = {[2]=1,[4]=2,3,4};
	return 0;
}

编译器会根据指定初始化的内容合理分配空间,初始化的最后一个元素为arr[6],所以编译器为其分配7个元素的空间。

一维数组的使用

声明完数组后,借助数组的下标可对其进行赋值。

#define num 5
int main()
{
	int arr1[num] = {0};
	int i = 0;
	for (i = 0; i < num; i++)
	{
		arr1[i] = i;
	}
	return 0;
}

C语言中不允许把数组作为一个值赋给另一个数组,并且除了初始化外,不允许以花括号的形式进行赋值,以下为错误示例:

#define num 5
int main()
{
	int arr1[num] = { 1,2,3,4,5 };
	int arr2[num] = { 0 };

	arr2 = arr1;//不允许
	arr1[num] = arr2[num];//数组下标越界
	arr2[num] = { 1,2,3,4,5 };//无效
	
	return 0;
}
  • arr1[num] = arr2[num]理解为:将arr2中下标为num的元素,将其值赋给arr1中下标为num的元素,但是arr1和arr2中的元素下标最大为num-1,访问越界。
  • 若将其改成arr1[num-1] = arr2[num-1],仅为下标是[num-1]元素的赋值操作,并不是数组赋值操作。

数组边界

在使用数组时,需要防止数组下标超出边界,必须确保数组下标是有效值。

例如:

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

arr1数组中有效下标为0~4,访问越界时,打印的就是无意义的数。但是编译器并不会报错,可以正常运行。这是因为C语言信任程序员的原则,不检查边界。为了C语言可以运行的更快,编译器没有必要捕获所有的下标错误。为了防止访问出界,在声明数组时用符号常量来表示数组的大小。

指定数组的大小

在前文创建数组时,举了三种例子来指定数组的大小,这里更加详细的介绍指定数组大小的各种方法:

#define size 5
int main()
{
	int a1[size];//可以,整数符号常量
	int a2[5];//可以,整数字面常量
	int a3[5 * 2 - 1];//可以,整数常量表达式
	int a4[0];//不可以,数组大小必须大于0
	int a5[-1];//不可以,数组大小必须大于0
	int a6[2.5];//不可以,数组大小必须是整数
	int a7[(int)2.5];//可以,强制类型转换成整型
	int a8[sizeof(int)];可以,sizeof表达式被视为整型常量
	int n = 5;
	int a9[n]; //变长数组VLA,C99之前不允许
	return 0;
}

值得注意的是,const修饰的常变量,虽然具有常量的属性,但本质还是变量,C99标准前不允许用const修饰的常变量指定数组的大小。

 

一维数组在内存中的存储

创建一个整型数组,打印其地址(以X86环境为例)

#include <stdio.h>
int main()
{
	int arr[10] = { 0 };
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);

	for (i = 0; i < sz; ++i)
	{
		printf("&arr[%d] = %p\n", i, &arr[i]);
	}
	return 0;
}

结果如下:

通过观察可以得知:数组元素在存放时是连续存放的,随着下标的增长由低地址指向高地址,本例中arr为整型数组,所以每一个元素之间间隔4个字节。

2.二维数组

二维数组的创建

一维数组创建的一般格式:元素类型 数组名 [常量表达式1][常量表达式2];表达式1为数组的行,表达式2为数组的列。

int arr1[3][3];
char arr1[3][3];
double arr1[3][3];

二维数组的初始化

int arr[3][3] = { {1,2,3},{4,5,6},{7,8,9} };
int arr[3][3] = { 1,2,3,4,5,6,7,8,9 };//只要对应的数值正确,也可以省略内部的小括号
int arr[][3] = { 1,2,3,4,5,6,7,8,9 };//初始化时,行可以省略,列不能省略

例1:

下述代码初始化数组的结果是什么?

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

结果:

 与一维数组一样,未初始化的元素默认为0。

例2:

下述代码初始化数组的结果是什么?

int arr5[][3] = { {1,2,3},{(4,5),5},{7}};

 结果:

(4,5)为逗号表达式,赋值时取后者5。

二维数组的使用

与一维数组一样,二维数组的使用也是用for循环遍历,区别在于二维数组需要用到两个for循环嵌套使用。

打印一个二维数组:

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

二维数组在内存中的存储

打印二维数组中每一位的地址

#include <stdio.h>
int main()
{
	int arr[3][4];
	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;
}

结果:

通过观察发现,二维数组的存储也是连续的,先存储第一行的元素,然后再存储第二行的,以此类推。

3.指针和数组

指针提供一种以符号形式使用地址的方法,使用指针可以更加有效率的存储数据,而数组则是变相的使用指针?

数组名是什么?

对于一维数组来说,数组名就是首元素的地址。

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

打印arr的地址和arr首元素arr[0]的地址,发现二者是相同的。通过对arr地址的解引用*,可以找到该数组的首元素1。

对于二维数组来说,数组名就是第一行元素的地址。

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

 结果:

第一行元素的地址又等于其第一行中首元素的地址,打印arr的地址和第一行第一个元素的地址,发现其二者相同。(*arr)[0]表示为:通过对arr进行解引用找到第一行,再从第一行中找到下标为0的元素,即1。

通过地址对数组进行操作

打印一维数组的每一位

#include <stdio.h>
#define n 5
int main()
{
	int arr[n] = { 1,2,3,4,5};
	int i = 0;
	for (i = 0; i < n; i++)
	{
		printf("%d ", *(arr + i));
	}
	return 0;
}

打印二维数组的每一位

#include <stdio.h>
#define n 3
int main()
{
	int arr[][n] = { 1,2,3,4,5,6,7,8,9};
	int i = 0, j = 0;
	for (i = 0; i < n; i++)
	{
		for (j = 0; j < n; j++)
		{
			printf("%d ", *( * (arr + i)+j));
		}
		putchar('\n');
	}
	return 0;
}

数组参数、指针参数

设计一个函数,返回值是数组内所有整数的和。

一维数组传参的几种形式:

数组形式:

#include <stdio.h>

int text1(int arr[],int sz)//int arr[3]亦可,arr[]里有没有数都一样
{
	int i = 0;
	int sum = 0;
	for (i = 0; i < sz; i++)
	{
		sum += arr[i];
	}
	return sum;
}


int main()
{
	int arr[3] = {1,2,3};
	int sz = sizeof(arr) / sizeof(arr[0]);
	return 0;
}

因为数组名是首元素的地址,传入到text函数中,看似arr还是数组,其实只是首元素的地址,还需要将数组的大小sz也传入text函数中。

指针形式:

int text(int* arr, int sz)
{
	int i = 0;
	int sum = 0;
	for (i = 0; i < sz; i++)
	{
		sum += *(arr+i);
	}
	return sum;
}

二维数组传参的几种形式:

数组形式:

#include <stdio.h>
#define ROW 3
#define COL 3

int text(int arr[][ROW], int row,int col)//int arr[][ROW]接收时,可以省略行,不能省略列
{
	int i = 0, j = 0;
	int sum = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			sum += arr[i][j];
		}
	}
	return sum;
}

int main()
{
	int arr[ROW][COL] = {1,2,3,4,5,6,7,8,9};
	printf("%d\n", text(arr, ROW,COL));
	return 0;
}

指针形式:

#include <stdio.h>
#define ROW 3
#define COL 3

int text(int (*arr)[ROW], int row, int col)
{
	int i = 0, j = 0;
	int sum = 0;
	for (i = 0; i < row; i++)
	{
		for (j = 0; j < col; j++)
		{
			sum += *(*(arr+i)+j);
		}
	}
	return sum;
}

int main()
{
	int arr[ROW][COL] = { 1,2,3,4,5,6,7,8,9 };
	printf("%d\n", text(arr, ROW, COL));
	return 0;
}

数组形式和指针形式的本质都是地址的使用,二者在实质上没有区别。

扫雷三子棋的核心思想就是二维数组的运用。

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是元笙阿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值