数组字符串深度解析:从入门到卡牌实战

数组和字符串深度解析

学习目标

  1. 一维数组的定义和使用
  2. 二维数组的定义和使用
  3. 字符数组和常用使用函数
  4. 数组应用案例卡牌游戏实战

PART.01 一维数组的定义和使用

为什么使用数组和数组的概念

  • 数组是一组有序的、类型相同的数据的集合,集合内的数据称为数组元素。
  • 每个数组拥有数组名,数组名代表数组的起始地址。
  • 数组元素通过索引/下标标识,下标从0开始

数组定义

数组使用前必须定义

语法:

复制代码
存储类型 数据类型 数组名[数组长度];

示例:int player[11];

  • 数组长度只能是常量、常量表达式,且大于0
  • 全部数组元素类型保持一致。
  • 数组元素可以使用在普通变量能够使用的任意位置。

一维数组内存存放:地址连续

初始化数组

  1. externstatic修饰的数组元素会自动初始化为0;普通自动数组元素不会自动初始化。

  2. 数组定义时初始化语法:

    类型 数组名[常量表达式] = {值, 值 ...};

示例:

c 复制代码
int ary[10]={0,1,2,3,4,5,6,7,8,9};
初始化规则
  1. 多个元素之间逗号分隔。
  2. 仅给部分元素赋值,剩余元素自动赋值为0。
  3. 全部元素赋值,可省略数组长度,编译器自动推导长度。
示例代码
c 复制代码
//1.完整初始化,指定长度
int ary1[10] = {1,2,3,4,5,6,7,8,9,10};
//2.完整初始化,省略长度
int ary1[] = {1,2,3,4,5,6,7,8,9,10};
//3.逐个元素赋值
ary1[0] = 1; ary1[1] = 2;ary1[2] = 3;
//4.部分初始化,剩余元素置0
int ary2[10] = {1,2,3,4,5};
//5.定义之后单独赋值,未赋值元素为随机垃圾值
int ary3[5] ; ary3[0] = 1; ary3[1] = 2;
//6.错误,不能直接空定义数组
int ary4[];
循环给数组赋值示例
c 复制代码
#include <stdio.h>
int main()
{
    char alpha[26];
    int i, j;
    for(i = 65,j = 0; i < 91; i++,j++) {
        alpha[j] = i;
        printf("The character is %c \n", alpha[j]);
    }
    return 0;
}
数组使用示例:求最大值、求和
c 复制代码
#include <stdio.h>
void main()
{
    int ary[10];
    int i, total, high;
    for ( i = 0; i < 10; i++) {
        scanf("%d",&ary[i]);
        high = ary[0];
    }
    for (i = 1; i < 10; i++) {
        if (ary[i] > high) high = ary[i];
    }
    for (i = 0,total = 0; i < 10; i++)
        total += ary[i];
}

思考题

  1. 定义int Grade[5];,循环访问Grade[6],编译器不会报错,下标越界。
  2. Grade1 == Grade2:数组名是地址,直接比较比较的是地址,不是数组内容
  3. ary2 = ary1;:不能直接把一个数组整体赋值给另一个数组。
  4. ary = 1;ary ={1,2,...};不能对数组名整体赋值

数组使用注意事项

  1. C编译器不检查数组下标越界
  2. 数组之间不能直接用==判断内容是否相等。
  3. 不能直接将一个数组整体赋值给另一个数组。
  4. 不能对数组整体赋值,只能逐个元素操作。

PART.02 二维数组的定义和使用

应用场景

学生成绩管理,存储多名学生的多门科目成绩,类似表格,行、列结构。

二维数组定义

语法:

复制代码
类型说明符 数组名[常量表达式1][常量表达式2];

示例:int temp[3][3];

内存存储规则:按行优先顺序连续存放,先存第一行全部元素,再存第二行。

二维数组初始化4种方式

  1. 分行初始化
c 复制代码
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
  1. 全部写在一个大括号,按内存顺序赋值
c 复制代码
int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
  1. 部分元素初始化,未赋值元素置0
c 复制代码
//只给每行第一个元素赋值
int a[3][4]={{1},{5},{9}};
//指定行内个别元素赋值
int a[3][4]={{1},{0,6},{0,0,11}};
//只初始化前两行
int a[3][4]={{1},{5,6}};
  1. 全部初始化,省略第一维长度,第二维不可省略
