1、一维数组
创建与初始化
数组是一组相同类型元素的集合
数组的创建方式:
type_t arr_name [const_n];
//type_t 是指数组的元素类型
//const_n 是一个常量表达式,用来指定数组的大小
数组的初始化:
int arr[10];//不初始化,内容为随机值
int a;//局部变量未初始化,内容也是随机值
//全局变量,静态变量(static int b)则默认是0
打印上面的值时,可能无法打印(arr成功,a失败)
int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };//完全初始化
int arr2[10] = { 1,2,3,4 };//不完全初始化,其余元素默认为0
//但不能多放
//数组初始化时可以不指定大小,程序会根据初始化的内容确定数组大小:
int arr3[] = { 1,2,3,4 };
创建数组能不能用变量?
int n = 10;
int arr[n];//C99中引入了变长数组的概念,允许数组的大小用变量来指定,
//如果编译器不支持C99中的变长数组,那就不能使用,VS2022不支持
变长数组可以使数组可大可小:(动态内存管理也可以实现)
int n = 10;
scanf("%d", &n);
int arr[n];
C++(.cpp文件)中const修饰的常量就叫常量,C(.c文件)中为常变量
char ch1[] = { 'a', 98, 'c' };//b的ASCII码值可以直接填入代表b
char ch2[] = { 'a', 'b', 'c' };//放入3个字符 a b c
char ch3[5] = { 'a', 'b', 'c' };//多放2个\0
char ch4[] = "abc";//放入4个字符 a b c \0
char ch5[5] = "abc";//多放1个\0
错误:四个字符放在三个字符的数组中
char arr[3] = "abc";//还有一个\0
编译器有时不能报出所有的错误,所以不报错不代表没有错
改正:
char arr[4] = "abc";
使用
下标引用操作符:[]
int arr[100] = { 1,2,3,4,5,6 };
printf("%d\n", arr[4]);//下标引用操作符有两个操作数:arr(数组名)、4(下标)
printf("%d\n", sizeof(arr));//打印400,因为100个整型元素
printf("%d\n", sizeof(arr[0]));//1个字符4字节
int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数的写法
//写代码来赋值1~100
int i = 0;
//赋值
for (i = 0; i < 100; i++)//100建议替换成sz
{
arr[i] = i + 1;
}
//打印
for (i = 0; i < 100; i++)//100建议替换成sz
{
printf("%d ", arr[i]);
}
//可以把赋值打印合成一个
//一维数组在内存中的存储
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
//打印数组的每个元素的地址
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for(i = 0; i < sz; i++)
{
printf("&arr[%d] = %p\n", i, &arr[i]);
}
return 0;
}
发现:两元素之间的地址差4,因为1个整型元素的大小是4个字节
说明:
- 一维数组在内存中是连续存放的
- 随着数组下标的增长,地址是由低到高变化的
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
int* p = &arr[0];
for(i = 0; i < sz; i++)
{
printf("&arr[%d] = %p <===> %p\n", i, &arr[i], p+i);
}
return 0;
}
左&arr[i] 右p+i
第一个元素时,i=0
第二个元素时,i=1,p+1相当于跳了4个字节,因为p是一个整型指针,+1应该跳过一个整型(字节)
char* p,p+1相当于跳过一个字符,跳了1个字节
2、二维数组
创建
int arr[3][4];//3行4列的整型数据
char arr[3][5];
double arr[2][4];
初始化
行可以省略,列不能省略:
//完全初始化
int arr[3][5] = { {1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15} };
int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };//挨个放
//不完全初始化,未被初始化元素默认初始化为0
int arr[3][5] = { {1,2,3},{4,5,6},{7,8,9} };
int arr[3][5] = { 1,2,3,4,5,6,7,8,9 };
//省略行数
int arr[][5] = { {1,2,3},{4,5,6},{7,8,9} };//自动补为3行
int arr[][5] = { 1,2,3,4,5,6,7,8,9 };//自动补为2行
//省略列数(和行数)
int arr[3][] = { 1,2,3,4,5,6,7,8,9 };//错误
int arr[][] = { 1,2,3,4,5,6,7,8,9 };//错误
使用
行和列的下标都从0开始
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[][5] = { {1,2},{3,4},{5,6} };
int i = 0;
for (i = 0; i < 3; i++)//行
//或i < sizeof(arr) / sizeof(arr[0]),整个数组大小 / 数组第一行大小
{
int j = 0;
for (j = 0; j < 5; j++)//列
//或j < sizeof(arr[0]) / sizeof(arr[0][0]),数组第一行大小 / 第一行第一个元素大小
{
printf("%d ", arr[i][j]);//打印一行
}
printf("\n");//换行
}
return 0;
}
内存中的存储
二维数组在内存中也是连续存放的
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[3][5] = { {1,2},{3,4},{5,6} };
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("&arr[%d][%d] = %p\n", i, j, &arr[i][j]);//打印地址
}
}
return 0;
}
C语言支持多维数组,多维数组可以理解为一维数组的数组(的数组…),常用一维、二维数组
可以理解为:第一行数组名为arr[0],第二行数组名为arr[1],第三行数组名为arr[2]
三维数组
int arr[3][4][5];//三个维度,类似长宽高,第一维范围0~2,第二维范围0~3,第三维范围0~4
3、数组越界
数组的下标是有范围限制的,如果超出下标范围,会出错,但是编译器不一定报错,所以最好自己做越界的检查
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int i = 0;
for (i = 0; i <= 10; i++)
{
printf("%d\n", arr[i]);//当i=10时,越界访问了,打印出的arr[10]数值是无意义的
}
return 0;
}
二维数组的行和列也可能存在越界
4、数组传参
不管是二维还是一维数组,在传参的时候,都不会去创建数组,所以数组的大小可以不用明确指定
一维数组传参的时候,形参的数组大小可以省略
二维数组传参的时候,形参的数组中,行可以省略,列不能省略
排序算法:冒泡、选择、插入、快速
将数组作为参数传给函数,比如:我要实现一个冒泡排序(这里要讲算法思想)函数
冒泡排序函数的错误设计
数组中2个相邻的元素进行比较,如果不满足顺序就交换,最终排成升序
每一趟冒泡排序让一个数字出现在它应该出现的位置上,所以n个数字需要n-1趟冒泡排序
例如:9 8 7 6 5 4 3 2 1 0
10个数字,要进行9趟冒泡排序(搞定9个数字之后,最后1个数字必然在应在位置)
第一趟****冒泡排序:9对比较(10个需比较数字)
第一次比较(第一个和第二个比):8 9 7 6 5 4 3 2 1 0
第二次比较(第二个和第三个比):8 7 9 6 5 4 3 2 1 0
第三次比较(第三个和第四个比):8 7 6 9 5 4 3 2 1 0
…
第九次比较(第九个和第十个比):8 7 6 5 4 3 2 1 0 9
第二趟****冒泡排序:8对比较(9个需比较数字)
第一次比较(第一个和第二个比):7 8 6 5 4 3 2 1 0 9
…
第八次比较(第八个和第九个比):7 6 5 4 3 2 1 0 8 9
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void bubble_sort(int arr[])//int arr[]本质上是int* arr,是个指针,不是数组
//数组传参写成数组,更易理解,只是形式而已,可以写成int* arr,但是不能写成int arr
{
int i = 0;//趟数,与元素个数有关系,等于元素个数减一
int sz = sizeof(arr) / sizeof(arr[0]);//4/4 = 0
for (i = 0; i < sz - 1; i++)
{
//每一趟冒泡排序过程
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
//交换
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
//写一个冒泡排序的函数,来排序arr数组的内容
bubble_sort(arr);//不能写&arr
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
运行后结果为8 9 7 6 5 4 3 2 1 0,不符合预期
调试后发现:第8行sz期望值为10,但监视发现是1
数组名是什么
#include <stdio.h>
int main()
{
int arr[10] = { 5,4,3,2,1 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
printf("%d\n", *arr);
//输出结果
return 0;
}
结论:数组名是首元素的地址(有两个例外)
- sizeof(数组名),数组名不是首元素的地址,数组名表示整个数组,计算的是整个数组的大小
- &数组名,数组名不是首元素的地址,数组名表示整个数组,取出的是整个数组的地址
例外1:
int arr[10] = { 0 };
printf("%d\n", sizeof(arr));//(若为地址,结果应该是4/8)
例外2:
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%p\n", arr);
printf("%p\n", arr+1);//地址+4
printf("\n");
printf("%p\n", &arr[0]);
printf("%p\n", &arr[0]+1);//地址+4
printf("\n");
printf("%p\n", &arr);//发现打印的是首元素地址
printf("%p\n", &arr+1);//地址+40
printf("%d\n", sizeof(&arr));//输出4/8
printf("%d\n", sizeof(arr));//输出40(无论32/64)
自测
int a = 0;
printf("%p\n", &a);
printf("%d\n", sizeof(&a));
冒泡排序函数的正确设计
把int sz = sizeof(arr) / sizeof(arr[0]);从冒泡函数放到主函数中,再把sz传参给冒泡函数
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void bubble_sort(int arr[], int sz)//int arr[]本质上是int* arr,是个指针,不是数组
//数组传参写成数组,更易理解,只是形式而已,可以写成int* arr,但是不能写成int arr
{
int i = 0;//趟数,与元素个数有关系,等于元素个数减一
for (i = 0; i < sz - 1; i++)
{
//每一趟冒泡排序过程
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
//交换
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
}
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
//写一个冒泡排序的函数,来排序arr数组的内容
bubble_sort(arr, sz);//不能写&arr
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
优化:若一趟冒泡排序没有发生交换,则已经有序,在冒泡排序添加flag去判断是否有序
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
void bubble_sort(int arr[], int sz)//int arr[]本质上是int* arr,是个指针,不是数组
//数组传参写成数组,更易理解,只是形式而已,可以写成int* arr,但是不能写成int arr
{
int i = 0;//趟数,与元素个数有关系,等于元素个数减一
for (i = 0; i < sz - 1; i++)
{
int flag = 1;//假设已经有序
//每一趟冒泡排序过程
int j = 0;
for (j = 0; j < sz - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
flag = 0;//发生交换就无序
int tmp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = tmp;
}
}
if (1 == flag)
{
break;
}
}
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
//写一个冒泡排序的函数,来排序arr数组的内容
bubble_sort(arr, sz);//不能写&arr
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
5、三子棋
//game.h文件
#pragma once
//函数声明,符号定义
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#define ROW 3
#define COL 3
//初始化棋盘
void init_board(char board[ROW][COL], int row, int col);
//打印棋盘
void print_board(char board[ROW][COL], int row, int col);
//玩家下棋
void player_move(char board[ROW][COL], int row, int col);
//电脑下棋
void computer_move(char board[ROW][COL], int row, int col);
//判断输赢
char if_win(char board[ROW][COL], int row, int col);
//game.c文件
#define _CRT_SECURE_NO_WARNINGS 1
//游戏的实现
#include "game.h"
void init_board(char board[ROW][COL], int row, int col)//row - 行;col - 列
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
board[i][j] = ' ';
}
}
}
void print_board(char board[ROW][COL], int row, int col)
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
//打印数据
for (j = 0; j < col; j++)
{
printf(" %c ", board[i][j]);
if (j < col - 1)
printf("|");
}
printf("\n");
//打印分割行
if (i < row - 1)
for (j = 0; j < col; j++)
{
printf("---");
if (j < col - 1)
printf("|");
}
printf("\n");
}
}
void player_move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
while (1)
{
printf("玩家下棋\n");
printf("请输入坐标(行/列):>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
//下棋
if (board[x - 1][y - 1] == ' ')
{
board[x-1][y-1] = '*';
break;
}
else
{
printf("该坐标被占用,请重新输入\n");
}
}
else
{
printf("坐标非法,请重新输入\n");
}
}
}
void computer_move(char board[ROW][COL], int row, int col)
{
int x = 0;
int y = 0;
printf("电脑下棋\n");
while (1)
{
x = rand() % row;//使用rand函数之前需要调用srand,整个工程只需要调用一次
y = rand() % col;
if (board[x][y] == ' ')
{
board[x][y] = '#';
break;
}
}
}
static int if_full(char board[ROW][COL], int row, int col)//static修饰只能在这个.c文件使用
{
int i = 0;
for (i = 0; i < row; i++)
{
int j = 0;
for (j = 0; j < col; j++)
{
if (board[i][j] == ' ')
{
return 0;
}
}
}
return 1;
}
char if_win(char board[ROW][COL], int row, int col)
{
int i = 0;
//判断行
for (i = 0; i < row; i++)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] != ' ')
{
return board[i][0];
}
}
//判断列
for (i = 0; i < col; i++)
{
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] != ' ')
{
return board[0][i];
}
}
//判断对角线
if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[0][0] != ' ')
{
return board[0][0];
}
if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[2][0] != ' ')
{
return board[0][2];
}
//判断平局
if (if_full(board, row, col) == 1)
{
return 'Q';
}
//上面都不是就继续
return 'C';
}
//test.c文件
#define _CRT_SECURE_NO_WARNINGS 1
//测试游戏的逻辑
#include "game.h"
void menu()
{
printf("*****************************\n");
printf("********* 1. play *********\n");
printf("********* 0. exit *********\n");
printf("*****************************\n\n");
}
void game()
{
char ret = 0;
//存放下棋数据
char board[ROW][COL] = { 0 };
//初始化棋盘为全空格
init_board(board, ROW, COL);
//打印棋盘
print_board(board, ROW, COL);
while (1)
{
//玩家下棋
player_move(board, ROW, COL);
print_board(board, ROW, COL);
//判断输赢
//玩家赢 - '*'
//电脑赢 - '#'
//平局 - 'Q'
//继续 - 'C'
ret = if_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
//电脑下棋
computer_move(board, ROW, COL);
print_board(board, ROW, COL);
//判断输赢
ret = if_win(board, ROW, COL);
if (ret != 'C')
{
break;
}
}
if (ret == '*')
{
printf("玩家获胜\n");
}
else if (ret == '#')
{
printf("电脑获胜\n");
}
else
{
printf("平局\n");
}
}
void test()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();//三子棋逻辑
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,重新选择\n");
break;
}
} while (input);
}
int main()
{
test();
return 0;
}
6、扫雷
//game.h文件
#pragma once
#include <stdio.h>
#include <time.h>
#include <stdlib.h>
#define ROW 9
#define COL 9
#define ROWS ROW + 2
#define COLS COL + 2
#define EASY_COUNT 10
//初始化棋盘
void init_board(char arr[ROWS][COLS], int rows, int cols , char set);
//打印棋盘
void print_board(char arr[ROWS][COLS], int row, int col);
//布置雷
void set_mine(char mine[ROWS][COLS], int row, int col);
//排查雷
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);
//game.c文件
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void init_board(char arr[ROWS][COLS], int rows, int cols, char set)
{
int i = 0;
int j = 0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
arr[i][j] = set;
}
}
}
void print_board(char arr[ROWS][COLS], int row, int col)
{
int i = 0;
int j = 0;
printf("-------------扫雷-------------\n");
for (i = 0; i <= col; i++)
{
printf("%d ", i);
}
printf("\n");
for (i = 1; i <= row; i++)
{
printf("%d ", i);
for (j = 1; j <= col; j++)
{
printf("%c ", arr[i][j]);
}
printf("\n");
}
printf("------------------------------\n");
}
void set_mine(char mine[ROWS][COLS], int row, int col)
{
int count = EASY_COUNT;
int x = 0;
int y = 0;
while (count)
{
x = rand() % 9 + 1;
y = rand() % 9 + 1;
if (mine[x][y] != '1')
{
mine[x][y] = '1';
count--;
}
}
}
int get_mine_count(char mine[ROWS][COLS], int x, int y)
{
return mine[x - 1][y - 1] + mine[x - 1][y] + mine[x - 1][y + 1] + mine[x][y - 1] + mine[x][y + 1] + mine[x + 1][y - 1] + mine[x + 1][y] + mine[x + 1][y + 1] - 8 * '0';
}
void find_mine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col)
{
int x = 0;
int y = 0;
int win = 0;
while (win < row * col - EASY_COUNT)
{
printf("请输入坐标:>");
scanf("%d%d", &x, &y);
if (x >= 1 && x <= row && y >= 1 && y <= col)
{
if (mine[x][y] == '1')
{
printf("你被炸死了,下图是雷区图(1为雷,0为非雷)\n");
print_board(mine, ROW, COL);
break;
}
else
{
int count = get_mine_count(mine, x, y);
show[x][y] = count + '0';
print_board(show, ROW, COL);
win++; }
}
else
{
printf("坐标超出范围\n");
}
}
if (win == row * col - EASY_COUNT)
{
printf("成功通关!!!\n");
printf("下图是雷区图(1为雷,0为非雷)\n");
print_board(mine, ROW, COL);
}
}
//test.c文件
//mine数组 - 存放布置好的雷的信息
//'1' - 雷
//'0' - 非雷
//show数组 - 存放排查出的雷的信息
//'*' - 未排查
//数组字符 - 已排查
#define _CRT_SECURE_NO_WARNINGS 1
#include "game.h"
void menu()
{
printf("*****************************\n");
printf("******** 1. play ********\n");
printf("******** 0. exit ********\n");
printf("*****************************\n");
}
void game()
{
//棋盘创建
char mine[ROWS][COLS] = { 0 };//全部初始为'0'
char show[ROWS][COLS] = { 0 };//全部初始为'*'
//棋盘初始化
init_board(mine, ROWS, COLS, '0');
init_board(show, ROWS, COLS, '*');
//布置雷
set_mine(mine, ROW, COL);
//打印棋盘
print_board(mine, ROW, COL);
print_board(show, ROW, COL);
//排查雷
find_mine(mine, show, ROW, COL);
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf("请输入:>");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新输入\n\n");
break;
}
} while (input);
return 0;
}
拓展功能
- 标记
0 };//全部初始为’’
//棋盘初始化
init_board(mine, ROWS, COLS, ‘0’);
init_board(show, ROWS, COLS, '');
//布置雷
set_mine(mine, ROW, COL);
//打印棋盘
print_board(mine, ROW, COL);
print_board(show, ROW, COL);
//排查雷
find_mine(mine, show, ROW, COL);
}
int main()
{
srand((unsigned int)time(NULL));
int input = 0;
do
{
menu();
printf(“请输入:>”);
scanf(“%d”, &input);
switch (input)
{
case 1:
game();
break;
case 0:
printf(“退出游戏\n”);
break;
default:
printf(“选择错误,请重新输入\n\n”);
break;
}
} while (input);
return 0;
}
拓展功能
1. 标记
2. 展开(1.该坐标不是雷 2.该坐标周围8个不是雷 3.该坐标未被排查过)