一.数组基础
1.数组定义
数组一般形式:类型说明符 数组名 [常量或常量表达式];
类型说明符:基本数据类型、构造数据类型(不能是void类型)。说明数组中保存的数据的数据类型。数组名:遵循标识符的命名规则。
常量或常量表达式:数组中可以保存的元素的个数。
int a[10];sizeof (a); //40字节
char str[32]; //32字节
float array[5]; //20字节
注意:
常量表达式中可以包括常量和符号常量,不能包含变量。也就是说,C语言不允许对数组的大小作动态定义,即数组的大小不依赖于程序运行过程中变量的值。例如,下面这样定义数组是不行的
int n; //n为随机值 int a[n]; scanf("%d",&n); //在程序中临时输人数组的大小 ❌但在创建数组之前对变量赋值是可以的
int n; scanf("%d",&n); //在程序中临时输人数组的大小 int a[n]; ✔ //C89 / C90 ❌ 不允许 //C99 ✅ 允许
2.数组访问
数组必须先定义,然后使用。C语言规定只能逐个引用数组元素而不能一次引用整个数组。
下标可以是整型常量或整型表达式。例如:
a[0]=a[5]+a[7]-a[2* 3]
注意:定义数组时用到的“数组名[常量表达式]”和引用数组元素时用到的“[下标]”的区别,例如:
a[0]=a[5]+a[7]-a[2* 3] int a[10]; //定义数组长度为10 t=a[6]; //引用a数组中序号为6的元素。此时6不代表数组长度数组定义时只能用常量,但访问时可以使用变量访问(应以整型为结果访问)
3.数组初始化
对数组元素的初始化可以用以下方法实现。
(1)在定义数组时对数组元素赋予初值。例如:
int a[10]={0,1,2,3,4,5,6,7,8,9};将数组元素的初值依次放在一对花括号内。
(2)可以只给一部分元素赋值。例如:
int a[10]={0,1,2,3,4};定义a数组有10个元素,但花括号内只提供5个初值,这表示只给前面5个元素赋初值,后5个元素值为0
(3)如果想使一个数组中全部元素值为0,可以写成:
int a[10]= {0,0,0,0,0,0,0,0,0,0};或int a[10]={0}; 不能写成int a[10]={0*10};(4)在对全部数组元素赋初值时,由于数据的个数已经确定,因此可以不指定数组长
int a[5]={1,2,3,4,5};可以写成int a[]={1,2,3,4,5};在第二种写法中,花括号中有5个数,系统就会据此自动定义a数组的长度为5。但若数组长度与提供初值的个数不相同,则数组长度不能省略。例如,想定义数组长度为10,就不能省略数组长度的定义,而必须写成:
int a[10]={1,2,3,4,5};
只初始化前5个元素,后5个元素为0。(5)数组定义时,若int a[],没指定数组大小,则需要初始化;数组定义若不进行初始化,则赋随机值。
4.数组存储
数组的存储形式
单一性:数组中的元素,每个元素的数据类型都是一致。
有序性:数组中的元素按照a[0]-a[n]的顺序进行存储。
连续性:数组在内存中选取连续内存空间存储。数组越界:
1.初始化数组时,初始化的元素个数超出了数组本身的元素个数。
2.访问数组元素时,[]中的下标超出了数组元素的访问范围。注意当使用sizeof时数组越界编译器并不会报错,
sizeof是编译时运算符,它计算的是表达式或类型的字节大小,而不会对表达式进行求值(即不会真正执行该表达式)。因此,即使数组长度为 10,使用sizeof(a[10])也不会报错。
5.数组排序(选择、冒泡、插入排序)
1)选择排序
基本思想是:每一趟(如第i趟)在后面n-i+1(i=1,2,...,n-1)个待排序元素中选取关键字最小的元素,作为有序子序列的第i个元素,直到第n-1趟做完,待排序元素只剩下1个,就不用再选。
简单选择排序不断重复地选择待排序元素中最小的一个元素,当前处于第几轮就将该元素与
第几个位置上的元素交换位置,直至所有元素完成排序。例如,第一轮将最小的元素与第一个位置上(arr[0])的元素交换位置,第二轮将第二小的元素与第二个位置上的元素交换位置,不断重复上述过程,直到所有元素都已有序。具体步骤如下:
(1)从待排序序列中,找到关键字最小的元素;
(2)将该元素与待排序序列中的第一个元素交换位置(初始时,有序序列中没有元素);
(3)对于余下的n-1个元素,重复上述两个步骤。
对于给定的待排序关键字序列(25,20,99,16,11,14,22,25,42,17),使用简单选择排序的过程流程如下图所示:
简单选择排序算法实现代码如下:
for(int i=0;i<len-1;i++) { for(int j=i+1;j<len;j++) { if( arr[i]>arr[j]) { tmp=arr[i]; arr[i]=arr[j]; arr[j]=tmp; } } }也可采用如下方式:其中swap为封装交换两数的函数;
由于采用双重循环,故其时间复杂度为O(n*n);空间复杂度没有额外开销,为O(1);同时该算法不具备稳定性,因为其在内层循环中当遇到两数相同时并不会进行替换,而会继续向后比较,当遇到满足条件的值时会进行替换,从而越过中间相同的数,改变了两数的顺序
2)冒泡排序
基本思想:从后往前(或从前往后)两两比较相邻元素的值,若为逆序(A[i-1]>A[i]),则交换它们,直到序列比较完。我们称它为第一趟冒泡,结果是将最小的元素交换到待排序列的第一个位置(或将最大的元素交换到待排序列的最后一个位置),关键字最小的元素如气泡一般逐渐往上“漂浮”至“水面”(或关键字最大的元素如石头一般下沉至水底)。下一趟冒泡时,前一趟确定的最小元素不再参与比较,每趟冒泡的结果是把序列中的最小元素(或最大元素)放到了序列的最终位置,这样最多做n-1趟冒泡就能把所有元素排好序。
流程图如下所示:
冒泡排序算法实现代码如下:
for(int i=0;i<len-1;i++) { for(int j=0;j<len-i-1;j++) { if( arr[j]>arr[j+1]) { tmp=arr[j]; arr[j]=arr[j+1]; arr[j+1]=tmp; } } }书中相比上述代码额外采用了flag标志位用来判断表初始是否有序,并且倒序实现,每次将最小值交换到序列前方,参考上述流程图。
其性能如下:
3)插入排序
基本思想:假设待排序序列存放在数组arr[0,...,n-1]中(temp代表当前待排序元素),算法步骤如n-1个元素无序。排序过程的插入操作主要分为两部分:
(1)将数组中的数据分成有序和无序两个部分。初始状态下,第1个元素(arr[0])有序,后
(2)循环n-1次,依次将后n-1个元素按关键字有序插入有序序列中的合适位置上。
1有序序列中的元素从后往前地与当前待排序的元素比较,寻找其插入位置p;
2将有序序列中位置p及其之后的元素统一后移1位,然后将当前待插入元素插入目标位置
p。需要注意的是,在算法的实现上,一般是在比较的同时移动数据元素。流程图如下:
插入排序算法实现代码如下:
性能如下:
6.折半查找
折半查找也称二分查找,是一种在有序数组中查找某一特定数据元素的查找算法。
基本思想:
(1)从当前表的中间元素开始查找,如果中间元素刚好是要查找的数据元素,则查找成功(初
始时刻的当前表是整个数组)。
(2)如果要查找的元素大于(或小于)中间元素,则在数组大于(或小于)中间元素的那一半
子表中查找,在子表中从步骤(1)重新查找。
(3)如果在当前数组为空,则代表查找失败。
折半查找算法的每一次比较都使查找范围缩小一半,该算法对于待查找的列表有两个要求:
(1)查找表必须支持随机查找,为此必须采用顺序存储结构;
(2)查找表内元素必须按关键字大小有序排列。流程图如下:
折半查找算法实现代码如下:
int low=0,mid=0,high=len-1; while(low<=high) { mid=(low+high)/2; if(num>arr[mid]) low=mid+1; else if(num<arr[mid]) high=mid-1; else { printf("%d\n",arr[mid]); return 0; } } printf("not find\n");此外,因为折半查找需要方便地定位查找区域,所以它要求线性表必须具有随机存取的特性。因此,该查找法仅适合于顺序存储结构,不适合于链式存储结构,且要求元素按关键字有序排列。
二.字符数组
1.字符型数组定义
char 数组名 [常量];
数组名:标识符命名规则
char str[32];--->sizeof(str)--->32字节
2.字符型数组元素访问
数组名[下标]:
str[0]; ---->char
注意:下标避免数组越界。
3.字符型数组初始化
局部初始化:
char str[32] = {'h', 'e', 'l', 'l', 'o'}; char str[32] = {"hello"}; //字符串初始化 char str[32] = "hello"; char str[32] ={0}; //数字0即ASCII为0,而非字符0没有初始化的部分,默认存储0('\0'字符)
全部初始化:
char str[5] ={'h','e','l,'l,'o'}; char str[] = {'h','e','l','l','o'}; ----->5字节 char str[] = {"hello"}; ------>6字节 == char str[] = "hello"; char str[5]="good!"; 5 字节 "good!" 无结束符(越界) ❌ 错误
当以字符串初始化时,[]常量被省略时,将来数组空间按照初始化后的字符个数去确定,额外增加‘0'字符的存储位置
4.字符型数组输入输出
输入:
1.scanf
scanf("%s",str); 只能输入不带空格,tab与enter的字符,否则停止2.gets()
输出:
1.以遍历的方式输出
for (i=0;str[i]!='\0';++i) { printf("%c", str[i]); }2.以字符串方式输出
printf("%s\n",str);3.puts输出
puts(str);//自动换行
5.字符型数组相关函数
strlen(str):计算字符串的长度(字符型数组中有效字符个数,不包含'\0')
其一般形式为:
strlen(字符数组)。它是测试字符串长度的函数。函数的值为字符串中的实际长度(不包括'\0'在内)。例如:char str[10]={"China"}; printf("%d",strlen(str));输出结果不是10,也不是6,而是5。
sizeof(str):求字符型数组在内存中所占的字节数(和空间开辟时大小有关)。
char str1[]={"hol"}; printf("%ld\n",strlen(str1)); //3 printf("%ld\n",sizeof(str1)); //4 注意,strlen只能读取以\0为结尾的字符串,若定义char str1[]={'q','t'}此类,则不会输出2,而是 会继续向t后面的内存查找,直到找到以\0为结尾的字符位置;而若定义char str1[]={'q','t','\0'}则会 输出2。
strcpy(str1,str2):字符串拷贝
其一般形式为:strcpy(字符数组1,字符串2)
strcpy是stringcopy(字符串复制)的简写。它是“字符串复制函数”,作用是将字符串2复制到字符数组1中去。注意:
(1)字符数组1必须定义得足够大,以便容纳被复制的宇符串。字符数组1的长度不应小于字符串2的长度。
(2)“字符数组1”必须写成数组名形式(如strl),“字符串2”可以是字符数组名,也可以是一个字符串常量。例如:strcpy(strl,"China")。
(3)如果在复制前未对strl数组赋值,则strl各字节中的内容是无法预知的,复制时将str2中的字符串和其后的’0'一起复制到字符数组1中,取代字符数组1中的前面字符,最后几个字符并不一定是斜杠零,而是strl中原有的字节的内容。strncpy(str1,str2,n)将str2的前n个字符复制到str1中替换,要注意替换的字符长度不应超过str1的大小。
strcat(str1,str2):字符串拼接
其一般形式为:strcat(字符数组1,字符数组2)
strcat是String Catenate(字符串连接)的缩写。其作用是连接两个字符数组中的字符串,把字符串2接到字符串1的后面,结果放在字符数组1中,函数调用后得到一个函数值即字符数组1的地址。strncat(str1,str2,n)将str2的前n个字符拼接到str1末尾。
strcmp(str1,str2):字符串比较
返回int型数据,若str1>str2,返回大于0的值;若str1==str2,返回0;;若str1<str2,返回小于零的值。
同样的,strncmp(str1,str2,n)比较str1和str2前n个字符大小,返回结果同上
6.数字在字符串中存储
char str[] = "12345"; int num = str; // 错误:str 是地址(指针),不是数值 int sum = str[0] + str[1]; // 结果是 49 + 50 = 99(不是 1+2=3)“12345" 存的是 49,50,51,52,53 的二进制值;只有当用
%s或调用strlen时,编译器才会按照 ASCII 标准把这串字节"翻译"成眼中的数字图案;而数学上的整数 12345,在底层是完全不同的另一串二进制码(0x3039,即十进制的 12345)。两者在物理层面不是同一个东西。既然底层存的是 ASCII 码,那 C 语言是如何实现"把字符串转为整数"的呢?这正是标准库函数
atoi("12345")或scanf在底层做的事:
读取
'1'(49),减去'0'(48),得到整数1。计算:
1 * 10 + ( '2' - 48 )=12。计算:
12 * 10 + ( '3' - 48 )=123。如此循环,直到遇到
\0停止,最后在内存中生成一个新的二进制数0x3039(即十进制的 12345)。
三.二维数组的定义与引用
1.二维数组定义
二维数组定义的一般形式为:
类型说明符 数组名[常量表达式][常量表达式];
例如:float a[3][4];定义a为3X4(3行4列)的数组。
注意,不能写成:float a[3,4];
C语言对二维数组采用这样的定义方式,使得二维数组可被看作是一种特殊的一维数组:它的元素又是一个一维数组。
2.二维数组初始化
1.全部初始化
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};注意,在二维数组有初始化值的情况下,可以省略行号,但不能省略列号;当没赋初值时,行号与列号都应明确。
2.局部初始化
1)可以对部分元素赋初值。例如:
int a[3][4]={{1},{5},{9}};它的作用是只对各行第1列(即序号为0的列)的元素赋初值,其余元素值自动为0。
2)在定义时也可以只对部分元素赋初值而省略第一维的长度,但应分行赋初值。例如:
int a[][4]={{0,0,3},{},{0,10}};表示三行四列的数组,没有赋值的数均为0
3)当初始化未确定行号时,会根据数值位自动计算行号,例如:
int a[][4]={1,2,3,4,5,6,7,8,9};会被默认为三行四列,且第三行为元素为9 0 0 0。
3.初始化为0
1)全部初始化为0
int a[3][4]={0};2)
int a[][4]={0};//默认为一行四列的数组
3.二维数组访问
int x=sizeof(arr)/sizeof(arr[0]); //行号
int y=sizeof(arr[0])/sizeof(arr[0][0]); //列号
for(int i=0;i<x;i++)
{
for(int j=0;j<y;j++)
{
printf("%d ",arr[i][j]);
}
printf("\n");
}
4.二维字符型数组
二维字符型数组用于保存多个字符串,可全部初始化也可局部初始化,未赋值的字节空间默认为'\0'
char str[3][128]={"hello","world"};可使用二维字符型数组配合字符型数组相关函数实现比较多个字符串的大小,如:
char str[3][128]={0}; for(int i=0;i<3;i++) { gets(str[i]); } char max[128]={0}; strcpy(max,str[0]); for(int i=1;i<4;i++) { if(strcmp(str[i],max)>0) strcpy(max,str[i]); } puts(max);











3948

被折叠的 条评论
为什么被折叠?



