易码技术论坛

 找回密码
 加入易码
搜索
查看: 657052|回复: 14

[教程] [连载]游戏开发之旅(二)——稍稍走近c++

[复制链接]
发表于 2006-10-27 18:07:40 | 显示全部楼层
悄悄地坐上沙发~
不过感觉贪吃蛇用队列比链表好~
木有缩进,看着有些痛苦。貌似你把函数都内联了。
发表于 2006-10-28 12:49:02 | 显示全部楼层
public前定义的变量属于共有还是私有?
发表于 2006-10-28 12:49:56 | 显示全部楼层
希望能介绍一下模板类
发表于 2006-10-28 14:12:44 | 显示全部楼层
保存起来慢慢看!
发表于 2006-10-28 15:20:00 | 显示全部楼层
引用第2楼Cadina2006-10-28 12:49发表的“”:
public前定义的变量属于共有还是私有?
默认情况下,类的成员成员访问权限是private,即私有的。
引用第3楼Cadina2006-10-28 12:49发表的“”:
希望能介绍一下模板类
模版提供了一种通用的函数机制,比如我们要写一个交换变量的函数:
  1. void swap(int &a, int &b)
  2. {
  3.    int tmp = a;
  4.    a = b;
  5.    b = a;
  6. }
复制代码
但是这个函数只能对int类型的数据使用,如果我们要交换的数据类型为double或是char或是class foo呢?
如果没有模版我们就需要为每一种类型的数据写一个函数,那样就会有:
  1. void swap(double &a, double &b);
  2. void swap(char &a, char &b);
  3. ...
复制代码
这个无疑是很糟糕的一件事。
但是如果我们有了模版,这个问题将变得很简单。我们的函数可以写成下面这样:
  1. template<class T>
  2. void swap(T &a, T &b)
  3. {
  4.    T tmp = a;
  5.    a = b;
  6.    b = a;
  7. }
复制代码
这样,编译器在编译的过程中就会根据你提供的参数a, b的类型来自动制定T的类型。
另外关于SLT(标准模版库)里面所提供的类都是通过这样的方法定义的数据类型。比如LZ的例子中使用到的"list"链表类:
  1. list<POINT> m_nodeList;
复制代码
这样指明了一个由POINT类型数据组成的链表。当然我们也可以根据需要,将POINT换成我们想用的任何类型。
 楼主| 发表于 2006-10-28 15:30:37 | 显示全部楼层
引用第1楼yzk03702006-10-27 18:07发表的“”:
悄悄地坐上沙发~
不过感觉贪吃蛇用队列比链表好~
木有缩进,看着有些痛苦。貌似你把函数都内联了。

队列的话更清楚一些,不过链表用惯了,而起其实stl中队列就是链表上的一个套子:)大部分时候还是链表更好用,因为更灵活。


函数内联是因为不这样就要扯出更多的东西,没精力打了……
发表于 2006-10-28 16:21:33 | 显示全部楼层
既然创建一个实例时有构造函数,那么delete这个实例的时候能不能也自动调用一个函数呢
 楼主| 发表于 2006-10-28 16:46:23 | 显示全部楼层
有析构函数,不过没有细讲,下一回吧,等我想个由头。
不过这里可以简单说下,析构函数的名字是~加上类名
对于直接定义的变量,比如像Fool fool这种,会在作用域退出的时候自动析构,自动调用析构函数。对于动态分配的,比如Fool* f=new Fool,则会在delete f的时候自动调用析构函数。

要注意区分自动变量(在栈上分配的变量)和动态生成的(在堆上分配的变量)之间的区别与联系,这也是c++中最麻烦的一部分之一
发表于 2006-10-28 17:41:28 | 显示全部楼层
越说动态分配越想研究是怎么实现的~~~
发表于 2006-10-28 20:59:28 | 显示全部楼层
会在作用域退出的时候自动析构
什么意思?
是不是在局部定义的作用域结束?
像这样:
void function(void)
{
class a;
.....
}结束时析构
发表于 2006-10-28 21:11:00 | 显示全部楼层
是这个意思。
不过,class是不能作为class的名字的=v=
 楼主| 发表于 2006-10-28 21:11:05 | 显示全部楼层
bingo~~~~~~
发表于 2006-10-29 08:30:25 | 显示全部楼层
受教啦~
^_^~~~
不布置作业?
 楼主| 发表于 2006-10-29 19:40:37 | 显示全部楼层
