数组的递进理解——由一维到多维,数组和指针的关系比较等

2024-06-02 13:58

本文主要是介绍数组的递进理解——由一维到多维,数组和指针的关系比较等,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

最近写程序发现自己对数组方面的概念知识不是特别的清晰,然后就在网上查了些资料,把书上关于数组的知识复习了一遍,现在把我学习数组的过程记录下来,让自己对数组有更加深刻的影响。我主要是介绍了数组的一些基本概念,然后还有数组和指针之间的一些关系,例如:数组指针和指针数组等。下面就开始我对数组理解的一个递进的过程,希望能和大家一起分享讨论。当然这里不是纯粹的讲解数组的知识,如果是那样的话还不如自己去看书本呢,其实这里更多的是记录我在学习数组的过程中容易犯的错误以及我平时的一些总结(这些地方我都用红色给标出来了),好让大家以后少走一些弯路。
1、一维数组
1.1一维数组的初始化
int array1_int[5] = {0, 1, 2, 3, 4};
int array2_int[5] = {0, 1};
int array3_int[] = {0, 1, 2, 3, 4};
数组的下标是从’0‘开始的,所以定义了int array1_int[5],并不能达到array1_int[5]。当元素个数不够时,后面的元素初始化为0,但是不能忽略中间的值,忽略的值只能从后面开始。最后一个初始化式子说明,如果没有定义数组长度的时候,编译器就把数组长度设置为刚好能够容纳所有初始值的长度。调试结果如图:

char array1_char[6] = {'h', 'e', 'l', 'l', 'o', 0};
char array2_char[6] = "hello";
字符数组的初始化有如上两种方式,字符串要有结束标志符。其实我们平时用的更多的是下面的这种方式,因为它会自动添加结束标志符。调试结果如图:

这里有个要注意的地方,第二个式子中的“hello”看上去像是个字符串常量,实际上不是。当用于初始化一个字符数组时,它就是一个初始化列表。在其他任何地方,它都表示一个字符串常量。
char array2_char[6] = "hello";
char *parray = "hello";
上面这两种方式就是典型了,第一种方式中字符串是初始化列表;第二种方式是字符串常量。当然这里有的编译器(本人使用IDE:CodeBlocks,gcc编译器)会有warning: deprecated conversion from string constant to 'char*',意思就是“不赞成把字符串常量转换为char*,那么怎样才不会警告呢,就是把第二种方式改为:
char const *parray = "hello";
这样就不会有警告了,为什么呢?其实就是char*本身就是可以让你改变的意思,把一个不能改变的东西赋给一个可以改变它的变量,编译当然会发出警告了。
虽然只是警告也应该引起高度的重视,下面有个例子就说明了这个问题:
#include <stdio.h>int main(void)
{char *parray = "hello";*parray = 'a';printf("parray = %s\n", parray);return 0;
}
这个程序编译时,只会警告(如上面的警告),但是运行时就会出问题。如果你把这个程序修改一下,改成下面这种再看下:
#include <stdio.h>int main(void)
{char const *parray = "hello";*parray = 'a';printf("parray = %s\n", parray);return 0;
}
这个时候就不能通过编译了,会提示错误。这样我们就可以及时发现错误。
所以,如果我们及早改正这些问题的话,那么就可以节省很多调试的时间。

