目录
1.一维数组的创建和初始化
2.一维数组的使用
3.一维数组在内存中的存储
4.二维数组的创建和初始化
5.二维数组的使用
6.二维数组在内存中的存储
7.数组作为函数参数
1.一维数组的创建和初始化
数组是什么?
数组是一组相同类型元素的集合。
数组的创建:
type_t arr_name [const_n]
type_t — 数组的元素类型 ; arr_name — 数组的命名 ; const_n是一个常量表达式,用来表示数组元素的大小 ,如 1+1 ,3,2-1等等
//创建一个数组 -- 存放整型元素,元素个数为10int main(){int arr_1[10] ;//要注意数组的个数表示必须得是常量表达式,变量是不能放进去的!//如 int n = 5;int arr2[n];//上面这个数组就是错误的,因为它的括号里放的是变量而不是常量return 0;}
数组的初始化:
数组的初始化是指,在创建数组的同时给数组的内容设定一些合理的初始化值(初始化)
首先是非字符型的初始化,设我们规定数组中有n个元素
1.如果只给x(x<=n-1)个值设定初始化值,则被称为不完全初始化,不完全初始化中没被设定值的元素被默认初始化为 0
2.int arr[x] = { } (花括号中被括起来的就是被主动设定初始化值的元素,被初始化的元素的顺序是下标从0开始数,默认初始化为0的则不显示)
另一种是字符型的初始化,这一种的初始化则较为多样
1.常规的一个字符一个字符的初始化: char arr[n]={ ‘a’ , ‘b’ ,’c’ }
2.用字符串来初始化 : char arr[n] = “abc” 注意用字符串来初始化不要玩了字符串中的转义字符 \n 也会被放进数组中,它也是一个字符元素,是要被计数的!
3.用acii码值来初始化 char arr[n] = {98 , 97 , 99} 字符数值的ascii码值会被自动转译为字符,然后再给元素初始化
还有一种比较特殊的初始化,就是不单独设定数组中元素的个数,直接由我们输入的初始化元素的个数来决定数组中的元素个数。
如 int arr[] = {1,2,3,4} 这个数组就有4个元素,且按照顺序分别被初始化为了1,2,3,4
补充知识点 strlen 函数和 sizeof操作符 的区别
strlen 函数是用来计算字符串的长度的,strlen在遇到字符串中的转义字符 \n 时会停止计算,即 \n 不计入字符串长度,它只能用来求字符串的长度。另外strlen是库函数 – 使用得引头文件 strlen函数的用法 — strlen(字符串参数/字符数组) — 因此strlen是无法用来计算单个字符的长度的
strlen 函数返回的是一个无符号整型 (unsigned int),有符号signed
如:
#include #include //首先要引入 string 头文件int main(){char arr[]={'a','b','c'};strlen("abc"); // strlen()函数中的实参只能是字符串/字符数组(只用输入数组名即可)strlen(arr);//对于字符串而言strlen是从第一个字符开始到/n停止计数//对于字符数组而言则是从第一个元素开始到\n元素结束//如果字符数组中没有\n元素则strlen会在计算已有元素的长度后随机生成一个字符并继续计算,直到随机到\n为止printf("%zd",strlen("abc"));//strlen函数的输出类型声明是 %zdreturn 0;}
sizeof是用来计算数组,变量,类型所占空间的大小的,这个大小的单位是 字节。sizeof是操作符,不用引头文件。 sizeof操作符的使用 sizeof(要被求大小的数组,变量和类型)
(字符串存入数组中的时候,千万不要忘了转义字符 \n)
综上
字符数组的元素个数== strlen( arr ) 头文件 : string
数字类型数组的元素个数== sizeof(arr) / sizeof(arr [ 0 ] ) 总数组长度除以一个数组的长度
2.一维数组的使用
在讲数组的引用之前,需要先介绍一个操作符 –>[ ] ,这个操作符是下标引用操作符,我们的数组通过下标引用操作符来访问数组中的元素。
如果要访问数组中的第i个元素,则要在下标引用操作符中输入对应元素的下标,即 i-1
例子
int main(){char arr[] = "abcde" ; //注意存字符串时不要忘了那个终止字符 \n//访问第四个元素d,下标为 4 -1 = 3printf("%c",arr[3]);return 0;}
数组不能被直接打印,但数组中的元素可以通过下标引用操作符直接访问
所以当我们要将数组中的元素都打印出来时,我们可以采用循环或者迭代的方法,通过元素下标的变化来实现我们想要的功能。
3.一维数组在内存中的存储
数组在内存中是连续存放的
打印地址用的类型是 %p (p–>指针)
pritnf("%p",&a);
地址是由十六进制数组成的,十六进制数包括 0~9 A B C D E F
知道上面这些之后综合第一张图我们可以发现数组中的元素的地址都与其相邻元素的地址差k
且地址排序是 第n个元素的地址就等于第n-1元素的地址+k
这个k的值则由数组的类型确定,整型 int 开辟四个字节的空间,则k=4,其他的同理,看对应类型开辟多大的空间,k就取多大
4.二维数组的创建和初始化
创建
arr[ 3 ] [ 4 ] — 表示创建一个3行4列的数组,第一个括号是行数,第二个括号是列数
初始化
如上面的1,2,3,4,5个元素,我们进行不完全初始化,则二维数组会将已初始化的元素按顺序从左往右塞满一行,塞满一行后自动换行,然后初始化。其中未被我们主动初始化的元素被默认初始化为0
另外值得注意的一点是! 每一行元素的下标都是从0开始的,行与行之间没有继承关系
除了直接塞元素的初始化,我们也可以选择塞入一元数组来初始化,塞入的数组个数必须 <= 行的个数,数组中的元素必须<=列数
二元数组中塞入一元数组后,从左往右数,第一行属于第一个数组,第二行属于第二个数组,一元数组中的元素进到行后再按顺序排放,未被主动初始化的元素默认被初始化为0
元素型初始化和一元数组型初始化不能出现在同一个二元数组中
一元数组和二元数组的清零都是 ={0}
5.二维数组的使用
二维数组的使用依然是通过下标引用操作符来访问数组中的元素的
其中二维数组的行和列依然是从第零行和第零列开始的
输出二维数组的所有元素的方式两层循环的嵌套
6.二维数组在内存中的存储
二维数组中元素的地址 ,根据地址我们可以发现二维数组中内存的存放并不是我们想象中的矩阵类型存放,而是和一元数组一样的线性存放
二维数组的第 i 行看作一维数组时的数组名是arr[ i-1] ,为什么可以这样看呢,因为我们在访问第一行数组中的元素时,格式是 : arr[ 1-1= 0] [ j – 1 ] –> arr[ 0] [ j-1 ] j从1开始,而我们在一维数组中访问元素时则是 arr(数组名) [ n] ,两相对比我们就可以将 arr[i-1] 作为二维数组arr[ i ] [ j ]中的i行被看作一维数组时的数组名
关于二维数组的数组名以及二数组元素的引用的补充
在二维数组中 ,
其数组名arr指向的是第0行数组的地址
arr + i是第i行数组的地址
*arr指向的是第0行第0列元素的地址!!!
*(arr+i)是第i行第0列的地址
*(arr+i)+j — 第i行第j列元素的地址
二维数组的指针
7.数组作为函数参数
在我们写代码的时候,会将数组作为参数(数组可以作为参数)(实参)传给函数,比如我要实现一个冒泡排序函数将一个整型数组排序的时候,就会将数组作为参数传给冒泡排序函数。
#include void bubble_sort(int arr[]){}int main(){int arr[]={9,8,7,6,5,4,3,2,1};//对arr进行排序,排成升序bubble_sort(arr);// bubble 冒泡 sort 排序// 要将数组排好序,首先要把数组这个参数传过去,传过去后再创建一个与实参数组相同类型的空数组接收进行copyreturn 0;}
冒泡排序:
1.排升/降序时,第一趟冒泡排序必然会将最大/最小的值排到最右边,因为冒泡排序的移动逻辑是
从下标为0的元素与为1的元素比较,若0元素大于1元素二者交换位置,接着下标为1的元素继续比较,后面的依次类推
否则,则不交换位置,但1元素继续与二元素比较,后面的依次类推
元素的交换与否不影响下标的前进
在这样的逻辑思路下我们可以发现排升/降序时,第一趟冒泡排序必然会将最大/最小的值排到最右边,第二趟冒泡排序必然将最大/最小的值排到次右边,依次类推,冒泡排序最多进行n-1趟,最少进行0趟。
n个元素最对进行n*(n-1)/2次比较 ,等差数列求和 n个元素最多比较n-1次 , n-1 – -n-2 ,….
求和 0 +1 + 2+ 3+…. + n-1,总共有n项,等差数列求和公式:Sn=n*a1+n(n-1)d/2或Sn=n(a1(首项)+an(尾项))/2 。”,首项为0,n为项数
每一趟冒泡排序都能让一个元素回到对应的位置,当有n-1个元素回到正确的位置时,第n个元素只能呆在自己正确的位置(前提:每一个元素都有自己正确位置可以坐,但它们都做乱了),所以n个元素最多n-1趟冒泡
一趟一趟冒泡排序下来,泡会冒的越来越快
错误的冒泡排序程序实现如下:
这一个冒泡排序的实现从算法的角度来看是没有错的,但是从语言的角度来看则是完全错误的。
错误的根源就是对数组名最为函数参数传参时的错误理解。
首先
当我们输入这样一个语句,并让计算机执行时,会发生什么呢?
int arr[5]={0};
1.这样一个语句是在告诉计算机我们在创建一个拥有五个整型元素的数组(数组的类型声明与数组的元素一致,数组中只能有一种类型的元素);
2.计算机接收到我们的指令之后,会根据我们给出的元素个数内存区开辟5个整型(4个字节)的空间来存储数组中的元素,如果我们没有初始化元素的话,则默认存0;
3.开辟完一个包含5个相同的大小的内存空间的数组空间之后,计算机会返回第一个元素的地址并将其存在我们设定的数组名之中。
由3我们可以得出一个结论,那就是我们设定的数组名的本质其实是指针常量,即数组名 == &arr[0]
4.就这样我们得到了计算机返回的用来描述数组的其中一个重要参数 : 数组中的第一个元素的地址(这个第一个元素的地址被认为是对应数组的编码,通过查找这个编码我们就能找到对应的数组),有了这个再加上我们自己设定的元素个数和下标引用操作符,我们就能够去访问数组中的元素了。
(在内存空间中,用来存放元素的内存空间按照从左到右的顺序,以地址作为编号,以对应类型的空间大小地址递增排序,而在创建数组后得到的数组空间中,这些内存空间又被赋予了第二个编号 — 下标,这些内存空间按照上面的顺序从0开始编号递增1排列)
5.有了数组名,元素个数,下标引用操作符后我们就可以直接访问数组中的元素了
如arr[1] 即访问arr中下标为1的元素 ,访问的实现方式是
以arr这个指针常量中存的地址找到对应的数组空间,然后再在数组空间中找到对应下标的存储空间,然后输出存储在空间中的数据
第一个元素的地址:编码1 –> 找到对应的内存空间 ——> 给定的下标(编码2)——>找到对应的元素输出。
另外无论一个一元数组,它的下标都是从0开始,那从哪里结束呢?答案是到元素个数减1结束。
即 一个一元数组有n个元素,那么它的下标为 0 到(n-1)
综上:如果我们要找到一个数组中的所有元素,我们需要知道两个参数
一个是第一个元素的地址,该地址被存放在指针常量 数组名中 — 编码1–找到数组空间
一个是数组中的元素个数,通过元素个数和下标的关系得到下标范围–此时下标确定–编码2–可找到所有元素,并通过下标引用操作符输出
当我们说把数组作为参数传给函数时,本质上我们是想把数组中的元素全都传给函数,并让函数可以访问,而要实现这个目的并不需要在调用函数时重新再开辟一个数组空间,然后copy所有元素,然后再把所有元素放进数组中,然后让函数进行访问,我们需要做的只是将数组名和元素个数作为参数传给函数即可
这样,函数就可以通过数组名得到编码1直接访问已有数组空间,然后通过元素个数得到下标–代码2直接访问想要的元素,而不要费劲的进行重新copy这样费时费空间的做法
格式如下
void stt(int(与传过来的数组的类型一致) arr[元素个数](下标引用操作符及其中的内容与数组相同))//我们将装有数组第一个元素地址的地址名传过来了,我们将元素个数传过来了//此时相当于编码1和编码2都以到位,函数可以直接通过编码1和编码2访问和操作对应的数组{}// 二维数组在传给函数元素的个数时并并不像一维数组一样只要1个参数就可以实现//二维数组需要的是数组的行数和列数,这两个参数一乘就可以表示数组中元素的个数了所以有void add(int arr[a][b])二维数组 两个参数表示元素的个数 函数的实现add(int arr[a]) 一维数组,一个参数表示元素的个数数组作为实参时只要数组名即可add(arr) 函数的使用
就这样通过这种方式我们便可以在函数中直接访问和操作函数外对应数组中的元素
补充知识点:
1.函数元素的个数的计算要提前算好才好传参,计算方法是 sizeof(arr)/sizeof(arr[0])
(sizeof是用来计算类型,变量,数组等所占空间的大小的,单位是字节,其中一个字符占一个字节)
欸看完上面那些后你就会发现问题出现了
如果arr是一个指针常量(这个指针常量的类型为元素的类型+*)的话,它的大小应该是4个字节(32位)(假设为整型),而一个整型元素所占的空间是4个字节,通过上面那个sizeof来求的话我们只能得到1,应该是得不到元素个数的啊!
(补充一个知识点,指针+1 时对应的是指针中的地址 + 1*对应指针类型的字节大小,如32位下的整型指针+1的话,它里面的十六位进制的地址+4)
指针的大小,各类型的大小
关于这个问题的解释就涉及到两个特例
1.sizeof中单独放一个数组名的时候,我们认为sizeof计算的整个数组的大小,单位是字节
2. &数组名的时候,取出的是整个数组空间的地址(编码1),但在输出时仍输出第一个元素的地址,不过意思已经完全不同了,即已经变为了数组空间的地址(编码1),此时&数组名+1,表示的是直接跳过整个数组名对应的数组空间后,输出紧邻数组空间的第一个内存空间的地址。
除了以上两种情况以外,数组名都被看作是存放第一个元素的地址的指针常量
综上我们就可以得到正确的冒泡排序实现
注意在冒泡排序时可以设置一个判断值,排第一次后,如果发生了交换,则判断值为0,继续进行下一趟冒泡,重复判断,如果不发生交换,则判断值为1,冒泡结束,已排完,这样子可以提升冒泡效率。
动态数组的创建
以整型一维数组为例
n是我们想要的元素个数
int* p; 创建一个整型指针 — 数组名 — 这一步还没有初始化 — 初始化由下面那一行代码实现
p = (int* 指针类型)malloc( n * sizeof(int) ) 从堆中动态分配一组连续空间用来建立数组,同时返回该连续内存空间中的第一个内存空间的地址并用来初始化p — 用来组建数组空间的大小 == n*(sizeof(int)) — sizeof(int)是我们想建立的数组中单个元素所占的空间大小 — n为元素个数 —
#include — 不要忘了这个头文件
//一些零散的笔记
符合运算符
a += n –> a = a+n ; 减同理
a %= n –> a = a%n 乘除同理
if else 的简写判断式子
a = (a>10? a : 0)
翻译: a是否大于10,是的化输出a ,否则输出0