有啊,不是说把其中一段程序的vector换成list嘛。 其实应该找本书看看stl:)
 楼主| 发表于 2006-10-27 17:55:01 | 显示全部楼层 |阅读模式
上一讲给了大家一个对于windows编写游戏的概括印象,这一次,我们将继续把windows编程概括着不动,转过来看看c++大概是个什么样子


一、class 关键字:

是的,这是个关键字,并且也仅仅是个关键字。这个关键字的一个用途,就是来定义类。关键问题是类是什么?大家可以暂时不要被OO的这种术语搞乱套,就把类当作一个带有函数的struct(不知道struct?就是结构,Pascal和QBasic中都有的东西,如果还是不知道,请参见最后Q&A)。这个struct使用起来其实和struct并无二致,下面是一段示例代码片段:
  1. class Fool
  2. {
  3. public:          //这个叫做访问控制,表示这个标签下的东西可以任意访问
  4.    int a;        //一个整数成员
  5.    char p;      //一个字符型成员
  6. private:        //这个标签下的东西外界不能访问
  7.    int p2;
  8. };              //不要忘了分号
  9. void Main(HWND hWnd)
  10. {
  11.    //与c中的struct不同,这里Fool就是这个类型的全称,这里定义一个变量
  12.    //类型为Fool,名字为f
  13.    Fool f;
  14.    //为这个变量分量a赋值
  15.    f.a=10;
  16.    //为这个变量中分量p赋值
  17.    f.p=&#39;h&#39;;
  18.    //注意这里不可以使用f.p2,因为p2是私有(private)的
  19. }
复制代码

要注意的就是class中的访问控制标签,共有public,protected,private三种,其中public标签下的东西可以被随意访问,private标签下的东西不能被类外的东西访问,至于protected标签,则表示只能被自己和自己的子类访问(关于子类,以后会讲到)

另外定义类的变量(专业些叫“实例”)的时候,只要直接给出类名加变量名就可以,c程序员要注意

而class这个关键字不是白引进的,类和传统意义的struct不同的最大一点就是类可以有方法(c++中叫成员函数,而“方法”在Java或者OO社区中叫得比较多)。就是说类是一个带有函数的结构。这个函数操纵的对象就是类中的各个成员,下面看一个例子:
  1. class Bean
  2. {
  3.    //这里没有访问控制标签,则默认为private,也就是别人不能访问
  4.    //m_x中的m_是一种命名习惯,表示这是一个成员变量(就是类中的变量)
  5.    int m_x,m_y;
  6. public:
  7.    //在public下的函数可以被外界访问
  8.    void Draw(HDC hdc)
  9.    {
  10.       //这里m_x,m_y就是类自己的m_x,m_y
  11.       ::Rectangle(hdc,m_x,m_y,m_x+10,m_y+10);
  12.    }
  13.    void Set(int x,int y)
  14.    {//这里为访问m_x,m_y提供一种途径,但是是有条件的,对于正数才会赋值
  15.       if(x>0 && y>0)
  16.       {
  17.         m_x=x;
  18.         m_y=y;
  19.       }
  20.    }
  21.    //下面这个函数比较古怪,名称叫构造函数(Constructor)
  22.    //它的名字和类名一样,但是没有返回值,可以带参数
  23.    //这个函数在创建类的变量的时候,就会对这个变量自动调用
  24.    //下面这个意思就是在创造变量的时候把成员初时化一下
  25.    Bean(int x, int y)
  26.    {
  27.       m_x=x;
  28.       m_y=y;
  29.    }
  30. };    //!!!!!分号
  31. void Main(HWND hWnd)
  32. {
  33.    //定一一个Bean类型的变量
  34.    //变量名后面的小括号其实就是上面那个构造函数的参数
  35.    Bean bean(10,10);
  36.    //调用成员函数其实和访问成员变量一样,都是加个点
  37.    //只不过函数要有小括号
  38.    //这里bean的m_x,m_y已经被构造函数初始化了,所以可以直接画
  39.    bean.Draw(::GetDC(hWnd));
  40.    //换个地方再画一个
  41.    bean.Set(100,100);
  42.    bean.Draw(::GetDC(hWnd));
  43.    //再定义一颗豆子(bean),要注意c++可以在要用的时候定义变量
  44.    //而不是在开始处全定义好
  45.    Bean bean2(300,300);
  46.    bean2.Draw(::GetDC(hWnd));
  47. }
复制代码

