Saturday, December 10, 2011

What I should do during the winter vacation

The last class was over today. There are three finals and a project need to do. I think it is necessary to make a plan before the beginning of the vacation, which could make the plan more aggressive and ambitious. Now what is in my mind is as followed:

  1. do some ACM-ICPC problems. The best way to do it may be not by “Interview street”, as Lanbo told me that the problem there is a little complicated. I think I could find some problem from those blogs. I think I could do 30 or 40 and ensure the answers are correct;
  2. Get more familiar with CUDA, OpenCV, numerical calculation and matrix computation;

There may be something more, to be continued.

Thursday, December 8, 2011

How to verify the unique min-cut

How to verify that there is only one min-cut in a graph G(V, E), which have one sink, t, and a source, s. The first idea is as followed:
  • run the max-flow algorithm, such as Ford-Fulkerson or Edmonds-Karp, and keep the residual graph as G`.
  • run DFS, from s and t, on G` and find out a reachable set R1 and R2.
  • if and only if R1 + R2 = E, then return true, otherwise, return false.
This method uses a constraint of min-cut that all the edges through a min-cut in a max-flow graph have 0 capacity. So, if there are two min-cut, there must be another “bottleneck” that does not allow DFS to reach the node.
For a directed graph G(V, E). We could do the following to verify the unique or not.
  • run the max-flow algorithm;
  • for each edge e though the min cut, increase the capacity;
  • if the max flow does not change, the min cut is not unique; otherwise, it is unique.

Monday, December 5, 2011

VC/C++ 变量命名规则

一、程序风格:

1、严格采用阶梯层次组织程序代码:

各层次缩进的分格采用VC的缺省风格,即每层次缩进为4格,括号位于下一行。要求相匹配的大括号在同一列,对继行则要求再缩进4格。例如:

2、提示信息字符串的位置

在程序中需要给出的提示字符串,为了支持多种语言的开发,除了一些给调试用的临时信息外,其他所有的提示信息必须定义在资源中。

3、对变量的定义,尽量位于函数的开始位置。

二、命名规则:

1、变量名的命名规则

①、变量的命名规则要求用“匈牙利法则”。即开头字母用变量的类型,其余部分用变量的英文意思或其英文意思的缩写,尽量避免用中文的拼音,要求单词的第一个字母应大写。

即: 变量名=变量类型+变量的英文意思(或缩写)

对非通用的变量,在定义时加入注释说明,变量定义尽量可能放在函数的开始处。

见下表:

bool(BOOL)   用b开头   bIsParent   

byte(BYTE)   用by开头   byFlag   

short(int)   用n开头   nStepCount   

long(LONG)   用l开头   lSum   

char(CHAR)   用c开头   cCount   

float(FLOAT)   用f开头   fAvg   

double(DOUBLE)   用d开头   dDeta   

void(VOID)   用v开头   vVariant   

unsigned   int(WORD) 用w开头   wCount   

unsigned   long(DWORD)   用dw开头   dwBroad   

HANDLE(HINSTANCE) 用h开头   hHandle   

DWORD   用dw开头   dwWord   

LPCSTR(LPCTSTR)   用str开头   strString   

用0结尾的字符串 用sz开头   szFileName   

对未给出的变量类型要求提出并给出命名建议给技术委员会。

②、指针变量命名的基本原则为:

对一重指针变量的基本原则为:

“p”+变量类型前缀+命名

如一个float*型应该表示为pfStat   

对多重指针变量的基本规则为:

二重指针:   “pp”+变量类型前缀+命名

三重指针:   “ppp”+变量类型前缀+命名

......   

③、全局变量用g_开头,如一个全局的长型变量定义为g_lFailCount,即:变量名=g_+变量类型+变量的英文意思(或缩写)

④、静态变量用s_开头,如一个静态的指针变量定义为s_plPerv_Inst,即: 变量名=s_+变量类型+变量的英文意思(或缩写)

⑤、成员变量用m_开头,如一个长型成员变量定义为m_lCount;即:变量名=m_+变量类型+变量的英文意思(或缩写)

⑥、对枚举类型(enum)中的变量,要求用枚举变量或其缩写做前缀。并且要求用大写。

如:enum   cmEMDAYS   

{   

EMDAYS_MONDAY;   

EMDAYS_TUESDAY;   

……   

};   

⑦、对struct、union、class变量的命名要求定义的类型用大写。并要加上前缀,其内部变量的命名规则与变量命名规则一致。

结构一般用S开头

如:struct   ScmNPoint   

{   

int   nX;//点的X位置

int   nY;   //点的Y位置

};   

联合体一般用U开头

如:   union   UcmLPoint   

{   

long   lX;   

long   lY;   

}   

类一般用C开头

如:

class   CcmFPoint   

{   

public:   

float   fPoint;   

};   

对一般的结构应该定义为类模板,为以后的扩展性考虑

如:

template   

class   CcmTVector3d   

{   

public:   

TYPE   x,y,z;   

};   

⑧、对常量(包括错误的编码)命名,要求常量名用大写,常量名用英文表达其意思。

如:#define   CM_FILE_NOT_FOUND   CMMAKEHR(0X20B)   其中CM表示类别。

⑨、对const   的变量要求在变量的命名规则前加入c_,即:c_+变量命名规则;例如:

const   char*   c_szFileName;   

2、 函数的命名规范:

函数的命名应该尽量用英文表达出函数完成的功能。遵循动宾结构的命名法则,函数名中动词在前,并在命名前加入函数的前缀,函数名的长度不得少于8个字母。

例如:

long   cmGetDeviceCount(……);   

3、函数参数规范:

①、 参数名称的命名参照变量命名规范。

②、 为了提高程序的运行效率,减少参数占用的堆栈,传递大结构的参数,一律采用指针或引用方式传递。

③、 为了便于其他程序员识别某个指针参数是入口参数还是出口参数,同时便于编译器检查错误,应该在入口参数前加入const标志。如:

……cmCopyString(const   char   *   c_szSource,   char   *   szDest)   

4、引出函数规范:

对于从动态库引出作为二次开发函数公开的函数,为了能与其他函数以及Windows的函数区分,采用类别前缀+基本命名规则的方法命名。例如:在对动态库中引出的一个图象编辑的函数定义为   imgFunctionname(其中img为image缩写)。

现给出三种库的命名前缀:

①、 对通用函数库,采用cm为前缀。

②、 对三维函数库,采用vr为前缀。

③、 对图象函数库,采用img为前缀。

对宏定义,结果代码用同样的前缀。