c 复制代码
int a[][4]={1,2,3,4,5,6,7,8,9,10,11,12};
//部分初始化,分行写,也可以省略第一维
int a [][4]={{0,0,3},{},{0, 10}};

二维数组元素引用

语法:数组名[行下标][列下标]

示例:ary[1][3] = 12;

示例:矩阵转置

c 复制代码
#include <stdio.h>
#define MAX_ROW 2
#define MAX_COL 3
void main()
{
    int i,j;
    int a[MAX_ROW][MAX_COL] = {{1,2,3},{4,5,6}};
    int b[MAX_COL][MAX_ROW];
    printf("Source:\n");
    for(i = 0;i < MAX_ROW;i++)
        for(j = 0;j < MAX_COL;j++) {
            printf("%4d",a[i][j]);
        }
    putchar('\n');

    //矩阵转置
    for(i = 0;i < MAX_ROW;i++)
    {
        for(j = 0;j < MAX_COL;j++)
        {
            b[j][i] = a[i][j];
        }
    }
    printf("\nResult:\n");
    for(i = 0;i < MAX_COL;i++) {
        for(j = 0;j < MAX_ROW;j++)
        {
            printf("%4d",b[i][j]);
        }
        putchar('\n');
    }
    return 0;
}

PART.03 字符数组和常用使用函数

C语言没有专门字符串类型,字符串依靠字符数组存储

  • 一维字符数组:保存1个字符串。
  • 二维字符数组:保存多个字符串,每一行代表一个字符串。
  • 字符串末尾自带\0空结束符,占用内存,但不计入字符串长度。

字符串变量初始化

c 复制代码
char str1[6] = "hello";
char str2[6] = {"hello"};
char str3[6] = {'h','e','l','l','o'};
char str4[] = {'h','e','l','l','o'}; //没有\0,不是C字符串
char str5[] = {'h','e','l','l','o','\0'};

注意:数组存储字符串,需要多预留1字节给\0

二维字符数组初始化

c 复制代码
char buf[2][10]={"aaa","bbb"};
char buf[][10]={"aaa","bbb"};
char buf[3][10]={"aaa","bbb"};
char buf[3][10]={0};

⚠️字符串数组易错点

c 复制代码
char str1[20] = "abc",str2[20];
str2 = str1;  //错误,数组名不能整体赋值
str2 = "abc"; //错误
str1 == str2; //错误,比较的是地址,不是字符串内容

可以下标访问单个字符:str[0] = 'H';

