一,数组的介绍
数组是一组类型相同的值的集合,在数学里,N表示自然数集,Q表示有理数集,R表示实数集,数组可以类比一下数学中的这些集合。数组里的数据,在计算机里是连续存储的。
数组有很多类型,包括整型数组,字符数组,指针数组,函数指针数组等等。
数组里每一个数称为数组的元素。
比如下面这些数组
int main()
{
int a[5] = {0};//这是一个整型数组,数组里有5个数据,
//每个数都是int类型,a表示数组的名称,[]里的数表示数组的元素个数
char b[] = { "abcdef" };
//这是一个字符数组,数组的每个元素都是字符
//[]里可以不给数字,如果不给,那么数组的大小就是,后面花括号内的元素个数,但是这里是7
//因为字符串后面隐藏了一个\0
int length = sizeof(b) / sizeof(char);
printf("%d", length);//可以通过这种方式打印证明
int a1[] = { 0 };
int a2[] = { 0 };
int a3[] = { 0 };
int* c[] = {a1,a2,a3};//这是一个指针数组,每个元素都是指针类型
return 0;
}
计算数组的大小,可以用到关键字sizeof,方式为sizeof(数组名),即可计算数组的大小,那么该怎么计算数组元素的个数呢,总不能一个一个数吧,有了上面的基础很容易想到,用数组的大小除以元素类型即可
也就是sizeof(数组名)/sizeof(类型名),这样算出来的就是元素个数。
二,数组的创建与初始化
创建数组时,需要按照
类型 数组名[ ];
这样的格式创建,类型是数组内元素的类型,不是数组的类型,[ ]里面的数字表示数组元素的个数,且这个数字必须是常量。如果没有数字,比如
int a[ ]={0};这种是对的方式,下面这种方式是错误的,因为b是一个变量
int b=5;
int c[b]={0};
那么数组里就一个元素0,元素类型是int,那么数组的类型是什么
呢?首先将数组名去掉,剩下的就是数组的类型,比如
int [1];这个就是上面数组的类型
通过vs调试可以看到,数组a的类型为,int[5],b的类型为char[7],7也表示数组b有七个元素,c的类型为int*[3]。
创建数组后,还要进行初始化。
int a[10]={0};
创建的同时就可以初始化,这样叫不完全初始化,只初始化了一个元素的值
int b[5]={1,2,3,4,5};
这样是完全的初始化,每个元素之间用逗号隔开
有了数据,接下来就介绍如何访问。
三,数组的访问
1,下标访问
数组可以通过下标访问,下标就是数据的一个标识符,通过这个标识符找到数据。下标从0开始,依次增加,最大值是数组元素个数减一。比如
int a[5]={1,2,3,4,5};
数据1的下标是0,数据2的下标是1…数据5的下标是4
元素访问方式为:数组名[元素下标];
如要访问数据3,则可以写为a[2];
int main()
{
int a[] = {1,2,3,4,5};
for (int i = 0; i < 5; ++i)
{
printf("%d ", a[i]);
}
return 0;
}
通过一个for循环就可以打印数组内的元素,元素间用空格隔开。下标的最大值是元素个数减一,当下标大于等于元素个数时,还去访问的话,就会越界访问了。
2,指针访问
数组也可以通过指针访问,那么指针又是什么,通常讲的指针是指针变量,这个变量存储的是地址。在计算机中,每一个内存单元是一个字节,而每个字节又有一个地址,指针变量存储的就是这些地址,通俗的说,内存单元就像房间,房间里的人就是存的数据,这个房间号就是地址,我们可以通过这个房间号找到这个人,在计算机里,可以通过这个地址找到数据。
先看一个简单的代码
int main()
{
int a[] = {1,2,3,4,5 };
int* p = a; //定义一个指针变量,*代表p是一个指针变量,
//int代表p这个指针变量 指向的内存里 存储的数据的类型是int类型
int sz = sizeof(a) / sizeof(int); // 计算数组的大小
for (int i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
a是数组名,也是数组首元素的地址,正因为它是地址,所以才可以用指针变量存储。
p是一个指针变量,p+i代表这个指针跳过了i个整型。(在这里可以这么认为,但是不准确,后期会专门有指针的介绍和解析的。)
*(p+i)中的 *代表解引用,表示通过这个指针找到指针指向的这个数据。在依次循环,打印数据。
一个简图帮助大家理解。
如果a代表数组首元素的地址,那么打印是否可以这样呢
int main()
{
int a[] = {1,2,3,4,5 };
int sz = sizeof(a) / sizeof(int);
for (int i = 0; i < sz; i++)
{
printf("%d ", *(a + i));
}
return 0;
}
如果可以这样打印,就能少定义一个指针变量了,也简化了代码
通过运行测试,发现,打印结果是正确的。
所以,*(p+i)==*(a+i)==a[i],这样就可以很好的解释为什么,下标最大值不能是数组的个数,因为如果下标是数组的个数,那么a+i,就跳过了i个整型,即把整个数组都跳过了,那么访问的就不是数组的数据。
关于数组的访问,指针访问的方法比较难理解,但更底层。接下来介绍二维数组
四,二维数组
形如
int a[][5]={0};的数组称为二维数组。
在类型名,初始化,数组名等方面,和一维数组无太大的差异
a后面的两个[]里的数字,前一个表示数组有几行,后一个表示数组有几列,第一个数字可以省略,第二个不能省略。
我们可以把二维数组看成一个一维数组,一维数组的每一个元素又是一个数组。
数组在内存里是连续存储的。
通过结果,我们可以看到,每个数据的起始地址相差4。%p是用来打印地址的占位符,&符号表示取地址,&a[i]表示,取出下标为i的元素的地址,通过循环就可以依次打印每一个元素的地址。
二维数组可以看成一维数组,那么存储时也是连续的吗
我们写个代码测试一下 。
int main()
{
int a[][5] = {1,2,3,4,5, //创建一个三行五列的数组
6,7,8,9,10,
11,12,13,14,15};
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%p\n", &a[i][j]);
}
}
return 0;
}
运行结果如图
可以看到,地址值之间也是相差4,所以二维数组的数据在内存里也是连续存储的。既然是连续存储的,在创建二维数组时,如果不给列数,那么数组就可能有很多个,不唯一,但是如果不给行数,比如上面的代码,只给了一个5列,可以通过元素个数除以列数算出行数,是唯一的。
int main()
{
int a[5][] = { 1,2,3, //五行数据可以这样放
4,5,6,
7,8,9,
10,11,12,
13,14,15 };
int b[5][] = { 1,2,3,4, //五行数据也可以这样放
5,6,
7,8,9,
10,11,12,
13,14,15 };
}
所以行可以省略,列不可以省略。
二维数组又该如何访问呢,其实上面已经展示了,通过a[i][j]的形式,i代表行,j代表列,i和j都是从0开始。也不能大于或等于[]里的数字。
int main()
{
int a[][5] = {1,2,3,4,5, //创建一个三行五列的数组
6,7,8,9,10,
11,12,13,14,15};
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%d ", a[i][j]);
}
printf("\n");
}
return 0;
}
通过下标访问,a[i][j]表示,第i行,第j列的元素,比如a[2][2]就是13。
数组名表示首元素的地址,而二维数组的首元素是一个一维数组,那么创建一个指针变量来接收这个地址,然后在加1,这个操作的结果是什么呢?
我们通过代码来测试一下
int main()
{
int a[][5] = {1,2,3,4,5,
6,7,8,9,10,
11,12,13,14,15};
int(*p)[5] = a;
printf("%p\n", *p);
printf("%p",*(p+1));
return 0;
}
首先,解释一下 int(*p)[5]=a;这句代码,由于二维数组的首元素是一个一维数组,那么需要一个指针变量来指向这个一维数组,指向数组的指针叫做数组指针,这和前面的指针数组不一样,这两个概念很容易混,大家要注意一下噢。
*还是说明p是一个指针,和前面不同,加括号的原因是要让*和p先结合,括号的优先级高,这样p就是一个指针,去掉(*p)剩下的int [5]就是指针指向的数据的类型,可以看到,指向的数据是一个数组,里面有五个元素,每个元素是int型。
如果不加括号就变成int* p[5],这个大家应该很眼熟吧!这不就是指针数组嘛,p是数组名,数组的每个元素的类型是int*,所以这个括号是有必要加的。
了解到这,那么看一下运行结果吧
地址是十六进制表示的,前面都一样,只看最后两位,6C和58都是16进制,转化为十进制后分别是,108和88,它们相差20,刚好是5个整形,即20个字节。(关于进制的转换,后面也会详细的介绍)
通过结果,我们知道了,指向一个数组的指针加1操作,是跳过这个数组。
那么通过指针,该怎么访问二维数组呢
代码如下,运行结果如上。
int main()
{
int a[][5] = {1,2,3,4,5,
6,7,8,9,10,
11,12,13,14,15};
int(*p)[5] = a;
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%d ", *(*(p + i) + j));
}
printf("\n");
}
return 0;
}
*(*(p + i) + j)这句代码可能是难点,给大家一层层的分析,首先(p+i)是指跳过i行元素,存储的值是地址,前面在加一个*表示,对这一行的地址进行解引用,即找到这一行的数据。*(p+i)+j后的+j表示跳过这一行的j个数据,加一个*在进行解引用得到最后的表达式*(*(p + i) + j),即访问到第i行,第j列的元素,然后打印。
内容还是比较多的,关于指针的部分比较难,大家可以自己写一下代码,加深理解,有关数组的知识,就介绍到这里啦!