当前位置:   article > 正文

【C语言初阶学习笔记】数组(超详细内容总结)_c语言数组知识点总结

c语言数组知识点总结

 

目录

前言:

一 、一维数组的创建和初始化

        1.1 数组的创建

        1.2 数组的初始化 

        1.3 一维数组的使用

        1.4 一维数组在内存中的存储

二、二维数组的创建和初始化

        2.1 二维数组的创建

        2.2 二维数组的初始化

        2.3 二维数组的使用

        2.4  二维数组在内存中的存储

三、数组的越界访问

四、数组作为函数参数

小结


前言:

      数组是在程序设计中,为了处理方便,把具有相同类型的若干元素有序的形式组织起来的一个形式。我们把相同类型元素的集合称之为数组。在C语言中,数组归属于构造数据类型。

      按数组元素的类型不一样,数组又可分成数值数组、字符数组、指针数组、结构数组等各种类别。  


一 、一维数组的创建和初始化

1.1 数组的创建

   数组的创建方式:

  1. type_t   arr_name   [const_n];
  2. //type_t 是指数组的元素类型
  3. //arr_name 是数组的名字
  4. //const_n 是一个常量表达式,用来指定数组的大小

 单单像这样讲略显枯燥,下面我们用具体的代码好好学一学:

  1. int arr[5];
  2. //创建一个 int 类型的数组,数组名叫arr,数组里面有五个元素
  3. char ch[10];
  4. //创建一个 char 类型的数组,数组名叫ch,数组里面有十个元素

不过,我们要知道,创建数组的时候,[ ]里面的数字必须是常量。在C99标准之前是不支持使用变量的,只能是常量!在C99中增加了变长数组的概念,允许数组的大小是变量,而且要求编译器支持C99标准!但是我们常见的编译器对C99标准支持的不够好,比如说,VS系列编译器就是不咋的支持!

我用的就是VS2019,很明显它不是特别支持的。


对了,小声提一句,如果是变长数组的话就不可以初始化的;如上,用变量指定数组的大小,而变量是在代码运行起来的时候创建的,而n在初始化的时候,编译器不知道数组有多少个元素,所以初始化时没有意义的,这是语法限制死的。


1.2 数组的初始化 

我们在创建数组的同时给数组一些初始值叫初始化:

  1. int arr1[10] = {1,2,3,4,5,6,7,8,9,10};
  2. //完全初始化
  3. int arr2[10] = {1,2,3};
  4. //不完全初始化,剩余的元素默认为0
  5. int arr3[10] = {0};
  6. //不完全初始化,剩余的元素默认为0
  7. int arr4[ ] = {1,2,3};
  8. //数组会根据初始化的内容,默认[ ]的数字,如上默认为存放了3个元素

上面数组可以在编译器监事窗口看到:


不过,这里我们需要注意一个小细节,就是在用字符串作为数组的初始值时,稍微注意一下:

 如上所示,arr1有3个元素,数组的大小是3个字节,arr2有4个元素,数组的大小是4个字节。下面我们来测试一下:


注意:

1、strlen是一个库函数,计算的是字符串的长度,并且只能针对字符串;关注的字符串中是        否有\0,计算的是\0之前的字符个数。

2、sizeof是一个操作符(运算符),sizeof使用来计算变量所占空间的大小的,任何类型都          可以使用;只关注空间大小,不在乎内存中是否存在\0。

 在使用printf打印字符以及strlen求字符串长度时候,遇到’\0’才停止,没遇到’\0’之前不停止。


1.3 一维数组的使用

对于数组的使用我们之前介绍了一个操作符:[ ] 下标引用操作符。它其实就数组访问的操作符。

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int arr[10] = { 0 };
  5. int sz = sizeof(arr) / sizeof(arr[0]);//计算数组的元素个数
  6. int i = 0;//数组是使用下标来访问的,下标从0开始。
  7. for (i = 0; i < sz; ++i)//输入数组
  8. {
  9. scanf("%d",&arr[i]);
  10. }
  11. for (i = 0; i < sz; ++i)//输出数组的内容
  12. {
  13. printf("%d ", arr[i]);//arr[i]这个不是在创建数组,而是在访问数组的某一个元素
  14. //访问的是下标,是可以用变量的
  15. }
  16. return 0;
  17. }

 

1.4 一维数组在内存中的存储

要想知道一位数组是怎样在内存中存储的,我们可以先把它打印出来,然后再编译器里看:

