2005年11月29日

除了7.2小节中的绘图函数,块传送函数也是重要的图形操作函数。块传送指把源位置中的数据块按照指定的方式传送到目的位置中。把内存中的位图复制到窗口客户区以及在不同的DC中复制图形数据都要用到块传送操作,块传送完成的工作就相当于图形之间的拷贝工作。块传送函数有PatBltBitBltMaskBltPlgBltTransparentBltStretchBlt等。

7.4.1  块传送方式

7.2.4小节中介绍的绘图函数的ROP操作类似,块传送函数也是可以用ROP码来定义的传送方式,但块传送函数的ROP码定义不同于7.2.4小节中的ROP码,因为这里涉及的操作对象更多。

块传送的ROP码是一个32位的整数,对应的操作涉及3种原始数据:源像素、目标像素和画刷,块传送操作的结果是目标像素的数据被3种原始数据的计算结果替换掉。

并不是每一种ROP码都要用到全部3种原始数据,有的甚至连1种也用不到,例如全黑或者全白的ROP码。块传送函数使用的ROP码总共有256种,它们是3种原始数据进行不同位操作(取反、与、或和异或)的组合,但有些ROP码对应的操作结果实在是太难想像了,比如ROP0e20746对应的操作是((目标像素 xor 画刷) and 源像素) xor 目标像素),凭这个算式的确比较难以想像最后得到的位图是什么样子的!在实际使用中很多算法组合也并不是那么有用,所以Windows只对15种最常用的ROP码定义了预定义的助记代码,

如表7.6所示,对于这些ROP码,在程序中可以直接使用助记码,对于表中没有列出的ROP码,可以直接使用16进制数值。

7.6  块传送函数中使用的ROP

ROP

16进制数值

新像素点算法

BLACKNESS

00000042h

全部为0

DSTINVERT

00550009h

not 目标像素

MERGECOPY

00c000cah

画刷 and 源像素

MERGEPAINT

00bb0226h

not 源像素)or 目标像素

NOTSRCCOPY

00330008h

not 源像素

NOTSRCERASE

001100a6h

not(源像素 or 目标像素)

PATCOPY

00f00021h

画刷

PATINVERT

005a0049h

画刷 xor 目标像素

PATPAINT

00fb0a09h

画刷 or (not 源像素)or 目标像素

SRCAND

008800c6h

源像素 and 目标像素

SRCCOPY

00cc0020h

源像素

SRCERASE

00440328h

源像素 andnot 目标像素)

SRCINVERT

00660046h

源像素 xor 目标像素

SRCPAINT

00ee0086h

源像素 or 目标像素

WHITENESS

00ff0062h

全部为1

7.4.2  块传送函数

1. PatBlt函数

PatBlt函数完成的是“图案块传送”的功能,即“Pattern Block Transfer”。使用的方法是:

invoke  PatBlthDCxDestyDestdwWidthdwHeightdwROP

这个函数将当前画刷的图案拷贝到hDC中以(xDestyDest)为左上角坐标,dwWidth为宽度,dwHeigth为高度的区域中,传送的方式由dwROP中的ROP码指定。PatBlt函数的功能和矩形填充函数FillRectInvertRect等是很像的,但它包含了它们的全部功能,如ROP码指定DSTINVERT,那么PatBlt的功能就相当于InvertRect函数;ROP码指定为PATCOPY的时候,PatBlt的功能相当于FillRect函数。

BmpClock.asm_CreateBackGround子程序中,当建立背景图片的时候,就是用PATCOPY方式的PatBlt函数用资源中的背景图片填充整个时钟背景的。

在所有的ROP码中,可以用在PatBlt函数中的只有一部分,所有算法中包含源像素的ROP码在PatBlt函数中都不能使用,因为PatBlt函数只涉及当前画刷和目标像素,并没有一个“源像素”,所以对于这个函数来说,表7.6中的ROP码中只有BLACKNESSWHITENESSDSTINVERTPATINVERTPATCOPY是可用的。

2. BitBlt函数

PatBlt函数能完成的工作BitBlt函数都能完成,BitBlt是“数据块传送”的意思,即“Bit Block Transfer”。BitBlt函数的用法是:

invoke    BitBlthDcDestxDestyDestdwWidthdwHeigt\

          hDcSrcxSrcySrcdwROP

这个函数将源hDcSrc中以(xSrcySrc)为左上角的一个矩形区域传送到目标hDcDest中以(xDestyDest)为左上角的地方去,矩形的宽度为dwWidth,高度为dwHeight,当然目标DC中的最后结果是由dwROP中的ROP码定义的源、目标和画刷三者数据的组合。

灵活使用ROP码可以实现很多的功能,比如在一个背景图片上叠加一个非矩形的位图,游戏程序中人物在背景上面的移动就是这样的一个例子。BmpClock程序中也实现了类似的功能——读者可以注意到,程序可以自由更换背景和边框,但是边框是中空的,它相当于以一个不规则的图形叠加在背景上面,图7.10示范了实现的方法。

分析一下BmpClock.asm中的_CreateBackGround子程序就可以发现,程序用到了资源中的Back1.bmpMask1.bmpCircle1.bmp3个图片(在图7.10中以ABC来表示),子程序将3个图片装入内存后,创建了3DC来存取它们,DC句柄分别放在@hBmpBack@hBmpMask@hBmpCircle中。

好了!现在的任务是将图片C中需要的部分(非黑色部分)透明叠放在以图片A形成的背景上,得到时钟背景图片D。继续做准备工作:为图片D建立一个未初始化位图和内存DCDC句柄存放在hDcBack中。


7.10  在背景上叠加不规则图形的方法

如图7.10中的步骤1所示,首先,程序用CreatePatternBrush建立以图片A为图案的画刷,用PatBlt函数以这个画刷填充整个图片:

    invoke  CreatePatternBrush,@hBmpBack

    push        eax

    invoke  SelectObject,hDcBack,eax

    invoke  PatBlt,hDcBack,0,0,CLOCK_SIZE,CLOCK_SIZE,PATCOPY

    pop     eax

    invoke  DeleteObject,eax

现在如果直接将图片C拷贝上去,虽然需要的部分是拷贝上去了,但是图片C中的黑色部分也会覆盖全部的背景像素,为了让黑色部分的背景像素保持不变,应该使用or操作,因为黑色的颜色值为0,任何数据和0进行or操作将保持不变,查看ROP码可以发现,SRCPAINT使用的是or操作,所以可以使用SRCPAINT操作码进行BitBlt操作。

但还有个问题:图片C中的非黑色部分如果也用or操作绘画到背景上,那么经过和背景像素的or操作后就不是原来的颜色值了,为了让这部分不变,解决的办法是预先将背景中对应的部分先绘制成黑色,这样对应图片C中的黑色部分将保持背景颜色,而非黑色部分将使用图片C中的像素。遮掩图片B就是这样用的,它是一个黑白两色的图片,黑色部分是图片C中要在背景上“镂空”的部分,在步骤2中,程序使用下列语句将图片BSRCAND操作码绘制到背景上:

invoke  BitBlt,hDcBack,0,0,CLOCK_SIZE,CLOCK_SIZE,@hDcMask,0,0,SRCAND

查表7.6可以发现,SRCAND进行源像素和目标像素的and操作,任何数和1进行and将保持不变,和0进行and将变成0,这样背景中对应图片B中的白色部分将保持不变,对应图片B中的黑色部分将被“镂空”。

接下来就是最后的步骤3了:

invoke  BitBlt,hDcBack,0,0,CLOCK_SIZE,CLOCK_SIZE,@hDcCircle,0,0,SRCPAINT

程序用SRCPAINT操作码将图片C和已经镂空的背景进行or操作,得到的结果就是将由遮掩图片B指定的图片C中的不规则区域画到了背景上面。

为了简化起见,BmpClock程序中的遮掩图片是预先设计好的,实际使用中也可以通过扫描源位图中的像素位,找出背景颜色并动态生成一个遮掩位图,虽然这样可能对速度有一些影响,但灵活性要高得多。

在游戏程序中,将一个不规则的图形如人或物体等图形叠加到背景上面使用的就是这样的技术。

3. MaskBlt函数

MaskBlt函数允许在一个图片中对不同的部分以不同的ROP码进行操作,它的语法是:

    invoke  MaskBlthDcDestxDestyDestdwWidthdwHeigt\

            hDcSrcxSrcySrchMaskBmpxMaskyMaskdwROP

它和BitBlt的不同之处是多了一个遮掩图片句柄hMaskBmp(注意:是位图句柄而不是DC句柄)以及hMaskBmp的开始坐标。

MaskBlt函数同样是将hDcSrc以(xSrcySrc)为左上角的矩形区域以指定ROP码操作方式传送到hDcDest中以(xDestyDest)为左上角的矩形区域中,矩形的宽度和高度由dwWidthdwHeight指定,函数的特殊之处是可以指定两个ROP码,传送时使用哪个ROP码要参考遮掩图片,参考的位置从遮掩图片的(xMaskyMask)坐标开始。

hMaskBmp指定了一幅黑白位图,如果位图中对应位置的像素为黑(即为0),那么使用背景ROP码,如果对应位置的像素为白(即为1),那么使用前景ROP码。注意:遮掩位图一定要是黑白两色的,如果使用其他颜色深度的位图,那么函数调用将会失败。

读者一定有个问题:参数中只有一个dwROP参数,如何指定两个ROP码?这正是这个函数比较费解的地方,实际上dwROP参数是两个ROP码的组合,组合的算法是:

((背景ROP shl 8& 0ff000000hor 前景ROP

灵活使用这个函数可以带来很多的方便,比如在_CreateBackGround子程序中,以“;$$$$$”为注释之间的8句代码完全可以改成下面的4句:

    invoke  CreatePatternBrush,@hBmpBack

    invoke  SelectObject,hDcBack,eax

    invoke  DeleteObject,eax

    invoke  MaskBlt,hDcBack,0,0,CLOCK_SIZE,CLOCK_SIZE,\

        @hDcCircle,0,0,@hBmpMask,0,0,\

        ((SRCCOPY shl 8) and 0ff000000h) or PATCOPY

相比之下少了一个PatBlt和一个BitBlt调用。因为程序的要求是遮掩图片中黑色的部分使用边框图片,而白色部分使用背景,所以将背景ROP设置为SRCCOPY就可以将需要的边框部分复制过去,而前景ROP使用PATCOPY就免除了使用PatBlt填充背景的步骤。当然,使用前需要把Mask1.bmpMask2.bmp重新保存成黑白色,因为现在这两个位图文件是256色格式的。

如果hMaskBmp不指定(参数写为NULL),那么MasmBlt函数就相当于BitBlt函数,只不过ROP是使用dwROP中指定的前景ROP码。

 

4. PlgBlt函数

PlgBlt实现平行四边形旋转传送操作(Parallelogram Block Transfer),它复制一幅位图,同时将其转换成一个平行四边形,所以利用它可对位图进行旋转处理。PlgBlt函数的使用语法是:

invoke  PlgBlthdcDestlpPoint\

            hdcSrcxSrcySrcdwWidthdwHeight\

            hBmpMaskxMaskyMask

这个函数和ROP码无关,但它同样指定了一个单色的遮掩位图,遮掩位图中为1的像素对应的位置会被传送,为0的像素不被传送。(xSrcySrc)指定了源DC中需要传送的矩形的左上角,dwWidthdwHeight指定宽度和高度,这个矩形将被旋转后传送到目的DC中去,旋转后的平行四边形位置由lpPoint指定的POINT结构阵列指出。

lpPoint是一个指针,指向含有3POINT结构的内存中(这种使用POINT结构数组的方法在PolyLine中已经使用过),其中第一个点对应于一个平行四边形的左上角位置,第二个点代表右上角位置,第三个点代表右下角的位置,不需要第四个点是因为它可以从上面3个点的坐标推导出来。

5. StretchBlt函数

StretchBlt函数实现像素的缩放功能,它的语法是:

invoke      StretchBlt,hDcDest,xDest,yDest,dwWidthDest,dwHeightDest,\

            hDcSrc,xSrc,ySrc,dwWidthSrc,dwHeightSrc,dwROP

这个函数将源hDcSrc中以(xSrcySrc)为左上角,dwWidthSrcdwHeightSrc为宽度和高度的矩形以dwROP指定的方式传送到目标hDcDest中去,目标位置是(xDestyDest),新的矩形区域大小为dwWidthDestdwHeight,如果源DC中的矩形大小和目标DC中的不一样,函数会将像素数据自动缩放。

但是StretchBlt对像素的缩放办法仅仅是删除多余的像素(从大到小)或者重复像素(从小到大),并不像一些图形处理软件一样进行插值计算,所以缩放的效果并不好,笔者建议如果能够不用这个函数就不要去用它,除非对图形的质量并没有要求。

6. TransparentBlt函数

还有一个函数可以方便地实现不规则区域的像素拷贝操作,那就是TransparentBlt函数,它的用法如下:

invoke  TransparentBlt,hDcDest,xDest,yDest,dwWidthDest,dwHeightDest,\

            hDcSrc,xSrc,ySrc,dwWidthSrc,dwHeightSrc,crTransparent

可以看出,它和StretchBlt函数的参数很像,也有dwWidthSrcdwHeightSrc参数,难道这个函数也可以缩放吗?答案是肯定的,它也可以进行像素缩放。TransparentBlt函数的最后一个参数指定了一个透明色,源DC指定的矩形区域中和这个颜色相同的像素不会被拷贝,所以,BmpClock程序中的下列两个语句:

invoke  BitBlt,hDcBack,0,0,CLOCK_SIZE,CLOCK_SIZE,@hDcMask,0,0,SRCAND

invoke  BitBlt,hDcBack,0,0,CLOCK_SIZE,CLOCK_SIZE,\

            @hDcCircle,0,0,SRCPAINT

完全可以用下面这一句来代替:

invoke  TransparentBlt,hDcBack,0,0,CLOCK_SIZE,CLOCK_SIZE,\

            @hDcCircle,0,0,CLOCK_SIZE,CLOCK_SIZE,0

这样甚至连遮掩图片Back1.bmpBack2.bmp都可以省掉了。

当然,这个函数的传送方式使用拷贝方式,如果需要用到ROP码,那么只有使用其他函数了。由此可见,各种块传送函数都有它们的优缺点,在实际应用中,最好的办法就是根据实际情况灵活使用。

TransparentBlt函数并不包含在Gdi32.dll中,而是在Msimg32.dll中,所以使用时注意在源程序头部加上下面的包含语句:

include     Msimg32.inc

includelib  Msimg32.lib

我们在进行程序的界面设计时,常常希望将位图的关键部分,也既是图像的前景显示在界面上,而将位图的背景隐藏起来,将位图与界面很自然的融合在一起,本实例介绍了透明位图的制作知识,并将透明位图在一个对话框中显示了出来,界面效果如图一所示:



图一、对话框界面上透明显示位图


  一、 实现方法

  绘制"透明"位图是指绘制某一位图中除指定颜色外的其余部分,我们称这种颜色为"透明色"。通过将位图的背景色指定为"透明色",在绘制时,不绘制这部分背景,而仅绘制图像,这样就可以将位图中图像透明地绘制到窗口上。

  绘制"透明"位图的关键是创建一个"掩码"位图(mask bitmap),"掩码"位图是一个单色位图,它是位图中图像的一个单色剪影。在Windows编程中,绘图都要用到设备描述表,我们需创建两个内存设备描述表:位图设备描述表(image DC)和"掩码"位图设备描述表(mask DC)。位图设备描述表用来装入位图,而"掩码"位图设备描述表用来装入"掩码"位图。在"掩码"位图设备描述表中制作"掩码"位图的方式是:先创建一个单色的Bitmap,装入mask DC,然后,以"SRCCOPY"的方式将装有位图的位图设备描述表绘制(BitBlt)到mask DC上。这样,mask DC的显示平面中的位图即是"掩码"位图。

  一般情况下,绘制"透明"位图的实际操作步骤如下:

  1、 位图设备描述表以"SRCINVERT"的方式绘制(BitBlt)到显示设备描述表上;

  2、 "掩码"位图设备描述表以"SRCAND"的方式绘制(BitBlt)到显示设备描述表上;

  3、 再将位图设备描述表以"SRCINVERT"的方式绘制(BitBlt)到显示设备描述表上。这样除"透明色"外的其余位图部分(图像部分)就被绘制到窗口上了。

  上述操作中需要用到的显示函数BitBlt的原型和说明如下:


BOOL BitBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, DWORD dwRop );


  函数的参数说明如下:int x表示贴到目的地的左上角X坐标;int y表示/贴到目的地的左上角Y坐标;int nWidth表示贴到目的地的区域宽度;int nHeight表示贴到目的地的区域高度;CDC* pSrcDC表示存储源位图的设备描述表;int xSrc表示源位图的左上角X坐标;int ySrc表示源位图的左上角Y坐标;DWORD dwRop为柵格运算标志,一般我们选择SRCCOPY,直接拷贝源位图到目标。还可以让源位图和目标位图进行XOR,AND,OR等等的操作。大家可以查看MSDN。

  二、 编程步骤

  1、 启动Visual C++6.0,生成一个基于对话框架的应用程序,讲程序命名为"TransPrarentImageTest";

  2、 添加位图资源,其ID为IDB_DRAGON,然后在对话框上添加一个IDC_STATIC控件,在其属性设置里选择显示该资源图像;

  3、 使用Class Wizard自定义类CtransparentImage,其基类选择Cstatic;

  4、 添加代码,编译运行程序。

  三、 程序代码


//////////////////////////////////////////////////////////
#ifndef __TRANSPARENTIMAGE_H_TRANSPARENTIMAGE_42A6E395_97E4_11D3_B6F0_005004024A9E
#define __TRANSPARENTIMAGE_H_TRANSPARENTIMAGE_42A6E395_97E4_11D3_B6F0_005004024A9E
#if _MSC_VER >= 1000
#pragma once
#endif
class CTransparentImage : public CStatic
{
 public:
  CTransparentImage() ;
  virtual ~CTransparentImage() ;
 protected:
  //{{
AFX_MSG( CTransparentImage )
    afx_msg void OnPaint() ;
  //}}AFX_MSG
  DECLARE_MESSAGE_MAP()
} ;
//{{AFX_INSERT_LOCATION}}
#endif

//////////////////////////////////////////////////////////////
#include "StdAfx.h"
#include "TransparentImage.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__ ;
#endif

CTransparentImage::CTransparentImage()
{}

CTransparentImage::~CTransparentImage()
{}

BEGIN_MESSAGE_MAP( CTransparentImage, CStatic )
 //{{AFX_MSG_MAP( CTransparentImage )
  ON_WM_PAINT()
 //}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CTransparentImage::OnPaint()
{
 HBITMAP l_hbmpBitmap = GetBitmap() ;
 if( l_hbmpBitmap == NULL )
 {
  Default() ;
  return ;
 }
 CPaintDC l_PaintDC( this ) ;
 // Prepare everything for drawing
 CRect l_rcClient ;
 GetClientRect( &l_rcClient ) ;
 CDC l_BufferDC ;
 l_BufferDC.CreateCompatibleDC( &l_PaintDC ) ;
 CBitmap l_BufferBitmap ;
 l_BufferBitmap.CreateCompatibleBitmap(&l_PaintDC,l_rcClient.Width(), l_rcClient.Height() ) ;
 CBitmap* l_pOldBufferBitmap = l_BufferDC.SelectObject( &l_BufferBitmap ) ;
 CDC l_MaskDC ;
 l_MaskDC.CreateCompatibleDC( &l_PaintDC ) ;
 CBitmap l_MaskBitmap ;
 l_MaskBitmap.CreateBitmap( l_rcClient.Width(), l_rcClient.Height(), 1, 1, NULL ) ;
 CBitmap* l_pOldMaskBitmap = l_MaskDC.SelectObject( &l_MaskBitmap ) ;
 #define SRCMASK 0×00220326
 // Fill with transparent
color
 l_BufferDC.FillSolidRect( &l_rcClient, RGB( 255, 0, 255 ) ) ;
 // Blit the bitmap to the buffer
 CDC l_MemoryDC ;
 l_MemoryDC.CreateCompatibleDC( &l_PaintDC ) ;
 CBitmap* l_pOldMemoryBitmap = l_MemoryDC.SelectObject( CBitmap::FromHandle( l_hbmpBitmap ) ) ;
 l_BufferDC.BitBlt( 0, 0, l_rcClient.Width(), l_rcClient.Height(), &l_MemoryDC,0, 0, SRCCOPY ) ;
 l_MemoryDC.SelectObject( l_pOldMemoryBitmap ) ;
 // Create the mask.
 COLORREF l_crOldBack = l_BufferDC.SetBkColor( RGB( 255, 0, 255 ) ) ;
 l_MaskDC.BitBlt( 0, 0, l_rcClient.Width(), l_rcClient.Height(), &l_BufferDC,0, 0, SRCCOPY ) ;
 l_BufferDC.SetBkColor( l_crOldBack ) ;
 // Draw the bitmap transparently now;
 if( ! l_PaintDC.MaskBlt( 0, 0, l_rcClient.Width(), l_rcClient.Height(),&l_BufferDC, 0, 0, l_MaskBitmap, 0, 0,ROP4_TRANSPARENTBLIT ) )
 {
  CDC l_CopyDC ;
  l_CopyDC.CreateCompatibleDC( &l_PaintDC ) ;
  CBitmap l_CopyBitmap ;
  l_CopyBitmap.CreateCompatibleBitmap( &l_PaintDC, l_rcClient.Width(),l_rcClient.Height() ) ;
  CBitmap* l_pOldCopyBitmap = l_CopyDC.SelectObject( &l_CopyBitmap ) ;
  l_CopyDC.BitBlt( 0, 0, l_rcClient.Width(), l_rcClient.Height(), &l_PaintDC,0, 0, SRCCOPY ) ;
  l_CopyDC.BitBlt( 0, 0, l_rcClient.Width(), l_rcClient.Height(), &l_MaskDC,0, 0, SRCAND ) ;
  l_BufferDC.BitBlt( 0, 0, l_rcClient.Width(), l_rcClient.Height(), &l_MaskDC,0, 0, SRCINVERT ) ;
  l_CopyDC.BitBlt( 0, 0, l_rcClient.Width(), l_rcClient.Height(), &l_BufferDC,0, 0, SRCPAINT ) ;
  l_PaintDC.BitBlt( 0, 0, l_rcClient.Width(), l_rcClient.Height(), &l_CopyDC,0, 0, SRCCOPY ) ;
  l_CopyDC.SelectObject( l_pOldCopyBitmap ) ;
 }
 // Clean up.
 l_MaskDC.SelectObject( l_pOldMaskBitmap ) ;
 l_BufferDC.SelectObject( l_pOldBufferBitmap ) ;
}


  四、 小结

  本实例介绍了如何在对话框中实现透明位图的显示,读者朋友可能感觉到文中介绍的方法和代码的实现过程仿佛不太一致,其实这些只是表面现象,读者朋友在吃透实例代码的实现过程后,就会发现两者之间并没有什么冲突。

http://www.51education.net/UploadFiles/200462018525592.mp3

500 miles

歌手:The brother four     
If you miss the train I’m on
You will know that I am gone
You can hear the whistle blow
a hundred miles
A hundred miles a hundred miles
A hundred miles a hundred miles
You can hear the whistle bow
a hundred miles
Lord I’m one lord I’m two
lord I’m three lord I’m four
Lord I’m five hundred miles
away home
Away home away home
away home away home
Lord I’m five hundred miles
away home
Not a shirt on my back
not a penny to my name
Lord I can’t go back home
this away
This away this away
this away this away
Lord I can’t go back home this away
If you miss the train I’m on
You will know that I am gone
You can hear the whistle blow
a hundred miles
http://bbs.kofei.com/music/中文流行/杜德伟/杜德伟-嫁给我.wma
 
歌曲:嫁给我  
歌手:杜德伟



  小星星亮晶晶闪在你的眼睛里
从此投入我的心呼风又唤雨
我愿意好愿意双手奉上我自己
单身夜里找到你再也不离去
爱是我爱是你爱是肯定句
oh~谁也不能阻挡我永远守护你
日出日落黑夜白昼时时刻刻拥在怀中
清清楚楚这感动分秒可以成永久
我望著你你看著我有句话我想对你说
今生今世跟著我做你幸福的理由
嫁给我

http://play8.tom.com/upload/6437.mp3

歌曲:一千年以后

 
歌手:林俊杰 专辑:编号89757



  心跳乱了节奏
梦也不自由
爱时的绝对承诺不说
沉到一千年以后
放任无奈淹没尘埃
我在废墟之中守着你走来
我的泪光承载不了
所有一切你要的爱
因为在一千年以后
世界早已没有我
无法深情挽着你的手
浅吻着你额头
别等到一千年以后
所有人都遗忘了我
那时红色黄昏的沙漠
能有谁
解开缠绕千年的寂寞

http://www.2400dpi.com/netU/files/forum.musichk.org—9%20%20%20.mp3

歌曲:黑白照  
歌手:邓丽欣



  抱抱到了我进睡独自就像没伴随
背对镜去抹眼泪年月逝去
惯了说我太安静学习著独立过程
发觉了也太镇定未够感性

这近照已不再新面容共我相近
还傻著要给你苦等

@扮作开心天真知不多未够两岁你竟抛弃我
时日远去也需要负荷就算他每天振作静坐
没你关心紧张的当初习惯等某天失去宝座
唯独这合照看你清楚证实你曾是家中那个

笑笑说这个宿命自幸运便没法停
这次故意更冷静没有反应
看著你我等再等面容让我拉近
谁伴我等到天阴

repeat @

扮作开心天真知不多未够两岁你竟抛弃我
时日远去也需要负荷就算他每天振作静坐
没你关心紧张的当初习惯等某天失去宝座
唯独这合照看你清楚证实你曾递给温暖我

人生许多的不幸和悲哀多数都是源自于无知与疏忽,无知就是你该知道的不知道,疏忽就是你该注意而没有注意,这就非常非常不值得。疏忽所造成的伤害远远大过无知,这两个都必需要花些时间。有一个东西,是全世界没有象祖国大陆蔓延那么多的,而且你看看现在年轻人身体为什么越来越差,你看看他们肚子饿,在各大机场,甚至在贵宾室,还有在工地,我看到他们忙得没有时间吃饭。还有科学家,为什么身体越来越差,去年二月台湾发生了一件很大的新闻,很优秀的三个大学教授,很年轻的,都四十多岁,最年轻的才41岁,两个礼拜死了三个优秀的大学教授,你有空去看看。为什么他们身体这么差?你看他们平时很忙啊,口渴的时候没时间喝水,你知道他们喝什么吗?就是喝可乐汽水,你看现在小孩子是不是都这样,年轻人是不是都这样。肚子饿了,没有时间吃饭,吃什么?吃方便面。祖国大陆是全世界方便面销量最大的地区,而且年轻人吃方便面的数字比例,(对不起,那个统计数字我没有带过来),是我去年看到的统计数字,真是惊人啊!吃方便面量非常大。方便面是先用油去炸过,它是高油脂的食物,它是高热量的食物,尤其它是高磷的食物,它的磷之高几乎无以伦比,它是没有纤维的,是多盐的,是多味精调料的,完全符合癌症食品的要求。如果你家的孩子爱吃方便面,请你叫他去做一个实验,如果你养一只小老鼠,让它吃方便面,连续吃21天,它就死掉了,你看多毒。我知道我讲完你还是要吃的,因为有时候没有时间。那怎么办呢?所以我建议你,一个月最多吃一包,吃之前先吃五
斤的蔬菜,解毒。

我现在要讲的东西,就是你们每天都吃的,很可怕的东西,就是红肉。肉,尤其是红肉,在医学上已经证实,在世界上红肉消耗量最大的地区,就是得癌症比例最高的地区。什么叫红肉呢?红肉就是羊肉、牛肉、猪肉。我曾经在祖国大陆的医学院,请教了几位医学院的院长,还有几位医学专家,我说请你们告诉我,以前在困难时期,肉很少摄取的时候,尤其在配给的时候,那个时候有没有大肠癌?他们说根本听都没有听过。大肠癌一定都是喜欢吃高热量,高脂肪,高蛋白,零纤维,那就“恭喜”你,很 容易得大肠癌。得大肠癌很痛苦,死之前很折磨你,所以尽量不要再吃红肉。如果你非吃不可,请你吃白肉,白肉是比红肉好的。我就想到了,最近我们在临床上发现,很奇怪,为什么小朋友,八九岁,十岁的小孩子,小女生胸部发育很快,那么小男生呢,小鸡鸡长不大。你不相信你回去看你儿子的小鸡鸡,不过他不让看不要勉强,他会生气的。为什么会有这个现象呢?后来发现他们身上都有大量的女性荷尔蒙,你知道从哪里来的吗?他们都有一个共同的饮食习惯,喜欢吃鸡,尤其是炸鸡。以前一只鸡要养九个月到一年,现在不用了,美国最快的技术已经教全世界了,三个星期,就会养到以前九个月那么大。为什么?因为打针呐,生下来就打抗生素,让它不生病呀,打激素荷尔蒙,让它长得快呀。可是针打多了,鸡胃口不好,它不吃呀,那就长不大,不行呀,那就再打一针,叫开胃剂,那鸡就吃得很多,长得很快。问题是针打
多了打得鸡都神经错乱,所以鸡都乱抓乱咬,鸡毛满天飞呀,这样也不行,那不是整个鸡舍变成斗鸡场了吗,那就再打一针镇定剂。所以你们有没有感觉,现在的鸡都笨笨地,真的,我有时候会去养鸡场看那些鸡,我请它看着我,我看着它,真的,现在鸡好笨,为什么?因为针打太多了。我有一次在一个地方演讲,讲完以后,有一个长辈,他是有糖尿病、心脏病、高血压,他说该有的病都得了,现在只剩下没有得癌症,他说我养了十万只鸡,以前就是象你讲的这样打针,现在不用打针了,我说真的 吗,有良心啊,他说不,是来不及打,太多了,所以现在全部混在饲料里给它们吃了,这很可怕的。鸡蛋的胆固醇含量是所有食物中胆固醇含量最高的。我看到很多似是而非的观念,非常可怕的,以前美国蛋制品协会,鼓励大家每天吃一个蛋,后来心脏协会讲话了,一天吃一个蛋最后都是心脏病,所以我这两个月在美国看到,改了,他们做了一个好大的标牌,在高速公路边:一个礼拜吃四个蛋也可以啦。其实三个就已经会引起胆固醇问题了,我告诉你其实现在的蛋不只是胆固醇问题,更严重就是现在的鸡,母体的问题。这个鸡本身就有问题,因为吃饲料的鸡,以前要两三天才生一个蛋,现在是每天下蛋。你如果非吃鸡不可,请你自己养,好不好,它每天是吃虫的,每天在大地奔驰心情很愉快的,这样的鸡才好。不过也不要吃太多。其实现在吃鸡风险很大,尤其你在外面快餐店买到这些鸡,为什么他喜欢用油炸,因为它已经坏掉、臭掉、腐化掉,你完全吃不出来,还觉得好香噢,太可怕了。
那你说,鸡也不能吃了,那吃什么呢?鱼,鱼是我唯一不反对的,我没有讲赞成,为什么?因为鱼其实是非常好,如果你一个礼拜吃一次到两次的鱼,那么不但可以补充你足够的动物性蛋白质,而且更主要,现在在医学上证实,可以减少60%老年痴呆症,以及减少心脑血管疾病的发作。可是,现在的鱼,有一点不一样了,因为近海的鱼我们发现有很严重的重金属污染,尤其是汞,就是水银。我们发现常吃鱼的人,他们身体里面汞的含量非常高,汞就是伤肾,伤脑神经。在美国,环保已经是很严格的要求,可是工业家庭污水废水大量排入河川之后,现在美国发现河里的鱼抓上来竟然已经得癌症,而且是美国跟加拿大交界的尼亚加拉大瀑布那个地方。那你说远洋的鱼呢?远洋的鱼会好一些,没有这么严重的重金属污染,可是远洋的鱼有另外一个问题,从它捕捞到送到你口里至少死了六个月了。所以你非吃鱼不可,我建议你最好想办法找高山湖泊的鱼,河流上游的鱼,都是非常好的。所以你尽可能还是小心一点,因为我发现很多人喜欢吃海鲜,海鲜是比红肉要安全,可是有很多的海鲜现在都是养殖的,你特别注意,去年有一个很大的新闻,震惊全世界,各位都知道大闸蟹,让人非常非常想去吃,而且很贵很贵,可是你知道吗?去年十月二十九号,这个新闻爆发出来:广州大闸蟹喂抗生素催熟。养蟹人士自曝内幕,如果都依照正规程序养,要花两年时间,那我还赚啥!他们现在都是给它吃抗生素,让它快速地脱壳,而且更加生猛肥大,只需要五个月的时间。用抗生素和激素,来节省成本,让它快速成长。这些东西
你吃下去之后,你会发现现在的孩子,身体已经严重扭曲掉,整个变形,所以各位吃肉一定要谨慎小心。

我再讲一个,除了肉之外还有什么东西呢,是个大问题。我刚刚已经跟各位讲过,全世界心脑血管疾病总人数超过癌症的死亡人数。那么比例这么高的心脑血管疾病,你看患者,他们身体都有一个共同特色,就是得心脏病之前,心脏病发之前,胆固醇含量一定特别高,你体内的胆固醇超过正常值的1%,死于心脏病的可能性就会多2%-3%。高那么多,所以胆固醇含量越高的时候,身体里面得心脑血管疾病的可能性就会越高。各位你知道吗?我们可以看到,我常常会碰到这种情况,他说我心悸很厉害,胸闷,我就跟他讲,你回去肉就不要吃了,还有一样东西不要吃,两样就好,他真的很有效,三天而已,就感觉心脏很舒服。有一次我在天津学院讲学的时候,有一个老教授跟我讲,他说光常老弟,我随时会死,我说不会,你好人啊,你会活久一点,你要活久一点,他说我现在心脏每跳两下停一下。我一检查他吃的东西,都是让他不太会跳的东西。你知道是什么东西吗?就是胆固醇含量太高的食物,全世界胆固醇含量最高的食物是什么?蛋,鸡蛋!鸡蛋的胆固醇含量是所有食物中最高的。

我再讲一样,我讲这一样会得罪很多人。那么请你们原谅我,必须按照学者的良心与正直,来跟大家报告,听完各位做参考,也许不对,但你可以试试,不要做学术的辩论,因为学术有很多是错的,昨是今非的,对不对?很多医学的研究也是一样,但我跟你讲,你试试看,我讲的,有一样东西你不要吃,你身体整个机能会有很大改善。 去年我回到台湾的时候,我妹妹的小孩,四个月大,过敏得非常厉害,我就跟她讲:牛奶不要喝!第二天就好一半。我跟她讲,九岁的孩子,过敏很厉害,我在世界各地都看到,每天抓,痒啊,我说牛奶不要喝就好了,蛋不要吃,就好了,就这么简单。我前几天在香港演讲,三天,五六场演讲,有一个妈妈就一定要特别来感谢我,为什么?我两个月前在香港演讲的时候,她的孩子很严重的过敏,他的皮肤不到一公分好,整个人抓得痛得不得了,她说她只是不给他喝牛奶,不给他吃鸡蛋,限制他的肉,吃肉只吃鱼,其他的红肉不吃,就这样而已,好了!就这么简单。各位你知道为什么吗?我先讲蛋白质,蛋白质重要不重要,非常重要,头发、指甲、皮肤、细胞、器官统统都需要蛋白质,组织受损修复都需要蛋白质。蛋白质好不好,好!重要不重要,重要!蛋白质太重要太好了。可是,就像水一样,如果缺水,旱灾,对不对,可是每一天给你下大雨,连续下八十天,好不好?不好了。对不对?蛋白质就是这个问题,适量的蛋白质对身体是非常好的,过量的蛋白质会杀人。所有现代文明病,80%以上的罹患,都跟蛋白质摄取过量有关。你只要超过身体需要三倍以上,一定得癌症,
我这么讲,肯定,没有例外。很可怕,现在牛奶里的蛋白质是母奶的三倍,我们身体根本没有办法处理,而且它的蛋白质几乎都是酪蛋白,身体很难消化的,非常非常难消化,那就造成消化不良的问题。小孩子消化不良,年轻人消化不良,胃病,胃酸过多,胃痛,我说你酪蛋白不要吃就好了,就这么简单。
我跟你讲美国乳制品学会打了一个广告很大:牛奶,是最完整的食物。对不对?对!非常正确,但是他忘了告诉你,后面加注??对牛而言。尤其是现在的牛奶含有大量蛋白质,蛋白质在身体里代谢的时候会产生大量酸性物质,那酸性物质产生的时候,体质就会偏酸,体质一偏酸你就会容易生病,所以我们身体自然有一个机能,会从骨骼,会从身体里面溶出碱性物质去平衡这个酸性,让你不会那么快生病,还有机会反悔,改变你的生活方式。那么各位,我们身体里面什么物质碱性特别高啊?就是钙。
所以你越喝牛奶,体质越酸,钙又是储存在骨骼里面,所以骨头里面的钙就溶出越多,越喝牛奶,产生越多酸性物质,酸性物质越多,钙就越流失,钙质越流失,你越以为不够,再去喝牛奶补钙,越喝牛奶钙越流失。你为什么钙永远补不够?因为你开了一个更大的漏洞,除了甜食之外,就是牛奶,就是大量的蛋白质,这些蛋白质产生大量的酸性物质,使我们身体整个严重偏酸,那你蛋白质永远都不够的。根据英国布 里斯托尔大学研究报告,他们调查1972年-1974年之间所出生的婴儿,做随访的研究,这些婴儿现在已经都二十多岁了,结果发现,奶粉喂养的婴儿在成年之后,他们有两高,一个是身高很高,一个是血压很高。到中国的内地,比较封闭的地方,妇女所摄取的钙质,平均一天只有400-500毫克,距离800-1000毫克还有一段距离,可是她们没有骨质疏松症。你知道为什么吗?因为她们没有喝过牛奶呀。在妇女的哺乳期,怀孕期所需要的钙质更高,对不对?非洲有个地方叫邦图这个国家,妇女一生中平均要生十个小孩,用母乳喂至少十个月以上,可是她们平均钙摄取只有400毫克,没有人得骨质疏松症。你知道为什么吗?因为她们没有喝过牛奶呀。所以结论:想得骨质疏松症吗?请多喝牛奶。你说不对,牛奶里面有很多钙,牛奶已经经过巴氏杀菌,所以它里面的钙是无机钙,而这个无机钙是造成肾结石很重要的材料。我在临床发现乳癌的病
人几乎都有骨质疏松症,她们不是得了乳癌才有骨质疏松症,她们几乎每一个人都喝大量的牛奶。允许我多讲一点牛奶,因为这是一个大问题,如果你继续再喝牛奶下去,因为它的钙已经经过巴氏杀菌,身体不容易吸收,是无机钙,那牛奶越喝你体质越酸,体质越酸,钙就越流失,所以你永远补不够钙。一个是甜食,一个是蛋白质,这两个都是让你身体钙质流失的地方,所以你不是钙不够,你不需要补更多的钙,你需要更少的蛋白质。少到什么程度?体重乘上0.6,就是你一天需要的公克数。体重是以公斤数来算,不是以斤来算,比如说体重60公斤,你一天需要的是36公克。可是你知道吗?你随便一杯牛奶喝下去,再吃一个蛋,你很快就会超过了,再吃一些肉。。。。所以为什么蛋越多,肉越多,乳制品越多。。。。乳制品包括鲜奶、酸奶,各位你知道为什么现在很多酸奶发不起来,因为现在酸奶里面放了大量的抗生素,乳牛就有抗生素,我拿几份最近的报告给大家作说明,在今年国外份权威的报告,叫作糖尿病这份杂志,芬兰的研究人员发现,在孩子时代,每天喝500CC,就是半公升以上牛奶的儿童,他们亲属之中如果又有人得糖尿病,他们会比牛奶喝得少的孩子高出五倍的可能性发展成为异型的糖尿病。为什么现在异型的糖尿病越来越多,就是牛奶。

为什么现在小孩子越来越严重的过敏,气喘,就是牛奶。因为母亲就开始喝。所以我们在世界各地有越来越多的孕妇,怀孕前,怀孕中都开始吃健康排毒餐,所以小孩子生出来都是无毒宝宝,没有黄疸,非常健康,看上去就像个人,不像猴子。为什么?她们就是严格地把酪蛋白的摄取大幅减少,乳制品是完全不吃。所以你不要喝牛奶,喝什么?喝豆浆,豆奶。那还有比豆奶更好的是什么?是糙米浆。根据英国营养学家的研究,英国目前至少有七百万人喝牛奶后,因为胃肠无法完全消化而产生了长期的疲劳,关节炎,消化不良等健康问题,这些症状在医学上叫乳糖不适症。英国专门治疗这个疾病的马太医生就说,临床上他对这些病人进行调查,在他们不喝牛奶之后,健康状况就有显著的改善,甚至完全恢复健康。原先喝牛奶的在不喝牛奶之后,疲劳,头痛,持续性的肠胃不适,气喘,心悸,完全消失。这都是今年的报告。可是各位你们有发现吗?你给孩子喝牛奶,越喝越打瞌睡吗?对不对?国家拨了好大的预算让孩子喝牛奶,喝健康奶,结果喝到最后居然生病。改!不要喝牛奶,喝什么?要喝豆奶,可是豆奶你要小心,我前天看一个报告,美国抗议说中国政府这边对黄豆的进口开始限制,其实本来就该限制,美国的黄豆90%是基因改造的。再吃下去,下一代身体全部垮掉,二三十岁就垮掉了。不是癌症就是身体乱七八糟的乱病全部出来,所以黄豆必须是没有这些乱七八糟改造的。最好是粗米磨出来的米浆,是最好的。还有,不只是这样,我讲一个在祖国大陆的研究,我看过的研究唯一是祖国大陆的大概就只有这一份,在重庆医科大学附属儿童医院,花费了将近一年时间,对3140名2岁以下儿童进行食物过敏的流行病学调查,他们采用问卷调查,皮肤点刺测试和排除性饮食试验的方法,初步完成了诊断。专家研究发现,4-6个月大的孩子,是食物过敏的高发年龄段,大豆、花生、鱼和桔子,这些以前都不会有问题的,现在都变成过敏源,为什么?因为是改造过的,因为是中毒的。那么还有大量的农药和化肥所造成的一些影响,引起了婴幼儿的过敏,其中以牛奶和鸡蛋是引起儿童食物过敏最常见的食物,占整个食物过敏患者的63.6%。你还吃吗?你不害死孩子吗?当孩子出现呕吐、腹泻、打喷嚏,皮肤出现湿疹,这就是告诉你,我不能吃这些东西,不要再喂我吃了。我要感谢我的家庭,因为小时候家里很穷,没有牛奶吃,所以身体比较好。我讲个最新的报告,在美国最近召开的世界乳癌医学会,在渥太华召开,科学家提出了许多的报告,CX提出了一个研究报告,他发现如果奶牛施打生长激素以后,牛奶中会出现一种叫IGF1的生长激素,而它是导致乳癌的祸手。为什么乳癌现在在沿海地区的城市是女性癌症的第一名,非常多,跟牛奶有直接的关联。正常的乳牛在牛奶中并不含有很高的IGF1,有,但是不高,可是如果打完生长激素,IGF1会比一般的牛奶高出40倍,那么IGF加入到正常的细胞中,细胞就很容易癌化,而乳癌患者体内IGF都非常高。就是牛奶喝出来的,你还喝吗?
最后,结束之前我跟大家报告吃什么?对不对?这就是为什么我的?无毒一身轻?这本书在去年十月出版之后,它创造了全世界出版物的一个记录,他们叫它健康界的哈里波特,为什么?没有别的原因,因为我讲的观念非常地实用,各位你可以试试看,不要争辩,我们不管是祖国大陆也好,台湾地区也好,或是新加坡,日本也好,我们用的营养学教材很多都受到美国的影响,美国的营养学教材是谁编的?各位知道吗?肉制品协会,蛋制品协会,乳制品学会编好,送给美国各级学校,免费当教材,所以他 就告诉他们,你们要多吃肉,多吃蛋,多吃奶,他是给自己宣传。美国的小孩子从小喝牛奶,他们不喝水,可是美国的妇女现在三个有一个是骨质疏松症。美国妇女是全世界罹患骨质疏松症最多的地区,如果喝牛奶可以预防骨质疏松症,美国不可能得骨质疏松症,对不对?可是很可怕,在美国他们听我演讲笑得哈哈哈,一下子骨头断了,真的很离谱,这样讲很夸张,可是真的骨头很脆,就是牛奶喝得多了。我一边演讲他们一边喝牛奶,我讲完了就没有人敢再喝了,这很可怕的。我不知道你早上吃了多少中毒的食品,现在告诉你该吃什么。那么我在?无毒一身轻?里面主要就是谈到一个健康的生活方式以及饮食习惯的改变。我先强调,你不需要100%照着做,你只需要改变20%,30%,50%就有很大很大的效果,当然癌症,糖尿病,心脏病人至少要80%以上照着做。那一般人,还没有得病之前,我不是说你很健康,你只是还没有得,选一个黄道吉日才爆发出来。那么你该吃什么呢?我们来看看,我先讲早餐,五个手指头,你记着:一份水果,两种蔬菜,一份地瓜(红薯),一份杂粮(粗粮)米饭。什么叫作份呢?就是以它的重量来比较,比如说你今天早上吃的水果是一个大西瓜,那么你今天早上吃的红薯就要这么大一个,米饭就这么大一碗。如果你今天早上吃的水果是一个小葡萄,那你今天吃的红薯就这么小一个。。。。这个比例很重要,一份水果,两种蔬菜,一份地瓜(红薯),一份杂粮(粗粮)米饭。也有人讲我每天也吃很多五谷杂粮,为什么身体还是不好?那因为你选错食物了。你记得,蔬菜水果五谷杂粮应该怎么选择呢?一个最重要的原则,一定要选择:当地、当季、盛产这六个字的原则。当地就是同一个纬度,同一个气候带的,本地的食物。我们住在南方的人不适合吃北方的食物,住北方的人不适合吃南方的食物。我们常说一方水土养一方人,或者我们讲水土不服,就是他吃的食物跟他的体质相冲突。这是非常重要的一个原则。很多人为什么会得富贵病,因为他经常吃外面进口,很贵的东西,所以他就得富贵病,因为那些食物根本不适合他的。比如我们住北方的人不适合吃香蕉的,你吃香蕉一定身体不好,更不能吃榴莲,榴莲是谁吃的你知道吗?泰国人吃的。你再吃榴莲,以后你的脸就长得像榴莲,你这样想你就不敢吃了。因为中医很了不起的地方,发现每一个地区生长的植物,就是在这个地区里面可以帮助吃的人适应这个地区的环境,气候的变化,就不容易生病,这是很了不起的发现。所以各位你一定要记得,吃当地的。那我怎么知道当地当季的?这个季节,这个时令的食物,这个季节特产是什么?你到市场上去看,卖得最便宜的,最多摊位在卖的就对了。

就这么简单。所以有人说照排毒餐吃很省钱,因为都是买最便宜的。你们有没有发现,前一阵子在医院里面发现,一个小女孩,五岁,月经就来了。你知道为什么吗?
她除了爱吃这些高蛋白的食物之外,还有很严重,她家里很有钱,所以每一次水果刚出来她就去买来吃,水果太早出产的全部都是打激素的,你绝对不要吃,因为那个吃下去小孩子就要过度发育。我刚刚漏了告诉大家一件重要的事情,以前小女生月经平均都是十四、五岁才来,现在都提早到十二岁,甚至十岁,我发现现在最小的是五岁,月经就来了。临床上证实,月经越早来,得乳癌的机率越高,我最小的一个乳癌病人八岁。你看她们吃的东西,就是肉、蛋、奶,高热量、高蛋白、高脂肪。吃很多有激素的食物,他就催熟地很快。我在美国的时候,有一天一个亲戚,一个十二岁的小女生问我,她说:uncle,我是不是生病? 我说你为什么觉得你生病?她说我们全班月经都来了,只有我还没有来。我说你们全班都生病,只有你还没有生病。中国内地比较穷困的地方,很少吃到大量肉蛋奶的地区,妇女平均初经期都在十四五岁,可是各位,她们很少得乳癌。所以你不要以为孩子长得很快很高很好,你害死他了,我讲一句不好听的话,你当然不是故意的,但你今天过后你还让他这样吃,你就是故意的。多长了两公分没有用,没错,喝牛奶会比没有喝牛奶多长2.5-3公分,没有意义,多长2公分成就不会多20%,30%,不会的,但是身体会垮掉多200%-300%,真的很可怕。所以你一定要吃这个季节生产的,最便宜的。再举个例子,像我们以吃五谷杂粮
来讲,你选择什么?北方的五谷杂粮就非常丰富,比南方要多,除了糙米之外,还有燕麦,荞麦,大麦,小麦,对不对,燕麦、荞麦都是非常好的东西,可是南方的人就不适合吃大麦,小麦,燕麦,高粱,南方就适合吃小米(小米北方也有),玉米(各地都有)。所以你在哪一个地方生存,你就吃哪一个地方生产的食物,那个气候带的,你就会越吃越健康。这个原则就这么简单,而且很便宜。所以为什么全世界都邀我去讲这个内容你知道吗?我后来发现,全世界都很怕进关贸总协定,进世贸,因为进世贸WTO之后,所有的农产品要进来,对不对,各国农民都会受到影响。可是我告诉你,只要全中国推动健康排毒餐,都吃当地的食物,没有人会吃进口的东西。所以美国的东西怎么进口,没有人要嘛,他自己就不会再进来了,九亿农民的生计就可以保住。所以一定要吃当地的。南方的朋友你跟他讲不要吃麦制品,燕麦很好没有错,非常好,防止心脑血管疾病,可是南方人就不适合吃啊,他的体质就不能吃。可是南北方全世界都适合吃什么?吃玉米。都适合吃什么?都适合吃糙米,就是粗粮,这个非常好。
那你说我要吃水果,蔬菜,蔬菜我们怎么选择?你记得四个字的原则:根,茎,花,果,这四大类。根类,茎类,花类,果类。有四种特别建议大家吃的:大番茄,芹菜,芹菜一定要连叶子吃,我在书里面有解释为什么要连叶子吃,因为对心脏,血管的疾病很有效果的。第三个:胡萝卜。第四个:小黄瓜,这边没有小黄瓜,因为这边黄瓜都很大,就是黄瓜,非常好的,这四种。那么在整个排毒的食物里面,那你说有啊,我吃很多蔬菜水果,照你讲的,为什么身体还是不好?很简单,因为你吃错方法。你一定要记得:连皮吃,一定要连皮吃。我用中西医不同的观念来讲,中医里面认为:一般地皮是比较阳性的,果肉是比较阴性的,所以你要一起吃。如果你都削皮吃,你说不行啊,农药很多,所以我们要告诉农民,我要吃没有农药的,请不要有农药,而不是你有农药,我要削皮吃。皮的营养超过果肉,像西瓜,外层的皮本身就是入药的,在中医里,就是入药的。可是还有一层更重要,西瓜红的绿的中间那层白的,对呼吸系统特别好,可是我们都不吃,不甜嘛,所以都不吃,其实那个最有营养。柳橙那个白色的部分,最营养,可是我们都不吃,很可惜。很多啦,我在书里面举很多例子,有机会可以参考一下。还不止是这样,最重要,整个排毒餐有一个核心的思想,就是要恢复主食的地位,什么叫作主食?也就是说要回归五谷杂粮为整个总饮食里面 占最高份量比例的地位。可是很可惜,我发现现代人都不吃五谷杂粮了,都吃鸡鸭鱼肉,蛋奶炸鸡薯条牛排,对不对?身体全吃坏掉,你下次再吃牛排的时候你要记得,
每一刀割下去,其实不是割牛排,是在割你的心脏,那叉子一叉就是叉心脏,那多毒的东西呀,对身体伤害多大的东西。所以你一定要记得,当我们要吃的时候,我不是说肉完全不能吃,肉可以吃,但是你要记得冬天吃,15摄氏度以下吃,晚上六点以前吃,尽可能吃鱼,或是一个礼拜最多吃1-2次,然后吃的时候一定要记得:最后吃。所有东西都吃完了,最后才吃肉。那很多人都说了:你好聪明啊,也吃不下了。他自然就不吃了。所以你照排毒餐吃,一个月可以瘦6-8斤,所以他们叫作大吃大喝减肥法,吃很多,而且瘦都是从肚子瘦起。你看我四十多岁了,算身体不错,非常好,看起来怎么那么年轻,你不相信我可以和你赌,四十年后我们在这边再见一次面,看谁健康,我绝对有把握。上天如果许可我不要死于意外的话,我绝对有把握活到120岁。你照我讲的这个方式去生活,平均寿命大概就是120岁。而且到120岁,男像40,女像30,保证不像妖精。

那么现在年轻人身体为什么那么差?中医讲:天生万物,独厚五谷。五谷最养脾,你发现现在年轻人普遍脾脏都很差,脾胃系统都很不好,而脾管免役,管消化,管造血。这么重要的功能,什么东西对脾最好?就是五谷杂粮。可是很不幸,好不容易吃一点五谷杂粮,都吃白米,精致的白米,大白米,我不讲毒白米,我讲大白米,大白米是很不好的,为什么?你可以回去做一个实验:放水里,大白米烂掉、臭掉、腐化掉,糙米发芽了,因为糙米有生命,大白米没有生命。你是有生命的人,去吃那个没有生命的东西,没有多久就变得没有生命了。一定要吃糙米,一定要吃粗粮,没有经过精致加工的,为什么?你看糙米里面,我们用学术去分析,我们刚讲维生素B群很重要,维生素B1被称作是神经维他命,糙米里面的B1比白米多12倍。我们都知道维他命E对心脏很好,对不对,你不要从外面去买那个维他命E胶囊,越吃身体越糟糕。所有这些胶囊状的东西,用化学合成的维他命丸,芬兰政府做过很多临床的研究证实:有吃这个胶囊五年到八年的,维他命E胶囊,死于心脏命比没有吃的多50%,你最好不要吃这种东西,我是很反对吃这种化学合成的东西。可是我看从北京到广州,遍地都是,每天都要吃,越吃身体越不好。糙米里面这个维他命E就比白米多10倍。还有大肠癌的克星,大肠的蠕动最好的维生素,被世界卫生组织1996年定为第六大营养素的纤维
素,减少便秘问题的,糙米比白米纤维素多14倍以上。好不好?太好了。可是现在没有人吃,都吃白米。商人喜欢你买白米,因为白米只要保持干燥,两三年都不会坏,商业损失很低。可是糙米不行,几个月就长虫,因为虫都想吃,对不对,那白米虫都不吃你还吃?
那我们讲五谷杂粮不是只有糙米,所以你可以把糙米放70%,另外30%放杂粮,小红豆,薏仁,莲子,枸杞子,枣子,这些东西占30%,糙米占70%,这些东西一起煮,很好吃,我有机会真想做一餐给你们吃。你回去以后什么都可以不改变,一定要开始吃红薯地瓜,你知道为什么吗?有一份资料,日本国家癌症研究中心做了一个研究,把四十种最抗癌的食物排名列出来,你知道第一名是什么吗?就是红薯。第二名你知道是什么吗?还是红薯。为什么?第一名是熟的红薯,第二名是生的红薯。然后我讲花椰菜,绿色的花椰菜是第四名,马铃薯也排进去,不过是二三十名了,都在后头。所以无论如何你今天回家一定要开始吃红薯,尤其早餐一定要吃红薯,午餐可吃可不吃,晚餐可不吃。主要是消化的问题,如果你消化很好,像我三餐都吃,如果你消化不好,早餐吃,好不好。而且这是最重要的,一定要吃红薯,连皮吃。15度以上的时候,热天蒸着吃,15度以下冷天可以烤着吃,一定要连皮吃,因为皮比肉更营养,皮里面抗癌成分非常高,它是抗癌第一名。刚开始吃完你会一直放屁,所以你不要自己吃,要吃全家一起吃,要放大家一起放,放一放就好了。放屁很好啊,中医有三宝:一汗,就是流汗,一涕,就是流鼻涕,还有一屁,三大宝,这个很好,所以你一定要吃。最后一点你要记得:照顺序吃。我们经常听的一句话:饭后吃水果,帮助消化,
可是你知道吗?我们的食物从食道进来,经过胃到小肠,到大肠,水果大部分都在小肠消化吸收,它很快就会通过胃。可是如果你饭吃饱了,尤其吃了一堆肉,在胃里面至少4个小时才能通过。水果下不去,塞在门口,结果呢,过了一个多钟头以后烂掉臭掉腐化掉。所以饭后吃水果,帮助消化,加两个字就对了“不良”。饭后吃水果,帮助消化不良。所以你今后一定要改变,水果要在饭前吃,饭前半个小时到一个小时,两餐之间空腹吃,最好。你照排毒餐吃,三个小时一定饿,因为全部消化完了,身体没有负担,很轻松,很有体力,很有活力,很健康。然后水果吃完吃生的蔬菜。美国人很少得胃病,为什么?因为吃饭第一道菜吃生菜沙拉。中国人都是熟食,所以胃病特别多,因为一煮熟就没有酵素,没有酵素食物就有能消化分解吸收,就出问题,要靠你库存的酵素,那你身体很快就会用完,所以你一定要吃一部分的生食,所以一定要买到没有农药没有化肥的菜。生蔬菜先吃,再吃熟蔬菜,不要再用油去炒了,油煎油炒油炸是最伤肝,因为油包肝。为什么肝病那么多?因为中国人喜欢用油去料理,这个肝一定报费掉,肝最怕碰到油,一碰到油就不行了。好,那生的蔬菜吃完吃熟蔬菜,熟蔬菜吃完你就吃五谷杂粮,五谷杂粮吃完后你就吃蛋白质,植物蛋白先吃,最后再吃动物蛋白。所以可以吃肉,但最后吃。你这样吃的话,吃好多东西,吃得好饱,可是一点都不累。你有没有发现喝喜酒喝完好累,吃大餐吃完好累,你吃错东西。

2005年11月25日

  据我的观察总结,人一过个二十六岁就难得再有机会打架了。我个人认为没有打过架的男性人生是不完整的人生,想把看着不顺眼的人打倒在地然后踏上一万只脚这个冲动简直是胎里带来的。我上中学的时候虽然已经开化很多,但是知道自己“虽然不喜欢打架,但真要打起来也不怕”这种话成为既定事实基本上对每一个男生都非常之重要。

  话说回来,高中时候的打架实际上是缺乏艺术性与传奇性的:家长、学校、各路流氓痞子这类第三势力的粗暴干涉,外加身体没有发育到巅峰状态,因此这一时期的打架既不凶残也不好看。从上大学开始真正的打架生涯才算是到来,在高中打架打多了的人会失掉在大学中打架的不少乐趣。现在高考完毕,不少弟弟妹妹各奔前程,所以我总结了一下我在前半生的打架活动中积累的一些经验教训,力争帮助大家摆正思路,开拓视野,用三个代表指导我们的打架活动。

  从参加人数上讲,打架可以分为单挑和群殴,从双方指导思想上来讲,可以分为想把对方打趴下、想把对方打残和想把对方打死三种心理,从阶段来划分,可以分为比较漫长的前期准备、收尾善后和比较短暂的实际操作阶段。我们将就每个阶段首先研究在所有情况之中都适用的一些基本要点,然后分别讨论在大学中可能遇到的不同情况下的应对办法。

  首先是打架之前的准备工作,第一个问题就是武器的选择问题。

  我个人不推荐用刀,原因有三点:第一,刀算凶器,副作用比较大,其次:只有在双方距离非常接近并且使用熟练的情况下才能发挥比较大的威力,最后,实际上刀这种兵器本身在使对方失去战斗力的性能表现上就已经劣于其他武器,更何况现在伪劣产品太多,不少看起来甑明瓦亮的刀砍在人身上连个白印子都没有,刀的唯一好处是让经验较少或者胆量较小的对手产生恐惧心理而消减其战斗力。如果实在要用刀,在祖国各地都应该找得到王麻子或者张小泉刀剪批发店,选择刀身狭长、相对较厚的刀(普通菜刀、板刀、西瓜刀都可以),采取“拍”的战术而非“砍”的战术。这样即使打架失败,也可以回来切西瓜用。

  绝不推荐用匕首(包括被磨尖了头的改锥、剪刀、刮刀等),除非你想要对方的命。在古往今来的打架活动中,匕首造成的死亡率远远大于其他兵器。而且,匕首对使用者的技巧要求是非常高的,所以在身上带一把吓吓人是可以的,但假如真的打了起来,那末立刻扔掉匕首并拣起附近的板砖。

  各种斧头、有尖头的锤子等也不推荐,虽然论综合性能它们很可能是所有应手兵器中最好的。但是一来这些家伙资源比较稀少,二来由于在迅速使对方失去抵抗能力方面拥有非常优秀的性能,所以还是慎用为好。大家萍水相逢,又没有什么深仇大恨,只是打个架玩玩,何必那么认真下手那么绝呢,是吧。

  推荐棍棒(包括木棍、钢管等,扳手不算,扳手太重了。)。看起来棍棒似乎没有没有以上器具的威慑力,但是棍棒造成的伤害相对不大,而在使对方失去抵抗力的性能上却比较优秀,而且成规模之后声势骇人,最后,它还不算凶器,几乎可以说是十全十美,唯一的弱点就是容易断裂。在这里我要传授一点私人经验:学校里一般都有小教室,请进去,找一张课桌,低头看两条桌腿之间——不是让你看在那里学习的MM大腿——那里一般有一根用来保持课桌稳定的横撑,因为常常要用脚踩,所以都是用最结实的木料制作的,请趁人不备将之取下,然后卷在图纸里扛回来,把一头削细到可以非常方便地用双手握着,这样就可以了。顺便补充一句:绝不推荐狼牙棒!我大学同寝某曾经想在这种木棍上钉钉子,被我严词拒绝,我们都是受教育的知识份子,打个架散散心是可以的,但不能抱着下毒手的心态去打架。

  不推荐板砖。因为实在没有什么杀伤力。唯一的用处也就是正式开打之前双方互相投掷,古话叫“射住阵脚”。

  不推荐石灰粉、辣椒水、硫酸。这些东西都会给对方造成永久性伤害,我们要充满爱心地去打架,是人民内部矛盾,不能象对待阶级敌人那样对待对手。如果实在想干扰对方视力或者呼吸的话,用面粉或者芥末好了。

  推荐链条(主要是自行车的附加品例如车锁和车链),但是熟练使用链条需要相当强的基本功。因此建议大家多加练习。要多洗衣服,用手充当甩干机。

  不推荐移动电话,太昂贵了,杀伤效果也不好。

  不推荐饭盆——打架的基本原则是千万别用吃饭的家伙去打架!

  最后,强烈推荐毛巾。你可以没有任何兵器,但是你不能没有毛巾。用一至两条毛巾包裹左手,可以抵抗大部分敌人的打击,在受伤之后还可以及时包扎,并且可以比较容易地区分敌我(经常有在一伙的人第一次见面的)。建议在打架之前先按照每人1.5~1.75条购买毛巾,毛巾的尺寸要达到标准,不大不小。

  其次是药品、医疗卫生等问题。

  大学中一般打架过程中可能受到的伤害有:普通外伤、软组织挫伤、骨挫伤、骨折、出血、脑震荡、关节脱臼、牙齿脱落等。所以要在打架之前就联系好附近的医院,至少也要把打架地点选择在距离医院很近或者容易叫到出租车的地方。有了伤势尽快去医院,不要自行处理,也不要告诉我你脑子冒水了要去校医院!

  应该准备的一般用具如下:酒精棉球、脱脂棉、镊子、纱布,有条件的弄一些冰块,此外,找一个O型血的家伙以备不时之需。

  应该准备的一般药品如下:创可帖、红药水或者紫药水、云南白药、风油精、各种消炎药、跌打膏药、镇痛片。

  打架之前大家都要喝一点浓糖水(最好是白糖或者冰糖),打架之后都要喝一点果汁等饮料。

  最后是组织工作准备问题。

  所有参加打架行动的人大致有三种,一种是发起者,一种是助拳者,还有一种是打架之后的谈判者。在漫长幸福的学习生活中我们有大量机会成为所有这三种人,稍安勿燥。

  哪些人可以选择来助拳?这个范围要搞清楚。首先,不能由于打架拖累人,如果你有一个骁勇善战的死党正背着处分或者正准备入党,那么这场架是说什么也不能让他打的。其次,不能找要钱的助拳者,打架这东西是一件高尚的事业,不能让金钱玷污它的纯洁。在几乎所有的东西都成为商业规律的填房之后保持一点原始和理想主义是应该的。一般情况下,助拳者的范围仅限于同寝和同乡中比较茁壮的。要事先打听以对方的能量估计一下他可以搞来多少人,我方的指战员数目要大于对方的60%并取整——基本上再少就很难赢了。

  比指战员更加难以挑选的是战后的谈判专家。这个人的选择应当很慎重,事实上我们知道打架非但不能解决任何问题,恰好相反,它还会带来大量乱糟糟的问题。这时双方就需要各自找个有点头脸的人说话,把事情协商解决一下。以我的经验,男性辅导员是最佳人选(绝对不能考虑保安,除非他是你的直系亲属),他是个半老师不老师的人物,而且由于上多了政治课普遍都能言善辩并且说的话都非常没有道理——大家可以想像派个唐僧去谈判是什么效果。毛主席教导我们说:“谈判是战争的继续”,让谈判专家在嘴仗中气对方一个七死八活吧。

  好了,要准备的就是这么多,下面的事情就是正式动手打架了。

  首先是群架。打架这东西,跟双方是否强壮其实关系并不大,尤其是在群架的时刻,除非你有吕布之勇,否则基本上是不会有什么显山露水的机会的。打群架和单挑完全不同。打群架的最终目标是保护自己。什么时候你如果能象一条鱼一样在鏖战正酣的人群中穿来穿去而毫发无伤,什么时候就可以认为:你打群架的水准已经达到了炉火纯青的高度。要尽量控制自己出手的次数,否则被你打到的人都会在潜意识里认为你是最直接的敌人而紧追不舍。你所要做的事情主要有两个方面:一是躲闪,二是投机。如果几个盟友追着一个敌人冲上来,一定要大喝一声,挺身而出,把那个倒霉蛋一棍打翻,那几个冲上来的盟友必然对你敬仰有加——玩游戏的人都知道,得到经验值的总是最后把敌人消灭的那厮,不管他事先出没出力——然后你就可以大喝一声“跟我来”带领你这几个暂时的手下冲向一个瘦弱的敌人。以多取少焉有不胜的道理。但如果是几个敌人追着你的一个盟友过来,你最好还是装作往斜刺里冲杀的样子做战略性的撤退。

  打群架需要一种本能:瞬间找到敌人中最弱小的家伙。此外,永远记住大地就是你的生命,永远保持自己不被摔倒。一旦被摔倒你将失去绝大部分抵抗力量。永远保持冷静。愤怒和激动确实有短暂提升战斗力的效果,但同时也会浪费掉你大部分体力,保持呼吸的稳定,不发抖。否则你将很快疲惫不堪。

  至于单挑,复杂得多了。除了保持不被摔倒、保持冷静和保持呼吸平稳之外,基本上是用最快速度让对方失去战斗力的过程。打击对手以下部位可以获得最大的效果:两肋和腰部、心口隔膜与胃一带、眼睛。打斗之前千万别和对方对骂,你可以采取积分方式,例如对方侮辱伟大的党和国家,记1分,怀疑你的智商和种族,记10分,把你比喻成赵薇或者韩国,记20分,辱及双亲,记100分,一旦积分超过200分立刻翻脸动手。这种方法的好处是对方的盛气和斗志一路宣泄而你的盛气和斗志一路高昂,坏处是万一他骂到190分忽然住了口,则你的处境将会很尴尬:动手等于违反了自己的原则,不动手又实在不甘心。这时可以略带威胁地问一句:“你说完了没有?”按照一般规律对方会立刻补齐分数差值,这样你也可以算师出有名了。

  基本上,单挑主要发生在以下几个地方:食堂、澡堂、操场。我个人认为澡堂的战斗实在是很难展开:人太多,站不稳,没工具。食堂的工具一般都是饭盆,又不符合我们不用吃饭家伙作战的思路,操场上的战斗会很快演变为群殴,因此建议大家多到这些地方体验生活,做到天时地利全都熟悉才能百战不殆。

  在战斗开始之后可以喊一些口号,一来威慑敌人,二来表示一下自己的愤慨。这个口号因人而异,但也有几句坚决不能喊。下面我们一一剖析之。

  “为了新中国”“毛主席万岁”这种口号绝对不能喊。大家会在迷惑之余给精神病院打电话。

  “计划生育是我国的基本国策”也不能喊,大家会认为你在抵抗强暴。

  “坚持一个中心,两个基本点”也最好别喊,万一对方被人当特务办了就不好了。

  “打倒法仑功残余分子”可以喊,喊得好的话会有人冲上来和你一起打,而且对方的精力会立刻转移到“我不是……”之类辩白上,干扰他的斗志。

  “打倒美帝国主义”“打倒日本军国主义”可以喊,但不会有太大效果,对方有可能笑得失去抵抗能力,但是“打倒苏联修正主义”不能喊。现在没多少人知道那是什么东西。

  “哈哈哈哈!觉不觉得好玩啊?说来听听!说啊、说啊、说啊、说啊、说啊、说啊、说啊、说啊、说啊!为什么老是、老是、老是、老是、老是不说话呢?”推荐,可以把打击渗透在音节之中,而且周围的人会以为你们是在排练而鼓掌。

  “你有多少兄弟姐妹?你父母尚在吗?你说句话啊,我只是想在临死之前多交一个朋友而已啊。”不推荐。缺乏气势。

  “我手持钢鞭将你打”和“过了二十年又是一条好汉!”也不推荐,词不达意,不能起到干扰敌人的作用。

  “我的忍耐是有限度的!”非但不能喊,连想都不能想。因为这是我们寝室原来老大打架专用的。无限怀念其人中。

  OK,经过了我的一系列指导,你应该已经把那个或者那些敢于捋虎须的家伙打得遍体鳞伤、血口喷人了。下面就是最后的善后工作。

  一份总结报告是必须的,它可以让你积累经验,知道本次打架活动成功在哪里,不足在哪里,哪些人可以打,哪些人最好不要去碰,最好和哪些人搞好一点关系等等。

  把预算帐单和最后的支出对照一下。预算包括买兵器药品营养品的钱、门诊费用、处理费用、出租车发票、请同伙喝酒的酒钱、还有大约的赔偿费用(各地行情不同),其中赔偿费用一项是可以让谈判专家去就地还钱的。通常总预算都在三千元左右。

  如果被学校知道了,则必须准备一万元左右用于统一口径(包括我方和敌方)、打通关节、收买保卫处相关人等这些事情。在谈判桌上要尽量扩大我方的伤亡,在吹牛的时候则要尽量扩大我方的战果。

  最后,准备下一次打架,让自己的青春有些东西可以回忆。

如何实现双缓冲
首先给出实现的程序,然后再解释,同样是在OnDraw(CDC *pDC)中:

CDC MemDC; //首先定义一个显示设备对象
CBitmap MemBitmap;//定义一个位图对象

//随后建立与屏幕显示兼容的内存显示设备
MemDC.CreateCompatibleDC(NULL);
//这时还不能绘图,因为没有地方画 ^_^
//下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小
MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);

