1、数组
1.1 一维数组
1.1.1 定义
数组是内存中连续存储的有着相同类型的一组数据的集合。
1.1.2 定义形式
类型说明符 数组名[常量表达式] ; 如int a[5];
int型变量占4个字节的内存空间,那么5个int型变量就占4*5=20个字节的内存空间。
说明:
1)一个数组a[n],这个数组中元素最大的下标是n-1;而元素a[i]表示数组a中第i+1个元素。
2)[常量表达式],这个可以是“数字常量表达式”,或者是“符号常量表达式”,必须是常量,不能是变量,如果非要,那么用动态定义的方式。
1.1.3 初始化
1)完全初始化
int a[5] = {1, 2, 3, 4, 5};
初始化各元素使逗号隔开的,不是用分号。
当完全初始化时,[]里面就不用指定数组的长度,因为此时元素的个数已经确定了。
int a[] = {1, 2, 3, 4, 5};
编程可以使用这种,方便。
2)部分初始化
int a[5] = {1, 2};
没有被初始化的元素补0。
1.1.4 数组元素的引用
只有在定义数组的同时才可以整体赋值,其他情况下都不能整体赋值,因为除此之外程序中任何地方看到“a[常量]”都不是数组,都只是数组的一个元素、一个变量。
1.1.5 如何将数组a赋给数组b
b=a;
a、b都是数组名,数组名都是地址,一个常数,是不能相互赋值。
应该用for循环,逐个赋值。
1.1.6 获取数组长度
使用关键字sizeof,能获取某个变量或某种数据类型所占字节数,数组同理。
sizeof(a)/sizeof(a[0])
// 总的字节数除以一个元素所占的字节数就是数组的长度
1.1.6 宏定义
定义:用#define定义一个标识符来表示一个常量
定义形式: #define 标识符 常量
//注意,最后没有分号
特点:定义的标识符不占内存,只是一个临时的符号,预编译后这个符号就不存在了
说明:
1)宏所表示的常量可以是数字、字符、字符串、表达式。其中最常用的是数字
2)#define的作用域为自#define那一行起到源程序结束。如果要终止其作用域可以使用**#undef**命令
1.2 二维数组
1.2.1 定义:
类型说明符 数组名[常量表达式][常量表达式];
如:int a[3][4];
说明:
1)与一维数组一样,行序号和列序号的下标都是从0开始的。
2)二维数组是不存在的,内存是不分行,不分列的,元素都按顺序一个个往后排。因此二维数组本质是一维数组。
3)a[0]、a[2]、a[3]分别是这三个一维数组的数组名
1.2.2 初始化
第一维的长度可以不指定,但第二维的长度不能省。(二维数组第二维不能省)
int arr[3][3]={1,2,3,4,5,6,7,8,9};
int arr[3][3]={{1,2,3},{4,5,6},{7,8,9}};
2. 数组和指针
2.1 一维数组和指针
定义:
1)数组中任意元素地址赋给指针
int a[10]; /*定义a为包含10个整型数据的数组*/
int *p; /*定义p为指向整型变量的指针*/
p=&a[i];
2)将数组首地址(第一个元素地址)赋给指针
p=&a[0];
p=a;//两个等价
说明:
p、a、&a[0]均代表同一存储单元的地址,它们是数组a的首地址,也是下标为0的数组元素的地址。应注意的是:p是变量,而a、&a[0]都是常量,因此不能赋值。
int array[5],i; //那么,下面的两条语句都**是错误的**:
array=&i;array++;//左值要求是变量
int *p =array, *q; q=array+3 //这是可以的,赋值不能,但是地址为一个常数,可以加一个整数,p++是可以的
//等价与下面两条
q=array[3];
q = p+3;
2.1.1 数组元素的引用
1)&array[i] 等价 p+i 等价array+i(i是个整数),表示第i个元素的地址,可得出
*(p+i)和*(array+i)就是array[i]。
2)p[i]与*(p+i)是等价的
因此有四种表达方式::①array[i];②*(array+i);③*(p+i);④p[i]。 ①④形式一样,②③形式一样
说明:对应前面指针章节讲的,p++,如p刚开始指向a[3],p++实际就是a[4],移动了一个int,一个步长,四个字节。
2.2 二维数组和指针
2.2.1 二维数组的组成
int a[3][4]={{0,1,2,3},{4,5,6,7},{8,9,10,11}};
从下图第一行来看,3行4列的二维数组a,是由3个一维数组按顺序组成的。
3个一维数组名分别是a[0]、a[1]、a[2],也都是地址常量。这3个数组又都是包含4个整型元素的一维数组。
如数组a[0]包含了a[0][0]、a[0] [1]、a[0][2]和a[0][3]
2.2.2 二维数组的地址
二维一般取元素地址是&a[i] [j]
1)从一维角度取地址
通过一维数组名a[i]加上整数j来实现,即a[i]+j就是a[i][j]的地址
得出第一个等价关系:
①&a[i][j]
②a[i]+j
2)再推导
在一维中,a[i]是与*(a+i)等价的,因此a[i][j]等价于*(a+i)+j
。
3)从元素个数角度,再再推导
a[i][j]与a[0][0]相差4*i+j个元素(图看出的),利用“移动指针”(还没定义,所以引号,但是可以当做)表示:
&a[0][0]+4*i+j;
a[0]+4*i+j
2.2.3 取值方式
① *(&a[i][j])
② *(a[i]+j)
③ *(*(a+i)+j)
④ *(&a[0][0]+4*i+j)
⑤ *(a[0]+4*i+j)
2.2.4 指向二维数组的指针变量
定义:数组指针指向具有n个元素的一维数组(数组指针(指向数组的指针)指向二维数组)
类型标识符 (*指针变量名)[长度]
如:
int a[3][4], (*p)[4],p=a; //指针第二维也不能少
说明:
1)p指向a[0],那么p+1,则指向一维数组a[1];p+2则指向一维数组a[2]。
2)定义p为指向二维数组的指针变量。它表示p是指针变量,又称为行指针,它指向具有4个整型元素的一维数组(指向由四个元素构成的一维数组)。
3)装的是一维数组,且是4个int型的
① p[i][j]
② *(*(p+i)+j)
③ *(p[i]+j)
④ (*(p+i))[j]
3、指针数组
定义:一个数组的元素值为指针则是指针数组。(装的是地址)
要求: 指针数组的所有元素都必须是具有相同存储类型(自动的(auto)、静态的(static)、寄存器(register)、外部的(exteren))和指向相同数据类型的指针变量。
1)定义形式:
数据类型 *指针数组名[元素个数] //与上面二维数组的指针少了个括号
int *pa[2];表示定义了一个指针数组pa,它由指向int型数据的pa[0]和pa[1]两个指针元素组成。
2)应用:二维数组和指针数组
二维数组a[2][3]可分解为a[0]和a[1]这两个一维数组,它们各有3个元素。指针数组pa由两个指针pa[0]和pa[1]组成。可以把一维数组a[0]和a[1]的首地址分别赋予指针pa[0]和pa[1]
int a[2][3] ,*pa[2];
a[0]=a[0];
或pa[0]=&a[0][0];
pa[1]=a[1];
或pa[1]=&a[1][0];
在程序中a[i][j]、(a[i]+j)、(pa[i]+j) 、pa[i][j]
3)指针数组和数组指针变量的区别
联系:都可用来表示二维数组,但是意义和方法不同。
区别:
int (*p)[3]; //数组指针
表示一个指向二维数组的指针变量。该二维数组的列数为3或分解后一维数组长度为3。
int *p[3]; //指针数组,由指针构成的一个数组
表示p是一个指针数组,有三个数组元素p[0]、p[1]、p[2],而且均为指针变量。
4、数组常见的算法
4.1 倒置算法
首尾交换
#include "stdio.h"
//数组首尾交换
main()
{
int a[10],i,j,t;
for(i=0;i<10;i++) /*用for循环给数组输入10个数*/
scanf("%d",&a[i]);
for(i=0,j=9;i<j;i++,j--) /*循环实现逆置*/
{
t=a[i];
a[i]=a[j];
a[j]=t;
}
for(i=0;i<10;i++) /*用for循环输出排序后的10个数组元素*/
printf("%d",a[i]);
putchar('n');
}
4.2 折半查找
要求:已经排好序
特点:相比链表,一般数组效率更高。
# include <stdio.h>
int main(void)
{
int a[] = {13, 45, 78, 90, 127, 189, 243, 355};
int key; //存放要查找的数
int low = 0;
int high = sizeof(a)/sizeof(a[0]) -1;
int mid;
int flag = 0; //标志位,用于判断是否存在要找的数
printf("请输入您想查找的数:");
scanf("%d", &key);
while ((low <= high))
{
mid = (low + high) / 2;
if (key < a[mid])
{
high = mid -1;
}
else if (a[mid] < key)
{
low = mid +1;
}
else
{
printf("下标 = %d\n", mid);
flag = 1;
break;
}
}
if (0 == flag)
{
printf("sorry, data is not found\n");
}
return 0;
}
4.3 数组插入、删除
数组不擅长插入和删除。数组的优点在于它是连续的,所以查找数据速度很快。但这也是它的一个缺点。正因为它是连续的,所以当插入一个元素时,插入点后所有的元素全部都要向后移;而删除一个元素时,删除点后所有的元素全部都要向前移
1)插入
# include <stdio.h>
int main(void)
{
int a[23] = {1, 5, 66, 8, 55, 9, 1, 32, 5, 65, 4, 8, 5, 15, 64, 156, 1564,15, 1, 8, 9, 7, 215};
int b[24]; //用来存放插入数字后的新数组,因为又插入了一个值,所以长度为24
int Index; //插入值的下标,Index是“下标”的英文单词
int num; //插入的值
int i; //循环变量
printf("请输入插入值的下标:");
scanf("%d", &Index);
printf("请输入插入的数值:");
scanf("%d", &num);
for (i=0; i<24; ++i)
{
if (i < Index)
{
b[i] = a[i]; /*循环变量i小于插入值位置Index时,每一个元素所放的位置
不变*/
}
else if (i == Index)
{
b[i] = num; //i等于Index时,将插入值赋给数组b
}
else
{
b[i] = a[i-1]; /*因为插入了一个新的元素,所以插入位置后的每一个元素所
存放的位置都要向后移一位*/
}
}
for (i=0; i<24; ++i)
{
printf("%d\x20", b[i]);
}
printf("\n");
return 0;
}
2)删除
#include <stdio.h>
int main(void)
{
int a[23] = {1, 5, 66, 8, 55, 9, 1, 32, 5, 65, 4, 8, 5, 15, 64, 156, 1564,
15, 1, 8, 9, 7, 215};
int b[22]; /*用来存放删除数字后的新数组,因为删除了一个值,所以长度为22*/
int Index; //要删除的值的下标
int i; //循环变量
printf("请输入要删除的值的下标:");
scanf("%d", &Index);
for (i=0; i<23; ++i)
{
if (i < Index)
{
b[i] = a[i]; /*循环变量i小于插入值位置Index时,每一个元素所存放的位
置不变*/
}
else
{
b[i] = a[i+1]; /*删除值后面的元素都往前移一位,要删除的值直接被覆盖*/
}
}
for (i=0; i<22; ++i)
{
printf("%d\x20", b[i]); // \x20表示空格
}
printf("\n");
return 0;
}
4.4 排序
1)冒泡排序
2)插入排序
3)选择排序
4)快速排序