5、文件名(包括动态库、组件、控件、工程文件等)的命名规范:

文件名的命名要求表达出文件的内容,要求文件名的长度不得少于5个字母,严禁使用象file1,myfile之类的文件名。

三、注释规范:

1、函数头的注释

对于函数,应该从“功能”,“参数”,“返回值”、“主要思路”、“调用方法”、“日期”六个方面用如下格式注释:   

//程序说明开始

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

//   功能: 从一个String   中删除另一个String。

//   参数:   strByDelete,strToDelete   

//   (入口)   strByDelete:   被删除的字符串(原来的字符串)

//   (出口)   strToDelete:   要从上个字符串中删除的字符串。

//   返回: 找到并删除返回1,否则返回0。(对返回值有错误编码的要//   求列出错误编码)。

//   主要思路:本算法主要采用循环比较的方法来从strByDelete中找到

//   与strToDelete相匹配的字符串,对多匹配strByDelete   

//   中有多个strToDelete子串)的情况没有处理。请参阅:

//   书名......   

//   调用方法:......   

//   日期:起始日期,如:2000/8/21.9:40--2000/8/23.21:45   

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

函数名(……)   

//程序说明结束

①、 对于某些函数,其部分参数为传入值,而部分参数为传出值,所以对参数要详细说明该参数是入口参数,还是出口参数,对于某些意义不明确的参数还要做详细说明(例如:以角度作为参数时,要说明该角度参数是以弧度(PI),还是以度为单位),对既是入口又是出口的变量应该在入口和出口处同时标明。等等。

②、 函数的注释应该放置在函数的头文件中,在实现文件中的该函数的实现部分应该同时放置该注释。

③、 在注释中应该详细说明函数的主要实现思路、特别要注明自己的一些想法,如果有必要则应该写明对想法产生的来由。对一些模仿的函数应该注释上函数的出处。

④、 在注释中详细注明函数的适当调用方法,对于返回值的处理方法等。在注释中要强调调用时的危险方面,可能出错的地方。

⑤、 对日期的注释要求记录从开始写函数到结束函数的测试之间的日期。

⑥、 对函数注释开始到函数命名之间应该有一组用来标识的特殊字符串。

如果算法比较复杂,或算法中的变量定义与位置有关,则要求对变量的定义进行图解。对难以理解的算法能图解尽量图解。

2、变量的注释:

对于变量的注释紧跟在变量的后面说明变量的作用。原则上对于每个变量应该注释,但对于意义非常明显的变量,如:i,j等循环变量可以不注释。

例如:   long   lLineCount   //线的根数。

3、文件的注释:

文件应该在文件开头加入以下注释:

/////////////////////////////////////////////////////////////////////   

//   工程:   文件所在的项目名。

//   作者:**,修改者:**   

//   描述:说明文件的功能。

//   主要函数:…………   

//   版本:   说明文件的版本,完成日期。

//   修改:   说明对文件的修改内容、修改原因以及修改日期。

//   参考文献:   ......   

/////////////////////////////////////////////////////////////////////   

为了头文件被重复包含要求对头文件进行定义如下:   

#ifndef   __FILENAME_H__   

#define   __FILENAME_H__   

其中FILENAME为头文件的名字。

4、其他注释:

在函数内我们不需要注释每一行语句。但必须在各功能模块的每一主要部分之前添加块注释,注释每一组语句,在循环、流程的各分支等,尽可能多加以注释。

其中的循环、条件、选择等位置必须注释。

对于前后顺序不能颠倒的情况,建议在注释中增加序号。

例如:

在其他顺序执行的程序中,每隔3—5行语句,必须加一个注释,注明这一段语句所组成的小模块的作用。对于自己的一些比较独特的思想要求在注释中标明。

四、程序健壮性:

1、函数的返回值规范:

对于函数的返回位置,尽量保持单一性,即一个函数尽量做到只有一个返回位置。(单入口单出口)。

要求大家统一函数的返回值,所有的函数的返回值都将以编码的方式返回。

例如编码定义如下:

#define   CM_POINT_IS_NULL   CMMAKEHR(0X200)   

:   

:   

建议函数实现如下:

long   函数名(参数,……)   

{   

long   lResult;   //保持错误号

lResult=CM_OK;   

//如果参数有错误则返回错误号

if(参数==NULL)   

{   

lResult=CM_POINT_IS_NULL;   

goto   END;   

}   

……   

END:   

return   lResult;   

}   

2、关于goto的应用:

对goto语句的应用,我们要求尽量少用goto语句。对一定要用的地方要求只能向后转移。

3、资源变量的处理(资源变量是指消耗系统资源的变量):

对资源变量一定赋初值。分配的资源在用完后必须马上释放,并重新赋值。

4、对复杂的条件判断,为了程序的可读性,应该尽量使用括号。

例:if(((szFileName!=NULL)&&(lCount> =0)))||(bIsReaded==TRUE))   

五、可移植性:

1、高质量的代码要求能够跨平台,所以我们的代码应该考虑到对不同的平台的支持,特别是对windows98和windowsnt的支持。

2、由于C语言的移植性比较好,所以对算法函数要求用C代码,不能用C++代码。

3、对不同的硬件与软件的函数要做不同的处理。

a Array 数组

b BOOL (int) 布尔(整数)

by Unsigned Char (Byte) 无符号字符(字节)

c Char 字符(字节)

cb Count of bytes 字节数

cr Color reference value 颜色(参考)值

cx Count of x (Short) x的集合(短整数)

dw DWORD (unsigned long) 双字(无符号长整数)

f Flags (usually multiple bit values) 标志(一般是有多位的数值)

fn Function 函数

g_ global 全局的

h Handle 句柄

i Integer 整数

l Long 长整数

lp Long pointer 长指针

m_ Data member of a class 一个类的数据成员

n Short int 短整数

p Pointer 指针

s String 字符串

sz Zero terminated String 以0结尾的字符串

tm Text metric 文本规则

u Unsigned int 无符号整数

ul Unsigned long (ULONG) 无符号长整数

w WORD (unsigned short) 无符号短整数

x,y x, y coordinates (short) 坐标值/短整数

v void 空

有关项目的全局变量用g_开始,类成员变量用m_,局部变量若函数较大则可考虑用l_用以显示说明其是局部变量。

前缀 类型 例子

g_ 全局变量 g_Servers

C 类或者结构体 CDocument,CPrintInfo

m_ 成员变量 m_pDoc,m_nCustomers

VC常用前缀列表:

前缀 类型 描述 例子

ch char 8位字符 chGrade

ch TCHAR 16位UNICODE类型字符 chName