//将位图选入到内存显示设备中
//只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上
CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap);

//先用背景色将位图清除干净,这里我用的是白色作为背景
//你也可以用自己应该用的颜色
MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));

//绘图
MemDC.MoveTo(……);
MemDC.LineTo(……);

//将内存中的图拷贝到屏幕上进行显示
pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);

//绘图完成后的清理
MemBitmap.DeleteObject();
MemDC.DeleteDC();

禁止系统擦掉原来的图象
可以重载OnEraseBkgnd()函数,让其直接返回TRUE就可以了。如
BOOL CMyWin::OnEraseBkgnd(CDC* pDC)
{
  return TRUE;
  //return CWnd::OnEraseBkgnd(pDC);//把系统原来的这条语句注释掉。
}


多数人认为MFC的绘图函数效率很低,总是想寻求其它的解决方案。
MFC的绘图效率的确不高但也不差,而且它的绘图函数使用非常简单,
只要使用方法得当,再加上一些技巧,用MFC可以得到效率很高的绘图程序。
我想就我长期(呵呵当然也只有2年多)使用MFC绘图的经验谈谈
我的一些观点。
1、显示的图形为什么会闪烁?
    我们的绘图过程大多放在OnDraw或者OnPaint函数中,OnDraw在进行屏
幕显示时是由OnPaint进行调用的。当窗口由于任何原因需要重绘时,
总是先用背景色将显示区清除,然后才调用OnPaint,而背景色往往与绘图内容
反差很大,这样在短时间内背景色与显示图形的交替出现,使得显示窗口看起来
在闪。如果将背景刷设置成NULL,这样无论怎样重绘图形都不会闪了。
当然,这样做会使得窗口的显示乱成一团,因为重绘时没有背景色对原来
绘制的图形进行清除,而又叠加上了新的图形。
    有的人会说,闪烁是因为绘图的速度太慢或者显示的图形太复杂造成的,