成员函数也受访问控制标签的控制,可以有私有(private)和保护(protected)成员函数。
构造函数是特殊的成员函数,不能被直接调用,而是在定义一个变量的时候被自动调用,他的参数就跟在变量名之后。如果你没有定义过构造函数,那么c++会帮你自动生成一个什么也不做的(当然你看不到),在初时化的时候会调用一下(当然,这只是简单的情况,大家不要死扣这句话,会有特例的)

类的情况就先讲这么多,足够大家看下面的代码了:)

二、STL

STL不是c++语法中的部分,他是Standard Template Library的缩写,就是传说中的标准模版库。这是c++中提供的一套标准的数据结构,算法以及很多好玩的东西堆积的库,大家现在只要拿来用就可以,至于以后嘛,建议把他的源码读一读,起码他的所有原理都要搞搞清楚。

STL中最常用的就是他的容器(Container),堆栈,链表等等给你写好的玩意,拿来即可用,相当的方便:
  1. //这里展示vector的使用,要包含这个头,注意用尖括号并且没有.h
  2. #include <vector>
  3. //表示打开命名空间std,暂时不用理会,一般使用标准库内容都要加这个
  4. using namespace std;
  5. void Main(HWND hWnd)
  6. {
  7.   //定义一个vector,那个尖括号里面的东西表示这个vector中放int型的东西。
  8.   vector<int> IntList;
  9.   //往vector中加入一个int型的值:10
  10.   IntList.push_back(10);
  11.   //再加一个
  12.   IntList.push_back(30);
  13.   IntList.push_back(50);
  14.   //注意这里:
  15.   //IntList.size()表示这个vector的大小,现在放进去三个数字,就是3
  16.   for(int i=0 ;i<IntList.size() ;i++)
  17.   {
  18.       //注意IntList的使用方法,他可以直接使用[]来表示他里面的第几个内容
  19.       ::Rectangle(hdc,10,IntList[i],20,IntList[i]+10);
  20.   }
  21. }
复制代码

好玩吧?

关于那个尖括号,那个是模版方面的东西,我暂时还不会做这方面的讲解,大家只要知道里面放一个类型名就可以,可以是int,可以是咱们前面定义的Fool,Bean之类的。来表示这个容其中放什么。

一般常用的这种容器有两个,一个就是这个vector,另外一个则是list。vector表现的是数组的意义,而list则表现了链表。这两个容器都有push_back,pop_back,insert等实现,而由于数组和链表结构的不同,他们也有自己不同的地方,比如vector就可以使用[]像数组一样直接访问内容,而list则要使用一种叫做迭代器的东西来逐个访问内容。(关于数组和链表有什么不同,请参见Q&A)

迭代器是什么?其实就是长得好像指针一样的东西,我这里也只给出示例,要彻底讲明白他是什么可是太困难了,大家会用就好:
  1. #include <list>
  2. using namespace std;
  3. void Main(HWND hWnd)
  4. {
  5.    list<int> List;
  6.    List.push_back(10);
  7.    List.push_back(30);
  8.    List.push_back(50);
  9.    //注意这里:
  10.    //list<int>::iterator表示list<int>这中类型容器的迭代器
  11.    list<int>::iterator i;
  12.    //已经定义过i,这里就不再定义,只赋值:
  13.    //List.begin()表示指向List中第一个位置的迭代器,就是头指针。
  14.    //List.end()表示指向List中末尾的迭代器,这个迭代器是个假的,就是用来标识
  15.    //结尾的
  16.    //i++表示迭代器向下移动一个位置,和指针++的意义一样
  17.    for( i=List.begin() ;i != List.end() ;i++)
  18.    {
  19.       //注意i的使用方法,他可以直接使用*来表示他指向位置的内容
  20.       //和指针的用法一样
  21.       ::Rectangle(hdc,10,*i,20,*i+10);
  22.    }
  23. }
复制代码

大家可以试试把上面那段使用vector的代码中直接访问的代码改成使用迭代器:)


啊啊,打了这么多,累死了,本来还想说的一个内容放到下一次做专题吧,或者有谁愿意自学一下帮我写写:)

