从零开始学习C语言---数组

简介: 本章介绍C语言中一维数组,二维数组相关知识,以及结合二维数组实现三子棋和扫雷等小游戏。快来学习吧!!!

1、一维数组的创建和初始化

1.1、数组的创建

数组是一组相类型元素的集合。

数组的创建方式:

type_t arr_name [const_n];

//type_t 是指数组的元素类型
//const_n 是一个常量表达式,用来指定数组的大小。

1.2、数组的初始化

数组的初始化是指,在创建数组的同时给数组的内容一些合理初始值(初始化)。

//不完全初始化,剩余的元素默认初始化位0
int arr1[10] = {1,2,3};

int arr2[] = {1,2,3};

int arr3[5] = {'1','2','3','4','5'};

char arr4[3] = {'a','98','c'};

char arr5[10] = {'a','b','c'};

char arr6[10] = "abcdef";

arr5和arr6存放元素的效果一样,但是有些细节还是不相同的:

arr5:是人为放进去:'a','b','c',然后后面补充0。

arr6:是认为放进去:a','b','c','\0',然后后面补充0。

那为什么说效果一样呢?因为'\0'的ASCII值为0,所以数组里面存放的是'\0',其实是以它的ASCII值0存放的。

重点是:字符串有'\0',而字符没有'\0'。

1.3、一维数组的使用

int arr[10] = {0};                 //数组的不完全使用

int sz = sizeof(arr) / sizeof(arr[0]);      //计算数组元素的个数

arr[i]       //数组的访问。

总结:

  • 数组是使用下标来访问的,下标是从0开始的。
  • 数组的大小是可以通过计算得到的。

1.4、一维数组在内存中的存储

看代码:

#include <stdio.h>
int main()
{
    int i = 0;
    int arr[10] = { 0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    for (i = 1; i <= 9; i++)
    {
        printf("&arr[%d]:%p\n", i, &arr[i]);
    }
    return 0;
}

输出:

image.png

可以发现:每一个元素地址值相差为4,因为一个int类型的数据占据4个字节。

随着数组下标的增长,元素的地址,也有规律的递增。

得出结论:数组在内存中是来逆序存放的。

1.5、数组的类型

我们知道一个变量是有类型的,比如:

int a = 0;

char u = 'a';

那数组有没有类型呢?答案:有!

int arr[10] = {0};

//int [10]就是arr数组的类型,[]里面的10,也是类型的一部分,不能省略。

【补充:】sizeof()和strlen的用法

#include <stdio.h>
#include <string.h>
int main()
{
    char str[] = "hello bit";
    printf("%d %d", sizeof(str), strlen(str));
    return 0;
}

输出:

image.png

分析:

  • sizeof()是一个操作符,是用来计算变量、类型、数组等所占空间大小的。
  • strlen()是一个库函数,是专门求字符串长度的,只能针对字符串,从参数给定的地址向后一直找'\0'或者说统计'\0'之前出现的字符个数。

首先:str数组中存放:h e l l o _ b i t \0

注意str数组中是存放了10个元素的,一定一定要想到'\0'也是个元素,它也是存放在str数组中的,他也算一个元素。

因此str数组中存放了10个元素,又因为全部都是char类型的,所以str数组大小一共10字节。因为sizeof()输出为10。

而strlen()是统计字符串个数的,并且只统计'\0'之前的字符个数。因为'\0'之前有9个元素,所以strlen()输出为9。

image.png

2、二维数组的创建和初始化

2.2、二维数组的创建

//数组创建
int arr[3][4];      //表示这个二维数组是3行4列的,列就是一行放几个元素,像一个表格一样。二维数组本质上就是多行的数组

char arr[3][5];

double arr[2][4];

2.3、二维数组的初始化

//3行4列,前四个元素放在第一行,中间四个元素放在第二行,最后四个元素放在第三行
int arr[3][4] = {1,2,3,4,2,3,4,5,3,4,5,6};


//前四个元素放在第一行,中间四个元素放在第二行,最后两个元素放在第三行然后剩余的两个位置用0补充
int arr[3][4] = {1,2,3,4,2,3,4,5,3,4};


//将1,2元素放在第一行剩余补0,0,将3,4元素放在第二行剩余补0,0,将4,5元素放在第三行剩余补0,0
int arr[3][4] = {
  
  {1,2},{3,4},{4,5}};


//只能省略行,不能省略列
int arr[][4] = {
  
  {1,2,3,4},{1,2}};

int arr[][4] = {1,2,3,4,5,6};

2.3、二维数组的使用

行下标是从0开始,列下标也是从0开始,先确定行,在确定列。

#include <stdio.h>
int main()
{
    int arr[3][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };
    int i = 0;
    for (i = 0; i < 3; i++)
    {
        int j = 0;
        for (j = 0; j < 4; j++)
        {
            printf("%d ", arr[i][j]);  //打印元素
            scanf("%d", &arr[i][j]);   //输入元素
        }
        printf("\n");
    }
    return 0;
}

输出:

image.png

补充:如何把二维数组看作为一维数组

image.png

我们可以把二维数组的每一行看作是一维数组中的元素,然后把arr[0],arr[1],arr[2]看作是每一行的数组名。

2.4、二维数组在内存中的存储

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
    int arr[3][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };
    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;
}

