赞
踩
目录
前言:
数组是在程序设计中,为了处理方便,把具有相同类型的若干元素按有序的形式组织起来的一个形式。我们把相同类型元素的集合称之为数组。在C语言中,数组归属于构造数据类型。
按数组元素的类型不一样,数组又可分成数值数组、字符数组、指针数组、结构数组等各种类别。
数组的创建方式:
type_t arr_name [const_n]; //type_t 是指数组的元素类型 //arr_name 是数组的名字 //const_n 是一个常量表达式,用来指定数组的大小单单像这样讲略显枯燥,下面我们用具体的代码好好学一学:
int arr[5]; //创建一个 int 类型的数组,数组名叫arr,数组里面有五个元素 char ch[10]; //创建一个 char 类型的数组,数组名叫ch,数组里面有十个元素不过,我们要知道,创建数组的时候,[ ]里面的数字必须是常量。在C99标准之前是不支持使用变量的,只能是常量!在C99中增加了变长数组的概念,允许数组的大小是变量,而且要求编译器支持C99标准!但是我们常见的编译器对C99标准支持的不够好,比如说,VS系列编译器就是不咋的支持!
我用的就是VS2019,很明显它不是特别支持的。
对了,小声提一句,如果是变长数组的话就不可以初始化的;如上,用变量指定数组的大小,而变量是在代码运行起来的时候创建的,而n在初始化的时候,编译器不知道数组有多少个元素,所以初始化时没有意义的,这是语法限制死的。
我们在创建数组的同时给数组一些初始值叫初始化:
int arr1[10] = {1,2,3,4,5,6,7,8,9,10}; //完全初始化 int arr2[10] = {1,2,3}; //不完全初始化,剩余的元素默认为0 int arr3[10] = {0}; //不完全初始化,剩余的元素默认为0 int arr4[ ] = {1,2,3}; //数组会根据初始化的内容,默认[ ]的数字,如上默认为存放了3个元素上面数组可以在编译器监事窗口看到:
不过,这里我们需要注意一个小细节,就是在用字符串作为数组的初始值时,稍微注意一下:
如上所示,arr1有3个元素,数组的大小是3个字节,arr2有4个元素,数组的大小是4个字节。下面我们来测试一下:
注意:
1、strlen是一个库函数,计算的是字符串的长度,并且只能针对字符串;关注的字符串中是 否有\0,计算的是\0之前的字符个数。
2、sizeof是一个操作符(运算符),sizeof使用来计算变量所占空间的大小的,任何类型都 可以使用;只关注空间大小,不在乎内存中是否存在\0。
在使用printf打印字符以及strlen求字符串长度时候,遇到’\0’才停止,没遇到’\0’之前不停止。
对于数组的使用我们之前介绍了一个操作符:[ ] 下标引用操作符。它其实就数组访问的操作符。
#include <stdio.h> int main() { int arr[10] = { 0 }; int sz = sizeof(arr) / sizeof(arr[0]);//计算数组的元素个数 int i = 0;//数组是使用下标来访问的,下标从0开始。 for (i = 0; i < sz; ++i)//输入数组 { scanf("%d",&arr[i]); } for (i = 0; i < sz; ++i)//输出数组的内容 { printf("%d ", arr[i]);//arr[i]这个不是在创建数组,而是在访问数组的某一个元素 //访问的是下标,是可以用变量的 } return 0; }
要想知道一位数组是怎样在内存中存储的,我们可以先把它打印出来,然后再编译器里看:
为什么会相差4呢?——因为一个整型元素的大小是4个字节
总结:
1.一维数组在内存中是连续存放的。
2.随着数组下标的增长,地址是由低到高变化的。
数组地址连续存放有什么实际意义呢?
我们可以很容易推测,p+i是数组中arr[i]的地址;
下面我们可以验证一下:
理解了这个以后,我们就可以不用下标的形式访问数组内容了:
这个例子可以很好的说明,因为数组是连续存放的,通过数组首元素的地址往后找可以找到每一个数组对应的元素!
int arr1[3][4]; //3行4列的二维数组,里面的元素类型是 int类型 char arr2[3][4]; //3行4列的二维数组,里面的元素类型是 char类型 double arr3[4][5]; //4行5列的二维数组,里面的元素类型是 double类型有了前面的一维数组的知识,我们就可以更容易的理解二维数组的概念,请看下面的例子:
第一种方式:
int arr1[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };//完全初始化 int arr2[3][4] = { 1,2,3,4,5,6,7 };//不完全初始化 int arr3[3][4] = { {1,2},{3,4},{5,6} };//不完全初始化
第二种方式:
//初始化的时候括号里面的数字是否可以省略 //行可以省略,列不可以省略看,这个就是一种错误的初始化方式。虽然我们想像一位数组一样,通过数组的内容来判断数组里面有几个元素。因为我们不知道到底有多少行多少列。是1行12列,2行6列,3行4列,4行3列......当行和列都没有指定的时候,这种写法肯定是错误的。
注意:二维数组在创建的时候行可以省略,列不能省略(第一个[ ]中的值可以省略,第二个[ ]中的值必须要写出来)。
思考:那么为什么二维数组在创建的时候行可以省略,列不能省略呢?
在一维数组中,数组名[常量表达式]是它的定义方式;
而在二维数组中,可以看成是一维数组的数组,那么,arr[0]、arr[1]、arr[2]就可以看成是第一行、第二行、第三行,就是一个新的数组名;
所以说,arr[ ][4]是这个样子的:
知道了列数,就知道第二行该从哪里出发,第三行该从哪里出发。
但是如果只知道了行数,那是没有办法知道有多少列的。
实在不理解的话,可以想一想一维数组的用法,把arr[0]、arr[1]、arr[2]看成新的数组名,类似于一维数组,又加了一个[ ]的用法。
二维数组的使用方式也是通过下标的方式。二维数组的行和列下标都是从0开始的:
让我们看一看这个例子:
从上面可以看出,下标是[2][3]的元素是12,结果果然就是12。
打印二维数组的元素:
#include<stdio.h> int main() { int arr[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 }; int i = 0; for (i = 0; i < 3; i++) { int j = 0; for (j = 0; j < 4; j++) { printf("%d ", arr[i][j]); } printf("\n"); } return 0; }
为了研究二维数组的存储,这里我们可以尝试将二维数组的每个元素都打印出来:
我们可以看出,第一行的每个元素之间间隔了4个字节,而第一行最后一个元素到第二行第一个元素也是间隔了4个字节,后面的也是这样。
就这样,虽然在我们的感觉中二维数组是多行多列的,实际上,在内存中的存储方式是这样的:
二维数组可以看成是一维数组的数组 。
结论:二维数组在内存中也是连续存储的。
在内存中,我们只申请了 arr[0]到arr[9] 的空间,而当运行到i=10的时候,超出了申请的空间的边界,就叫做越界访问。
我们在写代码的时候,往往会将数组作为参数传给函数,比如:我们要实现一个冒泡排序函数将一个整型数组排序。那我们将这样使用函数:
冒泡排序的思想:两两相邻的元素进行比较,如果有可能的话需要交换。
一趟冒泡排序能搞定一个数字 让当前待排的数组中的一个元素来到最终应该出现的位置上 n个元素应该进行n-1趟的冒泡排序
下面先来展示一个错误的代码:
#include <stdio.h> void bubble_sort(int arr[])//可以用数组来接收 { //确定趟数 int sz = sizeof(arr) / sizeof(arr[0]); int i = 0; for (i = 0; i < sz - 1; i++) { //一趟冒泡排序的过程 int j = 0; //一趟冒泡排序比较的对数 for (j = 0; j <sz-1-i ; j++) //第一趟:10个数字待排序,9对比较 //第二趟:9个数字待排序,8对比较 //第三趟:8个数字待排序,7对比较 //…… //第九趟:2个数字待排序,1对比较 //sz-1是需要排序的趟数,sz-1-i是每一趟排序是两 //两相互比较的对数 { if (arr[j] > arr[j + 1]) { //交换 int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } } int main() { //数组传参 int arr[10] = { 1,4,6,3,2,5,8,9,7,0 }; //设计一个函数对arr数组进行排序——冒泡排序的算法 bubble_sort(arr); //数组在传参的时候传 数组名 就可以了 //传arr[]是错的,传arr[10]是错的 int i = 0; for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; }在vs2019上运行的结果是:
经过调试,发现错误的根源在这里:
sz=1 => sz-1=0 => for循环不执行。
本质上:
既然传过去的是首元素的地址,那么必定为指针接收,指针的大小sizeof(arr)在32位机器上是4个字节,sizeof(arr[0]),整形第一个元素的大小是4个字节,所以sz=1。
既然sz在里头不好算出来,那么我们可以在外头算一下,那么传过去,接收的还要加一个sz:
#include <stdio.h> void bubble_sort(int arr[],int sz ) { int i = 0; for (i = 0; i < sz - 1; i++) { int j = 0; for (j = 0; j <sz-1-i ; j++) { if (arr[j] > arr[j + 1]) { //交换 int tmp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = tmp; } } } } int main() { int arr[10] = { 1,4,6,3,2,5,8,9,7,0 }; int sz = sizeof(arr) / sizeof(arr[0]); bubble_sort(arr,sz); int i = 0; for (i = 0; i < 10; i++) { printf("%d ", arr[i]); } return 0; }此时运行的结果是:
当然,因为传的是首地址,所以也可以这样定义:
void bubble_sort(int* arr, int sz)
思考:数组名的本质是什么?
我们经常会说数组名是首元素地址,那么这个说法对不对呢?这里我们可以验证一下。
由此看来,数组名就是数组首元素的地址。
其实,这里还有两种特例:
1.sizeof(数组名)-- - 数组名表示整个数组-- - 计算的是整个数组的大小,单位是字节
如果这种情况下,数组名是首元素的地址,那么,在32位机器下,程序运行出来的结果应该就是4,可是打印出来的是20;
2.&数组名-- - 数组名表示整个数组-- - 取出的是整个数组的地址。(这种情况下,明显是成立的,地址是一串编号,总不能说编号的编号吧)除上述两种情况以外,其余情况数组名均表示首元素地址!
思考:数组地址和数组首元素地址有什么区别?
两者的地址值是一样的,但是含义和使用不同。
首元素的地址+1=>跳过一个元素(这个是整形,4个字节)
数组的地址+1 => 跳过一个数组(5个元素,每个大小是4个字节)
这篇博客总结的是关于数组的原理的知识点 ,后面会总结出关于数组的应用实例。
有啥不足的地方,欢迎提出来一起共同进步哦!
如果喜欢这篇博客的话,欢迎铁汁们动动你们的小手,一键三连哦!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。