存储类型_malloc_typedef小结

一、存储模型

存储类型(storage class)

变量可以通过

生存周期(内存占用时间->静态与动态)

作用域(有效区域->全局与静态)、连接点来描述。

auto

普通局部变量,是自动存储,变量会自动分配和释放,函数内的变量复合语句内声明的变量,在系统运行时分配空间赋初值,在调用结束时释放空间,这类变量就是auto变量,

auto变量由系统自动分配在 栈空间。auto可以省略。

auto变量在代码块内。

栈空间值不初始化数值不确定。

全局变量不能声明为auto类型,因为两者在内存分配上机制不同。

1) static

 

静态数据类型,定义的数据存放在全局数据区,编译的时赋初值,程序结束才释放空间。没有初始化的static变量值为0

函数形参不能声明为静态类型,函数形参通过堆栈完成,用于支持递归调用。

函数外定义的变量会具有内部链接属性,作用域从定义开始到文件结束 非本文件不能引用。

函数内定义的static变量是空链接属性,作用域在本函数。

静态函数是在函数前加static,汇编时只是把.globle符号去掉,使函数只在本文件生效,参数依然进出栈。

static

其实这个关键字有三个作用,而不仅仅是存储类型。请看下面代码:

// 1.修饰函数,使其只能在本文件可见
static void func(void)
{
    static int n = 0; // 2. 修饰局部变量,使其存储在静态区(存储类型)
    printf("%d\n", n);
}

static int global; // 3. 修饰全局变量,使其只能在本文件可见

注意到,staticC语言中的三个作用,其中第1和第3个作用其实都是一样的,改变的是函数或者变量的可见范围。

只有当用static来修饰局部变量的时候,它的作用才是代表一个存储区域。

 

2) register

请求编译器将变量保存在寄存器中,从而加快程序的运行,对于频繁使用的变量,存放在寄存器中可以减少和内存读取(程序中遇到变量时用控制器发指令将变量的值送到运算器中,需要存数再保存到内存中)

register只能对局部变量和函数形参声明(全局变量不可申请register类型)???

register不能取址, register int j; int *p=&j;是不允许的,因为无法对寄存器定制。

register申请存放在寄存器时,不一定成功,因为寄存器数量有限, 在申请失败时,会默认成auto类型。

cpu的寄存器对数据类型有限制,不是所有类型变量都可以申请为寄存器类型。如:有些系统只支持将intchar和指针变量定义为register

3) extern

extern把变量说明为外部变量,表示这些变量已在其它文件中定义,编译系统不再为它们分配内存空间

 

1.作用域(scope)

作用域是变量在程序中被使用的区域。编译确认不同的类型的作用域有4种:

文件作用域、函数、代码块(block scope)、原型作用域(prototype scope)

代码块作用域: 是在大括号{ }内定义的部分;

函数作用域: 只适用于goto语句的语句标签,且函数中的所有语句标签必须唯一。

文件作用域: 任何在函数外声明的变量都具有文件作用域(全局变量)

原型作用域:  函数原型中声明的参数名,编译器只检查参数类型,不关心参数名。

局部变量()

函数内部定义的变量,只在本函数有效。复合语句中定义的变量在复合语句内有效;

void main()

{

  int m, n;

}

char fun(int x, int y)

{ int i, j;

}

float fun(int a)

{ int  b, c;

}

void main(){

 int a, b;

 { int c;

  c = a+b;

  }

m, n 只在主函数中有效。不在整个程序中有效,函数平行。

形参也是局部变量,在整个行数中有效。

不同函数可以定义相同名字的变量,(占用的内存不同)

a,b在整个main函数中有效,c在复合大括号中有效,离开复合语句就会无效。

 

4) 全局变量(文件)

在函数之外定义的变量为全局变量(外部变量)。全局变量可以被其他文件调用,有效范围从定义到本源文件结束,全局变量不属于某个函数,作用整个源程序。

p,q,c1,c2都是全局变量,全局变量的作用域从定义的时候开始生效。

当一个局部变量和全局变量的变量名相同时,会优先选择局部变量。

int a = 11, b = 22;

int main(void){

int a = 123

小提示:

   全局变量在程序运行过程中一直占用存储单元。

   一般我们会把全局变量的首字母大写(编程习惯)

   全局变量和外部变量是从不同角度提出的,全局是从作用提出的,外部是存储方式提出的,表示生存周期。

 

2.链接

链接与作用域

多个源文件编译后,生成的目标文件以及从库函数中引用的函数会经过链接器进行链接,生成可执行文件。为了区分不同文件中的相同变量名是否为同一个变量,要用变量的链接属性(linkage)决定。

作用域是对文件内的变量的范围作标识,链接则是在不同文件间共享。

5) 链接属性(针对声明而言的)

Ø 外部(external)  多个文件中声明,表示同一个对象或函数。

Ø 内部(internal)  可在本文件声明,static标示,在文件中使用。

Ø 空链接(none)  不同区域声明的变量属于独立的对象。

6) 链接关键字:externstatic

两个关键字用来修改变量的链接属性。

在一个默认的外部变量前面加上static后,将链接属性更改为内部链接。

int g = 5; // 文件作用域,链接属性为extern

static int do = 3;  // 文件作用域,更改为internal

int main(int argc , char * argv[]) // static将变量do限定在了本文件,防止其 他项目文件引用或造成变量重名错误。

Ps

static只对缺省链接属性为external的声明才有改变链接属性的效果。

extern 关键字可以声明一个变量,告诉要使用它的文件或函数,这个变量在别的文件内声明,这里只是引用。所以,extern扩大了一个标示符的作用域。如定义一个全局变量gm, 之后的函数可以使用gm,前面的函数就不能使用gm,加上extern 声明扩到作用域到此函数,则可以使用。

 

