第三十二讲 class(2)

      再说昨天的内容之前,我们先来看看下面的程序:
——————————————-
#include <windows.h>LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine,
int iCmdShow)
{
static TCHAR szAppName[] = TEXT (“Hello Win32”) ;

HWND         hwnd ;

MSG          msg ;

WNDCLASS     wndclass ;


     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;

wndclass.lpfnWndProc   = WndProc ;

wndclass.cbClsExtra    = 0 ;

wndclass.cbWndExtra    = 0 ;

wndclass.hInstance     = hInstance ;

wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;

wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;

wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;

wndclass.lpszMenuName  = NULL ;

wndclass.lpszClassName = szAppName ;


     if (!RegisterClass (&

wndclass))
{
MessageBox (NULL, TEXT (“This program requires Windows8!”),
szAppName, MB_ICONERROR) ;

return 0 ;

}
hwnd = CreateWindow (szAppName,
TEXT (“Hello Win32”),
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL) ;

ShowWindow (hwnd, iCmdShow) ;

UpdateWindow (hwnd) ;

while (GetMessage (&

msg, NULL, 0, 0))
{
TranslateMessage (&

msg) ;

DispatchMessage (&

msg) ;

}

return msg.wParam ;

}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC         hdc ;

PAINTSTRUCT ps ;

RECT        rect ;

switch (message)
{
case WM_PAINT:
hdc = BeginPaint (hwnd, &

ps) ;

GetClientRect (hwnd, &

rect) ;

DrawText (hdc, TEXT (“Hello, Win32编程!”), -1, &

rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;

EndPaint (hwnd, &

ps) ;

return 0 ;

case WM_DESTROY:
PostQuitMessage (0) ;

return 0 ;

}

return DefWindowProc (hwnd, message, wParam, lParam) ;

}
—————————————-
这是应那些喜欢win32编程的要求,顺便穿插一些win32的编程基础,这是最简单的win32程序,这个程序和我们学习C,C++时开始的时候一样,所以我们同样是打印Hello,但是这一次,我们打印的不是在控制台,而是在可视化的应用程序中:
第三十二讲 class(2)
一开始我们说过,控制台程序的入口函数是main,而应用程序的入口函数则是WinMain,所以,我们上面的程序的WinMain就是创建窗口,注册窗口,也就是产生我们的应用程序框架,接下来LRESULT CALLBACK WndProc是一个回调函数,他的功能就是处理窗口消息,比如要在窗口上显示什么,让窗口响应键盘消息,响应鼠标按键统统都是在这个函数里面完成,所以说,win32编程其实是有个框架的,尤其是WinMain函数,我们可以直接copy到我们的新程序中,然后稍作修改即可。同样,WndProc函数也是有个固定的模式:
——————————————
LRESULT CALLBACK WndProc((HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{

      …………
     switch(message)
    {
            //处理各类型的窗口消息,如鼠标键盘等等消息都是在这里完成,根据message类型的不同而执行不同的消息。
     
}

}
————————————————–
这样是不是觉得有些麻烦了呢,好吧,我们再来看看下面简单点的,我们创建一个头文件,在头文件里写下如下代码:
—————————————————–
//CMyApp.h
#pragma once
#include <afxwin.h>

class CMyApp :public CWinApp
{
public:
BOOL InitInstance();

};

class CMyFram:public CFrameWnd
{
public:
CMyFram();

afx_msg
void OnLButtonDown(UINT nFlags,CPoint point);

afx_msg
void OnPaint();

DECLARE_MESSAGE_MAP();

private:
CPoint m_point;

CString m_str;

};


———————————–
然后我们创建一个cpp文件,实现我们声明的头文件,如下:
—————————————-
//CMyApp.cpp
#include “CMyApp.h”
CMyApp theApp;


BOOL CMyApp::InitInstance()
{
m_pMainWnd  = new CMyFram();

m_pMainWnd->ShowWindow(this->m_nCmdShow);

m_pMainWnd->UpdateWindow();

return TRUE;

}
BEGIN_MESSAGE_MAP(CMyFram,CFrameWnd)
ON_WM_LBUTTONDOWN()
ON_WM_PAINT()
END_MESSAGE_MAP()

CMyFram::CMyFram():m_point(0,0),
m_str(“”)
{
Create(NULL,TEXT(“TheApp”));

}

void CMyFram::OnLButtonDown(UINT nFlags,CPoint point)
{
m_point = point;

m_str.Format(_T(“Enter CMyFram::OnLButtonDown – %xl,%d,%d”),(long)nFlags,point.x,point.y);

MessageBox(m_str);

}

void CMyFram::OnPaint()
{
CPaintDC dc(this);

dc.TextOut(m_point.x ,m_point.y,_T(“Hello C++”));

}
——————————————–
我们运行程序,得到如下结果:
第三十二讲 class(2)
我们在窗口里点击鼠标,会有相应的反应。同样是实现一个简单的程序,那么大家更喜欢种方式呢?第一个我们用的是C语言,几乎都是在调用API函数,第二个我们用的是C++,我们通过继承的方式利用微软的MFC库,所以代码简单清晰,而效果不错,如果大家喜欢第一种,那么就要熟悉API,如歌大家喜欢第二种,那么我们还得继续纠结class。
好吧,言归正传,我们回到昨天的内容上来,我们简单的回顾一下昨天我的Date类:
———————————-
namespace My_Code{//定义一个名称空间
class Date
{
int d,y,m;

public:
Date(int,int,int);

//初始化日月年
Date(int,int);

//初始化日月,本年
Date(int);

//初始化日,本年本月
Date(const char*);

//字符串初始化
void Add_year(int n);

//n年以后或以前
void Add_month(int n);

//n月以后或以前
void Add_day(int n);

//n天以后或以前
};

}
——————————————
通常情况,我们可以定义一个全局对象出来:
Date today(27,2,2014);

然后我们可以定义构造韩如下:
——————————————
Date::Date(int dd,int mm,int yy)
{
d = dd?dd:today.d;

m = mm?mm:today.m;

y = yy?yy:today.y;

}
——————————————–
这里我们使用了三元操作法,目的是为检查数据的合法性,但是全局对象不利于OO思想,所以我们可以考虑另外一种方式,我们可以声明一个静态对象和一个静态函数:
——————————–
namespace My_Code{
class Date
{
int d,y,m;

static Date default_day;

public:
static
void set_default(int,int,int);


            …………
};

}
————————————
于是,我们的构造函数就可以这样写:
————————————-
Date::Date(int dd,int mm,int yy)
{
d = dd?dd:default_day.d;

m = mm?mm:default_day.m;

y = yy?yy:default_day.y;

}
—————————————
静态成员不但有全局性,还可以像其他成员函数一样使用,当我们使用到我们的Date类型的静态函数时:
————————————–
void  f()
{
     Date::set_Default(27,2,2014);


}
—————————————–
那么我们的静态成员函数和成员都必须得在其他地方另行定义:
——————————————
Date Date::default_day(27,2,2014);


 
void Date::set_default(int d,int m,int y)
{
Date::default_day = Date(d,m,y);

}
———————————————-
还记得我们昨天的例子里面有这么一句话吗?
———————————
Date today;
……
Date tomorrow = today;
———————————
我们定义的Date里面似乎没有定义的有“=”类型,那么为什么tomorrow = today没问题呢,这是因为当我们这样写的时候,他调用类的默认赋值函数,不过通常情况下,这样做不是明知的,我们应该显示的调用,而显示的调用我们就得为我们的类添加赋值构造函数:
————————————————-
Date(const Date&

);//复制构造函数
Date&

operator=(const Date&

);//赋值构造函数
—————————————————
看得出,其实所谓的赋值构造函数实际上就是操作符“=”的重载。所以当他们声明了这两个构造函数之后,我们便可以这样写:
——————————
1).tomorrow(today);
2).tomorrow = today;
——————————-
第一种写法会显示调用复制构造函数,而第二个调用的就是赋值构造函数。
———————————-
Date::Date(const Date&

t)
{
d = t.d;

m = t.m;

y = t.y;

}
 Date&