其实这样说并不对,绘图的显示速度对闪烁的影响不是根本性的。
例如在OnDraw(CDC *pDC)中这样写:
 pDC->MoveTo(0,0);
 pDC->LineTo(100,100);
这个绘图过程应该是非常简单、非常快了吧,但是拉动窗口变化时还是会看见
闪烁。其实从道理上讲,画图的过程越复杂越慢闪烁应该越少,因为绘图用的
时间与用背景清除屏幕所花的时间的比例越大人对闪烁的感觉会越不明显。
比如:清楚屏幕时间为1s绘图时间也是为1s,这样在10s内的连续重画中就要闪
烁5次;如果清楚屏幕时间为1s不变,而绘图时间为9s,这样10s内的连续重画
只会闪烁一次。这个也可以试验,在OnDraw(CDC *pDC)中这样写:
 for(int i=0;i<100000;i++)
 {
  pDC->MoveTo(0,i);
  pDC->LineTo(1000,i);
 }
呵呵,程序有点变态,但是能说明问题。
    说到这里可能又有人要说了,为什么一个简单图形看起来没有复杂图形那么
闪呢?这是因为复杂图形占的面积大,重画时造成的反差比较大,所以感觉上要
闪得厉害一些,但是闪烁频率要低。
    那为什么动画的重画频率高,而看起来却不闪?这里,我就要再次强调了,