1.2一维数组和指针的关系
(1)数组名
int array[5];
数组名的值是一个指针常量,也就是数组一个元素的地址,类型是”指向int的常量指针“。从这里我们就可以发现数组名和指针是不一样的(本人在初学的时候总觉得它们是一样的概念),例如:不能自增array++是错误的,但是当数组名作为实参传递给函数参数的时候效果就一样了。
只有在两种场合下,数组名并不用指针常量来表示——就是当数组名作为sizeof操作符或单目操作符&的操作数时。siziof返回整个数组的长度,而不是指向数组的指针的长度。取一个数组名的地址所产生的是一个指向数组的指针,而不是一个指向某个指针常量值得指针。
(2)下标引用
除了优先级之外,下标引用和间接访问完全相同。例如下面这两个表达式是等同的:
array[5]
*(array + 5)
既然知道了这些,所以我们可以通过下面这个例子看到这个技巧的应用:
#include <stdio.h>int main(void)
{int i, array[5] = {0, 1, 2, 3, 4};int *parray = array;for (i=0; i<5; i++)printf("%d\n", parray[i]);return 0;



这里还有个让你很难接受的例子:
5[array]
做好心理准备吧,没错,这个式子是合法的,把它转换成对等的间接访问表达式如下:
*(array + 5)
这个诡异技巧之所以可行,缘于C实现下标的方法。对编译器来说,这两种形式并无差别。但是,你绝不应该编写5[array],因为它会大大影响程序的可读性。
(3)指针数组和数组指针
指针数组:array of pointers,指针组成的数组,就是每个数组元素都是指针。
int *parray[5](这里强调的是数组,数组中的每个元素的类型是:int *)
数组指针:a pointer to an array,一个指针,这个指针指向的是一个数组。
int (*parray)[5](这里强调的是指针,这个指针的类型是:int *[5])
(4)声明数组参数
当一个数组名参数传递给函数的时候,函数的原型可以定义如下:
void function(char *array_char);
void function(char *array_char[]);
你两种形式都可以用,但是哪个”更加准确“呢?答案是指针,因为实参实际上是个指针。
2、多维数组
多维数组和一维数组原理上是差不多的,但是理解上有一些要注意,就是把多维数组的看作一维数组,这个一维数组的每一个元素又是一个数组,那么后面就好理解了。    
初始化的时候注意用花括号来定界,这个技巧在初始化多维数组只有少数几个元素时尤其有效。下面看个例子(《c和指针》书本上的编程练习题):
#include <stdio.h>int main(void)
{unsigned char char_value[3][6][4][5] = {{},{{},{{},{0,' '}},{{},{},{0,0,0,'A'},{0,0,0,0,'x'}},{{},{},{0,0,0xf3}},{{},{},{0,0,0,'\n'}},{},},{{},{{},{0,0,0320}},{{},{0,'0'},{0,0,'\''},{0,'\121'}},{},{{},{},{},{0,0,'3',3}},{{},{},{},{0,0,0,0,125}},}};printf("1223=%x\n", char_value[1][2][2][3]);printf("1111=%x\n", char_value[1][1][1][1]);printf("1322=%x\n", char_value[1][3][2][2]);printf("2432=%x\n", char_value[2][4][3][2]);printf("1423=%x\n", char_value[1][4][2][3]);printf("2231=%x\n", char_value[2][2][3][1]);printf("2433=%x\n", char_value[2][4][3][3]);printf("2534=%x\n", char_value[2][5][3][4]);printf("1234=%x\n", char_value[1][2][3][4]);printf("2112=%x\n", char_value[2][1][1][2]);printf("2222=%x\n", char_value[2][2][2][2]);printf("2211=%x\n", char_value[2][2][1][1]);return 0;
}
多维数组在作为函数参数时,函数原型应按如下:
char array[3][5];void function(char (*array_char)[5]);
void function(char array_char[][5]);

PS:当然数组的知识远不止这些,由于本人能力有限,对数组的理解和总结就写到这里,希望大家一起讨论、学习。





这篇关于数组的递进理解——由一维到多维,数组和指针的关系比较等的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1024167

相关文章

C#比较两个List集合内容是否相同的几种方法

《C#比较两个List集合内容是否相同的几种方法》本文详细介绍了在C#中比较两个List集合内容是否相同的方法,包括非自定义类和自定义类的元素比较,对于非自定义类,可以使用SequenceEqual、... 目录 一、非自定义类的元素比较1. 使用 SequenceEqual 方法(顺序和内容都相等)2.

MYSQL关联关系查询方式

《MYSQL关联关系查询方式》文章详细介绍了MySQL中如何使用内连接和左外连接进行表的关联查询,并展示了如何选择列和使用别名,文章还提供了一些关于查询优化的建议,并鼓励读者参考和支持脚本之家... 目录mysql关联关系查询关联关系查询这个查询做了以下几件事MySQL自关联查询总结MYSQL关联关系查询

深入理解Apache Airflow 调度器(最新推荐)

《深入理解ApacheAirflow调度器(最新推荐)》ApacheAirflow调度器是数据管道管理系统的关键组件,负责编排dag中任务的执行,通过理解调度器的角色和工作方式,正确配置调度器,并... 目录什么是Airflow 调度器?Airflow 调度器工作机制配置Airflow调度器调优及优化建议最

对postgresql日期和时间的比较

《对postgresql日期和时间的比较》文章介绍了在数据库中处理日期和时间类型时的一些注意事项,包括如何将字符串转换为日期或时间类型,以及在比较时自动转换的情况,作者建议在使用数据库时,根据具体情况... 目录PostgreSQL日期和时间比较DB里保存到时分秒,需要和年月日比较db里存储date或者ti

Java 字符数组转字符串的常用方法

《Java字符数组转字符串的常用方法》文章总结了在Java中将字符数组转换为字符串的几种常用方法,包括使用String构造函数、String.valueOf()方法、StringBuilder以及A... 目录1. 使用String构造函数1.1 基本转换方法1.2 注意事项2. 使用String.valu

一文带你理解Python中import机制与importlib的妙用

《一文带你理解Python中import机制与importlib的妙用》在Python编程的世界里,import语句是开发者最常用的工具之一,它就像一把钥匙,打开了通往各种功能和库的大门,下面就跟随小... 目录一、python import机制概述1.1 import语句的基本用法1.2 模块缓存机制1.

深入理解C语言的void*

《深入理解C语言的void*》本文主要介绍了C语言的void*,包括它的任意性、编译器对void*的类型检查以及需要显式类型转换的规则,具有一定的参考价值,感兴趣的可以了解一下... 目录一、void* 的类型任意性二、编译器对 void* 的类型检查三、需要显式类型转换占用的字节四、总结一、void* 的

深入理解Redis大key的危害及解决方案

《深入理解Redis大key的危害及解决方案》本文主要介绍了深入理解Redis大key的危害及解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着... 目录一、背景二、什么是大key三、大key评价标准四、大key 产生的原因与场景五、大key影响与危

JAVA中整型数组、字符串数组、整型数和字符串 的创建与转换的方法

《JAVA中整型数组、字符串数组、整型数和字符串的创建与转换的方法》本文介绍了Java中字符串、字符数组和整型数组的创建方法,以及它们之间的转换方法,还详细讲解了字符串中的一些常用方法,如index... 目录一、字符串、字符数组和整型数组的创建1、字符串的创建方法1.1 通过引用字符数组来创建字符串1.2

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规