输出:

image.png

可以发现其实二维数组和一维数组在空间中存储的形式是一样的,都是连续的空间。

//以存储空间的角度来看,以下是等价的:
int arr[3][4] = { 1,2,3,4,2,3,4,5,3,4,5,6 };  =  int arr[12] = {1,2,3,4,2,3,4,5,3,4,5,6}

3、数组越界

数组的下标是有范围限制的。

数组的下标规定是从0开始,如果数组有n个元素,最后一个元素的下标就是n-1。

所以数组的下标如果是小于0,或者大于n-1,就是数组越界访问了,超出了数组合法空间的访问。

C语言本身是不做数组下标的越界检查,编译器也不一定会报错,但是编译器不报错,并不意味着程序就是正确的。

所以程序员写代码时,最好自己做好越界的检查。

4、数组作为函数参数

往往我们写代码的时候,会将数组作为一个参数传给函数,比如:我要实现一个冒泡排序(这里要讲算法思想)函数。

将一个整型数组排序。

4.1、冒泡排序函数的错误设计


5、冒泡排序的核心思想

冒泡排序的核心思想:两个相邻的元素进行比较。

6、数组名

6.1、一维数组的数组名理解

我们通常说数组名就是数组首元素地址

#include <stdio.h>
int main()
{
    int arr[3] = { 1,2,3 };
    printf("%p\n", arr);
    printf("%p\n", &arr[0]);
    return 0;
}

输出:

image.png

可以看到地址确实是一样的。

所以得出结论:数组名确实能表示数组首元素的地址。

但是又两个例外:

  • sizeof(数组名),这里的数组名表示整个数组,计算的是整个数组的大小,单位是字节。
  • &数组名,这里的数组名表示整个数组,取出的是整个数组的地址。

除此以上两种情况,其它遇到的数组名都是数组首元素地址。

6.2、二维数组的数组名理解

二维数组的数组名也表示数组首元素地址。

比如:int arr[3][4]; 数组首元素地址表示的是第一行元素的地址。并不是第一行第一个的元素的地址。

7、计算二维数组的行数和列数

#include <stdio.h>
int main()
{
    int arr[3][4] = { 0 };
    //sizeof(arr)表示整个二维数组的大小,sizeof(arr[0])表示二维数组的第一行大小。
    printf("%d\n", sizeof(arr) / sizeof(arr[0]));

    //sizeof(arr[0])表示二维数组的第一行大小,sizeof(arr[0][0])表示二维数组的一个元素大小。
    printf("%d\n", sizeof(arr[0]) / sizeof(arr[0][0]));
    return 0;
}

