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

7.数组、指针和数组的关系,第1张

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

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

目录

1.一维数组

一维数组的创建

一维数组的初始化

(一)传统方法

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

一维数组的使用

数组边界

指定数组的大小

一维数组在内存中的存储

2.二维数组

二维数组的创建

二维数组的初始化

二维数组的使用

二维数组在内存中的存储

3.指针和数组

数组名是什么?

通过地址对数组进行 *** 作

数组参数、指针参数


1.一维数组 一维数组的创建

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

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

示例1:

#include
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

#define n 10

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

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

示例3:

#include

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’占一个字节的空间。

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

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

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

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

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

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

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;
}

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

示例1:

  • 如果初始化器后面有更多的值:[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; }

    不指定数组大小会怎样?

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

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

    一维数组的使用

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

    #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;
    }

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

  • 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; }

    数组边界

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

    例如:

    #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;
    }

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

    指定数组的大小

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

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

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

     

    一维数组在内存中的存储

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

    #include 
    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 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 };//初始化时,行可以省略,列不能省略
    二维数组的初始化
    int arr4[][3] = { {1,2,3},{4,5},{7} };

    例1:

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

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

    结果:

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

    例2:

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

    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");
    	}

     结果:

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

    二维数组的使用

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

    打印一个二维数组:

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

    二维数组在内存中的存储

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

    #include 
    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 
    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 
    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 
    #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 
    #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 
    
    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函数中。

    指针形式:

     
    

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

    数组形式:

    #include 
    #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 
    #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;
    }

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

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

    欢迎分享,转载请注明来源:内存溢出

    原文地址:https://54852.com/langs/1499150.html

    (0)
    打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
    上一篇 2022-06-25
    下一篇2022-06-25

    发表评论

    登录后才能评论

    评论列表(0条)

      保存