b BOOL 布尔变量 bEnabled

n int 整型(其大小由操作系统决定) nLength

n UINT 无符号整型(其大小由操作系统决定) nLength

w WORD 16位无符号整型 wPos

l LONG 32位有符号整型 lOffset

dw DWORD 32位无符号整型 dwRange

p * Ambient memory model pointer 内存模块指针,指针变量 pDoc

lp FAR* 长指针 lpDoc

lpsz LPSTR 32位字符串指针 lpszName

lpsz LPCSTR 32位常量字符串指针 lpszName

lpsz LPCTSTR 32位UNICODE类型常量指针 lpszName

h handle Windows对象句柄 hWnd

lpfn (*fn)() 回调函数指针 Callback Far pointer to CALLBACK function lpfnAbort

Windows对象名称缩写:

Windows对象例子变量 MFC类 例子对象

HWND hWnd; CWnd* pWnd;

HDLG hDlg; CDialog* pDlg;

HDC hDC; CDC* pDC;

HGDIOBJ hGdiObj; CGdiObject* pGdiObj;

HPEN hPen; CPen* pPen; 

HBRUSH hBrush; CBrush* pBrush; 

HFONT hFont; CFont* pFont; 

HBITMAP hBitmap; CBitmap* pBitmap; 

HPALETTE hPalette; CPalette* pPalette; 

HRGN hRgn; CRgn* pRgn; 

HMENU hMenu; CMenu* pMenu; 

HWND hCtl; CStatic* pStatic; 

HWND hCtl; CButton* pBtn;

HWND hCtl; CEdit* pEdit; 

HWND hCtl; CListBox* pListBox;

HWND hCtl; CComboBox* pComboBox;

VC常用宏定义命名列表:

前缀 符号类型符号例子 范围

IDR_ 标识多个资源共享的类型 IDR_MAINFRAME 1~0x6FFF

IDD_ 对话框资源(Dialog) IDD_SPELL_CHECK 1~ 0x6FFF

HIDD_ 基于对话框的上下文帮助 HIDD_SPELL_CHECK 0x20001~0x26FF

IDB_ 位图资源(Bitmap) IDB_COMPANY_LOGO 1~0x6FFF

IDC_ 光标资源(Cursor) IDC_PENCIL 1~0x6FFF

IDI_ 图标资源(Icon) IDI_NOTEPAD 1~0x6FFF

ID_、IDM_ 工具栏或菜单栏的命令项 ID_TOOLS_SPELLING 0x8000~0xDFFF

HID_ 命令上下文帮助 HID_TOOLS_SPELLING 0x18000~0x1DFFF

IDP_ 消息框提示文字资源 IDP_INVALID_PARTNO 8~0xDFFF

HIDP_ 消息框上下文帮助 HIDP_INVALID_PARTNO 0x30008~0x3DFFF

IDS_ 字符串资源(String) IDS_COPYRIGHT 1~0x7FFF

IDC_ 对话框内的控制资源 IDC_RECALC 8~0xDFFF

Microsoft MFC宏命名规范 

名称 类型

_AFXDLL 唯一的动态连接库(Dynamic Link Library,DLL)版本

_ALPHA 仅编译DEC Alpha处理器

_DEBUG 包括诊断的调试版本

_MBCS 编译多字节字符集

_UNICODE 在一个应用程序中打开Unicode

AFXAPI MFC提供的函数

CALLBACK 通过指针回调的函数 

库标识符命名法

标识符 值和含义

u ANSI(N)或Unicode(U)

d 调试或发行:D = 调试;忽略标识符为发行

静态库版本命名规范

库 描述

NAFXCWD.LIB 调试版本:MFC静态连接库

NAFXCW.LIB 发行版本:MFC静态连接库

UAFXCWD.LIB 调试版本:具有Unicode支持的MFC静态连接库

UAFXCW.LIB 发行版本:具有Unicode支持的MFC静态连接库

动态连接库命名规范 

名称 类型

_AFXDLL 唯一的动态连接库(DLL)版本

WINAPI Windows所提供的函数

Windows.h中新的命名规范 

类型 定义描述

WINAPI 使用在API声明中的FAR PASCAL位置,如果正在编写一个具有导出API人口点的DLL,则可以在自己的API中使用该类型

CALLBACK 使用在应用程序回调程序,如窗口和对话框过程中的FAR PASCAL的位置

LPCSTR 与LPSTR相同,只是LPCSTR用于只读串指针,其定义类似(const char FAR*)

UINT 可移植的无符号整型类型,其大小由主机环境决定(对于Windows NT和Windows 9x为32位);它是unsigned int的同义词

LRESULT 窗口程序返回值的类型

LPARAM 声明lParam所使用的类型,lParam是窗口程序的第四个参数

WPARAM 声明wParam所使用的类型,wParam是窗口程序的第三个参数

LPVOID 一般指针类型,与(void *)相同,可以用来代替LPSTR

Google C++ 编码风格精简

  Google C++ 编码风格精简

头文件:

1.头文件防多重定义define格式:<PROJECT>_<PATH>_<FILE>_H_

2.能使用前置声明尽量不用头文件包含

3.只有当函数只有 10 行甚至更少时才将其定义为内联函数(注意虚函数,递归函数,以及使用了循环语句的函数)

4.复杂的内联函数的定义, 放在后缀名为 -inl.h 的头文件中

5.定义函数时,输入参数永远放在输出参数之前

6.项目内头文件应按照项目源代码目录树结构排列

7.example.cc包含头文件顺序:example.h,C系统文件,C++ 系统文件,其他库的h文件,本项目内h文件

作用域:

1.在.cc文件中, 允许甚至鼓励使用匿名名字空间;不要在h文件中使用匿名名字空间 

2.在.cc文件.h文件的函数, 方法或类中, 可以使用 "using"关键字 及 名字空间别名 

3.不要将嵌套类定义成公有, 除非它们是接口的一部分, 比如, 嵌套类含有某些方法的一组选项

4.使用静态成员函数或名字空间内的非成员函数, 尽量不要用裸的全局函数 

5.将函数变量尽可能置于最小作用域内, 并在变量声明时进行初始化

6.禁止使用 class 类型的静态或全局变量: 它们会导致很难发现的 bug 和不确定的构造和析构函数调用顺序

类:

1.构造函数中只进行那些没什么意义的初始化, 可能的话, 使用 Init() 方法集中初始化有意义的数据

2.如果一个类定义了若干成员变量又没有其它构造函数, 必须定义一个默认构造函数