8、三子棋

  • 新建个项目

  • 创建test.c 测试游戏的逻辑

  • 创建game.c 游戏代码的实现

  • game.h 游戏代码的声明(函数的声明、符号定义)

test.c文件

#define _CRT_SECURE_NO_WARNINGS
#include "game.h"
void menu()
{
    printf("*****************************\n");
    printf("******1. 进入游戏 0. 退出****\n");
    printf("*****************************\n");
}

void game()
{
    char ret = 0;
    char board[ROW][COL] = { 0 };
    //初始化棋盘的函数
    InitBoard(board, ROW, COL);

    //打印棋盘
    DisplayBoard(board, ROW, COL);

    //下棋
    while (1)
    {
        //玩家下棋
        PlayerMove(board,ROW,COL);
        //判断输赢
        ret = IsWin(board,ROW,COL);
        if (ret != 'C')
        {
            break;
        }
        DisplayBoard(board, ROW, COL);
        //电脑下棋
        ComputeMove(board,ROW,COL);
        //判断输赢
        ret = IsWin(board, ROW, COL);
        if (ret != 'C')
        {
            break;
        }
        DisplayBoard(board, ROW, COL);
    }
    if (ret == '*')
    {
        printf("玩家赢\n");
        DisplayBoard(board, ROW, COL);
    }
    else if (ret == '#')
    {
        printf("电脑赢\n");
        DisplayBoard(board, ROW, COL);
    }
    else
    {
        printf("平局\n");
        DisplayBoard(board, 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");
            break;
        }
    } while (input);
    return 0;
}

game.c文件

#include "game.h"

//初始化棋盘
void InitBoard(char board[ROW][COL], int row, int col)
{
    int i = 0;
    int j = 0;
    for (i = 0; i < row; i++)
    {
        for (j = 0; j < col; j++)
        {
            board[i][j] = ' ';
        }
    }
}

////第一个版本
//void DisplayBoard(char board[ROW][COL], int row, int col)
//{
//    int i = 0;
//    for (i = 0; i < row; i++)
//    {
//        打印数据
//        printf(" %c | %c | %c \n", board[i][0], board[i][1], board[i][2]);
//        打印分割信息
//        if (i < row - 1)
//        {
//            printf("---|---|---\n");
//        }
//    }
//}

//打印棋盘
void DisplayBoard(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 PlayerMove(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 ComputeMove(char board[ROW][COL], int row, int col)
{
    printf("电脑下棋:>\n");
    int x = 0;
    int y = 0;
    while (1)
    {
        x = rand() % row;   //0~2
        y = rand() % col;   //0~2
        if (board[x][y] == ' ')
        {
            board[x][y] = '#';
            break;
        }
    }
}

//平局的函数,返回1表示填满了,返回0表示未填满
int IsFull(char board[ROW][COL], int row, int col)
{
    int i = 0;
    int j = 0;
    for (i = 0; i < row; i++)
    {
        for (j = 0; j < col; j++)
        {
            if (board[i][j] == ' ')
            {
                return 0;
            }
        }
    }
    return 1;
}


//判断输赢
char IsWin(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][1] != ' ')
        {
            return board[i][1];
        }
    }

    //判断在列中赢的
    int j = 0;
    for (j = 0; j < col; j++)
    {
        if (board[0][j] == board[1][j] && board[1][j] == board[2][j] && board[1][j] != ' ')
        {
            return board[1][j];
        }
    }

    //判断对角线赢的
    if (board[0][0] == board[1][1] && board[1][1] == board[2][2] && board[1][1] != ' ')
    {
        return board[1][1];
    }
    if (board[0][2] == board[1][1] && board[1][1] == board[2][0] && board[1][1] != ' ')
    {
        return board[1][1];
    }

    //平局:都没赢就需要平局,平局的判断标准就是:以上赢的标准都不符合且九个格子全部填满。
    if (IsFull(board,ROW,COL))
    {
        return 'Q';
    }
    //游戏继续
    return 'C';
}

