目录
1.数组的基本用法
1.1 概念
数组(Array)是有序元素序列
若将有限个类型相同的变量的集合命名,那么这个名称为数组名。
重点必须是类型相同和连续的变量集合
1.2 定义格式
存储类型 数据类型 数组名[元素的个数];
例如:(auto) int a[5];
数组名:代表数组的首地址,也就是第一个元素的地址。a是地址常量,不能为左值,不能被赋值。
数组定义方法:
(1)数组名定义规则跟变量名相同,遵循标识符定义规则
(2)数组名后使用方括号括起来的常量表达式表示元素个数。
(3)常量表达式中可以包含常量和符号常量(宏定义),不能包含变量。
1.3 访问元素
数组名[下标];
注意:数组元素下标从0开始
例如:
访问第一个元素:a[0];
访问第n个元素:a[n-1];
注意数组越界问题:
特点:
数据元素的数据类型相同
内存连续
注意:
1). 数组的数据类型就是数据元素的数据类型
2). 数组名要符合标识符的命名规则
3).在同一个函数中,数组名不要与变量名相同
4). 下标从0开始的,到n-1结束
2.一维数组
2.1 一维数组的概念
只有一个下标的数组。
定义格式:存储类型 数据类型 数组名[元素个数];
访问元素:数组名[下标], 下标从0开始。
数组名:数组首地址,也就是第一个元素的地址。
2.2 初始化
int a[5] = {1, 2, 3, 4, 5};
printf("%d\n", a[0]);
printf("%d\n", a[1]);
printf("%d\n", a[2]);
printf("%d\n", a[3]);
printf("%d\n", a[4]);
(2)定义时部分初始化,未初始化的元素值自动为0
int a[5] = {1, 2}; //1 2 0 0 0
printf("%d\n", a[0]); //1
printf("%d\n", a[1]); //2
printf("%d\n", a[2]); //0
printf("%d\n", a[3]); //0
printf("%d\n", a[4]); //0
int a[5];
a[0] = 1;
a[1] = 2;
a[2] = 3;
a[3] = 4;
a[4] = 5;
for(int i=0; i<5;i++) //i的值循环为: 0 1 2 3 4 5(到5不满足循环条件出循环)
printf("%d\n",a[i]); //循环打印:a[0] a[1] a[2] a[3] a[4] a[5]
注意数组越界问题:
int a[3] = {4,3,2,1}; //错误,数组越界
a[3] =10; //错误,数组越界了
2.3 定义空数组
(1)int a[5] = {0,0,0,0,0};
(2)int a[5] = {0};
(3)int a[5] = {};
2.4 数组引用
(1)先定义后引用
(2)每次只能引用一个数组元素a[i], 如果想引用所有元素可以循环遍历
(3)打印数组元素地址用%p格式
2.5 数组的大小
int a[5]; //4*5 = 20
double b[3]; //8*3 = 24
char c[32]; //32
计算方式:
(1)手算:数元素个数*数据类型大小
(2)用sizeof: sizeof(数组名);
数组元素的个数 = sizeof(数组名) / 元素大小;
2.6 清零函数
2.6.1 bzero() 函数
#include <strings.h>
void bzero(void *s, size_t n);
功能:将内存空间设置为0
参数: s: 要清空的空间首地址
n: 字节大小
返回值:无
2.7.2 memset()函数
#include <string.h>
void *memset(void *s, int c, size_t n);
功能:将指定内存空间内容设置为0
参数:s:指定内存首地址
c:要设置的数,一般为0
n:要设置的内存大小
返回值:无
2.7 数组的内存分配
int a[5];
a是数组名,也是数组的首地址也就是第一个元素的地址,即&a[0]。
a+1 是数组第二个元素的地址,即&a[1]。
数组再内存中分配如下图:
3.字符数组
因为C语言没有字符串类型,所以可以用字符数组的形式表示字符串。
概念:类型为字符的数组,也就是数组中每个元素是字符型。可以组成字符串。
3.1 表示形式
(1)单个字符表示:
char s[5] = { 'h', 'e', 'l', 'l', 'o'}; //sizeof(5) = 5;
char s1[] = { 'h', 'e', 'l', 'l', 'o'}; //sizeof(s1) = 5; //定义数组的同时初始化可以省略元素个数,元素个数取决于你赋值的元素个数。
(2)用字符串表示,结尾会自动添加上'\0'。
char s2[] = "hello"; //sizeof(s2) = 6
char s3[] = {"hello"}; //sizeof(s3) = 6
char s4[6] ={"hello"}; //sizeof(s4) = 6
char s5[6] ="hello"; //sizeof(s5) = 6 //常用
char s6[] ={}; //错误,如果定义时不赋值则不能省略元素个数
初始化和其他类型数组相同
3.2 输入和输出
3.2.1 输入
(1)scanf:
char s[32] = {};
scanf("%s", s);
printf("%s\n", s);
不能输入空格,如果有空格,空格后面的内容不会存入数组。
例如:以下程序输入hello world只能存入hello
如果需要输入空格则:
har s[32] = {};
scanf("%[^\n]", s); //除了\n以外所有字符都可以输入到s地址下空间内保存,遇到\n就结束
printf("%s\n", s);
输入字符串会在后面加上'\0'
(2)用for遍历输入
char s[6] = "";
for (int i = 0; i < 6; i++)
scanf("%c", &s[i]);
(3)gets()
#include <stdio.h>
char *gets(char *s);
功能:从终端获取字符串输入
参数:s:目标字符串首地址
返回值:目标字符串首地址
注意:没有数组越界检查,使用时会报警告。
3.2.2 输出
char s[32]="hello"
printf("%s\n",s);
(2)for循环
char s[32] = "hello";
for (int i = 0; i < 32; i++)
printf("%c", s[i]);
(3)puts()函数
int puts(const char *s)
功能:向终端打印字符串
参数:打印的字符串首地址
返回值:字符串长度
char s[32] = "";
gets(s); //在结尾加个\0,不会接收到回车
puts(s); //在结尾自动加个回车
3.3. 计算字符串长度
char s[32]="hello"; //sizeof(s)=23
3.3.1 用循环统计
char s[32] = "hello";
int i;
// int len = 0;
// for (i = 0; s[i] != '\0'; i++)
// len++;
// printf("%d\n", len); //5
i = 0;
while (s[i] != '\0')
{
i++;
}
printf("%d\n", i); //5
3.3.2 用strlen()
#include <string.h>
size_t strlen(const char *s);
功能:计算字符串长度
参数:字符串的首地址
返回值:返回字符串实际长度,不包括'\0'在内。
char s[32] = "hello";
int num = strlen(s);
printf("%d\n", num); //5
3.3.3 sizeof和strlen的区别
(1) sizeof是关键字,strlen是函数。
(2)sizeof是计算数据所占空间大小,strlen计算字符串的实际长度。
(3)sizeof计算包括\0,strlen计算不包括\0;计算字符串长度时(元素个数省略情况下),sizeof比strlen大1。
4.排序
4.1 冒泡排序
4.1.1 排序过程
(1)比较第一个数与第二个数,若为逆序a[0]>a[1],则交换;然后比较第二个数与第三个数;依次类推,直至第n-1个数和第n个数比较为止,第一趟冒泡排序,结果最大的数被安置在最后一个元素位置上。
(2)对前n-1个数进行第二趟冒泡排序,结果使次大的数被安置在第n-1个元素位置
(3)重复上述过程,共经过n-1趟冒泡排序后,排序结束
4.1.2 原理
第一遍每一个数都会被比较到,总会排到最大的数,那么最大的数肯定会排到最后面,因为只要比前面数大就会排到后面。后面排序同理。
两两比较,第i个和i+1个比较
例如:int a[5]={5,4,3,2,1};
第一轮 | 第二轮 | 第三轮 | 第四轮 |
45321 | 34215 | 23145 | 12345 |
43521 | 32415 | 21345 | |
43251 | 32145 | ||
43215 |
i=0: 第一轮:5-1=4次排序
i=1: 第二轮:5-2=3次排序
i=2: 第三轮:5-3=2次排序
i=3: 第四轮:5-4=1次排序
外层轮数 = 个数 - 1 = N - 1 (i=0;i<N-1;i++)
每轮排序次数= 个数-轮数 (=N-i-1)
int a[5]={5,4,3,2,1};
for(i) //外层循环轮数
{
for(j) //内层循环比较的次数
{
if(a[j]>a[j+1])
{
//交换
}
}
}
4.1.3 编程实现
#define N 5
int a[N] = {5, 4, 3, 2, 1};
int main()
{
int t = 0, i, j;
for (i = 0; i < N - 1; i++) //i<4 i=4
{
for (j = 0; j < N - i - 1; j++) //j<1 j=1
{
if (a[j] > a[j + 1]) //1 2 3 4 5
{
t = a[j];
a[j] = a[j + 1];
a[j + 1] = t;
}
}
}
for (i = 0; i < N; i++)
printf("%d ", a[i]);
printf("\n");
}
或者
#include <stdio.h>
#define N 5
int a[N] = {5, 4, 3, 2, 1};
int main()
{
int t = 0, i, j;
for (i = 1; i < N ; i++) //i<4 i=4
{
for (j = 0; j < N - i; j++) //j<1 j=1
{
if (a[j] > a[j + 1]) //1 2 3 4 5
{
t = a[j];
a[j] = a[j + 1];
a[j + 1] = t;
}
}
}
for (i = 0; i < N; i++)
printf("%d ", a[i]);
printf("\n");
}
4.2 选择排序
4.2.1 排序过程
(1)首先通过n-1次比较,从n个数中找出最小的, 将它与第一个数交换—第一趟选择排序,结果最小的数被安置在第一个元素位置上。
(2)再通过n-2次比较,从剩余的n-1个数中找出关键字次小的记录,将它与第二个数交换—第二趟选择排序
(3)重复上述过程,共经过n-1趟排序后,排序结束
4.2.2 原理
第一次排序存取最小值,然后与第一个元素做交换,以此类推。
轮数i:个数-1 = N-1 (同冒泡排序)
每轮比较次数:从a[i]和它后一个a[i+1]开始比较,一直到最后一个a[N-1]
(只要有更小的就记录其下标)
4.2.3 编程实现
#include <stdio.h>
#define N 6
int a[N] = {100, 34, 56, 78, 99, 2}; //2 34 56 78 99 100
int main()
{
int i, j, t;
int min;
for (i = 0; i < N - 1; i++) //循环轮数 i=2
{
min = i; //min=1
for (j = i + 1; j < N; j++) //通过每轮比较的次数把最小下标找出来保存到min里面
{
if (a[min] > a[j])
min = j; // 记录最小元素的下标
}
if (a[min] != a[i]) //通过把最小数排到前面
{
t = a[min];
a[min] = a[i];
a[i] = t;
}
}
for (i = 0; i < N; i++)
printf("%d ", a[i]);
printf("\n");
}
或者
#include <stdio.h>
#define N 6
int a[N] = {100, 34, 4, 78, 99, 2};
int main()
{
int i, j, t;
for (i = 0; i < N - 1; i++)
{
for (j = i + 1; j < N; j++)
{
if (a[i] > a[j]) //只要遇到更小元素就交换,排完这轮之后找到的最小数排到了前面
{
t = a[j];
a[j] = a[i];
a[i] = t;
}
}
}
for (i = 0; i < N; i++)
printf("%d ", a[i]);
printf("\n");
}
- 二维数组
下标是两个的数组,下标分别是行下标和列下标。
5.1 格式
存储类型 数据类型 数组名[行数][列数];
int a[2][3];
5.2 访问元素
数组名[行下标][列下标]; (下标都是从0开始)
a[0][0] : 第一行第一列的元素
a[i-1][j-1]: 最后一行最后一列的元素
注意:
- 行下标和列下标都不能越界。
- 行数可以省略,列数不能省略。
- 数组名代表的是第一行的地址
5.3 二维数组数组名
二维数组数组名代表第一行地址:
例如: int a[][3] ={1,2,3,4,5,6};
a: 第一行的地址
a+1: 第二行的地址
5.4 数组元素个数和二维数组大小
元素个数:行数*列数
二维数组大小:
- 数据类型大小*行数*列数
- sizeof(数组名);
5.5 初始化
- 定义时全部初始化
int a[2][3] = {1,2,3,4,5,6}; //顺序赋值
int a1[2][3] = {{1,2,3},{4,5,6}}; //按行赋值
- 定义时部分初始化, 未初始化元素值为0
int a2[2][3] = {1,2,3,4}; //顺序赋值 1 2 3 4 0 0
int a3[2][3] ={{1,2},{3,4}}; //按行赋值 1 2 0 3 4 0
- 未初始化
随机值,需要单独赋值 a[i][j]=
5.6 遍历二维数组
循环嵌套,外层循环行数,内存循环列数。
int a[M][N]={};
for(i=0;i<M;i++) //行下标循环
{
for(j=0;j<N;j++) //列下标
{
scanf/prinf
}
}
5.7 内存分配
- 行地址
1、二维数组中,数组名a本质是第一行地址,其数值还是数组a首元素a[0][0]的地址,即&a[0][0],第一行第一个元素的地址;
2、二维数组中,数组名a+1本质是第二行地址,其数值还是数组a的元素a[1][0]的地址,即&a[1][0],第二行第一个元素的地址;
也可以在行地址前面加*表示将行地址降级成为列地址
- 列地址
1、二维数组中,a[0]的值,即该数组的首元素a[0][0]的地址,即&a[0][0];
2、二维数组中,a[0]+1的值,是数组元素a[0][1]的值,即&a[0][1];
- 总结:二维数组i行j列表示元素和地址的方式
地址:
&a[i][j]
a[i]+1
*(a+i)+j
内容:
a[i][j]
*(a[i]+j)
*(*(a+i)+j)