3.对单个参数的构造函数使用 C++ 关键字 explicit(如果构造函数只有一个参数, 可看成是一种隐式转换)

4.仅在代码中需要拷贝一个类对象的时候使用拷贝构造函数; 大部分情况下都不需要, 此时应使用 DISALLOW_COPY_AND_ASSIGN

{

#define DISALLOW_COPY_AND_ASSIGN(TypeName) /

TypeName(const TypeName&); /

void operator=(const TypeName&)

}

5.仅当只有数据时使用 struct, 其它一概使用 class

6.使用组合常常比使用继承更合理. 如果使用继承的话, 定义为 public 继承

7.如果你的类有虚函数, 则析构函数也应该为虚函数

8.当重载一个虚函数, 在衍生类中把它明确的声明为 virtual

9.只在以下情况我们才允许多重继承: 最多只有一个基类是非抽象类; 其它基类都是以Interface为后缀的纯接口类

10.接口是指满足特定条件的类, 这些类以 Interface 为后缀 (不强制).

11.尽量不要重载运算符

12.数据成员在任何情况下都必须是私有的, 并根据需要提供相应的存取函数

13.在类中使用特定的声明顺序: public: 在 private: 之前, 成员函数在数据成员前

14.如果函数超过 40 行, 可以思索一下能不能在不影响程序结构的前提下对其进行分割

Google奇技:

1.尽量避免使用智能指针(任何情况下都不要使用 auto_ptr),使用时也尽量局部化

2.使用 cpplint.py 检查风格错误

C++特性:

1. 输入参数是值参或 const 引用, 输出参数为指针.

2.仅在输入参数类型不同, 功能相同时使用重载函数 (含构造函数)

3.不允许使用缺省函数参数

4.不允许使用变长数组和 alloca()

5.不要在 Google 的开源项目中使用异常(关于这一点google仅仅是为了自身方便=。=)

6.禁止使用RTTI

7.使用 C++ 的类型转换, 如 static_cast<>(). 不要使用 int y = (int)x 或 int y = int(x) 等转换方式

{

用 static_cast 替代 C 风格的值转换, 或某个类指针需要明确的向上转换为父类指针时

用 const_cast 去掉 const 限定符

用 reinterpret_cast 指针类型和整型或其它指针之间进行不安全的相互转换. 仅在你对所做一切了然于心时使用 dynamic_cast 测试代码以外不要使用. 除非是单元测试, 如果你需要在运行时确定类型信息, 说明有 设计缺陷

}

8.只在记录日志时使用流

9.对迭代器和模板类型, 使用前置自增 (自减).对简单数值 (非对象)无所谓

10.强烈建议你在任何可能的情况下都要使用 const

{

如果函数不会修改传入的引用或指针类型参数, 该参数应声明为 const.

尽可能将函数声明为 const. 访问函数应该总是 const. 其他不会修改任何数据成员, 未调用非 const 函数, 不会返回数据成员非 const 指针或引用的函数也应该声明成 const.

如果数据成员在对象构造之后不再发生变化, 可将其定义为 const.

}

11.C++ 内建整型中, 仅使用 int. 如果程序中需要不同大小的变量, 可以使用 <stdint.h> 中长度精确的整型, 如 int16_t

12.使用断言来指出变量为非负数, 而不是使用无符号型

13.代码应该对 64 位和 32 位系统友好(!!!)

14.使用宏时要非常谨慎, 尽量以内联函数, 枚举和常量代替之

{

不要在 .h 文件中定义宏

在马上要使用时才进行 #define, 使用后要立即 #undef

不要只是对已经存在的宏使用#undef,选择一个不会冲突的名称

不要试图使用展开后会导致 C++ 构造不稳定的宏, 不然也至少要附上文档说明其行为

}

15.整数用 0, 实数用 0.0, 指针用 NULL, 字符 (串) 用 '/0'

16.尽可能用 sizeof(varname) 代替 sizeof(type)

17.只使用 Boost 中被认可的库

命名约定:

1.函数命名, 变量命名, 文件命名应具备描述性; 不要过度缩写. 类型和变量应该是名词, 函数名可以用 “命令性” 动词

2.文件名要全部小写, 可以包含下划线 (_) 或连字符 (-). 按项目约定来.

3.类型名称的每个单词首字母均大写, 不包含下划线: MyExcitingClass, MyExcitingEnum.

4.变量名一律小写, 单词之间用下划线连接. 类的成员变量以下划线结尾:my_local_variable,my_member_variable_

5.结构体的数据成员可以和普通变量一样, 不用像类那样接下划线

6.常量命名 在名称前加 k,接大写字母开头的单词

7.函数名的每个单词首字母大写, 没有下划线,取值和设值函数要与存取的变量名匹配.非常短小的内联函数名也可以用小写字母

8.名字空间用小写字母命名, 并基于项目名称和目录结构: google_awesome_project.

9.枚举的命名应当和常量一致: kEnumName.

代码注释:

1.使用行注释或块注释,统一就好

2.在每一个文件开头加入版权公告,然后是文件内容描述

{

版权;

许可版本:Apache2.0 BSD LGPL GPL;

作者;

文件内容

}

3.每个类的定义要附着类的功能和用法的注释

4.函数声明处注释描述函数功能,定义处描述函数实现

5.类数据成员应注释说明用途,是否可以接受NULL或-1等警戒值

6.全局变量注释说明含义和用途

7.复杂代码前要加注释;比较隐晦的代码后空两格加行尾注释

8.向函数传入整型,布尔值和指针时,注释说明含义,或使用常量让代码望文知意

9.临时,短期,或不够完美的解决方案使用TODO注释;格式//TODO(ID):Information

格式:

1.每一行长度不要超过80(路径,URL,头文件保护除外)

2.尽量不使用非ASCII字符,使用时必须使用UTF-8字符

3.只使用空格,每次缩进两个空格

4.函数声明:返回值和函数名在同一行,参数也尽量放在同一行

{

返回值总是和函数名在同一行;

左圆括号总是和函数名在同一行;

函数名和左圆括号间没有空格

圆括号与参数间没有空格;

左大括号总在最后一个参数同一行的末尾处 ;

右大括号总是单独位于函数最后一行;

右圆括号和左大括号间总是有一个空格;

函数声明和实现处的所有形参名称必须保持一致;

所有形参应尽可能对齐;

缺省缩进为2个空格;

换行后的参数保持4个空格的缩进;

}

5.如果函数声明成const,关键字const应与最后一个参数位于同一行 

6.如果有些参数没有用到, 在函数定义处将参数名注释起来