闪烁是什么?闪烁就是反差,反差越大,闪烁越厉害。因为动画的连续两个帧之间
的差异很小所以看起来不闪。如果不信,可以在动画的每一帧中间加一张纯白的帧,
不闪才怪呢。

2、如何避免闪烁
    在知道图形显示闪烁的原因之后,对症下药就好办了。首先当然是去掉MFC
提供的背景绘制过程了。实现的方法很多,
  * 可以在窗口形成时给窗口的注册类的背景刷付NULL
  * 也可以在形成以后修改背景
 static CBrush brush(RGB(255,0,0));
 SetClassLong(this->m_hWnd,GCL_HBRBACKGROUND,(LONG)(HBRUSH)brush);
  * 要简单也可以重载OnEraseBkgnd(CDC* pDC)直接返回TRUE
    这样背景没有了,结果图形显示的确不闪了,但是显示也象前面所说的一样,
变得一团乱。怎么办?这就要用到双缓存的方法了。双缓冲就是除了在屏幕上有
图形进行显示以外,在内存中也有图形在绘制。我们可以把要显示的图形先在内存中绘制好,然后再一次性的将内存中的图形按照一个点一个点地覆盖到屏幕上去(这个过程非常快,因为是非常规整的内存拷贝)。这样在内存中绘图时,随便用什么反差大的背景色进行清除都不会闪,因为看不见。当贴到屏幕上时,因为内存中最终的图形与屏幕显示图形差别很小(如果没有运动,当然就没有差别),这样看起来就不会闪。

