数组
数组的概述
在程序设计中,为了方便处理数据把具有相同类型的若干变量按有序形式组织起来——称为数组
数组就是在内存中连续的相同变量类型的变量空间,同一个数组中所有的元素(即变量)都是相同的数据类型,同时所有元素在内存中的地址是连续的。
数组属于构造数据类型,一个数组可以分解为多个数组元素,这些元素可以是基本数据类型,也可以是构造数据类型(由多个基本类型构造成的类型,例如结构体,数组)。
数组的分类
- 按照数组元素类型的不同:数组可以分为字符数组(char chs[10])、数值数组(int numbers[10])、指针数组(char *p[10])、结构数组等类别,本章节讨论字符数组和数值数组。
- 按照数组元素的维度不同:数组元素下标的个数([])也称为维数,根据维数不同可以将数组分为一维数组([]),二维数组([][]),三维数组([][][])等等,二维及其以上的都称为多维数组。
数组的定义
数组的定义和变量的定义类似:数据类型 数组名[] ;例如 in numbers[10];表示定义一个存储10个整数元素的数组。
- 变量名加[]代表这是个数组,变量名需要遵守命名规则和命名规范,数组的元素长度由[]的数值决定,定义数组时该数值只能是常量,不能是变量或者表达式,但是使用数组时[]的数值可以是常量或者变量。
- 数组元素的类型由定义数组时指定的数据类型决定。
- 如果数组定义时没有被初始化,在MSVC编译器下会给数组赋随机值
- 数组的每个元素都是变量,变量都是可以读写,即获取值和被赋值
- 数组通过数组名[下标]来访问元素,下标是从0开始,到数组的长度减1
- 定义数组时必须明确数组的长度,否则程序会编译错误
#define _CRT_SECURE_NO_WARNINGS#include #include /*数组的定义@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){// 数组的定义// 数据类型 变量名[];//变量名结合[]代表一个数组类型// 数组名的命名规范和变量的命名规范一样// 数组元素的类型由定义时的数据类型决定//数组中元素的个数由[]里面定义的数值决定//定义数组时,[]里面的值不能是变量,只能是常量//使用数组时[]里面的值可以是变量,也可以是常量//定义保存10个整数元素的数组int numbers[10];//打印数组的初始化值// 如果数组定义时没有被初始化,在MSVC编译器下会给数组赋随机值for (int i = 0; i < 10; i++) {printf("%d ", numbers[i]);}printf("");//数组的每个元素都是变量,可以被赋值//通过数组名[下标]来访问数组的元素,其中下标是从0开始,到数组的长度减1//给数组的第一个元素赋值为100numbers[0] = 100;printf("numbers数组元素的第一个值是%d",numbers[0]);system("pause");return 0;}
数组的初始化
在使用数组前,需要定义和初始化数组,否则编译器会给数组分配一个随机值
数组的初始化和变量一样,就是在定义数组时就给数组的元素赋值,例如int numbers[10] = {1,2,3,4,5,6,7,8,9,10};
数组在初始化时还可以给部分元素初始化赋值,例如int data[10] = {0};表示将第一个元素的初始值设置为0,此时其他元素的初始值也是0。
初始化数组时如果不指定数组的长度,即[]中没有值,此时数组的元素个数由{}的元素个数决定,例如 int values[] = {1,2,3,4,5,6,7,8};,此时编译器会自动计算数组的长度为8。
除此以外还可以初始化指定位置的元素,例如 int score[10] = {[5]=80};表示初始化第6个元素的值为80,其他元素的初始化赋值为0,不过此方式比较少用。
#define _CRT_SECURE_NO_WARNINGS#include #include /*数组的初始化@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){//数组的初始化就是给数组的元素赋值int numbers[10] = {1,2,3,4,5,6,7,8,9,10};//for循环遍历数组的元素for (int i = 0; i < 10;i++) {printf("%d ",numbers[i]);}printf("楚河******汉界*****");//部分初始化//针对整数类型的数组,如果数组只初始化部分元素,其他元素初始化为0//将data数组的元素都初始化为0int data[10] = {0};//for循环遍历数组的元素for (int i = 0; i < 10; i++) {printf("%d ", data[i]);}printf("楚河******汉界*****");//另外一种初始化的方式//如果定义数组时,[]中没有值,这个数组的元素个数由{}里面的元素个数决定int values[] = {1,2,3,4,5,6,7,8};printf("楚河******汉界*****");for (int i = 0; i < 8; i++) {printf("%d ", values[i]);}printf("楚河******汉界*****");//初始化指定位置的元素 第六个元素的值为80int score[10] = {[5]=80};for (int i = 0; i < 10;i++) {printf("%d ", score[i]);}printf("楚河******汉界*****");system("pause");return 0;}
数组的大小
数组的大小可以通过数组的元素个数乘以数组的数据类型占据的字节数量计算得来,例如int numbers[10]占据的内存大小就是40个字节,我们可以通过sizeof(numbers);或者sizeof(int[10]);两种方式来获得数组的大小。获取数组的大小以后就可以很轻松的获取数组的长度(即数组元素的个数)。通过数组的大小除以数组的元素大小得到的就是数组元素个数,即sizeof(numbers)/sizeof(numbers[0])。那样在遍历数组时,循环的判断条件不需要写常量值(例如10),而应该是通过计算数组元素的个数得来的变量值,这样程序会更加灵活。
#define _CRT_SECURE_NO_WARNINGS#include #include /*数组的大小@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){//初始化存放10个整数元素的数组int numbers[] = {1,2,3,4,5,6,7,8,9,10};//numbers是数组名,代表的就是数组,而sizeof(numbers)测试的就是数组的内存大小printf("整数数组占据的内存空间大小是%d字节",sizeof(numbers));printf("整数数组占据的内存空间大小是%d字节",sizeof(int[10]));//根据数组的大小求数组的元素数量//数组的元素数量=数组的大小/每个数组元素的大小int size = sizeof(numbers) / sizeof(numbers[0]);for (int i = 0; i < size;i++) {printf("%d ",numbers[i]);}printf("");system("pause");return 0;}
程序的运行结果
![e16e5dedf9c91eb1a25d52304239777d.png](https://i-blog.csdnimg.cn/blog_migrate/df3626f66f6c517caf5a4d17276ad10a.jpeg)
数组的内存结构
程序运行时,系统会分配一块内存空间,而内存的计量单位是字节(Byte),内存的每个字节都会有一个唯一的内存地址(俗称编号,类似于酒店的房间号),32位系统能寻址的范围采用十六进制表示方法是0x00000000到0xffffffff。
了解数组在内存中的存储之前,先了解char和int类型的变量在内存中是如何存储的。
char ch='a',char占据一个字节,假设地址是0xffff。
而定义整数变量 int number=10; number占据四个字节,由于每个字节都有编号,假设是0x1110,0x1111,0x1112,0x1113 ,而获取获取的变量地址就是起始地址0x1110。
数据在内存中的地址就是内存的首地址(也叫起始地址)
由于数组是一块连续的内存空间
定义存储5个整数元素的数组时 int number[5]={1,2,3,4,5}; ,numbers[0],numbers[1],numbers[2],numbers[3],numbers[4]都是int类型的元素,因此它们都是占据4个字节的内存,总内存是20个字节。
假设numbers[0]的地址是0x1110 0x1111 0x1112 0x1113
numbers[1]的地址是0x1114 0x1115 0x1116 0x1117
numbers[2]的地址是0x1118 0x1119 0x1120 0x1121
numbers[3]的地址是0x1122 0x1123 0x1124 0x1125
numbers[4]的地址是0x1126 0x1127 0x1128 0x1129
&numbers[0] 表示第1个元素的地址是0x1110,numbers代表数组,也代表数组中第1个元素的地址(即首元素的地址),也就是numbers==&numbers[0]0x1110,因此数组名numbers是一个常量(即地址值),常量是不能被赋值的。
&numbers 表示整个数组 的地址,&numbersnumbers==&numbers[0]==0x1110,但是它们在运算时是不相同的。
&numbers[0]+1 表示跨过一个数组元素,即地址加4,因此&numbers[0]+1的地址是0x1114,&numbers[0]+1比&numbers[0]大4
numbers+1 表示跨过一个数组元素加1,即地址加4,因此numbers+1的地址是0x1114,numbers+1比numbers大4
&numbers+1 表示整个数组的地址加1,跨过整个数组,因此&numbers+1的地址是0x1130,&numbers+1比&numbers大20
#define _CRT_SECURE_NO_WARNINGS#include #include /*数组的内存结构程序运行时,系统会给程序分配一块内存空间内存的最小单位是字节内存中的每一块字节都有编号,这个编号叫做内存地址,内存地址都是使用十六进制表示的32位系统的内存地址编号是0x0000 0000 到0xffffffff数据在内存中的地址就是它在内存中的起始地址 ,例如 int number=10; number占据四个字节,每个字节都有编号,假设是0x1110,0x1111,0x1112,0x1113 获取的变量地址就是起始地址0x1110而数组 int numbers[5] 在内存中开辟连续的20个字节空间,每个数组元素占据4个字节numbers[0]的地址是0x1110 0x1111 0x1112 0x1113numbers[1]的地址是0x1114 0x1115 0x1116 0x1117numbers[2]的地址是0x1118 0x1119 0x1120 0x1121numbers[3]的地址是0x1122 0x1123 0x1124 0x1125numbers[4]的地址是0x1126 0x1127 0x1128 0x1129&表示地址符,用于获取变量的地址&numbers[0]数组名 numbers 代表数组,也代表第0个元素的地址(首元素的地址) 等价于&numbers和&numbers[0],内存地址就是一个整数编号数组名是一个地址常量,不能赋值&numbers 就是整个数组的地址,也是numbers数组第一个元素的地址 即 &numbers=&numbers[0]&numbers[0]+1 元素的地址+1 跨过一个元素,因为1表示int,占据4个字节,因此此时为&number[5]&numbers+1 整个地址的元素加1 ,跨过整个数组 因此此时 &numbers[21]@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){int numbers[5] = {1,2,3,4,5};//使用无符号的十进制的方式输出数组的地址和数组第一个元素的地址是等价的//因此获取数组的地址实际上就是获取数组第一个元素的地址// 而numbers=&numbers=&numbers[0]printf("获取numbers数组的第一个元素的地址&numbers[0]是%u",&numbers[0]);printf("获取numbers数组的地址&numbers是%u",&numbers);//数组名代表内存中数组的第一个元素的地址printf("获取numbers数组的地址numbers是%u",numbers);//地址的对比//元素加1跨过一个元素,这里每个元素占据四个字节,因此&numbers[0]+1比&numbers[0]大4printf("获取numbers数组的第一个元素的地址&numbers[0]+1是%u", &numbers[0]+1);//数组地址加1,是整个地址加1,因为整块地址是20个字节,因此&numbers+1比&numbers大20printf("获取numbers数组的地址&numbers+1是%u", &numbers+1);//数组名表示数组的第一个元素地址,加1跨过一个元素,这里每个元素占据四个字节,因此 numbers+1比numbers大4printf("获取numbers数组的地址numbers+1是%u", numbers+1);system("pause");return 0;}
程序运行结果
![f1ee5bd9ef100bcd36c1c32261417059.png](https://i-blog.csdnimg.cn/blog_migrate/0d0d5a9fb364e5623174779b7467d4b9.jpeg)
数组的应用
数组的应用1:获取数组中最大的元素
#define _CRT_SECURE_NO_WARNINGS#include #include /*数组的应用1:获取数组中最大的元素@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){//初始化一个整数数组int numbers[] = {1,2,3,4,5,6,-1,-2,-3,-4,7,8,9,100,20,30};//获取数组的长度int size = sizeof(numbers) / sizeof(int);printf("遍历初始化的整型数组");for (int i = 0; i < size;i++) {printf("%d ",numbers[i]);}//换行printf("");printf("获取数组元素中的最大值");//假设第一个元素为最大值int max = numbers[0];//从第二个元素开始比较for (int i = 1; i < size;i++) {//循环比较数组的每个元素,如果元素的值比max大,那么就把值赋值给maxif (numbers[i]>max) {max = numbers[i];}}printf("数组元素的最大值是%d",max);system("pause");return 0;}
程序运行结果
![52009cf704a9c1065aa728d6797d0324.png](https://i-blog.csdnimg.cn/blog_migrate/5750a5df5358495a9deb31d34fe71776.jpeg)
数组的应用2:数组元素反转
#define _CRT_SECURE_NO_WARNINGS#include #include /*数组的应用2:数组元素反转@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){int numbers[10] = {1,2,3,4,5,6,7,8,9,10};//获取素组的元素数量int size = sizeof(numbers) / sizeof(numbers[0]);printf("数组元素反转之前的元素列表");for (int i = 0; i < size;i++) {printf("%d ",numbers[i]);}printf("");printf("数组元素反转之后的元素列表");int start = 0;int end = size-1;int tmp = 0;//循环首尾交换元素while (start
程序运行结果
![cbd26ac47605454436fbf2551b93fe77.png](https://i-blog.csdnimg.cn/blog_migrate/899ccd940d55ddada4d5156b64f7c28d.jpeg)
数组的应用3:冒泡排序的实现
数组的应用3:冒泡排序从大到小降序的实现
#define _CRT_SECURE_NO_WARNINGS#include #include /*数组的应用3:冒泡排序从大到小降序的实现@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){//初始化一个乱序的整数数组int numbers[5] = { 4,1,5,3,2 };int size = sizeof(numbers) / sizeof(numbers[0]);printf("冒泡排序从大到小降序之前数组元素列表");for (int i = 0; i < size;i++) {printf("%d ",numbers[i]);}printf(""); // 假设按照从大到小排序//冒泡排序的原理: 相邻两个元素的比较,前面比后面的大,两个元素交换位置/*冒泡排序的过程: 未排序之前数组的元素列表41532 第一轮相邻两个元素比较(比较四次)1 4 3 2 5 第二轮相邻两个元素比较(比较三次)1 3 2 4 5 第三轮比较两个相邻的元素(比较两次)1 2 3 4 5 第四轮比较两个相邻的元素(比较一次) 1 2 3 4 5冒泡排序 一共有n个元素,那么需要比较n-1轮 每比较一轮,下一轮就少比较一次*/int count = size - 1;//数组有5个元素,因此外层4轮for (int i = 0; i < count;i++) {//内层循环比较的次数 每一轮比较的次数为 size-1-ifor (int j = 0; j < size - 1 - i;j++) {//如果是从大到小排序if (numbers[j]>numbers[j+1]) {//比较两个相邻的元素,然后交换位置numbers[j] = numbers[j] ^ numbers[j + 1];numbers[j + 1] = numbers[j] ^ numbers[j + 1];numbers[j] = numbers[j] ^ numbers[j + 1];}}printf("外层循环第%d次排序结果如下",(i+1));for (int i = 0; i < size; i++) {printf("%d ", numbers[i]);}printf("");}printf("冒泡排序从大到小降序之后数组元素列表");for (int i = 0; i < size; i++) {printf("%d ", numbers[i]);}printf("");system("pause");return 0;}
程序运行结果
![c0f11928df973b87e7bcb01897acfd26.png](https://i-blog.csdnimg.cn/blog_migrate/19c1b0a6a562038475968c5cf3487663.jpeg)
数组的应用3:冒泡排序从小到大升序的实现
#define _CRT_SECURE_NO_WARNINGS#include #include /*数组的应用3:冒泡排序从小到大升序的实现@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){int data[10] = {1,2,4,5,7,8,9,6,3};int size = sizeof(data)/sizeof(data[0]);printf("冒泡排序从小到大升序排序之前数组元素列表");for (int i = 0; i < size;i++) {printf("%d ",data[i]);}printf("");int count = size-1;//总共比较9轮for (int i = 0; i < count;i++) {//每轮比较9-1-i次for (int j = 0; j < size - 1 - i;j++) {//相邻元素比较谁小if (data[j]
程序运行结果
![d18432e9089fc888d49551476f344f61.png](https://i-blog.csdnimg.cn/blog_migrate/a06f0a5ba928c03f408cf88e04902ec2.jpeg)
二维数组的定义
之前讨论数组的定义、初始化、大小、内存结构、应用都是针对一维数组的。
数组的维数使用[]来区分,一个[]表示一维数组依此类推,[][]表示二维数组,日常应用中使用最多的是一维数组和二维数组。
二维数组类似于excel表格
二维数组定义的语法格式为 数据类型 数组名[行数][列数];,其中第一个[]表示数组的行数,第二个[]表示数组的列数 ,行数和列数的值也只能使用常量,例如int numbers[2][3]表示一个2行3列的二维数组。
其中numbers[0]表示第1行,而numbers[0][0]表示第一行的第一列的元素。
二维数组元素的访问通过数组名[行数][列数]来实现,而遍历二维数组时需要使用嵌套for循环实现。
#define _CRT_SECURE_NO_WARNINGS#include #include /*二维数组的定义@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){/*二维数组类似于excel表格二维数组的定义:数据类型 数组名[行][列]二维数组的numbers[0]表示第1个一维数组,即第一行numbers[0][0] 表示第0个一维数组的第1个元素,依此类推*///定义一个两行三列的二维数组// 二维数组的每个元素也是变量int numbers[2][3];//遍历二维数组使用嵌套for循环for (int i = 0; i < 2;i++) {for (int j = 0; j < 3;j++) {printf("%d ",numbers[i][j]);}printf("");}//给数组的第一行的第三列赋值为10//给数组的第二行的第一列赋值为20numbers[0][2] = 10;numbers[1][0] = 20;//赋值之后再次遍历二维数组printf("赋值之后再次遍历二维数组");for (int i = 0; i < 2; i++) {for (int j = 0; j < 3; j++) {printf("%d ", numbers[i][j]);}printf("");}system("pause");return 0;}
程序运行结果
![ab36bb845f685c56c32a25103ab3c5e0.png](https://i-blog.csdnimg.cn/blog_migrate/98ecb62ecbbb1816a2fa161eabaaca43.jpeg)
二维数组的大小可以通过数组的元素个数乘以数组的数据类型占据的字节数量计算得来,例如int= numbers[2][3]占据的内存大小就是24个字节,我们可以通过sizeof(numbers);或者sizeof(int[10]);两种方式来获得数组的大小。
#define _CRT_SECURE_NO_WARNINGS#include #include /*通过sizeof获取二维数组的大小@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){int numbers[2][3];printf("两行三列的int类型二维整数数组的内存空间大小是%d字节", sizeof(numbers));printf("两行三列的int类型二维整数数组的内存空间大小是%d字节", sizeof(int[2][3]));system("pause");return 0;}
二维数组的初始化
二维数组的初始化就是在定义二维数组时给数组的元素赋值,例如int numbers[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };就是定义一个3行4列的二维整数数组,并给数组中的每个元素赋值。
因为二维数组在内存中的结构实际上和一维数组的内存结构是一样的,也是一块连续的内存空间,因此可以这样赋值int data[3][4] = {1,2,3,4,5,6,7,8};,这样最后一行的元素初始化值都是0。
一维数组在初始化时可以省略[长度]的常量值,二维数组在初始化时可以省略[行数]的值,但是[列数]不能省略,行可以自动计算出来,因此二维数组还可以这样初始化int values[][4] = {1,2,3,4,5,6,7};,values的行数2行。因为二维数组values有7个元素,每行有4列,那么行数就是2
#define _CRT_SECURE_NO_WARNINGS#include #include /*几种二维数组的初始化方式@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){//二维数组的初始化赋值int numbers[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };printf("按照无符号十进制整数打印numbers二维数组元素的内存地址");//遍历二维数组元素的内存地址for (int i = 0; i < 3;i++) {for (int j = 0; j < 4;j++) {//按照无符号十进制整数打印numbers二维数组元素的内存地址printf("%u ",&numbers[i][j]);}printf("");}//二维数组的遍历printf("嵌套for循环遍历numbers二维数组的元素");for (int i = 0; i < 3;i++) {for (int j = 0; j < 4;j++) {printf("%d ",numbers[i][i]);}printf("");}printf("");printf("二维数组的另外一种初始化方式");//二维数组的另外一种初始化方式//因为二维数组本来就是连续的内存空间,因此最后一行初始化为0int data[3][4] = {1,2,3,4,5,6,7,8};for (int i = 0; i < 3; i++) {for (int j = 0; j < 4; j++) {printf("%d ", data[i][i]);}printf("");}//二维数组定义时不能省略列的下标,行可以自动计算出来//values有7元素,但是有4列,因此行数为2int values[][4] = {1,2,3,4,5,6,7};system("pause");return 0;}
程序运行结果
![f4d85bd333a6b13ece87ffa2126773d6.png](https://i-blog.csdnimg.cn/blog_migrate/af941316d42d2dbd32e377a640d42aa0.jpeg)
获取二维数组的行和列
在使用嵌套for循环遍历二维数组时,二维数组的行数和列数都写的是常量值,当手动改变二维数组的行数和列数时,也需要去修改嵌套for循环的循环条件的值,因此这里通过计算二维数组的行数和列数,能够让使用嵌套for循环来遍历二维数组变得更加灵活。
假设声明并初始化二维数组 int values[][4] = { 1,2,3,4,5,6,7 };
数组元素的总大小: sizeof(values);
每行的大小 :sizeof(values[0])
每列的大小:sizeof(values[0][0])
二维数组的行数: 总大小/每行的大小 也就是sizeof(values)/sizeof(values[0])
二维数组的列数: 每行的大小除以每列的大小 也就是sizeof(values[0])/sizeof(values[0][0])
#define _CRT_SECURE_NO_WARNINGS#include #include /*二维数组的行和列计算@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){int values[][4] = { 1,2,3,4,5,6,7 };//获取二维数组的行和列// 总大小/单个元素的大小=元素的总数量int size = sizeof(values) / sizeof(sizeof(values[0][0]));//行=总大小/每一行的大小int rows = sizeof(values) / sizeof(values[0]);//列=一行的大小/每列的大小int cols = sizeof(values[0]) / sizeof(values[0][0]);printf("二维数组values的元素个数是%d",size);printf("二维数组values的总大小是%d",sizeof(values));printf("二维数组values每行的大小是%d",sizeof(values[0]));printf("二维数组values的每列大小是%d",sizeof(values[0][0]));printf("二维数组values的行数是%d", rows);printf("二维数组values的每列大小是%d",cols);//使用计算出来的行数和列数遍历二维数组printf("使用计算出来的行数和列数遍历二维数组values的元素列表");for (int i = 0; i < rows;i++ ) {for (int j = 0; j < cols;j++) {printf("%d ",values[i][j]);}printf("");}system("pause");return 0;}
程序运行结果
![1a3901f651ba98ee60edc43bf1fc14b6.png](https://i-blog.csdnimg.cn/blog_migrate/10fc44d29dbed6125028c750e37b837f.jpeg)
二维数组的内存结构
声明一个整数类型的3行3列的二维数组
int numbers[][3] = {1,2,3,4,5,6,7,8,9};
然后获取行和列
int rows = sizeof(numbers) / sizeof(numbers[0]);int cols = sizeof(numbers[0]) / sizeof(numbers[0][0]);
通过循环遍历获取二维数组元素的内存地址
printf("按照无符号十进制整数打印numbers二维数组元素的内存地址");for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {//按照无符号十进制整数打印numbers二维数组元素的内存地址(即首地址)printf("%u ", &numbers[i][j]);}printf("");}
因为系统是随机分配内存的,因此每次打印的内存地址可能不一致。
假设二维数组的九个元素的地址值依次是
20183764 20183768 2018377220183776 20183780 2018378420183788 20183792 20183796
从程序的输出结果看得出来,在内存的层面,没有真正的二维数组。因为二维数组的内存结构跟一维数组的是一样的,都是一块连续的内存空间,存储相同类型的集合
- numbers[0][0] 表示numbers二维数组的第一行第一列的元素1
- &numbers[0][0]表示numbers二维数组的第一行第一列元素的首地址20183772
- numbers[0]表示numbers二维数组的第一行第一列的首地址20183764
- &numbers[0]表示第一行元素的首地址20183764
- numbers表示二维数组numbers的数组名,也表示第一行元素的首地址20183764
- &numbers表示二维数组numbers的首地址20183764
因此 &numbers[0][0]和numbers[0],&numbers[0],numbers,&numbers的地址是等价的
但是他们运算时还是有区别的
- &numbers[0][0]+1 表示跨过一列,因此&numbers[0][0]+1比&numbers[0][0]的值大4,因为一列占据4个字节
- &numbers[0]+1表示跨过一行,而这里的每行有3个列,每列有4个字节,因此&numbers[0]+1比&numbers[0]的值大12
- &numbers+1表示跨过整个数组,而这里的数组有3行3列,每列有4个字节,因此&numbers+1比&numbers的值大36
- numbers+1 表示跨过一行,而这里的每行有3个列,每列有4个字节,因此numbers+1比numbers的值大12
#define _CRT_SECURE_NO_WARNINGS#include #include /*二维数组的内存结构@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){//定义整数类型的三行三列的二维数组int numbers[][3] = {1,2,3,4,5,6,7,8,9};printf("按照无符号十进制整数打印numbers二维数组元素的内存地址");//遍历二维数组元素的内存地址int rows = sizeof(numbers) / sizeof(numbers[0]);int cols = sizeof(numbers[0]) / sizeof(numbers[0][0]);for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {//按照无符号十进制整数打印numbers二维数组元素的内存地址(即首地址)printf("%u ", &numbers[i][j]);}printf("");}/*程序运行结果 因为内存是系统随机分配的,每次运行的结果可能不相同20183764 20183768 2018377220183776 20183780 2018378420183788 20183792 20183796*///从程序运行的结果看出,在内存层面看,二维数组的内存结构和一维数组的内存结构一样,没有真正意义上的二维数组,/* numbers[0][0] 表示第一行第一列的元素 &numbers[0][0] 表示第一行第一列元素的首地址,即20183764 numbers[0] 表示第一行的数组名,也表示第一行第一列元素的首地址 即20183764 &numbers[0] 表示第一行的元素的首地址 即20183764 numbers表示二维数组的数组名,也表示第一行元素的首地址 20183764 numbers= &numbers[0] &numbers 表示二维数组numbers的首地址 20183764 &numbers[0][0]+1 元素地址值加1 numbers[0]+1 元素地址值加1 &numbers[0]+1 行地址加1,跨过一行 numbers+1 行地址加1,跨过一行 &numbers+1 二维数组地址加1,跨过整个二维数组*/printf("二维数组的内存结构");//&numbers[0][0] 表示获取第一行第一列元素的地址printf("&numbers[0][0]=%u",&numbers[0][0]);//numbers[0][0]表示获取数组第一行第一列的值printf("numbers[0][0]=%u",numbers[0][0]);//&numbers[0]表示获取数组第一行的首地址printf("&numbers[0]=%u", &numbers[0]);//numbers[0]表示获取数组第一行的首地址printf("number[0]=%u", numbers[0]);//&numbers表示获取数组的地址printf("&numbers=%u", &numbers);//numbers表示数组名,也表示数组第一行的首地址printf("number=%u", numbers);// &numbers[0][0]+1比 &numbers[0][0] 大4,因为&numbers[0][0]+1表示跨了一个元素,而一个元素是四个字节printf("&numbers[0][0] +1 =%u", &numbers[0][0]+1);// &numbers[0] +1 比&numbers[0] 大12,因为&numbers[0] +1表示跨了一行,一行有3个元素,每个元素是4个字节printf("&numbers[0] +1 =%u", &numbers[0]+1);//&numbers + 1比&numbers大36,因为&numbers + 1表示跨了整个数组,3行3列,每个元素四个字节printf("&numbers + 1 =%u", &numbers+1);//numbers + 1比numbers大12,因为numbers +1表示跨了一行,一行有3个元素,每个元素是4个字节printf("number +1 =%u", numbers+1);system("pause");return 0;}
程序运行结果
![e5758bfed9494fdef1a8aa7d6116dc0a.png](https://i-blog.csdnimg.cn/blog_migrate/e87b21943a5777786379c957178c3322.jpeg)
二维数组的应用
首先假设有一个二维数组存储了5个学生的三门成绩, double scores[5][3] = { {80,75,56},{59,65,71},{59,63,70},{85,45,90},{76,77,45} };,
然后分别求出各个学科每个学生的平均分,以及统计不及格的人数。
#define _CRT_SECURE_NO_WARNINGS#include #include /*二维数组案例:求各个学科的学生平均分以及统计各个学科不及格的人数@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){// 存储5个学生的三门(语文、数学、英语)成绩double scores[5][3] = { {80,75,56},{59,65,71},{59,63,70},{85,45,90},{76,77,45} };/*语文数学英语807556596571596370854590767745*/int subjects = sizeof(scores[0]) / sizeof(scores[0][0]);int numbers = sizeof(scores) / sizeof(scores[0]);//每科的平均分double subjects_avgs[3] = {0.0};//每科不及格的人数int subjects_flunk_count[3] = {0};double sum = 0.0;//首先遍历三门学科for (int i = 0; i < subjects;i++) {//遍历每门学科之前先清理之前的和sum = 0.0;//然后遍历5个学生for (int j = 0; j < numbers;j++) {//累加每个学生的每个科目成绩sum += scores[j][i];if (scores[j][i]<60) {subjects_flunk_count[i]++;}}//计算每个学科的平均分subjects_avgs[i] = sum / numbers;}printf("语数外三门科目的平均分分别是");for (int i = 0; i < subjects;i++) {printf("%.1lf ",subjects_avgs[i]);}printf("");printf("语数外三门科目不及格的数量分别是");int subjects_flunk_count_size = sizeof(subjects_flunk_count) / sizeof(subjects_flunk_count[0]);for (int i = 0; i < subjects_flunk_count_size;i++) {printf("%d ", subjects_flunk_count[i]);}printf("");system("pause");return 0;}
程序运行结果
![fd0c9dc5eb94022a5aae01a6f210c0c6.png](https://i-blog.csdnimg.cn/blog_migrate/deb71db1a8a0f84637f411483ec3d58a.jpeg)
三维数组
在有些游戏场景中会使用到三维数组,三维数组的声明语法格式为 数据类型 数组名[][][]; 例如 int data[2][3][4] = { { {1,2,3,4},{5,6,7,8},{9,10,11,12} },{ {13,14,15,16},{17,18,19,20},{21,22,23,24} } };;,
表示声明了一个三维数组,其中包含2个二维数组,每个二维数组是三行四列。
三维数组的遍历采用三层for循环实现
#define _CRT_SECURE_NO_WARNINGS#include #include /*多维数组的定义、初始化和使用@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){//定义一个三行四列的二维数组int numbers[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };//定义一个三维数组//有2个二维数组//每个二维数组有3行4列int data[2][3][4] = { { {1,2,3,4},{5,6,7,8},{9,10,11,12} },{ {13,14,15,16},{17,18,19,20},{21,22,23,24} } };//使用三层for循环遍历三维数组for (int i = 0; i < sizeof(data) / sizeof(data[0]);i++) {for (int j = 0; j < sizeof(data[0]) / sizeof(data[0][0]); j++) {for (int k = 0;k
程序运行结果
![46f9721b7733a85d720c0a21fdcb24fb.png](https://i-blog.csdnimg.cn/blog_migrate/4acb71771e30d9f549962571436ae164.jpeg)
字符数组
字符数组的定义和初始化
字符数组用于存储多个字符,字符数组定义的语法格式为 char ch[长度],其中长度为常量值,例如char chs[5]; chs表示字符数组,能存储5个字符。
数组在尚未初始化赋值前最好别使用,因为会被系统赋垃圾值
//定义字符数组时如果尚未初始化,系统会赋随机值,因此尚未初始化之前最好别使用char x[5];printf("遍历尚未初始化字符数组");for (int i = 0; i < 5;i++) {//系统会赋随机值printf("%d ",x[i]);}printf("");
字符数组的初始化方式1,此时将数组的所有元素都已经赋值
//定义字符数组chs,初始化赋值5个元素char chs[5] = {'a','b','c','d','e'};int size = sizeof(chs) / sizeof(chs[0]);printf("遍历初始化全部字符数组元素");//遍历字符数组的元素for (int i = 0; i < size;i++) {printf("%c ",chs[i]);}printf("");
字符数组的初始化方式2:此时只给数组的部分元素赋值,而其他元素的默认值都是'0',因为字符'0'在终端上是不可见字符,所有只能以整数的方式遍历输出
printf("遍历初始化部分字符数组元素");//定义字符数组chx,初始化赋值3个元素,其他元素的默认值为'0'char chx[5] = { 'a','b','c'};//遍历字符数组的元素for (int i = 0; i < size; i++) {//因为'0'在终端上无法显示,这里以'0'的ASCII编码方式打印结果printf("%d ", chx[i]);}printf("");
字符串以'0'结尾。使用printf()函数结合字符串格式符%s打印字符串常量时会将'0'之前的内容打印出来。
// printf()函数打印字符串常量时会将0之前的内容全部打印出来//字符串都是以'0'字符结尾的printf("%s","hello");
字符串是一个特殊的字符数组,即如果字符数组以'0'结尾,那么它就是字符串,'0'作为一个字符串的结束标记。
char chz[5] = {'a','b','c','d','0'};printf("chz=%s", chz);
字符串常量使用""表示,而""中的字符串内容是0结尾
//定义了一个字符数组,存储的是abcd0 5个字符char chy[5] = "abcd";
在统计字符数组大小时,如果是字符串,会将'0'连带统计
//定义了一个字符数组,存储的是hello0 6个字符 因为字符串都是0结尾的char chm[] = "hello";//一个字符占据1个字节,因此chm的大小是6printf("chm的字节大小是%d",sizeof(chm));
字符数组两种初始化0的方式
//定义了数组chi 初始化赋值0 //将数组的第一个元素赋值为0,其他元素都是0char chi[10] = "";for (int i = 0; i < sizeof(chi) / sizeof(chi[0]);i++) {printf("%d ",chi[i]);}printf("");//等价于char chj[10] = {0};for (int i = 0; i < sizeof(chj) / sizeof(chj[0]); i++) {printf("%d ", chj[i]);}printf("");
在以字符串的方式打印字符数组时,如果字符数组没有'0',那么打印会得到一个乱码的结果
char chx[] = {'a','b','c'};printf("chx字符数组占据的字节数量是%d", sizeof(chx));//此时会出现乱码的结果,因为%s遇到0才会结束printf("chx = %s", chx);
而字符'0',整数0都表示字符串的结束,即如果以字符串的方式输出字符数组,那么0或者是'0'之后的字符不会再输出
//理解 0 '0'和'0'在字符数组的作用char chy[] = {'a','b',0,'d','e'};char chz[] = {'a','b','0','d','e'};char chm[] = {'a','b','0','d','e'};// chy = abprintf("chy = %s",chy);//chz = ab0deprintf("chz = %s", chz);//chm = abprintf("chm = %s", chm);
在定义字符数时,如果赋值为字符串,而字符串包含了'0',此时0后面不要跟着数字,有可能刚好几个数字连起来是一个转义字符
// 012 表示换行char str[] = "012tony";
字符串的输入输出
scanf读取字符串
scanf在读取字符串时遇到空格或者就会结束读取。
如果存放scanf读取字符的空间不足,还会造成内存污染,例如键盘输入的字符是4个,存储时只有2个字符,但是scanf依然将4个字符赋值给了2个字符的变量,因此scanf是不够安全的。
#define _CRT_SECURE_NO_WARNINGS#include #include /*使用scanf读取字符串@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){printf("请输入你的用户名");//定义字符数组保存键盘输入的字符串char username[2] = "";//%s表示从键盘获取一个字符串,遇到或者空格结束//%s需要的是字符数组的首元素地址//数组名就是首元素的地址scanf("%s",username);printf("你输入的用户名是%s",username);system("pause");return 0;}
程序运行结果
![254627f544c06ee0c477ebadde8ccb43.png](https://i-blog.csdnimg.cn/blog_migrate/570fb0a4d7baece5ca3b5734608dfc73.jpeg)
程序运行结果
此时还会引发程序的运行时错误
![a87f15984c78eaaa33b30bcd06821e64.png](https://i-blog.csdnimg.cn/blog_migrate/c6815f4475d1db586e4c399203a3fd65.jpeg)
gets函数读取字符串
gets()函数读取字符串时能够读取空格,但是还是和scanf()函数一样会造成内存污染
#define _CRT_SECURE_NO_WARNINGS#include #include /*使用gets()函数从键盘读取字符串@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){char str[2] = "";//gets遇到会结束,遇到空格不会结束,即gets()可以读取空格//gets也会和scanf一样造成内存污染printf("请输入字符串hello world");gets(str);printf("str = %s",str);system("pause");return 0;}
![be02bf4359f9786a99e3a28ee6cc89b0.png](https://i-blog.csdnimg.cn/blog_migrate/82548d6b5b0e7238f1170a1ad3df0b13.jpeg)
程序运行结果
此时还会引发程序的运行时错误
![702810901bdc6ba64e8b1586eb979f45.png](https://i-blog.csdnimg.cn/blog_migrate/a133f9bf2176f1be3bf9030b17da9ba3.jpeg)
fgets函数读取字符串
fgets()标准库函数,从stream指定的文件内读入字符,保存到s所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 '0' 作为字符串结束
相对于scanf()和gets()不会污染内存,即是安全的,但是会读取,此时可以使用strlen()函数获取数组的有效元素的个数,将最后一个元素()置空即可。
#define _CRT_SECURE_NO_WARNINGS#include #include #include /*fgets()标准库函数,从stream指定的文件内读入字符,保存到s所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 '0' 作为字符串结束相对于scanf()和gets()不会污染内存,即是安全的,但是会读取@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){printf("请输入用户名 ");char user_name[128];//fgets()会读取,输出时会有换行的效果//fgets()会读取空格,也不会造成内存污染,默认读取size -1个字符fgets(user_name, sizeof(user_name), stdin);//将置为0user_name[strlen(user_name) - 1] = 0;printf("user_name = %s ",user_name);system("pause");return 0;}
strlen()函数位于string.h头文件中,因此在使用之前需要使用#inlcude将其包含到源文件中,该函数用于查找字符数组中有效字符的个数,所谓的有效字符就是非0的字符,我们也可以自己实现strlen()函数
#define _CRT_SECURE_NO_WARNINGS#include #include #include /*获取数组中有效字符的个数@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){//模拟从键盘读取的字符串char buf[128] = "helloworld";int i = 0;//字符数组的有效字符的个数int length = 0;while (buf[i] !=0) {i++;}length = i;printf("buf数组的元素长度是%d",length);//可以使用strlen()函数获取int len=strlen(buf);printf("使用strlen()函数获取buf数组的元素长度是%d", len);buf[length - 1] = '0';printf("buf = %s",buf); system("pause");return 0;}
字符串输出puts和fputs函数
字符串的输出除了最常用的printf()结合字符串格式符%s以外,C语言还提供了puts()和fputs()函数
其中puts()函数会输出字符串后换行,而fputs()函数输出字符串后不会换行。
#define _CRT_SECURE_NO_WARNINGS#include #include /*字符串的输出@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){char buf[128] = "helloworld";puts(buf);//数组第一个元素的首地址// stdout 标准输出,默认输出到终端fputs(buf,stdout);system("pause");return 0;}
程序运行结果
![e4f829f10753f9ea77fe4aa6a7d92a88.png](https://i-blog.csdnimg.cn/blog_migrate/de423b39c7eed311554081353f4c3570.jpeg)
实现字符串拼接
日常开发中经常会用到两个字符串的拼接
#define _CRT_SECURE_NO_WARNINGS#include #include /*字符串拼接@author liuguanglei 18601767221@163.com@wechat 18601767221@website ittimeline.net@version 2020/11/23*/int main(int argc, char* argv[]){char str1[128] = "hello";char str2[128] = "world";printf("字符串str1拼接之前的值是%s",str1);int i = 0;while (str1[i]!=0) {i++;}int str1_length = i;int j = 0;//将str2的内容拼接到str1后面while (str2[j]!=0) {str1[i] = str2[j];i++;j++;}printf("字符串str1拼接之后的值是%s", str1);system("pause");return 0;}
程序运行效果
![aa221ceb57108ed67d1aa1583b993402.png](https://i-blog.csdnimg.cn/blog_migrate/41d22bc504d49c5c375988e136bd6ffb.jpeg)
程序运行效果