7.函数调用尽量放在同一行, 否则, 将实参封装在圆括号中

8.switch 语句可以使用大括号分段. 空循环体应使用 {} 或 continue. 

9.如果一个布尔表达式超过标准行宽断行方式要统一

10.return 表达式中不要用圆括号包围

11.预处理指令不要缩进, 从行首开始 

12.访问控制块的声明依次序是 public:, protected:, private:, 每次缩进 1 个空格

13.构造函数初始化列表放在同一行或按四格缩进并排几行

14.名字空间内容不增加缩进层次

15.永远不要在行尾添加没意义的留白

16.不在万不得已, 不要使用空行. 尤其是: 两个函数定义之间的空行不要超过 2 行, 函数体首尾不要留空行, 函数体中也不要随意添加空行

规则特例:

对于Windows程序员:

1.不要使用匈牙利命名法,使用 Google 命名约定, 包括对源文件使用 .cc 扩展名

2.尽量使用原有的 C++ 类型

3.使用 Microsoft Visual C++ 进行编译时, 将警告级别设置为 3 或更高, 并将所有 warnings 当作 errors 处理

4.不要使用 #pragma once; 而应该使用 Google 的头文件保护规则

5.除非万不得已, 不要使用任何非标准的扩展,

C++宏的使用(一)

C/C++宏的使用

1. 防止多重包含 2

2. 条件编译 2

3. 定义字面值常量 2

4. 定义为函数 2

5. 可变参数宏 3

6. 宏组合 3

6.1 一般用法 4

6.2 当宏参数是另一个宏的时候 4

6.2.1 非'#'和'##'的情况 4

6.2.2 当有'#'或'##'的时候 4

6.3 '#'和'##'的一些应用特例 5

6.3.1 合并匿名变量名 5

6.3.2 填充结构 5

6.3.3 记录文件名 6

6.3.4 得到一个数值类型所对应的字符串缓冲大小 6

7. 其他使用例子 6

7.1 得到指定地址上的一个字节或字 6

7.2 求最大值和最小值 6

7.3 得到一个field在结构体(struct)中的偏移量 6

7.4 得到一个结构体中field所占用的字节数 7

7.5 按照LSB格式把两个字节转化为一个Word 7

7.6 按照LSB格式把一个Word转化为两个字节 7

7.7 得到一个变量的地址(word宽度) 7

7.8 得到一个字的高位和低位字节 7

7.9 返回一个比X大的最接近的8的倍数 7

7.10 将一个字母转换为大写 7

7.11 判断字符是不是10进值的数字 7

7.12 判断字符是不是16进值的数字 8

7.13 防止溢出的一个方法 8

7.14 返回数组元素的个数 8

7.15 对于IO空间映射在存储空间的结构,输入输出处理

1. 防止多重包含

防止头文件多重包含:

如下

CODE:

#ifndef MAIN_H_

#define MAIN_H_

其它内容

#endif

作用就是阻止这个头文件被多次include。多次include就会出现重复的定义情况,所以需要在每个头文件中都使用这个定义。

2. 条件编译

#ifdef _DEBUG

printf("this debug info/n");

#endif

如果没有定义_DEBUG宏,那么上面那一行是不会编译进去。但是定义了_DEBUG后,上面那行就会编译进去。

#ifdef  _M_IX86

#elif defined _M_MRX000

#endif

3. 定义字面值常量

方便修改,尽量做到修改地方少。

#define PRINT_STR "你好"

main()

{

printf(PRINT_STR);

return 0;

}

4. 定义为函数

#ifdef _DEBUG

#define A(x) a(x)

#else

#define A(x) b(x)

#endif

int a(int x)

{

return x+1;

}

int b(int x)

{

return x+100;

}

int main()

{

printf ("A(10) value is %d",A(10));

return 0;

}

其实也可以定义成#define A a

但是定义成A(x)后只有A后面带一个(x)类型的编译器才会执行替换,比较安全,可以保证只替换函数而不替换变量。

5. 可变参数宏

有些时候定义一个宏来代替某个函数,但是这个函数是可变参数的话,那就需要考虑办法了

定义方法如下

#include <iostream>

using namespace std;

#define PRINT(...) cout<<(__VA_ARGS__)

#define PRINTC(...) printf(__VA_ARGS__)

int _tmain(int argc, _TCHAR* argv[])

{

//C++6.0不可运行

PRINT("FLY编译例子");

PRINT(endl);

PRINTC("%d %s %s",1,"吃饭了吗 smile MM:)","/n");

return 0;

}

6. 宏组合

也就是##和#的用法

##是连接符号,连接两个宏

#是把名字代替成字符串

6.1 一般用法

#define s5(a)    supper_##a

#include <stdio.h>

void supper_printf(const char* p )

{

printf("this is supper printf:/n%s/n",p);

}

int main()

{

s5(printf)("hello owrld");

return 0;

}

#用法如下

#include <stdio.h>

#define s(p)   #p

int main()

{

printf(s(p)"/n");

return 0;

}

6.2 当宏参数是另一个宏的时候

需要注意的是凡宏定义里有用'#'或'##'的地方宏参数是不会再展开。

6.2.1 非'#'和'##'的情况

#define TOW      (2)

#define MUL(a,b) (a*b)

printf("%d*%d=%d/n", TOW, TOW, MUL(TOW,TOW));

这行的宏会被展开为:

printf("%d*%d=%d/n", (2), (2), ((2)*(2)));

MUL里的参数TOW会被展开为(2)。

6.2.2 当有'#'或'##'的时候

#define A          (2)

#define STR(s)     #s

#define CONS(a,b)  int(a##e##b)

printf("int max: %s/n",  STR(INT_MAX));  // INT_MAX #include<climits>

这行会被展开为:

printf("int max: %s/n", "INT_MAX");

printf("%s/n", CONS(A, A));               // compile error

这一行则是:

printf("%s/n", int(AeA));

INT_MAX和A都不会再被展开,然而解决这个问题的方法很简单。加多一层中间转换宏。

加这层宏的用意是把所有宏的参数在这层里全部展开,那么在转换宏里的那一个宏(_STR)就能得到正确的宏参数。

#define A           (2)

#define _STR(s)     #s

#define STR(s)      _STR(s)          // 转换宏

#define _CONS(a,b)  int(a##e##b)

#define CONS(a,b)   _CONS(a,b)       // 转换宏

printf("int max: %s/n", STR(INT_MAX));          // INT_MAX,int型的最大值,为一个变量#include<climits>

输出为: int max: 0x7fffffff

STR(INT_MAX)-->_STR(0x7fffffff) 然后再转换成字符串;

