目录
前言
数组是C语言中一个非常基础和重要的数据结构。数组的起源可以追溯到早期的计算机编程语言,如Fortran和Algol。这些语言在设计和实现过程中,为了解决如何高效地存储和访问一组相同类型的数据,引入了数组这个概念。
数组的作用主要是为了提高代码效率和可读性。通过将同类型的数据存储在连续的内存空间中,数组可以提供一种简单且直观的方式来组织和处理一组数据。使用数组的索引,我们可以快速地访问和操作数组中的特定元素,而不需要为每个元素单独分配内存空间。
在C语言中,数组的使用非常普遍,可以用于处理各种问题,如排序、查找、数学计算等。同时,数组也经常被用于存储和操作复杂的数据结构,如多维数组和稀疏矩阵等。
一、数组的概述
在程序设计中,为了方便处理数据把具有相同类型的若干变量按有序形式组织起来——称为数组。
数组就是在内存中连续的相同类型的变量空间。同一个数组所有的成员都是相同的数据类型,同时所有的成员在内存中的地址是连续的。
数组属于构造数据类型:
1)一个数组可以分解为多个数组元素:这些数组元素可以是基本数据类型或构造类型。
int a[10];
struct Stu boy[10];
2)按数组元素类型的不同,数组可分为:数值数组、字符数组、指针数组、结构数组等类别。
int a[10];
char s[10];
char *p[10];
通常情况下,数组元素下标的个数也称为维数,根据维数的不同,可将数组分为一维数组、二维数组、三维数组、四维数组等。通常情况下,我们将二维及以上的数组称为多维数组。
试想一下这样的使用场景,我们想在程序中存储一头猪的重量,存储一个的时候可以定义一个变量,存储 10头猪的重量,可以定义 10 个变量,存储 100 ,1000头呢?如果一个一个变量的定义,恐怕时间都不够用。那么这种情况我们可以采用数组。
二、一维数组
2.1 一维数组的定义
数组定义的语法:
数据类型 数组名[数组长度];
例如: int arr[10];
1 数组名的命名规则和变量名相同,遵循标识符命名规则。
2数组名后接一个或多个方括号,用以指定数组的维数(元素个数)
3数组长度只能是常量和常量表达式 (大于0),不能是变量。
注:(c99 标准中规定允许使用变量,但vs 编译器并不支持,因为不符合c++标准)。
2.2 一维数组元素的引用
数组可以通过下标来访问某一个元素,下标是具有整数值的表达式,也就是说下标可以是:
1 常量
2 变量
3 其他有整数值的表达式
数组元素的表示形式为:
数组名[下标]
例: arr[3],arr[i + 1]。
注意:通过下标对每个数组元素的使用与普通变量一样。数组下表从0开始,故而像 int nTemp[10]这样的数组,下标最大到 9。
代码示例如下
#include <stdio.h>
int main()
{
int nNum = 10;
//int Arr[nNum]; //定义时长度不支持变量
int Arr1[10]; //定义一个整型数据的数组,元素有 10 个
int Arr2[10]={10, 25, 50, 1, 2, 9, 4, 5, 36, 15}; //初始化
int num = 0;
//使用数组下标访问数组
printf(“%d\n", Arr2[num]); //我们使用数组的时候,下标可以是变量
num++;
printf("%d\n", Arr2[num]);
num++;
printf("%d\n", Arr2[num]);
return 0;
}
使用数组名和下标来访问数组中的每一个元素,数组中的某一个元素在使用的时候,和普通的变量没有任何区别。
2.3 一维数组初始化
在数组定义的时候,给每一个元素初值,称之为初始化,通常有以下初始化的情况:
1.整体赋值
int ary[5] ={1,2,3,4,5};
2.部分赋值
int arr[5]={1,2,3};//还有最后两个元素为 0。
3.不给定数组长度 根据实际数组元素个数分配
int arrl[] ={1,2,3,4,5,6); //共有6 个元素,所以实际分配 24 个字节空间
#include <stdio.h>
int main()
{
int nNum = 10;
int Arr1[10]; //定义一个整型数组,元素有 10 个
int Arr2[10] = {10, 30, 50, 1, 2, 3, 4, 5, 9, 15};//初始化
int Arr3[] = {1, 2, 3, 4, 5}; //正确的,数组长度可以省略
//下面都是常见的错误情况
//int Arr4[]; //错误的,没有初始化的时候,数组长度不能省略。
//int Arr3[nNum]; //错误的,数组长度必须是常量
//Arr1 = Arr2; //错误的,数组不能整体赋值
//Arrl = {10, 30, 50, 1, 2, 3, 4, 5, 9, 15}; //错误的,数组不能整体赋值
//使用数组元素和使用变量一致
scanf_s("%d", &Arr1[3]);
printf("%d", Arrl[3]);
return 0;
}
注意:在定义数组的时候给值叫做初始化,之后的时候叫做赋值。
2.4 一维数组的存储
数组在内存中的存储是连续的。
例:一个包含 11个整型元素的数组
int arrTem[11]; //11 表示的有 11个元素,11是数组长度,而不是数组的下标
arrTem[0],arrTem[1],arrTem[2]....arrTem[10]; //数组下标从0开始,所以最大下标为 10。
注:在内存中用连续的空间存储,每个元素占 4 个字节。
定义数组的时候,数组长度必须是一个常量,使用数组的时候下标既可以是一个常量也可以是一个变量。
2.5 一维数组的越界问题
使用数组的时候,假如不小心下标超过了最大界限会发生什么?这种情况,编译器是不会检查的,称之为数组的越界问题。这种情况非常的危险,会破坏程序的内存。造成程序间歇性的不稳定。(就是程序运行有可能会出错,有可能不会出错)。下面是举例代码。
#include <stdio.h>
int main() {
int arr[5]; // 定义一个长度为5的整型数组
int i;
arr[0] = 0;
printf("%d ", arr[0]);
arr[1] = 1;
printf("%d ", arr[1]);
arr[2] = 2;
printf("%d ", arr[2]);
arr[3] = 3;
printf("%d ", arr[3]);
arr[4] = 4;
printf("%d ", arr[4]);
// 越界访问数组元素
arr[5] = 5;
printf("%d ", arr[5]);
return 0;
}
2.6 字符数组
字符数组是一种比较特殊的数组,主要体现在它的初始化方面以及存储字符串时候的使用方面。
#include <stdio.h>
int main()
{
char aryl[] = {'H','e','l','l','o','j','e','r','y'}; //正常用法和之前类似
char ary2[] = "Hellojery"; //字符数组的特殊用法,可以用一个字符串初始化数组
printf("%d\n", sizeof(ary1)); //打印9
printf(“%d\n", sizeof(ary2)); //打印 10 因为字符串最后有一个’0’。
printf("%s\n", ary2); //这里会正确的打印 Hellojery
printf("%s\n", ary1); //这里不会正确的打印,会多打印一部分字符,因为遇到"\0"才结束。
return 0;
}
在 C语言中,字符串是以’\0’为结尾的,所有使用字符串的地方,都是检测字符串结尾的’\0’来判断字符串是不是结束了。
注意:字符串的结尾是一个’\0’,这个字符的ASCII码是0。而字符 0的ASCII码是 0x30。
2.7 字符数组的输入输出
数组名其实就是数组的起始地址,是一个常量,这个概念可能小白第一次接触到。这种特性也使得数组在内存中占据连续的空间。当我们创建一个数组时,系统会为它分配一块连续的内存空间,而这块空间的起始地址就是数组名所代表的地址。因此,如果我们知道一个数组的起始地址和每个元素的大小,就可以通过简单的计算得到任意元素的地址。
#include <stdio.h>
int main(){
char string[10];
scanf_s("%s", string, 10); //会检测输入的字符个数是不是超过了第三个参数
//scanf("%s", string); //越界是不会检测的
//为什么 scanf中string 不用取地址呢?因为数组名就是首地址,在此可以得到结论。
printf("%x", string);
return 0;
}
2.8 字符数组与字符串
2.8.1 两者的区别
1)C语言中没有字符串这种数据类型,可以通过char的数组来替代;
2)字符串一定是一个char的数组,但char的数组未必是字符串;
3)数字0(和字符‘\0’等价)结尾的char数组就是一个字符串,但如果char数组没有以数字0结尾,那么就不是一个字符串,只是普通字符数组,所以字符串是一种特殊的char的数组。
#include <stdio.h>
int main()
{
char c1[] = { 'c', ' ', 'p', 'r', 'o', 'g' }; //普通字符数组
printf("c1 = %s\n", c1); //乱码,因为没有’\0’结束符
//以‘\0’(‘\0’就是数字0)结尾的字符数组是字符串
char c2[] = { 'c', ' ', 'p', 'r', 'o', 'g', '\0'};
printf("c2 = %s\n", c2);
//字符串处理以‘\0’(数字0)作为结束符,后面的'h', 'l', 'l', 'e', 'o'不会输出
char c3[] = { 'c', ' ', 'p', 'r', 'o', 'g', '\0', 'h', 'l', 'l', 'e', 'o', '\0'};
printf("c3 = %s\n", c3);
return 0;
}
2.8.2 字符串一些常见函数
字符串除了前面介绍的输入输出的函数,还有一些其他的常见函数,比如
strlen 求字符串的长度
strcpy/strcpy_s 字符串拷贝函数
strcmp 比较两个字符串是否一样
strcat/strcat_s 将两个字符串拼接到一起
1)strlen 函数
出处:
#include <string.h>
size_t strlen(const char *s);
功能:计算指定指定字符串s的长度,不包含字符串结束符‘\0’
参数:s:字符串首地址
返回值:字符串s的长度,size_t为unsigned int类型
示例:
char str[] = "abcdefg";
int n = strlen(str);
printf("n = %d\n", n);
2)strcpy 函数
出处:
#include <string.h>
char *strcpy(char *dest, const char *src);
功能:把src所指向的字符串复制到dest所指向的空间中,'\0'也会拷贝过去
参数:
dest:目的字符串首地址
src:源字符首地址
返回值:
成功:返回dest字符串的首地址
失败:NULL
注意:如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况。
代码示例:
char dest[20] = "123456789";
char src[] = "hello world";
strcpy(dest, src);
printf("%s\n", dest);
3)strncpy 函数
出处:
#include <string.h>
char *strncpy(char *dest, const char *src, size_t n);
功能:把src指向字符串的前n个字符复制到dest所指向的空间中,是否拷贝结束符看指定的长度是否包含'\0'。
参数:
dest:目的字符串首地址
src:源字符首地址
n:指定需要拷贝字符串个数
返回值:
成功:返回dest字符串的首地址
失败:NULL
代码示例:
char dest[20];
char src[] = "hello world";
strncpy(dest, src, 5);
printf("%s\n", dest);
dest[5] = '\0';
printf("%s\n", dest);
4) strcat 函数
出处:
#include <string.h>
char *strcat(char *dest, const char *src);
功能:将src字符串连接到dest的尾部,‘\0’也会追加过去
参数:
dest:目的字符串首地址
src:源字符首地址
返回值:
成功:返回dest字符串的首地址
失败:NULL
代码示例:
char str[20] = "123";
char *src = "hello world";
printf("%s\n", strcat(str, src));
5)strncat 函数
出处:
#include <string.h>
char *strncat(char *dest, const char *src, size_t n);
功能:将src字符串前n个字符连接到dest的尾部,‘\0’也会追加过去
参数:
dest:目的字符串首地址
src:源字符首地址
n:指定需要追加字符串个数
返回值:
成功:返回dest字符串的首地址
失败:NULL
代码示例:
char str[20] = "123";
char *src = "hello world";
printf("%s\n", strncat(str, src, 5));
6)strcmp 函数
出处:
#include <string.h>
int strcmp(const char *s1, const char *s2);
功能:比较 s1 和 s2 的大小,比较的是字符ASCII码大小。
参数:
s1:字符串1首地址
s2:字符串2首地址
返回值:
相等:0
大于:>0
小于:<0
代码示例:
char *str1 = "hello world";
char *str2 = "hello mike";
if (strcmp(str1, str2) == 0)
{
printf("str1==str2\n");
}
else if (strcmp(str1, str2) > 0)
{
printf("str1>str2\n");
}
else
{
printf("str1<str2\n");
}
7)strncmp 函数
出处:
#include <string.h>
int strncmp(const char *s1, const char *s2, size_t n);
功能:比较 s1 和 s2 前n个字符的大小,比较的是字符ASCII码大小。
参数:
s1:字符串1首地址
s2:字符串2首地址
n:指定比较字符串的数量
返回值:
相等:0
大于: > 0
小于: < 0
代码示例:
char *str1 = "hello world";
char *str2 = "hello mike";
if (strncmp(str1, str2, 5) == 0)
{
printf("str1==str2\n");
}
else if (strcmp(str1, "hello world") > 0)
{
printf("str1>str2\n");
}
else
{
printf("str1<str2\n");
}
当然strcpy_s和strcat_s是后缀加“_s”的安全版本函数,使用示例如下
#include <stdio.h>
int main()
{
char dest[20] = "123456789";
char src[] = "hello world";
strcpy(dest, src);
printf("%s\n", dest);
char cChar1[10] = "hello";
char cChar2[20] = "hello";
char cChar3[20] = "world";
cChar3[5] = 0; //\0
//求字符串长度 strlen
printf("%d\n", sizeof(cChar1));
printf("%d\n", sizeof(cChar2));
printf("%d\n", strlen(cChar1));
//cChar2[3] = 0;
printf("%d\n", strlen(cChar2)); //注意:strlen 只检测0
//将一个字符串拷贝到一块内存区域中:strcpy
//strcpy(cChar2, cChar3);非安全版函数
//strcpy_s(cChar2, 20, cChar3); //把后面的字符串拷贝到前面去
//比较两个字符串是不是一样的: strcmp
printf("%d\n", strcmp(cChar2, cChar3));
//如果要是说,两个字符串完全一样,这个函数得到结果是0,否则就是非0。
//拼接两个字符串strcat
strcat_s(cChar1, 10, cChar3); //会报错,因为缓冲区大小不够
strcat s(cChar2, 20, cChar3); //够了就不报错了,这就是安全函数的作用
printf("%s", cChar1);
return 0;
}
三、二维数组
二维数组就是一种有行和列概念的数组,你可以把二维数组看成是一维数组的延伸,因为从内存的角度来看,他们是一样的。但是由于有了行和列的概念,在处理起一些问题来,更为便捷。
3.1 二维数组的定义
声明方式:
类型说明 符数组名[常量表达式 1][常量表达式2];
二维数组也可以看成是一个一维数组,它的每一个元素又是一个一维数组。
例如二维数组a[2][3],可以看作由2个一维数组a[0]、a[1]组成,这两个数组元素是包含3个整型数组元素。
如果是多维数组,以此类推
声明方式:类型标识符 n维数组名[元素个数1][元素个数2]…[元素个数n];
3.2 二维数组的初始化
1.分行给二维数组赋初值。
例:int arrTem[2][3] = {{1,2,3},{4,5,6}};//使用大扩号分别把每行标记出来
2.可以将所有数据写在一个花括号内,按数组排列的顺序对各元素赋初值。
例:int arrTem[2][3] = {1,2,3,4,5,6};
3.可以对部分元素赋初值。
例:
int arrTem[2][3] = {{1},{2,3}}; //分行方式初始化
int arrTem[2][3] = {1,2,3}; //整体方式初始化
注:这两种方式虽然都是给部分赋值,但是有区别的.
分行方式:至少每行都有值,不足的补 0。
整体方式: 将第一行赋值完后,再赋值后面的内容,有可能该部分值只给了第一行,后面的值都默认为0。
4. 如果对全部元素都赋初值(即提供全部初始数据),则定义数组时对第一维的长度可以不指定,但第二维的长度不能省。
例 1:
int arrTem[][5] = (1,2,3,4,5,6,7,8,9):
数组会自动根据元素实际的个数去开辟空间。一共有9个元素,每一行有 5 个元素,那么能够确定有两行,
第一行:1,2,3,4,5;
第二行:6,7,8,9,0; //不足补0
例 2:
int arrTem[2][]={1,2,3,4,5,6);//这种方式是不行的
这种为什么就不行呢?因为我们只知道有两行,但每一行中有多少个元素不确定。可以是任何大小的元素个数
3.3 二维数组的引用
二维数组元素的使用方式和一维数组类似:
数组名[下标][下标]
1.下标可以是普通的数值
代码示例:
int Arry1[4][5] ={1, 2, 3, 4, 5, 6, 7, 8, 9, 1, 2, 3, 4, 5};
printf("%d\n", Arry1[0][0]); //打印第一行的第一个元素:1
printf("%d\n", Arry1[0][1]); //打印第一行的第二个元素:2
printf("%d\n", Arry1[1][0]); //打印第二行的第一个元素:6
2.下标可以是整型表达式
代码示例:
arrTem[2-1][2* 2- 1]; //无论什么表达式,只要满足结果大于等于0
3.下标可以是变量
代码示例:
//二维数组的遍历
for (int i=0; i<4; i++){
for(int j=0; j < 10; j++){
printf("%d",Arry2[i][j]);
}
printf("\n");
}
什么是遍历?就是访问数组中的每一个元素,这种操作是数组中常见操作。
4. 数组元素在使用的时候和普通变量一样,可以出现在表达式中,也可以被赋值。
代码示例:
arrTem[1][2] = arrTem[2][3] / 2;
上述代码是将该下标中的元素值作除法后,再赋值给前面的元素。
注意:
下标值都是从0开始,注意应在已定义的数组大小的范围内。
常出现的错误代码示例:
int a[3][4];
a[3][4] = 3; //数组中没有该下标的元素。下标是从0 开始的。
3.4 二维数组的内存存放顺序
比如定义了一个2行3列的数组,数组名为a其元素类型为整型,该数组的元素个数为2×3个。
int a[2][3];
二维数组a是按行进行存放的,先存放a[0]行,再存放a[1]行,并且每行有3个元素,也是依次存放的。
二维数组在概念上是二维的,其下标在两个方向上变化,对其访问一般需要两个下标。在内存中并不存在二维数组,二维数组实际的硬件存储器是连续编址的,也就是说内存中只有一维数组,即放完一行之后顺次放入第二行,和一维数组存放方式是一样的。