第127讲 MDx11Frame(7)

上一讲我们主要介绍了一个基类HAppWindow,其实如果只想快速进入3D世界的话我们完全可以更加简单粗暴一些,之所以花了这么多时间费这么大的劲是因为我们希望在学习DirectX的时候顺便就把C++的编程思想也搞定。看过所有C++的语法但是还是搞不好一个系统这种情况比比皆是,所以我们希望在了解DirectX的时候也顺便来一起看看如何架构一个框架。


目前我们已经能够很快速的实现一个窗口,当然如果我们使用MFC或者Qt的话这些东西都可以忽略了,但是我们现在并没有使用Qt,同样没有使用MFC,就算我们现在使用Qt或者MFC,我们同样也会面临一个问题:比如很多人会问,如何在Qt里面使用DirectX呢?当然使用MFC的话会相对简单一些,但同样会存在问题,比如,事情的处理这些都是需要我们解决的。现在好了,我们设计得有通道的接口(HAppWindow),所以不管是在Qt或者MFC中,使用DirectX就变得很简单了。


说了一圈的废话,那么今天的主题是什么呢?在画出一个窗口后我们应该考虑做些什么吧,比如,我们可以开始对DirectX进行初始化啦,对于DirectX来说,我们可以准备一个基类,这个类我们就暂定为MDx11Base。


//==============================

class MDx11Base : public MNoCopy

{

public:

MDx11Base();

virtual ~MDx11Base();


virtual bool dx_Init(MAppWindow* container,bool isMultSample = true);

 

//初始化


void dx_Clear();

 

 

//清除资源


//

// 加载和卸载资源

//

virtual bool dx_LoadContent();

virtual bool dx_UnloadContent();



//

// 更新操作

//

virtual
void dx_Update(float dt);

 

//按时间更新


//

// 改变大小

//

virtual
void dx_ReShape(int width,
int height);


//

// 渲染函数

//

virtual
void dx_Render();

//渲染视图



//

// CPU空闲处理函数

//

virtual
void dx_OnIdle();


//

// 获取D3d相关信息

//

ID3D11Device* dx_GetDevice();

ID3D11DeviceContext* dx_GetContext();

IDXGISwapChain* dx_GetSwapChain();

ID3D11RenderTargetView* dx_GetRanderTargetView();

ID3D11Texture2D* dx_GetDepthBuffer();

ID3D11DepthStencilView* dx_GetDepthStencilView();

D3D11_VIEWPORT dx_GetViewPort();



private:

HINSTANCE m_hInstance;

 

//实例句柄

HWND  

m_hwnd;

 

 

 

//窗口句柄


D3D_DRIVER_TYPE m_driverType;

 

//驱动类型

D3D_FEATURE_LEVEL m_featureLevel;

 


ID3D11Device* p_d3dDevice;

 

 

 

ID3D11DeviceContext* p_d3dContext;

//渲染环境

IDXGISwapChain* p_swapChain;

//交换链

ID3D11RenderTargetView* p_backBufferTarget;

//渲染目标视图


//

// 深度模板资源

//

ID3D11Texture2D* p_depthTexture{nullptr };

ID3D11DepthStencilView* p_depthStencilView{nullptr };


//

// 视口信息

//

D3D11_VIEWPORT m_viewport;


//

// 是否启用多重采样

//

bool b_is_Enable4xMsaa{ true
};

bool b_is_UsePreBuidFont{ true
};

unsigned m_4xMsaaQuality{0
};

};


//========================================


如大家所见,这个基类的东西都相当简单,所以我们重点来看看dx_Init这个函数,这是重点。dx_Render 负责3D模型的渲染,dx_Update 可以简单的实现动画效果。dx_ReShape 当窗口大小改变时会被调用。好吧,我们接下来实现这个基类。


//=======================================

MDx11Base::MDx11Base():

m_driverType(D3D_DRIVER_TYPE_NULL), 

m_featureLevel(D3D_FEATURE_LEVEL_11_0),

p_d3dDevice(0), 

p_d3dContext(0), 

p_swapChain(0), 

p_backBufferTarget(0),

p_depthTexture(nullptr),

p_depthStencilView(nullptr)

{

}



MDx11Base::~MDx11Base()

{

dx_Clear();

}



bool MDx11Base::dx_Init(MAppWindow* container, bool isMultSample){

if (container == nullptr || container->WindHwnd() == nullptr)


return false;

m_hwnd = container->WindHwnd();

b_is_UsePreBuidFont = isPreBuidFont;

pContainerWindow = container;


m_hInstance = GetModuleHandle(nullptr);

b_is_Enable4xMsaa = isMultSample;



RECT dimensions;

GetClientRect(m_hwnd, &

dimensions);


unsigned
int width = dimensions.right – dimensions.left;

unsigned
int height = dimensions.bottom – dimensions.top;



D3D_DRIVER_TYPE driverTypes[] =

{

D3D_DRIVER_TYPE_HARDWARE, D3D_DRIVER_TYPE_WARP,

D3D_DRIVER_TYPE_REFERENCE, D3D_DRIVER_TYPE_SOFTWARE

};


unsigned
int totalDriverTypes = ARRAYSIZE(driverTypes);


D3D_FEATURE_LEVEL featureLevels[] =

{

D3D_FEATURE_LEVEL_11_0,

D3D_FEATURE_LEVEL_10_1,

D3D_FEATURE_LEVEL_10_0

};


unsigned
int totalFeatureLevels = ARRAYSIZE(featureLevels);


D3D_FEATURE_LEVEL myFeatureLevel;

HRESULT hr = D3D11CreateDevice(NULL, D3D_DRIVER_TYPE_HARDWARE, 0, 0,

featureLevels, 3, D3D11_SDK_VERSION, &

p_d3dDevice, &

myFeatureLevel, &

p_d3dContext);

if (FAILED(hr))

{

box::ErrorBox(“创建d3d11设备失败!”, m_hwnd);


return false;

}


if (myFeatureLevel != D3D_FEATURE_LEVEL_11_0)

{

if (IDNO == box::InfoBox(“您的机器不支持D3D11全部功能n程序运行可能会出问题,继续吗?”, m_hwnd))

{


return false;

}

}



//

// 检测4x采样等级

//

p_d3dDevice->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM, 4, &

m_4xMsaaQuality);


DXGI_SWAP_CHAIN_DESC swapChainDesc ={ 0
};

swapChainDesc.BufferDesc.Width = width;

//宽、高

swapChainDesc.BufferDesc.Height = height;

swapChainDesc.BufferDesc.RefreshRate.Denominator = 1;

//刷新率

swapChainDesc.BufferDesc.RefreshRate.Numerator = 60;

swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

//恒定参数,按照这样指明即可

swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;

//同上

swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

//数据格式,一个为RGBA四元色格式

swapChainDesc.BufferCount = 1;

//后缓冲区个数,1个足够

swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

//Usage,很好理解

swapChainDesc.Flags = 0;

swapChainDesc.OutputWindow = m_hwnd;

//主窗口句柄

if (b_is_Enable4xMsaa){

swapChainDesc.SampleDesc.Count = 4;

//多重采样

swapChainDesc.SampleDesc.Quality = m_4xMsaaQuality – 1;

}

else{

swapChainDesc.SampleDesc.Count = 1;

//多重采样

swapChainDesc.SampleDesc.Quality = 0;

}

swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

//交换:绝大多数情况用DISCARD

swapChainDesc.Windowed = true;

//窗口模式


unsigned
int creationFlags = 0;




#ifdef _DEBUG

creationFlags |= D3D11_CREATE_DEVICE_DEBUG;

#endif


HRESULT result;

unsigned
int driver = 0;


//

//获取IDXGIFactory以创建交换链

//

IDXGIDevice *dxgiDevice(NULL);

p_d3dDevice->QueryInterface(__uuidof(IDXGIDevice), reinterpret_cast<void**>(&

dxgiDevice));

IDXGIAdapter *dxgiAdapter(NULL);

dxgiDevice->GetParent(__uuidof(IDXGIAdapter), reinterpret_cast<void**>(&

dxgiAdapter));

IDXGIFactory *dxgiFactory(NULL);

dxgiAdapter->GetParent(__uuidof(IDXGIFactory), reinterpret_cast<void**>(&

dxgiFactory));

result = dxgiFactory->CreateSwapChain(p_d3dDevice, &

swapChainDesc, &

p_swapChain);

if (FAILED(hr))

{

MDx11String str = MDx11String::Format(“Failed to create the Direct3D SwapchainnFILE:%1nFun:%2nLine:%3”, __FILE__, __FUNCTION__, __LINE__);

box::ErrorBox(str, m_hwnd);


return false;

}

dxgiFactory->Release();

dxgiAdapter->Release();

dxgiDevice->Release();


if (FAILED(result))

{

MDx11String str = MDx11String::Format(“Failed to create the Direct3D devicenFILE:%1nFun:%2nLine:%3”, __FILE__, __FUNCTION__, __LINE__);

box::ErrorBox(str, m_hwnd);


return false;

}



ID3D11Texture2D* backBufferTexture;


result = p_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&

backBufferTexture);


if (FAILED(result))

{

MDx11String str = MDx11String::Format(“Failed to get the swap chain back buffernFILE:%1nFun:%2nLine:%3”, __FILE__, __FUNCTION__, __LINE__);

box::ErrorBox(str, m_hwnd);


return false;

}


result = p_d3dDevice->CreateRenderTargetView(backBufferTexture, 0, &

p_backBufferTarget);



if (backBufferTexture)

backBufferTexture->Release();


if (FAILED(result))

{

MDx11String str = MDx11String::Format(“Failed to create the render target viewnFILE:%1nFun:%2nLine:%3”, __FILE__, __FUNCTION__, __LINE__);

box::ErrorBox(str, m_hwnd);


return false;

}


//

// 创建深度模板缓存

//

D3D11_TEXTURE2D_DESC depth_desc;

ZeroMemory(&

depth_desc, sizeof(depth_desc));

depth_desc.Width = width;

depth_desc.Height = height;

depth_desc.MipLevels = 1;

depth_desc.ArraySize = 1;

depth_desc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;

depth_desc.SampleDesc.Count = 1;

depth_desc.SampleDesc.Quality = 0;

depth_desc.Usage = D3D11_USAGE_DEFAULT;

depth_desc.BindFlags = D3D11_BIND_DEPTH_STENCIL;

depth_desc.CPUAccessFlags = 0;

depth_desc.MiscFlags = 0;


safe_release(p_depthTexture);

hr = p_d3dDevice->CreateTexture2D(&

depth_desc, nullptr, &

p_depthTexture);

if (FAILED(hr)){

MDx11String str = MDx11String::Format(“CreateTexture2D FailnFILE:%1nFun:%2nLine:%3”, __FILE__, __FUNCTION__, __LINE__);

box::ErrorBox(str, m_hwnd);


return false;

}



D3D11_DEPTH_STENCIL_VIEW_DESC dsv_desc;

ZeroMemory(&

dsv_desc, sizeof(dsv_desc));

dsv_desc.Format = depth_desc.Format;

dsv_desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;

dsv_desc.Texture2D.MipSlice = 0;


safe_release(p_depthStencilView);

hr = p_d3dDevice->CreateDepthStencilView(p_depthTexture, &

dsv_desc, &

p_depthStencilView);

if (FAILED(hr)){

MDx11String str = MDx11String::Format(“CreateDepthStencilView FailnFILE:%1nFun:%2nLine:%3”, __FILE__, __FUNCTION__, __LINE__);

box::ErrorBox(str, m_hwnd);


return false;

}


p_d3dContext->OMSetRenderTargets(1, &

p_backBufferTarget, p_depthStencilView);

//p_depthStencilView


m_viewport.Width = static_cast<
float>(width);

m_viewport.Height = static_cast<
float>(height);

m_viewport.MinDepth = 0.0f;

m_viewport.MaxDepth = 1.0f;

m_viewport.TopLeftX = 0.0f;

m_viewport.TopLeftY = 0.0f;


p_d3dContext->RSSetViewports(1, &

m_viewport);


//

// 绑定必要的相关函数

//

pContainerWindow->RegisterOnIdleFun(MSLOT(&

MDx11Base::dx_Render, this));

pContainerWindow->RegisterDrawScreen(MSLOT(&

MDx11Base::dx_Render, this));

pContainerWindow->RegisterReShape(MSLOT(&

MDx11Base::dx_ReShape, this));

pContainerWindow->RegisterUpdataFun(MSLOT(&

MDx11Base::dx_Update, this));

pContainerWindow->RegisterClearFun(MSLOT(&

MDx11Base::dx_Clear, this));

pContainerWindow->RegisterMouseEnventFun(MSLOT(&

MDx11Base::dx_MouseEvent, this));

pContainerWindow->RegisterKeyEventFun(MSLOT(&

MDx11Base::dx_KeyEvent, this));


//

// 加载资源

//


return dx_LoadContent();

}