game.h文件

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#define ROW 3
#define COL 3
#include <stdlib.h>
#include <time.h>

//初始化棋盘
void InitBoard(char board[ROW][COL], int row, int col);

//打印棋盘
void DisplayBoard(char board[ROW][COL], int row, int col);

//玩家下棋
void PlayerMove(char board[ROW][COL], int row, int col);

//电脑下棋
void ComputeMove(char board[ROW][COL], int row, int col);

//判断输赢,包含四种情况:
//玩家赢-'*'
//电脑赢-'#'
//平局-'Q'
//继续-'C'
char IsWin(char board[ROW][COL], int row, int col);

9、扫雷

image.png

test.c文件

#define _CRT_SECURE_NO_WARNINGS
#include "game.h"

void menu()
{
    printf("*********************************\n");
    printf("*********   1.玩游戏    *********\n");
    printf("*********   0.退  出    *********\n");
    printf("*********************************\n");
}

void game()
{
    //存放布置好雷的信息
    char mine[ROWS][COLS] = { 0 };
    //存放排查出雷的信息
    char show[ROWS][COLS] = { 0 };
    //mine 初始化数组在没有布置雷的时候都是'0'
    InitBoard(mine, ROWS, COLS,'0');
    //show 初始化数组在没有布置雷的时候都是'*'
    InitBoard(show, ROWS, COLS, '*');

    //设置雷
    SetMine(mine, ROW, COL);
    //打印棋盘,这里只需要打印9*9的棋盘,所以参数需要变化
    //DisplayBoard(mine, ROW, COL);
    DisplayBoard(show, ROW, COL);

    //排查雷
    FindMine(mine, show, ROW, COL);

}

int main()
{
    int input = 0;
    srand((unsigned int)time(NULL));
    do
    {
        menu();
        printf("请选择:>");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            game();
            break;
        case 2:
            printf("退出游戏");
            break;
        default:
            printf("选择错误\n");
            break;
        }
    } while (input);
    return 0;
}

game.c

#include "game.h"

//初始化棋盘为'0'
void InitBoard(char board[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++)
        {
            board[i][j] = set;
        }
    }
}

//打印棋盘
void DisplayBoard(char board[ROWS][COLS], int row, int col)
{
    int i = 0;
    int j = 0;
    printf("-----------------扫雷游戏--------------------\n");
    for (j = 0; j <= col; j++)
    {
        printf("%d ", j);
    }
    printf("\n");
    for (i = 1; i <= row; i++)
    {
        printf("%d ", i);    //添加行号
        for (j = 1; j <= col; j++)
        {
            printf("%c ",board[i][j]);
        }
        printf("\n");
    }
    printf("-----------------扫雷游戏--------------------\n");
}

//设置雷
void SetMine(char board[ROWS][COLS], int row, int col)
{
    int count = EASY_COUNT;
    while (count)
    {
        int x = rand() % row + 1;
        int y = rand() % col + 1;
        if (board[x][y] == '0')
        {
            board[x][y] = '1';
            count--;
        }
    }
}

int get_mine_count(char board[ROWS][COLS],int x,int y)
{
    return (board[x - 1][y] +
        board[x - 1][y] +
        board[x][y - 1] +
        board[x + 1][y - 1] +
        board[x + 1][y] +
        board[x + 1][y + 1] +
        board[x][y + 1] +
        board[x - 1][y + 1] - 8 * '0'); 


}