3、如何实现双缓冲
    首先给出实现的程序,然后再解释,同样是在OnDraw(CDC *pDC)中:
 CDC MemDC; //首先定义一个显示设备对象
 CBitmap MemBitmap;//定义一个位图对象
 //随后建立与屏幕显示兼容的内存显示设备
 MemDC.CreateCompatibleDC(NULL);
 //这时还不能绘图,因为没有地方画 ^_^
 //下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小
 MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);
 
 //将位图选入到内存显示设备中
 //只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上
 CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap);
 //先用背景色将位图清除干净,这里我用的是白色作为背景
 //你也可以用自己应该用的颜色
 MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));
 //绘图
 MemDC.MoveTo(……);
 MemDC.LineTo(……);
 
 //将内存中的图拷贝到屏幕上进行显示
 pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);
 //绘图完成后的清理
 MemBitmap.DeleteObject();
 MemDC.DeleteDC();
上面的注释应该很详尽了,废话就不多说了。

4、如何提高绘图的效率
    我主要做的是电力系统的网络图形的CAD软件,在一个窗口中往往要显示成千上万个电力元件,而每个元件又是由点、线、圆等基本图形构成。如果真要在一次重绘过程重画这么多元件,可想而知这个过程是非常漫长的。如果加上了图形的浏览功能,鼠标拖动图形滚动时需要进行大量的重绘,速度会慢得让用户将无法忍受。怎么办?只有再研究研究MFC的绘图过程了。
    实际上,在OnDraw(CDC *pDC)中绘制的图并不是所有都显示了的,例如:你
