
数组和字符串深度解析
学习目标
- 一维数组的定义和使用
- 二维数组的定义和使用
- 字符数组和常用使用函数
- 数组应用案例卡牌游戏实战
PART.01 一维数组的定义和使用
为什么使用数组和数组的概念
- 数组是一组有序的、类型相同的数据的集合,集合内的数据称为数组元素。
- 每个数组拥有数组名,数组名代表数组的起始地址。
- 数组元素通过索引/下标标识,下标从0开始。
数组定义
数组使用前必须定义
语法:
存储类型 数据类型 数组名[数组长度];
示例:int player[11];
- 数组长度只能是常量、常量表达式,且大于0。
- 全部数组元素类型保持一致。
- 数组元素可以使用在普通变量能够使用的任意位置。
一维数组内存存放:地址连续。
初始化数组
-
extern、static修饰的数组元素会自动初始化为0;普通自动数组元素不会自动初始化。 -
数组定义时初始化语法:
类型 数组名[常量表达式] = {值, 值 ...};
示例:
c
int ary[10]={0,1,2,3,4,5,6,7,8,9};
初始化规则
- 多个元素之间逗号分隔。
- 仅给部分元素赋值,剩余元素自动赋值为0。
- 全部元素赋值,可省略数组长度,编译器自动推导长度。
示例代码
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];
}
思考题
- 定义
int Grade[5];,循环访问Grade[6],编译器不会报错,下标越界。 Grade1 == Grade2:数组名是地址,直接比较比较的是地址,不是数组内容。ary2 = ary1;:不能直接把一个数组整体赋值给另一个数组。ary = 1;、ary ={1,2,...};:不能对数组名整体赋值。
数组使用注意事项
- C编译器不检查数组下标越界。
- 数组之间不能直接用
==判断内容是否相等。 - 不能直接将一个数组整体赋值给另一个数组。
- 不能对数组整体赋值,只能逐个元素操作。
PART.02 二维数组的定义和使用
应用场景
学生成绩管理,存储多名学生的多门科目成绩,类似表格,行、列结构。
二维数组定义
语法:
类型说明符 数组名[常量表达式1][常量表达式2];
示例:int temp[3][3];
内存存储规则:按行优先顺序连续存放,先存第一行全部元素,再存第二行。
二维数组初始化4种方式
- 分行初始化
c
int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
- 全部写在一个大括号,按内存顺序赋值
c
int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
- 部分元素初始化,未赋值元素置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}};
- 全部初始化,省略第一维长度,第二维不可省略
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)
scanf("%s", str);:读取字符串,遇到空格停止。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 |
strlen和sizeof区别: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 数组应用之卡牌游戏实战
功能需求
- 卡牌初始化:生成成对卡牌,打乱顺序
- 卡牌显示:未翻开显示
#;翻开/匹配显示字符 - 翻卡牌:连续翻两张牌
- 判断卡牌是否配对
- 判断全部卡牌是否配对完成,游戏通关
核心变量定义
c
#define ROW 4
#define COL 4
char cards[ROW][COL]={0}; //卡牌图案
int cardState[ROW][COL]={0}; //卡牌状态:0隐藏,1翻开,2已匹配
char cardChars[10]={"ABCDEFGH"}; //卡牌字符集合
卡牌初始化逻辑
- 生成成对字符放入二维数组。
- 使用随机数,交换数组元素,打乱卡牌顺序。
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输出#,否则输出卡牌字符,打印表格边框。
翻牌逻辑
- 输入第一张牌行列坐标,校验坐标合法,校验卡牌是否已经翻开。
- 输入第二张牌行列坐标,同样校验合法性。
- 判断两张卡牌图案:
- 相等:设置状态为2(已匹配),匹配计数+1,打印匹配成功。
- 不相等:休眠延时,恢复卡牌隐藏状态。
- 判断匹配计数到达总对数,游戏通关。
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;
}
总结
- 数组:相同类型连续内存集合,下标从0,C语言不做越界检查;数组名代表首地址,不能整体赋值、直接比较。
- 二维数组:行优先存储,初始化可以省略第一维长度,第二维不能省略。
- C字符串:字符数组存储,必须以
\0结尾;不能直接赋值、比较,使用string.h库函数操作。 - 实战:二维数组存储游戏地图/卡牌,使用状态数组记录每个格子状态。