printf("%d/n", CONS(A, A));

输出为:200

CONS(A, A)-->_CONS((2), (2))-->int((2)e(2))

6.3 '#'和'##'的一些应用特例

6.3.1 合并匿名变量名

#define     ___ANONYMOUS1(type, var, line)  type  var##line

#define  __ANONYMOUS0(type, line)  ___ANONYMOUS1(type, _anonymous, line)

#define    ANONYMOUS(type)  __ANONYMOUS0(type, __LINE__)

例:ANONYMOUS(static int); 即: static int _anonymous70;  70表示该行行号;

第一层:ANONYMOUS(static int);  -->  __ANONYMOUS0(static int, __LINE__);

第二层:                        -->  ___ANONYMOUS1(static int, _anonymous, 70);

第三层:                        -->  static int  _anonymous70;

即每次只能解开当前层的宏,所以__LINE__在第二层才能被解开。

6.3.2 填充结构

#define    FILL(a)   {a, #a}

enum IDD{OPEN, CLOSE};

typedef struct MSG

{

IDD id;

const char * msg;

}MSG;

MSG _msg[] = {FILL(OPEN), FILL(CLOSE)};

相当于:

MSG _msg[] = {{OPEN, "OPEN"}, {CLOSE, "CLOSE"}};

6.3.3 记录文件名

#define  _GET_FILE_NAME(f)   #f

#define  GET_FILE_NAME(f)    _GET_FILE_NAME(f)

static char  FILE_NAME[] = GET_FILE_NAME(__FILE__);

6.3.4 得到一个数值类型所对应的字符串缓冲大小

#define  _TYPE_BUF_SIZE(type)  sizeof #type

#define  TYPE_BUF_SIZE(type)   _TYPE_BUF_SIZE(type)

char  buf[TYPE_BUF_SIZE(INT_MAX)];

-->  char  buf[_TYPE_BUF_SIZE(0x7fffffff)];

-->  char  buf[sizeof "0x7fffffff"];

这里相当于:

char  buf[11];

7. 其他使用例子

7.1 得到指定地址上的一个字节或字

#define MEM_B( x ) ( *( (byte *) (x) ) )

#define MEM_W( x ) ( *( (word *) (x) ) )

7.2 求最大值和最小值

#define MAX( x, y ) ( ((x) > (y)) ? (x) : (y) )

#define MIN( x, y ) ( ((x) < (y)) ? (x) : (y) )

7.3 得到一个field在结构体(struct)中的偏移量

#define FPOS( type, field ) /

( (dword) &(( type *) 0)-> field )

7.4 得到一个结构体中field所占用的字节数

#define FSIZ( type, field ) sizeof( ((type *) 0)->field )

7.5 按照LSB格式把两个字节转化为一个Word

#define FLIPW( ray ) ( (((word) (ray)[0]) * 256) + (ray)[1] )

7.6 按照LSB格式把一个Word转化为两个字节

#define FLOPW( ray, val ) /

(ray)[0] = ((val) / 256); /

(ray)[1] = ((val) & 0xFF)

7.7 得到一个变量的地址(word宽度)

#define B_PTR( var ) ( (byte *) (void *) &(var) )

#define W_PTR( var ) ( (word *) (void *) &(var) )

7.8 得到一个字的高位和低位字节

#define WORD_LO(xxx) ((byte) ((word)(xxx) & 255))

#define WORD_HI(xxx) ((byte) ((word)(xxx) >> 8))

7.9 返回一个比X大的最接近的8的倍数

#define RND8( x )       ((((x) + 7) / 8 ) * 8 )

7.10 将一个字母转换为大写

#define UPCASE( c ) ( ((c) >= ''a'' && (c) <= ''z'') ? ((c) - 0x20) : (c) )

7.11 判断字符是不是10进值的数字

#define DECCHK( c ) ((c) >= ''0'' && (c) <= ''9'')

7.12 判断字符是不是16进值的数字

#define HEXCHK( c ) ( ((c) >= ''0'' && (c) <= ''9'') ||/

((c) >= ''A'' && (c) <= ''F'') ||/

((c) >= ''a'' && (c) <= ''f'') )

7.13 防止溢出的一个方法

#define INC_SAT( val ) (val = ((val)+1 > (val)) ? (val)+1 : (val))

7.14 返回数组元素的个数

#define ARR_SIZE( a ) ( sizeof( (a) ) / sizeof( (a[0]) ) )

7.15 对于IO空间映射在存储空间的结构,输入输出处理

#define inp(port)      (*((volatile byte *) (port)))

#define inpw(port)     (*((volatile word *) (port)))

#define inpdw(port)    (*((volatile dword *)(port)))

#define outp(port, val)   (*((volatile byte *) (port)) = ((byte) (val)))

#define outpw(port, val)  (*((volatile word *) (port)) = ((word) (val)))

#define outpdw(port, val)  (*((volatile dword *) (port)) = ((dword) (val)))

Virtual table in C++

The main purpose of virtual function mechanism is to implement polymorphism, which is one of the most important characters of OOP. Polymorphism, from some point of view, is to enable the base class to call the member function which is concrete, or override, in a derivative class. In another word, it tries to act different action without changing the code.

How to use virtual function is trivial to a coder. So I wont say much here. You may refer to some books about C++ for this topic. In this article, I would mainly focus on virtual table and analyze how virtual table works.

Virtual table, V-Table for short, is a table located in the top of an object. It is a list of addresses of virtual functions. Once we have such a table, inheritance, override could be implemented on top of it. For instance, if we have a pointer to parent class and point it to a derivative object, the virtual table contains the addresses of the function should be involved.

Let us check the following example:

typedef void(*Fun)(void);
 
class Base
{
public:
    virtual void a()
    {
        cout<<"Base::a()"<<endl;
    }
    virtual void b()
    {
        cout<<"Base::b()"<<endl;
    }
    virtual void c()
    {
        cout<<"Base::c()"<<endl;
    }
};
 
int main()
{
    Fun pFun = NULL;
    Base b;
    //
    cout<<"The address of virtual table: "<<(int *)*(int*)(&b)<<endl;
    cout<<"The address of the first virtual function: "<<(int*)*(int *)*(int*)(&b)<<endl;
    //
    pFun = (Fun)*(int *)*(int*)(&b);
    pFun();
    return 0;
}



The output of above code is as followed:


vt1


Furthermore, we could easily guess the addresses of other member functions as followed:


(Fun)*((int*)*(int*)(&b)+0); // Base::a()
(Fun)*((int*)*(int*)(&b)+1); // Base::b()
(Fun)*((int*)*(int*)(&b)+2); // Base::c()



Ok, the above addresses are too simple, right. Lets try an example a little complicated, simple inheritance without override.


Suppose we have two classes as the UML described.


o_Drawing3


As the derivative class does not override the functions in parent class, the virtual table are as followed:


o_vtable2


It is easy to find out that



  1. virtual functions are in the order of declaration;
  2. Functions in parent class are in front of derivative class.

For the case of inheritance with override, as the following classes.


o_Drawing4


The virtual table is as followed. Be aware of the first element in the table, which is different from the above example.


o_vtable3


From the figure above, we should notice that:



  1. the virtual function in parent class is replaced by the override function from the derivative class;

  2. the other functions remain the same.

So we run some code like:

Base* b = new Derive();
b->a();




We would get some result like “Derive::a()”.


Then, lets check the multiple inheritance. The first example is as followed:


o_Drawing1


As the first example, there is not virtual function override. So the virtual table would be very similar to simple inheritance. The only difference is that there would be as many virtual table as parent classes. In this case, there would be three virtual table, in the order of declaration.


o_vtable4



The final case is the multiple inheritance with overriding.


The classes are as follow:


o_Drawing2


The virtual table is as followed:


o_vtable5


We could find that all the virtual functions in the parent classes are replaced by the derivative class.



So, let try the following code to verify the above knowledge.


class Base1
{
public:
    virtual void a()
    {
        cout<<"Base1::a()"<<endl;
    }
    virtual void b()
    {
        cout<<"Base1::b()"<<endl;
    }
    virtual void c()
    {
        cout<<"Base1::c()"<<endl;
    }
};
class Base2
{
public:
    virtual void a()
    {
        cout<<"Base2::a()"<<endl;
    }
    virtual void b()
    {
        cout<<"Base2::b()"<<endl;
    }
    virtual void c()
    {
        cout<<"Base2::c()"<<endl;
    }
};
class Base3
{
public:
    virtual void a()
    {
        cout<<"Base3::a()"<<endl;
    }
    virtual void b()
    {
        cout<<"Base3::b()"<<endl;
    }
    virtual void c()
    {
        cout<<"Base3::c()"<<endl;
    }
};
 
class Derive: public Base1, public Base2, public Base3
{
public:
    void a()
    {
        cout<<"Derive::a()"<<endl;
    }
};
 
int main()
{
    Derive d;
    Base1 *b1 = &d;
    Base2 *b2 = &d;
    Base3 *b3 = &d;
    b1->a();
    b2->a();
    b3->a();
    b1->b();
    b2->b();
    b3->b();
    return 0;
}

vt2


Criticizes:


The virtual table mechanism plays an very important role in C++, without doubt. However, there are also some problems caused by it. 



  1. Try to access the functions in derivative class through a pointer of parent class. As shown in the following image:

o_vtable2


There is an entry for f1(), but I cannot access that function. However, you may violate this C++ grammar by directly decompose the virtual table to call it.



  1. Access non-public virtual function. Try the following code:


class Base 
{    
    private:virtual void f() 
    { 
        cout << "Base::f" << endl; 
    }
};
class Derive : public Base
{};
typedef void(*Fun)(void);
void main() 
{    
    Derive d;   
    Fun pFun = (Fun)*((int*)*(int*)(&d)+0);    
    pFun();
}





 


Thanks to the post here in Chinese. Some images in this blogs are from there.


Saturday, December 3, 2011

【转】怎么理解编码