字符串I/O操作(stdio.h

  1. scanf("%s", str);:读取字符串,遇到空格停止。
  2. printf("%s", str);:输出字符串。
gets函数(读取带空格字符串,已废弃)
c 复制代码
char *gets(char *buffer);
  • 读取回车前全部字符,把换行符替换成\0
  • 参数:缓冲区首地址。>

风险:无缓冲区大小检查,容易越界。

puts函数
c 复制代码
int puts(const char *string);
  • 输出字符串,把末尾\0替换为换行,自动换行。
  • 参数:字符串首地址。

字符串处理函数(string.h

函数 作用
strlen() 获取字符串有效长度,不包含\0
strcpy(dst,src) 字符串拷贝,dst空间必须足够大
strcat(dst,src) 字符串拼接,src接到dst末尾,dst空间足够
strcmp(str1,str2) 字符串比较,逐字符ASCII对比 str1<str2返回负数;相等返回0;str1>str2返回正数
strchr() 查找字符
strstr() 查找子串,找到返回子串首指针,找不到返回NULL

strlensizeof区别:strlen看有效字符;sizeof获取整个数组内存字节。

stdlib.h 字符串转数值

函数原型 功能
double atof(const char *s); 字符串转浮点
int atoi(const char *s); 字符串转int整型
long atol(const char *s); 字符串转long长整型

sprintf格式化输出到字符数组

头文件:stdio.h

c 复制代码
int sprintf( char *buffer, const char *format [, argument] ... );
  • 将数字等格式数据转为字符串写入buffer字符数组。
  • 返回值:成功返回写入字符个数,失败返回EOF。

示例:

c 复制代码
sprintf(s, "%d", 123);                 //数字转字符串
sprintf(s, "%.3f", 3.1415626);         //控制浮点数格式
sprintf(s, "%s love %s.", "i", "CSDN");//拼接字符串
sprintf(s, "%p", &i);                  //打印地址信息
int len = sprintf(s, "%d", i);         //利用返回值获取字符串长度

PART.04 数组应用之卡牌游戏实战

功能需求

  1. 卡牌初始化:生成成对卡牌,打乱顺序
  2. 卡牌显示:未翻开显示#;翻开/匹配显示字符
  3. 翻卡牌:连续翻两张牌
  4. 判断卡牌是否配对
  5. 判断全部卡牌是否配对完成,游戏通关

核心变量定义

c 复制代码
#define ROW 4
#define COL 4
char cards[ROW][COL]={0};        //卡牌图案
int cardState[ROW][COL]={0};     //卡牌状态:0隐藏,1翻开,2已匹配
char cardChars[10]={"ABCDEFGH"}; //卡牌字符集合

卡牌初始化逻辑

  1. 生成成对字符放入二维数组。
  2. 使用随机数,交换数组元素,打乱卡牌顺序。
c 复制代码
int i=0,j=0,k=0,l=0;
int index=0;
char temp;
//生成成对卡牌
for (i=0;i<ROW;i++){
    for(j=0;j<COL;j+=2){
        cards[i][j]=cardChars[index];
        cards[i][j+1]=cardChars[index];
        index++;
    }
}
//打乱卡牌
srand(time(0));
for (i=0;i<ROW;i++){
    for(j=0;j<COL;j++){
        k = rand() % ROW;
        l = rand() % COL;
        //交换两个位置卡牌
        temp = cards[i][j];
        cards[i][j] = cards[k][l];
        cards[k][l]= temp;
    }
}

卡牌绘制函数draw()

逻辑:遍历二维数组,cardState==0输出#,否则输出卡牌字符,打印表格边框。

翻牌逻辑

  1. 输入第一张牌行列坐标,校验坐标合法,校验卡牌是否已经翻开。
  2. 输入第二张牌行列坐标,同样校验合法性。
  3. 判断两张卡牌图案:
    • 相等:设置状态为2(已匹配),匹配计数+1,打印匹配成功。
    • 不相等:休眠延时,恢复卡牌隐藏状态。
  4. 判断匹配计数到达总对数,游戏通关。
c 复制代码
//配对判断伪代码
if (cards[row1][col1] == cards[row2][col2]){
    printf("恭喜,找到一对匹配的卡片!\n");
    cardState[row1][col1] = 2;
    cardState[row2][col2]= 2;
    count+=1;
}else {
    printf("很遗憾,这两张卡片不匹配...\n");
    Sleep(1500);
}
draw();
if (count==8){
    printf("\n 恭喜你,通关啦!\n");
    break;
}

整体代码

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <windows.h>
#define ROW 4
#define COL 4

//定义存卡牌的二维数组
char cards[ROW][COL]={0};
char cardChar[9]="ABCDEFGH";//定义存放几种图案
char cardStates[ROW][COL]={0};//定义存放卡牌状态,0-隐藏,1-已翻开,2-已配对
//绘制卡牌游戏启动页面
void draw(){
	system("cls");
	int i=0,j=0;
	//绘制首行
	putchar('+');
	for(i=0;i<COL;i++){
		printf("---+");
	}
	printf("\n");
	for(j=0;j<ROW;j++){
		//画一行卡牌
		putchar('|');
		for(i=0;i<COL;i++){
			if(cardStates[j][i]==0){
				printf(" # |");
			}else{
				printf(" %c |",cards[j][i]);
			}
		}
		printf("\n");
		//画分割线
		putchar('+');
		for(i=0;i<COL;i++){
			printf("---+");
		}
		printf("\n");
	}
	
}
//1. 启动游戏,初始化卡牌
void setup(){
	int i=0,j=0;
	int index=0;
	//1. 初始化卡牌
	for(i=0;i<ROW;i++){
		for(j=0;j<COL;j+=2){
			cards[i][j]=cardChar[index];
			cards[i][j+1]=cardChar[index];
			index++;
		}
	}
	//2. 打乱
	srand(time(0));
	int k=0,l=0;
	char tempCh=0;
	for(i=0;i<ROW;i++){
		for(j=0;j<COL;j++){
			k=rand()%ROW;
			l=rand()%COL;
			tempCh=cards[i][j];
			cards[i][j]=cards[k][l];
			cards[k][l]=tempCh;
		}
	}
	
	//显示
	for(i=0;i<ROW;i++){
		for(j=0;j<COL;j++){
			printf("%c",cards[i][j]);
		}
		printf("\n");
	}
}

//第一个执行的函数
int main(){
	int row1,col1,row2,col2;
	int count=0;//配对数
	//1. 启动游戏,初始化卡牌
	setup();
	
	//2. 显示卡牌
	draw();
	
	while(1){
		
		//3. 翻第一张卡牌
		while(1){
			printf("已使用翻牌次数:0;已完成的配对数:0/8\n");
			printf("请输入要翻牌的位置,以空格隔开(1,1):" );
			scanf("%d %d",&row1,&col1);
			//判断输入是否越界
			if(row1<0||row1>=ROW||col1<0||col1>=COL){
				printf("请重新输入!\n");
			}else if(cardStates[row1][col1]!=0){
				printf("该卡牌被翻过,请重新输入!\n");
				Sleep(1500);
			}
			else{
				break;
			}			
		}

		//4. 显示第一张卡牌
		cardStates[row1][col1]=1;
		draw();
		
		//5. 翻第二张卡牌
		while(1){
			printf("已使用翻牌次数:0;已完成的配对数:%d/8\n",count);
			printf("请输入要翻牌的位置,以空格隔开(1,1):" );
			scanf("%d %d",&row2,&col2);
			//判断输入是否越界
			if(row2<0||row2>=ROW||col2<0||col2>=COL){
				printf("请重新输入!\n");
			}else if(cardStates[row2][col2]!=0){
				printf("该卡牌被翻过,请重新输入!\n");
				Sleep(1500);
			}else{
				break;
			}			
		}
		//6. 显示第二张卡牌
		cardStates[row2][col2]=1;
		draw();
		//7. 判断2张卡牌是否配对,配对计数,否则重新翻牌
		if(cards[row1][col1]==cards[row2][col2]){
			cardStates[row1][col1]=2;
			cardStates[row2][col2]=2;
			count++;
		}else{
			cardStates[row1][col1]=0;
			cardStates[row2][col2]=0;
			Sleep(1500);
		}
		draw();
		//8. 判断是否都配对完成,游戏是否通关
		if(count==8){
			printf("恭喜你,通过成功!\n");
			break;
		}
	}
	
	return 0;
}

总结

  1. 数组:相同类型连续内存集合,下标从0,C语言不做越界检查;数组名代表首地址,不能整体赋值、直接比较。
  2. 二维数组:行优先存储,初始化可以省略第一维长度,第二维不能省略。
  3. C字符串:字符数组存储,必须以\0结尾;不能直接赋值、比较,使用string.h库函数操作。
  4. 实战:二维数组存储游戏地图/卡牌,使用状态数组记录每个格子状态。
相关推荐
正在走向自律18 分钟前
从 Python 基础到大模型落地:读《深入浅出 Python 人工智能》,吃透 AI 工程化 CRUD 实战
开发语言·人工智能·python·机器学习·知识脉络
被摘下的星星35 分钟前
Java 中 .length、.length() 以及 .size() 的区分
java·开发语言
geovindu1 小时前
CSharp: State Pattern
开发语言·后端·c#·.net·状态模式·行为模式
小欣加油1 小时前
Leetcode2058 找出临界点之间的最小和最大距离
数据结构·c++·算法·leetcode·职场和发展
山岚的运维笔记1 小时前
mysql 专业笔记 -- 第 1 章:MySQL 入门
运维·数据库·笔记·后端·学习·mysql·dba
IT小白杨1 小时前
TikTok小店防封号浏览器实战:2026年多店铺环境隔离与账号安全运营技术解析
开发语言·数据库·安全·php·chrome devtools·指纹浏览器
2601_966949651 小时前
Python 量化开发为什么适合使用金融数据 SDK?从数据获取到策略研究的工程化实践
开发语言·python·数据分析·量化交易·股票数据·quantdash
万年咸鱼1 小时前
Java ArrayList 详解:原理、用法与实战
java·开发语言·windows
长友cy1 小时前
Qt官方示例Application源码阅读
开发语言·qt