Date::operator=(const Date&

t)
{
if(this == &

t)

return *this;

d = t.d;

m = t.m;

y = t.y;

return *this;

}
——————————————
在使用赋值构造函数的时候记得避免自我赋值,自我赋值,听起来很滑稽,但是在编程里面什么陷阱都会存在,所以,我们得避免这种情况,所以我们既然要显示调用赋值构造函数,那就要来个深度赋值:
——————–
先判断,再赋值:
if(this == &

t)
     

return *this;


…………
——————–
那么不需要复制和赋值但又担心他偷偷摸摸的调用默认的赋值和复制构造函数怎么办呢?很简单,我们把这两个构造函数声明为私有的就好,这样就会避免编译器调用默认赋值和复制构造函数。
当然,一个Date如果只有上面这些东西显得太过单调,很多应该要用的功能都还没有,比如我们虽然有一个月的接口,但是我们却不知道要怎么表示他才能够让大家接手,所以,我们还得定义一个类型,用来专门表示月份的类型,当然,如果这个类型用struct或者是class显得大材小用,所以我们今天用一个新的东西来定义一个自定的类型,这种类型叫做枚举。枚举的关键字enum,所以要声明一个枚举类型很简单,就像下面:
————————————————-
enum Month{jan=1,feb,mar,apr,may,jun,jul,aug,sep,oct,nov,dec };


我们可以再添加几个函数,用来获取天数和月份年的
int day() const;


int month() const;


int year() const;


其实,天数和月份的函数我们可以声明为内联函数,这样执行起来效率会大大提高,声明为内联很简单,只需要在函数原型前面添加inline关键字即可
———————————————
还记得昨天我们定义的状态更新函数(
void Add_year(int n)……)是没有返回值的,这样虽然没什么,但是我们可以让他变得更加实用一些,我们让他们都返回Date类型的引用,这样一来便可以实现操作上的连接了,如同:
———————————————
…………
today.Add_year(1).Add_month(1).Add_day(1);


…………
———————————————–
要实现这样的操作不难,如同上面我们说的,只需让他返回一个Date的引用就好:
——————————————
…………
Date&

Add_year(int n);


Date&

Add_month(int n);


Date&

Add_day(int n);


…………
—————————————
看了下时间,发觉也一大晚上了,看来C++部分真不好说啊,尤其是这种实际开发中更是不好理解,不过我们这个Date类型也算完成了1/3,余下的内容就留待明天说吧,下面,我把我们这两天完成的内容贴上来,然后大家思考一个问题,我们的这个class里面的缺点是什么:
————————————–
//Date.h
namespace My_Code{
class Date
{
int d,y,m;

static Date default_day;

public:
Date(int,int,int);

Date(int,int);

Date(int);

Date();

Date(const char*);

Date&

Add_year(int n);

Date&

Add_month(int n);

Date&

Add_day(int n);

inline
int day() const
{

return d;

}
inline
int month() const
{

return m;

}
inline
int year() const
{

return y;

}
Date(const Date&

);

Date&

operator=(const Date&

);

static
void set_default(int,int,int);

};

}
————————————————–
由于每条信息只能容纳2万字,所以实现清单今天就不贴出来,本来还想让大家挑错的说。
思考一个问题:
当我们写下一个class时,编译器为我们产生了几个默认函数,分别是什么?

发表评论