很久很久以前,有一群人,他们决定用8个可以开合的晶体管来组合成不同的状态,以表示世界上的万物。他们看到8个开关状态是好的,于是他们把这称为"字节"。 再后来,他们又做了一些可以处理这些字节的机器,机器开动了,可以用字节来组合出很多状态,状态开始变来变去。他们看到这样是好的,于是它们就这机器称为"计算机"。 开始计算机只在美国用。八位的字节一共可以组合出256(2的8次方)种不同的状态。 他们把其中的编号从0开始的32种状态分别规定了特殊的用途,一但终端、打印机遇上约定好的这些字节被传过来时,就要做一些约定的动作。遇上00x10, 终端就换行,遇上0x07, 终端就向人们嘟嘟叫,例好遇上0x1b, 打印机就打印反白的字,或者终端就用彩色显示字母。他们看到这样很好,于是就把这些0x20以下的字节状态称为"控制码"。 他们又把所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第127号,这样计算机就可以用不同字节来存储英语的文字了。大家看到这样,都感觉很好,于是大家都把这个方案叫做 ANSI 的"Ascii"编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上所有的计算机都用同样的ASCII方案来保存英文文字。 后来,就像建造巴比伦塔一样,世界各地的都开始使用计算机,但是很多国家用的不是英文,他们的字母里有许多是ASCII里没有的,为了可以在计算机保存他们的文字,他们决定采用127号之后的空位来表示这些新的字母、符号,还加入了很多画表格时需要用下到的横线、竖线、交叉等形状,一直把序号编到了最后一个状态255。从128到255这一页的字符集被称"扩展字符集"。从此之后,贪婪的人类再没有新的状态可以用了,美帝国主义可能没有想到还有第三世界国家的人们也希望可以用到计算机吧! 等中国人们得到计算机时,已经没有可以利用的字节状态来表示汉字,况且有6000多个常用汉字需要保存呢。但是这难不倒智慧的中国人民,我们不客气地把那些127号之后的奇异符号们直接取消掉, 规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。 中国人民看到这样很不错,于是就把这种汉字方案叫做 "GB2312"。GB2312 是对 ASCII 的中文扩展。 但是中国的汉字太多了,我们很快就就发现有许多人的人名没有办法在这里打出来,特别是某些很会麻烦别人的国家领导人。于是我们不得不继续把 GB2312 没有用到的码位找出来老实不客气地用上。 后来还是不够用,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为 GBK 标准,GBK 包括了 GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。 后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK 扩成了 GB18030。从此之后,中华民族的文化就可以在计算机时代中传承了。 中国的程序员们看到这一系列汉字编码的标准是好的,于是通称他们叫做 "DBCS"(Double Byte Charecter Set 双字节字符集)。在DBCS系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。那时候凡是受过加持,会编程的计算机僧侣们都要每天念下面这个咒语数百遍: "一个汉字算两个英文字符!一个汉字算两个英文字符……" 因为当时各个国家都像中国这样搞出一套自己的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码,连大陆和台湾这样只相隔了150海里,使用着同一种语言的兄弟地区,也分别采用了不同的 DBCS 编码方案——当时的中国人想让电脑显示汉字,就必须装上一个"汉字系统",专门用来处理汉字的显示、输入的问题,但是那个台湾的愚昧封建人士写的算命程序就必须加装另一套支持 BIG5 编码的什么"倚天汉字系统"才可以用,装错了字符系统,显示就会乱了套!这怎么办?而且世界民族之林中还有那些一时用不上电脑的穷苦人民,他们的文字又怎么办? 真是计算机的巴比伦塔命题啊! 正在这时,大天使加百列及时出现了——一个叫 ISO (国际标谁化组织)的国际组织决定着手解决这个问题。他们采用的方法很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号的编码!他们打算叫它"Universal Multiple-Octet Coded Character Set",简称 UCS, 俗称 "UNICODE"。 UNICODE 开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了。于是 ISO 就直接规定必须用两个字节,也就是16位来统一表示所有的字符,对于ascii里的那些“半角”字符,UNICODE 包持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于"半角"英文符号只需要用到低8位,所以其高8位永远是0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。 这时候,从旧社会里走过来的程序员开始发现一个奇怪的现象:他们的strlen函数靠不住了,一个汉字不再是相当于两个字符了,而是一个!是的,从 UNICODE 开始,无论是半角的英文字母,还是全角的汉字,它们都是统一的"一个字符"!同时,也都是统一的"两个字节",请注意"字符"和"字节"两个术语的不同,“字节”是一个8位的物理存贮单元,而“字符”则是一个文化相关的符号。在UNICODE 中,一个字符就是两个字节。一个汉字算两个英文字符的时代已经快过去了。 从前多种字符集存在时,那些做多语言软件的公司遇上过很大麻烦,他们为了在不同的国家销售同一套软件,就不得不在区域化软件时也加持那个双字节字符集咒语,不仅要处处小心不要搞错,还要把软件中的文字在不同的字符集中转来转去。UNICODE 对于他们来说是一个很好的一揽子解决方案,于是从 Windows NT 开始,MS 趁机把它们的操作系统改了一遍,把所有的核心代码都改成了用 UNICODE 方式工作的版本,从这时开始,WINDOWS 系统终于无需要加装各种本土语言系统,就可以显示全世界上所有文化的字符了。 但是,UNICODE 在制订时没有考虑与任何一种现有的编码方案保持兼容,这使得 GBK 与UNICODE 在汉字的内码编排上完全是不一样的,没有一种简单的算术方法可以把文本内容从UNICODE编码和另一种编码进行转换,这种转换必须通过查表来进行。 如前所述,UNICODE 是用两个字节来表示为一个字符,他总共可以组合出65535不同的字符,这大概已经可以覆盖世界上所有文化的符号。如果还不够也没有关系,ISO已经准备了UCS-4方案,说简单了就是四个字节来表示一个字符,这样我们就可以组合出21亿个不同的字符出来(最高位有其他用途),这大概可以用到银河联邦成立那一天吧! UNICODE 来到时,一起到来的还有计算机网络的兴起,UNICODE 如何在网络上传输也是一个必须考虑的问题,于是面向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF8就是每次8个位传输数据,而UTF16就是每次16个位,只不过为了传输时的可靠性,从UNICODE到UTF时并不是直接的对应,而是要过一些算法和规则来转换。 受到过网络编程加持的计算机僧侣们都知道,在网络里传递信息时有一个很重要的问题,就是对于数据高低位的解读方式,一些计算机是采用低位先发送的方法,例如我们PC机采用的 INTEL 架构,而另一些是采用高位先发送的方式,在网络中交换数据时,为了核对双方对于高低位的认识是否是一致的,采用了一种很简便的方法,就是在文本流的开始时向对方发送一个标志符——如果之后的文本是高位在位,那就发送"FEFF",反之,则发送"FFFE"。不信你可以用二进制方式打开一个UTF-X格式的文件,看看开头两个字节是不是这两个字节? 讲到这里,我们再顺便说说一个很著名的奇怪现象:当你在 windows 的记事本里新建一个文件,输入"联通"两个字之后,保存,关闭,然后再次打开,你会发现这两个字已经消失了,代之的是几个乱码!呵呵,有人说这就是联通之所以拼不过移动的原因。 其实这是因为GB2312编码与UTF8编码产生了编码冲撞的原因。 从网上引来一段从UNICODE到UTF8的转换规则: Unicode UTF-8 0000 - 007F 0xxxxxxx 0080 - 07FF 110xxxxx 10xxxxxx 0800 - FFFF 1110xxxx 10xxxxxx 10xxxxxx 例如"汉"字的Unicode编码是6C49。6C49在0800-FFFF之间,所以要用3字节模板:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 1100 0100 1001,将这个比特流按三字节模板的分段方法分为0110 110001 001001,依次代替模板中的x,得到:1110-0110 10-110001 10-001001,即E6 B1 89,这就是其UTF8的编码。 而当你新建一个文本文件时,记事本的编码默认是ANSI, 如果你在ANSI的编码输入汉字,那么他实际就是GB系列的编码方式,在这种编码下,"联通"的内码是: c1 1100 0001 aa 1010 1010 cd 1100 1101 a8 1010 1000 注意到了吗?第一二个字节、第三四个字节的起始部分的都是"110"和"10",正好与UTF8规则里的两字节模板是一致的,于是再次打开记事本时,记事本就误认为这是一个UTF8编码的文件,让我们把第一个字节的110和第二个字节的10去掉,我们就得到了"00001 101010",再把各位对齐,补上前导的0,就得到了"0000 0000 0110 1010",不好意思,这是UNICODE的006A,也就是小写的字母"j",而之后的两字节用UTF8解码之后是0368,这个字符什么也不是。这就是只有"联通"两个字的文件没有办法在记事本里正常显示的原因。 而如果你在"联通"之后多输入几个字,其他的字的编码不见得又恰好是110和10开始的字节,这样再次打开时,记事本就不会坚持这是一个utf8编码的文件,而会用ANSI的方式解读之,这时乱码又不出现了。 好了,终于可以回答NICO的问题了,在数据库里,有n前缀的字串类型就是UNICODE类型,这种类型中,固定用两个字节来表示一个字符,无论这个字符是汉字还是英文字母,或是别的什么。 如果你要测试"abc汉字"这个串的长度,在没有n前缀的数据类型里,这个字串是7个字符的长度,因为一个汉字相当于两个字符。而在有n前缀的数据类型里,同样的测试串长度的函数将会告诉你是5个字符,因为一个汉字就是一个字符。