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

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

相关文章

深入理解Go语言中二维切片的使用

《深入理解Go语言中二维切片的使用》本文深入讲解了Go语言中二维切片的概念与应用,用于表示矩阵、表格等二维数据结构,文中通过示例代码介绍的非常详细,需要的朋友们下面随着小编来一起学习学习吧... 目录引言二维切片的基本概念定义创建二维切片二维切片的操作访问元素修改元素遍历二维切片二维切片的动态调整追加行动态

Java中的数组与集合基本用法详解

《Java中的数组与集合基本用法详解》本文介绍了Java数组和集合框架的基础知识,数组部分涵盖了一维、二维及多维数组的声明、初始化、访问与遍历方法,以及Arrays类的常用操作,对Java数组与集合相... 目录一、Java数组基础1.1 数组结构概述1.2 一维数组1.2.1 声明与初始化1.2.2 访问

C# 比较两个list 之间元素差异的常用方法

《C#比较两个list之间元素差异的常用方法》:本文主要介绍C#比较两个list之间元素差异,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1. 使用Except方法2. 使用Except的逆操作3. 使用LINQ的Join,GroupJoin

java中新生代和老生代的关系说明

《java中新生代和老生代的关系说明》:本文主要介绍java中新生代和老生代的关系说明,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、内存区域划分新生代老年代二、对象生命周期与晋升流程三、新生代与老年代的协作机制1. 跨代引用处理2. 动态年龄判定3. 空间分

MySQL查询JSON数组字段包含特定字符串的方法

《MySQL查询JSON数组字段包含特定字符串的方法》在MySQL数据库中,当某个字段存储的是JSON数组,需要查询数组中包含特定字符串的记录时传统的LIKE语句无法直接使用,下面小编就为大家介绍两种... 目录问题背景解决方案对比1. 精确匹配方案(推荐)2. 模糊匹配方案参数化查询示例使用场景建议性能优

关于集合与数组转换实现方法

《关于集合与数组转换实现方法》:本文主要介绍关于集合与数组转换实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1、Arrays.asList()1.1、方法作用1.2、内部实现1.3、修改元素的影响1.4、注意事项2、list.toArray()2.1、方

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语

MySQL JSON 查询中的对象与数组技巧及查询示例

《MySQLJSON查询中的对象与数组技巧及查询示例》MySQL中JSON对象和JSON数组查询的详细介绍及带有WHERE条件的查询示例,本文给大家介绍的非常详细,mysqljson查询示例相关知... 目录jsON 对象查询1. JSON_CONTAINS2. JSON_EXTRACT3. JSON_TA

Java空指针异常NullPointerException的原因与解决方案

《Java空指针异常NullPointerException的原因与解决方案》在Java开发中,NullPointerException(空指针异常)是最常见的运行时异常之一,通常发生在程序尝试访问或... 目录一、空指针异常产生的原因1. 变量未初始化2. 对象引用被显式置为null3. 方法返回null

JAVA数组中五种常见排序方法整理汇总

《JAVA数组中五种常见排序方法整理汇总》本文给大家分享五种常用的Java数组排序方法整理,每种方法结合示例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录前言:法一:Arrays.sort()法二:冒泡排序法三:选择排序法四:反转排序法五:直接插入排序前言:几种常用的Java数组排序