void MDx11Base::dx_Clear(){

dx_UnloadContent();

safe_release(p_depthStencilView);

safe_release(p_depthTexture);

safe_release(p_backBufferTarget);

safe_release(p_swapChain);

safe_release(p_d3dContext);

safe_release(p_d3dDevice );

safe_delete(pBufferManagerPtr);

}


bool MDx11Base::dx_LoadContent(){


return true;

}


bool MDx11Base::dx_UnloadContent(){


return true;

}



void MDx11Base::dx_Update(float dt){

//  

重载的时候重写该函数,如有必要的话

}


void MDx11Base::dx_ReShape(int width,
int height){

assert(p_d3dContext);

assert(p_d3dDevice);

assert(p_swapChain);

safe_release(p_backBufferTarget);

safe_release(p_depthStencilView);

safe_release(p_depthTexture);



HRESULT hr = p_swapChain->ResizeBuffers(1, width, height, DXGI_FORMAT_R8G8B8A8_UNORM, 0);

ID3D11Texture2D* backBuffer;

hr = p_swapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void**>(&

backBuffer));

hr = p_d3dDevice->CreateRenderTargetView(backBuffer, 0, &

p_backBufferTarget);

safe_release(backBuffer);



D3D11_TEXTURE2D_DESC depthStencilDesc;


depthStencilDesc.Width = width;

depthStencilDesc.Height = height;

depthStencilDesc.MipLevels = 1;

depthStencilDesc.ArraySize = 1;

depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;



if (b_is_Enable4xMsaa)

{

depthStencilDesc.SampleDesc.Count = 4;

depthStencilDesc.SampleDesc.Quality = m_4xMsaaQuality – 1;

}

// No MSAA

else

{

depthStencilDesc.SampleDesc.Count = 1;

depthStencilDesc.SampleDesc.Quality = 0;

}


depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;

depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;

depthStencilDesc.CPUAccessFlags = 0;

depthStencilDesc.MiscFlags = 0;


hr = p_d3dDevice->CreateTexture2D(&

depthStencilDesc, 0, &

p_depthTexture);

hr = p_d3dDevice->CreateDepthStencilView(p_depthTexture, 0, &

p_depthStencilView);

p_d3dContext->OMSetRenderTargets(1, &

p_backBufferTarget, p_depthStencilView);

//



m_viewport.TopLeftX = 0;

m_viewport.TopLeftY = 0;

m_viewport.Width = static_cast<
float>(width);

m_viewport.Height = static_cast<
float>(height);

m_viewport.MinDepth = 0.0f;

m_viewport.MaxDepth = 1.0f;


p_d3dContext->RSSetViewports(1, &

m_viewport);

}


void MDx11Base::dx_Render(){

//  

重载的时候重写该函数(必须)

}


void MDx11Base::dx_OnIdle(){

// 需要的时候可以重新写

}




//

// 获取D3d相关信息

//

ID3D11Device* MDx11Base::dx_GetDevice(){


return p_d3dDevice;

}


ID3D11DeviceContext* MDx11Base::dx_GetContext(){


return p_d3dContext;

}


IDXGISwapChain* MDx11Base::dx_GetSwapChain(){


return p_swapChain;

}


ID3D11RenderTargetView* MDx11Base::dx_GetRanderTargetView(){


return p_backBufferTarget;

}


ID3D11Texture2D* MDx11Base::dx_GetDepthBuffer(){


return p_depthTexture;

}


ID3D11DepthStencilView* MDx11Base::dx_GetDepthStencilView(){


return p_depthStencilView;

}


D3D11_VIEWPORT MDx11Base::dx_GetViewPort(){


return m_viewport;

}


//=========================


除了 dx_Init 和 dx_ReShape 两个函数之外其余的都没啥好说,其实这两个函数也没啥好说,如大家所见这是在初始化DirectX11 库的初始化,所以只要记住这么初始化就好了,嗯,确实是简单粗暴啊,现在我们只需要记住一点就好:如果我们有额外的需要初始化的东西可以放在dx_Init函数的后面,如果是在我们的框架中注定要有的东西可以这么干,如果是独立的初始化那么在子类的dx_Init函数中先调用父类的dx_Init函数,如果没问题了再对额外的东西进行初始化,当然为了更大的灵活性可以考虑不要在父类的dx_Init函数中调用dx_LoadContent(),具体调用由你决定,但事实上我们几乎不会去重写dx_Init这个函数,至于dx_ReShape几乎都会重写,但重写的时候记得调用MDx11Base::dx_ReShape。


现在有了这个基类之后,我们来实现我们的第一个DirectX11应用程序:


//======================

class FirstDirectWindow : public MDx11Base{

public:

FirstDirectWindow (){

}


~FirstDirectWindow (){ }

bool dx_LoadContent(){


return true;

}


void dx_Render();

};


void FirstDirectWindow ::dx_Render(){

if (!dx_GetDevice() || !dx_GetContext()){

return;

}

float clearColor[4] ={ 0.f, 0.f, 0.25f, 1.0f
};

p_d3dContext->ClearRenderTargetView(p_backBufferTarget, clearColor);

p_d3dContext->ClearDepthStencilView(p_depthStencilView, D3D11_CLEAR_DEPTH, 1.0f, 0.0f);

dx_GetSwapChain()->Present(0, 0);

}

//=========================


为了简单一点,所以我们什么都没有绘制,只是将窗口背景修改为蓝色,嗯,浅蓝色还是深蓝色有点搞不定了,总之是rgb的b分量为0.25.


现在我们来使用这个类,将这个窗口绘制出来。


//========================

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE prevInstance, LPWSTR cmdLine,
int cmdShow)

{

INITCOM;

MWindow w;

TestDirctWindow d3dwindow;

if (!d3dwindow.dx_Init(&

w)){


return 0;

}

w.SetTitle(“HellotWorld”);

w.Show();


return w.Run();

}


//========================

第127讲 MDx11Frame(7)


ok,我们这一讲的内容就这么简单的完成了吧,重点就是DirectX11库的初始化,这个初始化不要求是要理解,只要记住就好,把他放在基类的初始化就是因为这个原因,以后我们就不用在关心DirectX11的初始化啦,而是将更多的心思放在我们的任务上面。所以接下来我们可以在上面绘制一些简单的几何图形啦。


//============================================

回复D查看目录,回复数字查看相应章节


原文始发于微信公众号(

C/C++的编程教室

):第127讲 MDx11Frame(7)

|

第126讲 DirectX11Frame(6)

 

 

 

 

 

 

 

 

 


第126讲 DirectX11Frame(6)

 

 

 

 

 

 

 

前面几讲的内容过于难了一些,当然看不懂没关系,因为在整个框架里面他们只是作为底层应用来使用,而最终暴露的接口都相当的易于使用,那么,对于拿来主义这就足够了,因为接下来的内容都注定会是一马平川。

 

 

 

 

 

 

 

 

 