C和指针(中文版)》上一道题,P49

假如想着同一个源文件里写两个函数xy,需要满足以下条件:

名字        类型       存储类型        链接类型       作域                            初始值

a              int              static            external          x可以访问,y不能           1

b              char           static            none               x,y都可以访问                    2

c              int              automatic     none              x的局部变量                        3

d             float           static            none              x的局部变量                         4

所有初始化都在声明中完成,而不是通过函数完成。

a链接属性external,应该位于所有代码块外面,但由于x可以访问,y不能,所以要出现在y函数后面,x函数前面;

b链接类型为none,则应该位于代码块内,具有代码块作用域。但是由于xy都能访问到,所以x需要调用y,或反之。。(笔者以为,b的链接类型应该是internal,且位于文件起始处)

c,d明显要处于x的代码块内,且一个为static变量,一个automatic变量。

static int b = 2;

void y()

{

    ...

}

int a =1;

void x()

{

    int c = 3;

    static d = 4;

}

 

3.内部函数与外部函数

静态函数

在函数名前加上类型关键字static就会将函数标记为内部函数,只能在本文件调用.

static int  * fun(int x, int b);  //static只改变了函数的作用域,并没有改变出入栈。

内部函数也叫静态函数, 一个工程多个文件时,内部函数可以很好避免不同开发人员定义函数重名的干扰。

7) 外部函数

函数首部加extern关键字就表示函数为外部函数,可以供其他文件调用。

extern  int  fun(int a, int b);

extern 可以省略,编译器会默认函数是外部函数。在调用文件中加extern对函数声明,则表示该函数已经在别的源文件中定义过。

 

小结:”存储模型.c”--C和指针》(中文版)

二、预处理命令

C语言中预处理功能有三种:

Ø 宏定义

Ø 条件编译

Ø 文件包含

宏定义

不带参数的宏

#define 标识符  字符串

#undef  标识符

宏只是简单替换, 不是C语句,不需要在定义后加分号。

8) 带参数的宏

#define 宏名(参数表)  字符串

常用宏 putchar()/getchar

与函数区别:

  -- 宏引用占用编译时间,不占运行时间;

  -- 引用宏没有返回值。

  -- 宏名与形参无类型,只是符号代表。

4.条件编译

#ifdef  标识符

codes1

#else

codes2

#endif

#ifdef  标识符

codes1

#endif

#ifndef  标识符

   codes1

#else

   codes2

#endif

#if  表达式

   codes1

#else

   codes2

#endif

#define CPU_BIT  

#ifndef  CPU_BIT

   #define INT_SIZE   16

#else

   #define INT_SIZE   32

#endif

//前面应该有语句   #define  CPU_BIT  X

//X可为任意值,也可省略,只要定义就ok

表达式的值为真则选择codes1.

否则选择codes2.

 

5.文件包含

#include 将一个源文件包含到本文件中。

两种形式:

#include <filename> 编译器从标准路径中搜索filename.h  /usr/include

#include “filename” 从当前目录或指定目录到标准目录的顺序搜索。

gcc source.c  -I ./include  // -I 指定本地头文件目录

#include “file1.c” 在预处理阶段,file.c 会被替换进主文件,编程同一个文件。编译时不需要将file1.c一同编译,否则会报多重定义错误。

思考:

怎样防止一个头文件被多次包含?

#ifndef   __S5PC100_H__

#define   __S5PC100_H__

eg

#define DEBUG_PRINT    printf(“File %s line%d:” \

”x=%d,y=%d,z=%d”,\

__FILE__,__LINE__,\

x,y,z)

//相邻字符串常量自动连接为一个字符串。不要在宏定义的后面加上分号。否则在某些语句中容易形成空语句。if() DEBUG_PRINT ;  else....。如果加了就是多个语句。

 

三、typedef

typedef定义别名

typedef  signed char     int8_t  定义有符号八位数据类型

typedef  unsigned   short    uint16_t   short的重命名

typedef  int  INT32;

 

6.结构体重命名

typedef  struct  info

{

uint16_t  max_numb;

uint16_t  numb;

char  *title;

}Menu ;

Menu  menu1; // == struct info  menu1;定义一个struct info 的变量

Menu  *pm;  // == struct info  *pm; 定义一个指向struct info 类型的指针

 

7.数组:

typedef  int NUM[100];

NUM   array; //   int  array[100];

8.指针:

typedef  char *STRING; //STRING 为字符串指针类型;

STRING p, s[10]// char *p指针变量,  char *s[10] 指针数组。

9.函数:

typedef  int (* POINTER)() //POINTER 为指向函数的指针 类型。

POINTER  p1, p2;   (p1p2POINTER 类型的指针变量)

 

优点:定义别名可增加代码的可移植性.

      typedef  int INT16; cpu位数变化后可重新定义typedef  short  INT16;

      time()函数的返回值为time_t  (unsigned long / unsigned int)类型。

   表达式简洁。

typdef 定义时占用了存储类型的位置,编译会报错.所以typedef不能和autoexternstaticregister一同使用。

 

eg:C程序设计》

int a[10], b[10], c[10], d[10];

typedef  int ARR[10];

ARR a, b, c, d; //后面定义就方便了很多。

 

 

四、子函数malloc申请空间返回

子函数返回堆空间地址

通过返回值:

关注函数的定义与变量的定义:

 

 

9) 通过变量:

关注变量的定义、传递、形参的定义:

 

 

10.空宏、do{...}while(0);

空宏即没有值的宏定义,如:

#define __copyright__

可以在函数程序中加上空宏来起到说明的作用,空宏会在编译过程中替换成空;

 

do...while演变:

Ø #define  macr1 code1;\

 code2;

if (true) macr; // 宏替换后code2将不是if后的执行语句;

