GraphicItem是滑板、砖块、小球等类的基类。它包含着将一个图像部件渲染到屏幕上所需的基本信息。
以下代码中,各种get方法是我留下的坑,其实把geometry设为public就行了,但由于这是面向对象实习,我觉得一刀切全public可能会影响分数。。。于是。。。
class GraphicItem { public: GraphicItem(void); GraphicItem(SDL_Texture *t); ~GraphicItem(void); // 具体使用哪个材质,由具体的类根据宏定义去选择 virtual void set_texture(SDL_Texture **textures) = 0; SDL_Texture* get_texture(); POINT speed; //POINT get_pos(); // 几何信息的set和get void set_speed_x(int s); void set_speed_y(int s); void set_pos(int x, int y); void set_pos(POINT p); POINT get_pos(); SDL_Rect get_geometry(); void set_geometry(int x, int y, int w, int h); void set_geometry(SDL_Rect rect); void set_width(int w); virtual void update(); // 获取图像的边界坐标 int get_left_edge(); int get_right_edge(); int get_top_edge(); int get_bottom_edge(); // 获取图像的中点坐标 int get_v_mid(); int get_h_mid(); SDL_Texture **textures; protected: SDL_Texture *texture; SDL_Rect geometry; };
对于一个可渲染的图像部件而言,需要有它的位置、大小、移动速度、当然还有贴图。对于砖块而言,速度当然是没有意义的。
类中的set_texture方法是一个纯虚函数,所以这个类是一个虚基类。之所以这样做,是因为我希望每个具体的图像部件知道自己所需的贴图在贴图列表中的索引。所以在具体的类中实现这个方法时,会提供宏定义的索引,上级对象只需提供贴图列表即可。
Game类是对一局游戏的抽象。它保存着游戏的所有数据,拥有一些游戏逻辑层面的方法。如:开始新游戏、重置滑板、道具生效等。
class Game { public: Game(void); ~Game(void); // 分数、命数 int score; int life; bool paused; int level; // 板 Paddle paddle; // 球 list<Ball*> balls; //Ball *balls[3]; // 砖块 //Block **blocks; list<Block*> blocks; // 道具 list<Item*> items; // 动画 list<Animation*> animations; // 新游戏 void new_game(); // 读取关卡 void load_stage(int level); // 装载材质 void set_textures(SDL_Texture *textures[]); // 有关道具的函数 void improve_ball(); void triple_ball(); // 重置滑板 void reset(); bool is_clear(); };
score、life、level 分别是分数、生命数、关卡号。
paused是暂停标志,如果是true,window类会停止更新游戏状态。
paddle是滑板对象。
new_game()用于开始一局新的游戏。要做的操作有:清空砖块、小球列表,重置滑板位置、长度、生命数等信息。
improve_ball()是用来实现小球升级道具的效果的函数,会将小球升级为高级版本(如果有的话)。P.S.我在考虑是否要把所有用于实现道具效果的函数加个命名前缀
我没有找到什么好的办法来确定小球当前的类型,最后使用了RTTI一个一个试......=。= ,因为我觉得如果给小球类加个变量表示它是什么类型,好像还不如干脆不集成这些类,直接用一个类的成员变量来区别小球呢......如果有更好的办法,请不吝赐教。
void Game::improve_ball() { Ball *temp = nullptr; for(list<Ball*>::iterator ball = balls.begin(); ball!=balls.end(); ++ball) { if(*ball) { // 这里把派生类当成基类了 cout << typeid(*ball).name(); if(dynamic_cast<NormalBall*>(*ball) != nullptr) { temp = new GravityBall(); temp->set_geometry((*ball)->get_geometry()); temp->speed = (*ball)->speed; temp->set_texture((*ball)->textures); temp->launch(); delete (*ball); *ball = temp; // TODO: 这里以后可以用拷贝构造函数重写 } else if(dynamic_cast<GravityBall*>(*ball) != nullptr) { temp = new SuperBall(); temp->set_geometry((*ball)->get_geometry()); temp->speed = (*ball)->speed; temp->set_texture((*ball)->textures); temp->launch(); delete (*ball); *ball = temp; } } } }
这是我的面向对象程序设计实习题目,发上来与各位分享。
git地址:https://github.com/DEAGS3000/BlockBreaker-with-SDL2
之所以选择SDL,是因为它有自动控制帧率的功能。之前我使用WIN32 API编程的时候,仅仅是刷新文字就会出现难以消除的闪动状况。为了避免再次踩坑,这次选择了SDL。
首先,我们需要一个Window类来封装一下(姑且算是)SDL的功能。
有如下几个目标:
1. 在构造函数中初始化SDL及其模块,在析构函数中卸载这些内容。
2. 包含读取游戏要用到的素材的操作。
class Window { public: SDL_Window *ptr; SDL_Renderer *renderer; SDL_Texture *textures[30]; SDL_Texture *background; Game *game; TTF_Font *font; bool game_started; Window(void); ~Window(void); // 初始化 void init(); // 重绘 void refresh(); // 更新 void update(); // 处理碰撞 void handle_collision(); // 读取图像 SDL_Texture* load_image(string path); SDL_Texture* render_text(string message); bool quit; bool mouse_on_button; SDL_Rect option_rect; };
我对于这个类的定位是:处理所有图像和物理层面上的功能。
ptr当然就是SDL创建的窗口的指针。实际上ptr这个变量名也就是会在析构的时候用一下而已。
renderer是渲染器。我个人的理解是,这玩意儿类似于WIN32的hdc,需要通过它才能将数据画到屏幕上。如果你不知道这个东西,可以先看看SDL的教程。
textures[30]这个数组存储了游戏需要的所有贴图。之所以这样做,是因为这个游戏很小,需要用到的贴图不多。我的思路是:既然两个相同的图样部件持有的都是其贴图的指针而非一个副本(当然得这样做),那么贴图可以由Window统一管理,具体的类只要知道自己具体取用其中的哪一个就行。这个设计通过宏定义来实现,把图像部件的名称对应到整数索引上。
#define BACKGROUND 0 // 背景 #define STICK 1 // 板子 #define NORMAL_BALL 2 // 普通球 #define GRAVITY_BALL 3 // 重力球 #define SUPER_BALL 4 // 穿透球 #define NORMAL_BLOCK 5 // 普通砖块 #define HARD_BLOCK 6 // 硬砖块 #define ITEM_BLOCK 7 // 物品砖块 #define STEEL_BLOCK 8 // 钢砖块 #define HALF_BLOCK 9 // 半钢砖块 #define BOMB_BLOCK 10 // 爆炸砖块 #define ITEM_SHORT 12 // 板变短 #define ITEM_SPEED_UP 13 // 板速度上升 #define ITEM_SPEED_DOWN 14 // 板速度下降 #define ITEM_LONG_PADDLE 15 // 板变长 #define ITEM_ADD_LIFE 16 // 加命 #define ITEM_TRIPLE_BALL 17 // 小球分裂 #define ITEM_IMPROVE_BALL 18 // 小球升级 #define HARD_BLOCK_CRACKED 19 // 碎裂硬块 #define ANIMATION 20 // 爆炸动画 #define PAUSE 21 // 暂停 #define TITLE 22 // 标题 #define NEW_GAME 23 // 新游戏 #define CHOOSE_STAGE 24 // 选关 #define NEW_GAME_2 25 // 高亮的按钮
background是背景贴图。放在这里没什么特别的意义,就是开发过程中的遗留而已。完全可以放到textures里。
game是游戏类Game的一个实例的指针。Game和Window两个类是聚合关系,代表着“一个窗口中进行着一局游戏”。
font是一个字体,用于渲染游戏中的文字。他的读取和释放也在构造函数完成。
game_started是一个bool变量,用来标志当前窗口是处于游戏状态还是标题菜单。由于我的主菜单是最后加上去的,所以看着有些牵强。只能暂且这样做了。
Window的构造函数中包含SDL及其相关模块的初始化操作,析构函数中包含卸载这些模块的操作。
Window::Window(void) { game_started = false; // 载入SDL if (SDL_Init(SDL_INIT_VIDEO) != 0) { cout << "SDL_Init Error: " << SDL_GetError() << endl; exit(1); } IMG_Init(IMG_INIT_PNG | IMG_INIT_JPG); TTF_Init(); // 创建窗口 ptr = SDL_CreateWindow("test", 100, 100, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN); // 创建渲染器,其中SDL_RENDERER_PRESENTVSYNC可以自动控制帧率。 renderer = SDL_CreateRenderer(ptr, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); // 创建游戏 game = new Game; game->new_game(); // 清空材质列表 for (int i = 0; i < 30; i++) textures[i] = nullptr; // 初始化字体 font = nullptr; font = TTF_OpenFont("res/SourceSansPro-Regular.ttf", 20); quit = false; // 这个用来实现鼠标放到新游戏按钮上使其变色的功能 mouse_on_button = false; }
init函数是读取游戏所需素材的函数。其实这个函数也可以合并到构造函数。与之相应的,
void Window::init() { textures[BACKGROUND] = IMG_LoadTexture(renderer, "res/bg2.jpg"); // 球 textures[NORMAL_BALL] = load_image("res/normal_ball.png"); textures[GRAVITY_BALL] = load_image("res/gravity_ball.png"); textures[SUPER_BALL] = load_image("res/super_ball.png"); // 板 textures[STICK] = IMG_LoadTexture(renderer, "res/stick.bmp"); // 砖块 textures[NORMAL_BLOCK] = load_image("res/normal_block.png"); textures[HARD_BLOCK] = load_image("res/hard_block.png"); textures[HARD_BLOCK_CRACKED] = load_image("res/hard_block_cracked.png"); textures[STEEL_BLOCK] = load_image("res/steel_block.png"); textures[ITEM_BLOCK] = load_image("res/item_block.png"); // 物品 textures[ITEM_LONG_PADDLE] = load_image("res/item_long.jpg"); textures[ITEM_IMPROVE_BALL] = load_image("res/item_improve_ball.jpg"); textures[ITEM_TRIPLE_BALL] = load_image("res/item_triple_ball.png"); textures[ITEM_ADD_LIFE] = load_image("res/item_add_life.png"); // 动画 textures[ANIMATION] = load_image("res/animation2.png"); // 暂停 textures[PAUSE] = load_image("res/pause.png"); // 标题 textures[TITLE] = load_image("res/title.jpg"); textures[NEW_GAME] = load_image("res/new_game.png"); textures[NEW_GAME_2] = load_image("res/new_game2.png"); textures[CHOOSE_STAGE] = load_image("res/choose_stage.png"); this->background = textures[BACKGROUND]; // 为游戏中的对象设置材质 game->set_textures(textures); }
refresh这个函数比较鸡肋,实际上只是调用了一下SDL_RenderPresent而已。在优化结构时完全可以去掉。
void Window::refresh() { SDL_RenderPresent(renderer); }
update函数比较重要,它会要求game将其下辖的所有图像部件的状态更新,期间调用handle_collision函数进行碰撞检测和处理,最后将更新后的图像数据画到屏幕上。
下面的函数定义包含了简单的标题菜单和游戏结束、进入下一关对话框,这部分可以先无视。
// 更新图像 void Window::update() { if (game_started) { //SDL_Event event; if (!game->paused) { // 清空图像 SDL_RenderClear(renderer); // 如果过关 if (game->is_clear()) { MessageBox(NULL, "请点击OK进入下一关...", "恭喜过关!", MB_OK); game->level++; game->new_game(); game->set_textures(textures); return; } // 如果没有小球,且有生命,重置 if (game->balls.size() == 0) { if (game->life == 0) { int choice = 0; choice = MessageBox(NULL, "再试一次?\n点是重试\n点否退出", "Game Over", MB_YESNO); // 判断用户的选择 switch (choice) { // 用户选择是 case 6: game->level = 1; game->new_game(); game->set_textures(textures); return; break; case 7: //event.type = SDL_QUIT; // 事件会被复制一份到事件队列,这里可以不管 // 收不到事件 quit = true; //SDL_PushEvent(&event); return; break; case 2: break; } } else { game->reset(); game->balls.front()->set_texture(textures); } } // 更新素材 //game->set_textures(textures); // 背景 SDL_RenderCopy(renderer, background, NULL, NULL); // 更新板状态 game->paddle.update(); SDL_RenderCopy(renderer, game->paddle.get_texture(), NULL, &game->paddle.get_geometry()); // 更新所有球状态 for (list<Ball*>::iterator it = game->balls.begin(); it != game->balls.end(); ++it) { if (*it) { // 如果小球未发射,跟着板走 if (!(*it)->is_launched()) (*it)->set_pos(game->paddle.get_pos().x + game->paddle.get_geometry().w / 2 - (*it)->get_geometry().w / 2, game->paddle.get_pos().y - (*it)->get_geometry().h); (*it)->update(); } SDL_RenderCopy(renderer, (*it)->get_texture(), NULL, &(*it)->get_geometry()); } // 更新所有物品状态 for (list<Item*>::iterator item = game->items.begin(); item != game->items.end(); ++item) { if (*item) { (*item)->update(); } SDL_RenderCopy(renderer, (*item)->get_texture(), NULL, &(*item)->get_geometry()); } // 处理碰撞 handle_collision(); // 所有砖块更新 for (list<Block*>::iterator block = game->blocks.begin(); block != game->blocks.end(); ++block) { if (*block) { SDL_RenderCopy(renderer, (*block)->get_texture(), NULL, &(*block)->get_geometry()); } } // 获取帧数,更新动画信息 int frame = int(((SDL_GetTicks() / 10) % 30)); for (list<Animation*>::iterator anim = game->animations.begin(); anim != game->animations.end(); ) { if (*anim) { (*anim)->update2(); SDL_RenderCopy(renderer, (*anim)->texture, &(*anim)->source_rect, &(*anim)->target_rect); } // 删除播放完的 if ((*anim)->frame == 27) anim = game->animations.erase(anim); else ++anim; } // 渲染生命数和关卡号 SDL_Rect text_rect = { 880, 700, 130, 25 }; char temp_str[50]; sprintf(temp_str, "LEVEL: %d LIFE: %d", game->level, game->life); SDL_RenderCopy(renderer, render_text(temp_str), NULL, &text_rect); } else { SDL_Rect temp = { SCREEN_WIDTH / 2 - 50, SCREEN_HEIGHT / 2 - 50, 100, 100 }; SDL_RenderCopy(renderer, textures[PAUSE], NULL, &temp); } } // 如果没有开始游戏 else { SDL_RenderClear(renderer); SDL_RenderCopy(renderer, textures[TITLE], NULL, NULL); // SDL_Rect option_rect; if(!mouse_on_button) { SDL_QueryTexture(textures[NEW_GAME], NULL, NULL, &option_rect.w, &option_rect.h); option_rect.x = SCREEN_WIDTH/2 - option_rect.w/2; option_rect.y = 450; SDL_RenderCopy(renderer, textures[NEW_GAME], NULL, &option_rect); } else { SDL_QueryTexture(textures[NEW_GAME_2], NULL, NULL, &option_rect.w, &option_rect.h); option_rect.x = SCREEN_WIDTH / 2 - option_rect.w / 2; option_rect.y = 450; SDL_RenderCopy(renderer, textures[NEW_GAME_2], NULL, &option_rect); } /*SDL_QueryTexture(textures[CHOOSE_STAGE], NULL, NULL, &option_rect.w, &option_rect.h); option_rect.x = SCREEN_WIDTH / 2 - option_rect.w/2; option_rect.y = 520; SDL_RenderCopy(renderer, textures[CHOOSE_STAGE], NULL, &option_rect);*/ SDL_RenderPresent(renderer); } }
handle_collision函数进行碰撞事件的检测和处理。
尽管游戏支持多个小球、多个砖块、多个掉落物品,但碰撞检测是线性的。也就是说,要不断进行遍历操作。对每个小球,判断它和每个砖块是否发生了碰撞,诸如此类。
这里写得有点繁琐,因为在遍历过程中,可能要删除list的某些元素。所以对iterator的++操作不是每次循环都执行的。
void Window::handle_collision() { for (list<Ball*>::iterator ball = game->balls.begin(); ball != game->balls.end(); ) { // 墙壁 if ((*ball)->speed.x > 0 && (*ball)->get_geometry().x >= SCREEN_WIDTH - (*ball)->get_geometry().w) (*ball)->speed.x *= -1; if ((*ball)->speed.x < 0 && (*ball)->get_geometry().x <= 0) (*ball)->speed.x *= -1; if ((*ball)->speed.y > 0 && (*ball)->get_geometry().y >= SCREEN_HEIGHT - (*ball)->get_geometry().h) { // 坠毁 delete (*ball); ball = game->balls.erase(ball); continue; } if ((*ball)->speed.y < 0 && (*ball)->get_geometry().y <= 0) (*ball)->speed.y *= -1; if ((*ball)->speed.y > 0 && (*ball)->get_geometry().y >= game->paddle.get_geometry().y - (*ball)->get_geometry().h && (*ball)->get_geometry().x + (*ball)->get_geometry().w / 2 > game->paddle.get_geometry().x && (*ball)->get_geometry().x + (*ball)->get_geometry().w / 2 < game->paddle.get_geometry().x + game->paddle.get_geometry().w) { (*ball)->speed.y *= -1; // 板的速度影响小球的速度 (*ball)->speed.x += game->paddle.speed.x / 3; /*// 小球水平速度最高正负12 if(abs((*ball)->speed.x)>12) (*ball)->speed.x = (*ball)->speed.x / abs((*ball)->speed.x) *12;*/ // 打在板的不同位置会对小球水平速度造成不同的影响 /*(*ball)->speed.x += ((*ball)->get_pos().x-game->stick.get_pos().x - game->stick.get_geometry().w / 2) / (game->stick.get_geometry().w/2) * 12;*/ (*ball)->speed.x = (double)((*ball)->get_pos().x + (*ball)->get_geometry().w / 2 - game->paddle.get_pos().x - game->paddle.get_geometry().w / 2) / (double)(game->paddle.get_geometry().w / 2) * 8; // 小球水平速度最高正负12 /*if(abs((*ball)->speed.x)>12) (*ball)->speed.x = (*ball)->speed.x / abs((*ball)->speed.x) *12;*/ } // 砖块 bool hit = false; for (list<Block*>::iterator block = game->blocks.begin(); block != game->blocks.end();) { hit = false; if (*block) { // 测试 if ((*block)->get_geometry().x == 287 && (*block)->get_geometry().y == 370 && (*ball)->get_top_edge() <= 370 && (*ball)->speed.y < 0) { int a; a = 10; } // 首先判断小球是否与砖块发生了接触 // 碰撞砖块上下边界 // 如果小球有像素位于砖块左右边界之内 // 关于角度,先把等于也放在碰撞上下边界中 double algle_with_ball = (*block)->angle_with_ball((*ball)->get_geometry()); double right_top_angle = (*block)->right_top_angle(); double left_top_angle = (*block)->left_top_angle(); if (((*ball)->get_h_mid() >= (*block)->get_left_edge() && (*ball)->get_h_mid() <= (*block)->get_right_edge()) && (((*block)->angle_with_ball((*ball)->get_geometry()) <= (*block)->right_top_angle() && (*block)->angle_with_ball((*ball)->get_geometry()) >= (*block)->left_top_angle()) || ((*block)->angle_with_ball((*ball)->get_geometry()) <= (*block)->left_bottom_angle() && (*block)->angle_with_ball((*ball)->get_geometry()) >= (*block)->right_bottom_angle()))) { // 碰撞砖块下界 if ((*ball)->get_top_edge() < (*block)->get_bottom_edge() && (*ball)->get_bottom_edge() > (*block)->get_bottom_edge()) { (*ball)->hit(BOTTOM); (*block)->hit_by((*ball), &game->items); } // 碰撞砖块上界 else if ((*ball)->get_bottom_edge() > (*block)->get_top_edge() && (*ball)->get_top_edge() < (*block)->get_top_edge()) { (*ball)->hit(TOP); (*block)->hit_by((*ball), &game->items); } } // 碰撞砖块左右边界 // 如果小球有像素位于砖块上下边界之内 else if ((*ball)->get_v_mid() >= (*block)->get_top_edge() && (*ball)->get_v_mid() <= (*block)->get_bottom_edge()) { // 碰撞砖块左界 int ball_right = (*ball)->get_right_edge(); int block_left = (*block)->get_left_edge(); int ball_left = (*ball)->get_left_edge(); if ((*ball)->get_right_edge() > (*block)->get_left_edge() && (*ball)->get_left_edge() < (*block)->get_left_edge()) { (*ball)->hit(LEFT); (*block)->hit_by((*ball), &game->items); } // 碰撞砖块右界 else if ((*ball)->get_left_edge() < (*block)->get_right_edge() && (*ball)->get_right_edge() > (*block)->get_right_edge()) { (*ball)->hit(RIGHT); (*block)->hit_by((*ball), &game->items); } } // 删除生命值小于等于0 的砖块 // 如果砖块被破坏,创建一个动画 if ((*block)->health <= 0) { // 在砖块的位置创建一个动画 game->animations.push_back(new Animation(textures[ANIMATION], (*block)->get_h_mid() - 35, (*block)->get_v_mid() - 17)); delete (*block); *block = nullptr; block = game->blocks.erase(block); hit = true; } if (!hit) block++; } } ++ball; } // 道具 for (list<Item*>::iterator item = game->items.begin(); item != game->items.end();) { // 如果道具被吃到 if ((*item)->speed.y > 0 && (*item)->get_geometry().y >= game->paddle.get_geometry().y - (*item)->get_geometry().h && (*item)->get_geometry().x + (*item)->get_geometry().w / 2 > game->paddle.get_geometry().x && (*item)->get_geometry().x + (*item)->get_geometry().w / 2 < game->paddle.get_right_edge() && (*item)->get_top_edge() <= game->paddle.get_bottom_edge()) { // 判断类型,发动效果 switch ((*item)->type) { case ITEM_LONG_PADDLE: game->paddle.size_up(); (*item)->gotten = true; break; case ITEM_IMPROVE_BALL: game->improve_ball(); (*item)->gotten = true; break; case ITEM_TRIPLE_BALL: game->triple_ball(); (*item)->gotten = true; break; case ITEM_ADD_LIFE: game->life++; (*item)->gotten = true; break; } } // 如果没吃到 else if ((*item)->get_bottom_edge() == SCREEN_HEIGHT) (*item)->gotten = true; // 删除生命值小于等于0 的砖块 if ((*item)->gotten) { delete (*item); *item = nullptr; item = game->items.erase(item); continue; } ++item; } }
我目前还在做Windows程序设计的课程实验,开发一个编辑器。我尝试设置鼠标光标样式为IDC_IBEAM
SetCursor(LoadCursor(IDC_IBEAM));
但是没有什么作用,鼠标会在点击左键的时候消失一下,一移动又回来了,不过还是原来的光标。
在网上查到,需要在SetCursor之前先设置原来的光标不显示,设置之后再显示新的光标。
ShowCursor(FALSE); SetCursor(LoadCursor(IDC_IBEAM)); ShowCursor(TRUE);
结果依然令人失望。幸运的是,我在网上找到了这样一个解答:
如果产生了鼠标消息,系统可能会用窗口的hcursor重新刷新光标,
所以用
SetClassLong(hwnd,GCL_HCURSOR,(long)LoadCursor(NULL,IDC_CROSS));
才能一直保持。
按这个方法尝试后,鼠标成功地变成了插入符,并且不受点击等鼠标操作影响。
MessageBox 是Windows系统库 user32.dll 的一个导出函数,用于显示一个提示消息对话框,其原型定义如下 :
int MessageBox( HWND hWnd, // handle to owner window LPCTSTR lpText, // text in message box LPCTSTR lpCaption, // message box title UINT uType // message box style );
可以看到有四个参数,第一个是消息框所有者窗口句柄,可以是NULL,第二个是消息框的文本内容,第三个是消息框标题,第四个参数是消息框样式(按钮和图标)。
按钮样式及常量(可用【样式=数字】表示)
0、确定按钮; _MB_OK=@0x0
1、确定、取消按钮; _MB_OKCANCEL=@0x1
2、终止、重试、忽略按钮;_MB_ABORTRETRYIGNORE=@0x2
3、是、否、取消按钮;_MB_YESNOCANCEL=@0x3
4、是、否按钮;_MB_YESNO=@0x4
5、重试取消钮;_MB_RETRYCANCEL=@0x5
6、终止、重试、继续 0x00000006(需声明API才能使用)
图标常量
_MB_ICONASTERISK=@0x40/ “i”图标
_MB_ICONEXCLAMATION=@0x30/ “!”号图标
_MB_ICONHAND=@0x10 / “×” 号图标
_MB_ICONINFORMATION=@0x40 / “i” 图标
_MB_ICONMASK=@0xF0 / “i” 图标
_MB_ICONQUESTION=@0x20/ “?” 号图标
_MB_ICONSTOP=@0x10/ “×” 号图标
按钮返回值
1=确定钮; IDOK
2=取消钮; IDCANCEL
3=终止钮; IDABORT
4=重试钮; IDRETRY
5=忽略钮;IDIGNORE
6=是钮;IDYES
7=否钮;IDNO
当用户点击右上角的关闭按钮时,发出的是WM_CLOSE消息,该消息意味着关闭窗口。
这个消息的处理方式是调用DestroyWindow()来发出WM_DESTROY消息。
而WM_DESTROY消息意味着关闭程序,这个消息的处理方式是调用PostQuitMessage()来发送WM_QUIT,以退出程序。
我在做Windows程序设计课程实验的时候,参照书上的例子使用了strcpy,结果VS报错说这个函数不安全,应该使用strcpy_s。
两者有什么区别呢?其实是因为strcpy无法判断他的操作对象是否有足够的缓冲区,若缓冲区不够的话,就会发生溢出。
strcpy_s避免了这个问题。它会显式地向你报告缓冲区不足的错误,避免丈二和尚摸不着头脑。