在OnDraw中画了两个矩形,在一次重绘中虽然两个矩形的绘制函数都有执行,但是很有可能只有一个显示了,这是因为MFC本身为了提高重绘的效率设置了裁剪区。裁剪区的作用就是:只有在这个区内的绘图过程才会真正有效,在区外的是无效的,即使在区外执行了绘图函数也是不会显示的。因为多数情况下窗口重绘的产生大多是因为窗口部分被遮挡或者窗口有滚动发生,改变的区域并不是整个图形而只有一小部分,这一部分需要改变的就是pDC中的裁剪区了。因为显示(往内存或者显存都叫显示)比绘图过程的计算要费时得多,有了裁剪区后显示的就只是应该显示的部分,大大提高了显示效率。但是这个裁剪区是MFC设置的,它已经为我们提高了显示效率,在进行复杂图形的绘制时如何进一步提高效率呢?那就只有去掉在裁剪区外的绘图过程了。可以先用pDC->GetClipBox()得到裁剪区,然后在绘图时判断你的图形是否在这个区内,如果在就画,不在就不画。
如果你的绘图过程不复杂,这样做可能对你的绘图效率不会有提高。


关键字 双缓冲
原作者姓名 戚高

介绍
在论坛中经常见到关于刷新时界面闪烁的帖子,如何控制在进行高效绘图时不出现界面闪烁的感觉呢,下文就双缓冲方法进行讲解.

正文
图形为什么会闪烁的原因是:我们的绘图过程大多放在OnDraw或者OnPaint函数中,OnDraw在进行屏幕显示时是由OnPaint进行调用的。当窗口由于任何原因需要重绘时,总是先用背景色将显示区清除,然后才调用OnPaint,而背景色往往与绘图内容反差很大,这样在短时间内背景色与显示图形的交替出现,使得显示窗口看起来在闪。如果将背景刷设置成NULL,这样无论怎样重绘图形都不会闪了。当然,这样做会使得窗口的显示乱成一团,因为重绘时没有背景色对原来绘制的图形进行清除,而又叠加上了新的图形。有的人会说,闪烁是因为绘图的速度太慢或者显示的图形太复杂造成的,其实这样说并不对,绘图的显示速度对闪烁的影响不是根本性的。
如何实现双缓冲:在OnDraw(CDC *pDC)中:
      CDC MemDC; //首先定义一个显示设备对象
      CBitmap MemBitmap;//定义一个位图对象

      //随后建立与屏幕显示兼容的内存显示设备
      MemDC.CreateCompatibleDC(NULL);
      //这时还不能绘图,因为没有地方画 ^_^
      //下面建立一个与屏幕显示兼容的位图,至于位图的大小嘛,可以用窗口的大小
      MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight);
      //将位图选入到内存显示设备中
      //只有选入了位图的内存显示设备才有地方绘图,画到指定的位图上
      CBitmap *pOldBit=MemDC.SelectObject(&MemBitmap);
      //先用背景色将位图清除干净,这里我用的是白色作为背景
      //你也可以用自己应该用的颜色
      MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255));
      //绘图
      MemDC.MoveTo(……);
      MemDC.LineTo(……);

      //将内存中的图拷贝到屏幕上进行显示
      pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY);
      //绘图完成后的清理
      MemBitmap.DeleteObject();
      MemDC.DeleteDC();


以论坛的一个帖子例子为例来说明一些具体如何解决问题.
帖子那容是:

我想让一个区域动起来,
如何解决窗口刷新时区域的闪烁。
void CJhkljklView::OnDraw(CDC* pDC)
{
    CJhkljklDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    // TODO: add draw code for native data here
            int i;
    int x[20],y[20];
    CPen hPen;
     POINT w[5];
    
             x[0]=a/100+10;
             x[1]=a/100+30;
                 x[2]=a/100+80;
                 x[3]=a/100+30;
                 x[4]=a/100+10;

                     y[0]=10;
                  y[1]=10;
                   y[2]=25;
                    y[3]=40;
                     y[4]=40;    

      for (i=0;i<5;i++)
      {         w[i].x=x[i];
      w[i].y=y[i];
      }
      //CClientDC dc(this);
             //hPen=CreatePen(PS_SOLID,1,RGB(255,0,0));
      CRgn argn,Brgn;
        CBrush abrush(RGB(40,30,20));
        argn.CreatePolygonRgn(w, 5, 1);// point为CPoint数组,
        pDC->FillRgn(&argn, &abrush);
         abrush.DeleteObject();
}

void CJhkljklView::OnTimer(UINT nIDEvent)
{
    // TODO: Add your message handler code here and/or call default
        InvalidateRect(NULL,true);
        UpdateWindow();
        a+=100;
    CView::OnTimer(nIDEvent);
}

int CJhkljklView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    if (CView::OnCreate(lpCreateStruct) == -1)
        return -1;
    
    // TODO: Add your specialized creation code here
    SetTimer(1,10,NULL);
    return 0;
}


利用定时器直接进行10毫秒的屏幕刷新,这样效果会出现不停的闪烁的情况.

解决方法利用双缓冲,首先触发WM_ERASEBKGND,然后修改返回TRUE;
定义变量:
CBitmap *m_pBitmapOldBackground ;
CBitmap m_bitmapBackground ;
CDC m_dcBackground;

//绘制背景
if(m_dcBackground.GetSafeHdc()== NULL|| (m_bitmapBackground.m_hObject == NULL))
    {
        m_dcBackground.CreateCompatibleDC(&dc);
        m_bitmapBackground.CreateCompatibleBitmap(&dc,rect.Width(),rect.Height()) ;
        m_pBitmapOldBackground = m_dcBackground.SelectObject(&m_bitmapBackground) ;
        //DrawMeterBackground(&m_dcBackground, rect);
        CBrush brushFill, *pBrushOld;        
        // 背景色黑色
        brushFill.DeleteObject();
        brushFill.CreateSolidBrush(RGB(255, 255, 255));
        pBrushOld = m_dcBackground.SelectObject(&brushFill);
        m_dcBackground.Rectangle(rect);
        m_dcBackground.SelectObject(pBrushOld);
    }
    memDC.BitBlt(0, 0, rect.Width(), rect.Height(),
                       &m_dcBackground, 0, 0, SRCCOPY) ;

    //绘制图形
    int i;
    int x[20],y[20];
    CPen hPen;
    POINT w[5];
    
    x[0]=a/100+10;
    x[1]=a/100+30;
    x[2]=a/100+80;
    x[3]=a/100+30;
    x[4]=a/100+10;
    
    y[0]=10;
    y[1]=10;
    y[2]=25;
    y[3]=40;
    y[4]=40;
    
    
    
    for (i=0;i<5;i++)
    {         w[i].x=x[i];
    w[i].y=y[i];
    }
    //CClientDC dc(this);
    //hPen=CreatePen(PS_SOLID,1,RGB(255,0,0));
    CRgn argn,Brgn;
    CBrush abrush(RGB(40,30,20));
    argn.CreatePolygonRgn(w, 5, 1);// point为CPoint数组,
    memDC.FillRgn(&argn, &abrush);
    abrush.DeleteObject();
}


这样编译运行程序就会出现屏幕不闪烁的情况了.

2005年11月24日

作者:[Seraphim] 来源:商海桥 
 

  关于 const 的一件美妙的事情是它允许你指定一种语义上的约束:一个特定的对象不应该被修改。而编译器将执行这一约束。它允许你通知编译器和其他程序员,某个值应该保持不变。如果确实如此,你就应该明确地表示出来,因为这样一来,你就可以谋取编译器的帮助,确定这个值不会被改变。

  关键字 const 非常多才多艺。在类的外部,你可以将它用于全局常量或命名空间常量,就像那些在文件、函数或模块范围内被声明为 static 的对象。在类的内部,你可以将它用于 static 和 non-static 数据成员上。对于指针,你可以指定这个指针本身是 const,或者它所指向的数据是 const,或者两者都是,或者都不是。

char greeting[] = "Hello";

char *p = greeting; // non-const pointer,
// non-const data

const char *p = greeting; // non-const pointer,
// const data

char * const p = greeting; // const pointer,
// non-const data

const char * const p = greeting; // const pointer,
// const data

  这样的语法本身其实并不像表面上那样反复无常。如果 const 出现在 * 左边,则指针指向的内容为常量;如果 const 出现在 * 右边,则指针自身为常量;如果 const 出现在 * 两边,则两者都为常量。

  当指针指向的内容为常量时,一些人将 const 放在类型之前,另一些人将 const 放在类型之后 * 之前。两者在意义上并没有区别,所以,如下两个函数具有相同的参数类型:

void f1(const Widget *pw); // f1 takes a pointer to a
// constant Widget object

void f2(Widget const *pw); // so does f2

  因为它们都存在于实际的代码中,你应该习惯于这两种形式。

  STL iterators 以指针为原型,所以一个 iterator 在行为上非常类似于一个 T* 指针。声明一个 iterator 为 const 就类似于声明一个指针为 const(也就是说声明一个 T* const 指针):不能将 iterator 指向另外一件不同的东西,但是它所指向的东西本身可以变化。如果你要一个 iterator 指向一个不能变化的东西(也就是 const T* 的 STL 对等物),你应该用 const_iterator:

std::vector<int> vec;
...
const std::vector<int>::iterator iter = // iter acts like a T* const

vec.begin();

*iter = 10; // OK, changes what iter points to

++iter; // error! iter is const

std::vector<int>::const_iterator cIter = //cIter acts like a const T*

vec.begin();

*cIter = 10; // error! *cIter is const

++cIter; // fine, changes cIter

  对 const 最强有力的用法来自于它在函数声明中的应用。在一个函数声明中,const 既可以用在函数返回值上,也可以用在个别的参数上,对于成员函数,还可以用于整个函数。

  一个函数返回一个常量,常常可以在不放弃安全和效率的前提下尽可能减少客户的错误造成的影响。例如,考虑在 Item 24 中考察的 rational 成员 operator* 的声明:

class Rational { ... };

const Rational operator*(const Rational& lhs, const Rational& rhs);

  很多第一次看到这些的人会不以为然。为什么 operator* 的结果应该是一个 const 对象?因为如果它不是,客户就可以犯下如此暴行:

