一、数组
1. 基本概念
- 逻辑:一次性定义多个相同类型的变量,并存储到一片连续的内存中
- 示例:
int a[5];
- 语法释义:
- a 是数组名,即这片连续内存的名称
- [5] 代表这片连续内存总共分成5个相等的格子,每个格子称为数组的元素
- int 代表每个元素的类型,可以是任意基本类型,也可以是组合类型,甚至可以是数组
- 初始化:在定义的时候赋值,称为初始化
// 正常初始化 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,只初始化数组元素的一部分
数组的真实存储
注意
- 定义数组时,a[5],这里的5指的是定义数组内存放的元素个数的最大值,从1开始数。
- 在利用数组下标查找元素时,下标是从0开始的,所以下标的最大值是是定义时数组元素的数值减一。数组下标其实叫做偏移量。
2. 数组元素的引用/初始化
- 存储模式:一片连续的内存,按数据类型分割成若干相同大小的格子
- 元素下标:数组开头位置的偏移量,a[0]引用第1个格子,a[1]引用第2个格子,以此类推
元素下标偏移量
示例:
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; // 错误,不可对数组名赋值
int b[5]={0}//不完全初始化,则定义一个数组,并全初始化为0
//用户输入数组进行赋值
for(int i=0;i<Strlen(b);i++){
int ret_val=scanf("%d",&b[i]);
if (ret_val!=1){
while(getchar()!='\n');
printf("请重新输入…\n");
i—;
}
}
注意
int a[5]; // 有效的下标范围是 0 ~ 4
a = 10; // 错误,不可对数组名赋值
1.不可对数组名赋值原因:
在大多数编程语言中,数组名代表的是数组在内存中的地址,是一个常量指针,不能被赋值。这是因为数组名在编译时被解释为一个指向数组首元素的指针,而指针是一个固定的内存地址。因此,尝试对数组名赋值会导致编译器报错。
2.数组在定义的时候必须确定大小,而确定大小的方式有两种:
3.初始化:
[完全初始化]:数组大小和赋值数据个数相同
[越界初始化]:赋值数据个数大于给定数组大小。则越界数据不会被存入数组,并且编译时会报错。
[不完全初始化]:在对数组进行不完全初始化的时候,未初始化部分默认0
3. 各种类型的数组
3.1 字符数组
-
概念:
- 专门存放字符的数组,称为字符数组
-
初始化:
char s1[5] = {'a', 'b', 'c', 'd', 'e'}; // s1存放的是字符序列,非字符串(因为这样子定义没有以'\0'结尾) //以下三种写法相同,存的都是一个字符串 char s2[6] = {'a', 'b', 'c', 'd', 'e', '\0'}; // s2存放了一个字符串 char s[6] = {"abcde"}; // 使用字符串直接初始化字符数组 char s[6] = "abcde" ; // 大括号可以省略
-
元素引用
s[0] = 'A'; // 索引第一个元素,赋值为 'A'
数组长度:
函数strlen(字符串数组):计算的是数组中字符串的长度,不包含结束符'\0'
关键字sizeof(数组名):计算的是数组的占用字节数。如果是字符数组的话,一个字符刚好是一个字节,所以等于定义时[]中的数组大小。
注意点:
a. char s1[5] = {'a', 'b', 'c', 'd', 'e’};
// s1存放的是字符序列,非字符串(因为这样子定义没有以"0'结尾)。只能说是字符数组。
b.字符数组和普通数组不一样的是,字符数组通常用于存储字符串,即以"0'(空字符)结尾的字符序 列->字符串数组
c.在打印字符串的时候,printf函数会一直打印字符串,除非遇到'\0'。
3.2 多维数组
-
概念:
若数组元素类型也是数组,则该数组称为多维数组
-
声明多维数组:
声明为多维数组必须具有除第一个维度以外的所有维度的边界,所以
int a[][]={{1,2,3},{4,5,6}};是错误的多维数组声明。
int a[][3]={{1,2,3},{4,5,6}};是正确的多维数组声明。
int a[5]={1,2,3,4,5};
int b[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
printf("*b=%p\n",*b);
此时打印出来的是一个地址,该地址为数组{1,2,3,4}的首元素的地址。因为二维数组中,母数组的子元素都是地址(子数组的首元素的地址),而在以上示例中,b代表母数组的第一个元素的地址,即子数组首元素的地址的地址,二级指针。所以*b(解引用b)得到的是子数组的首元素的地址。
在C语言中,当你声明一个二维数组时,母数组的元素的地址已经分配完成,被确定为一个常量指针,可以被用于指向一个一维数组的首地址,但是指针本身不可修改,所以数组名是不能被赋值的,而母数组的元素就是一个数组名。c语言中,数组名被解释为指向数组第一个元素的指针,且这个指针是常量,不允许修改。
数组类型 数组名 [ 数组元素的数量 ] [ 数组类型的大小 ]
int* ptrArrArr[3][2];
int a = 1, b = 2, c = 3, d = 4, e = 5, f = 6;
int* ptr1[2] = {&a, &b};
int* ptr2[2] = {&c, &d};
int* ptr3[2] = {&e, &f};
ptrArrArr[0] = ptr1;
ptrArrArr[1] = ptr2;
ptrArrArr[2] = ptr3;
此时报错为:赋值给数组类型的表达式
error: assignment to expression with array type
160 | ptrArrArr[0] = &ptr1;
在C语言中,数组名是不能被赋值的,因为数组名本身就是数组首元素的地址,是一个常量指针,无法修改。因此,你无法直接给
ptrArrArr[0]、
ptrArrArr[1]和
ptrArrArr[2]赋值。
示例:
int a[2][3];
// 代码释义:
// 1, a[2] 是数组的定义,表示该数组拥有两个元素
// 2, int [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,只初始化数组元素的一部分
int a[2][ ] = {{1,2,3}, {4,5,6}}; // 编译失败,C语言只会自动计算数组的声明中元素个数,即数组声明可以自动补齐。但是元素声明无法自动补齐
int a[2][ ] = {{1,2,3}, {4,5,6}};编译失败,
C语言只会自动计算数组的声明中元素个数,即数组声明可以自动补齐。但是元素声明无法自动补齐
元素引用:
// a[0] 代表第一个元素,这个元素是一个具有 3 个元素的数组:{1,2,3}
// a[1] 代表第二个元素,这个元素也是一个具有 3 个元素的数组:{4,5,6}
printf("%d", a[0][0]); // 输出第一个数组的第一个元素,即1
printf("%d", a[1][2]); // 输出第二个数组的第三个元素,即6
注意点
- int a[2][ ] = {{1,2,3}, {4,5,6}}; // 编译失败,C语言只会自动计算数组的声明中元素个数,即数组声明可以自动补齐。但是元素声明无法自动补齐
- 对于1中的a数组,不能通过a[0]={10,20,30}的方式进行重新赋值,因为不能直接对数组名进行赋值,而a[0]是数组{1,2,3}的数组名。(赋值符号的左右两边应该是相同类型的,而在C语言中,数组名代表的是数组的首地址,是一个常量指针,因此不能直接对数组名进行赋值。当我们声明一个数组时,数组名就代表了该数组在内存中的起始地址,这个地址是固定的,无法改变。)
- 要重新赋值可以用a[0][0]=10,a[0][1]=20,a[0][2]=30,的方式进行赋值。
4. 数组语法解剖
- 任意的数组,不管有多复杂,其定义都由两部分组成。
- 第1部分:说明元素的类型,可以是任意的类型(除了函数)
- 第1部分:说明数组名和元素个数
- 示例:
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, char); // 第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部分的两边
练习:
1、判断声明是否正确
假设有如下声明:
float a[3];
float b[2][3];
float c = 2.2, *p;
则下列语句中那些是正确的,哪些是错误的?原因是什么?
a[2] = c;
a = c;
scanf("%f", &a);
printf("%f", a[3]);
b[1][2] = a[2];
b[1] = a;
p = c;
p = a;
- 解析
a[2] = c; // 对数组某个元素赋值,正确
a = c; // a 是数组,不可直接赋值
scanf("%f", &a); // a 是数组,类型不匹配
printf("%f", a[3]); // a[3] 是float数据,类型不匹配
b[1][2] = a[2]; // 对数组某个元素赋值,正确
b[1] = a; // b[1] 是数组,不可直接赋值
p = c; // 类型不匹配
p = a; // a 代表其首元素地址,等价于p=&a[0],正确
b[1] = a; // b[1] 是数组名,不可直接赋值
2、定义一个二维数组存放用户输入的名单
char names[10][32]={0};
printf("\n请输入十个以内名字,单个名字以换行结束,输入以单个名字后加.结束\n");
int len;
for (size_t i = 0; i < 10; i++)
{
len=i+1;
fgets(names[i],32,stdin);
names[i][strlen(names[i])-1]='\0';
// printf("输入时:%d\n",names[i][strlen(names[i])-1]);
if (names[i][strlen(names[i])-1]=='.')
{
names[i][strlen(names[i])-1]='\0';
break;
}
}
printf("您输入的名字为:\n");
for (size_t i = 0; i < len; i++){
printf("%s ",names[i]);
}
printf("\n");