数组
引例
如果我们要在程序中表示一个学生的成绩,我们会使用一个int来表示,如:int score
。假如我们要在程序中表示一组成绩,此时我们所学的常规数据类型就无法再表示,这个 时候我们就需要使用到一种新的表现形式,这种表现形式就是我们的数组。
什么是数组
数组是相同类型,有序数据的集合。
数组的特征
- 数组中的元素被称为数组的元素,是同构的。
- 数组中的元素存放在内存空间里(如:char ct_name[20]:在内存中申请20块连续 的基于char类型的变量空间)
衍生概念:下标(索引)
- 下标或索引代表了数组中元素举例第一个元素的偏移位置(距离第一个地址的偏移位置,所以从0开始)。
- 数组中的元素的地址值,下标越大,地址值越大。(每一块内存空间都有一个独有的内存地址,变量空间以字节为单位进行地址的标注,我们也可以说一个1内存单元=1字节)
- 数组的下标是从0开始。
一维数组
数组的定义
语法:
类型说明符 数组名[数组的容量];
说明:
- 数组的类型说明符由数组中的元素来决定,也就是元素是什么类型,数组就是什么类型
- 数组名也是 标识符,我们所说的数组 (名),大家可以理解为数据类型是数组的变量(名)。命名规则于变量名相同。遵循标识符 命名 规则
- 数组容量也可以叫做元素个数或者常量表达式,表达式必须为整型,==可以包含常量和符号常量,但不能是变量。 ==
int arr[5]; // 常量#define COUNT 5
int arr[COUNT]; // 符号常量int arr[800 * 400]; // 常量表达式
类型:代表了数组中元素的类型
容量:数组中能存储多少元素,数组容量可以是常量、常量表达式、符号常量。但必须是整数
深入理解:
- 定义一个数组,相当于申请了 一个可以容纳所指定元素数量的内存空间。所申请的内存单元是连续的。
- 定义一个数组,相当于定义了多个匿名的变量,这些变量可以通过
数组名[下标]
来标识。
举例:
// 定义一个数组
int arr[10]; // 最小索引:0,最大索引 = 数组元素 个数 -1
经过上面的案例,分析得到:
数组的最大下标 = 数组元素个数(数组容量)- 1
数组元素的访问
原则:数组中的元素不能一次性访问所有元素,只能一个一个的访问。
语法:
数组名[下标]
说明:以上语法,既能实现数组中元素的取,也能够实现数组中元素的存。
举例:
// 定义一个能够容纳10个元素的int数组
int arr[10];
// 给数组的第一个元素进行赋值
arr[0] = 89;
arr[0] = 88;// 访问数组的第一个元素
int a = arr[0]; // 88// 问题:以下访问是否正确
int b = arr[10]; // error:下标越界异常
注意:数组元素的访问一定不能越界
案例:
#include <stdio.h>
// 数学库
#include <math.h>#include <string.h>
#include <time.h>
#include <stdlib.h>/*** 需求:数组案例:利用循环给数组 元素赋值0~9,并且逆序输出。*/
int main()
{// 创建一个数组int arr[10];// 利用for循环给数组元素赋值for( int i = 0; i < 10; i++ ){arr[i] = i + 10;}// 逆序 输出// 通过一个循环将数组中的每一个元素取出,称之为遍历for( int j = 9; j >= 0; j-- ){printf("%-4d", arr[j]);}printf("\n");return 0;
}
数组的初始化
定义数组的同时,用指定数据来给对应元素赋值。
简化数组 定义后,需要对元素一一赋值操作。
语法:
类型 数组名[容量大小] = {常量1,常量2,常量3...};
注意:
-
数组可以部分初始化:也就是可以给数组中的前几个元素初始化,未被初始化的元素系统将自动初始化;如果定义数组时未被指定数据容量,则系统会根据初始化元素的个数来决定数组容量。
// 1.如果 定义数组时只给数组前几个元素初始化,后续剩余元素自动完成初始化 int arr[10] = {11,12,13,14,15}; // 推荐写法,部分初始化 int arr[10] = {11,12,13,14,15,0,0,0,0,0};// 等价于上面的写法// 2.如果定义数组时未指定数组容量,根据初始化 元素的个数量来决定容量 int arr[] = {1,2,3,4,5}; // 推荐写法,等价于以下写法 int arr[5] = {1,2,3,4,5};
衍生概念:
-
==柔性数组:==柔性数组的概念在C99标准,针对结构体的最后一个成员可以是一个未指定大小的数组;
广义简单理解:数组容量待定或者待确定的数组,举例:int arr[] = {1,2,3,4,5}
面试题:
-
在不知道数组类型的情况下,如何确定数组元素的个数
int length = sizeof(arr)/sizeof(arr[0]);
说明:
- arr就是我们计算的数组本身,
sizeof(arr)
用来计算该数组中总的字节大小。 sizeof(arr[0])
用来计算数组中第一个元素所占的字节大小,因为数组中元素类型相同,所以计算哪一个都行。sizeof(arr)/sizeof(arr[0])
就是用数字中总的字节数除以每一个元素所占的字节数,从而得到元素的个数
- arr就是我们计算的数组本身,
-
-
冒泡排序
向后冒泡
思想:
- 一次只排好一个数,针对n个数,最差情况需要 n-1次就可以排好
- 每次排序将相邻数据两两比较,将较大或较小的数据向后交换,等所有数据都比较完成,将
较大/较小的数就会出现在最后,这也是该数应该有的位置。
- 在余下的数中,再次应用第2步的操作,直到只剩下一个数。
向前冒泡
思想:
- 一次只排好一个数,针对n个数,最差情况需要n-1次就可以排好
- 每次排序假定第一个元素是最大/最小的,用第一个元素的后面的元素一一与第一个元素比
较,遇到较大/较小的和第一个元素交换,访问完数组的最后一个元素,就排好了一个数;
- 在余下的数中,再次应用第2步的操作,直到只剩下一个数。
一维数组案例
案例1:
#include <stdio.h>/*** 需求:一维数组案例:斐波拉契数列n = (n - 1)+(n - 2)。*/
int main()
{int i; // 循环变量int f[20] = {1,1}; // 定义一个数组,用来存储数列,默认存储第一位和第二位// 计算获取数组的大小,主要用于 遍历数组int len = sizeof(f) / sizeof(f[0]);// 将生成的序列存入数组for(int i = 2; i < len; i++){// 给数组元素赋值,从数组的三个元素开始f[i] = f[i-1] + f[i-2];}// 遍历数组for( int j = 0; j < len; j++ ){// 遍历的时候,要求一行显示5个数if( j % 5 == 0){printf("\n");}printf("%-6d", f[j]);}printf("\n");return 0;
}
案例2:
#include <stdio.h>/*** 需求:一维数组案例:冒泡排序*/
int main()
{// 创建一个数组,用来存储排序的数列int arr[10];// i: 外层-比较的轮数,j:内层-每一轮比较的次数,temp:临时变量,用来比较交换的数据int i, j, temp;printf("请输入:\n");int le = sizeof(arr)/sizeof(arr[0]);// 循环录入数据for( int a = 0; a < le; a++ ){scanf("%d", &arr[a]);}printf("\n");// 对数组arr使用冒泡排序(注意:我们使用的是标准的冒泡排序)// 外层循环:控制比较轮数,轮数 = 数组容量 -1for( i = 0; i < le -1; i++ ){// 每一轮比较的次数for( j = 0; j < le - i - 1; j++ ){// 相邻的两个数比较,满足条件就交换位置if( arr[j] > arr[j+1] ){temp = arr[j];arr[j] = arr[j+1];arr[j+1] = temp;}}}printf("冒泡排序后遍历数组:\n");for( int a = 0; a < le; a++ ){printf("%-4d", arr[a]);}return 0;
}
案例3:
#include <stdio.h>/*** 需求:一维数组案例:从键盘输入年,月,日,并计算输出是该年的第几天*/
int main()
{// 定义变量:年,月,日,统计总天数,循环变量,用来遍历当前月前面的月份int year,month,day,sum,k;// 定义一个数组,用来存放1~12月每月的天数int t[] = { 31,28,31,30,31,30,31,31,30,31,30,31};printf("请输入年月日:");scanf("%d.%d.%d", &year, &month, &day);// 2024.12.3//if((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) // 优先级: 算术>关系>逻辑{// 先将输入的天记录到总天数t[1] = 29;}// 先输入记录到总天数for( k = 0; k < month -1; k++ ){sum += t[k];}printf("%d月%d日是%d年的第%d天。\n", month, day, year, sum);return 0;
}
二维数组
定义
二维数组本质上是一个行列式的组合,也就是说 二维数组是由行和列两部分组成。二维数组数据是通过行列进行解读.
二维数组可以被视为一个特殊 的一维数组,相当于二维数组又是一个一维数组,只不过它的元素是一维数组。
{3,,5}
{{1,2,3},{4,5,6}} // 数组的元素可以是数组类型
语法
类型 数组名[行数][列数];
举例:不能省略数组内的数组的大小
int arr1[3][3] = {{11,12,13},{21,22,23},{31,32,33}}; // 二维数组的定义并初始化
int arr1[][3] = {{11,12,13},{21,22,23},{31,32,33}}; // 等价于上面的写法,柔性数组int arr1[][3] = {{11,12},{21,22,23},{31}}; // 等价于下面写法 (柔性数组,根据最大的元素个数分配)
int arr1[][3] = {{11,12,0},{21,22,23},{31,0,0}}; int arr1[][] = {{11,12,13},{21,22,23},{31,32,33}}; // 这种写法会编译报错
注意:在C语言中,二维数组在计算机的储存顺序是按行进行的,即第一维的下标变化慢,第二维的下标变化快。
应用场合
主要应用 在数据有行列要求的情况,比如说我们现在要 存储粤嵌目前在读班级的学生的成绩。
{1,2,3} // 一个班的成绩
{{1,2,3},{4,5,6}} // 管理两个班的数组
特殊写法
- 下标可以是整型表达式,如:
a[2-1][2*2-1]
- 下标可以是已经有变量或数组元素,如
a[2*x - 1][b[3]][1]
- 数组元素可以出现在表达式中,如:
b[1][2] = a[2][3]/2
注意:使用数组元素的下标应在已定义数组的大小范围内;应注意区别定义 数组大小和引用数组元素的区别。
-
分行给二维数组赋初值
int arr[3][4] = {{11,12,13,14},{21,22,23,24},{31,32,33,34}};
-
可以用 将所有数据写在一对花括号内,按照排序对元素赋值
int arr[3][4] = {11,12,13,14,21,22,23,24,31,32,33,34};
-
可以对部分元素赋初值,其余未赋值部分自动补充0,整型表现形式:0,字符型表现形式:‘\0’,浮点型表现形式:0.0
int arr[3][4] = {{11},{21,22},{31}};
-
若对全部元素赋初值,自定义 数组时可以省略第1维数组的长度,第2维数组的长度必须指明
int a[][4] = {11,12,13,14,21,22,23,24,31,32,33,34};
-
在分行赋初值时,也可以省略第1维的长度
int arr[][4] = {{11,12,13},{0},{0,10}};
二维数组的遍历
案例1:
/*************************************************************************> File Name: work11.c> Author: sgc> Description: 二维数组的遍历> Created Time: 2024年12月04日 星期三 10时29分37秒************************************************************************/#include <stdio.h>int main(int argc,char *argv[])
{// 创建一个二维数组,经过 论证:二维数组我们必须指定列的大小,行的大小可以省略int arr[][3] = {{11},{21,22},{31,32,33}};// 获取二维数组的大小,其实就是行数int len = sizeof(arr)/sizeof(arr[0]);//int len1 = sizeof(arr[0])/sizeof(arr[0][0]);// 遍历二维数组,因为二维数组是行列式,所以要使用双层循环// 先遍历外层数组,获取行for( int i = 0; i < len; i++ ){for( int j = 0; j < len1; j++ ){printf("%-3d", *(*(arr+i)+j)); // 等价于arr[i][j],(arr[i])[j],*(arr[i]+j)}printf("\n");}return 0;
}
案例2:
数组的转置:
/*************************************************************************> File Name: work11.c> Author: sgc> Description: 二维数组的-矩阵的转置(就是将一个2行3列转换成3行2列的数组)> Created Time: 2024年12月04日 星期三 10时29分37秒************************************************************************/#include <stdio.h>int main(int argc,char *argv[])
{// 准备两个数组,存放转置前后的数列int arr_before[2][3] = {11,12,13,21,22,23};int arr_after[3][2] = {0};for( int i = 0; i < 2; i++ ){for( int j = 0; j < 3; j++ ){arr_after[j][i] = arr_before[i][j];printf("%-3d", arr_before[i][j]);}printf("\n");}printf("\n");for( int i = 0; i < 3; i++ ){for( int j = 0; j < 2; j++ ){printf("%-3d", arr_after[i][j]);}printf("\n");}return 0;
}
字符数组
概念
元素类型为char字符类型的数组往往是用来存储字符串数据的。需要注意的一点是,我们C语言中的字符是字节字符。
字节字符:也就是1个字符占一个字节,在C语言中,我们使用char标识字节。
测试题:
char a = 'A'; // 正确
char b = '1'; // 正确
char c = 65; // 正确,ASCII码
char d = "A"; // 错误,这是字符串的写法,也是char数组的写法,char d[1] = "A";
char e = '司';// 错误,中文一个字符超过一个字节,中文以字符串的形式存储
语法
char 数组名[容量];
char 数组名[行容量][列容量];
字符数组的语法就是我们前面所学的一维数组和二维数组的语法,只不过数据类型是char而已。
注意:
如果我们的char数组初始化的时候,没有完全赋值的时候,空出来的地方使用\0
进行填充。
比如:char c[8] = {'h','e','l','l','o'}
等价于char c[8] = {'h','e','l','l','o','\0','\0','\0'}
案例:
/*************************************************************************> File Name: work11.c> Author: sgc> Description: 字符数组案例> Created Time: 2024年12月04日 星期三 10时29分37秒************************************************************************/#include <stdio.h>int main(int argc,char *argv[])
{/** 输出一个字符串(I LOVE YOU!) */// 准备一个测试数组 ASCII中码0和32对应的不是同一个意义的空char c[12] = {'I',32,'L','O','V','E',' ','Y','o','u','!'};// 通过一个for循环进行遍历输出for( int i = 0; i < sizeof(c)/sizeof(c[0]); i++ ){printf("%c", c[i]);}return 0;
}
案例2:
/*************************************************************************> File Name: work11.c> Author: sgc> Description: 字符数组案例> Created Time: 2024年12月04日 星期三 10时29分37秒************************************************************************/#include <stdio.h>int main(int argc,char *argv[])
{/** 输出一个用字符组成的菱形 */// 准备数据char arr[5][5] = {{ ' ',' ','*',' ',' ' },{ ' ','*','*','*',' ' },{ '*','*','*','*','*' },{ ' ','*','*','*',' ' },{ ' ',' ','*',' ',' ' }};// 遍历数组for( int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++ ){for(int j = 0; j < sizeof(arr[i])/sizeof(arr[0][0]); j++){printf("%c", arr[i][j]);}printf("\n");}printf("\n");return 0;
}
注意:
1.如果定义时不初始化,元素值 不确定;
char arr1[2]; // 此时属于未初始化,元素值是不确定的(随机的) char arr2[5] = {'a','b','c'}; // 此时属于不完全初始化,未初始化的时候,用'\0'填充
字符串和字符串结束标志
说明
- C语言规定,字符串以字符
\0
作为结束标志。 - 编译系统对字符串常量自动加一个
\0
作为结束标志。 - 程序中往往通过判断
\0
来检测字符串是否结束。 \0
的ASCII码为0,不是一个可显示的字符,是“空操作符”,它什么都不做,不会增加有效字符,仅有一个供判别的标志。
字符数组的多样表示
我们的char数组可以以数组的方式一个个输出每个字符;我们的char数组也可以以字符串的方式整体进行输出所有字符。
/**
* 需求:字符数组→ 字符串
*/
#include <stdio.h>
int main()
{// 字符串的第1种表示:char s1[] = {'h','e','l','l','o',' ','w','o','r','l','d'};// 字符串的第2种表示:char s2[] = {"hello world"};// 字符串的第3种表示:char s3[] = "hello world";// 输出字符串,printf("%s,%s,%s\n",s1,s2,s3);// hello world,hello world,hello world
}
注意:
-
字符串的长度于字符数组的长度不一定相同。
-
利用字符串的常量可以对字符数组进行初始化,但不能用字符串常量为字符数组赋值。
// 正确演示,利用字符串常量可以对字符数组进行初始化 char arr1[6] = "hello";// 错误演示,用字符串常量为字符数组赋值。 char arr2[6]; arr2 = "hello";
字符数组
字符串的基础操作
在用格式化说明符%s进行输入输出时,其输入输出项均为数组名,但在输入时,相邻两个字符串之间要用空格分隔,系统将自动的在字符串最后加结束符“\0”。在输出时,遇到结束符"\0"作为输出结束标志。
对于字符串的操作,我们需要使用到一些系统提供的函数。
字符串的输入
语法:
scanf(“%s”, 数组名);
scanf("%s", 数组名);
案例:
/*************************************************************************> File Name: work11.c> Author: sgc> Description: 字符串操作函数> Created Time: 2024年12月04日 星期三 10时29分37秒************************************************************************/#include <stdio.h>int main(int argc,char *argv[])
{/** 字符串的输入* 语法:scanf("%s",数组名);* */// 创建一个数组,用来存放人的名字char name[20];printf("请输入您的姓名:");scanf("%s", name); // 注意:如果是字符串,这里不是变量地址,是变量名,数组本身不占内存空间,数组名默认指向第一个元素的首地址printf("%s\n", name);return 0;
}
注意:采用scanf()进行字符串输入,要求字符串中不能存在空格,否则字符串遇到空格会结束。
fgets(数组名,数组容量,sdtin);
功能:
从键盘录入一个字符串到字符数组,返回字符数组的地址。
说明:
采用fgets进行字符输入,可获取所有输入的字符串,包含\n
,在实际的字符串处理时,我们可能需要
/*************************************************************************> File Name: work11.c> Author: sgc> Description: 字符串操作函数> Created Time: 2024年12月04日 星期三 10时29分37秒************************************************************************/#include <stdio.h>int main(int argc,char *argv[])
{/** 字符串的输入* 语法:fgets(数组名, 数组容量, stdin);* */// 创建一个数组,用来存放人的名字char name[20];printf("请输入您的名字:");// fgets和scanf只能二选一fgets(name, sizeof(name)/sizeof(char), stdin);printf("%s\n", name);return 0;
}
注意:
- 如果输入的字符不包括
空格
和换行
,可以使用scanf()|fgets(); - 如果输入的字符串需要包含
空格
和换行
,只能使用fgets(); - 经过对比,我们发现,在字符串的输入中,fgets();
字符串的输出
printf(“%s”, 数组名);
案例:
/*************************************************************************> File Name: work11.c> Author: sgc> Description: 字符串操作函数> Created Time: 2024年12月04日 星期三 10时29分37秒************************************************************************/#include <stdio.h>int main(int argc,char *argv[])
{/** 字符串的输出* 语法:printf("%s", name);* */// 创建一个数组,用来存放人的名字char name[20];printf("请输入您的名字:");// fgets和scanf只能二选一fgets(name, sizeof(name)/sizeof(char), stdin);printf("%s\n", name);return 0;
}
puts(数组名);
功能:
输出一个字符串
说明:
字符串可以包含转义字符
案例
/*************************************************************************> File Name: work11.c> Author: sgc> Description: 字符串操作函数> Created Time: 2024年12月04日 星期三 10时29分37秒************************************************************************/#include <stdio.h>int main(int argc,char *argv[])
{/** 字符串的输出* 语法:puts(数组名);* */// 创建一个数组,用来存放人的名字char name[20];printf("请输入您的名字:");// fgets和scanf只能二选一fgets(name, sizeof(name)/sizeof(char), stdin);puts(name); // 无法进行字符串拼接,只能直接输出return 0;
}
字符串的拼接
strcat(数组名(字符串1), “需要拼接的字符串(字符串2)”);
引用:
#include <string.h>
案例:
/*************************************************************************> File Name: work11.c> Author: sgc> Description: 字符串操作函数> Created Time: 2024年12月04日 星期三 10时29分37秒************************************************************************/
#include <string.h>
#include <stdio.h>int main(int argc,char *argv[])
{/** 字符串的拼接* 语法:strcat(数组名, "需要拼接的字符串");* */// 创建一个数组,用来存放人的名字char name[20];printf("请输入您的名字:");// fgets和scanf只能二选一fgets(name, sizeof(name)/sizeof(char), stdin);strcat(name,"快跑"); // 进行拼接,并返回拼接后的字符串puts(name); // 无法进行字符串拼接,只能直接输出return 0;
}
注意:
字符数组(字符串1)
的长度必须足够大,以便能容纳被连接的字符串。- 连接后系统将自动取消
字符串1(字符数组1)
后面的结束符"\0" 字符串2
可以是字符数组名,也可以是字符串常量,如:strcat(s1,“def”);
字符串的拷贝
strcpy(数组名,字符串);
引入:
#include <string.h>
说明:
这个函数适合给字符串赋值用
char str[16] = "司高超"; // 正确,初始化char str[16];
str[16] = "司高超"; // 错误,赋值char str[16];
strcpy(str,"司高超"); // 正确,借用函数完成赋值
案例:
/*************************************************************************> File Name: work11.c> Author: sgc> Description: 字符串操作函数> Created Time: 2024年12月04日 星期三 10时29分37秒************************************************************************/
#include <string.h>
#include <stdio.h>int main(int argc,char *argv[])
{/** 字符串的拷贝* 语法:strcpy(数组名, "需要拷贝的字符串");* */// 创建一个数组,用来存放人的名字char name[20];printf("请输入您的名字:");// fgets和scanf只能二选一fgets(name, sizeof(name)/sizeof(char), stdin);strcpy(name, "sgc");puts(name); // 无法进行字符串拼接,只能直接输出return 0;
}
字符串比较
strcmp(字符串1,字符串2);
引入:
#include <string.h>
功能:
比较两个字符串对应位置字符ASCII的大小(字典顺序比较)。
返回值:
若字符串1等于字符串2,返回0
若字符串1大于字符串2,返回正整数
如字符串1小于字符串2,返回负整数
说明:
- 执行这个函数时,自左到右逐个比较对应字符的ASCII的值,直到发现了不同字符或者字符串结束符
\0
为止。 - 对字符串不能用数值型比较符
- 字符串1与字符串2可以是字符数组名,也可以是字符串常量。
案例:
/*************************************************************************> File Name: work11.c> Author: sgc> Description: 字符串操作函数> Created Time: 2024年12月04日 星期三 10时29分37秒************************************************************************/
#include <string.h>
#include <stdio.h>int main(int argc,char *argv[])
{/** 字符串的比较* 语法:strcmp(字符串1,字符串2);* */// 创建一个char数组,用来存放账户和密码char username[20], password[8];printf("请输入您的账户:");scanf("%s", username);printf("请输入您的密码:");scanf("%s", password);if( !strcmp(username,"admin") && !strcmp(password,"123456") ){printf("登录成功!\n");}else{printf("账号或密码错误");}return 0;
}
获取字符串长度
strlen(字符串)
引用:
#include <string.h>
注意:
返回字符串中包含的字符的实际个数,不含\0
案例:
/*************************************************************************> File Name: work11.c> Author: sgc> Description: 字符串操作函数> Created Time: 2024年12月04日 星期三 10时29分37秒************************************************************************/
#include <string.h>
#include <stdio.h>int main(int argc,char *argv[])
{/** 字符串的长度* 语法:strlen(字符串);* */// 创建一个char数组,用来存放账户和密码char username[20];printf("请输入您的账户:");scanf("%s", username);unsigned long len = strlen(username);if( strcmp(username,"admin") ){printf("账户输入错误%d\n", len);}else{printf("账户输入正确%d\n", len);}return 0;
}
gets和fgets的区别
1.fgets 允许指定最大读取字符数(包括终止空字符\0),这可以防止缓冲区溢出,因为函数会确保不超过指定的字符数。
gets 函数没有参数来指定最大读取字符数,它会一直读取直到遇到换行符或文件结束符,但如果输入字符串过长,可能会覆盖数组以外的内存。
2.fgets 会读取最多 n-1 个字符,并在最后添加终止空字符 \0。如果遇到换行符或文件结束符,它会停止读取。
gets 会读取字符直到遇到换行符或文件结束符,并将换行符替换为终止空字符 \0。
这两个函数的返回值都是如果成功,返回str(是一个指向字符数组的指针),如果遇到文件结束符或读取错误,返回 NULL。