为什么会相差4呢?——因为一个整型元素的大小是4个字节 

 

 总结:

1.一维数组在内存中是连续存放的。
2.随着数组下标的增长,地址是由低到高变化的。


数组地址连续存放有什么实际意义呢?

 我们可以很容易推测,p+i是数组中arr[i]的地址;

下面我们可以验证一下:


理解了这个以后,我们就可以不用下标的形式访问数组内容了:

 这个例子可以很好的说明,因为数组是连续存放的,通过数组首元素的地址往后找可以找到每一个数组对应的元素!


二、二维数组的创建和初始化

2.1 二维数组的创建

  1. int arr1[3][4];
  2. //34列的二维数组,里面的元素类型是 int类型
  3. char arr2[3][4];
  4. //34列的二维数组,里面的元素类型是 char类型
  5. double arr3[4][5];
  6. //45列的二维数组,里面的元素类型是 double类型

有了前面的一维数组的知识,我们就可以更容易的理解二维数组的概念,请看下面的例子:

 2.2 二维数组的初始化

第一种方式:

  1. int arr1[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };//完全初始化
  2. int arr2[3][4] = { 1,2,3,4,5,6,7 };//不完全初始化
  3. int arr3[3][4] = { {1,2},{3,4},{5,6} };//不完全初始化

               


第二种方式:

  1. //初始化的时候括号里面的数字是否可以省略
  2. //行可以省略,列不可以省略

 看,这个就是一种错误的初始化方式。虽然我们想像一位数组一样,通过数组的内容来判断数组里面有几个元素。因为我们不知道到底有多少行多少列。是1行12列,2行6列,3行4列,4行3列......当行和列都没有指定的时候,这种写法肯定是错误的。

注意:二维数组在创建的时候行可以省略,列不能省略(第一个[ ]中的值可以省略,第二个[             ]中的值必须要写出来)。


思考:那么为什么二维数组在创建的时候行可以省略,列不能省略呢?

在一维数组中,数组名[常量表达式]是它的定义方式;

而在二维数组中,可以看成是一维数组的数组,那么,arr[0]、arr[1]、arr[2]就可以看成是第一行、第二行、第三行,就是一个新的数组名; 

所以说,arr[ ][4]是这个样子的:

知道了列数,就知道第二行该从哪里出发,第三行该从哪里出发。

但是如果只知道了行数,那是没有办法知道有多少列的。

实在不理解的话,可以想一想一维数组的用法,把arr[0]、arr[1]、arr[2]看成新的数组名,类似于一维数组,又加了一个[ ]的用法。 

2.3 二维数组的使用

 二维数组的使用方式也是通过下标的方式。二维数组的行和列下标都是从0开始的:

让我们看一看这个例子:

 

从上面可以看出,下标是[2][3]的元素是12,结果果然就是12。


 打印二维数组的元素:

  1. #include<stdio.h>
  2. int main()
  3. {
  4. int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
  5. int i = 0;
  6. for (i = 0; i < 3; i++)
  7. {
  8. int j = 0;
  9. for (j = 0; j < 4; j++)
  10. {
  11. printf("%d ", arr[i][j]);
  12. }
  13. printf("\n");
  14. }
  15. return 0;
  16. }

2.4  二维数组在内存中的存储

为了研究二维数组的存储,这里我们可以尝试将二维数组的每个元素都打印出来:

我们可以看出,第一行的每个元素之间间隔了4个字节,而第一行最后一个元素到第二行第一个元素也是间隔了4个字节,后面的也是这样。

就这样,虽然在我们的感觉中二维数组是多行多列的,实际上,在内存中的存储方式是这样的:

二维数组可以看成是一维数组的数组 。

结论:二维数组在内存中也是连续存储的。

三、数组的越界访问

 在内存中,我们只申请了 arr[0]到arr[9] 的空间,而当运行到i=10的时候,超出了申请的空间的边界,就叫做越界访问。

四、数组作为函数参数

我们在写代码的时候,往往会将数组作为参数传给函数,比如:我们要实现一个冒泡排序函数将一个整型数组排序。那我们将这样使用函数: 

冒泡排序的思想:两两相邻的元素进行比较,如果有可能的话需要交换。

  1. 一趟冒泡排序能搞定一个数字
  2. 让当前待排的数组中的一个元素来到最终应该出现的位置上
  3. n个元素应该进行n-1趟的冒泡排序

 