三、代码注释:
  1. #include "Window.h"
  2. #include "time.h"
  3. #include "Frame.h"
  4. //这里我们使用链表来表示蛇
  5. #include <list>
  6. using namespace WinApp;
  7. using namespace std;
  8. //几个常数,const表示常数,而static大家忽略就可以
  9. const int width=10,height=10;
  10. static const int dx[]={0,1,0,-1};
  11. static const int dy[]={-1,0,1,0};
  12. //蛇类型
  13. class Snake
  14. {
  15.    //一个存放POINT型内容的链表
  16.    list<POINT> m_nodeList;
  17.    int m_dir;
  18.    int m_width,m_height;
  19.    //自己会保存一份HDC,方便
  20.    HDC m_hdc;
  21.    HBRUSH bkBrush;
  22.    //屏幕的大小
  23.    RECT m_rect;
  24. public:
  25.    //构造函数
  26.    Snake(int width,int height,int dir)
  27.    {
  28.       m_dir=dir;
  29.       m_width=width;
  30.       m_height=height;
  31.    }
  32.    //不过仍然需要初始化一下,因为需要一个HWND的参数,只有在Main中才能获得
  33.    void Init(HWND hWnd)
  34.    {
  35.       m_hdc=::GetDC(hWnd);
  36.       bkBrush=::CreateSolidBrush(::GetBkColor(m_hdc));
  37.       ::GetWindowRect(hWnd,&m_rect);
  38.       //随机产生一个点
  39.       int rx=rand()%(m_rect.right-m_rect.left) / width * width;
  40.       int ry=rand()%(m_rect.bottom-m_rect.top) / height * width;
  41.       POINT begin;
  42.       begin.x=rx;
  43.       begin.y=ry;
  44.    
  45.       //把这个点放到POINT的链表中
  46.       m_nodeList.push_back (begin);
  47.    }
  48.    //把蛇移动一步
  49.    bool Move()
  50.    {
  51.       //back()表示取这个链表的最后一个节点的值
  52.       POINT p=m_nodeList.back();
  53.       RECT re;
  54.       re.left=p.x;
  55.       re.top = p.y;
  56.       re.right=p.x+width;
  57.       re.bottom=p.y+height;
  58.       ::FillRect(m_hdc,&re,bkBrush);
  59.       
  60.       //front()表示第一个节点的值
  61.       POINT front=m_nodeList.front();
  62.       
  63.       //pop_back()表示删去最后一个节点
  64.       m_nodeList.pop_back();
  65.       front.x+=dx[m_dir]*width;
  66.       front.y+=dy[m_dir]*height;
  67.       //insert表示插入一个节点,第一个参数是代表位置的迭代器
  68.       //这里使用begin()表示插入头部
  69.       //第二个参数是要插入的值。
  70.       m_nodeList.insert(m_nodeList.begin(),front);
  71.       //迭代器,开始指向头部
  72.       list<POINT>::iterator i=m_nodeList.begin();
  73.       if(i!=m_nodeList.end()) i++;
  74.       //遍历所有除了第一个节点的节点,看看是不是碰到了自己
  75.       for(;i!=m_nodeList.end();i++)
  76.       {
  77.         //由于i是一个指向POINT类型的迭代器,其行为类似于指向POINT的指针
  78.         //使用i->x是访问i指向的那个POINT的成员x,是(*i).x的简写
  79.         if(i->x == front.x && i->y == front.y)
  80.            return false;
  81.       }
  82.       if(front.x<0 || front.x>=m_rect.right || front.y<0 || front.y >= m_rect.bottom)
  83.         return false;
  84.       return true;
  85.    }
  86.    void ChangeDir(int key)
  87.    {
  88.       switch(key)
  89.       {
  90.       case VK_UP:
  91.         if(m_dir==1||m_dir==3)
  92.            m_dir=0;
  93.         break;
  94.       case VK_RIGHT:
  95.         if(m_dir==0||m_dir==2)
  96.            m_dir=1;
  97.         break;
  98.       case VK_DOWN:
  99.         if(m_dir==1||m_dir==3)
  100.            m_dir = 2;
  101.         break;
  102.       case VK_LEFT:
  103.         if(m_dir==0||m_dir==2)
  104.            m_dir = 3;
  105.         break;
  106.       }
  107.    }
  108.    void Grow()
  109.    {//把自己长大一个
  110.       POINT p=m_nodeList.front();
  111.       //就是给自己的尾巴上添一个节点,添个怎样的节点倒是无所谓
  112.       //上面选择添一个位置在头部的节点为的游戏不出现飞在外面的点
  113.       m_nodeList.push_back(p);
  114.    }
  115.    void Draw()
  116.    {//画自己
  117.       HGDIOBJ hBr=::SelectObject(m_hdc,::GetStockObject(GRAY_BRUSH));
  118.       //这里把迭代器的定义放到了for中
  119.       for(list<POINT>::iterator i=m_nodeList.begin();i!=m_nodeList.end();i++)
  120.       {
  121.         ::Rectangle(m_hdc,i->x,i->y,i->x+width,i->y+height);
  122.       }
  123.       ::SelectObject(m_hdc,hBr);
  124.    }
  125.    POINT GetHead()
  126.    {
  127.       return m_nodeList.front();
  128.    }
  129. };
  130. class Seed
  131. {
  132.    int x;
  133.    int y;
  134.    int m_width,m_height;
  135.    RECT m_rect;
  136.    HDC m_hdc;
  137. public:
  138.    Seed(int width,int height)
  139.       :m_width(width),m_height(height)
  140.    {}
  141.    void Init(HWND hWnd)
  142.    {
  143.       ::GetWindowRect(hWnd,&m_rect);
  144.       int rx=rand()%(m_rect.right-m_rect.left) / width * width;
  145.       int ry=rand()%(m_rect.bottom-m_rect.top) / height * width;
  146.       x=rx;
  147.       y=ry;
  148.       m_hdc=::GetDC(hWnd);
  149.    }
  150.    void Regain()
  151.    {//被吃到后用来重新产生一个位置
  152.       int rx=rand()%(m_rect.right-m_rect.left) / width * width;
  153.       int ry=rand()%(m_rect.bottom-m_rect.top) / height * width;
  154.       x=rx;
  155.       y=ry;
  156.    }
  157.    void Draw()
  158.    {
  159.       HGDIOBJ hBr=::SelectObject(m_hdc,::GetStockObject(GRAY_BRUSH));
  160.       ::Rectangle(m_hdc, x,y,x+m_width,y+m_height);
  161.       ::SelectObject(m_hdc,hBr);
  162.    }
  163.    POINT GetPosition()
  164.    {
  165.       POINT p;
  166.       p.x=x;
  167.       p.y=y;
  168.       return p;
  169.    }
  170. };
  171. 定义一个蛇,一个种子
  172. Snake snake(width,height,0);
  173. Seed seed(width,height);
  174. void Main(HWND hWnd)
  175. {
  176.    srand(::time(NULL));
  177.    //初始化他们
  178.    snake.Init(hWnd);
  179.    seed.Init(hWnd);
  180. }
  181. //^_^,这个就是没有精力去讲的东西
  182. //这个是运算符重载的例子
  183. //定义了这个POINT类型就可以用==来判断是不是相等了:)
  184. bool operator==(const POINT& p1,const POINT& p2)
  185. {
  186.    return p1.x==p2.x && p1.y==p2.y;
  187. }
  188. void Render(HWND hWnd)
  189. {
  190.    int k=GetKey();
  191.    //根据key的值判断是不是要变向
  192.    snake.ChangeDir(k);
  193.    HDC hDC=::GetDC(hWnd);
  194.    if(!snake.Move())
  195.    {
  196.       ::TextOut(hDC,100,100,"Die",3);
  197.       ::WaitKey();
  198.       ::Exit(hWnd);
  199.    };
  200.    //碰到了豆子
  201.    if(snake.GetHead()==seed.GetPosition())
  202.    {
  203.       //蛇长一截,豆子重新产生
  204.       snake.Grow();
  205.       seed.Regain();
  206.    }
  207.    //画出来
  208.    snake.Draw();
  209.    seed.Draw();
  210.    ::Sleep(100);
  211. }
复制代码


四、Q&A
1. 什么是结构?

结构是一种符合变量类型,他的地位和int,double等标准类型是相似的,但是他有自己的成员,或者叫域,或者叫字段(foxbase的遗老)。一个结构可以包含各种成员,他们共同组成一种类型。比如可以定一一个结构叫做“人”,包含有成员:名字,年龄,身高等

详情请参见任意一种有素质的语言的教程

2. 数组和链表有什么区别?

数组是内存中连续存放的一组值,可以通过下标直接访问。而链表则是通过指针讲每个节点和下一个节点联系在一起的结构,必须从头节点开始,一个挨着一个的访问,无法通过下标直接找到某个位置的值,他们在内存中存放的位置一般是不连续的
您需要登录后才可以回帖 登录 | 加入易码

本版积分规则

Archiver|手机版|小黑屋|EMAX Studio

GMT+8, 2024-4-28 04:17 , Processed in 0.013852 second(s), 18 queries .

Powered by Discuz! X3.4

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表