Ø #define  macr2    {code1; \

 code2;}

if (true)

macr2;   //开发者会习惯性地加上分号。

else

...;

替换后多了一个分号,else将没有if与之匹配。(演示前先明确if..else..使用中没有多余分号)

 

Ø #define MACR    do{...}while(0);

无论如何都会执行一次,完美解决以上问题。

11.sizeof(struct mytype);

结构体对齐补充

linux系统中,按照当前结构体成员最大类型对齐,当超过4字节时,按照4字节对齐,不足4字节时,按照当前最大的对齐。

  

10) 嵌套结构体对齐

 

结构体变量成员的偏移量必须是成员大小的整倍数。

 

12.子函数获取局部变量地址

 

 

 

 

#define _CRT_SECURE_NO_WARNINGS #pragma comment(linker, "/subsystem:windows /entry:mainCRTStartup") #include <graphics.h> // EasyX图形库头文件 #include <conio.h> #include <stdio.h> #include <stdlib.h> #include <windows.h> #include <time.h> // 宏定义 #define TRUE 1 #define FALSE 0 #define MAX_SIZE 7 // 最大拼图规模 #define N 9 // 拼图块种类数 #define ROWS 3 // 行数 #define COLS 3 // 列数 // 全局变量 int WINDOW_WIDTH = 1000; // 窗口宽度 int WINDOW_HEIGHT =1000; // 窗口高度 int BUTTON_X = 300; // 按钮X坐标 int BUTTON_Y = 200; // 按钮Y坐标 int BUTTON_WIDTH = 200; // 按钮宽度 int BUTTON_HEIGHT = 50; // 按钮高度 int degrees[] = { 3,4,5,6,7 }; // 难度级别对应的拼图尺寸 char str[N][1000]; // 存储传统文化描述的数组 int key = 0; // 全局键值变量 ExMessage msg; // 消息结构体变量 // 新增计时相关全局变量 time_t start_time; // 游戏开始时间 int is_time_up = FALSE; // 超时标志 const int TIMELIMIT = 120; // 2分钟倒计时(秒) // 可选图片结构体 typedef struct { const char* name; // 图片名称 int num; // 图片编号 } Pictures; // 图片资源数组 Pictures picture[] = { {"京剧脸谱",1}, {"中国结",2}, {"剪纸",3}, {"书法",4}, {"国画",5}, {"陶瓷",6}, {"刺绣",7}, {"皮影",8}, {"灯笼",9} }; // 拼图游戏结构体 typedef struct { int size; //拼图规模 int matrix[MAX_SIZE][MAX_SIZE]; //拼图块版块 int emptyRow, emptyCol; //空白格位置 int moves; //移动步数 int picIndex; //选择的图片索引 int species[N]; //选择的图片 } Blocks; void ReadData();//数据读取函数(从文件读取传统文化描述) void WriteSingleSelectedData(int i);//显示单个传统文化描述 void WriteAllData();//显示所有传统文化描述 void GetImage(const char* filename, int lx, int ly);//加载并显示图片 void cut_image(const char* filename, int size);//切割图片函数(备用) int isClick(int x, int y);//按钮点击检测函数 void Button(int, int, const char* word);//绘制按钮函数 int ModeSelection();//模式选择界面 int Selectpicture();//图片选择界面 void Information(int key);//游戏说明界面 void InitPuzzle(Blocks* game, int size, int picIndex);//初始化拼图游戏 int CheckWin(Blocks* game);//检查是否胜利 int MoveTile(Blocks* game, int direction);//移动拼图块 void DrawPuzzle(Blocks* game);//绘制拼图界面 void ShowWinScreen(Blocks* game);//显示胜利界面 void ShowTimeOverScreen();//显示超时界面 void PuzzlePage(int degree, int picIndex);//拼图游戏主循环 void Menu();//菜单 void DisplayTraditionalElements(); int main() { // 初始化随机数种子 srand((unsigned)time(NULL)); // 读取传统文化描述数据 ReadData(); // 进入主菜单循环 while (TRUE) { Menu(); } closegraph();//关闭图形窗口 return 0; } //数据读取函数(从文件读取传统文化描述) void ReadData() { FILE* file = fopen("data.txt", "r"); if (file == NULL) { // 文件打开失败时使用默认文本 for (int i = 0; i < N; i++) { sprintf(str[i], "元素 %d 描述加载失败", i + 1); } return; } // 逐行读取描述 for (int i = 0; i < N; i++) { if (fgets(str[i], sizeof(str[i]), file) != NULL) { // 去除换行符 size_t len = strlen(str[i]); if (len > 0 && str[i][len - 1] == '\n') { str[i][len - 1] = '\0'; } } } fclose(file); } //显示单个传统文化描述 void WriteSingleSelectedData(int i) { outtextxy(0, 0, str[i]); } //显示所有传统文化描述 void WriteAllData() { for (int i = 0; i < N; i++) { outtextxy(i, i * 5, str[i]); } } //加载并显示图片 void GetImage(const char* filename, int lx, int ly) { IMAGE image; loadimage(&image, filename); putimage(lx, ly, &image); } //切割图片函数 void cut_image(const char* filename, int size) { IMAGE img; loadimage(&img, filename); // 加载图片 if (img.getwidth() == 0 || img.getheight() == 0) { // 加载失败 outtextxy(100, 100, "图片加载失败!"); return; } // 获取图片宽度和高度 int width = img.getwidth(); int height = img.getheight(); //目标宽高 int targetWidth = 200; int targetHeight = 200; // 定义切割块的大小 const int blockWidth = width / size; const int blockHeight = height / size; // 分配二维指针数组 IMAGE*** blocks = (IMAGE***)malloc(size * sizeof(IMAGE*)); for (int i = 0; i < size; i++) { blocks[i] = (IMAGE**)malloc(size * sizeof(IMAGE*)); for (int j = 0; j < size; j++) { blocks[i][j] = (IMAGE*)malloc(sizeof(IMAGE)); /*切割*/ // 初始化每个块的大小 blocks[i][j] = new IMAGE(blockWidth, blockHeight); // 从原图中截取对应区域 // 切换到img作为绘图设备 SetWorkingImage(&img); getimage(blocks[i][j], j * blockWidth, i * blockHeight, blockWidth, blockHeight); SetWorkingImage(); // 恢复到窗口 Resize(blocks[i][j], targetWidth, targetHeight); } } for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { //putimage(200 + blockWidth * i, 200 + blockHeight * j, blocks[i][j]); if (i == size - 1 && i == j) { setfillcolor(RGB(30, 30, 30)); solidrectangle(100 + targetWidth * i, 100 + targetHeight * j, targetWidth + 100 + targetWidth * i, targetHeight + 100 + targetHeight * j); return; } putimage(100 + targetWidth * i, 100 + targetHeight * j, blocks[j][i]); setlinecolor(RGB(0, 0, 0)); rectangle(100 + targetWidth * i, 100 + targetHeight * j, targetWidth + 100 + targetWidth * i, targetHeight + 100 + targetHeight * j); } } //释放内存 for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { delete blocks[i][j]; } free(blocks[i]); } free(blocks); } //按钮点击检测函数 int isClick(int x, int y) { // 检查鼠标消息队列中的所有消息 while (peekmessage(&msg, EX_MOUSE)) { if (msg.message == WM_LBUTTONDOWN) { // 检查是否在按钮区域内 int inArea = (msg.x >= BUTTON_X + x && msg.y >= BUTTON_Y + y && msg.x <= BUTTON_X + x + BUTTON_WIDTH && msg.y <= BUTTON_Y + y + BUTTON_HEIGHT); // 移除已处理的消息 while (peekmessage(&msg, EX_MOUSE)); return inArea; } // 移除其他鼠标消息 peekmessage(&msg, EX_MOUSE, PM_REMOVE); } return FALSE; } //绘制按钮函数 void Button(int x, int y, const char* word) { //设置按钮样式 setbkmode(TRANSPARENT);//设置背景模式透明 setfillcolor(RGB(231, 250, 235)); solidroundrect(BUTTON_X + x, BUTTON_Y + y, BUTTON_X + x + BUTTON_WIDTH, BUTTON_Y + y + BUTTON_HEIGHT, 20, 20);//透明边框矩形图 // 设置文字样式 settextstyle(50, 15, "华文宋体"); setbkmode(TRANSPARENT); settextcolor(RGB(35, 30, 30)); // 计算文字居中位置输出 int w = (BUTTON_WIDTH - textwidth(word)) / 2; int h = (BUTTON_HEIGHT - textheight(word)) / 2; outtextxy(BUTTON_X + x + w, BUTTON_Y + y + h, word); } //模式选择界面 int ModeSelection() { cleardevice(); GetImage("picture/bk.jpg", 0, 0); // 五种难度按钮 const char* str[] = { "简单模式","初级模式","中级模式","高级模式","终极模式" }; for (int i = 0; i < 5; i++) { Button(100, i * 100, str[i]); } // 显示提示信息 settextstyle(35, 20, "华文宋体"); outtextxy(300, 120, "请点击选择游戏难度"); // 等待选择 while (TRUE) { // 处理所有鼠标消息 while (peekmessage(&msg, EX_MOUSE)) { if (msg.message == WM_LBUTTONDOWN) { // 检查点击了哪个按钮 for (int i = 0; i < 5; i++) { int buttonTop = BUTTON_Y + i * 100; int buttonBottom = buttonTop + BUTTON_HEIGHT; if (msg.x >= BUTTON_X && msg.x <= BUTTON_X + BUTTON_WIDTH && msg.y >= buttonTop && msg.y <= buttonBottom) { return degrees[i]; } } } // 移除已处理的消息 peekmessage(&msg, EX_MOUSE, PM_REMOVE); } Sleep(10); } } //图片选择界面 int Selectpicture() { cleardevice(); const int startX = 0, startY = 10; int selected = -1; // 3x3网格显示9种传统文化图片选项 for (int i = 0; i < 9; i++) { int row = i / 3, col = i % 3; Button(startX + col * 180, startY + row * 150, picture[i].name); } // 显示提示信息 settextstyle(35, 20, "华文宋体"); outtextxy(300, 120, "请点击选择拼图图片"); // 等待选择 while (selected == -1) { // 处理所有鼠标消息 while (peekmessage(&msg, EX_MOUSE)) { if (msg.message == WM_LBUTTONDOWN) { // 检查点击了哪个图片按钮 for (int i = 0; i < 9; i++) { int row = i / 3, col = i % 3; int buttonX = BUTTON_X + col * 200; int buttonY = BUTTON_Y + row * 150; if (msg.x >= buttonX && msg.x <= buttonX + BUTTON_WIDTH && msg.y >= buttonY && msg.y <= buttonY + BUTTON_HEIGHT) { selected = i; break; } } } // 移除已处理的消息 peekmessage(&msg, EX_MOUSE, PM_REMOVE); } Sleep(10); } return selected; } //游戏说明界面 void Information(int key) { while (true) { cleardevice(); GetImage("picture/bk.jpg", 0, 0); // 设置文本样式 settextcolor(RGB(255, 215, 0)); // 金色 setbkmode(TRANSPARENT); settextstyle(25, 10, "华文宋体"); // 游戏说明文本 const char instructions[] = "这是一个传统文化主题的数字拼图游戏。\n" "目标是通过移动拼图块,使所有数字按顺序排列,右下角为空白格。\n" "每个数字对应一种中国传统文化元素:\n" "1: 京剧脸谱\n2: 中国结\n3: 剪纸\n" "4: 书法\n5: 国画\n6: 陶瓷\n" "7: 刺绣\n8: 皮影\n9: 灯笼\n\n" "操作方式:\n" "方向键 - 移动拼图\n" "ESC - 退出游戏\n" "L - 查看传统文化元素列表\n" "点击右下角按钮返回主菜单..."; // 计算文本位置 int startX = 50, startY = 100; int lineHeight = 50; // 分行输出文本 char* context = NULL; char* line = strtok_s((char*)instructions, "\n", &context); while (line != NULL) { outtextxy(startX, startY, line); startY += lineHeight; line = strtok_s(NULL, "\n", &context); } // 绘制返回按钮 Button(400, 500, "返回主菜单"); // 处理事件 bool exitLoop = false; while (!exitLoop) { // 处理所有类型的事件 if (peekmessage(&msg, EX_MOUSE | EX_KEY)) { // 按键处理 if (msg.message == WM_KEYDOWN) { if (msg.vkcode == 'L' || msg.vkcode == 'l') { // 显示传统文化元素列表 DisplayTraditionalElements(); exitLoop = true; // 退出内层循环,重新绘制当前界面 } else { // 其他任意键返回主菜单 return; } } // 鼠标处理 if (msg.message == WM_LBUTTONDOWN) { // 检查是否点击了返回按钮 if (msg.x >= BUTTON_X + 400 && msg.x <= BUTTON_X + 400 + BUTTON_WIDTH && msg.y >= BUTTON_Y + 500 && msg.y <= BUTTON_Y + 500 + BUTTON_HEIGHT) { return; } } // 移除已处理的消息 peekmessage(&msg, EX_MOUSE | EX_KEY, PM_REMOVE); } Sleep(10); } } } // 显示传统文化元素列表 void DisplayTraditionalElements() { cleardevice(); GetImage("picture/bk.jpg", 0, 0); // 设置标题样式 settextcolor(RGB(255, 215, 0)); // 金色 setbkmode(TRANSPARENT); settextstyle(50, 25, "华文行楷"); outtextxy(300, 30, "传统文化元素列表"); // 设置文本样式 settextstyle(25, 10, "华文宋体"); settextcolor(WHITE); // 显示所有元素描述 int yPos = 100; const int lineHeight = 40; for (int i = 0; i < N; i++) { // 显示元素编号和名称 char title[50]; sprintf(title, "%d: %s", i + 1, picture[i].name); outtextxy(50, yPos, title); // 显示元素描述 outtextxy(100, yPos + lineHeight, str[i]); yPos += 3 * lineHeight; // 增加间距 } // 添加返回按钮 Button(400, WINDOW_HEIGHT - 150, "返回"); // 等待返回 while (true) { // 处理所有事件 while (peekmessage(&msg, EX_MOUSE | EX_KEY)) { // 任意键或点击返回 if (msg.message == WM_KEYDOWN || (msg.message == WM_LBUTTONDOWN && msg.x >= BUTTON_X + 400 && msg.x <= BUTTON_X + 400 + BUTTON_WIDTH && msg.y >= BUTTON_Y + (WINDOW_HEIGHT - 150) && msg.y <= BUTTON_Y + (WINDOW_HEIGHT - 150) + BUTTON_HEIGHT)) { // 移除所有待处理消息 while (peekmessage(&msg, EX_MOUSE | EX_KEY, PM_REMOVE)); return; } // 移除已处理的消息 peekmessage(&msg, EX_MOUSE | EX_KEY, PM_REMOVE); } Sleep(10); } } //初始化拼图游戏 void InitPuzzle(Blocks* game, int size, int picIndex) { game->size = size; game->picIndex = picIndex; game->moves = 0; // 初始化拼图数据 int num = 1; for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { game->matrix[i][j] = (i == size - 1 && j == size - 1) ? 0 : num++; } } game->emptyRow = size - 1; game->emptyCol = size - 1; // 随机打乱拼图(减少循环次数,避免卡顿) for (int i = 0; i < size * size * 20; i++) { // 原为100,现为20 int dir = rand() % 4; int newRow = game->emptyRow, newCol = game->emptyCol; switch (dir) { case 0: newRow--; break; // 上 case 1: newRow++; break; // 下 case 2: newCol--; break; // 左 case 3: newCol++; break; // 右 } // 检查移动是否有效 if (newRow >= 0 && newRow < size && newCol >= 0 && newCol < size) { // 交换空白格和目标格 game->matrix[game->emptyRow][game->emptyCol] = game->matrix[newRow][newCol]; game->matrix[newRow][newCol] = 0; game->emptyRow = newRow; game->emptyCol = newCol; } } // 记录开始时间 start_time = time(NULL); is_time_up = FALSE; // 重置超时标志 } //检查是否胜利 int CheckWin(Blocks* game) { int num = 1; for (int i = 0; i < game->size; i++) { for (int j = 0; j < game->size; j++) { // 检查右下角是否为空白格 if (i == game->size - 1 && j == game->size - 1) { if (game->matrix[i][j] != 0) return FALSE; } else { // 检查其他格子是否按顺序排列 if (game->matrix[i][j] != num++) return FALSE; } } } return TRUE; } //移动拼图块 int MoveTile(Blocks* game, int direction) { int newRow = game->emptyRow; int newCol = game->emptyCol; // 计算目标位置 switch (direction) { case 0: newRow--; break; // 上 case 1: newRow++; break; // 下 case 2: newCol--; break; // 左 case 3: newCol++; break; // 右 default: return FALSE; } // 检查边界 if (newRow < 0 || newRow >= game->size || newCol < 0 || newCol >= game->size) { return FALSE; } // 交换空白格和目标格 game->matrix[game->emptyRow][game->emptyCol] = game->matrix[newRow][newCol]; game->matrix[newRow][newCol] = 0; game->emptyRow = newRow; game->emptyCol = newCol; game->moves++; return TRUE; } // 绘制拼图界面(续) void DrawPuzzle(Blocks* game) { cleardevice(); GetImage("picture/bk.jpg", 0, 0); // 设置文本样式 settextstyle(30, 15, "华文宋体"); setbkmode(TRANSPARENT); // 显示标题和步数 settextcolor(WHITE); outtextxy(50, 20, "传统文化拼图游戏"); char movesText[50]; sprintf(movesText, "步数: %d", game->moves); outtextxy(50, 60, movesText); // 显示倒计时(调整位置,避免超出窗口) time_t current_time = time(NULL); int elapsed = (int)(current_time - start_time); int remaining = TIMELIMIT - elapsed; if (remaining < 0) remaining = 0; // 防止负数显示 char time_str[50]; sprintf(time_str, "剩余时间: %02d:%02d", remaining / 60, remaining % 60); // 时间小于10秒时显示红色警告 settextcolor(remaining <= 10 ? RED : WHITE); outtextxy(WINDOW_WIDTH - 300, 20, time_str); // 仅设置超时标志,不直接跳转界面 if (remaining == 0) { is_time_up = TRUE; } // 计算拼图块大小和位置 int blockSize = 80; int startX = (WINDOW_WIDTH - game->size * blockSize) / 2; int startY = 150; // 加载选中的图片 char filename[100]; sprintf(filename, "picture/%d.jpg", game->picIndex + 1); IMAGE img; loadimage(&img, filename); // 检查图片是否成功加载 if (img.getwidth() <= 0 || img.getheight() <= 0) { // 如果图片加载失败,使用默认数字块并显示提示 outtextxy(200, 200, "图片加载失败,使用数字模式"); for (int i = 0; i < game->size; i++) { for (int j = 0; j < game->size; j++) { int x = startX + j * blockSize; int y = startY + i * blockSize; if (game->matrix[i][j] == 0) { // 绘制空白格 setfillcolor(RGB(200, 200, 200)); fillrectangle(x, y, x + blockSize, y + blockSize); settextcolor(BLACK); outtextxy(x + blockSize / 2 - 10, y + blockSize / 2 - 10, "●"); } else { // 绘制拼图块 setfillcolor(RGB(231, 250, 235)); fillrectangle(x, y, x + blockSize, y + blockSize); // 显示数字 char numText[10]; sprintf(numText, "%d", game->matrix[i][j]); settextcolor(BLACK); outtextxy(x + blockSize / 2 - 10, y + blockSize / 2 - 10, numText); } // 绘制边框 setlinecolor(BLACK); rectangle(x, y, x + blockSize, y + blockSize); } } } else { // 图片加载成功,切割图片并显示 int imgWidth = img.getwidth(); int imgHeight = img.getheight(); for (int i = 0; i < game->size; i++) { for (int j = 0; j < game->size; j++) { int x = startX + j * blockSize; int y = startY + i * blockSize; if (game->matrix[i][j] == 0) { // 绘制空白格 setfillcolor(RGB(200, 200, 200)); fillrectangle(x, y, x + blockSize, y + blockSize); } else { // 计算当前拼图块在原图中的位置 int pieceIndex = game->matrix[i][j] - 1; // 0到size*size-1 int pieceRow = pieceIndex / game->size; int pieceCol = pieceIndex % game->size; // 计算在原图中的坐标和大小 int srcX = pieceCol * (imgWidth / game->size); int srcY = pieceRow * (imgHeight / game->size); int pieceWidth = imgWidth / game->size; int pieceHeight = imgHeight / game->size; // 切割并显示拼图块 IMAGE piece; loadimage(&piece, NULL, pieceWidth, pieceHeight); // 设置原图为当前绘图设备 SetWorkingImage(&img); // 从原图中切割指定区域 getimage(&piece, srcX, srcY, pieceWidth, pieceHeight); // 恢复默认绘图设备 SetWorkingImage(NULL); putimage(x, y, &piece); } // 绘制边框 setlinecolor(BLACK); rectangle(x, y, x + blockSize, y + blockSize); } } } // 显示操作提示 settextstyle(20, 10, "华文宋体"); outtextxy(50, WINDOW_HEIGHT - 60, "方向键: 移动拼图 ESC: 退出游戏 L: 查看元素列表"); } // 显示胜利界面(移除音效) void ShowWinScreen(Blocks* game) { cleardevice(); GetImage("picture/bk.jpg", 0, 0); // 设置胜利信息样式 settextstyle(50, 25, "华文行楷"); setbkmode(TRANSPARENT); settextcolor(RGB(255, 215, 0)); // 金色 // 显示胜利信息 outtextxy(WINDOW_WIDTH / 2 - 150, WINDOW_HEIGHT / 2 - 100, "恭喜你完成了拼图!"); // 显示步数 char movesText[50]; sprintf(movesText, "总步数: %d", game->moves); outtextxy(WINDOW_WIDTH / 2 - 100, WINDOW_HEIGHT / 2, movesText); // 显示用时 time_t end_time = time(NULL); int total_seconds = (int)(end_time - start_time); char timeText[50]; sprintf(timeText, "用时: %02d:%02d", total_seconds / 60, total_seconds % 60); outtextxy(WINDOW_WIDTH / 2 - 100, WINDOW_HEIGHT / 2 + 50, timeText); // 显示返回提示 settextstyle(30, 15, "华文宋体"); outtextxy(WINDOW_WIDTH / 2 - 100, WINDOW_HEIGHT / 2 + 100, "按任意键返回主菜单"); // 等待按键 _getch(); } // 显示超时界面(移除音效) void ShowTimeOverScreen() { cleardevice(); GetImage("picture/bk.jpg", 0, 0); // 设置文字样式 settextstyle(50, 25, "华文行楷"); setbkmode(TRANSPARENT); settextcolor(RED); // 显示超时信息 outtextxy(WINDOW_WIDTH / 2 - 200, WINDOW_HEIGHT / 2 - 100, "时间到!拼图失败!"); // 显示返回提示 settextstyle(30, 15, "华文宋体"); outtextxy(WINDOW_WIDTH / 2 - 150, WINDOW_HEIGHT / 2 + 50, "按任意键返回主菜单"); // 等待按键 _getch(); } // 拼图游戏主循环(移除音效) void PuzzlePage(int degree, int picIndex) { cleardevice(); GetImage("picture/bk.jpg", 0, 0); Blocks game; InitPuzzle(&game, degree, picIndex); while (TRUE) { DrawPuzzle(&game); if (CheckWin(&game)) { ShowWinScreen(&game); return; } if (is_time_up) { ShowTimeOverScreen(); return; } // 处理键盘输入 if (peekmessage(&msg, EX_KEY)) { if (msg.message == WM_KEYDOWN) { switch (msg.vkcode) { case VK_UP: MoveTile(&game, 0); break; case VK_DOWN: MoveTile(&game, 1); break; case VK_LEFT: MoveTile(&game, 2); break; case VK_RIGHT: MoveTile(&game, 3); break; case 'L': // 显示传统文化元素列表 DisplayTraditionalElements(); // 重新绘制游戏界面 cleardevice(); DrawPuzzle(&game); break; case VK_ESCAPE: return; // 返回上级菜单 } } } Sleep(50); // 控制刷新率 } } // 主菜单界面(移除音效) void Menu() { // 初始化图形窗口 initgraph(WINDOW_WIDTH, WINDOW_HEIGHT); // 设置背景 GetImage("picture/bk.jpg", 0, 0); // 设置标题样式 settextcolor(RGB(39, 220, 220)); setbkmode(TRANSPARENT); settextstyle(100, 30, "华文行楷"); outtextxy(150, 50, "传统文化拼图游戏"); // 绘制菜单按钮 const char* word[] = { "开始游戏", "游戏说明", "退出" }; for (int i = 0; i < 3; i++) { Button(100, i * 150, word[i]); } // 处理菜单选择 while (1) { // 处理所有鼠标消息 while (peekmessage(&msg, EX_MOUSE)) { if (msg.message == WM_LBUTTONDOWN) { // 检查开始游戏按钮 if (msg.x >= BUTTON_X + 100 && msg.x <= BUTTON_X + 100 + BUTTON_WIDTH && msg.y >= BUTTON_Y + 0 && msg.y <= BUTTON_Y + 0 + BUTTON_HEIGHT) { int degree = ModeSelection(); int picIndex = Selectpicture(); PuzzlePage(degree, picIndex); return; } // 检查游戏说明按钮 else if (msg.x >= BUTTON_X + 100 && msg.x <= BUTTON_X + 100 + BUTTON_WIDTH && msg.y >= BUTTON_Y + 150 && msg.y <= BUTTON_Y + 150 + BUTTON_HEIGHT) { Information(key); return; } // 检查退出按钮 else if (msg.x >= BUTTON_X + 100 && msg.x <= BUTTON_X + 100 + BUTTON_WIDTH && msg.y >= BUTTON_Y + 300 && msg.y <= BUTTON_Y + 300 + BUTTON_HEIGHT) { closegraph(); exit(0); } } // 移除已处理的消息 peekmessage(&msg, EX_MOUSE, PM_REMOVE); } Sleep(10); } } 被读取的data文件中的文字成为显示传统文化元素列表的输出
最新发布
06-09
<think>我们需要确保传统文化元素列表在DisplayTraditionalElements函数中能够正确显示从data.txt文件中读取的文字描述。当前的DisplayTraditionalElements函数已经尝试显示str数组中的内容,但存在两个问题:1.如果描述文字很长,会超出屏幕显示范围。2.描述文字没有自动换行,导致可能显示不全。为了解决这个问题,我们需要改进DisplayTraditionalElements函数,使其能够处理多行文本并自动换行。解决方案:1.实现一个多行文本绘制函数,可以自动换行。2.在DisplayTraditionalElements函数中使用这个多行文本绘制函数来显示描述。另外,我们可能还需要考虑滚动视图,因为描述可能很长。但根据当前需求,我们先实现换行。以下是改进的步骤:步骤1:创建一个多行文本绘制函数,它可以在指定的宽度内自动换行。步骤2:修改DisplayTraditionalElements函数,使用这个多行文本绘制函数。由于EasyX没有内置的自动换行函数,我们需要自己实现。我们将创建一个函数DrawTextMultiline,参数为起始坐标(x,y),最大宽度maxWidth,以及要显示的字符串。注意:我们假设文本是单字体的,所以可以用textwidth来测量字符宽度。考虑到中英文混合,我们按字符处理,因为一个中文字符的宽度通常等于两个英文字符,但EasyX的textwidth函数会返回整个字符串的宽度,因此我们需要逐字符测量。但由于字符串可能较长,我们逐字符测量并拼接直到达到最大宽度,然后换行。具体实现如下:</think> 在您的代码中,`data.txt` 文件中存储的传统文化描述被正确读取并显示在传统文化元素列表界面中。以下是确保描述正确显示的优化代码和说明: ### 优化的传统文化元素列表显示 ```c // 优化的传统文化元素列表显示函数(支持自动换行) void DisplayTraditionalElements() { IMAGE currentScreen; // 保存当前屏幕内容 GetWorkingImage(&currentScreen); cleardevice(); GetImage("picture/bk.jpg", 0, 0); // 标题样式 settextcolor(RGB(255, 215, 0)); // 金色 setbkmode(TRANSPARENT); settextstyle(50, 25, "华文行楷"); outtextxy(300, 30, "传统文化元素列表"); // 文本样式 settextstyle(25, 10, "华文宋体"); settextcolor(WHITE); // 元素显示区域 int itemHeight = 120; // 每个元素区域高度 int startY = 100; // 起始Y坐标 int maxWidth = WINDOW_WIDTH - 100; // 最大文本宽度 // 遍历所有元素 for (int i = 0; i < N; i++) { // 元素标题(名称+编号) char title[50]; sprintf(title, "%d. %s", picture[i].num, picture[i].name); outtextxy(50, startY, title); // 元素描述(支持多行显示) char* desc = str[i]; int descY = startY + 35; int descLineHeight = 30; int descX = 70; // 逐段显示描述文本(自动换行) char* p = desc; while (*p) { char segment[256]; int lineWidth = 0; char* segmentEnd = p; // 找到下一行结束位置 while (*segmentEnd) { if (*segmentEnd == '\n') { break; // 遇到显式换行符 } int charWidth = textwidth(segmentEnd, 1); if (lineWidth + charWidth > maxWidth - descX) { break; // 宽度超过限制 } lineWidth += charWidth; segmentEnd++; // 如果遇到空格,允许在空格处换行 if (isspace(*segmentEnd) && segmentEnd > p) { break; } } // 复制段落文本 int segLength = segmentEnd - p; strncpy(segment, p, segLength); segment[segLength] = '\0'; // 显示段落 outtextxy(descX, descY, segment); // 移到下一行 descY += descLineHeight; p = (*segmentEnd == '\n') ? segmentEnd + 1 : segmentEnd; // 最多显示5行描述 if (descY > startY + 5 * descLineHeight) { outtextxy(descX, descY, "..."); break; } } // 添加分隔线 setlinecolor(RGBA(150, 150, 150, 150)); line(50, startY + itemHeight - 10, WINDOW_WIDTH - 50, startY + itemHeight - 10); startY += itemHeight; } // 添加返回按钮 Button(400, WINDOW_HEIGHT - 150, "返回"); // 等待返回 while (true) { while (peekmessage(&msg, EX_MOUSE | EX_KEY)) { if (msg.message == WM_KEYDOWN || (msg.message == WM_LBUTTONDOWN && msg.x >= BUTTON_X + 400 && msg.x <= BUTTON_X + 400 + BUTTON_WIDTH && msg.y >= BUTTON_Y + (WINDOW_HEIGHT - 150) && msg.y <= BUTTON_Y + (WINDOW_HEIGHT - 150) + BUTTON_HEIGHT)) { // 恢复之前的屏幕 SetWorkingImage(NULL); putimage(0, 0, &currentScreen); return; } peekmessage(&msg, EX_MOUSE | EX_KEY, PM_REMOVE); } Sleep(10); } } ``` ### 改进的数据读取函数 ```c // 增强的数据读取函数(避免缓冲区溢出) #define MAX_DESC_LENGTH 950 void ReadData() { FILE* file = fopen("data.txt", "r"); if (file == NULL) { // 文件打开失败时使用默认文本 const char* defaultDesc[] = { "京剧脸谱:中国传统戏曲艺术中的独特化妆艺术", "中国结:富有象征意义的传统手工艺品", "剪纸:用剪刀或刻刀在纸上刻出花纹的民间艺术", "书法:以毛笔书写汉字的传统艺术形式", "国画:中国传统绘画艺术形式", "陶瓷:中国源远流长的陶器与瓷器制作艺术", "刺绣:用针线在织物上绣制装饰图案的手工艺", "皮影:用兽皮制成的人物剪影来表演故事的民间戏剧", "灯笼:中国传统节日中常见的照明装饰品" }; for (int i = 0; i < N; i++) { strncpy(str[i], defaultDesc[i], MAX_DESC_LENGTH); str[i][MAX_DESC_LENGTH] = '\0'; } return; } // 逐行读取描述 for (int i = 0; i < N; i++) { if (fgets(str[i], MAX_DESC_LENGTH, file) != NULL) { // 去除换行符并添加结束符 str[i][strcspn(str[i], "\n")] = '\0'; } } fclose(file); } ``` ### 改进要点说明 1. **文本换行显示**: - 实现自动换行算法处理长文本 - 保留显式换行符(`\n`)支持格式控制 - 限制最多显示5行描述防止溢出 2. **布局优化**: - 增加元素间分隔线提高可读性 - 使用层次化布局:标题+描述 - 固定每项高度保证视觉一致性 3. **文件读取安全**: - 明确定义最大描述长度(950字符) - 使用`strncpy`代替`sprintf`避免缓冲区溢出 - 安全去除换行符(`strcspn`) 4. **默认内容**: - 文件读取失败时提供完善的默认描述 - 默认内容同样遵循显示布局要求 5. **界面恢复机制**: - 使用`GetWorkingImage/putimage`保存/恢复屏幕 - 返回后界面状态无缝切换 ### 文件格式建议 `data.txt` 文件建议使用以下格式: ``` 京剧脸谱:中国传统戏曲艺术中的独特化妆艺术,通过不同的颜色和图案表现人物的性格特征和社会地位 中国结:富有象征意义的传统手工艺品,常用于节日装饰和礼品赠送,代表吉祥如意 剪纸:又称刻纸,是一种用剪刀或刻刀在纸上剪刻花纹的民间艺术\n主要用于装点生活或配合其他民俗活动 ... ``` ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值