C 语言复习总结记录四
一 一维数组
数组是一组相同类型元素的集合
数组的创建方式
type_t arr_name [const_n];
//type_t 是指数组的元素类型
//const_n 是一个常量表达式,用来指定数组的大小
int arr1[10];
int count = 10;
int arr2[count]; //报错, 表达式必须含有常量值 (常量表达式)
C99 标准前数组的创建,[] 中必须要一个常量表达式才可以,不能使用变量。在C99 标准支持了变长数组的概念,数组的大小可以使用变量指定,如下
//VS 编译器并不支持变长数组
int main()
{
int n = 0;
scanf("%d",&n); //10
int arr[n];//变长数组
int i = 0;
for(i = 0 ; i < n; i++)
arr[i] = i;
for(i=0;i<n;i++)
printf("%d ",arr[i]);
return 0;
}
1.1 数组初始化
在创建数组同时给数组内容一些初始值(初始化)
int arr1[10] = {1,2,3};
int arr2[] = {1,2,3,4};
int arr3[5] = {1,2,3,4,5};
char arr4[3] = {'a',98, 'c'};
char arr5[] = {'a','b','c'};
char arr6[] = "abcdef";
//注意区分内存分配
char arr1[] = "abc";
char arr2[3] = {'a','b','c'};
int sz1 = sizeof(arr1) / sizeof(arr1[0]); //4 '\0'
int sz2 = sizeof(arr2) / sizeof(arr2[0]); //3
数组在创建的时候如果不指定数组大小就必须初始化。数组的元素个数根据初始化的内容来确定
1.2 使用一维数组
[] 下标访问操作符,访问数组元素的操作符
#include <stdio.h>
int main()
{
int arr[10] = {0};//数组的不完全初始化
int sz = sizeof(arr)/sizeof(arr[0]);
int i = 0;//做下标
for(i=0; i<10; i++)
arr[i] = i;
for(i=0; i<10; ++i)
printf("%d ", arr[i]);
return 0;
}
数组一般使用下标来访问,下标从 0 开始(也可使用指针偏移和解引用操作符)
数组的大小可通过计算得到
1.3 内存中的存储
#include <stdio.h>
int main()
{
int arr[10] = {0};
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;
}
随着数组下标的增长,元素地址,也在规律的递增
数组在内存中是连续存放的
二 二维数组
2.1 二维数组创建与初始化
//数组创建
int arr[3][4];
char arr[3][5];
double arr[2][4];
//数组初始化
int arr[3][4] = {1,2,3,4};
int arr[3][4] = {{1,2},{4,5}};
int arr[][4] = {{2,3},{4,5}}; //二维数组初始化,行可省略,列不能省略
二维数组的初始化行可省略,列不能省略原因 :二数组是按行存储的,确定列后才能存储一行一行的数据
定义二维数组时省略行数,但确定了列数,计算机会根据列数数值以及初始化时给的数据,自动确定行数。
数组的寻址方式:编译器在处理二维数组时,对于 array[m][n]
的数组,如果要取特定位置的值,比如 array[i][j]
处的值,编译器的寻址方式是
array + n * i + j
即每行有几个元素(列的数量)
2.2 使用二维数组
使用二维数组也是通过下标访问(偏移 + 解引用也可)
#include <stdio.h>
int main()
{
int arr[3][4] = {0};
int i = 0;
for(i=0; i<3; i++)
{
int j = 0;
for(j = 0; j < 4; j++)
{
arr[i][j] = i * 4 + j;
}
}
for(i = 0; i < 3; i++)
{
int j = 0;
for(j = 0; j < 4; j++)
printf("%d ", arr[i][j]);
}
return 0;
}
2.3 二维数组在内存中的存储
#include <stdio.h>
int main()
{
int arr[3][4];
int i = 0;
for(i=0; i<3; i++)
{
int j = 0;
for(j=0; j<4; j++)
printf("&arr[%d][%d] = %p\n", i, j,&arr[i][j]);
}
return 0;
}
二维数组在内存中也是连续存储的
三 数组越界
数组下标有范围限制,规定从 0 开始,设数组有 n 个元素,最后一个元素的下标是n- 1。所以数组下标如果小于 0,或大于 n-1,就是数组越界访问了,超出了数组合法空间的访问
C 语言本身不做数组下标的越界检查,编译器也不一定报错
#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 的时候,越界访问
}
return 0;
}
四 数组作为函数参数
将数组常会作为函数参数,实现一个将整形数组排序的函数(冒泡)
void bubbleSort(int arr[], int sz) //当使用数组作为参数时,传递的其实是数组头指针
{
//int sz = sizeof(arr) / sizeof(int);
for (int i = 0; i < sz - 1;++i) //遍历 sz - 1 趟
{
for (int j = 0; j < sz - i - 1; ++j) //从第 1 个元素开始遍历,遍历至 sz - 1 - i
{
if (arr[j] > arr[j + 1])
{
arr[j] ^= arr[j + 1];
arr[j + 1] ^= arr[j];
arr[j] ^= arr[j + 1];
}
}
}
}
sizeof(数组名),计算整个数组的大小,sizeof 内部单独放一个数组名,数组名表示整个数组。&数组名,取出的是数组的地址
五 练习
5.1 三子棋
游戏规则
1、棋盘介绍
棋盘呈横三行列,纵三列分布,为九宫格样式
2、棋子介绍
双方手持若干数量(一般是 4 - 5)的棋子,因为如果棋子摆满棋盘,先手方摆放的棋子数量为5,后手方摆放棋子数量为 4
另外,双方的棋子一般以不同的形状来区分,
比如之前用“O”和“X”形状的棋子进行对战,所以《三子棋》又叫圈圈叉叉棋。
有时玩家也会用不同颜色来区分棋子,比如黑白两种颜色,所以又称为黑白棋。
3、胜负规则
1、双方轮流在格子里摆放棋子,先连成三棋一线者视为胜利;
2、棋盘被摆满棋子仍未分出胜利,视为平局
//game.h
#include<stdbool.h>
#include<stdio.h>
#include<time.h>
#include<string.h>
#define LENGTH 3
#define WIDTH 3
void borad(char arr[LENGTH][WIDTH]);
bool drow(char arr[LENGTH][WIDTH]);
bool isWin(char arr[LENGTH][WIDTH], char player);
bool peoplePlay(char arr[LENGTH][WIDTH]);
void computerPlay(char arr[LENGTH][WIDTH]);
void game();
void menu();
//game.c
#include "game.h"
void borad(char arr[LENGTH][WIDTH]) {
for (int i = 0;i < LENGTH;++i) {
for (int j = 0;j < WIDTH;++j) {
printf("%c ", arr[i][j]);
}
printf("\n");
}
}
bool drow(char arr[LENGTH][WIDTH]) {
for (int i = 0;i < LENGTH;++i) {
for (int j = 0;j < WIDTH;++j) {
if (arr[i][j] == '*') return false;
}
}
return true;
}
bool isWin(char arr[LENGTH][WIDTH], char player) {
for (int i = 0; i < LENGTH; ++i) {
if (arr[i][0] == player && arr[i][1] == player
&& arr[i][2] == player) return true;
}
for (int i = 0; i < WIDTH; ++i) {
if (arr[0][i] == player && arr[1][i] == player
&& arr[2][i] == player) return true;
}
if ((arr[0][0] == player && arr[1][1] == player && arr[2][2] == player)
|| (arr[0][2] == player && arr[1][1] == player && arr[2][0] == player))
{
return true;
}
return false;
}
bool peoplePlay(char arr[LENGTH][WIDTH]) {
int x = 0, y = 0;
while (1)
{
printf("请输入你要下的坐标 \n");
scanf("%d %d", &x, &y);
if (x <= 0 || x > 3 || y <= 0 || y > 3)
{
printf("坐标在棋盘外, 请重新输入 \n");
continue;
}
if (arr[x - 1][y - 1] == '*')
{
arr[x - 1][y - 1] = 'A';
break;
}
else
{
printf("该位置已落子, 请重新输入 \n");
continue;
}
}
}
void computerPlay(char arr[LENGTH][WIDTH]) {
int robot_x = 0, robot_y = 0;
while (1) {
robot_x = rand() % 3;
robot_y = rand() % 3;
if (arr[robot_x][robot_y] == '*')
{
arr[robot_x][robot_y] = 'B';
break;
}
}
}
void game() {
char arr[LENGTH][WIDTH];
memset(arr, '*', 9);
while (1) {
borad(arr); //打印棋盘
peoplePlay(arr); //落子
if (isWin(arr, 'A')) //判断胜负
{
borad(arr);
printf("人类胜利 \n");
break;
}
if (drow(arr)) { //判断是否和棋
printf("和棋,游戏结束 \n");
break;
}
computerPlay(arr); //电脑落子
if (isWin(arr, 'B')) //判断胜负
{
borad(arr);
printf("电脑胜利 \n");
break;
}
}
}
void menu() {
printf("*********************************\n");
printf("************ 1.play *************\n");
printf("************ 0.exit *************\n");
printf("*********************************\n");
}
//test_game.c
#include "game.h"
int main() {
int input;
srand((unsigned)time(NULL));
do {
menu();
printf("请输入以上数字选择功能 \n");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
break;
default:
printf("数字输入错误,请重新输入 !");
break;
}
} while (input);
return 0;
}
5.2 扫雷游戏
规则(简易版)
地图是 9 * 9 的大小
1、第一次点击不会是雷
2、输入坐标,显示自身和周围八个格子的附近雷数;格子里的数字表示它周围有几个雷
3、游戏目标是找出所有雷,地图所有的位置均显示周围有几个雷则赢,过程中 “触雷” 则输
//game.h
#define ROW 9
#define COL 9
#define MAP_ROW 11
#define MAP_COL 11
#define MINE_CNT 6
void printMap(char map[MAP_ROW][MAP_COL]);
void printMineMap(char mine_map[MAP_ROW][MAP_COL]);
int mineCnt(char mine_map[MAP_ROW][MAP_COL], int i, int j, int mineCount);
void updateMap(char map[MAP_ROW][MAP_COL], char mine_map[MAP_ROW][MAP_COL], int x, int y);
void layMines(char mine_map[MAP_ROW][MAP_COL], int x, int y);
bool isEnd(char map[MAP_ROW][MAP_COL]);
bool play(char map[MAP_ROW][MAP_COL], char mine_map[MAP_ROW][MAP_COL]);
void game();
void menu();
//game.c
bool layMinesFlag = false;
void printMap(char map[MAP_ROW][MAP_COL])
{
for (int i = 1;i <= ROW; ++i)
{
for (int j = 1;j <= COL; ++j)
printf("%c ", map[i][j]);
printf("\n");
}
}
void printMineMap(char mine_map[MAP_ROW][MAP_COL]) {
for (int i = 1;i <= ROW; ++i)
{
for (int j = 1;j <= COL; ++j)
printf("%c ", mine_map[i][j]);
printf("\n");
}
}
int mineCnt(char mine_map[MAP_ROW][MAP_COL], int i ,int j , int mineCount)
{
for (int tmpx = -1; tmpx <= 1; ++tmpx)
{
for (int tmpy = -1; tmpy <= 1; ++tmpy)
{
if (tmpx == 0 && tmpy == 0) continue;
if (mine_map[i + tmpx][j + tmpy] == 'x') ++mineCount;
}
}
return mineCount;
}
void updateMap(char map[MAP_ROW][MAP_COL], char mine_map[MAP_ROW][MAP_COL], int x, int y)
{
for (int tmpx = -1; tmpx <= 1; ++tmpx)
{
for (int tmpy = -1; tmpy <= 1; ++tmpy)
{
if (mine_map[x + tmpx][y + tmpy] != 'x')
map[x + tmpx][y + tmpy] = mine_map[x + tmpx][y + tmpy];
else
map[x + tmpx][y + tmpy] =
(char)('0' + mineCnt(mine_map, x + tmpx, y + tmpy, 0));
}
}
}
void layMines(char mine_map[MAP_ROW][MAP_COL], int x, int y)
{
int mineCount = 0, x_mine = 0, y_mine = 0;
while (1)
{
x_mine = (rand() % 9) + 1; //雷的范围 [1, 9]
y_mine = (rand() % 9) + 1;
if (x_mine == x && y_mine == y) continue; //第一次点击不会是雷
if (mine_map[x_mine][y_mine] == '*') //设置雷
{
mine_map[x_mine][y_mine] = 'x';
if (++mineCount >= MINE_CNT) break;
}
}
for (int i = 1;i <= ROW; ++i) //更新雷地图
{
for (int j = 1;j <= ROW; ++j)
{
if (mine_map[i][j] != 'x')
mine_map[i][j] = (char)('0' + mineCnt(mine_map, i, j ,0));
}
}
}
bool isEnd(char map[MAP_ROW][MAP_COL])
{
int cnt = 0;
for (int i = 1;i <= ROW; ++i)
{
for (int j = 1;j <= COL; ++j)
if (map[i][j] == '*') return false;
}
return true;
}
bool play(char map[MAP_ROW][MAP_COL], char mine_map[MAP_ROW][MAP_COL])
{
int x = 0, y = 0;
while (1)
{
printf("请输入你要扫描的坐标 \n");
scanf("%d %d", &x, &y);
if (x < 1 || x > 9 || y < 1 || y >9)
{
printf("坐标在棋盘外, 请重新输入 \n");
continue;
}
if (!layMinesFlag) //第一次点击不会是雷
{
layMines(mine_map, x, y);
layMinesFlag = true;
}
if (map[x][y] == '*')
{
if (mine_map[x][y] == 'x')
{
printf("该位置是地雷,游戏失败 \n");
printMineMap(mine_map);
return false;
}
else
{
updateMap(map, mine_map, x, y);
return true;
}
}
else
{
printf("该位置已排查过, 请重新输入坐标 \n");
continue;
}
}
}
void game()
{
char map[MAP_ROW][MAP_COL];
char mine_map[MAP_ROW][MAP_COL];
memset(map, '*', MAP_ROW * MAP_COL);
memset(mine_map, '*', MAP_ROW * MAP_COL);
while (1)
{
printMap(map); //展示地图
//printf("------------------- \n");
//printMineMap(mine_map); //debug
if (play(map, mine_map)) //选择坐标
{
if (isEnd(map))//查看雷是否排完
{
printf("排雷成功,游戏结束! \n");
break;
}
}
else break;
}
}
void menu()
{
printf("*********************************\n");
printf("************ 1.play *************\n");
printf("************ 0.exit *************\n");
printf("*********************************\n");
}
//test_game.c
#include "game.h"
int main() {
int input;
srand((unsigned)time(NULL));
do {
menu();
printf("请输入以上数字选择功能 \n");
scanf("%d", &input);
switch (input)
{
case 1:
game();
break;
case 0:
break;
default:
printf("数字输入错误,请重新输入 !");
break;
}
} while (input);
return 0;
}
关键字 extern
通常用于在 C 和 C++ 语言中进行外部变量和函数的声明。通过 extern 关键字,可以在一个源文件中声明一个在其他源文件中定义的全局变量或函数,从而使得该变量或函数对其他源文件可见和可用。
在 C 语言中,extern 声明的语法形式如下:
extern int variable; // 声明一个名称为 variable、类型为 int 的全局变量,该变量在其他源文件中定义
extern void function(); // 声明一个名称为 function、返回类型为 void 的函数,该函数在其他源文件中定义
extern 声明一般与定义分离,可以用于声明外部全局变量和函数以便让其他文件可以引用。
定义和声明的区别:
声明:用来告诉编译器变量的名称和类型,而不分配内存,不赋初值
定义:为了给变量分配内存,可以为变量赋初值
定义要为变量分配内存空间;而声明不需要为变量分配内存空间
与 include 相比,extern 不会引入大量用不到的头文件,进而不会引入大量的无关函数。这样做的一个明显的好处是,会加速程序的编译(确切的说是预处理)的过程,节省时间
extern 实例
// utils.c, utils.c 文件代码
#include <stdio.h>
int global_variable = 10;
void printMessage() {
printf("Message from utils.c\n");
}
// main.c, main.c 文件代码
#include <stdio.h>
extern int global_variable; // 使用 extern 声明全局变量
extern void printMessage(); // 使用 extern 声明函数
int main() {
printf("Global variable from utils.c: %d\n", global_variable);
printMessage(); // 调用来自 utils.c 的函数
return 0;
}
在 main.c 中,我们使用 extern 关键字声明了 global_variable 和 printMessage(),指示它们的定义将在其他文件中。然后我们可以在 main 函数中正常使用这些变量和函数。
在实际编译时,将这两个文件一起编译链接即可,例如:
gcc -o program main.c utils.c
运行结果为:
Global variable from utils.c: 10
Message from utils.c
可以看到,我们成功地在 main.c 中使用了来自 utils.c 的全局变量和函数,并打印出了相应的结果