前言
数组是一种数据结构,它可以存储多个相同类型的元素。
一、一维数组
1.1 定义一维数组
int a[10];
一般形式:类型名 数组名[常量表达式]
注意事项:
1、数组名的命名规则:遵循标识符命名规则
标识符必须以字母(A-Z,a-z)或下划线(_)开头。
标识符可以包含字母,数字(0-9)和下划线(_)。
标识符区分大小写,即大小写字母被视为不同的字符。
标识符不能使用关键字作为名称,例如if,else,for等。
2、常量表达式:用于表示元素的个数
a[10]表示a数组有10个元素
3、下标是从零开始
a[5]:a[0]、a[1]、a[2]、a[3]、a[4]。不存在数组元素a[5]。
4、常量表达式可以包含常量和符号变量,C99之后可以包含变量
常量:int a[10];
常量和符号变量:int a[3+5];
变量:int a[n];
1.2 引用一维数组元素
引用一维数组元素:数组中的元素可以通过索引来访问,索引从0开始,依次递增。例如,数组arr的第一个元素可以通过arr[0]访问,第二个元素可以通过arr[1]访问,以此类推。
int a[5] = { 1,2,3,4,5 }; printf("%d", a[0]);//输出1 printf("%d", a[1]);//输出2 printf("%d", a[2]);//输出3 printf("%d", a[3]);//输出4 printf("%d", a[4]);//输出5
数组名的特殊性:在C语言中,数组名本身实际上是一个指向数组第一个元素的指针,可以通过数组名来访问数组的元素。arr==&arr[0]。
int a[5] = { 1,2,3,4,5 }; scanf_s("%d", a); printf("%d", a[0]);//输入XX,就输出XX
数组不要越界访问:C语言中的数组是没有边界检查的,如果访问超出数组范围的元素,可能会访问到非法内存区域,导致程序崩溃或产生不可预期的结果。
int arr[5] = {1, 2, 3, 4, 5}; int x = arr[6]; // 越界访问
1.3 一维数组的初始化
C语言中,对一维数组进行初始化有多种方式。下面会一一列举:
1.逐个初始化。
int arr[5]; arr[0] = 1; arr[1] = 2; arr[2] = 3; arr[3] = 4; arr[4] = 5;//arr[5]={1,2,3,4,5}
2.在定义数组时对全部数组元素赋予初值
int arr[5]={1,2,3,4,5};//arr[5]={1,2,3,4,5}
3.使用部分初始化列表,剩下的元素会自动初始化为0。
int arr[5]={1,2,3};//arr[5]={1,2,3,0,0}
特殊情况:使数组元素全部为0
int arr[10]={0};
4.使用循环初始化
int arr[5]; for (int i = 0; i < 5; i++) { arr[i] = i + 1; }
注意事项:
如果在定义数值型数组时,指定了数组的长度并对之初始化,凡未被“初始化列表”指定初始化的数组元素,系统会自动把它们初始化为0(如果是字符型数组,则初始化为'\0';如果是指针型数组,则初始化为NULL,即空指针)。
1.4 一维数组程序举例
例1:对10个数组元素依次赋值为0~9,要求逆序输出
#include<stdio.h> int main() { int a[10]; int i; for (i = 0; i < 10; i++) { a[i] = i; } for (i = 9; i > -1; i--) { printf("%d\n", a[i]); } return 0; }
例2:给定两个整数A和B,输出从A到B的所有整数以及这些数的和。
输入格式:输入在一行中给出2个整数A和B,其中−100≤A≤B≤100,其间以空格分隔。
输出格式:首先顺序输出从A到B的所有整数,每5个数字占一行,每个数字占5个字符宽度,向左对齐。最后在一行中按
Sum = X
的格式输出全部数字的和X
。#include<stdio.h> int main() { int A, B, X = 1, i; int Sum = 0; scanf("%d %d", &A, &B); for (i = A; i <= B; i++, X++) { printf("%-5d", i);//每个数字占5个字符宽度,向左对齐 Sum = Sum + i; if (X % 5 == 0)//每5个数字占一行 printf("\n"); } //最后在一行中按Sum = X的格式输出全部数字的和X。 if ((X-1) % 5 != 0) printf("\n"); printf("Sum = %d", Sum); return 0; }
例3:有10个地区的面积,要求对它们按从小到大的顺序排列
起泡法排序:每次将相邻两个数比较,将小的调到前面。
#include<stdio.h> int main() { int area[10]; int i; for (i = 0; i < 10; i++) { scanf_s("%d", &area[i]); } for (i = 0; i < 9; i++) { if (area[i] > area[i + 1]) { int a = 0; a = area[i]; area[i] = area[i+1]; area[i + 1] = a; } } for (i = 0; i < 10; i++) { printf("%d", area[i]); printf("\n"); } return 0; }
二、二维数组
2.1 定义二维数组
int a[2][7];
一般形式:类型说明符 数组名[常量表达式][常量表达式]
注意事项:
1、二维数组中元素排列的顺序:按行存放,即先存放第一行的元素,接着再存放第二行的元素。
a[2][3]:a[0][0]->a[0][1]->a[0][2]->a[1][0]->a[1][1]->a[1][2]
2、C语言中允许使用多维数组
2.2 引用二维数组元素
引用二维数组元素:数组中的元素可以通过索引来访问,索引从0开始,依次递增。例如,数组arr的第一个元素可以通过arr[0][0]访问,第二个元素可以通过arr[0][1]访问,以此类推。
int a[2][2] = { {1,2},{3,4} }; printf("%d\n", a[0][0]);//输出1 printf("%d\n", a[0][1]);//输出2 printf("%d\n", a[1][0]);//输出3 printf("%d\n", a[1][1]);//输出4
数组名的特殊性:在C语言中,数组名本身实际上是一个指向数组第一个元素的指针,可以通过数组名来访问数组的元素。arr==&arr[0]。
(二维数组的数组名本身其实是指向二维数组第一行的地址。)
(多维数组的数组名本身其实是指向多维数组第一行的地址。)
数组不要越界访问:C语言中的数组是没有边界检查的,如果访问超出数组范围的元素,可能会访问到非法内存区域,导致程序崩溃或产生不可预期的结果。
int arr[3][4] = {0}; a[3][4]=5;//越界访问
2.3 二维数组的初始化
C语言中,对二维数组进行初始化有多种方式。下面会一一列举:
1.分行给二维数组赋初值
int a[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
2.将数据写在一个花括号内,按数组元素在内存中的排列顺序对各元素赋初值
int a[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
3.对部分元素赋初值,其余元素值自动为0
int a[3][4] = { {1},{5},{9} };//只对各行第一列的元素赋初值,其余元素自动为0 int c[3][4] = { {1,3},{5,8} };//只对第一、二行赋初值,第三行不赋初值
4.如果对所有元素赋初值(即提供所有初始数据),则定义数组时对第一维的长度可以不指定,但对第二维的长度不能省。
//两行代码等价 int a[3][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 }; int a[][4] = { 1,2,3,4,5,6,7,8,9,10,11,12 };
2.4 二维数组程序举例
例1:将一二维数组行与列的元素互换存入另一个二维数组中。如
![]()
int a[2][3] = { {1,2,3},{4,5,6} }; int b[3][2]; for (int j = 0; j < 3; j++) { for (int i = 0; i < 2; i++) { b[j][i] = a[i][j]; printf("%d ", b[j][i]); if (i == 1) { printf("\n"); } } }
例2:有一个3*4的矩阵,要求编程序求出其中值最大的那个元素的值,以及其所在的行号和列号。
int a[3][4]; int max = 0; int row = 0, colum = 0; for (int i = 0; i < 3; i++) { for (int j = 0; j < 4; j++) { scanf_s("%d", &a[i][j]); if (a[i][j] > max) { max = a[i][j]; row = i; colum = j; } } } printf("max=%d\nrow=%d\ncolum=%d", max, row, colum);
三、字符数组
3.1 定义字符数组
char c[10];
一般形式:char 数组名[常量表达式];
注意事项:
1、字符数组:用来存放字符数据的数组是字符数组,在字符数组中的一个元素内存放一个字符。
2、存储方式:字符型数据是以字符的ASCII代码存储在存储单元中的,一般占一个字节。
3、C语言中可以使用整型数组来存放字符型数据,但浪费存储空间(不建议使用)
int c[10]; c[0]='a';//合法,但浪费存储空间
3.2 字符数组的初始化
C语言中,对字符数组进行初始化有多种方式。下面会一一列举:
1.初始化单个字符
char a = 'c';
2.用“初始化列表”把各个字符依次赋值给数组中各元素
char a[5] = { 'a','b','c','d','e' };
3.对部分元素赋初值,其余元素值自动为空字符'\0'
char a[5] = { 'a','b' };
4.用字符串常量来使字符数组初始化
//两代码定义的字符数组等价 char a[] = {"I am happy"}; char a[] = {'I',' ','a','m',' ','h','a','p','p','y','\0'};
3.3 字符数组程序举例
例:输出一个已知的字符串
char a[5] = { 'a','b','c','d','e' }; for (int i = 0; i < 5; i++) { printf("%c\n", a[i]); }
四、字符串
4.1 字符串与字符数组
在C语言中,字符串是一种特殊的字符数组。字符串是由一系列字符组成的序列,以空字符('\0')作为结束标志。而字符数组是一个固定大小的数组,用于存储字符。
在C语言中,字符串可以使用字符数组来表示和操作。也就是说,一个字符串可以用字符数组来存储。字符数组可以用于存储单个字符,也可以用于存储一系列字符,形成字符串。
字符数组与字符串之间的关系表现在以下几个方面:
- 字符数组可以用于初始化字符串,即将一个字符数组的内容作为字符串的初始值。
- 字符数组可以通过赋值操作来修改字符串的内容。
- 字符数组可以通过相关的字符串处理函数来操作字符串,例如strcpy、strlen、strcmp等。
- 字符数组可以通过下标访问的方式来访问和修改字符串中的单个字符。
需要注意的是,字符数组与字符串之间的关系是相互转换的,但是并不完全等价。字符串是一个以空字符结尾的字符数组,而字符数组可以不以空字符结尾,并且可以包含其他非打印字符。因此,在处理字符串时,需要注意字符串的结束标志和字符数组的长度限制。
4.2 字符串的输入输出
字符数组的输入输出有两种方法:
1、逐个字符输入输出。用格式符%c输入输出一个字符。
char a[5]; int i; printf("请输入字符串:"); for (i = 0; i <4 ; i++) {//字符串尾部还有'\0',需要预留位置 scanf_s("%c", &a[i]); if (a[i] == '\n') { // 如果遇到换行符,则结束输入 break; } } a[i] = '\0'; // 在字符串的末尾添加字符串结束符 printf("请输出字符串:"); for (int i = 0; i < 5; i++) { printf("%c", a[i]); }
2、将整个字符串一次性输入输出。用%s格式符,意为对字符串的输入输出。
char a[6]; scanf("%s", a);//输入China printf("%s\n", a);//输出China
在内存中的存储情况
C h i n a \0
输出的注意事项:
1、输出的字符中不包含结束符'\0'
2、用%s格式符输出字符串时,printf函数中的输出项是字符数组名,而不是字符数组元素名
(错误)
char a[] = { "China" }; printf("%s\n", a[0]);
(正确)
char a[] = { "China" }; printf("%s\n", a);//输出China
3、如果数组长度大于字符串的实际长度,也只输出到遇到'\0'结束
char a[10] = { "China" }; printf("%s\n", a);//输出China
4、如果一个字符数组中包含一个以上'\0',则遇到第一个'\0'时就结束输出
输入的注意事项:
1、用scanf函数输入一个字符串。输入的字符串应短于已定义的字符数组长度。
char a[6]; scanf("%s", a);//输入China。字符个数<6,可行
2、用scanf函数输入多个字符串,则应在输入时以空格分隔。
char str1[5], str2[2], str3[5]; scanf("%s%s%s", str1, str2, str3);
输入数据How are you?
在内存中的存储情况:
H o w \0 \0 a r e \0 \0 y o u ? \0 3、系统自动把空格字符作为输入的字符串之间的分隔符
char str[13]; scanf("%s", str);
输入数据How are you?
在内存中的存储情况:
H o w \0 \0 \0 \0 \0 \0 \0 \0 \0 \0 4、用%s格式符输入字符串时,scanf函数中的输入是字符数组名,而不是字符数组的地址
(错误)
scanf("%s", &str);//错误的代码
(正确)
scanf("%s", str);//正确的代码
4.3 使用字符串处理函数
4.3.1 gets函数——输入字符串的函数
一般形式:gets(字符数组)
注意事项:
1、gets函数只能输入一个字符串。
2、使用gets()函数时要特别注意字符串的长度,以避免造成缓冲区溢出的问题。建议使用更安全的fgets()函数来获取用户输入的字符串。
4.3.2 puts函数——输出字符串的函数
一般形式:puts(字符数组)
注意事项:
1、puts函数只能输入一个字符串。
2、用puts函数输出的字符串可以包含转义字符
char str[] = { "China\nBeijing" }; puts(str);
4.3.3 strcat函数——字符串连接函数
一般形式:strcat(字符数组1,字符数组2)
注意事项:
1、字符数组1必须足够大,以便容纳连接后的新字符串。
2、连接前两个字符串的后面都有'\0',连接时会将字符串1后面的'\0'取消,只能在新字符串的最后保留'\0'。
char str1[30] = { "People's Republic of " }; char str2[] = { "China" }; printf("%s", strcat(str1, str2));
在内存中的存储情况:
P e o p l e ' s R e p u b l i c o f \0 \0 \0 \0 \0 \0 \0 \0 \0 C h i n a \0 P e p p l e ' s R e p u b l i c o f C h i n a \0 \0 \0 \0
4.3.4 strcpy和strncpy函数——字符串复制函数
一般形式:strcpy(字符数组1,字符串2)
注意事项:
1、字符数组1的长度不能小于字符串2的长度。
2、“字符数组1”必须写成数组名形式(如str),“字符串2”可以是字符数组名,也可以是一个字符串常量。
//两行代码是相同效果 char str1[10], str2[] = "China"; strcpy(str1, str2); strcpy(str1, "China");
3、如果在复制前未对str1数组初始化或赋值,则str1各字节中的内容是无法预估的。赋值时会将str2中的字符串和其后的'\0'一起复制到字符数组1中,取代字符数组1中的前面6个字符,最后4个字符并不一定是'\0',而是str1中原有的最后4个字节的内容。
4、不能用赋值语句将一个字符串常量或字符数组直接给一个字符数组。字符数组名是一个地址常量,它不能改变值,正如数值型数组名不能被赋值一样。
(错误)
str1 = "China";//错误的代码 str2 = str1;//错误的代码
只能用strcpy函数将一个字符串复制到另一个字符数组中去。用赋值语句只能将一个字符赋给一个字符型变量或字符数组元素。
char a[5], c1, c2; c1 = 'A', c2 = 'B'; a[0] = 'C', a[1] = 'h', a[2] = 'i', a[3] = 'n', a[4] = 'a';
5、可以用strncpy函数将字符串2中前n个字符复制到字符数组1中去。(不包括'\0')
strncpy(str1, str2, n);
4.3.5 strcmp函数——字符串比较函数
一般形式:strcmp(字符串1,字符串2)
注意事项:
1、字符串比较规则:将两个字符串自左向右逐个字符相比(按ASCII码值大小比较),直到出现不同的字符或遇到'\0'为止。
(1)如全部字符相同,则认为两个字符串相同。
(2)若出现不相同的字符,则以第一对不相同的字符的比较结果为准。
2、比较结果由函数值带回
(1)如果字符串1=字符串2,则函数值=0。
(2)如果字符串1>字符串2,则函数值>0。
(3)如果字符串1<字符串2,则函数值<0。
if (strcmp(str1, str2) == 0) { printf("str1=str2") }
4.3.6 strlen函数——测字符串长度的函数
一般形式:strlen(字符数组)
注意事项:
1、该函数用于测试字符串长度的函数。测得是字符串的实际长度(不包括'\0')。
2、这里拿strlen(字符数组)与sizeof(数组)进行对比char a[6] = "China"; printf("%d\n", strlen(a));//输出5,不包括'\0'
strlen函数只能用于字符数组,测得是字符串的实际长度。
sizeof函数能用于多种类型的数据,包括数组、指针、结构体、枚举等。当sizeof应用于字符数组时,它返回数组的大小(以字节为单位)。
char a[6] = "China"; printf("%d\n", strlen(a));//输出5 printf("%zu\n", sizeof(a));//输出6 printf("%zu\n", sizeof(a[0]));//输出1 printf("%zu\n", sizeof(a) / sizeof(a[0]));//输出6
4.3.7 strlwr函数——转换为小写的函数
一般形式:strlwr(字符数组)
4.3.8 strupr函数——转换为大写的函数
一般形式:strupr(字符数组)
五、数组的应用
5.1 数组分类
一维数组:数组是由相同类型的元素组成的有序集合,可以包含任意数量的元素。一维数组是最简单的数组形式,所有元素都排列在同一个线性序列中。
int numbers[5];
多维数组:除了一维数组,C语言还支持多维数组。多维数组是由多个一维数组组成的数据结构,可以是二维、三维或更高维度的数组。
int a[3][2][6];//三维数组
静态数组:静态数组在声明时就给定了固定的大小,无法在运行时改变数组的大小。
int numbers[5];
动态数组:动态数组是在运行时使用malloc或calloc等函数动态分配内存空间的数组,可以根据需要改变数组的大小。
printf("请输入数组大小:"); scanf("%d", &size); arr = (int*)malloc(size * sizeof(int));// 使用malloc函数动态分配内存 for (int i = 0; i < size; i++) { scanf("%d", &arr[i]); }
5.2 引用数组
引用数组元素:数组中的元素可以通过索引来访问,索引从0开始,依次递增。例如,数组arr的第一个元素可以通过arr[0]访问,第二个元素可以通过arr[1]访问,以此类推。
int a[5] = { 1,2,3,4,5 }; printf("%d", a[0]);//输出1 printf("%d", a[1]);//输出2 printf("%d", a[2]);//输出3 printf("%d", a[3]);//输出4 printf("%d", a[4]);//输出5
数组名的特殊性:在C语言中,数组名本身实际上是一个指向数组第一个元素的指针,可以通过数组名来访问数组的元素。arr==&arr[0]。
int a[5] = { 1,2,3,4,5 }; scanf_s("%d", a); printf("%d", a[0]);//输入XX,就输出XX
数组不要越界访问:C语言中的数组是没有边界检查的,如果访问超出数组范围的元素,可能会访问到非法内存区域,导致程序崩溃或产生不可预期的结果。
int arr[5] = {1, 2, 3, 4, 5}; int x = arr[6]; // 越界访问
5.3 数组作为函数参数(int a[]==int *a)
数组作为函数参数:在函数中,可以将数组作为参数传递给函数。
(数组作为函数参数时,往往必须要用另一个参数来传入数组的大小)
函数参数int a[]和int *a传递的东西是一样的。在函数调用时,数组名a会被自动转换为指向数组第一个元素的指针。因此,无论是使用int a[]还是int *a作为函数参数,在函数内部都可以通过指针操作来访问或修改数组的元素。
#include<stdio.h> void f(int *a, int L);//指针作为函数参数 int main() { int a[5] = {1,2,3,4,5}; f(a, sizeof(a) / sizeof(int)); printf("%d", a[0]);//输出99,成功修改a[0] return 0; } void f(int *a, int L) { a[0] = 99; }
#include<stdio.h> void f(int a[], int L);//数组作为函数参数 int main() { int a[5] = {1,2,3,4,5}; f(a, sizeof(a) / sizeof(int)); printf("%d", a[0]);//输出99,成功修改a[0] return 0; } void f(int a[], int L) { a[0] = 99; }
5.4 数组长度(sizeof函数)
数组的长度:数组的长度可以通过sizeof运算符来获取。
例如:sizeof(arr)返回数组arr的总字节数。
int arr[5] = { 1,2,3,4,5 }; printf("%zu", sizeof(arr));//输出20
sizeof(arr[0])返回数组元素的字节数。
int arr[5] = { 1,2,3,4,5 }; printf("%zu", sizeof(arr[0]));//输出4
sizeof(arr)/sizeof(arr[0])返回数组元素个数。
int arr[5] = { 1,2,3,4,5 }; printf("%zu", sizeof(arr)/sizeof(arr[0]));//输出5
六、可变数组、变长数组和动态数组
可变数组、变长数组和动态数组是C语言中三种不同的数据结构,它们之间有部分包含关系。想要理清三者之间的关系,要先知道各自的定义:
可变数组: 可变数组是通过库函数malloc()和realloc()动态分配内存来创建的,可以在运行时改变数组的大小。可变数组可以通过指针来访问和操作元素,可以使用下标或指针进行元素的读取和写入。可变数组可以使用free()函数释放内存。
变长数组: 变长数组(Variable Length Array, VLA)是C99标准引入的特性,允许在运行时创建具有不确定大小的数组。它的大小是在运行时确定的,而不是编译时确定的。变长数组可以使用下标或指针来访问和操作元素,与普通数组的操作方式相同。变长数组在超出作用域时会自动释放内存。
动态数组: 动态数组是一种在C语言中模拟的数据结构,通过使用可变数组和自定义的数据结构来实现。动态数组使用malloc()和realloc()分配内存,可以在运行时动态改变大小。动态数组使用下标或指针来访问和操作元素,与普通数组的操作方式相同。动态数组需要手动释放内存,使用free()函数
综上所述,可变数组和变长数组都属于动态数组的一种形式,它们都可以在运行时改变数组的大小。而动态数组是一个更广义的概念,包括了可变数组和变长数组。