下面先来展示一个错误的代码:

  1. #include <stdio.h>
  2. void bubble_sort(int arr[])//可以用数组来接收
  3. {
  4. //确定趟数
  5. int sz = sizeof(arr) / sizeof(arr[0]);
  6. int i = 0;
  7. for (i = 0; i < sz - 1; i++)
  8. {
  9. //一趟冒泡排序的过程
  10. int j = 0;
  11. //一趟冒泡排序比较的对数
  12. for (j = 0; j <sz-1-i ; j++) //第一趟:10个数字待排序,9对比较
  13. //第二趟:9个数字待排序,8对比较
  14. //第三趟:8个数字待排序,7对比较
  15. //……
  16. //第九趟:2个数字待排序,1对比较
  17. //sz-1是需要排序的趟数,sz-1-i是每一趟排序是两
  18. //两相互比较的对数
  19. {
  20. if (arr[j] > arr[j + 1])
  21. {
  22. //交换
  23. int tmp = arr[j];
  24. arr[j] = arr[j + 1];
  25. arr[j + 1] = tmp;
  26. }
  27. }
  28. }
  29. }
  30. int main()
  31. {
  32. //数组传参
  33. int arr[10] = { 1,4,6,3,2,5,8,9,7,0 };
  34. //设计一个函数对arr数组进行排序——冒泡排序的算法
  35. bubble_sort(arr);
  36. //数组在传参的时候传 数组名 就可以了
  37. //传arr[]是错的,传arr[10]是错的
  38. int i = 0;
  39. for (i = 0; i < 10; i++)
  40. {
  41. printf("%d ", arr[i]);
  42. }
  43. return 0;
  44. }

在vs2019上运行的结果是:

经过调试,发现错误的根源在这里:

 sz=1 => sz-1=0 => for循环不执行。


本质上:

 既然传过去的是首元素的地址,那么必定为指针接收,指针的大小sizeof(arr)在32位机器上是4个字节,sizeof(arr[0]),整形第一个元素的大小是4个字节,所以sz=1。


既然sz在里头不好算出来,那么我们可以在外头算一下,那么传过去,接收的还要加一个sz:

  1. #include <stdio.h>
  2. void bubble_sort(int arr[],int sz )
  3. {
  4. int i = 0;
  5. for (i = 0; i < sz - 1; i++)
  6. {
  7. int j = 0;
  8. for (j = 0; j <sz-1-i ; j++)
  9. {
  10. if (arr[j] > arr[j + 1])
  11. {
  12. //交换
  13. int tmp = arr[j];
  14. arr[j] = arr[j + 1];
  15. arr[j + 1] = tmp;
  16. }
  17. }
  18. }
  19. }
  20. int main()
  21. {
  22. int arr[10] = { 1,4,6,3,2,5,8,9,7,0 };
  23. int sz = sizeof(arr) / sizeof(arr[0]);
  24. bubble_sort(arr,sz);
  25. int i = 0;
  26. for (i = 0; i < 10; i++)
  27. {
  28. printf("%d ", arr[i]);
  29. }
  30. return 0;
  31. }

 此时运行的结果是:

 当然,因为传的是首地址,所以也可以这样定义:

void bubble_sort(int* arr, int sz)

 


思考:数组名的本质是什么?

我们经常会说数组名是首元素地址,那么这个说法对不对呢?这里我们可以验证一下。

 由此看来,数组名就是数组首元素的地址。


其实,这里还有两种特例:

1.sizeof(数组名)-- - 数组名表示整个数组-- - 计算的是整个数组的大小,单位是字节

 如果这种情况下,数组名是首元素的地址,那么,在32位机器下,程序运行出来的结果应该就是4,可是打印出来的是20;


2.&数组名-- - 数组名表示整个数组-- - 取出的是整个数组的地址。(这种情况下,明显是成立的,地址是一串编号,总不能说编号的编号吧)

除上述两种情况以外,其余情况数组名均表示首元素地址!


思考:数组地址和数组首元素地址有什么区别?

两者的地址值是一样的,但是含义和使用不同。

首元素的地址+1=>跳过一个元素(这个是整形,4个字节)

数组的地址+1 => 跳过一个数组(5个元素,每个大小是4个字节)

 

小结

这篇博客总结的是关于数组的原理的知识点 ,后面会总结出关于数组的应用实例。

有啥不足的地方,欢迎提出来一起共同进步哦!

如果喜欢这篇博客的话,欢迎铁汁们动动你们的小手,一键三连哦!

 

 

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/613098
推荐阅读
相关标签
  

闽ICP备14008679号