//排查雷
void FindMine(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 (show[x][y] != '*')
            {
                printf("该坐标被排查过了,不能重复排查\n");
            }
            else
            {
                if (mine[x][y] == '1')
                {
                    printf("很遗憾,你被炸死了\n");
                    DisplayBoard(mine, ROW, COL);
                    break;
                }
                else        //如果不是雷,需要统计周围有几个雷
                {
                    win++;
                    int count = get_mine_count(mine, x, y);
                    show[x][y] = count + '0';     //数字1转为字符'1'
                    DisplayBoard(show, ROW, COL);
                }
            }
        }
        else
        {
            printf("输入非法坐标,请重新输入\n");
        }
    }
    if (win == row * col - EASY_COUNT)
    {
        printf("恭喜你,排雷成功了\n");
        DisplayBoard(mine, ROW, COL);
    }
}

game.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#define ROW 9
#define COL 9

#define ROWS ROW+2
#define COLS COL+2

#define EASY_COUNT 10

void InitBoard(char board[ROWS][COLS], int rows, int cols,char set);

void DisplayBoard(char board[ROWS][COLS], int row, int col);

void SetMine(char board[ROWS][COLS], int row, int col);

void FindMine(char mine[ROWS][COLS], char show[ROWS][COLS], int row, int col);

'1'和1的运算

'1' - '0' = 1

原理:'1'的ASCII值为49

​ '0'的ASCII值为48

'0' - '0' = 0

原理同上。

相关文章
|
25天前
|
传感器 算法 安全
【C语言】两个数组比较详解
比较两个数组在C语言中有多种实现方法,选择合适的方法取决于具体的应用场景和性能要求。从逐元素比较到使用`memcmp`函数,再到指针优化,每种方法都有其优点和适用范围。在嵌入式系统中,考虑性能和资源限制尤为重要。通过合理选择和优化,可以有效提高程序的运行效率和可靠性。
78 6
|
28天前
|
存储 缓存 算法
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式
在C语言中,数据结构是构建高效程序的基石。本文探讨了数组、链表、栈、队列、树和图等常见数据结构的特点、应用及实现方式,强调了合理选择数据结构的重要性,并通过案例分析展示了其在实际项目中的应用,旨在帮助读者提升编程能力。
54 5
|
28天前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
1月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
1月前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。
|
1月前
|
存储 人工智能 算法
数据结构实验之C 语言的函数数组指针结构体知识
本实验旨在复习C语言中的函数、数组、指针、结构体与共用体等核心概念,并通过具体编程任务加深理解。任务包括输出100以内所有素数、逆序排列一维数组、查找二维数组中的鞍点、利用指针输出二维数组元素,以及使用结构体和共用体处理教师与学生信息。每个任务不仅强化了基本语法的应用,还涉及到了算法逻辑的设计与优化。实验结果显示,学生能够有效掌握并运用这些知识完成指定任务。
58 4
|
2月前
|
存储 编译器 C语言
【c语言】数组
本文介绍了数组的基本概念及一维和二维数组的创建、初始化、使用方法及其在内存中的存储形式。一维数组通过下标访问元素,支持初始化和动态输入输出。二维数组则通过行和列的下标访问元素,同样支持初始化和动态输入输出。此外,还简要介绍了C99标准中的变长数组,允许在运行时根据变量创建数组,但不能初始化。
59 6
|
2月前
|
存储 人工智能 BI
C语言:数组的分类
C语言中的数组分为一维数组、多维数组和字符串数组。一维数组是最基本的形式,用于存储一系列相同类型的元素;多维数组则可以看作是一维数组的数组,常用于矩阵运算等场景;字符串数组则是以字符为元素的一维数组,专门用于处理文本数据。
|
2月前
|
存储 算法 C语言
C语言:什么是指针数组,它有什么用
指针数组是C语言中一种特殊的数据结构,每个元素都是一个指针。它用于存储多个内存地址,方便对多个变量或数组进行操作,常用于字符串处理、动态内存分配等场景。
|
2月前
|
存储 C语言
C语言:一维数组的不初始化、部分初始化、完全初始化的不同点
C语言中一维数组的初始化有三种情况:不初始化时,数组元素的值是随机的;部分初始化时,未指定的元素会被自动赋值为0;完全初始化时,所有元素都被赋予了初始值。