最近写程序发现自己对数组方面的概念知识不是特别的清晰,然后就在网上查了些资料,把书上关于数组的知识复习了一遍,现在把我学习数组的过程记录下来,让自己对数组有更加深刻的影响。我主要是介绍了数组的一些基本概念,然后还有数组和指针之间的一些关系,例如:数组指针和指针数组等。下面就开始我对数组理解的一个递进的过程,希望能和大家一起分享讨论。当然这里不是纯粹的讲解数组的知识,如果是那样的话还不如自己去看书本呢,其实这里更多的是记录我在学习数组的过程中容易犯的错误以及我平时的一些总结(这些地方我都用红色给标出来了),好让大家以后少走一些弯路。
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.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:当然数组的知识远不止这些,由于本人能力有限,对数组的理解和总结就写到这里,希望大家一起讨论、学习。