??xml version="1.0" encoding="utf-8" standalone="yes"?>
对于d数据时Q如|络或文Ӟ要特别注意?/p>
比如
PBYTE pData;
//指向接收到的数据?/p>
int * pi = pData + q次接收到数据流的大?- 4Q?/p>
//指向接收到数据流的最??可能客户端发q来的这个数据流最?位是intQ那样就是对齐好的了。也有可能不是int?/p>
if *pi = 4561321 endparse data;//判断作ؓ(f)数据的l束标志Q最?位的前面可能是全是char的?/p>
׃q最?位可能不是intQ那样就有可能不寚wQ这里就?x)出现Datatype misalignment错误。但是我们还是必通过最?位作为结束标志,而只有知道什么时候结束才能用int指针指向最?位才不会(x)出现寚w错误。这样Ş成了一个死循环Q难道没办法解决了么Q?/p>
PBYTE pData;
PBYTE * pi = pData + q次接收到数据流的大?- 4Q?/p>
int i;
memcpy(&i, pi, 4);
if i = 4561321 endparse data;
q样可以解决用int* pi指向未对齐的int错误了?/p>
q一招对所有碰到Datatype misalignment的数据的讉K应该都有效,是把Datatype misalignment的数据用memcpy拯到对齐的内存来访问?/p>
1Q解析数据流时应该时L意。如果需要把一个数据流QBUFFERQ{化成l构q行取|应该把q个l构定义为按字节存取.考虑如下l构Q?/p>
struct a{
char a;
short b;
long c;
};
如果某个数据中包含q样的结构,而且我们要直接将数据的指针转化成该l构的指针,然后直接取结构成员的|我们应该将q个l构定义成按字节讉KQ即其夹在语句
#pragma pack(push,1)//设ؓ(f)1字节寚w
...
#pragma pack(pop)//q原为原来的字节寚w方式
之中。如果我们不q样做,~译器会(x)成员b的地址寚w到short指针的地址Q即在a之后加上一个char?位的成员Q将C寚w到LONGQ即在B之后再加一个char成员。如此一来,成员B和成员C得不到正确的g?/p>
pragma pack 只作用于l构的定义,而不是分配内存空间。把一个结构定义ؓ(f)pack1后,q个l构在程序中׃直是1了?/p>
上面q个例子是客L(fng)发?个连l的数据不是发送结构体Q服务端接收l构体?/p>
如果客户端也发送结构体Q服务端也接收结构体׃需要这样了。但是前提是双方的对齐方式一致。所以在客户端发送前也要?pragma pack()一下,服务端也?pragma pack()一下?/p>
最好还是客L(fng)也单个数据发送,服务端也单个数据接收?/p>
如果我们定义一个普通的l构用来存放一些数据,则不用定义成按字节存取,~译器会(x)加上一些占位成员,但ƈ不会(x)影响E序的运行。从q个意义上讲Q在ARM中,结构成员定义成CHAR和SHORT来节U内存是没有意义的?/p>
一个典型的例子文件系l的驱动E序Q文件是以一些已l定义好的结构存攑֜存储介质上的Q它们被dC个BUFFER中,而具体取某个文g、目录结构时Q我们会(x)地址转化成结构而读取其中的倹{?/p>
2Q访问外设时?br>例如Q磁盘驱动通常?6BIT的方式存取数据,xơ存取两个字节,q样p求传l它的BUFFER是双字节寚w的,驱动E序应该至上层传来的指针做出正确的处理以保证数据的正性?/p>
3.有时Q我们没有将数据指针{化ؓ(f)l构指针取|但如果我们读取的是双字节或者是四字节的数据Q同样需要注意对齐的问题Q例如,如果从一个BUFFER的偏U?0处读取一个四字节|则实际得到的值是偏移8处的
地址上的DWORD倹{?/p>
本文来自CSDN博客Q{载请标明出处Q?a >http://blog.csdn.net/lqk1985/archive/2008/10/23/3129842.aspx
最q笔者一直在做JPEG的解码工作,发现用完全用哈夫曼?wi)进行解码比较费Ӟ而用表l构存储~码和值的对应关系比较快捷Q但是也存在比较隑֤理的地方Q比如解码工作通常是以位ؓ(f)单位的操作,q里必然?x)涉及(qing)到UM操作Q而笔者之前对位的操作很少Q经验很Ơ缺Q经q这ơ历l终于发C一个自己曾l忽视的东西Q那是C/C++中的UM操作Ҏ(gu)出错的情c(din)?/p>
1、什么样的数据类型可以直接移?/strong>
char、short、int、long、unsigned char、unsigned short、unsigned int、unsigned long都可以进行移位操作,而double、float、bool、long double则不可以q行UM操作?/p>
2、有W号数据cd的移位操?/strong>
对于char、short、int、longq些有符L(fng)数据cdQ?/p>
3、无W号数据cd的移位操?/strong>
对于unsigned char、unsigned short、unsigned int、unsigned longq些无符h据类型:(x)
没有Ҏ(gu)要说明的Q?lt;< ?>> 操作W就O(jin)K?/p>
l束?/p>
8086 中存在逻辑UM、算术移位,而C\C++中的UMg既不是逻辑UMQ也不是术UM?/p>
比如-1Q我们若对它右移1位,C的结果仍旧是-1Q事实上无论右移多少位始l是-1Q逻辑UM得到的结果(8位表C)应该?128Q所以这点要注意
例子Q?br>1.?13800138000 转ؓ(f) 91 68 31 08 10 83 00 F0 //91? 68国家?不够位长以F?/strong>
二、命名规则:(x)
1、变量名的命名规?/strong>
①、变量的命名规则要求?#8220;匈牙利法?#8221;。即开头字母用变量的类型,其余部分用变量的英文意思或其英文意思的~写,量避免用中文的拼音,要求单词的第一个字母应大写?nbsp;
卻I(x) 变量?变量cd+变量的英文意思(或羃写)
寚w通用的变量,在定义时加入注释说明Q变量定义尽量可能放在函数的开始处?nbsp;
见下表:(x)
bool(BOOL) 用b开?nbsp; bIsParent
byte(BYTE) 用by开?nbsp; byFlag
short(int) 用n开?nbsp; nStepCount
long(LONG) 用l开?nbsp; lSum
char(CHAR) 用c开?nbsp; cCount
float(FLOAT) 用f开?nbsp; fAvg
double(DOUBLE) 用d开?nbsp; dDeta
void(VOID) 用v开?nbsp; vVariant
unsigned intQWORDQ?nbsp; 用w开?nbsp; wCount
unsigned long(DWORD) 用dw开?nbsp; dwBroad
HANDLEQHINSTANCEQ?nbsp; 用h开?nbsp; hHandle
DWORD 用dw开?nbsp; dwWord
LPCSTR(LPCTSTR) 用str开?nbsp; strString
?l尾的字W串 用sz开?nbsp; szFileName
Ҏ(gu)l出的变量类型要求提出ƈl出命名l技术委员会(x)?nbsp;
②、指针变量命名的基本原则为:(x)
对一重指针变量的基本原则为:(x)
“p”+变量cd前缀+命名
如一个float*型应该表CZؓ(f)pfStat
对多重指针变量的基本规则为:(x)
二重指针Q?nbsp; “pp”+变量cd前缀+命名
三重指针Q?nbsp; “ppp”+变量cd前缀+命名
......
③、全局变量用g_开?如一个全局的长型变量定义ؓ(f)g_lFailCount,卻I(x)变量?g_+变量cd+变量的英文意思(或羃写)
④、静态变量用s_开?如一个静态的指针变量定义?span style="COLOR: #008000">s_plPerv_Inst,卻I(x) 变量?s_+变量cd+变量的英文意思(或羃写)
⑤、成员变量用m_开?如一个长型成员变量定义ؓ(f)m_lCount;卻I(x)变量?m_+变量cd+变量的英文意思(或羃写)
⑥、对枚DcdQenumQ中的变量,要求用枚丑֏量或其羃写做前缀。ƈ且要求用大写?nbsp;
如:(x)enum cmEMDAYS
{
EMDAYS_MONDAY;
EMDAYS_TUESDAY;
……
};
⑦、对struct、union、class变量的命名要求定义的cd用大写。ƈ要加上前~Q其内部变量的命名规则与变量命名规则一致?nbsp;
l构一般用S开?nbsp;
如:(x)struct ScmNPoint
{
int nX;//点的X位置
int nY; //点的Y位置
};
联合体一般用U开?nbsp;
? union UcmLPoint
{
long lX;
long lY;
}
cM般用C开?nbsp;
如:(x)
class CcmFPoint
{
public:
float fPoint;
};
对一般的l构应该定义为类模板Qؓ(f)以后的扩展性考虑
如:(x)
template
class CcmTVector3d
{
public:
TYPE x,y,z;
};
⑧、对帔RQ包括错误的~码Q命名,要求帔R名用大写Q常量名用英文表辑օ意思?nbsp;
?span style="COLOR: #008000">Q?define CM_FILE_NOT_FOUND CMMAKEHR(0X20B) 其中CM表示cd?nbsp;
⑨、对const 的变量要求在变量的命名规则前加入c_,卻I(x)c_+变量命名规则Q例如:(x)
const char* c_szFileName;
2?nbsp; 函数的命名规范:(x)
函数的命名应该尽量用英文表达出函数完成的功能。遵循动宄构的命名法则Q函数名中动词在?q在命名前加入函数的前缀Q函数名的长度不得少?个字母?nbsp;
例如Q?nbsp;
long cmGetDeviceCount(……);
3、函数参数规范:(x)
①?nbsp; 参数名称的命名参照变量命名规范?nbsp;
②?nbsp; Z提高E序的运行效率,减少参数占用的堆栈,传递大l构的参敎ͼ一律采用指针或引用方式传递?nbsp;
③?nbsp; Z便于其他E序员识别某个指针参数是入口参数q是出口参数Q同时便于编译器查错误,应该在入口参数前加入const标志。如Q?nbsp;
……cmCopyString(const char * c_szSource, char * szDest)
4、引出函数规范:(x)
对于从动态库引出作ؓ(f)二次开发函数公开的函敎ͼZ能与其他函数以及(qing)Windows的函数区分,采用cd前缀+基本命名规则的方法命名。例如:(x)在对动态库中引出的一个图象编辑的函数定义?nbsp; imgFunctionname(其中img为image~写)?nbsp;
现给ZU库的命名前~Q?nbsp;
①?nbsp; 寚w用函数库,采用cm为前~?nbsp;
②?nbsp; 对三l函数库Q采用vr为前~?nbsp;
③?nbsp; 对图象函数库Q采用img为前~?/span>
对宏定义Q结果代码用同样的前~?nbsp;
5、文件名(包括动态库、组件、控件、工E文件等)的命名规范:(x)
文g名的命名要求表达出文件的内容Q要求文件名的长度不得少?个字母,严禁使用象file1,myfile之类的文件名?nbsp;
三、注释规范:(x)
1、函数头的注?/strong>
对于函数Q应该从“功能”Q?#8220;参数”Q?#8220;q回?#8221;?#8220;主要思\”?#8220;调用Ҏ(gu)”?#8220;日期”六个斚w用如下格式注释:(x)
//E序说明开?nbsp;
//================================================================//
// 功能Q?nbsp; 从一个String 中删除另一个String?nbsp;
// 参数Q?nbsp; strByDelete,strToDelete
// Q入口) strByDelete: 被删除的字符Ԍ原来的字W串Q?nbsp;
// Q出口) strToDelete: 要从上个字符串中删除的字W串?nbsp;
// q回Q?nbsp; 扑ֈq删除返?Q否则返?。(对返回值有错误~码的要// 求列出错误编码)?nbsp;
// 主要思\Q本法主要采用循环比较的方法来从strByDelete中找?nbsp;
// 与strToDelete相匹配的字符Ԍ对多匚wstrByDelete
// 中有多个strToDelete子串Q的情况没有处理。请参阅Q?nbsp;
// 书名......
// 调用Ҏ(gu)Q?.....
// 日期Qv始日期,如:(x)2000/8/21.9:40--2000/8/23.21:45
//================================================================//
函数?……)
//E序说明l束
①?nbsp; 对于某些函数Q其部分参数Z入|而部分参Cؓ(f)传出|所以对参数要详l说明该参数是入口参敎ͼq是出口参数Q对于某些意义不明确的参数还要做详细说明Q例如:(x)以角度作为参数时Q要说明该角度参数是以弧度(PIQ?q是以度为单位),Ҏ(gu)是入口又是出口的变量应该在入口和出口处同时标明。等{?nbsp;
②?nbsp; 函数的注释应该放|在函数的头文g中,在实现文件中的该函数的实现部分应该同时放|该注释?nbsp;
③?nbsp; 在注释中应该详细说明函数的主要实现思\、特别要注明自己的一些想法,如果有必要则应该写明Ҏ(gu)法生的来由。对一些模仿的函数应该注释上函数的出处?nbsp;
④?nbsp; 在注释中详细注明函数的适当调用Ҏ(gu)Q对于返回值的处理Ҏ(gu){。在注释中要调用时的危险斚wQ可能出错的地方?nbsp;
⑤?nbsp; Ҏ(gu)期的注释要求记录从开始写函数到结束函数的试之间的日期?nbsp;
⑥?nbsp; 对函数注释开始到函数命名之间应该有一l用来标识的Ҏ(gu)字符丌Ӏ?nbsp;
如果法比较复杂Q或法中的变量定义与位|有养I则要求对变量的定义进行图解。对难以理解的算法能图解量图解?nbsp;
2、变量的注释Q?/strong>
对于变量的注释紧跟在变量的后面说明变量的作用。原则上对于每个变量应该注释Q但对于意义非常明显的变量,如:(x)i,j{@环变量可以不注释?nbsp;
例如Q?nbsp; long lLineCount //U的Ҏ(gu)?nbsp;
3、文件的注释Q?/strong>
文g应该在文件开头加入以下注释:(x)
/////////////////////////////////////////////////////////////////////
// 工程: 文g所在的目名?nbsp;
// 作者:(x)**Q修改者:(x)**
// 描述:说明文g的功能?nbsp;
// 主要函数Q?#8230;………
// 版本: 说明文g的版本,完成日期?nbsp;
// 修改: 说明Ҏ(gu)件的修改内容、修改原因以?qing)修?gu)期?nbsp;
// 参考文献:(x) ......
/////////////////////////////////////////////////////////////////////
Z头文件被重复包含要求对头文gq行定义如下:
#ifndef __FILENAME_H__
#define __FILENAME_H__
其中FILENAME为头文g的名字?nbsp;
4、其他注释:(x)
在函数内我们不需要注释每一行语句。但必须在各功能模块的每一主要部分之前d块注释,注释每一l语句,在@环、流E的各分支等Q尽可能多加以注释?nbsp;
其中的@环、条件、选择{位|必L释?nbsp;
对于前后序不能颠倒的情况Q徏议在注释中增加序受?nbsp;
例如Q?nbsp;
在其他顺序执行的E序中,每隔3?行语句,必须加一个注释,注明q一D语句所l成的小模块的作用。对于自q一些比较独特的思想要求在注释中标明?nbsp;
四、程序健壮性:(x)
1、函数的q回D范:(x)
对于函数的返回位|,量保持单一性,即一个函数尽量做到只有一个返回位|?单入口单出口)?nbsp;
要求大家l一函数的返回|所有的函数的返回值都以~码的方式返回?nbsp;
例如~码定义如下Q?nbsp;
#define CM_POINT_IS_NULL CMMAKEHR(0X200)
:
:
函数实现如下Q?nbsp;
long 函数?参数,……)
{
long lResult; //保持错误?nbsp;
lResult=CM_OK;
//如果参数有错误则q回错误?nbsp;
if(参数==NULL)
{
lResult=CM_POINT_IS_NULL;
goto END;
}
……
END:
return lResult;
}
2、关于goto的应用:(x)
对goto语句的应用,我们要求量用goto语句。对一定要用的地方要求只能向后转移?nbsp;
3、资源变量的处理Q资源变量是指消耗系l资源的变量Q:(x)
对资源变量一定赋初倹{分配的资源在用完后必须马上释放Qƈ重新赋倹{?nbsp;
4、对复杂的条件判断,ZE序的可L,应该量使用括号?nbsp;
例:(x)if(((szFileName!=NULL)&&(lCount>=0)))||(bIsRead==TRUE))
五、可UL性:(x)
1、高质量的代码要求能够跨q_Q所以我们的代码应该考虑到对不同的^台的支持Q特别是对windows98和windowsnt的支持?nbsp;
2、由于C语言的移植性比较好Q所以对法函数要求用C代码Q不能用C++代码?nbsp;
3、对不同的硬件与软g的函数要做不同的处理
匈牙利命名法
MFC、句柄、控件及(qing)l构的命名规? Windowscd h变量 MFCc? h变量
HWND hWndQ? CWnd* pWndQ?
HDLG hDlgQ? CDialog* pDlgQ?
HDC hDCQ? CDC* pDCQ?
HGDIOBJ hGdiObjQ? CGdiObject* pGdiObjQ?
HPEN hPenQ? CPen* pPenQ?
HBRUSH hBrushQ? CBrush* pBrushQ?
HFONT hFontQ? CFont* pFontQ?
HBITMAP hBitmapQ? CBitmap* pBitmapQ?
HPALETTE hPaltteQ? CPalette* pPaletteQ?
HRGN hRgnQ? CRgn* pRgnQ?
HMENU hMenuQ? CMenu* pMenuQ?
HWND hCtlQ? CState* pStateQ?
HWND hCtlQ? CButton* pButtonQ?
HWND hCtlQ? CEdit* pEditQ?
HWND hCtlQ? CListBox* pListBoxQ?
HWND hCtlQ? CComboBox* pComboBoxQ?
HWND hCtlQ? CScrollBar* pScrollBarQ?
HSZ hszStrQ? CString pStrQ?
POINT ptQ? CPoint ptQ?
SIZE sizeQ? CSize sizeQ?
RECT rectQ? CRect rectQ?
一般前~命名规范 前缀 cd 实例
C cLl构 CDocumentQCPrintInfo
m_ 成员变量 m_pDocQm_nCustomers
变量命名规范 前缀 cd 描述 实例
ch char 8位字W? chGrade
ch TCHAR 如果_UNICODE定义Q则?6位字W? chName
b BOOL 布尔? bEnable
n int 整型Q其大小依赖于操作系l) nLength
n UINT 无符号|其大依赖于操作pȝQ? nHeight
w WORD 16位无W号? wPos
l LONG 32位有W号整型 lOffset
dw DWORD 32位无W号整型 dwRange
p * 指针 pDoc
lp FAR* q指? lpszName
lpsz LPSTR 32位字W串指针 lpszName
lpsz LPCSTR 32位常量字W串指针 lpszName
lpsz LPCTSTR 如果_UNICODE定义Q则?2位常量字W串指针 lpszName
h handle Windows对象句柄 hWnd
lpfn callback 指向CALLBACK函数的远指针
应用E序W号命名规范 前缀 W号cd 实例 范围
IDR_ 不同cd的多个资源共享标? IDR_MAIINFRAME 1?x6FFF
IDD_ 对话框资? IDD_SPELL_CHECK 1?x6FFF
HIDD_ 对话框资源的Help上下? HIDD_SPELL_CHECK 0x20001?x26FF
IDB_ 位图资源 IDB_COMPANY_LOGO 1?x6FFF
IDC_ 光标资源 IDC_PENCIL 1?x6FFF
IDI_ 图标资源 IDI_NOTEPAD 1?x6FFF
ID_ 来自菜单Ҏ(gu)工具栏的命o(h) ID_TOOLS_SPELLING 0x8000?xDFFF
HID_ 命o(h)Help上下? HID_TOOLS_SPELLING 0x18000?x1DFFF
IDP_ 消息框提C? IDP_INVALID_PARTNO 8?xDEEF
HIDP_ 消息框Help上下? HIDP_INVALID_PARTNO 0x30008?x3DEFF
IDS_ 串资? IDS_COPYRIGHT 1?x7EEF
IDC_ 对话框内的控? IDC_RECALC 8?xDEEF
Microsoft MFC宏命名规? 名称 cd
_AFXDLL 唯一的动态连接库QDynamic Link LibraryQDLLQ版?
_ALPHA 仅编译DEC Alpha处理?
_DEBUG 包括诊断的调试版?
_MBCS ~译多字节字W集
_UNICODE 在一个应用程序中打开Unicode
AFXAPI MFC提供的函?
CALLBACK 通过指针回调的函?
库标识符命名? 标识W? 值和含义
u ANSIQNQ或UnicodeQUQ?
d 调试或发行:(x)D = 调试Q忽略标识符为发行?
静态库版本命名规范 ? 描述
NAFXCWD.LIB 调试版本QMFC静态连接库
NAFXCW.LIB 发行版本QMFC静态连接库
UAFXCWD.LIB 调试版本Q具有Unicode支持的MFC静态连接库
UAFXCW.LIB 发行版本Q具有Unicode支持的MFC静态连接库
动态连接库命名规范 名称 cd
_AFXDLL 唯一的动态连接库QDLLQ版?
WINAPI Windows所提供的函?
Windows.h中新的命名规? cd 定义描述
WINAPI 使用在API声明中的FAR PASCAL位置Q如果正在编写一个具有导出API人口点的DLLQ则可以在自qAPI中用该cd
CALLBACK 使用在应用程序回叫例E,如窗口和对话框过E中的FAR PASCAL的位|?
LPCSTR 与LPSTR相同Q只是LPCSTR用于只读串指针,其定义类|const char FAR*Q?
UINT 可移植的无符h型类型,其大由L环境军_Q对于Windows NT和W(xu)indows 9x?2位)Q它是unsigned int的同义词
LRESULT H口E序q回值的cd
LPARAM 声明lParam所使用的类型,lParam是窗口程序的W四个参?
WPARAM 声明wParam所使用的类型,wParam是窗口程序的W三个参?
LPVOID 一般指针类型,与(void *Q相同,可以用来代替LPSTR
时常在cpp的代码之中看到这L(fng)代码:
#ifdef __cplusplus
extern "C" {
#endif
//一D代?/p>
#ifdef __cplusplus
}
#endif
q样的代码到底是什么意思呢Q首先,__cplusplus是cpp中的自定义宏Q那么定义了q个宏的话表C是一Dcpp的代码,也就是说Q上面的代码的含义是:如果q是一Dcpp的代码,那么加入extern "C"{和}处理其中的代?/span>?/p>
要明白ؓ(f)何用extern "C"Q还得从cpp中对函数的重载处理开始说赗在c++中,Z支持重蝲机制Q在~译生成的汇~码中,要对函数的名字进行一些处理,加入比如函数的返回类型等{?而在C中,只是单的函数名字而已Q不?x)加入其他的信?也就是说:C++和C对生的函数名字的处理是不一L(fng). 比如下面的一D늮单的函数Q我们看看加入和不加入extern "C"产生的汇~代码都有哪些变? int f(void) { return 1; } 在加入extern "C"的时候生的汇编代码? .file "test.cxx" .text .align 2 .globl _f .def _f; .scl 2; .type 32; .endef _f: pushl %ebp movl %espQ?%ebp movl $1Q?%eax popl %ebp ret 但是不加入了extern "C"之后 .file "test.cxx" .text .align 2 .globl __Z1fv .def __Z1fv; .scl 2; .type 32; .endef __Z1fv: pushl %ebp movl %espQ?%ebp movl $1Q?%eax popl %ebp ret 两段汇编代码同样都是使用gcc -S命o(h)产生的,所有的地方都是一L(fng)Q唯独是产生的函数名Q一个是_fQ一个是__Z1fv?/p>
明白了加入与不加入extern "C"之后对函数名UC生的影响Q我们l我们的讨论:Z么需要用extern "C"呢?C++之父在设计C++之时Q考虑到当时已l存在了大量的C代码Qؓ(f)了支持原来的C代码和已l写好C库,需要在C++中尽可能的支持CQ而extern "C"是其中的一个策略?/p>
试想q样的情?一个库文g已经用C写好了而且q行得很良好Q这个时候我们需要用这个库文gQ但是我们需要用C++来写q个新的代码。如果这个代码用的是C++的方式链接这个C库文件的话,那么׃(x)出现链接错误.我们来看一D代?首先Q我们用C的处理方式来写一个函敎ͼ也就是说假设q个函数当时是用C写成? //f1.c extern "C" { void f1() { return; } } ~译命o(h)?gcc -c f1.c -o f1.o 产生了一个叫f1.o的库文g。再写一D代码调用这个f1函数: // test.cxx //q个extern表示f1函数在别的地方定义,q样可以通过 //~译Q但是链接的时候还是需?/p>
//链接上原来的库文? extern void f1(); int main() { f1(); return 0; } 通过gcc -c test.cxx -o test.o 产生一个叫test.o的文件。然后,我们使用gcc test.o f1.o来链接两个文Ӟ可是出错了,错误的提C是: test.o(.text + 0x1f):test.cxx: undefine reference to 'f1()' 也就是说Q在~译test.cxx的时候编译器是用C++的方式来处理f1()函数的,但是实际上链接的库文件却是用C的方式来处理函数的,所以就?x)出现链接过不去的错?因ؓ(f)链接器找不到函数?/p>
因此Q?span style="COLOR: #ff0000">Z在C++代码中调用用C写成的库文gQ就需要用extern "C"来告诉编译器:q是一个用C写成的库文gQ请用C的方式来链接它们?/span> 比如Q现在我们有了一个C库文Ӟ它的头文件是f.hQ生的lib文g是f.libQ那么我们如果要在C++中用这个库文gQ我们需要这样写: extern "C" { #include "f.h" } 回到上面的问题,如果要改正链接错误,我们需要这样子改写test.cxx: extern "C" { extern void f1(); } int main() { f1(); return 0; } 重新~译q且链接可以过M. ȝ C和C++对函数的处理方式是不同的.extern "C"是C++能够调用C写作的库文g的一个手D,如果要对~译器提CZ用C的方式来处理函数的话Q那么就要用extern "C"来说?/span>?/p>
1.引言 C++语言的创建初h“a better C”Q但是这q不意味着C++中类似C语言的全局变量和函数所采用的编译和q接方式与C语言完全相同。作ZU欲与C兼容的语aQC++保留了一部分q程 式语a的特点(被世人称?#8220;不彻底地面向对象”Q,因而它可以定义不属于Q何类的全局变量和函数。但是,C++毕竟是一U面向对象的E序设计语言Qؓ(f)了支持函数的重蝲QC++对全局函数的处理方式与C有明昄不同?/p>
2.从标准头文g说v 某企业曾l给出如下的一道面试题Q?/p>
面试?/p>
Z么标准头文g都有cM以下的结构? #ifndef __INCvxWorksh #define __INCvxWorksh #ifdef __cplusplus extern "C" { #endif /*...*/ #ifdef __cplusplus } #endif #endif /* __INCvxWorksh */ 分析 昄Q头文g中的~译?#8220;#ifndef __INCvxWorksh?define __INCvxWorksh?endif” 的作用是防止该头文g被重复引?/span>?/p>
那么 #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif 的作用又是什么呢Q我们将在下文一一道来?/p>
3.深层揭密extern "C" extern "C" 包含双重含义Q从字面上即可得刎ͼ(x)首先Q被它修饰的目标?#8220;extern”的;其次Q被它修饰的目标?#8220;C”的。让我们来详l解读这两重含义?/p>
Q?Q?nbsp; 被extern "C"限定的函数或变量是externcd?/span>Q?/p>
extern是C/C++语言中表明函数和全局变量作用范围Q可见性)的关键字Q该关键字告诉编译器Q其声明的函数和变量可以在本模块或其它模块中使用。记住,下列语句Q?/p>
extern int a; 仅仅是一个变量的声明Q其q不是在定义变量aQƈ未ؓ(f)a分配内存I间。变量a在所有模块中作ؓ(f)一U全局变量只能被定义一ơ,否则?x)出现连接错误?/p>
通常Q在模块的头文g中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块BƲ引用该模块A中定义的全局变量?函数时只需包含模块A的头文g卛_。这P模块B中调用模块A中的函数Ӟ在编译阶D,模块B虽然找不到该函数Q但是ƈ不会(x)报错Q它?x)在q接阶段中从模块 A~译生成的目标代码中扑ֈ此函数?/p>
与extern对应的关键字是staticQ被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块用时Q其不可能被extern “C”修饰?/p>
Q?Q?nbsp; 被extern "C"修饰的变量和函数是按照C语言方式~译和连接的Q?/p>
未加extern “C”声明时的~译方式 首先看看C++中对cMC的函数是怎样~译的?/p>
作ؓ(f)一U面向对象的语言QC++支持函数重蝲Q而过E式语言C则不支持。函数被C++~译后在W号库中的名字与C语言的不同。例如,假设某个函数的原型ؓ(f)Q?/p>
void foo( int x, int y ); 该函数被C~译器编译后在符号库中的名字?span style="COLOR: #008000">_foo 同样圎ͼC++中的变量除支持局部变量外Q还支持cL员变量和全局变量。用h~写E序的类成员变量可能与全局变量同名Q我们以"."来区分。而本 质上Q编译器在进行编译时Q与函数的处理相|也ؓ(f)cM的变量取了一个独一无二的名字,q个名字与用L(fng)序中同名的全局变量名字不同?/p>
未加extern "C"声明时的q接方式Q?/span> 假设在C++中,模块A的头文g如下Q?/p>
// 模块A头文件 moduleA.h #ifndef MODULE_A_H #define MODULE_A_H int foo( int x, int y ); #endif 在模块B中引用该函数Q?/p>
// 模块B实现文g moduleB.cpp Qi nclude "moduleA.h" foo(2,3); 实际上,在连接阶D,q接器会(x)从模块A生成的目标文件moduleA.obj中寻找_foo_int_intq样的符P 加extern "C"声明后的~译和连接方式:(x) 加extern "C"声明后,模块A的头文g变ؓ(f)Q?/p>
// 模块A头文件 moduleA.h #ifndef MODULE_A_H #define MODULE_A_H extern "C" int foo( int x, int y ); #endif 在模块B的实现文件中仍然调用foo( 2,3 )Q其l果是:(x) Q?Q模块A~译生成foo的目标代码时Q没有对其名字进行特D处理,采用了C语言的方式; Q?Q连接器在ؓ(f)模块B的目标代码寻找foo(2,3)调用ӞL的是未经修改的符号名_foo?/p>
如果在模块A中函数声明了foo为extern "C"cdQ而模块B中包含的是extern int foo( int x, int y ) Q则模块B找不到模块A中的函数Q反之亦然?/p>
所以,可以用一句话概括extern “C”q个声明的真实目的(M语言中的M语法Ҏ(gu)的诞生都不是随意而ؓ(f)的,来源于真实世界的需求驱动。我们在思考问题时Q不能只停留在这个语a是怎么做的Q还要问一问它Z么要q么做,动机是什么,q样我们可以更深入地理解许多问题Q:(x) 实现C++与C?qing)其它语a的合编E?/p>
明白了C++中extern "C"的设立动机,我们下面来具体分析extern "C"通常的用技巧?/p>
4.extern "C"的惯用法 Q?Q在C++中引用C语言中的函数和变量,在包含C语言头文Ӟ假设为cExample.hQ时Q需q行下列处理Q?/p>
extern "C" { Qi nclude "cExample.h" } 而在C语言的头文g中,对其外部函数只能指定为externcdQC语言中不支持extern "C"声明Q在.c文g中包含了extern "C"时会(x)出现~译语法错误?/p>
W者编写的C++引用C函数例子工程中包含的三个文g的源代码如下Q?/p>
/* c语言头文Ӟ(x)cExample.h */ #ifndef C_EXAMPLE_H #define C_EXAMPLE_H extern int add(int x,int y); #endif /* c语言实现文gQcExample.c */ Qi nclude "cExample.h" int add( int x, int y ) { return x + y; } // c++实现文gQ调用addQcppFile.cpp extern "C" { Qi nclude "cExample.h" } int main(int argc, char* argv[]) { add(2,3); return 0; } 如果C++调用一个C语言~写?DLLӞ当包?DLL的头文g或声明接口函数时Q应加extern "C" { }?/p>
Q?Q在C中引用C++语言中的函数和变量时QC++的头文g需dextern "C"Q但是在C语言中不能直接引用声明了extern "C"的该头文Ӟ应该仅将C文g中将C++中定义的extern "C"函数声明为externcd?span class=Apple-style-span style="WORD-SPACING: 0px; FONT: medium Simsun; TEXT-TRANSFORM: none; COLOR: #000000; TEXT-INDENT: 0px; WHITE-SPACE: normal; LETTER-SPACING: normal; BORDER-COLLAPSE: separate; orphans: 2; widows: 2">
W者编写的C引用C++函数例子工程中包含的三个文g的源代码如下Q?/p>
//C++头文?cppExample.h #ifndef CPP_EXAMPLE_H #define CPP_EXAMPLE_H extern "C" int add( int x, int y ); #endif //C++实现文g cppExample.cpp Qi nclude "cppExample.h" int add( int x, int y ) { return x + y; } /* C实现文g cFile.c /* q样?x)编译出错?x)Qi nclude "cExample.h" */ extern int add( int x, int y ); int main( int argc, char* argv[] ) { add( 2, 3 ); return 0; }
容程序是?x)崩溃的Q而后者完全正?br>E序演示Q?br>试环境Devc++
代码
q行l果
2293628 4199056 abc
2293624 2293624 abc
2293620 4199056 abc
#include <iostream>
using namespace std;
main()
{
char *c1 = "abc";
char c2[] = "abc";
char *c3 = ( char* )malloc(3);
c3 = "abc";
printf("%d %d %s\n",&c1,c1,c1);
printf("%d %d %s\n",&c2,c2,c2);
printf("%d %d %s\n",&c3,c3,c3);
getchar();
}
参考资料:(x)
首先要搞清楚~译E序占用的内存的分区形式Q?br>一、预备知?E序的内存分?br>一个由c/C++~译的程序占用的内存分ؓ(f)以下几个部分
1、栈区(stackQ?q译器自动分配释放Q存攑և数的参数|局部变量的值等。其操作方式cM?/span>
数据l构中的栈?br>2、堆区(heapQ?一般由E序员分配释放,若程序员不释放,E序l束时可能由O(jin)S回收。注意它与数?/span>
l构中的堆是两回事,分配方式倒是cM于链表,呵呵?br>3、全局区(静态区Q(staticQ?全局变量和静态变量的存储是放在一块的Q初始化的全局变量和静?/span>
变量在一块区域,未初始化的全局变量和未初始化的静态变量在盔R的另一块区域。程序结束后ql?/span>
释放?br>4、文字常量区-帔R字符串就是放在这里的。程序结束后ql释放?br>5、程序代码区
q是一个前辈写的,非常详细
//main.cpp
int a=0; //全局初始化区
char *p1; //全局未初始化?br>main()
{
int b;?br>char s[]="abc"; //?br>char *p2; //?br>char *p3="123456"; //123456\0在常量区Qp3在栈上?br>static int c=0Q?//全局Q静态)初始化区
p1 = (char*)malloc(10);
p2 = (char*)malloc(20); //分配得来?0?0字节的区域就在堆区?br>strcpy(p1,"123456"); //123456\0攑֜帔R区,~译器可能会(x)它与p3所?123456"优化成一?/span>地方?br>}
二、堆和栈的理论知?br>2.1甌方式
stack:
ql自动分配。例如,声明在函C一个局部变量int b;pȝ自动在栈中ؓ(f)b开辟空?br>heap:
需要程序员自己甌Qƈ指明大小Q在c中malloc函数
如p1=(char*)malloc(10);
在C++中用newq算W?br>如p2=(char*)malloc(10);
但是注意p1、p2本n是在栈中的?br>2.2
甌后系l的响应
栈:(x)只要栈的剩余I间大于所甌I间Q系l将为程序提供内存,否则报异常提示栈溢出?br>堆:(x)首先应该知道操作pȝ有一个记录空闲内存地址的链表,当系l收到程序的甌Ӟ
?x)遍历该链表Q寻扄一个空间大于所甌I间的堆l点Q然后将该结点从I闲l点链表中删除,q将
该结点的I间分配l程序,另外Q对于大多数pȝQ会(x)在这块内存空间中的首地址处记录本ơ分配的?/span>
,q样Q代码中的delete语句才能正确的释放本内存I间。另外,׃扑ֈ的堆l点的大不一定正
好等于申L(fng)大小Q系l会(x)自动的将多余的那部分重新攑օI闲链表中?br>2.3甌大小的限?br>栈:(x)在Windows?栈是向低地址扩展的数据结构,是一块连l的内存的区域。这句话的意思是栈顶的地
址和栈的最大容量是pȝ预先规定好的Q在WINDOWS下,栈的大小?MQ也有的说是1MQM是一个编?/span>
时就定的常敎ͼQ如果申L(fng)I间过栈的剩余I间Ӟ提Coverflow。因此,能从栈获得的I间
较小?br>堆:(x)堆是向高地址扩展的数据结构,是不q箋的内存区域。这是由于系l是用链表来存储的空闲内存地
址的,自然是不q箋的,而链表的遍历方向是由低地址向高地址。堆的大受限于计算机系l中有效?/span>
虚拟内存。由此可见,堆获得的I间比较灉|Q也比较大?br>2.4甌效率的比较:(x)
?ql自动分配,速度较快。但E序员是无法控制的?br>?是由new分配的内存,一般速度比较慢,而且Ҏ(gu)产生内存片,不过用v来最方便.
另外Q在WINDOWS下,最好的方式是用Virtual Alloc分配内存Q他不是在堆Q也不是在栈,而是直接在进
E的地址I间中保留一块内存,虽然用v来最不方ѝ但是速度快,也最灉|?br>2.5堆和栈中的存储内?br>栈:(x)在函数调用时Q第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句Q的
地址Q然后是函数的各个参敎ͼ在大多数的C~译器中Q参数是由右往左入栈的Q然后是函数中的局部变
量。注意静态变量是不入栈的?br>当本ơ函数调用结束后Q局部变量先出栈Q然后是参数Q最后栈指针指向最开始存的地址Q也是?/span>
函数中的下一条指令,E序p点l运行?br>堆:(x)一般是在堆的头部用一个字节存攑֠的大。堆中的具体内容q序员安排?br>2.6存取效率的比?br>char s1[]="aaaaaaaaaaaaaaa";
char *s2="bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在q行时刻赋值的Q?br>而bbbbbbbbbbb是在~译时就定的;
但是Q在以后的存取中Q在栈上的数l比指针所指向的字W串(例如?快?br>比如Q?br>#include
voidmain()
{
char a=1;
char c[]="1234567890";
char *p="1234567890";
a = c[1];
a = p[1];
return;
}
对应的汇~代?br>10:a=c[1];
004010678A4DF1movcl,byteptr[ebp-0Fh]
0040106A884DFCmovbyteptr[ebp-4],cl
11:a=p[1];
0040106D8B55ECmovedx,dwordptr[ebp-14h]
004010708A4201moval,byteptr[edx+1]
004010738845FCmovbyteptr[ebp-4],al
W一U在d时直接就把字W串中的元素d寄存器cl中,而第二种则要先把指针D到edx中,在根?/span>
edxd字符Q显然慢了?br>2.7结Q?br>堆和栈的区别可以用如下的比喻来看出:(x)
使用栈就象我们去饭馆里吃饭,只管点菜Q发出申P、付钱、和吃(使用Q,吃饱了就赎ͼ不必理会(x)
切菜、洗菜等准备工作和洗、刷锅等扫尾工作Q他的好处是快捷Q但是自由度?br>使用堆就象是自己动手做喜Ƣ吃的菜_(d)比较ȝQ但是比较符合自q口味Q而且自由度大?/span>
自我ȝQ?br>char *c1 = "abc";实际上先是在文字帔R区分配了一块内存放"abc",然后在栈上分配一地址lc1q指?/span>
q块地址Q然后改变常?abc"自然?x)崩?/span>
然而char c2[] = "abc",实际上abc分配内存的地方和上者ƈ不一P可以?br>4199056
2293624 看出Q完全是两块地方Q推?199056处于帔R区,?293624处于栈区
2293628
2293624
2293620 q段输出看出三个指针分配的区域ؓ(f)栈区Q而且是从高地址C地址
2293620 4199056 abc 看出~译器将c3优化指向帔R区的"abc"
l箋思考:(x)
代码Q?br>
输出Q?br>2293628 4199056 abc
2293624 2293624 abc
2293620 4012976 gbc
写成注释那样Q后面改动就?x)崩?br>可见strcpy(c3,"abc");abc是另一块地方分配的Q而且可以改变Q和上面的参考文说法有些不一定,
#include <iostream>
using namespace std;
main()
{
char *c1 = "abc";
char c2[] = "abc";
char *c3 = ( char* )malloc(3);
// *c3 = "abc" //error
strcpy(c3,"abc");
c3[0] = 'g';
printf("%d %d %s\n",&c1,c1,c1);
printf("%d %d %s\n",&c2,c2,c2);
printf("%d %d %s\n",&c3,c3,c3);
getchar();
}
1)函数参数的压栈顺序,2)p用者还是被调用者把参数弹出栈,3)以及(qing)产生函数修饰名的Ҏ(gu)?/span>
1?/span>__stdcall调用U定Q函数的参数自右向左通过栈传递,被调用的函数在返回前清理传送参数的内存栈,
2?/span>_cdecl?/span>C?/span>CQ+E序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以生的可执行文件大会(x)比调?/span>_stdcall函数的大。函数采用从叛_左的压栈方式。注意:(x)对于可变参数的成员函敎ͼ始终使用__cdecl的{换方式?/span>
3?/span>__fastcall调用U定Q它是通过寄存器来传送参数的Q实际上Q它?/span>ECX?/span>EDX传送前两个双字Q?/span>DWORDQ或更小的参敎ͼ剩下的参C旧自叛_左压栈传送,被调用的函数在返回前清理传送参数的内存栈)?/span>
4?/span>thiscall仅仅应用?/span>"C++"成员函数?/span>this指针存放?/span>CX寄存器,参数从右到左压?/span>thiscall不是关键词,因此不能被程序员指定?/span>
5?/span>naked call采用1-4的调用约定时Q如果必要的话,q入函数时编译器?x)生代码来保?/span>ESIQ?/span>EDIQ?/span>EBXQ?/span>EBP寄存器,退出函数时则生代码恢复这些寄存器的内宏V?/span>naked call不生这L(fng)代码?/span>naked call不是cd修饰W,故必d_declspec共同使用?/span>
调用U定可以通过工程讄Q?/span>Setting...\C/C++ \Code Generation进行选择Q缺省状态ؓ(f)__cdecl?/span>
名字修饰U定Q?/span>
1、修饰名(Decoration name)Q?/span>"C"或?/span>"C++"函数在内部(~译和链接)通过修饰名识?/span>
2?/span>C~译时函数名修饰U定规则Q?/span>
__stdcall调用U定在输出函数名前加上一个下划线前缀Q后面加上一?/span>"@"W号和其参数的字节数Q格式ؓ(f)_functionname@number,例如Q?/span>function(int a, int b)Q其修饰名ؓ(f)Q?/span>_function@8
__cdecl调用U定仅在输出函数名前加上一个下划线前缀Q格式ؓ(f)_functionname?/span>
__fastcall调用U定在输出函数名前加上一?/span>"@"W号Q后面也是一?/span>"@"W号和其参数的字节数Q格式ؓ(f)@functionname@number?/span>
3?/span>C++~译时函数名修饰U定规则Q?/span>
__stdcall调用U定Q?/span>
1)、以"?"标识函数名的开始,后跟函数名;
2)、函数名后面?/span>"@@YG"标识参数表的开始,后跟参数表;
3)、参数表以代可C:(x)
X--void Q?/span>
D--charQ?/span>
E--unsigned charQ?/span>
F--shortQ?/span>
H--intQ?/span>
I--unsigned intQ?/span>
J--longQ?/span>
K--unsigned longQ?/span>
M--floatQ?/span>
N--doubleQ?/span>
_N--boolQ?/span>
PA--表示指针Q后面的代号表明指针cdQ如果相同类型的指针q箋出现Q以"0"代替Q一?/span>"0"代表一ơ重复;
4)、参数表的第一ؓ(f)该函数的q回值类型,其后依次为参数的数据cd,指针标识在其所指数据类型前Q?/span>
5)、参数表后以"@Z"标识整个名字的结束,如果该函数无参数Q则?/span>"Z"标识l束?/span>
其格式ؓ(f)"?functionname@@YG*****@Z"?/span>"?functionname@@YG*XZ"Q例?/span>
int Test1(char *var1,unsigned long)----"?Test1@@YGHPADK@Z"
void Test2()-----“?Test2@@YGXXZ”
__cdecl调用U定Q?/span>
规则同上面的_stdcall调用U定Q只是参数表的开始标识由上面?/span>"@@YG"变ؓ(f)"@@YA"?/span>
__fastcall调用U定Q?/span>
规则同上面的_stdcall调用U定Q只是参数表的开始标识由上面?/span>"@@YG"变ؓ(f)"@@YI"?/span>
VC++对函数的省缺声明?/span>"__cedcl",只能被C/C++调用.
注意Q?/span>
1?/span>_beginthread需?/span>__cdecl的线E函数地址Q?/span>_beginthreadex?/span>CreateThread需?/span>__stdcall的线E函数地址?/span>
2、一?/span>WIN32的函数都?/span>__stdcall。而且?/span>Windef.h中有如下的定义:(x)
#define CALLBACK __stdcall
#define WINAPI __stdcall
3?/span>extern "C" _declspec(dllexport) int __cdecl Add(int a, int b);
typedef int (__cdecl*FunPointer)(int a, int b);
修饰W的书写序如上?/span>
4?/span>extern "C"的作用:(x)如果Add(int a, int b)是在c语言~译器编译,而在c++文g使用Q则需要在c++文g中声明:(x)extern "C" Add(int a, int b)Q因?/span>c~译器和c++~译器对函数名的解释不一Pc++~译器解释函数名的时候要考虑函数参数Q这h了方便函数重载,而在c语言中不存在函数重蝲的问题)Q?/span>extern "C"Q实质就是告?/span>c++~译器,该函数是c库里面的函数。如果不使用extern "C"则会(x)出现链接错误?/span>
一般象如下使用Q?/span>
#ifdef _cplusplus
#define EXTERN_C extern "C"
#else
#define EXTERN_C extern
#endif
#ifdef _cplusplus
extern "C"{
#endif
EXTERN_C int func(int a, int b);
#ifdef _cplusplus
}
#endif
5?/span>MFC提供了一些宏Q可以?/span>AFX_EXT_CLASS来代?/span>__declspec(DLLexport)Qƈ修饰cdQ从而导出类Q?/span>AFX_API_EXPORT来修饰函敎ͼAFX_DATA_EXPORT来修饰变?/span>
AFX_CLASS_IMPORTQ?/span>__declspec(DLLexport)
AFX_API_IMPORTQ?/span>__declspec(DLLexport)
AFX_DATA_IMPORTQ?/span>__declspec(DLLexport)
AFX_CLASS_EXPORTQ?/span>__declspec(DLLexport)
AFX_API_EXPORTQ?/span>__declspec(DLLexport)
AFX_DATA_EXPORTQ?/span>__declspec(DLLexport)
AFX_EXT_CLASSQ?/span>#ifdef _AFXEXT
AFX_CLASS_EXPORT
#else
AFX_CLASS_IMPORT
6?/span>DLLMain负责初始?/span>(Initialization)和结?/span>(Termination)工作Q每当一个新的进E或者该q程的新的线E访?/span>DLLӞ或者访?/span>DLL的每一个进E或者线E不再?/span>DLL或者结束时Q都?x)调?/span>DLLMain。但是,使用TerminateProcess?/span>TerminateThreadl束q程或者线E,不会(x)调用DLLMain?/span>
7、一?/span>DLL在内存中只有一个实?/span>
DLLE序和调用其输出函数的程序的关系Q?/span>
1)?/span>DLL与进E、线E之间的关系
DLL模块被映到调用它的q程的虚拟地址I间?/span>
DLL使用的内存从调用q程的虚拟地址I间分配Q只能被该进E的U程所讉K?/span>
DLL的句柄可以被调用q程使用Q调用进E的句柄可以?/span>DLL使用?/span>
DLLDLL可以有自q数据D,但没有自q堆栈Q用调用进E的栈,与调用它的应用程序相同的堆栈模式?/span>
2)、关于共享数据段
DLL定义的全局变量可以被调用进E访问;DLL可以讉K调用q程的全局数据。用同一DLL的每一个进E都有自qDLL全局变量实例。如果多个线Eƈ发访问同一变量Q则需要用同步机Ӟ对一?/span>DLL的变量,如果希望每个使用DLL的线E都有自q|则应该用线E局部存?/span>(TLSQ?/span>Thread Local Strorage)?/span>
Visual C++ Compiler Options可以指定?span>Calling
Convention?/span> 3U:(x)
在带参数的构造函CQ初始化列表的初始化变量序是根据成员变量的声明序来执行的Q因此m_i?x)?br>赋予一个随机|输出?br>-858993460
98
如果在声明变量ؓ(f)
int m_j;
static 声明的变量在C语言中有两方面的特征Q?br> 1)、变量会(x)被放在程序的全局存储ZQ这样可以在下一ơ调用的时候还可以保持原来的赋倹{这一Ҏ(gu)它与堆栈变量和堆变量的区别?br> 2)、变量用static告知~译器,自己仅仅在变量的作用范围内可见。这一Ҏ(gu)它与全局变量的区别?br>Tips:
A.若全局变量仅在单个C文g中访问,则可以将q个变量修改为静态全局变量Q以降低模块间的耦合度;
B.若全局变量仅由单个函数讉KQ则可以这个变量改函数的静态局部变量,以降低模块间的耦合度;
C.设计和用访问动态全局变量、静态全局变量、静态局部变量的函数Ӟ需要考虑重入问题Q?br> D.如果我们需要一个可重入的函敎ͼ那么Q我们一定要避免函数中用static变量(q样的函数被UCؓ(f)Q带“内部存储?#8221;功能的的函数)
E.函数中必要使用static变量情况:比如当某函数的返回gؓ(f)指针cdӞ则必Lstatic的局部变量的地址作ؓ(f)q回|若ؓ(f)autocdQ则q回为错指针?o:p>
函数前加static使得函数成ؓ(f)静态函数。但此处“static”的含义不是指存储方式Q而是指对函数的作用域仅局限于本文?所以又U内部函?。用内部函数的好处是:(x)不同的h~写不同的函数时Q不用担心自己定义的函数Q是否会(x)与其它文件中的函数同名?o:p>
扩展分析:术语static有着不寻常的历史.起初Q在C中引入关键字static是ؓ(f)了表C退Z个块后仍然存在的局部变量。随后,static在C中有了第二种含义Q用来表CZ能被其它文g讉K的全局变量和函数。ؓ(f)了避免引入新的关键字Q所以仍使用static关键字来表示q第二种含义。最后,C++重用了这个关键字Qƈ赋予它与前面不同的第三种含义Q表C属于一个类而不是属于此cȝM特定对象的变量和函数(与Java中此关键字的含义相同)?o:p>
全局变量、静态全局变量、静态局部变量和局部变量的区别
变量可以分ؓ(f)Q全局变量、静态全局变量、静态局部变量和局部变量?br> 按存储区域分Q全局变量、静态全局变量和静态局部变量都存放在内存的静态存储区域,局部变量存攑֜内存的栈区?br> 按作用域分,全局变量在整个工E文件内都有效;静态全局变量只在定义它的文g内有效;静态局部变量只在定义它的函数内有效Q只是程序仅分配一ơ内存,函数q回后,该变量不?x)消失;局部变量在定义它的函数内有效,但是函数q回后失效?o:p>
全局变量(外部变量)的说明之前再冠以static 构成了静态的全局变量。全局变量本n是静态存储方式, 静态全局变量当然也是静态存储方式?q两者在存储方式上ƈ无不同。这两者的区别虽在于非静态全局变量的作用域是整个源E序Q?当一个源E序由多个源文gl成Ӟ非静态的全局变量在各个源文g中都是有效的?而静态全局变量则限制了其作用域Q?卛_在定义该变量的源文g内有效, 在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文g内,只能源文件内的函数公用, 因此可以避免在其它源文g中引起错误?
从以上分析可以看出, 把局部变量改变ؓ(f)静态变量后是改变了它的存储方式x变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域, 限制了它的用范围?o:p>
static函数与普通函C用域不同。仅在本文g。只在当前源文g中用的函数应该说明为内部函?static)Q内部函数应该在当前源文件中说明和定义。对于可在当前源文g以外使用的函敎ͼ应该在一个头文g中说明,要用这些函数的源文件要包含q个头文?o:p>
static全局变量与普通的全局变量有什么区别:(x)static全局变量只初始化一ơ,防止在其他文件单元中被引?
static局部变量和普通局部变量有什么区别:(x)static局部变量只被初始化一ơ,下一ơ依据上一ơ结果|
static函数与普通函数有什么区别:(x)static函数在内存中只有一份,普通函数在每个被调用中l持一份拷?br> 全局变量和静态变量如果没有手工初始化Q则q译器初始化ؓ(f)0。局部变量的g可知?o:p>
当我们同时编译多个文件时Q所有未加static前缀的全局变量和函数都h全局可见性。ؓ(f)理解q句话,我D例来说明。我们要同时~译两个源文Ӟ一个是a.cQ另一个是main.c.
下面是a.c的内容:(x)
char a = 'A'; // global variable void msg() { printf("Hello\n"); } |
下面是main.c的内容:(x)
int main(void) { extern char a; // extern variable must be declared before use printf("%c ", a); (void)msg(); return 0; } |
E序的运行结果是Q?/p>
A Hello
你可能会(x)问:(x)Z么在a.c中定义的全局变量a和函数msg能在main.c中用?前面说过Q所有未加static前缀的全局变量和函数都h全局可见性,其它的源文g也能讉K。此例中Qa是全局变量Qmsg是函敎ͼq且都没有加static前缀Q因此对于另外的源文件main.c是可见的?/p>
如果加了staticQ就?x)对其它源文仉藏。例如在a和msg的定义前加上staticQmain.cq不到它们了。利用这一Ҏ(gu)可以在不同的文件中定义同名函数和同名变量,而不必担心命名冲H。Static可以用作函数和变量的前缀Q对于函数来Ԍstatic的作用仅限于隐藏Q而对于变量,staticq有下面两个作用?/p>
2.static的第二个作用是保持变量内容的持久?/p>
存储在静态数据区的变量会(x)在程序刚开始运行时完成初始化Q也是唯一的一ơ初始化。共有两U变量存储在静态存储区Q全局变量和static变量Q只不过和全局变量比v来,static可以控制变量的可见范_(d)说到底staticq是用来隐藏的。虽然这U用法不常见Q但我还是D一个例子?/p>
#include Qstdio.hQ? int fun(void){ static int count = 10; // 事实上此赋D句从来没有执行过 return count--; } int count = 1; int main(void) { printf("global\t\tlocal static\n"); for(; count Q? 10; ++count) printf("%d\t\t%d\n", count, fun()); return 0; } |
E序的运行结果是Q?br>
global local static
1 10
2 9
3 8
4 7
5 6
6 5
7 4
8 3
9 2
10 1
3.static的第三个作用是默认初始化?.其实全局变量也具备这一属性,因ؓ(f)全局变量也存储在静态数据区?/p>
在静态数据区Q内存中所有的字节默认值都?x00Q某些时候这一特点可以减少E序员的工作量。比如初始化一个稀疏矩阵,我们可以一个一个地把所有元素都|?Q然后把不是0的几个元素赋倹{如果定义成静态的Q就省去了一开始置0的操作。再比如要把一个字W数l当字符串来用,但又觉得每次在字W数l末֊‘\0’太麻烦。如果把字符串定义成静态的Q就省去了这个麻烦,因ؓ(f)那里本来是‘\0’。不妨做个小实验验证一下?/p>
#include Qstdio.hQ? int a; int main(void) { int i; static char str[10]; printf("integer: %d; string: (begin)%s(end)", a, str); return 0; } |
E序的运行结果如下integerQ?0Q?stringQ?QbeginQ(endQ?/p>
最后对static的三条作用做一句话ȝ。首先static的最主要功能是隐藏,其次因ؓ(f)static变量存放在静态存储区Q所以它具备持久性和默认?.
如果来你不打算升到unicodeQ那么也不需要_T?/p>
_t("hello world")
在ansi的环境下Q它是ansi的,如果在unicode下,那么它将自动解释为双字节字符Ԍ既unicode~码?/p>
q样做的好处Q不是ansi环境Q还是unicode环境Q都适用?/p>
那么在VC++中,字符串_T("ABC")和一个普通的字符?ABC"有什么区别呢Q?/p>
_T("ABC")
如果定义了unicodeQ它?yu)表CZؓ(f)L"ABC"Q每个字Wؓ(f)16位,宽字W串?/p>
如果没有定义unicodeQ它?yu)是ascii?ABC"Q每个字Wؓ(f)8位?/p>
相当?/p>
#ifdef _UNICODE
#define _T("ABC") L"ABC"
#else
#define _T("ABC") "ABC"
#endif
_T("ABC")中的一个字W和汉字一P占两个字节,而在"ABC"中,英文字符占一个字节,汉字占两个字节?/p>
一?在字W串前加一个L作用:
?L"我的字符? 表示ANSI字符串{换成unicode的字W串Q就是每个字W占用两个字节?/p>
strlen("asd") = 3;
strlen(L"asd") = 6;
二?nbsp; _T宏可以把一个引号引h的字W串Q根据你的环境设|,使得~译器会(x)Ҏ(gu)~译目标环境选择合适的QUnicodeq是ANSIQ字W处理方?/p>
如果你定义了UNICODEQ那么_T宏会(x)把字W串前面加一个L。这?_T("ABCD") 相当?L"ABCD" Q这是宽字符丌Ӏ?/p>
如果没有定义Q那么_T宏不?x)在字符串前面加那个LQ_T("ABCD") q价于 "ABCD"
三、TEXT,_TEXT 和_T 一L(fng)
如下面三语句Q?/p>
TCHAR szStr1[] = TEXT("str1");
char szStr2[] = "str2";
WCHAR szStr3[] = L("str3");
那么W一句话在定义了UNICODE时会(x)解释为第三句话,没有定义时就{于W二句话?nbsp;
但二句话无论是否定义了UNICODE都是生成一个ANSI字符Ԍ而第三句话L生成UNICODE字符丌Ӏ?/p>
ZE序的可UL性,都用W一U表C方法?/p>
但在某些情况下,某个字符必须为ANSI或UNICODEQ那q后两U方?br>
char :单字节变量类型,最多表C?56个字W?/p>
wchar_t :宽字节变量类型,用于表示Unicode字符
它实际定义在<string.h>里:(x)typedef unsigned short wchar_t?/p>
Z让编译器识别Unicode字符Ԍ必须以在前面加一?#8220;L”,定义宽字节类型方法如下:(x)
wchar_t c = `A' ;
wchar_t * p = L"Hello!" ;
wchar_t a[] = L"Hello!" ;
其中Q宽字节cd每个变量占用2个字节,故上q数la的sizeof(a) = 14
TCHAR / _T( ) :
如果在程序中既包括ANSI又包括Unicode~码Q需要包括头文gtchar.h。TCHAR是定义在该头文g中的宏,它视你是否定义了
NICODE宏而定义成Q?
定义了_UNICODEQ?nbsp; typedef wchar_t TCHAR ;
没有定义_UNICODEQ?typedef char TCHAR ;
#ifdef UNICODE
typedef char TCHAR;
#else
typede wchar_t TCHAR;
#endif
_T( )也是定义在该头文件中的宏Q视是否定义了_UNICODE宏而定义成Q?
定义了_UNICODEQ?nbsp; #define _T(x) L##x
没有定义_UNICODEQ?#define _T(x) x
注意Q如果在E序中用了TCHARQ那么就不应该用ANSI的strXXX函数或者Unicode的wcsXXX函数了,而必M用tchar.h中定义的_tcsXXX函数?/p>
以strcpy函数Z子,ȝ一下:(x)
char是C语言标准数据cdQ字W型Q至于由几个字节l成通常q译器军_Q一般一个字节。WindowsZ消除?~译器的差别Q重新定义了一些数据类型,你提C另外几个cd都是q样?br>CHAR为单字节字符?br>q有个WCHAR为Unicode字符Q即不论中英文,?个字有两个字节组成?br>如果当前~译方式为ANSI(默认)方式QTCHAR{h(hun)于CHARQ?br>如果为Unicode方式QTCHAR{h(hun)于WCHAR?br>在当 前版本LPCSTR和LPSTR没区别,即以零结字符串指针,相当于CHAR *?br>
char :单字节变量类型,最多表C?56个字W,
wchar_t :宽字节变量类型,用于表示Unicode字符Q?/p>
它实际定义在<string.h>里:(x)typedef unsigned short wchar_t?/p>
Z让编译器识别Unicode字符Ԍ必须以在前面加一?#8220;L”,定义宽字节类型方法如下:(x)
wchar_t c = `A' ;
wchar_t * p = L"Hello!" ;
wchar_t a[] = L"Hello!" ;
其中Q宽字节cd每个变量占用2个字节,故上q数la的sizeof(a) = 14
TCHAR / _T( ) :
如果在程序中既包括ANSI又包括Unicode~码Q需要包括头文gtchar.h。TCHAR是定义在该头文g中的宏,它视你是否定义了_UNICODE宏而定义成Q?
定义了_UNICODEQ?nbsp; typedef wchar_t TCHAR ;
没有定义_UNICODEQ?typedef char TCHAR ;
#ifdef UNICODE
typedef char TCHAR;
#else
typede wchar_t TCHAR;
#endif
_T( )也是定义在该头文件中的宏Q视是否定义了_UNICODE宏而定义成Q?
定义了_UNICODEQ?nbsp; #define _T(x) L##x
没有定义_UNICODEQ?#define _T(x) x
注意Q如果在E序中用了TCHARQ那么就不应该用ANSI的strXXX函数或者Unicode的wcsXXX函数了,而必M用tchar.h中定义的_tcsXXX函数?/p>
以strcpy函数Z子,ȝ一下:(x)
CSDN:superarhow_(d)(x) 不要再用TCHAR和_T了!他分析了原因后ȝQ如 果?zhn)正开始一个新的项目,h论如何也要顶住压力,直接使用UNICODE~码Q切讎ͼ(zhn)只需要对(zhn)的l员q行10分钟的培训,Cstrcpy?wcscpyQsprintf用swprintf代替Q常数前加LQ就可以了!它不?x)花?zhn)很多时间的Q带l?zhn)的是E_和安全!怿Ӟ没错的!Q?/p>
一?在字W串前加一个L作用:
?nbsp; L"我的字符? 表示ANSI字符串{换成unicode的字W串Q就是每个字W占用两个字节?
strlen("asd") = 3;
strlen(L"asd") = 6;
二?nbsp; _T宏可以把一个引号引h的字W串Q根据你的环境设|,使得~译器会(x)Ҏ(gu)~译目标环境选择合适的QUnicodeq是ANSIQ字W处理方?
如果你定义了UNICODEQ那么_T宏会(x)把字W串前面加一个L。这?_T("ABCD") 相当?L"ABCD" Q这是宽字符丌Ӏ?
如果没有定义Q那么_T宏不?x)在字符串前面加那个LQ_T("ABCD") q价于 "ABCD"
三、TEXT,_TEXT 和_T 一L(fng)
如下面三语句Q?nbsp;
TCHAR szStr1[] = TEXT("str1");
char szStr2[] = "str2";
WCHAR szStr3[] = L("str3");
那么W一句话在定义了UNICODE时会(x)解释为第三句话,没有定义时就{于W二句话?nbsp;
但二句话无论是否定义了UNICODE都是生成一个ANSI字符Ԍ而第三句话L生成UNICODE字符丌Ӏ?nbsp;
ZE序的可UL性,都用W一U表C方法?nbsp;
但在某些情况下,某个字符必须为ANSI或UNICODEQ那q后两U方法?/p>
CSDN:superarhow_(d)(x) 不要再用TCHAR和_T了!他分析了原因后ȝQ如 果?zhn)正开始一个新的项目,h论如何也要顶住压力,直接使用UNICODE~码Q切讎ͼ(zhn)只需要对(zhn)的l员q行10分钟的培训,Cstrcpy?wcscpyQsprintf用swprintf代替Q常数前加LQ就可以了!它不?x)花?zhn)很多时间的Q带l?zhn)的是E_和安全!怿Ӟ没错的!Q?/p>
文g中的#ifndef
头g的中?ifndefQ这是一个很关键的东ѝ比如你有两个C文gQ这两个C文g都include了同一个头文g。而编译时Q这两个C文g要一同编译成一个可q行文gQ于是问题来了,大量的声明冲H? q是把头文g的内定w攑֜#ifndef?endif中吧。不你的头文g?x)不会(x)被多个文g引用Q你都要加上q个。一般格式是q样的:(x) #ifndef <标识> ...... #endif <标识>在理Z来说可以是自由命名的Q但每个头文件的q个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写,前后加下划线Qƈ把文件名中的“.”也变成下划线Q如Qstdio.h #ifndef _STDIO_H_ ...... #endif 2.?ifndef中定义变量出现的问题Q一般不定义?ifndef中)?/p>
#ifndef AAA l论Q?/p>
(1).当你W一个用这个头?cpp文g生成.obj的时候,int i 在里面定义了当另外一个用这个的.cpp再次[单独]生成.obj的时候,int i 又被定义然后两个obj被另外一?cpp也include q个头的Q连接在一P׃(x)出现重复定义. (2).把源E序文g扩展名改?c后,VC按照C语言的语法对源程序进行编译,而不是C++。在C语言中,若是遇到多个int iQ则自动认ؓ(f)其中一个是定义Q其他的是声明?/p>
(3).C语言和C++语言q接l果不同Q可能(猜测Q时在进行编译的时候,C++语言全局 解决Ҏ(gu)Q?/p>
(1).把源E序文g扩展名改?c?/p>
(2).推荐解决Ҏ(gu)Q?br>.h中只声明 extern int i;?cpp中定?/p>
<x.h> 注意问题Q?/p>
(1).变量一般不要定义在.h文g中?/p>
------------------------------------------------------------------------------------------------------------------------------------------- 一般情况下Q源E序中所有的行都参加~译。但是有时希望对其中一部分内容只在满一定条件才q行~译Q也是对一部分内容指定~译的条Ӟq就?#8220;条g~译”。有Ӟ希望当满x条g时对一l语句进行编译,而当条g不满x则编译另一l语句? --------------------------------------------------------------------------------------------------------------------------------------- 作用范围是当前文g啊。因为编译是以cpp或c文g位单位的嘛。还以这个ؓ(f)例:(x) #ifndef __SOMEFILE_H__ 方式二:(x) #pragma once
#define <标识>
......
#define _STDIO_H_
#define AAA
...
int i;
...
#endif
里面有一个变量定?br>在vc中链接时出Ci重复定义的错误,而在c中成功编译?/p>
变量默认为强W号Q所以连接出错。C语言则依照是否初始化q行强弱的判断的。(参考)
#ifndef __X_H__
#define __X_H__
extern int i;
#endif //__X_H__
<x.c>
int i;
条g~译命o(h)最常见的Ş式ؓ(f)Q?
#ifdef 标识W?
E序D?
#else
E序D?
#endif
它的作用是:(x)当标识符已经被定义过(一般是?define命o(h)定义)Q则对程序段1q行~译Q否则编译程序段2?
其中#else部分也可以没有,卻I(x)
#ifdef
E序D?
#denif
q里?#8220;E序D?#8221;可以是语句组Q也可以是命令行。这U条件编译可以提高C源程序的通用性。如果一个C源程序在不同计算机系l上pȝ上运行,而不同的计算机又有一定的差异。例如,我们有一个数据类型,在Windowsq_中,应该使用longcd表示Q而在其他q_应该使用float表示Q这样往往需要对源程序作必要的修改,q就降低了程序的通用性。可以用以下的条件编译:(x)
#ifdef WINDOWS
#define MYTYPE long
#else
#define MYTYPE float
#endif
如果在Windows上编译程序,则可以在E序的开始加?
#define WINDOWS
q样则编译下面的命o(h)行:(x)
#define MYTYPE long
如果在这l条件编译命令之前曾出现以下命o(h)行:(x)
#define WINDOWS 0
则预~译后程序中的MYTYPE都用float代替。这P源程序可以不必作M修改可以用于不同类型的计算机系l。当然以上介l的只是一U简单的情况Q可以根据此思\设计出其它的条g~译?
例如Q在调试E序Ӟ常常希望输出一些所需的信息,而在调试完成后不再输些信息。可以在源程序中插入以下的条件编译段Q?
#ifdef DEBUG
print ("device_open(%p) ", file);
#endif
如果在它的前面有以下命o(h)行:(x)
#define DEBUG
则在E序q行时输出file指针的|以便调试分析。调试完成后只需这个define命o(h)行删除即可。有人可能觉得不用条件编译也可达此目的,卛_调试时加一批printf语句Q调试后一一printf语句删除厅R的,q是可以的。但是,当调试时加的printf语句比较多时Q修改的工作量是很大的。用条g~译Q则不必一一删改printf语句Q只需删除前面的一?#8220;#define DEBUG”命o(h)卛_Q这时所有的用DEBUG作标识符的条件编译段都其中的printf语句不v作用Q即L(fng)一控制的作用,如同一?#8220;开?#8221;一栗?
有时也采用下面的形式Q?
#ifndef 标识W?
E序D?
#else
E序D?
#endif
只是W一行与W一UŞ式不同:(x)?#8220;ifdef”改ؓ(f)“ifndef”。它的作用是Q若标识W未被定义则~译E序D?Q否则编译程序段2。这UŞ式与W一UŞ式的作用相反?
以上两种形式用法差不多,Ҏ(gu)需要Q选一U,视方便而定?
q有一UŞ式,是#if后面的是一个表辑ּQ而不是一个简单的标识W:(x)
#if 表达?
E序D?
#else
E序D?
#endif
它的作用是:(x)当指定的表达式gؓ(f)真(非零Q时q译程序段1Q否则编译程序段2。可以事先给定一定条Ӟ使程序在不同的条件下执行不同的功能?/p>
//正常代码
#ifdef _DEBUG
TRACE("Some infomation");
#else
//Now is release version,so do nothing
#endif
//正常代码
~译时是先把所有的预编译处理展开Q比如宏Q再~译Q所以Debug模式下,~译时的代码是:(x)
//正常代码
TRACE("Some infomation");
//正常代码
Release模式下的代码是:(x)
//正常代码
//正常代码
1 #ifndef方式
2 #pragma once方式
在能够支持这两种方式的编译器上,二者ƈ没有太大的区别,但是两者仍然还是有一些细微的区别?br> 方式一Q?/span>
#define __SOMEFILE_H__
... ... // 一些声明语?br> #endif
... ... // 一些声明语?/p>
#ifndef的方式依赖于宏名字不能冲H,q不光可以保证同一个文件不?x)被包含多次Q也能保证内容完全相同的两个文g不会(x)被不心同时包含。当Ӟ~点是如果不同头文件的宏名不小?#8220;撞R”Q可能就?x)导致头文g明明存在Q编译器却硬说找不到声明的状?br>
#pragma once则由~译器提供保证:(x)同一个文件不?x)被包含多次。注意这里所说的“同一个文?#8221;是指物理上的一个文Ӟ而不是指内容相同的两个文件。带来的好处是,你不必再费劲想个宏名了,当然也就不会(x)出现宏名撞引发的奇怪问题。对应的~点是如果某个头文件有多䆾拯Q本Ҏ(gu)不能保证他们不被重复包含。当Ӟ相比宏名撞引发?#8220;找不到声?#8221;的问题,重复包含更容易被发现q修正?br>
方式一pa支持所以移植性好Q方式二 可以避免名字冲突
1. const修饰普通变量和指针
const修饰变量Q一般有两种写法Q?
const TYPE value;
TYPE const value;
q两U写法在本质上是一L(fng)。它的含义是Qconst修饰的类型ؓ(f)TYPE的变量value是不可变的?
对于一个非指针的类型TYPEQ无论怎么写,都是一个含义,即value只不可变?
例如Q?
const int nValueQ? //nValue是const
int const nValueQ?nbsp; // nValue是const
但是对于指针cd的TYPEQ不同的写法?x)有不同情况Q例如:(x)
A. const char *pContent;
B. char * const pContent;
C. char const *pContent;
D. const char* const pContent;
对于前三U写法,我们可以换个方式Q给其加上括?
A. const (char) *pContent;
B. (char*) const pContent;
C. (char) const *pContent;
q样׃目了然。根据对于const修饰非指针变量的规则Q很明显QA=C.
- 对于A,C, const修饰的类型ؓ(f)char的变?pContent为常量,因此QpContent的内容ؓ(f)帔R不可?
- 对于B, const修饰的类型ؓ(f)char*的变量pContent为常量,因此QpContent指针本n为常量不可变.
- 对于D, 其实是A和B的合体Q表C指针本w和指针内容两者皆为常量不可变
ȝ:
(1) 指针本n是常量不可变
(char*) const pContent;
(2) 指针所指向的内Ҏ(gu)帔R不可?
const (char) *pContent;
(char) const *pContent;
(3) 两者都不可?
const char* const pContent;
q有其中区别Ҏ(gu)Q?
沿着*号划一条线Q?
如果const位于*的左侧,则const是用来修饰指针所指向的内容变量,x针指向ؓ(f)帔RQ物帔RQ;
如果const位于*的右侧,const是修饰指针本nQ即指针本n是常量(指针帔RQ?/span> 2. const修饰函数参数 const修饰函数参数是它最q泛的一U用途,它表C函C中不能修改参数的?包括参数本n的值或者参数其中包含的?。它可以很好 void function(const int Var); //传递过来的参数在函数内不可以改?无意义,因ؓ(f)Var本n是形参)
void function(const char* Var); //参数指针所指内容ؓ(f)帔R不可?
void function(char* const Var); //参数指针本n为常量不可变(也无意义Q?因ؓ(f)char* Var也是形参)
参数为引用,Z增加效率同时防止修改?
修饰引用参数Ӟ(x)
void function(const Class& Var);//引用参数在函数内不可以改?
void function(const TYPE& Var); //引用参数在函数内为常量不可变
3. const 修饰函数q回?/p>
const修饰函数q回值其实用的ƈ不是很多Q它的含义和const修饰普通变量以?qing)指针的含义基本相同?
(1) const int fun1() q个其实无意义,因ؓ(f)参数q回本n是赋倹{?
(2) const int * fun2()
调用?const int *pValue = fun2();
我们可以把fun2()看作成一个变量,那么是我们上面所说的1.(1)的写法,x针内容不可变?
(3) int* const fun3()
调用?int * const pValue = fun2();
我们可以把fun2()看作成一个变量,那么是我们上面所说的1.(2)的写法,x针本w不可变?
4. const修饰cd?对象指针/对象引用 const修饰cd象表C对象为常量对象,其中的Q何成员都不能被修攏V对于对象指针和对象引用也是一栗?
const修饰的对象,该对象的M非const成员函数都不能被调用Q因ZQ何非const成员函数?x)有修改成员变量的企图?
例如Q?
class AAA
{ void func2() const;
}
const AAA aObj;
aObj.func1(); ×
aObj.func2(); 正确
const AAA* aObj = new AAA();
aObj->func1(); ×
aObj->func2(); 正确
5. const修饰成员变量 const修饰cȝ成员函数Q表C成员常量,不能被修改,同时它只能在初始化列表中赋倹{?
class A
{
…
const int nValue; //成员帔R不能被修?
…
A(int x): nValue(x) {}; //只能在初始化列表中赋?
}
6. const修饰成员函数 const修饰cȝ成员函数Q则该成员函C能修改类中Q何非const成员函数。一般写在函数的最后来修饰?
class A
{
…
void function()const; //常成员函? 它不改变对象的成员变? 也不能调用类中Q何非const成员函数?
}
对于constcd?指针/引用Q只能调用类的const成员函数Q因此,const修饰成员函数的最重要作用是限制对于const对象的用?
7. const帔R与define宏定义的区别 (1) ~译器处理方式不?
define宏是在预处理阶段展开?
const帔R是编译运行阶D用?
(2) cd和安全检查不?
define宏没有类型,不做Mcd查,仅仅是展开?
const帔R有具体的cdQ在~译阶段?x)执行类型检查?
(3) 存储方式不同
define宏仅仅是展开Q有多少地方使用Q就展开多少ơ,不会(x)分配内存?
const帔R?x)在内存中分?可以是堆中也可以是栈??/p>
例子:(x)
char ch[5] = "lisi";
const char* pStr = ch;
*pStr = 'w'; //errorQ物帔RQ不可更改内?br>pStr ="wangwu"; //OKQ物帔RQ可更改指针
cha ch[5] = "lisi";
char * const pStr = "lisi";
pStr = "zhangsan"; //errorQ指针常量,不可更改指针
*pStr = "w"; //OK Q指针常量,可更改内?
void func1();
大家都知道C语言中的随机函数randomQ可是random函数q不是ANSI C标准Q所以说Qrandom函数不能在gcc,vc{编译器下编
译通过。那么怎么实现VC语言中的随机函数呢?
其实Q除了random函数Q还有一个rand函数Q这个函C是一个随机函敎ͼ他可以生从0到rand_max(32767)的随机数?/p>
大家可以把以上的代码~译q行一下,发现他的生随机数了,但是你会(x)发现Q每ơ运行程序生的随机数都是一L(fng)Q不q你在程序里加上for循环Q每ơ生的C一P但是Q如果再q行q个E序Q它产生的数据却都是相同的?/p>
那么如何写一个程序,让它每次q行时生的随机数都不一样呢Q?L(fng)下面的例子:(x)
q时q行E序Q会(x)发现每次产生的随机数都不一栗?/p>
那么Z么第一个程序一栯第二个E序一样呢Q?/p>
W二个程序用C一个新的函数srand
q个函数是给随机C生一个随机种?seed)Q函数原型是srand( (unsigned)time( NULL ) );
time的值每时每刻都不同。所以种子不同,所以,产生的随机数也不同?/p>
所以说Q要想生不同的随机敎ͼ在用rand之前需要先调用srand
srand和rand函数都包含在stdlib.h的头文g里?/p>
׃rand产生的随机数是从0到rand_max的,而rand_maxQ?2767Q是一个很大的敎ͼ那么如何产生从X~Y的数呢?
从X到YQ有YQXQ?个数Q所以要产生从X到Y的数Q只需要这样写Q?/p>
k = rand() % (Y - X + 1) +X;
q样Q就可以产生你想要的M范围内的随机C?
问题Q如何生成K个小于Nq且互不重复的整?/span>
一.首先对于c++的随机函数我们要有所了解Q这里就不篏赘了Q请读者自行google之,
我们要用到的?br>1. void srand(unsigned int_seed)函数产生一个以当前旉开始的随机U子
srand(unsigned(time(NULL))),必须攑֜生成随机数前
2.int rand()函数Q随Z生一整数
rand()%MAX 产生[0,MAX)的整?br> a+rand()%(b-a+1) 产生[a,b]之间的整?br>3.需要头文g#include<time.h>
?考虑如何让数据不重复
看代码吧。。学?fn)下?gu)
首先搞个一l没有重复数据的数组Q就是x[i]=i;
此时注意那个swap函数Q每ơ生成的随机C为数l下标去取数Q然后交换,q就保证了xq个数组l不?x)有重复的数出现?br>l了Q!