掌握数组需求:
1.数组的定义和数组的初始化 2.数组下标的使用 3.数组名的含义
基本概念
-
数组的概念
-
由相同类型的多个元素所组成的一种复合数据类型
-
在工程中同时定义多个相同类型的变量时,重复定义,可以使用数组
-
-
逻辑:一次性定义多个相同的变量,并存储到一片连续的内存中
-
格式
-
类型说明符 数组名[整型常量表达式];
-
类型说明符:指定数组元素的数据类型,任意c语言合法类型都可以
-
数组名 : c语言标识符,其值为该数组的首地址(常量)
#include <stdio.h> int main(int argc, char const *argv[]) { char a[5]; printf("a size is %d\n",sizeof(a)); // 输出a的地址,数组名相当于这个数组的首元素地址 printf("a addr : %p\n",a); // 输出a的首元素地址 printf("a[0] addr : %p\n",&a[0]); printf("a[1] addr : %p\n",&a[1]); // 数组的下标地址与首地址的偏移量有关,与数组的大小无关 // 比如,int a[3] = a的基地址+偏移量2 printf("a[2] addr : %p,%p,%p\n",&a[2],a+2,&a[0]+2); // 输出变量b的地址 int b = 10; printf("%p\n",&b); return 0; }
-
整型常量表达式:指定数组元素的个数
-
-
示例:
int a[5];//定义一个数组a,该数组一个由五个元素,每个元素都是int类型 int size = 4; int array[size];//变长数组,如果式局部变量则正确,全局变量编译出错
-
语法释义:
-
a是数组名,即这片连续内存的名称
-
[5]代表这片连续内存总共分成5个相等的格子,每个格子称为数组的元素
-
int代表每个元素的类型,可以是任意基本类型,也可以是组合类型,甚至可以是数组
-
数组中所有元素的类型都是一致
-
数组申请的空间是连续的,从低地址到高地址依次连续存放数组中的每个元素
-
-
数组定义
#include <stdio.h> int main(int argc, char const *argv[]) { // 申请5块连续的空间,将a称为数组 int a[5]; // 将这5块空间逐一赋值,注意,数组下标从0开始 a[0] = 1; a[1] = 20; a[2] = 30; a[3] = 40; a[4] = 50; //a[5] = 60; // 越界,无法使用 printf("%d\n",a[4]); // 循环给数组a赋值 for(int data = 10,i = 0; i < 5; i++,data+=10) { a[i] = data; } // 遍历输出 for(int i = 0; i < 5; i++) { printf("%d\n",a[i]); } return 0; }
-
初始化:在定义的时候赋值,称为初始化
// 正常初始化 int a[5] = {100,200,300,400,500}; int a[5] = {100,200,300,400,500,600}; // 错误,越界了 int a[ ] = {100,200,300}; // OK,自动根据初始化列表分配数组元素个数 int a[5] = {100,200,300}; // OK,只初始化数组元素的一部分 // 不能在使用变长数组的情况下初始化数组 int a[size] = {1,2,3};//编译出错 // 变长数组只能先定义再使用 int a[size]; // 正确的 a[0] = 10;
数组的真实存储
#include <stdio.h> int main(int argc, char const *argv[]) { // 定义数组并初始化 int Array[5] = {10,25,31,48,60}; printf("%d\n",Array[2]); // 初始化的时候确定数组空间大小 int Array0[] = {10,20,39}; printf("%d\n",Array0[2]); // 初始化一部分空间,剩余的空间默认初始化为0 int Array1[10] = {68,70}; // 定义数组并清空数组 int Array2[10] = {0}; // 变长数组 int len = 3; // 错误,初始化的时候,数组大小必须为常量 //int Array3[len] = {100,200,300}; // 哪怕不确定len的大小,但是可以确定Array3的空间最小值为一个 // int类型的空间,所以至少可以存放一个int类型的数据 int Array3[len]; Array3[0] = 100; printf("Array3[0] = %d\n",Array3[0]); return 0; }
-
如果是定义的时候初始化,但是初始化列表个数小于数组的大小,那么无论这个数组是全局变量还是局部变量,没有赋值的成员都是0
int array[3] = {100,200,300,400};// 错误,越界访问没有申请到的空间
int array[3];// 局部变量为随机数,全局变量为0 int array[3] = {0};//清空数组
测量数组的总大小:sizeof(array) 测量数组元素个数:sizeof(array)/sizeof(array[0])
#include <stdio.h> int main(int argc, char const *argv[]) { // 根据初始化的时候分配空间从而确定数组的大小 int Array[] = {10,20}; printf("%d\n",sizeof(Array)); // 计算数组的元素个数 int count = sizeof(Array) / sizeof(Array[0]); printf("数组空间个数: %d\n",count); return 0; }
数组元素的引用
-
存储模式:一片连续的内存,按数据类型分割成若干相同大小的格子
-
元素下标:数组开头位置的偏移量
元素下标偏移量
数组名[下标] "下标":C语言的下标是从0开始,下标必须是>=0的整数 a[0]、a[1]、a[n] 引用数组元素a[i]和普通变量一样,既可以作为左值,也可以作为右值 下标最小值为0,最大值为 元素个数 -1
-
示例
int a[5]; // 有效的下标范围是 0 ~ 4 a[0] = 1; a[1] = 66; a[2] = 21; a[3] = 4; a[4] = 934; a[5] = 62; // 错误,越界了 a = 10; // 错误,不可对数组名赋值
a是数组的名字,代表最大元素类型的首地址
#include <stdio.h> int main() { int size = 4; //1)多个元素具有相同的数据类型,可以用一个集合来表示,数组 //int val1,val2,val3,val4; //int arr[size]; //2)数组的初始化 //int arr[4] = {10,20,30,40}; //3)清空数组 int arr[4] = {0}; //数组中 arr[0] == 0,后面没有赋值的默认都是0 //数组元素的引用,通过数组的名字 + 下标 arr[0] 下标一定是大于等于0,下标最大值等于 元素的个数 -1 //arr[0] ---》val1 //arr[1] ---》val2 //arr[2] ---》val3 //arr[3] ---》val4 //arr[4] --内存访问越界,此时程序不会给你报错 for(int i=0; i<4; i++) { printf("arr[%d] addr:%p value:%d\n",i,&arr[i],arr[i]); //arr[0] } printf("&arr[0] :%p\n",&arr[0]); printf("&arr[0]+1 :%p\n",&arr[0]+1); return 0 ; }
作业;
1.复习今天的内容从,把今天的代码自己写一遍或多遍,直到掌握为止 2.完成以下习题 3.预习字符数组以及后面的内容
字符数组
-
概念:专门存放字符的数组,称为字符数组
-
初始化与元素引用:
char s1[5] = {'a', 'b', 'c', 'd', 'e'}; // s1存放的是字符序列,非字符串 char s2[6] = {'a', 'b', 'c', 'd', 'e', '\0'}; // s2存放了一个字符串 char s[6] = {"abcde"}; // 使用字符串直接初始化字符数组 char s[6] = "abcde" ; // 大括号可以省略 s[0] = 'A'; // 索引第一个元素,赋值为 'A'
#include <stdio.h> int main(int argc, char const *argv[]) { char Array[6] = {'D','A','V','I','D','\0'}; printf("%c\n",Array[3]); // 'k' for(int i = 0; i < 6; i++) { printf("%c",Array[i]); } printf("\n"); // 输出字符串用%s printf("%s\n",&Array[0]); printf("%s\n",Array); char Array1[6] = {"DAVID"};// ""表示字符串,字符串末尾有\0 printf("%s\n",Array1); // 最常用的方法 char Array2[6] = "DAVID"; printf("%s\n",Array2); return 0; }
练习: 定义一个char str[6] = {'h','e','l','l','o'}; //把这个数组的内存图画出来,并且把数组的名字的地址,还有每个元素的地址都打印出来,每个元素的地址相差几个字节?
#include <stdio.h> int main(int argc, char const *argv[]) { char str[6] = {'h','e','l','l','o'}; printf("%p\n",str); printf("%p\n",&str[0]); printf("%p\n",&str[1]); printf("%p\n",&str[2]); printf("%p\n",&str[3]); printf("%p\n",&str[4]); printf("%p\n",&str[5]); return 0; }
练习1:定义一个整型数组,遍历数组中的每个元素,并把数组中的每个元素的地址都打印出来
int Array[6] = {10,20,30,40,50,60}; for(int i = 0; i < 6; i++) { printf("%p\n",Array[i]); }
数组偏移量
int Array[6] = {10,20,30,40,50,60}; // 数组地址偏移量 // 000000000061FE04,000000000061FE04 printf("%p,%p",&Array[1],&Array[0]+1);
数组元素地址解引用
通过对数组元素地址解引用,可以获取地址空间里面的数据
int a = 10; printf("%d\n",a); printf("%p\n",&a); // * 表示将地址里面的内容取出,我们把它称为解引用 printf("%d\n",*(&a)); //-------------------------- char Array[5] = {'j','a','c','k'}; printf("%c\n",Array[0]); printf("%c,%c\n",*(Array+0),*(&Array[0])); printf("%c,%c\n",*(Array+1),*(&Array[1])); printf("%c,%c\n",*(Array+2),*(&Array[2])); printf("%c,%c\n",*(Array+3),*(&Array[3]));
练习2:定义一个整型数组,存储从键盘上获取的多个整型数据(比如输入 10 40 23 13),输入-1输入完成,并打印出这一排数据的最小值和最大值
#include <stdio.h> int main(int argc, char const *argv[]) { int Array[100] = {0}; int i = 0; while(1) { scanf("%d",&Array[i]); if(Array[i] == -1) { break; } i++; } // 将-1剔除 Array[i--] = 0; // 假设Array[0]为最小值 int min_value = Array[0]; // 假设Array[0]为最大值 int max_value = Array[0]; // 遍历查找最小值和最大值 for(int j = 1; j < i; j++) { // 不断替换比min_value小的值 if(min_value > Array[j]) { min_value = Array[j]; } // 不断替换比max_value大的值 if(max_value < Array[j]) { max_value = Array[j]; } } printf("min_value : %d, max_value : %d\n",min_value,max_value); return 0; }
字符串常量
-
字符串常量在内存中的存储,实质是一个匿名数组
-
匿名数组,同样满足数组两种涵义的规定
-
示例:
printf("%s\n","hello"); printf("'h' addr:%p\n","hello"); printf("'e' addr:%p\n","hello" + 1); //地址+1 printf("'o' addr:%p value:%c\n","hello" + 4,*("hello" + 4)); //地址+1
printf("%d\n", sizeof("abcd")); // 此处 "abcd" 代表整个数组 printf("%p\n", &"abcd"); // 此处 "abcd" 代表整个数组 printf("%c\n", "abcd"[1]); // 此处 "abcd" 代表匿名数组的首元素地址
#include <stdio.h> int main(int argc, char const *argv[]) { printf("%s\n","abc"); printf("%c,%c\n","abc"[0],*("abc"+0)); printf("%c,%c\n","abc"[1],*("abc"+1)); printf("%c,%c\n","abc"[2],*("abc"+2)); printf("%d,%d\n","abc"[3],*("abc"+3)); // '\0' return 0; }
多维数组
-
概念:若数组元素类型也是数组,则该数组称为多维数组,就是一维数组的集合
-
示例:
int a1[3]; int a2[3]; int a[2][3];//a[0]-->a1 a[1]-->a2 第一种解释: 定义一个二维数组。该数组是由2个一维数组组成,分别是a[0] a[1] 每个一维数组由3个元素组成,所以二维数组有6个元素 数据类型 二维数组的名字[有多少个一维数组][每个一维数组有多少个元素] // 代码释义: // 1, a[2] 是数组的定义,表示该数组拥有两个元素 // 2, int [3]是元素的类型,表示该数组元素是一个具有三个元素的整 型数组 第二种解释: 该数组一共有2行,每行由3个元素组成(2行3列) 数据类型 二维数组名[行][列] 所谓的行:表示这个二维数组一共有多少个一维数组 所谓的列:表示这个二维数组每个一维数组有多少个元素
-
二维数组在内存中的存放
-
按行存放,先放第一行,再放第二行...
-
-
多维数组的语法跟普通的一维数组语法完全一致
-
初始化:
int a[2][3] = {{1,2,3}, {4,5,6}}; // 数组的元素是另一个数组 int a[2][3] = {{1,2,3}, {4,5,6}, {7,8,9}}; // 错误,越界了 int a[2][3] = {{1,2,3}, {4,5,6,7}}; // 错误,越界了 int a[ ][3] = {{1,2,3}, {4,5,6}}; // OK,自动根据初始化列表分配数组元素个数 int a[2][3] = {{1,2,3}}; // OK,只初始化数组元素的一部分
-
元素引用:
// a[0] 代表第一个元素,这个元素是一个具有 3 个元素的数组:{1,2,3} // a[1] 代表第二个元素,这个元素也是一个具有 3 个元素的数组:{4,5,6} printf("%d", a[0][0]); // 输出第一个数组的第一个元素,即1 printf("%d", a[1][2]); // 输出第二个数组的第三个元素,即6
//1、定义 //int arr[3][4]; //2、定义的时候初始化 //1)分行给二维数组初始化 /* int arr[3][4] = { {10,20,30,33}, {40,50,60,66}, {70,80,90,99}}; */ //2)将所有的数据全部写在一个大括号里面,按照数组的排列顺序进行赋值 //int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; //int arr[3][4] = {{1,2,3,4},{5},6,7}; //3)清空数组 //int arr[3][4] = {0}; //4)如果对全部元素进行赋值,则定义数组的时候可以省略第一维的长度 //int arr[][4] = {1,2,3,4,5,6,7,8,9,10,11,12}; //int arr[][] = {1,2,3,4,5,6,7,8,9,10,11,12}; 错误 //int arr[3][] = {1,2,3,4,5,6,7,8,9,10,11,12}; 二维数组解引用
// 二维数组初始化字符串 char buf1[2][5] = {"jack","rose"}; printf("%s,%s\n",&buf1[0][0],buf[0]); printf("%s,%s\n",&buf1[1][0],buf[1]); // 取二维数组中的某个字符 printf("%c,%c,%c,%c\n",buf1[0][1],*(&buf1[0][1]),*(buf1[0]+1),*(*(buf1+0)+1)); printf("%c,%c,%c,%c\n",buf1[1][2],*(&buf1[1][2]),*(buf1[1]+2),*(*(buf1+1)+2));
数组地址偏移
练习1 : 定义二维数组,int buf[2] [3];将二维数组中每个元素的值和地址都打印出来
#include <stdio.h> int main(int argc, char const *argv[]) { char buf[2][4] = {"abc","efg"}; printf("abc addrs : %p\n",buf[0]); printf("a addrs : %p\n",&buf[0][0]); printf("abc addr + 1 : %p\n",buf[0]+1); printf("&abc addr + 1 : %p\n",&buf[0]+1); printf("efg addrs : %p\n",buf[1]); printf("efg addrs : %p\n",&buf[1][0]); printf("buf addrs : %p\n",buf); printf("buf+1 addrs : %p\n",buf+1); printf("&buf addr : %p\n",&buf); printf("&buf+1 addr : %p\n",&buf+1); return 0; }
练习2 : 定义一个3行4列的二维数组,并初始化,输出该二维数组的和、最小值的下标(该最小值在第几行第几列)
#include <stdio.h> int main(int argc, char const *argv[]) { int Array[3][4] = { {20,15,18,13}, {8,6,3,7}, {25,38,23,16} }; int sum = 0; int min = Array[0][0]; int minRowIndex = 0;// 最小行 int minColIndex = 0; // 最小列 for(int i = 0; i < 3; i++)// 行 { for(int j = 0; j < 4; j++)// 列 { // 计算总大小 sum += Array[i][j]; // 查最小值 if(min > Array[i][j]) { min = Array[i][j]; minRowIndex = i; minColIndex = j; } } } printf("sum:%d, min:%d, minRowIndex:%d,minColIndex:%d\n", sum,min,minRowIndex,minColIndex); return 0; }
-
二维数组的名字其实也是地址
#include <stdio.h> int main(int argc, char const *argv[]) { int arr[2][3] = {{10,20,30},{40,50,60}}; for(int i = 0; i < 2; i++)// 表示有多少个一维数组 { for(int j = 0; j < 3; j++)// 表示每个一维数组有多少个元素 { printf("arr[%d][%d] arr:%p \t",i,j,&arr[i][j]); } printf("\n"); } printf("\n"); printf("arr数组名: %p\n",arr); printf("arr+1: %p\n",arr+1); printf("&arr[0][0]+4:%p\n",&arr[0][0]+4); printf("arr[0]+1:%p\n",arr[0]+1); printf("&arr[0]+1:%p\n",&arr[0]+1); printf("arr[0]+5:%p\n",arr[0]+5); /**************************/ printf("**************************\n"); printf("&arr:%p\n",&arr); printf("&arr+1:%p\n",&arr+1); return 0; }
练习3:
char str[3][5] = { {'h','e','a','l','o'}, {'w','o','r','c','d'}, {'h','d','g','b','k'}};
画出 该二维数组的内存图 , 数组的名字代表的地址范围 是哪个,还有 &str[1] [2] +2 代表的地址范围
str[2] +2 代表的地址范围 &str代表的地址范围 &str + 1代表的地址范围
&str[1] [2] 表示字符'r'空间的地址,+2 表示字符'd'的地址 str[2] 表示第二行数组的首元素地址其实就是字符'h'的地址, +2表示字 符'g'的地址 &str表示二维数组str的地址, +1越界,内存泄漏,段错误
作业:
1.复习今天的所有内容,将代码重新写一遍或者多遍 2.完成练习1,2,3
数组万能拆解法
-
任意的数组,不管有多复杂,其定义都由两部分组成。
-
第1部分:说明元素的类型,可以是任意的类型(除了函数)
-
第2部分:说明数组名和元素个数
-
-
示例:
int a[4]; // 第2部分:a[4]; 第1部分:int int b[3][4]; // 第2部分:b[3]; 第1部分:int [4] int c[2][3][4]; // 第2部分:c[2]; 第1部分:int [3][4] int *d[6]; // 第2部分:d[6]; 第1部分:int * int (*e[7])(int, float); // 第2部分:e[7]; 第1部分:int (*)(int, float)
-
注解:
-
上述示例中,a[4]、b[3]、c[2]、d[6]、e[7]本质上并无区别,它们均是数组
-
上述示例中,a[4]、b[3]、c[2]、d[6]、e[7]唯一的不同,是它们所存放的元素的不同
-
第1部分的声明语句,如果由多个单词组成,C语言规定需要将其拆散写到第2部分的两边
-