Rational a, b, c;
...
(a * b) = c; // invoke operator= on the
// result of a*b!

  我不知道为什么一些程序员要为两个数的乘积赋值,但是我知道很多程序员这样做也并非不称职。所有这些可能来自一个简单的输入错误(要求这个类型能够隐式转型到 bool):

if (a * b = c) ... // oops, meant to do a comparison!
  如果 a 和 b 是内建类型,这样的代码显而易见是非法的。一个好的用户自定义类型的特点就是要避免与内建类型毫无理由的不和谐,而且对我来说允许给两个数的乘积赋值看上去正是毫无理由的。将 operator* 的返回值声明为 const 就可以避免这一点,这就是我们要这样做的理由。

  关于 const 参数没什么特别新鲜之处——它们的行为就像局部的 const 对象,而且无论何时,只要你能,你就应该这样使用。除非你需要改变一个参数或本地对象的能力,否则,确认将它声明为 const。它只需要你键入六个字符,就能将你从我们刚刚看到的这个恼人的错误中拯救出来:“我想键入‘==’,但我意外地键入了‘=’”。

  const 成员函数

  成员函数被声明为 const 的目的是确信这个函数可能会被 const 对象调用。因为两个原因,这样的成员函数非常重要。首先,它使一个类的接口更容易被理解。知道哪个函数可以改变对象而哪个不可以是很重要的。第二,它们可以和 const 对象一起工作。书写高效代码有一个很重要的方面,就像 Item 20 所解释的,提升一个 C++ 程序的性能的基本方法就是就是传递一个对象的引用给一个 const 参数。这个技术只有在 const 候选对象有 const 成员函数可操作时才是可用的。

  很多人没有注意到这样的事实,即成员函数只有常量性不同时是可以被重载的,这是 C++ 的一个重要特性。考虑一个代表文字块的类:

class TextBlock {

  public:
   ...
   const char& operator[](std::size_t position) const // operator[] for
   { return text[position]; } // const objects
   char& operator[](std::size_t position) // operator[] for
   { return text[position]; } // non-const objects
  private:
   std::string text;
};

  TextBlock 的 operator[]s 可能会这样使用:

TextBlock tb("Hello");

std::cout << tb[0]; // calls non-const
// TextBlock::operator[]

const TextBlock ctb("World");

std::cout << ctb[0]; // calls const TextBlock::operator[]

  顺便提一下,const 对象在实际程序中最经常使用的是作为这样一个操作的结果:将指针或者引用传递给 const 参数。上面的 ctb 的例子是人工假造的。下面这个例子更真实一些:

void print(const TextBlock& ctb) // in this function, ctb is const
{
  std::cout << ctb[0]; // calls const TextBlock::operator[]
  ...
}

  通过将 operator[] 重载,而且给不同的版本不同的返回类型,你能对 const 和 non-const 的 TextBlocks 做不同的操作:

std::cout << tb[0]; // fine - reading a
// non-const TextBlock

tb[0] = ’x’; // fine - writing a
// non-const TextBlock

std::cout << ctb[0]; // fine - reading a
// const TextBlock

ctb[0] = ’x’; // error! - writing a
// const TextBlock

  请注意这个错误只是发生在调用 operator[] 的返回类型上,而调用 operator[] 本身总是正确的。错误出现在企图为 const char& 赋值的时候,而这正是 const 版本的 operator[] 的返回类型。

  再请注意 non-const 版本的 operator[] 的返回类型是一个字符的引用而不是字符本身。如果 operator[] 只是简单地返回一个字符,下面的语句将无法编译:

tb[0] = ’x’;
  因为改变一个返回内建类型的函数的返回值总是非法的。如果它合法,那么 C++ 以值(by value)返回对象这一事实(参见 Item 20)就意味着 tb.text[0] 的副本被改变,而不是 tb.text[0] 自己,这不会是你想要的行为。

  让我们为哲学留一点时间。看看一个成员函数是 const 意味着什么?有两个主要的概念:二进制位常量性(bitwise constness)(也称为物理常量性(physical constness))和逻辑常量性(logical constness)。

  二进制位 const 派别坚持认为,一个成员函数,当且仅当它不能改变对象的任何数据成员(static 成员除外),也就是说不能改变对象内的任何二进制位,则这个成员函数就是 const。二进制位常量性的一个好处是比较容易监测违例:编译器只需要寻找对数据成员的赋值。实际上,二进制位常量性就是 C++ 对常量性的定义,一个 const 成员函数不被允许改变调用它的对象的任何 non-static 数据成员。

  不幸的事,很多成员函数并不能完全通过二进制位常量性的检验。特别是,一个经常改变一个指针指向的内容的成员函数。除非这个指针在这个对象中,否则这个函数就是二进制位 const 的,编译器也不会提出异议。例如,假设我们有一个类似 TextBlock 的类,因为它需要与一个不知 string 为何物的 C API 打交道,所以它需要将它的数据存储为 char* 而不是 string。

class CTextBlock {
  public:
   ...
   char& operator[](std::size_t position) const // inappropriate (but bitwise

   { return pText[position]; } // const) declaration of
   // operator[]
  private:
   char *pText;
};

  尽管 operator[] 返回对象内部数据的引用,这个类还是(不适当地)将它声明为 const 成员函数(Item 28 将谈论一个深入的主题)。先将它放到一边,看看 operator[] 的实现,它并没有使用任何手段改变 pText。结果,编译器愉快地生成了 operator[] 的代码,因为对所有编译器而言,它都是二进制位 const 的,但是我们看看会发生什么:

const CTextBlock cctb("Hello"); // declare constant object

char *pc = &cctb[0]; // call the const operator[] to get a
// pointer to cctb’s data

*pc = ’J’; // cctb now has the value "Jello"

  这里确实出了问题,你用一个确定的值创建一个常量对象,然后你只是用它调用了 const 成员函数,但是你改变了它的值! 这就引出了逻辑常量性的概念。这一理论的信徒认为:一个 const 成员函数被调用的时候可能会改变对象中的一些二进制位,但是只能用客户无法感觉到的方法。例如,你的 CTextBlock 类在需要的时候可以储存文字块的长度:

class CTextBlock {
  public:
   ..
   std::size_t length() const;

  private:
   char *pText;
   std::size_t textLength; // last calculated length of textblock
   bool lengthIsValid; // whether length is currently valid
};

std::size_t CTextBlock::length() const
{
  if (!lengthIsValid) {
   textLength = std::strlen(pText); // error! can’t assign to textLength
   lengthIsValid = true; // and lengthIsValid in a const
  } // member function
  return textLength;
}

  length 的实现当然不是二进制位 const 的—— textLength 和 lengthIsValid 都可能会被改变——但是它还是被看作对 const CTextBlock 对象有效。但编译器不同意,它还是坚持二进制位常量性,怎么办呢?

  解决方法很简单:利用以关键字 mutable 为表现形式的 C++ 的 const-related 的灵活空间。mutable 将 non-static 数据成员从二进制位常量性的约束中解放出来:

class CTextBlock {
  public:
   ...
   std::size_t length() const;

  private:
   char *pText;
   mutable std::size_t textLength; // these data members may
   mutable bool lengthIsValid; // always be modified, even in
}; // const member functions

std::size_t CTextBlock::length() const
{
  if (!lengthIsValid) {
   textLength = std::strlen(pText); // now fine
   lengthIsValid = true; // also fine
  }
  return textLength;
}

  避免 const 和 non-const 成员函数的重复

  mutable 对于解决二进制位常量性不太合我的心意的问题是一个不错的解决方案,但它不能解决全部的 const-related 难题。例如,假设 TextBlock(包括 CTextBlock)中的 operator[] 不仅要返回一个适当的字符的引用,它还要进行边界检查,记录访问信息,甚至数据完整性确认,将这些功能加入到 const 和 non-const 的 operator[] 函数中,使它们变成如下这样的庞然大物:

class TextBlock {
  public:
   ..
   const char& operator[](std::size_t position) const
   {
    ... // do bounds checking
    ... // log access data
    ... // verify data integrity
    return text[position];
   }
   char& operator[](std::size_t position)
   {
    ... // do bounds checking
    ... // log access data
    ... // verify data integrity
    return text[position];
   }
  private:
   std::string text;
};

  哎呀!你是说重复代码?还有随之而来的额外的编译时间,维护成本以及代码膨胀等令人头痛的事情吗?当然,也可以将边界检查等全部代码转移到一个单独的成员函数(当然是私有的)中,并让两个版本的 operator[] 来调用它,但是,你还是要重复写出调用那个函数和返回语句的代码。

  怎样才能只实现一次 operator[] 功能,又可以使用两次呢?你可以用一个版本的 operator[] 去调用另一个版本。并通过强制转型去掉常量性。

  作为一个通用规则,强制转型是一个非常坏的主意,我将投入整个一个 Item 来告诉你不要使用它,但是重复代码也不是什么好事。在当前情况下,const 版本的 operator[] 所做的事也正是 non-const 版本所做的,仅有的不同是它有一个 const 返回类型。在这种情况下,通过转型去掉返回类型的常量性是安全的,因为,无论谁调用 non-const operator[],首要条件是有一个 non-const 对象。否则,他不可能调用一个 non-const 函数。所以,即使需要一个强制转型,让 non-const operator[] 调用 const 版本以避免重复代码的方法也是安全的。代码如下,随后的解释可能会让你对它的理解更加清晰:

class TextBlock {
  public:
   ...
   const char& operator[](std::size_t position) const // same as before
   {
    ...
    ...
    ...
    return text[position];
   }
   char& operator[](std::size_t position) // now just calls const op[]
   {
    return
    const_cast<char&>( // cast away const on
    // op[]’s return type;
    static_cast<const TextBlock&>(*this) // add const to *this’s type;
    [position] // call const version of op[]
   );
  }
  ...
};

  正如你看到的,代码中有两处强制转型,而不止一处。我们让 non-const operator[] 调用 const 版本,但是,如果在 non-const operator[] 的内部,我们仅仅调用了 operator[],那我们将递归调用我们自己一百万次甚至更多。为了避免无限递归,我们必须明确指出我们要调用 const operator[],但是没有直接的办法能做到这一点,于是我们将 *this 从它本来的类型 TextBlock& 强制转型到 const TextBlock&。是的,我们使用强制转型为它加上了 const!所以我们有两次强制转型:第一次是为 *this 加上 const(目的是当我们调用 operator[] 时调用的是 const 版本),第二次是从 const operator[] 的返回值之中去掉 const。

  增加 const 的强制转型是一次安全的转换(从一个 non-const 对象到一个 const 对象),所以我们用 static_cast 来做。去掉 const 的强制转型可以用 const_cast 来完成,在这里我们没有别的选择。

  在完成其它事情的基础上,我们在此例中调用了一个操作符,所以,语法看上去有些奇怪。导致其不会赢得选美比赛,但是它通过在 const 版本的 operator[] 之上实现其 non-const 版本而避免重复代码的方法达到了预期的效果。使用丑陋的语法达到目标是否值得最好由你自己决定,但是这种在一个 const 成员函数的基础上实现它的 non-const 版本的技术却非常值得掌握。

  更加值得知道的是做这件事的反向方法——通过用 const 版本调用 non-const 版本来避免代码重复——是你不能做的。记住,一个 const 成员函数承诺不会改变它的对象的逻辑状态,但是一个 non-const 成员函数不会做这样的承诺。如果你从一个 const 成员函数调用一个 non-const 成员函数,你将面临你承诺不会变化的对象被改变的风险。这就是为什么使用一个 const 成员函数调用一个 non-const 成员函数是错误的,对象可能会被改变。实际上,那样的代码如果想通过编译,你必须用一个 const_cast 来去掉 *this 的 const,这样做是一个显而易见的麻烦。而反向的调用——就像我在上面的例子中用的——是安全的:一个 non-const 成员函数对一个对象能够为所欲为,所以调用一个 const 成员函数也没有任何风险。这就是 static_cast 可以在这里工作的原因:这里没有 const-related 危险。

  就像在本文开始我所说的,const 是一件美妙的东西。在指针和迭代器上,在涉及对象的指针,迭代器和引用上,在函数参数和返回值上,在局部变量上,在成员函数上,const 是一个强有力的盟友。只要可能就用它,你会为你所做的感到高兴。

  Things to Remember

  ·将某些东西声明为 const 有助于编译器发现使用错误。const 能被用于对象的任何范围,用于函数参数和返回类型,用于整个成员函数。

  ·编译器坚持二进制位常量性,但是你应该用概念上的常量性(conceptual constness)来编程。(此处原文有误,conceptual constness 为作者在本书第二版中对 logical constness 的称呼,正文中的称呼改了,此处却没有改。其实此处还是作者新加的部分,却使用了旧的术语,怪!——译者)

  ·当 const 和 non-const 成员函数具有本质上相同的实现的时候,使用 non-const 版本调用 const 版本可以避免重复代码。