回顾我们前面说过的内容:一套反射机制(对于这种非主流的GUI库来说没有反射靠硬编码来实现界面配置的话那么结果会是有些吃力不讨好),一套事件系统(新的事件机制没有推送,以前我们使用的事件响应是依赖于boost的signal,不过boost的signal后来被我废弃了,所以现在的事件响应是一套全新的方案,当然也不算是全新的了,因为大致结构是模仿C#的事件),一套完备的属性系统(微信里面推送的是当初设计这个属性的一个版本,它的缺点我们已经说过,至于第二个版本没有在微信里进行推送,第二个版本不但支持多槽函数进行绑定还支持同步和异步两种连接方式,当然最重要的还实现了对象有效性的追踪),一个基础对象MObject(该对象继承至属性和反射,所以凡是MObject的子类都可以实现动态创建对象以及除了使用新的事件机制向外部发送事件外还能够使用属性的操作方式和一些需要接收事件的槽函数进行同步)。

 

 

 

 

 

 

 

 

 

好吧,简单的总结了一些我们前面说过的一些东西后下面我们继续向前,如同前面我们说过,DirectX本身并没有窗口的概念,他需要依附在特定的窗口上面才能够进行可视化的绘制,而标识这个窗口的东西就是HWND。虽然当初这个Frame使用win32来实现的,但后来被应用在Qt里面(公司使用的是Qt)进行项目开发,所以该Frame的设计就是支持跨平台的(不是指操作系统,而是指在Windows下面的开发平台),只要能够获取到HWND就能够使用这套Frame。

 

 

 

 

 

 

 

 

 

为了达到上面的效果,我们有必要来设计一个基础窗口类,它提供了一些必须的接口以及一些共有的实现,这个类就是MAppWindow.

//===============================
#pragma once
#include “MDx11Comm.h”
#include “MObject.h”
class MAppWindow : public MObject
{
public:
 

//
 

// 鼠标消息函数
 

// 鼠标按键,鼠标动作,x,y,是否为全局坐标
 

//
 

//
 

typedef std::function<bool(MDx11::MouseButton, MDx11::MouseAction,float,float, bool)>MouseFunType;

 

typedef std::function<void(void)>ADD_INIT;

 

 

 

 

 

 

 

// 附加初始化函数
 

typedef std::function<bool(void)>RECALL_INIT;

 

 

 

 

// 初始化函数
 

typedef std::function<void(void)>RECALL_DRAW;

 

 

 

 

// 绘图函数
 

typedef std::function<void(void)>RECALL_CLEAR;

 

 

 

// 清理资源函数
 

typedef std::function<void(int, int)>RECALL_RESHAPE;

 

// 更改尺寸处理函数
 

typedef std::function<LRESULT(HWND, UINT, WPARAM, LPARAM)> 

RECALL_WNDPROC;

 

// 底层消息回调处理函数
 

typedef std::function<void()> 

 

RECALL_IDLE;

 

// 空闲处理函数
 

typedef std::function<void(float)>RECALL_UPDATA;

 

 

// 动画更新函数
 

typedef std::function<void(wchar_t)>InputFunType;

 

// 输入回掉函数
 

typedef std::function<void(unsigned char)>KeyEventFunType;

// 键盘状态回调函数
 

typedef std::function<void(wchar_t)>KeyInputCharFunType;

// 处理输入字符函数


public:
 

MAppWindow();


 

virtual ~MAppWindow();

 

//
 

// 获取窗口句柄
 

//
 

virtual HWND WindHwnd(){

return nullptr;

}

 

//
 

// 移除窗口边框
 

//
 

virtual
void RemoveBorder(){ }

 

//
 

// 注册函数
 

//
 

void RegisterFunInitD3D(RECALL_INIT initGLfun);

 

 


 

void RegisterDrawScreen(RECALL_DRAW drawFun);

 

 

 

 

 

// 注册显示函数,很重要,没他,就显示不出3D画面
 

void RegisterReShape(RECALL_RESHAPE reshapeFun);

 

 

// 移动窗口会被调用
 

void RegisterWndProc(RECALL_WNDPROC WndProc);

 

 

 

 

 

// 注册事件回调函数,该函数将会相应窗口消息
 

void RegisterAddProcFun(RECALL_WNDPROC WndProc);


 

void RegisterOnIdleFun(RECALL_IDLE idlefun);

// 注册系统闲时处理函数
 

void RegisterClearFun(RECALL_CLEAR clearFun);

 

// 注册清除资源函数
 

void RegisterUpdataFun(RECALL_UPDATA updatafun) 

// 注册更新界面函数
 

void RegisterMouseEnventFun(MouseFunType fun);

 

 

// 注册鼠标事件函数

protected:
 

MouseFunType 

 

mMouseEventFun{ nullptr
};


 

RECALL_INIT 

 

mInitD3d;


 

RECALL_DRAW 

 

mDrawScreen;


 

RECALL_RESHAPE 

 

mReshape;


 

RECALL_CLEAR 

 

mClearFun;


 

RECALL_IDLE 

 

mIdleFun;


 

RECALL_UPDATA 

 

mUpdateFun;


};


 


//==========================================

 

注册系列函数我们已经实现,唯一需要用户自行实现的就只有WindHwnd和RemoveBorder这两个函数,对于使用Win32或者MFC的同学来说获取这个HWND实在是太简单了,对于使用Qt的同学来说这两个函数的实现依然很简单,对了,为什么我们要实现RemoveBorder呢?看看我们文章开头的图片,整个窗口全都是使用DirectX绘制出来的,包括标题栏和边框,这样我们就可以实现我们想要的任意风格了,否则就算你把Client区域做得多华丽但是一看窗口边框和标题栏就会不自觉的认为这不是一个风格的。

 

由于我们现在是在win32下,所以这里我们可以使用MAppWindow来作为我们真正的窗口基类。真正的win32窗口类MWindow:

//==================================

#pragma once
#include “MDx11String.h”
#include <unordered_map>
#include “MNoCopy.h”
#include <ITimer.h>
#include “MAppWindow.h”
using namespace MDx11;

class MWindow;


class MEventFun;

//
// 消息回调函数
//
HRESULT __stdcall WndProc(HWND, UINT, WPARAM, LPARAM);


class MWindow : public MAppWindow, public MNoCopy
{
 

DECLARE_CLASS(MWindow)

public:
 

explicit MWindow(MWindow* parent = nullptr);


 

virtual ~MWindow();

public:
 

unsigned Width() const{

return mWidth;

}
 

unsigned Height() const{

return mHeight;

}

 

void SetTitle(const MDx11String&

Title);

 

//
 

// 重写继承而来的两个虚函数
 

//
 

void RemoveBorder();


 

virtual HWND WindHwnd(){

return mHwnd;

}
 

//
 

// 开启消息循环
 

//
 

int Run() const;

 

//
 

// 显示窗口
 

//
 

void Show();

 


 

virtual
void SetExStyle(DWORD dwExStyle);


 

virtual
void SetStyle(DWORD dwStyle);

 

virtual
void SetIsFullScreen(bool fullscreen);

 

virtual
void Update(){ InvalidateRect(*this, nullptr, false);

}
 

virtual operator HWND() const{

return mHwnd;

}
 

virtual LRESULT __stdcall MemWndProc(HWND, UINT, WPARAM, LPARAM);

 

bool IsFullScreen(){

return bIsFullscreen;

}
 

bool IsActive(){

return bIsActive;

}

 

void CalculateFrameStats();

 

 

//
 

// 事件
 

//
public:
 

friend LRESULT __stdcall WndProc(HWND, UINT, WPARAM, LPARAM);

// 窗口回调函数 

 

//
 

// 定义几个事件属性
 

//
 

static MDx11String 

MouseClickedEvent;


 

static MDx11String 

MouseMoveEvent;


 

static MDx11String 

MouseEnterEvent;


 

static MDx11String 

MouseLeverEvent;


 

static MDx11String 

ContentChangedEvent;


 

static MDx11String 

SelectedChangedEvent;

 

void RegisterEventFun(const MDx11String&

EventDesc, MEventFun* SlotFun);


 

protected:
 

virtual bool 

 

 

InitWindow();


 

bool 

 

RegisterWndClass();

 

 

// 注册窗口类
 

bool 

 

GenWindow();

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

// 创建窗口
 

 

 


 

virtual int 

 

 

 

Msgloop() const;

 

 

 

 

 

 

 

 

 

 

 

 

 

// 消息循环

 

virtual
void MouseEnterWindow();


 

virtual
void MouseLeavesWindow();

 

//
 

// 窗口消息经回调函数转而进相关的成员函数,方便操作成员数据
 

//
 

LRESULT __stdcall BaseWndProc(HWND, UINT, WPARAM, LPARAM);

 

RECALL_WNDPROC 

mAddFun;

 

 

// 消息回调附加函数


private:
 

MDx11String 

 

 

 

 

 

 

mTitle;


 

HWND 

 

 

 

mHwnd;


 

unsigned 

 

 

mWidth;


 

unsigned 

 

 

mHeight;


 

DWORD 

 

 

 

mDwExStyle;

 

 

 

 

// 窗口风格
 

DWORD 

 

 

 

mDwStyle;

 

 

 

 

// 窗口风格
 

bool* 

 

 

 

 

 

 

 

 

 

 

 

 

bIsKeys;

 

 

//监控键状态
 

bool 

 

 

 

bIsInited;


 

volatile bool 

 

 

bIsDone;


 

bool 

 

 

 

bIsFullscreen;


 

bool 

 

 

 

bIsActive;


 

bool 

 

 

 

 

 

 

 

 

 

 

bIsAppPaused;


 

bool 

 

 

 

bIsMinimized;


 

bool 

 

 

 

bIsMaximized;


 

bool 

 

 

 

bIsResizing;

 

 

 

 

 

 

 

 


 


 

WNDCLASS 

 

 

mWndclass;


 

MDx11String 

 

 

 

 

 

 

mWndClassName;

 

// 


 

// 一个计时器,凡是以I开头的东西都是从com组件中导出来的
 

//
 

ITimer* 

 

 

pTimer{ nullptr
};

 

//
 

// 记录鼠标是否进入窗口
 

//
 

bool 

 

 

bIsEnter{ false
};

 

//
 

// 保存消息响应函数
 

//
 

std::unordered_map<MDx11String, MEventFun*>mEventFunMap;


};


//=====================================
 


 

 

 

 

 

 

 

 

 

这个类没啥多说的,唯一的技巧性操作是将回调函数过程转发指成员函数身上,要解决这个问题很简单,正cpp文件中创建一个全局窗口对象,在类的构造函数中将this直接赋给该全局指针,然后在回掉函数中根据HWND来检查出对应的窗口,从而将事件转发指相应的成员函数身上(其实如果只是简单的实现一个窗口大不必要这么麻烦,之所以这么干是考虑到以后该窗口类的扩展性)。

 

现在只需要下面的代码窗口就出来了:
//=====================================
MWindow w;

 

//创建一个对象
w.SetTitle(“Hello World”);

// 设置窗口标题
w.Show();

 

// 显示窗口
w.Run();

 

// 开启消息循环
//=======================================

第126讲 DirectX11Frame(6)

 

 

 

 

 

 

 

 

 

ok,今天的就到这里吧,具体实现因为比较简单就比贴出来了,只需要知道这些类有这些接口以及相关成员就足够了,接下来我们开始真正的进入DirectX,下一讲我们来说说该类的渲染核心——DirectX11的初始化。
//============================================
回复D查看目录,回复数字查看相应章节


原文始发于微信公众号(

C/C++的编程教室

):第126讲 DirectX11Frame(6)

|

第125讲 DirectX11Frame(5)

这一讲的内容和我们的框架有些偏,但是他实现基础实现的一部分,这一讲的主题应该叫巧用C++的异常机制配合模板元技巧将一定格式的字符串转换到指定类型的tuple中。


大家都知道字符串是没有类型的,或者说他是一种更为一般的泛型,凡是可以通过流序列化的对象即可转换为字符的形式,比如“123”是可以转换到double,同样可以转换到int等等。而C++的tuple却又是强类型的东西,这两者的区别可以是千差万别,而我们今天的主题是要将两者结合在一起。


std::tuple_element<0,std::tuple<…>>::type

std::tuple_element<1,std::tuple<…>>::type


上面的方式是我们获取tuple元素类型的方法。下面进入主题。


 

 

数据储存在文本中那么都是字符串,所以当需要处理数据的时候需要将字符串转换成相应的类型,这本来没什么,只需要知道类型即可转换,那么如何将一个字符串转换到指定类型的tuple中呢?因为把参数放进数据放进tuple中有很多好处,简单点说在操作lua函数中可以带来很大的便利,好吧,就算不在C++中使用lua,那么统一管理数据也是很好的,再比如在操纵函数过程中用来打包函数参数也是很好的.



 

 

假如有一个函数需要int,float,float的参数,而这些参数我们大概可能是从文件中读取,那么他们是字符串,通常一般大概都会这么做,知道参数类型,将字符串转换成相应的类型的数据,然后调用函数,问题来了,这样硬编码是不是真的很好呢?




void test(int i, double b){

std::cout <<i <<std::endl;

std::cout <<b <<std::endl;

}

double test(int i, double b,double c){

std::cout <<i <<std::endl;

std::cout <<b <<std::endl;

std::cout <<c <<std::endl;


return i*b*c;

}


 

 

 

 

typedef double(*F)(int, double, double);

F __f = test;

std::string str;

std::cin>>str;

ManualFun(__f, str);

// 使用输入的参数调用函数


 

 

 

如果我们想要达到上面的效果,我们通常会根据函数特性来根据参数类型把字符串转换到tuple,在将tuple解包得到我们要的函数执行所必须的参数即可,当然这些都在内部函数的调用中完成,所以比较简单,这里就不多说,下面我们来看看怎么将那个储存参数的tuple提取出来。

 

 

嗯,为什么说把储存参数的tuple提取出来需要拿出来说一下呢?因为tuple的操作都是使用了大量的模板,其实就是tuple就是一个跨越编译期和运行期的一个模板元应用,所以操作tuple不能像操作一般的容器对待,这是因为每一次操作tuple其实都是在操作一个临时的类型不同的对象。简单点说,你要检查tuple的每一个数据的类型都是一个模板的不同实例化:

 

 

 

 

 

std::tuple_element<0,std::tuple<…>>::type

std::tuple_element<1,std::tuple<…>>::type

 

 

同样是获取tuple中元素的类型,但是不同的元素就是使用tuple_element的不同实例化,也就是说std::tuple_element<0,std::tuple<…>>和 std::tuple_element<1,std::tuple<…>>就像大圣和白龙马一样没啥关系。而问题是我们想要让字符串转换到指定的tuple中我们就要知道tuple中每一个元素的类型。所以,这样一来,还是逃离不了使用模板元的节奏。

 

 

 

 

 

template<size_t index,size_t M,class T>

struct TypeConvert;


template<size_t index, size_t M, class … Args>

struct TypeConvert<index, M, std::tuple<Args…>>{

typedef typename std::tuple_element<index, std::tuple<Args…>>::type Type;

template<class T>

struct Apply{

Apply(T t) :mT(t){ }

inline
void apply(std::vector<MString>&

v)

{

MString str = v.front();

v.erase(v.begin());

auto tt = std::tuple_cat(mT, std::make_tuple(str.ToOtherType<Type>()));

TypeConvert<index + 1, M, std::tuple<Args…>>::Apply<decltype(tt)>(tt).apply(v);

}

T mT;

};

};


template<size_t M,class … Args>

struct TypeConvert<M,M,std::tuple<Args…>>{

typedef  

typename std::tuple_element<M-1, std::tuple<Args…>>::type Type;

template<class T>

struct Apply{

Apply(T t) :mT(t){ }

inline
void apply(std::vector<MString>&

v)

{

;

}

T mT;

};

};

template<class…Args>

std::tuple<Args…>&

Totuple(const std::string&

str, std::tuple<Args…>&

output){

MString Mstr = str;

std::vector<MString>v;

Mstr.split(” t”, v);

if (v.size() <sizeof…(Args)){


return output;

}

TypeConvert<0, sizeof…(Args), std::tuple<Args…>>::Apply<std::tuple<>>(std::tuple<>()).apply(v);


return output;

 

// 这里不是我们想要的结果

}


 

 

好吧,到现在为止,我们确实是将字符串的内容转换到tuple里面了,mT就是储存结果的,但是问题来了,我们该怎么获取这最后一个mT呢?

 

 

 

当然下面的使用方式是不可以的:

inline auto apply(std::vector<MString>&

v)->decltype(……………)

{


return mT;

}


 

 

就算通过各种技巧将返回类型给推导出来,那代码大概也是相当难看的,之所以不这么做,是因为可以很简单的获取我们想要的结果,那就是异常,只需要在递归终止时将结果当做异常抛出来即可:

 

 

 

 

 

inline auto apply(std::vector<MString>&

v)->decltype(……………)

{

throw mT;

}


 

 

然后在ToTuple函数中抓一下异常:

template<class…Args>

std::tuple<Args…>&

Totuple(const std::string&

str, std::tuple<Args…>&

output){

MString Mstr = str;

std::vector<MString>v;

Mstr.split(” t”, v);

// 使用空格来或者t来分隔字符串

 

 

 

// MString是自己写的一个字符串库,使用boost里的算法也能够完成这些操作

if (v.size() <sizeof…(Args)){


return output;

}

try{

TypeConvert<0, sizeof…(Args), std::tuple<Args…>>::Apply<std::tuple<>>(std::tuple<>()).apply(v);

}

catch (std::tuple<Args…>e){

output = std::forward<std::tuple<Args…>&

&

>(e);


return output;

 

// 这里就是我们想要的结果

}

catch (…){


return output;

// 如果得到的结果不是我们想要的

}

}


 

 

 

有木有觉得很巧妙呢?在对输出流操作符重载一下让他可以接受输入是不是更好呢?

template<class…Args>

inline std::istream&

operator>>(std::istream&

is, std::tuple<Args…>&

t){

std::string str;

std::getline(is, str);

if (str.empty())


return is;

t = Totuple(str, t);


return is;

}

 

 

 

 

 

现在可以这么来对tuple进行操作,还能够从文件中读取数据并保存在指定的类型中:


std::tuple<int,float,float>t;

Totuple(“10 20.3 30.5”,t);

 

std::cout <<t <<std::endl;

std::cin >>t;

std::cout <<t <<std::endl;

 

 

 


//============================================

回复D查看目录,回复数字查看相应章节


原文始发于微信公众号(

C/C++的编程教室

):第125讲 DirectX11Frame(5)

|

第124讲 DirectX11Frame(4)

今天没加班,好吧,那我们继续,上一讲我们说了Connect的声明原型,这一讲我们来看如何实现这个函数,从参数中其实我们大概可以推断出Connect的实现应该和第三个参数有关,这里我们再来回顾一下上一讲我们的声明式:


template<class T,class…Args>

 

 


void Connect(

 

 

 

 

 

 

const MString&

funName,  

 

// 作为信号触发的函数名

 

 

 

 

 

 

void(T::*fun1)(Args…),  

// funName对应的函数指针

 

 

 

 

 

 

T* obj1,  

 

 

// 持有函数名为funName的对象

 

 

 

 

 

 

std::function<void(Args…)>eventfun  

// 函数对象

 

 

 

 

 

 


其实从注释里面几乎可以肯定重点在于obj1本身,因为前面三个参数都是相关联的,而第四个是一个function(关于function到底是什么我没记错的话好像我们在前面的章节中有说过),而第四个参数所带来的灵活性就是无节操的无限制。说到这里,那么问题来了,Connect要如何做才能将这些杂乱的东西联系在一起呢?

我们先设想一种模型:obj1存储一张表,这张表记录他自身函数和槽函数相关联的信息(可以想象一下虚函数的派发原理,当然我们这个可没那个复杂)。ok,现在剩下的就是如何实现这张表了,map似乎可以满足我们的需求,这个关系也很简单,只需要将函数名作为key,将槽函数作为value来储存。这实现起来还是相对简单的,但是缺点是不支持重载函数,如果是Qt的信号槽就不存在这个问题,但是前提是得声明各种各样的信号。

怎么声明这个map呢???向下面这么干么?


template<class R,class…Args>

using AnyFun = std::function<R(Args…)>;


std::map<MString,AnyFun> 

mPropretyChangedFunMap;


知道这为什么不行吗?因为AnyFun本身依然还是模板,依然还是需要进行实例化才能够使用,所以上面的声明是不对的,那么,既然不能这么干,那么是不是可以考虑另辟蹊径了呢?前提首先要明白一点关键的地方在AnyFun上面而不是map上面,使用map来储存这个关系是没问题(如果我们想要支持重载函数的话那么可能要考虑multmap了),嗯,std::map<MString,void*>是不是可以呢?但是当回调的时候如何还原到函数类型呢?好吧,我们这里就不纠结,至少到这一步已经离成功不远了。


在我的工具库里面有一个MAny的东西,他能够储存任意类型的对象同时还能够还原对象的原型,这就是我们想要的,boost里面也有一个any(话说当初写这个MAny的时候也参考了boost的any,之所以没有使用boost的any主要是想让自己的东西依赖更少),所以我们可以这样声明map:

std::map<MString,MAny> 

mPerpertChangedFunMap;


此处,距离成功80%。接下来要做的事就是将响应槽函数和函数名关联起来:



void BindPropertChangedFun(const MString&

funName,const MAny&

fun){

 

 

 

 

mPerpertChangedFunMap[funName] = fun;

 

 

}


该是时候实现Connect了。


template<class T,class…Args>

 

 

static
void Connect(

 

 

 

 

 

 

const MString&

funName,

 

 

 

 

 

 

void(T::*fun1)(Args…),

 

 

 

 

 

 

T* obj1,

 

 

 

 

 

 

std::function<void(Args…)>eventfun

 

 

 

 

 

 

)

 

 

{

 

 

 

 

(obj->BindPropertChangedFun)(funName,eventfun);

 

 

}


 

 

从这个实现我们看出来那个函数指针似乎没什么用啊,也不知道当初脑子是怎么想的,好吧,先不管,好吧,现在我们已经将这种关系联系在一起,但是任务仅仅完成了三分之一。接下来要做的事就是我们如何让被关联的槽对象被执行。让完成这个过程那么应该有一个通知的动作,而这个通知的动作应该要知道是什么样的类型函数被调用,同时还要知道当前调用的函数的函数名,还能够传递参数,从这些信息中可以简单的推断出来这个通知函数的原型:


template<class T,class…Args>

 

 

void NotifyPropertyChanged(

 

 

 

 

 

 

const MString&

funName,

 

 

 

 

 

 

void(T::*func)(Args…),

 

 

 

 

 

 

Args…agrs

 

 

 

 

 

 

);


这个函数的调用也相当的复杂,声明也挺吓人,好吧,我们简化一下调用操作,比如如下:


NotifyPropertyChanged<TestA, const MString&

>(

MPropertyNotifer(TestA, SetValue),

value);


 

 

这样一来不至于在参数里面输入一个字符串那么突兀的吓人,这样一来还能够在编译器对参数进行检测,那么MPropertyNotifer又是什么呢?应该想起来了,和MSIGNAL一样同样是一个宏定义:


#ifndef MPropertyNotifer

#define MPropertyNotifer(className,memFun)
#memFun,&

className::memFun

#endif


现在调用约定也ok了,剩下的也就是怎么实现这个调用过程了,可以想象一下,首先我们通过第一个参数查询map,从中找到对应的MAny对象,然后还原到原始类型,最后调用传递进来的参数执行任务。


思路理清了,接下来就是怎么实现:


//

// 通过函数名查找MAny

//

MAny GetPropertChangedFun(const MString &

funName) const{

if(mPerpertChangedFunMap.count(funName)){

 

 

 

 

MAny any = mPerpertChangedFunMap.at(funName);

 

 

 

 


return any;

 

 

}

 

 

else{

 

 

 

 


return MAny();

 

 

}

}

//

// 获取被执行的函数

//

template<class…Args>

 

 

std::function<void(Args…)>MGetPropertChangedFun(

 

 

 

 

 

 

const MString&

funName)

 

 

{

 

 

 

 

try{

 

 

 

 

 

 

typedef std::function<void(Args…)>
funType;

 

 

 

 

 

 

MAny Any = GetPropertChangedFun(funName);

 

 

 

 

 

 


return mj::any_cast<
funType>(Any);

 

 

 

 

}

 

 

 

 

catch(mj::bad_any_cast e){

 

 

 

 

 

 


return nullptr;

 

 

 

 

}

 

 

}


有了这两个辅助函数后我们就可以实现


template<class T,class…Args>

 

 


void NotifyPropertyChanged(

 

 

 

 

 

 

const MString&

funName,

 

 

 

 

 

 

void(T::*func)(Args…),

 

 

 

 

 

 

Args…agrs

 

 

 

 

 

 

)

 

 

{

 

 

 

 

//

 

 

 

 

// 获取转发函数

 

 

 

 

//

 

 

 

 

std::function<void(Args…)>
fun =

 

 

 

 

 

 

 

 

MGetPropertChangedFun<Args…>(funName);

 

 

 

 

if(fun){

 

 

 

 

 

 

fun(agrs…);

 

 

 

 

}

 

 

}


到了这里就大功告成了,现在将这一部分封装到一个基类中——MProperty


class  

MProperty

{

 

 

friend class MDataBind;

 

 

template<class T>

 

 

friend class MConnect;

public:

 

 

MProperty();

 

 

virtual ~MProperty();


protected:

 

 

//

 

 

// 将属性被修改的这件事通知出去

 

 

// 第一参数为修改属性函数的名字,可以使用 MPropertyNotifer 来调用函数简化操作

 

 

// MPropertyNotifer 该宏会对函数名和函数进行参数打包

 

 

// eg:

 

 

// NotifyPropertyChanged(MPropertyNotifer(className,memFunName),args…)

 

 

//

 

 

template<class T,class…Args>

 

 

void NotifyPropertyChanged(

 

 

 

 

 

 

const MString&

funName,

 

 

 

 

 

 

void(T::*func)(Args…),

 

 

 

 

 

 

Args…agrs

 

 

 

 

 

 

)

 

 

{

 

 

 

 

//

 

 

 

 

// 获取转发函数

 

 

 

 

//

 

 

 

 

std::function<void(Args…)>
fun =

 

 

 

 

 

 

 

 

MGetPropertChangedFun<Args…>(funName,this);

 

 

 

 

if(fun){

 

 

 

 

 

 

fun(agrs…);

 

 

 

 

}

 

 

}



 

 

//

 

 

// 就算是子类也禁止访问

 

 

//

private:


 

 

MAny GetPropertChangedFun(const MString &

funName) const;


 

 

template<class…Args>

 

 

void BindPropertChangedFun(const MString&

funName,const MAny&

fun){

 

 

 

 

mPerpertChangedFunMap[funName] = fun;

 

 

}


 

 

//

 

 

// 获取多参数函数

 

 

//

 

 

template<class…Args>

 

 

std::function<void(Args…)>MGetPropertChangedFun(

 

 

 

 

 

 

const MString&

funName,

 

 

 

 

 

 

const MProperty* obj)

 

 

{

 

 

 

 

if(obj == nullptr)

 

 

 

 

 

 

return nullptr;

 

 

 

 

try{

 

 

 

 

 

 

typedef std::function<void(Args…)>
funType;

 

 

 

 

 

 

MAny Any = obj->GetPropertChangedFun(funName);

 

 

 

 

 

 

return mj::any_cast<
funType>(Any);

 

 

 

 

}

 

 

 

 

catch(mj::bad_any_cast e){

 

 

 

 

 

 

return nullptr;

 

 

 

 

}

 

 

}


private:

 

 

//

 

 

// 触发事件的函数名,接收事件的函数

 

 

//

 

 

MMap<MString,MAny> 

mPerpertChangedFunMap;

};



Connect作为MDataBind的静态成员函数,MDataBind作为MProperty的友元类,所以他能够访问到MProperty的私有函数:


class MDataBind

{

public:

 

 

MDataBind(){ }

 

 

virtual ~MDataBind(){ }


 

 

//

 

 

// 两个类之间的关联

 

 

//

 

 

template<class T,class…Args>

 

 

static std::shared_ptr<MConnect<T>>Connect(

 

 

 

 

 

 

const MString&

funName,

 

 

 

 

 

 

void(T::*fun1)(Args…),

 

 

 

 

 

 

T* obj1,

 

 

 

 

 

 

std::function<void(Args…)>eventfun

 

 

 

 

 

 

)

 

 

{

 

 

 

 

MAddPropertChangedFun(

 

 

 

 

 

 

 

 

 

 

funName,

 

 

 

 

 

 

 

 

 

 

fun1,

 

 

 

 

 

 

 

 

 

 

obj1,

 

 

 

 

 

 

 

 

 

 

eventfun);


 

 

 

 

std::shared_ptr<MConnect<T>>connectObj(new MConnect<T>(funName,obj1,eventfun));

 

 

 

 

return connectObj;

 

 

}



 

 

//

 

 

// 断开连接

 

 

//

 

 

template<class T,class…Args>

 

 

static
void DisConnect(

 

 

 

 

 

 

const MString&

funName,

 

 

 

 

 

 

void(T::*fun1)(Args…),

 

 

 

 

 

 

T* obj1,

 

 

 

 

 

 

std::function<void(Args…)>eventfun

 

 

 

 

 

 

)

 

 

{

 

 

 

 

std::function<void(Args…)>
fun{nullptr };

 

 

 

 

(obj1->BindPropertChangedFun)(funName,fun);

 

 

}


private:

 

 

//

 

 

// 添加属性改变函数

 

 

//

 

 

template<class T,class …Args>

 

 

static
void MAddPropertChangedFun(const MString&

funName,

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

void(T::*fun)(Args…),

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

T* obj,

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

std::function<void(Args…)>eventfun)

 

 

{

 

 

 

 

if(obj == nullptr)

 

 

 

 

 

 

return;

 

 

 

 

(obj->BindPropertChangedFun)(funName,eventfun);

 

 

}


};



现在我们再来看看上一讲我们说到的两个类:


class A : public MObject{

public:


void SetValue(const MString&

value){

if (mValue == value)

return;

mValue = value;


NotifyPropertyChanged<A, const MString&

>(

MPropertyNotifer(A, SetValue),

value);

}

private:

MString mValue;

};


class B : public MObject{

public:


void setValue(const MString&

value){

if (mValue == value)

return;

mValue = value;


NotifyPropertyChanged<B, const MString&

>(

MPropertyNotifer(B, setValue),

value);

}

private:

MString mValue;

};


void TestAFun(const MString&

value){

MString str(“TestAFun “);

str <<value;

box::QueBox(str);

}


现在当执行下面程序时首先objA中的值被修改,接着通知objB修改,最后通知自由函数TestAFun

{

A objA;

B objB;

MDataBind::Connect(MSIGNAL(A, SetValue, &

objA),

MSLOT(&

B::setValue,&

objB));

MDataBind::Connect(MSIGNAL(B, setValue, &

objB),

MSLOT(TestAFun));

objA.SetValue(“Hello World”);

}


现在来说一下这个属性的一些用处,他可以用于MVC模型,由于历史原因所以功能单一的他并没有从代码库中被移除,而是在当前版本上稍作修改。

这个版本的不足之处想必大家都看到了,他不支持多播,只能一对一,至于双向绑定不是问题,只需要反向调一下就行,尽管一对一监听已经可以满足我们大部分的需求,但有时候多播还是需要的,另一个问题就是就是不安全性,也就是说当objB被销毁时,槽对象并没有被销魂,他依然能够得到执行,这时候就是未定义行为,新版本解决了这些问题,但是当前版本依然被使用。


下面这个这个table就是以前使用这个属性完成的:

第124讲  DirectX11Frame(4)
第124讲  DirectX11Frame(4)
第124讲  DirectX11Frame(4)


MTableWindow  

pTableWindow{nullptr };

// 视图

 

 

MTableData  

 

mTableData;

 

 

 

 

 

// 数据


然后两者之间相互绑定:

pTableWindow.BindTableData(&

mTableData);

 

 

mTableData.BindTableWindow(&

pTableWindow);


我们通过右键触发生成一些随机数来填充数据,然后界面会得到相应的更新,同时如果我们在街面上进行单元格修改,数据也会得到相应的更新。


int row = gen();

// 生成随机的整数

 

 

 

 

int col = gen();

// 生成随机的整数

 

 

 

 

mTableData.reSize(row,col);

// 修改数据大小,同时界面的表格也会相应的被修改

HArray<HArray<MString>>data;

 

 

 

 

for(int i=0;

i<row;

++i){

 

 

 

 

 

 

HArray<MString>v;

 

 

 

 

 

 

for(int j=0;

j<col;

++j){

 

 

 

 

 

 

 

 

v<<
fgen();

// 生成随机的浮点数

 

 

 

 

 

 

}

 

 

 

 

 

 

data<<v;

 

 

 

 

}

 

 

 

 

mTableData.setData(data);



ok,剩下的问题,如何解决我们上面所说的缺点。


//============================================

回复D查看目录,回复数字查看相应章节


原文始发于微信公众号(

C/C++的编程教室

):第124讲 DirectX11Frame(4)

|

第123讲  DirectX11Frame(3)

时隔将近半年的时间,想想应该说声抱歉,总感觉这抱歉有些无力,还是算了,大家都是搞技术的,所以还是直接来点实际点的吧。关于上一讲的内容已忘记得差不多,也懒得去纠结,不论是说过的亦或是没说过的我们就当重新开始,以便能够更好的连贯,一句话,我们的所说的东西都是这个Frame里的内容。

原本打算先和大家说怎么在C++里面操作的lua的,毕竟这个Frame的主要功能便是靠lua实现的,但是想想可能有些朋友对lua不甚了解所以在说怎么和lua通信之前那么大家可以先了解lua是个什么鬼,哦,对了,我用的是5.3版本的,虽然这个版本没什么第三方库,但是现在很多常用的东西我自己已经添加好,所以顺手了也就无所谓了。


那么今天我们从什么地方入手呢?嗯,好吧,就从“属性”这玩意而已开始吧,属性这个东西可能不是大家想想的属性,我取名字这事已经被同事吐槽了不下10次了,总觉得我名字莫名其妙,好吧,不说这些,先来认识下我所谓的属性到底是个什么鬼吧。


假设对象A表示一个矩形,那么他将有长和宽的概念,另一个对象B表示一个椭圆,当对象A的尺寸发生变化时而对象B能够知道A的尺寸发生变化而做出相应的改变,这就是我所谓的属性的概念,长和宽是矩形的属性,属性改变将会通知关心他属性的对象所作相应的动作,现在我用代码模拟这种情况出来:


//===================================

class A{

public:


void SetValue(const MString&

value){

if(mValue == value)

return;

mValue = value;

}

private:

MString mValue;

};


class B{

public:


void setValue(const MString&

value){

if(mValue == value)

return;

mValue = value;

}

private:

MString mValue;

};



void testFun(const MString&

value){

ErrorBox(“testFun called”)

}


int main(){

A objA;

B objB;

objA.SetValue(123);


return 0;

}

//======================================


现在我们要做的事就是当A类的SetValue被调用时B类的setValue也相应的被调用,也就是A的mValue的值被修改时那么B也应当修改自己的mValue值,现在我们要解决的就是该问题。


解决该问题的方法可能有很多,如果在Qt里面我们可以考虑使用信号槽来解决,在MFC中可以考虑使用事件通知来完成,在C#里面使用事件委托来完成,嗯不过好像不论是哪一种方式感觉都不是最理想的,因为如果使用信号槽那么如果我们需要观察多个属性的话可能会要定义好多个信号,同样如果使用事件的话那么我们同样需要定义很多事件类型,所以这些都不是一个一劳永逸的方法,这都是我所认为最理想的方法。


大家可能想起了前面我们所说的boost里面的信号槽,不错的主意,但是这个Qt的信号槽本身是差不多的东西,所以一样不满意,当然最重要的一点,如果我们通过objA调用SetValue的时候会触发objB的setValue,而objB在调用setValue的时候就会触发testFun,那么无论是Qt的信号槽还是boost的信号槽都没法做到,那么好吧,这样一来似乎没有什么选择的余地了,要么是函数对象要么就是函数指针。


接下来要说的东西可能以我之口舌没法很好的说清楚,重要的还是大家多理解代码,我觉得一码胜千言。要让objA和objB联系起来那么必然通过一种介质,假如这个函数为Connect,那么我们先将设会是下面这种形式:


Connect(SIGANL(objA,xxxx),SLOT(objB,xxxx));


这个形式模仿的是Qt的connect,这就不说了,我们现在来看看如果实现这个Connect函数,而他的参数又应该是什么?想一下,当我们操作objA到时候objB会得到相应的响应,那么这个函数里面我们至少要知道objA和objB关联的到底是那个函数,所以此处应该需要将函数传递到Connnect里面。ok,现在我们来看一个原型:


template<class T,class…Args>

 

 


void Connect(

 

 

 

 

 

 

const MString&

funName,

 

 

 

 

 

 

void(T::*fun1)(Args…),

 

 

 

 

 

 

T* obj1,

 

 

 

 

 

 

std::function<void(Args…)>eventfun

 

 

 

 

 

 

)


从这个原型我们几乎可以肯定这就是我们所需要的了,funName是函数的名字fun1就是objA要调用的函数,obj1就是objA,eventfun即为我们被触发的函数,从这个函数原型来看他是支持无限参数的,所以他所完成的事是Qt的信号槽亦或是boost的信号都做不到的,大家也许会像何以见得呢?当然更多的可能是怎么来调用这个函数?和上面一样我们可以再进一步封装然后如下调用:


Connect(MSIGNAL(TestA, SetValue, &

objA),MSLOT(&

B::setValue,&

objB));

这样一来是不是很清晰了呢?MSIGNAL包装的是类的名字,函数,对象的指针,MSLOT的参数为成员函数的地址以及对象指针,好吧,这就作为我们的调用约定,那么问题来了,MSIGNAL和MSLOT是如何实现的呢?为什么两个参数调用方式差距还如此之大呢?为了满足我们上面Connect的原型,SIGNAL必须能够生成一个和函数名等同的字符串以及该函数的函数指针和一个对象指针,而MSLOT将会通过成员函数的地址以及对象指针生成一个函数对象出来。


看到这里大家是不是觉得开始有些懵了呢?好吧,我们正在C++的路上越走越深,因为这些如果不是出于构建库的目的话正常使用过程中几乎可能肯定不会使用得到。


MSIGNAL和MSLOT的调用差别如此之大正是因为两者的实现方式天差地别,因为MSIGNAL的现实相当简单,其实他就是一个宏定义:


#ifndef MSIGNAL

#define MSIGNAL(className,memFun,obj)
#memFun,&

className::memFun,obj

#endif


但是MSLOT的实现却相当复杂,要说清楚真心不是那么容易,代码虽然不多,但是想要理清还是需要好好琢磨一下:


//===================================


//

// 下面只是一些辅助函数,不需要看懂,只需要知道怎么使用MSLOT即可

//

template<size_t N>

struct MPropertyFunHelp{

 

 

template<class T,class F,class K,class…Args>

 

 

static inline auto Apply(T t,F fun,K obj, Args…args)->

 

 

decltype(

 

 

 

 

 

 

MPropertyFunHelp<N – 1>::Apply

 

 

 

 

 

 

 

(

 

 

 

 

t,fun, obj,

 

 

 

 

std::get<N – 1>(t),

 

 

 

 

args…)

 

 

 

 

 

 

)

 

 

{

 

 

 

 

return MPropertyFunHelp<N – 1>::Apply

 

 

 

 

 

 

 

 

(

 

 

 

 

 

 

t,fun,obj,

 

 

 

 

 

 

std::get<N – 1>(t),

 

 

 

 

 

 

args…

 

 

 

 

 

 

 

 

 

 

);

 

 

}

};


template<>

struct MPropertyFunHelp<0>{

 

 

template<class T,class F,class K,class…Args>

 

 

static inline auto Apply(T t, F fun,K obj,Args…args)->decltype(

 

 

 

 

 

 

std::bind(fun,obj,args…)

 

 

 

 

 

 

)

 

 

{

 

 

 

 

return std::bind(fun,obj,args…);

 

 

}

};




template<int N,int M,class R>

struct ToFun{

 

 

template<class T,class F,class K,class…Args>

 

 

static std::function<R(Args…)>Apply(T t,F fun,K obj){

 

 

 

 

auto __t = std::tuple_cat(t,std::make_tuple(std::_Ph<N+1>()));

 

 

 

 

return ToFun<N+1,M,R>::Apply<decltype(__t),F,K,Args…>

 

 

 

 

 

 

 

 

(__t,fun,obj);

 

 

}

};


template<int N,class R>

struct ToFun<N,N,R>{

 

 

template<class T,class F,class K,class…Args>

 

 

static std::function<R(Args…)>Apply(T t,F fun,K obj){

 

 

 

 

return MPropertyFunHelp<sizeof…(Args)>::Apply(t,fun,obj);

 

 

}

};


//

// 包装成员函数,我们需要用的就是下面的函数

//

template<class T,class R,class…Args>

std::function<R(Args…)>MSLOT(R(T::*fun)(Args…),T* obj){

 

 

auto t = std::make_tuple();

 

 

return ToFun<0,sizeof…(Args),R>::Apply<decltype(t),decltype(fun),T*,Args…>

 

 

 

 

 

 

(

 

 

 

 

 

 

 

 

t,fun,obj

 

 

 

 

 

 

);

}


//

// 包装自由函数

//

template<class R,class…Args>

std::function<R(Args…)>MSLOT(R(*fun)(Args…)){

 

 

return std::forward<std::function<R(Args…)>>(fun);

}



//================================


到此下面的函数便可被正确调用了:

Connect(MSIGNAL(TestA, SetValue, &

objA),MSLOT(&

B::setValue,&

objB));

该调用模式适用于任何类型包括自由函数而且不受参数个数限制都是同样的调用方式,也就是说无论SetValue和setValue的参数有多少个上面的函数调用依旧不变。


接下来大家关心的可能是Connect的实现了,只是今天就先到此为止吧,下一讲我们来讲如何实现Connect以及如果让这一切工作起来。


//============================================

回复D查看目录,回复数字查看相应章节


原文始发于微信公众号(

C/C++的编程教室

):第123讲  DirectX11Frame(3)

|

第122讲 DirectX11Frame(2)

上一讲我们说关于这个框架用到的必要的基础信息,那么这一讲我们开始构造这个框架的基类,通常来说一个通用的基类一般只会提供接口信息,但是这里我们并不打算这么做,为什么呢?真正的接口我们将他放进后面的MDx11Window这个类,MDx11Window类是用于处理DirectX11窗口的信息的基类,而MDx11Window类派生于MObject类,那么既然我们使用的是MDx11犀利那么为什么又要从MObject类说起来呢?嗯,事情是这样的,在以前写这个Frame的时候并没有将MDx11考虑进去,当时只是将Windows的窗口进行按需封装,所以在这个Frame里面还有一系列的类,而这系列的类就是MWindow系列,使用MWindow系列一样能够简单的构架应用程序,而且MWindow一样支持DirectX11进行渲染,不过他的渲染方式和MDx11Window的渲染方式有所不同,MWindow的每一个控件都是有自己的HWND的,而MDX11Window的空间都是DirectX11绘制出来的,简单点说,MWindow是先有控件然后在控件上面进行绘制,而MDx11Window是直接在一块画布上绘制出控件以及所要的信息,MWindow不需要Lua脚本语言就能够绘制出界面,而MDx11Window需要Lua脚本语言的支持再能生成漂亮的界面,因为MDx11Window的界面信息全都是放在Lua脚本处理的。不过MDx11Window可以绘制出任何想要的界面类型,而MWindow就显得中规中矩,因为MWindow只是简单的封装Windows控件,即使使用DirectX11对控件进行渲染,也只能修改窗口控件背景而已,所以相对来说,MDx11Window就显得要炫酷得多。

好吧,扯了这些大家也算是对为什么我们要在这里写一个基类有所理解了,如我们一开始所说,这个基类几乎不干什么有用的事,他只是提供了一个方便性而已,让MDx11Window和MWindow拥有一套相同的事件处理方式而已。


//==============================================================================

#pragma once

#include "

MReflection.h"


//———————————————-

// 框架基础类

//———————————————-

class MObject

{

 

 

DECLARE_CLASS(MObject) // 如果想要动态创建,就需要使用该宏

public:

 

 

MObject();

 

 

virtual ~MObject();

 

 

MObject(const MObject&

) = delete;

 

 

MObject&

operator=(const MObject&

) = delete;

public:

 

 

std::shared_ptr<MClassFactory>GetObjFactory() const;

 

 

std::shared_ptr<MClassFactory>GetObjFactory();

 

 


void Clear();


 

 


 

 

//=================================================

 

 

// 下面的都是虚函数

 

 

// 根据不同情况进行改写

 

 

//=================================================

public:

virtual const MDx11String&

GetContexData() const;

virtual
void SetContexData(const MDx11String&

contex);

virtual MDx11String ToString() const;

 

 

virtual
void Show();

 

 

virtual
void Update();


 

 

//================================================

 

 

// 只有子类才能够访问的信息

 

 

//================================================

protected:

 

 

std::shared_ptr<MClassFactory>mClassFactory;


 

 

//===============================================

 

 

// 友员函数,对流输出的支持

 

 

//===============================================

 

 

friend std::ostream&

operator<<(std::ostream&

os,const MObject&

obj){

 

 

 

 

os<<obj.ToString()<<"

t"

<<&

obj<<std::endl;

 

 

 

 


return os;

 

 

}

MDx11String mContexData;

};


//===========================================================


对于所有的MObject派生的对象,我们是禁止拷贝和复制,mClassFactory这是我们在动态创建对象的时候所以使用的,mContexData我们用来保存空间信息的对象,比如保存一个文本框的所有内容,一个按钮的名字,一个列表控件的信息等等,而MDx11String这是一个字符串类,以前我们曾经介绍过一个MString的字符串类,而这个Frame原本我们使用的就是MString,但是后来发现在我们自己处理输入的情况的时候显得无法满足于是在MString的基础上重新架构了MDx11String,MDx11String不仅对MString兼容还同时对unicode字符的支持,而且修改了一些核心函数的实现方式,使得MDx11String有更好的鲁棒性(测试到现在没有发生任何运行问题)。

//===========================================================

#include "

MObject.h"


IMPLEMENT_CLASS(MObject)


MObject::MObject()

{

 

 

mClassFactory = MClassFactory::Instance();

}


MObject::~MObject()

{

}



void MObject::RegistProperty()

{

}


std::shared_ptr<MClassFactory>MObject::GetObjFactory() const

{

 

 

return mClassFactory;

}


std::shared_ptr<MClassFactory>MObject::GetObjFactory(){

 

 

return mClassFactory;

}


const MDx11String&

MObject::GetContexData() const{


return mContexData;

}


void MObject::SetContexData(const MDx11String&

contex){

mContexData = contex;

}



void MObject::Clear()

{

mContexData.clear();

}


MObject::operator MDx11String() const

{

 

 

return this->ToString();

}


MDx11String MObject::ToString() const

{

 

 

return this->MObjectName;

}


void MObject::Show()

{

}


void MObject::Update()

{

}

//===============================================

关于实现相当的简单,没啥可说,就是在构造函数中将我们的MClassFactory进行实例化即可。和MObject一样通用的是另一个类——事件处理类,在这个Frame中我们的事件管理使用的boost里面的signal,大家还记得吗?如果忘记的话可以回头去看看我们说的boost的信号槽。在这个事件管理类里面我们仅仅对四个参数一下的事件进行管理,当然还是可以扩展的,但是因为没有多少场合会是应用超过4个参数的事件回调函数,而传递的参数我们使用的是boost的any类型,any,顾名思义他可以是任意的类型,但是需要付出的代价是在我们写回调函数的时候需要对参数进行转换,而转换函数便是AnyTypeCast,如果转换成功我们就获取参数的指针,如果不成功就是一个未初始化的opional,这个是不能使用的。


//===============================================

#pragma once

//=========================================================

// 该类用来管理事件的处理状态

// 当然可以直接使用MKeyEvent 和 MMouseEvent进行处理事件

//=========================================================

#include <boost/signals2.hpp>

#include "

MReflection.h"

#include <iostream>

#include <thread>

#include <condition_variable>

#include <mutex>


class MObject;


//———————————————-

// 事件回掉函数

//———————————————-

typedef const boost::any&

AnyType;

// 使用下面的转换函数可以转换到正确的类型


//———————————————————

// 需要对返回结果进行判断是否转换成功

//———————————————————

template<class T>

boost::optional<T>AnyTypeCast(AnyType any){

try{


return boost::optional<T>(boost::any_cast<T>(any));

}

catch (boost::bad_any_cast e){


return boost::optional<T>();

}

}


//—————————————————————

// 定义几种事件驱动类型

//—————————————————————

typedef boost::signals2::signal<void(MObject*)>EventNoArgHandle;

typedef boost::signals2::signal<void(MObject*, AnyType)>Event1ArgHandle;

typedef boost::signals2::signal<void(MObject*, AnyType, AnyType)>Event2ArgHandle;

typedef boost::signals2::signal<void(MObject*, AnyType, AnyType, AnyType)>Event3ArgHandle;

typedef boost::signals2::signal<void(MObject*, AnyType, AnyType, AnyType, AnyType)>Event4ArgHandle;



//—————————————————————–

// 能够被执行回调操作的函数类型

//—————————————————————–

typedef boost::function<void(MObject*)>EventNoArgDelegation;

typedef boost::function<void(MObject*, AnyType)>Event1ArgDelegation;

typedef boost::function<void(MObject*, AnyType, AnyType)>Event2ArgDelegation;

typedef boost::function<void(MObject*, AnyType, AnyType, AnyType)>Event3ArgDelegation;

typedef boost::function<void(MObject*, AnyType, AnyType, AnyType, AnyType)>Event4ArgDelegation;


typedef boost::signals2::connection  

ConnectStatus;

// 管理连接状态

typedef boost::signals2::shared_connection_block SharedConnectedBlock;

// 阻塞连接


class MEventHandle

{

public:

MEventHandle();

virtual ~MEventHandle();


ConnectStatus operator+=(const EventNoArgDelegation&

fun);

ConnectStatus operator+=(const Event1ArgDelegation&

fun);

ConnectStatus operator+=(const Event2ArgDelegation&

fun);

ConnectStatus operator+=(const Event3ArgDelegation&

fun);

ConnectStatus operator+=(const Event4ArgDelegation&

fun);



void EmitEvent(MObject* sender);


void EmitEvent(MObject* sender, AnyType args);


void EmitEvent(MObject* sender, AnyType arg1, AnyType arg2);


void EmitEvent(MObject* sender, AnyType arg1, AnyType arg2, AnyType arg3);


void EmitEvent(MObject* sender, AnyType arg1, AnyType arg2, AnyType arg3, AnyType arg4);


private:

std::shared_ptr<EventNoArgHandle> 

mNoArgHandle;

std::shared_ptr<Event1ArgHandle> 

mOneHandle;

std::shared_ptr<Event2ArgHandle> 

mTwoHandle;

std::shared_ptr<Event3ArgHandle> 

mThreeHandle;

std::shared_ptr<Event4ArgHandle> 

mFourHandle;


std::vector<boost::shared_ptr<EventNoArgDelegation>> mNoArgsFuns;

std::vector<boost::shared_ptr<Event1ArgDelegation>> mOneArgsFuns;

std::vector<boost::shared_ptr<Event2ArgDelegation>> mTwoArgsFuns;

std::vector<boost::shared_ptr<Event3ArgDelegation>> mThreeArgsFuns;

std::vector<boost::shared_ptr<Event4ArgDelegation>> mFourArgsFuns;

};



//====================================================


如果大家还记得我们以前说的boost的信号槽的话,那么实现也是相当简单,简单到完全没啥可说的:


//====================================================#include "

MEventHandle.h"

#include <
future>

#include <HPath_File.h>

MEventHandle::MEventHandle()

{

mNoArgHandle = std::shared_ptr<EventNoArgHandle>(new EventNoArgHandle);

mOneHandle = std::shared_ptr<Event1ArgHandle>(new Event1ArgHandle);

mTwoHandle = std::shared_ptr<Event2ArgHandle>(new Event2ArgHandle);

mThreeHandle = std::shared_ptr<Event3ArgHandle>(new Event3ArgHandle);

mFourHandle = std::shared_ptr<Event4ArgHandle>(new Event4ArgHandle);

}



MEventHandle::~MEventHandle()

{

}


ConnectStatus MEventHandle::operator+=(const EventNoArgDelegation&

fun){

boost::shared_ptr<EventNoArgDelegation>__F(new EventNoArgDelegation(fun));

mNoArgsFuns.push_back(__F);


return mNoArgHandle->connect(EventNoArgHandle::slot_type(*__F).track(__F));

}


ConnectStatus MEventHandle::operator+=(const Event1ArgDelegation&

fun){

boost::shared_ptr<Event1ArgDelegation>__F(new Event1ArgDelegation(fun));

mOneArgsFuns.push_back(__F);


return mOneHandle->connect(Event1ArgHandle::slot_type(*__F).track(__F));

}


ConnectStatus MEventHandle::operator+=(const Event2ArgDelegation&

fun){

boost::shared_ptr<Event2ArgDelegation>__F(new Event2ArgDelegation(fun));

mTwoArgsFuns.push_back(__F);


return mTwoHandle->connect(Event2ArgHandle::slot_type(*__F).track(__F));

}


ConnectStatus MEventHandle::operator+=(const Event3ArgDelegation&

fun){

boost::shared_ptr<Event3ArgDelegation>__F(new Event3ArgDelegation(fun));

mThreeArgsFuns.push_back(__F);


return mThreeHandle->connect(Event3ArgHandle::slot_type(*__F).track(__F));

}


ConnectStatus MEventHandle::operator+=(const Event4ArgDelegation&

fun){

boost::shared_ptr<Event4ArgDelegation>__F(new Event4ArgDelegation(fun));

mFourArgsFuns.push_back(__F);


return mFourHandle->connect(Event4ArgHandle::slot_type(*__F).track(__F));

}



void MEventHandle::EmitEvent(MObject* sender){

std::async(std::launch::async, [=](){(*mNoArgHandle)(sender);

});

}


void MEventHandle::EmitEvent(MObject* sender, AnyType args){

std::async(std::launch::async, [=](){(*mOneHandle)(sender,args);

});

}


void MEventHandle::EmitEvent(MObject* sender, AnyType arg1, AnyType arg2){

std::async(std::launch::async, [=](){(*mTwoHandle)(sender, arg1, arg2);

});

}


void MEventHandle::EmitEvent(MObject* sender, AnyType arg1, AnyType arg2, AnyType arg3){

std::async(std::launch::async, [=](){(*mThreeHandle)(sender, arg1, arg2, arg3);

});

}


void MEventHandle::EmitEvent(MObject* sender, AnyType arg1, AnyType arg2, AnyType arg3, AnyType arg4){

std::async(std::launch::async, [=](){(*mFourHandle)(sender, arg1, arg2, arg3, arg4);

});

}


//=============================================


operator+= 将信号和槽函数连接在一起,EmitEvent是执行回调函数,从我们的实现中所有的回调函数都是异步操作,也就是立即返回的,这样一来不会因为回调函数过大而导致主程序的卡顿了。


这一讲就到这里吧,因为下面我们就开始讲怎么构建一个窗口出来,这个内容会比较多,所以可能还会被分成几讲来说。


//============================================

回复D查看目录,回复数字查看相应章节


原文始发于微信公众号(

C/C++的编程教室

):第122讲 DirectX11Frame(2)

|

第121讲 DirectX11Frame(1)

 

前面两讲我们大概的说了一下DirectX11编程的一些基础——向量和矩阵,但是还没有进入主题,那么从这一讲开始我们正式进入正题,当然,这个首先得说清楚些,不管怎么说C++我们已经说了两年了,相信大家的C++基础已经够扎实的,所以接下来要说的东西会很多,你们可以把他理解为一个基于DirectX11的一个Frame,所以这期间我们会从怎么设计这个Frame说起直到最后我们将这个Frame搭建起来。
 


 

既然这是一个基于DirectX11的Frame,那么相应的反射机制应该是要有的,因为对于DirectX编程都是基于顶点,所以如果不具有反射机制使用硬编码的方式设计应用程序太不灵活,其次外观的控制我们想要达到的效果是只要一个美工就能够搞定一个华丽的界面。所以无论如何反射是必不可少的了。
 


 

C++不像java或者C#具有天生反射机制,所以这个工作自然落在了我们的头上。不过这不是什么难事,只是首先听起来觉得不是那么简单,但是我们不去尝试又怎么知道真的不简单呢?


 

关于实现反射机制的方式就我而言就有两种,一种是通过无尽的if……else……来得到自己想要的类型,这样可行,在指定的范围内确实也是有效,甚至可以有更好的容错性,但是不管从什么角度出发他都不是一种明智的选择,因为这种扩展性实在太糟,而且局限性也很大,他必须要求所有的东西都继承自同一个基类,当然这种要求不过分,但是可以有更好的选择。


 

熟悉过MFC就应该知道MFC有几个宏,其中两个就是用来这种序列化的,所以大可参考MFC的实现机制完成我们的类反射机制。



 

/*******************************************************/


 

首先,我们定义一个函数指针,这个函数指针用于创建类的对象实例:


 

typedef void*(*MCreateInstanceFun)(void);


 


 

这是一类无参但返回指针的函数,所以我们只需要在我们的类中添加一个这种类型的函数,然后让该函数作为该类的静态函数以便消除this指针的影响即可,他的样子如下:
 

class A{
 

public:
 

 

 

 

 

static void* CreateInstance(){
 

 

 

 

 

 

return new A;


 

 

}
 

};


 

CreateInstance() 便是我们所需要的创建对象实例的函数,于是我们只需要将这个函数保存在某个地方,然后当我们需要该对象的时候直接调用该函数即可得到该对象的指针,当然,这里有一个问题存在,当new失败的时候会抛出异常std::bad_alloc异常,所以这里得注意些,但是这种情况通常是不会发生的。


 

那么,问题来了,我们把该函数放在什么地方呢?回想一下有什么东西是在程序进入main函数之前执行的呢?全局变量或者是全局静态变量,对于一个类来说,他的静态数据或者方法总是存在的,所以,我们大可将这些信息扔进类的静态对象里面保存起来。嗯,说是保存倒不如说是转存。看下面,下面我们来设计一个工厂类。
 

//—————————————————–
 

// 设计一个反射工厂类型
 

//—————————————————–
 

class MClassFactory{
 

protected:
 

 

 

 

 

MClassFactory(){ }
 

 

 

 

 

virtual ~MClassFactory(){ }
 

public:
 

 

void* GetInstanceByName(const MDx11String&

name);


 

 

void RegisterClass(const MDx11String&

name, MCreateInstanceFun __class);


 

 

static
void Destroy(MClassFactory* p);


 

 

 

 

 

static std::shared_ptr<MClassFactory>Instance();


 

private:
 

 

 

 

 

std::unordered_map<std::string,MCreateInstanceFun> 

 

mClassMap;


 

 

 

 

 

std::vector<std::string> 

mRegisterClassName;


 

};



 

这个反射工厂类是彻彻底底的单例类型,我们只能通过Instance构造函数构造出他的对象,对了,这里有智能指针的高级用法,可以参考一下。


 

mClassMap ,上面我们说转存其实就是将类的工厂函数转存到这里啦,他是以类名为key,工厂函数为值,所以当将类名作为字符串就能够创建出一个相应的对象了,如同:


 

A* ptr = (A*)MClassFactory::Instance()->GetInstanceByName("

A"

);


 

嗯,此时,他的威力还没有体现出来。现在我们假设我们要些一个Frame级别的东西,那么我们是不是应该让所有的东西都继承自一个共同的基类呢?这是必须的,如果要是没有打算这么做那么这种设计方式是值得推敲的。


 

嗯,现在我们可以来看看这个反射工厂是如何实现的,其实大家想必应该是清楚的了,他的实现相当的简单:


 

//———————————————
 

// 反射工厂类的实现
 

//———————————————
 

void* MClassFactory::GetInstanceByName(const MDx11String &

name)
 

{
 

 

 

 

 

if(name.empty())
 

 

 

 

 

 

 

 

 

return nullptr;


 

 

 

 

 

auto iter = mClassMap.find(name);


 

 

 

 

 

if(iter == mClassMap.end())
 

 

 

 

 

 

 

 

 

return nullptr;


 

 

 

 

 

return iter->second();


 

}


 

void MClassFactory::RegisterClass(const MDx11String &

name, MCreateInstanceFun __class)
 

{
 

 

 

 

 

if(name.empty())
 

 

 

 

 

 

 

 

 

return;


 

 

 

 

 

mClassMap[name] = __class;


 

}


 

void MClassFactory::Destroy(MClassFactory* p){
 

 

if (p){
 

 

 

p->~MClassFactory();


 

 

}
 

}


 

std::shared_ptr<MClassFactory>MClassFactory::Instance(){
 

 

 

 

 

static std::shared_ptr<MClassFactory>__data(new MClassFactory,Destroy);


 

 

 

 

 

return __data;


 

}


 

上面的Instance函数所使用的就是智能指针的高级用法,因为这个反射工厂类作为一个彻彻底底的单例类型,所以我们需要有一个定制的删除器,只有只能才能够访问得到该类的析构函数。


 

现在回头看看,机制我们已经说了,就是这么干的,现在的问题是如何将我们自己的类中的工厂信息转存到这里来,而且还有保证这个反射工厂类的对象在我们所需要的时候他一定是已经存在了的。当然可以大张旗鼓的干,但是这不是好点子,而且就目前我们设计的反射工厂这个信息来看似乎也不怎么满足,当然修改一下也是可以的了,但是这真不是什么好办法。


 

我们可以在创建一个类,而这个类只实用于动态创建反射工厂类,这已经足够了。


 

//—————————————————-
 

// 设计一个反射动态生成类
 

//—————————————————-
 

class MClassDynamic{
 

public:
 

 

MClassDynamic(const MDx11String&

name, MCreateInstanceFun __class);


 

};


 


 

可以说这是一个空类,所以他不会占多少空间,就一个字节,而他就只是为了保证我们的反射工厂类的实例永远存在。


 

MClassDynamic::MClassDynamic(const MDx11String &

name, MCreateInstanceFun __class)
 

{
 

 

 

 

 

MClassFactory::Instance()->RegisterClass(name,__class);


 

}


 

类的工厂信息的转存就是在这里完成的。


 

好吧,现在一切都有了,那么接下来我们就使用最简单的方法来将他们用在我们以后的类中,那就是一组宏:


//—————————————————-
// 宣告Class信息
// 在class的声明中使用
//—————————————————-
#define DECLARE_CLASS(className)
 

 

 

private:
 

 

 

std::string className##Name{#className } ;


 

 

 

static 

MClassDynamic* pclassName;


 

 

 

public:
 

 

 

static void* CreateInstance(){
 

 

 

return new className;


 

 

 

}
 

 

 

virtual const std::string&

ClassName() const{
 

 

 

 

 

 

 

return className##Name;


 

 

 

}
 

 

 

virtual const std::string&

ClassName(){
 

 

 

 

 

 

 

return className##Name;


 

 

 

}


//—————————————————
// 针对 DECLARE_CLASS 的实现
//—————————————————
#define IMPLEMENT_CLASS(className)
 

 

 

MClassDynamic* className::pclassName =
 

 

 

new MClassDynamic(#className,
 

 

 

reinterpret_cast<MCreateInstanceFun>(&

className::CreateInstance));


 

现在我们就可以在我们的类中如下使用啦:


 

//====================================
 

// 头文件中的声明
 

//====================================
 

class A{
 

 

DECLARE_CLASS(A)
 

public:
 

 

A();


 

 

…..
 

};


 

//=================================
 

// cpp 中的实现
 

//=================================
 

IMPLEMENT_CLASS(A)
 

A::A(){
 

 


 

}


 

…….


 

我们就可以使用字符串创建对象了。


 

这个Frame的内容会很多,这一讲我们就暂时完成反射类,下一讲我们将这个反射类放进MObject类进行管理,MObject类是我们这个Frame的基类。


 

=======================================
 

回复D查看目录,回复数字查看相应章节。 


原文始发于微信公众号(

C/C++的编程教室

):第121讲 DirectX11Frame(1)

|

第120讲 矩阵

 

矩阵在三维变换中扮演着重要角色。比如三维物体的旋转,又或者是平移缩放等,这些操作都是有矩阵来决定的。
 

上一讲里面我们说了向量,所以这一讲我们来简单的说说矩阵,关于矩阵的定义以及他的一些属性这里就不做细说,我们主要说他和我们接下来的三维编程相关的一些东西,以便大家在接触到三维图形的时候心里有数。


 

世界变换矩阵,在绘制物体的通常都是按照自己的坐标系进行绘制,那么绘制完成后想要在频幕上显示出来这时需要使用同一的坐标系,而这个坐标系就是世界坐标系。而从物体从自己的坐标系中变换到世界坐标系中的变换矩阵就是世界变换矩阵。
 

那么世界变换矩阵包括旋转矩阵,平移矩阵,缩放矩阵。
 


 

为什么矩阵可以用来旋转平移缩放物体呢?好吧,看下面:
 

假设点P(x,y,z,1):


 

矩阵:M =
 

 

{ m00,m01,m02,m03,
 

 

 

m10,m11,m12,m14,
 

 

 

m20,m21,m22,m23,
 

 

 

m30,m31,m32,m33 }
 

如果M的m00,m11,m22,m33都为1,而其他的都是0的话那么M被称为单位矩阵。


 

那么,点P经矩阵M变换后他的位置在哪里呢?


 

向量和矩阵相乘大家一定都清楚了,那么我们现在来描述一下他们的物理意义。还记得上一讲里面我们所说的向量和向量相加的物理意义吗?其实这里我们可以看成是几个向量的和,所以P点的物理位置被改变了。那么向量和矩阵相乘怎么能够看成是几个向量的和呢?好吧,这里解释一下,对于矩阵M来说,其实我们可以看成是由四个向量组成的,其中:
 

V1 ={mm00,m01,m02,m03 };


 

V2 ={m10,m11,m12,m13 };


 

V3 ={m20,m21,m22,m23 }'
 

V4 ={m30,m31,m32,m33 };


 

而这四个向量分别乘以一个不同的常数,然后相加就得到了变换的结果,那么这四个常数哪里来的呢?当时就是点P的四个分量啦。所以,我们可以这么来理解:


 

P*M = x*V1 + y*V2 + z*V3 + 1*V4;


 

我们可以推导验证一下:


 

P*M ={x*m00 + y*m10 + z*m20 + 1*m30,x*m01 + y*m11 + z*m21 + 1*m31,x*m02 + y*m12 + z*m22 + 1*m32,m03+m13+m23+m33 };


 

上面的结果和下面的结果对比一下是不是一样的呢?


 

那么现在结果是不是出来了呢?如果M是单位矩阵,那么点P的位置没有变动。


 

如果M不是单位矩阵就会改变点 P的位置,但是通常情况下我们的变换矩阵其实都是单位矩阵的基础上修改几个变值就得到我们所想要的矩阵了。所以从上面的变换式中我们不难看出,其实对角线的m00,m11,m22他们的作用就是缩放,而m30,m31,m32的作用就是平移。当变换矩阵中这些位置上的数值都不为0时那么该矩阵具有缩放也有平移。


 

关于旋转矩阵,这里不做推导,不过给出来大家有兴趣的就推导一下,没有兴趣的就了解一下:
 

关于X轴旋转的旋转绝阵据,angle表示角度。
 

 

 

{ 1, 0,  

 

 

 

0, 

 

0 }
 

Rx 

 

 

{ 0,cos(angle), 

 

sin(angle), 

0 }
 

 

 

{ 0, -sin(angle), 

cos(angle), 

0 }
 

 

 

{ 0, 0, 

 

 

 

 

 

 

 

 

 

 

 

, 1 }



 

关于Y轴的旋转矩阵:


 

 

 

{ cos(angle), 

0, 

-sin(angle),0 }
 

Ry 

 

 

{ 0, 

 

1, 

 

0, 

 

 

0 }
 

 

 

{ sin(angle), 

 

0, 

 

cos(angle), 

0 }
 

 

 

{ 0, 0, 

 

 

 

 

 

 

 

 

 

 

 

 

1 }


 

关于Z轴的旋转矩阵:


 

 

 

{ cos(angle), 

sin(angle), 

0, 0 }
 

Ry 

 

 

{ -sin(angle), cos(angle), 

 

0, 

0 }
 

 

 

{ 0, 

 

 

 

0, 

 

 

 

 

 

 

 

1, 

0 }
 

 

 

{ 0,  

 

0, 

 

 

 

 

 

0, 

1 }


 

这几个矩阵的推导过程并不复杂,有兴趣的话可以尝试。因为关于这些变换矩阵XNA数学库提供的有相关操作。
 


 

在XNA数学库中矩阵定义如下:
 

typedef struct _XMMATRIX{
 

 

union
 

 

{
 

 

 

XMVECTOR[4];

// 这里是四个向量__m128数组
 

 

 

struct{
 

 

 

 

FLOAT _11,_12,_13,_14;


 

 

 

 

FLOAT _21,_22,_23,_24;


 

 

 

 

FLOAT _31,_32,_33,_34;


 

 

 

 

FLOAT _41,_42,_43,_44;


 

 

 

}
 

 

 

FLOAT m[4][4];


 

 

}
 

} XMMATRIX


 

XNA的XMMATRIX针对C++重载了常用的操作符,比如,* ,*= 等等
 


 

XNA对矩阵的操作:


 

XMMATRIX XMMatrixIdentity();

//获取一个单位矩阵


 

XMVECTOR XMMatrixDeterminant(XMMATRIX);

// 求一个矩阵的行列式


 

XMMATRIX XMMatrixTranspose(XMMATRIX);

//求一个矩阵的转置矩阵


 

XMMATRIX XMMatrixInverse(XMVECTOR,XMMATRIX);

//求矩阵的逆矩阵
 


 

XMECTOR XMVector2Transform(XMVECTOR,XMMATRIX);

// 向量矩阵相乘


 

………………


 

拥有大量的操作函数,这里就不一一例举,这几个都是常用的。
 


 

那么XMMATRIX的优势是什么?当然是速度快了,但是也必然有他的劣势,那就是一不小心你的程序就崩溃了,当然这不是你的事,只能说是不小心,因为XMMATRIX的运行使用了硬件加速所以对内存对齐有需求,而从我们看到的__m128类型就应该不难发现,这是需要16位对齐的,然后在X86系统上堆默认是按照8为对齐,所以如果你打算将XMMATRIX作为类的数据成员时那就得小心小心再小心些。XMMATRIX通常不要将他所谓数据成员储存,而是作为局部变量或者全局变量来使用,那么我们该使用什么来储存矩阵信息呢?
 

答案是XMLOAT4X4,他不要求内存对齐,但是可以很方便的和XMMATRIX相互转换,也就是说当我们需要矩阵相乘的时候将XMFLOAT4X4转换为XMMATRIX再使用XMMatrixMultiply对矩阵进行相乘,然后再转换到XMFLOAT4X4对象中,当然不用担心这些转换会带来效率的损失,这些些函数库都是XNA库 提供的,速度相当快。


 

那么如果我们一定要在数据成员中使用XMMATRIX怎么办呢?第一是使用_aligned_malloc(sizeof(T), 16);

进行分配空间然后再在该空间上创建包含有XMMATRIX的对象。第二重新改写new操作,然后按照16位字节对齐分配内存,当然这两种方式其实都一样的啦,就是在分配内存的时候一定要保证是16字节对齐的。


 

关于XMFLOAT4X4和XMMATRIX的转换函数如下:


 

XMStoreFloat4x4(XMFLOAT4X4&

dest,XMMATRIX source);

// 将矩阵数据储存进XMFLOAT4X4的数据中
 

XMMATRIX XMLoadFloat4x4(XMFLOAT4X4 data);

// 将XMFLOAT4X4数据加载进XMMATRIX中


 

其他的3X3,3X4,4X3等等类型都是一样的操作。


 

ok,那么关于矩阵我们就简单的说了这些,接下来我们就准备进去DirectX11吧,不过最近在外面出差,真没时间更新,实在抱歉。


//==================================
 

 

回复D查看目录,回复数字查看相应章节

原文始发于微信公众号(

C/C++的编程教室

):第120讲 矩阵

|

第119讲 新的征程(向量)

晃悠晃悠的我们的课程已经说了两年多了,犹记当初不知怎么表达而说得稀里糊涂到后来说到哪里我自己都搞不清状况,想来也是好笑,大家也就当我姑妄言之且姑妄信之吧,好吧,既然大家都比较对DirectX感兴趣,那么今年我们就来简单的熟悉3D引擎DirectX11吧。

本来说好接下来要说正则表达式的,但是大家对DirectX的呼声这么高那就好吧,我们暂且不去说C++的事,毕竟光说那些稀奇古怪的语法不但把大家都搞晕了而我自己有时候回头去看自己曾经写过的东西我都怀疑这真是我写的吗?哎,真尼玛高深啊,说笑了,好吧,我们开始进入正题。

向量,今天我们要说的就是向量,说到向量,其实大家都已经比较清楚的了,不过可能时间久了我们就生疏了,所以这一讲我们就来重温一下向量,因为向量在3D绘图中扮演着很重要的角色。

在三维空间中我们通常表示一个向量为:P(x,y,z),但是在3D绘图中我们通常用四维来表示一个向量为什么呢?好吧,等到下一讲我们说矩阵的时候就明白了。

P1(x1,y1,z1),P2(x2,y2,z2);

P1 + P2 = (x1 + x2,y1 + y2,z1 + z2);

那么两个向量加减的物理意义是什么?其实很简单,就是变换,比如物体的移动等等。

a.P1 = (a.x1,a.y1,a.z1)

向量和常数的乘除法表示的物体的缩放。

P1.P2 = x1.x2 + y1.y2 + z1.z2;

两个向量的点乘的意义是什么呢?

因为点乘还有一种写法:

P1.P2 = ||P1||.||P2||.cos(angle);

所以这里可以计算出两向量的夹角。

P1 X P2 = (y1.z2 – z1.y2,x1.z2 – z1.x2,z1.y2 – y1.x2)

两个向量的叉乘得到的和P1和P2正交的正交向量。


刚才我们说在DirectX中都是用四维来表示顶点或者向量,因为在计算机图形学中使用的都是齐次坐标,而其次坐标就是在原本的维度上再加一维来表示,所以原本的三维顶点就变成了四维,在DirectX中,在三维坐标系统顶点P(x,y,z)的齐次坐标为(x,y,z,1),而向量的第四维为0.当然顶点的第四维不一定是1,只要是非0都可以表示,因为这是有用处的,而通常我们也可以通过第四维来判断一个顶点倒是三维坐标系统的点呢还是表示一个向量呢?如果第四维是0那么就是向量,如果非零表示的就是点。


在DirectX11中,我们通常使用XNA数学库计算向量和矩阵,XNA库是DirectX11 SDK的一部分,在DirectX11之前所使用的数学库是D3DXMath库,不过大家要是对这个库熟悉的话DirectX11依然支持这个库的,我们使用XNA数学库只需要包含xnamath.h头文件即可,这个库的最基本类型是XMVECTOR ,这是一个typedef,他真正的原型是__m128,而这个类型是针对一个可映射至SIMD寄存器的变量定义,这个定义有些复杂,这里不多说。我们只需要知道怎么使用XNA数学库就好了。

XMVECTOR和普通类型一样,他重载了大量的数学常用操作符,比如+-*/等等,他是实现XNA库的基础,那么接下来我们看看XNA库的向量操作。


#include <D3d11/D3D11.h>

#include <D3d11/D3DX11.h>

#include <D3d11/DxErr.h>

#include <D3d11/d3dx11effect.h>

#include <D3d11/D3Dcompiler.h>

#include <D3d11/xnamath.h>

std::ostream&

operator<<(std::ostream&

os, XMVECTOR u){

os <<"

("

<<XMVectorGetX(u) <<"

,"

<<XMVectorGetY(u) <<"

,"

<<XMVectorGetZ(u) <<"

,"

<<XMVectorGetW(u) <<"

)"

<<std::endl;


return os;

}


int main(){

// 创建两个向量

XMVECTOR u = XMVectorSet(1.f, 2.f, 3.f, 1.f);

XMVECTOR v = XMVectorSet(1.f, 1.f, 1.f, 1.f);


XMVECTOR a, b, c, d, e, f, g, h, i;


// 向量加法

a = u + v;


// 向量减法

b = u – v;


// 向量乘以常数

c = 10 * u;


// 向量点乘

d = XMVector3Dot(u, v);


// 四维点乘

e = XMVector4Dot(u, v);


// 叉乘

f = XMVector3Cross(u, v);


// 向量规范化

g = XMVector4Normalize(u);


h = XMVector3Length(u);


i = XMVector4Length(u);


std::cout <<"

u + v = "

<<a <<std::endl;

std::cout <<"

u – v = "

<<b <<std::endl;

std::cout <<"

10*u  

= "

<<c <<std::endl;

std::cout <<"

u.v  

= "

<<d <<std::endl;

std::cout <<"

u.v  

= "

<<e <<std::endl;

std::cout <<"

uXv  

= "

<<
f <<std::endl;

std::cout <<"

Normalize of u  

= "

<<g <<std::endl;

std::cout <<"

Length of u  

= "

<<h <<std::endl;

std::cout <<"

Length of u  

= "

<<i <<std::endl;


return 0;

}


向量的基本操作掌握这些就差不多了。下一讲我们来说矩阵。

第119讲   新的征程(向量)

//=================================

回复D查看目录,回复数字查看相应章节


原文始发于微信公众号(

C/C++的编程教室

):第119讲 新的征程(向量)

|

第118讲 boost::algorithm::string之其他

字符串算法我们说来也好几讲的内容来,剩下的已经不多又或者是我们没有必要再一一例举,那么这一讲我们就来结束关于boost::algorithm::string剩下的一些内容。


判断一个字符属于什么类型这些小工具boost::algorithm::string自然也是有提供来的,比如:

//================================================

is_space : //字符是空格或者是制表符

is_alnum : //字符是字母或者数字字符

is_alpha : //字符是否为字母

is_cntrl : //字符是否为控制字符

is_digit : //字符是否为十进制数字

is_xdigit : //字符是否为十六进制数字

is_graph : //字符是否为图形字符

is_lower : //字符是否为小写字符

is_upper : //字符是否为大写字母

is_print : //字符是否为可打印字符

is_any_of : //字符是否为参数中的任意字符

is_from_rang : //字符是否在指定范围之内


//===============================================


除来这些分类函数之外,自然也和我们说过的MString一样拥有start_with ,end_with,他们同样有i开头的版本,i开头的自然是不区分大小写的比较,同样也有修剪系列函数,他们的功能和我们说过的MString的修剪函数一样,而且名字也一样,trim_left,trim_right,trim,他们也有带有_copy后最的版本,但是boost里面的却还有_if的版本,他们需要一个函数对象或者能够作为判断式的函数指针,我们甚至可以认为非_if的版本是_if后缀版本的特殊情况。


除来上面这些还有一些有用的函数,比如contains,该函数判断一个字符串中是否包含指定字符串,equals判断两个字符串是否相等,lexicographical_compare函数用于根据字典顺序判断第一个字符串是否小于第二个字符串,当然这些函数也拥有i前缀的版本,这些函数可以说在实际开发过程中是相当有用的。


//================================================

if (boost::algorithm::is_digit()(48)){

std::cout <<"

48 is digit"

<<std::endl;

}

else{

std::cout <<"

48 isn't digit"

<<std::endl;

}


if (boost::algorithm::is_xdigit()(75)){

std::cout <<"

75 is xdigit"

<<std::endl;

}

else{

std::cout <<"

75 isn't xdigit"

<<std::endl;

}


if (boost::algorithm::is_cntrl()(13)){

std::cout <<"

13 is cntrl "

<<std::endl;

}

else{

std::cout <<"

13 isn't cntrl "

<<std::endl;

}


if (boost::algorithm::is_print()(13)){

std::cout <<"

13 is print "

<<std::endl;

}

else{

std::cout <<"

13 isn't print "

<<std::endl;

}


if (boost::algorithm::is_alnum()(70)){

std::cout <<"

70 is alnum "

<<std::endl;

}

else{

std::cout <<"

70 isn't alnum "

<<std::endl;

}


if (boost::algorithm::is_any_of("

abcdefg"

)(67)){

std::cout <<"

67 is is_any_of(acbdefg)"

<<std::endl;

}

else{

std::cout <<"

67 isn't is_any_of(acbdefg)"

<<std::endl;

}


if (boost::algorithm::is_from_range(67, 90)(70)){

std::cout <<"

70 is is_from_range(67,90)"

<<std::endl;

}

else{

std::cout <<"

70 isn't is_from_range(67,90)"

<<std::endl;

}


std::string str("

Hello World Hello Hello"

);

assert(boost::algorithm::istarts_with(str, "

hello"

));

//不区分大小写

assert(boost::algorithm::starts_with(str, "

Hello"

));

//区分大小写

assert(boost::algorithm::iends_with(str, "

hello"

));

//不区分大小写

assert(boost::algorithm::ends_with(str, "

Hello"

));

//区分大小写


str = "

Hello "

;

boost::algorithm::trim(str);

// 将前后空格去掉

assert(boost::algorithm::equals(str, "

Hello"

));

assert(boost::algorithm::iequals(str, "

hello"

));


//======================================

第118讲 boost::algorithm::string之其他

这里就列举这些,其他的大家按照上面的方法来试着怎么用吧。


关于字符串的操作的,只要熟练使用来这些就基本足够应付各种问题来,如果要遇到这些都无法满足的情况,那么就开大招——使用正则表达式吧。大家可以预先去了解一下正则表达式的东西,因为我们可能不在课堂上给大家介绍正则表达式怎么写,这里只会告诉大家怎么使用C++中的正则表达式。

//=============================================

回复D或者d查看目录,回复阿拉伯数字查看相应章节


原文始发于微信公众号(

C/C++的编程教室

):第118讲 boost::algorithm::string之其他

|