版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領
文檔簡介
編程規(guī)范—第二講
變量與類型軟件工程系變量和類型是程序的基礎,也是編程中容易忽視的地方。本節(jié)將學習變量和類型的相關概念和編程陷阱。實用經驗5:計算機是怎么存儲變量的?實用經驗6:確保每個對象在使用前已被初始化。實用經驗7:掌握局部變量和全局變量的區(qū)別實用經驗8:掌握變量定義的位置與時機。實用經驗9:合理使用引用實用經驗13:typedef使用的陷阱實用經驗16:提防隱式轉換帶來的麻煩實用經驗17:深刻理解void和void*實用經驗18:如何判定變量是否相等?5:計算機如何存儲變量數據在內存中的放置包括兩個部分:一是數據放置的位置即數據存儲區(qū)域二是數據放置的格式。數據存儲區(qū)域可分為:只讀數據區(qū),全局靜態(tài)存儲區(qū),自由存儲區(qū),棧區(qū),堆區(qū)。(P23)只讀/靜態(tài)存儲區(qū):存儲常量和恒值;全局靜態(tài)存儲區(qū):全局變量和靜態(tài)變量自由存儲區(qū):CRT通過malloc\free函數管理的內存。與堆區(qū)同一種管理方式,統(tǒng)稱為堆區(qū)。棧區(qū):數據由編譯器自動分配釋放,主要存放函數的參數值、局部變量等。堆區(qū):存放new分配的內存塊,編譯器不負責他們的釋放。堆區(qū)分配數據雖具有節(jié)省空間,使用方便。堆區(qū)分配和釋放內存會造成內存空間的不連續(xù),形成大量的碎片,程序效率降低。分配的數據如果沒有釋放,很容易造成內存泄露。思考:什么情形應該用到全局變量和靜態(tài)變量,兩者有什么區(qū)別
什么時候應該用到new/delte?5:計算機如何存儲變量數據放置的格式:整型值整數,Int\short\long類型都表示整型值,不同之處是存儲變量所占的內存空間大小不同,計算機按位序列存儲數據,8位一個字節(jié),一個字節(jié)和一個稱為地址的數關聯起來,并且需要知道存在該地址的值的類型。字符型
char基本字符集;wchar_t擴展字符集布爾值算術類型。Bool類型表示true(非0)和false(0)。浮點型單精度(32:1+8+23雙精度(64:1+11+52)兩類三部分符號位、指數位、尾數部分。浮點計算,三部分必須是二進制形式。5計算機如何存儲變量變量在不同語境下,其分配內存區(qū)域是不同的。靜態(tài)變量和全局變量一般分布在全局/靜態(tài)存儲區(qū),函數的參數和變量一般分配在棧中,new和malloc申請的數據一般分配在堆中。變量在定義和初始化時,一定要注意其取值范圍,以防出現數據截斷異?,F象。同樣數據在使用過程中應禁止降級強制轉換,這種轉換一般會降低數據的精度,嚴重時會出現模棱兩可的問題。6:確保每個對象在使用前已被初始化對象在使用前是否會被初始化是無法確定的如:Pt的成員變量有時會被初始化為0,有時不會最佳的處理方式就是:永遠在變量被使用之前將它初始化
內置數據類型,必須手動完成初始化。
如:Inti=5;char*pszString=“acstring”內置類型以外的其他成員,對象的構造函數完成初始化classCPoint{public: intm_iX;intm_iY;};intmain(){CPointpt;cout<<pt.m_iX<<endl;return0;}讀取未初始化的對象會導致不確定的行為6確保每個對象在使用前已被初始化最佳的處理方式就是:永遠在變量被使用之前將它初始化內置類型以外的其他成員,對象的構造函數完成初始化
typedefenumtagsex{MALE_SEX=0,FEMALE_SEX=1,}Sex;classCPerson{public:stringm_strName;Sexm_sex;CPerson(stringstrName,Sexsex);};CPerson::CPerson(stringstrName,Sexsex){m_strName=strName;m_sex=sex;}intmain(){CPersonc1("lucy",MALE_SEX);cout<<c1.m_strName<<c1.m_sex<<endl;return0;}typedefenumtagsex{ MALE_SEX=0,FEMALE_SEX=1,}Sex;classCPerson{public:stringm_strName;Sexm_sex;CPerson(stringstrName,Sexsex);};CPerson::CPerson(stringstrName,Sexsex):m_strName(strName),m_sex(sex){}intmain(){CPersonc1("lucy",MALE_SEX);cout<<c1.m_strName<<c1.m_sex<<endl;return0;}復制初始化:之前需調用默認構造函數,效率低列表初始化:無需調用默認構造函數,效率高,且常成員只能用列表初始化聲明次序決定初始化新婚許列表次序應與聲明次序一致6確保每個對象在使用前已被初始化Tips:為內置類型對象進行手動初始化,因為C++不保證初始化它們。構造函數最好使用成員初始化列表,而不是在構造函數本體內使用賦值操作。初值列表列出的成員變量,其排列順序應和它們在class中的聲明次序相同。
7局部變量和全局變量的差別變量一般包括4種:全局變量,靜態(tài)全局變量,靜態(tài)局部變量,和局部變量。按照存儲區(qū)域分:全局變量,靜態(tài)全局變量,靜態(tài)局部變量都存放在內存的靜態(tài)存儲區(qū),局部變量存放在內存的棧區(qū)。按照作用域分:全局變量在整個工程文件內都有效;靜態(tài)全局變量只在定義它的文件內有效;靜態(tài)局部變量只在定義它的函數內有效,只是程序分配一次內存,函數返回后,該變量不會消失;局部變量在定義它的函數內有效,函數返回后失效。注意:全局變量和靜態(tài)變量如果沒有手動初始化,則由編譯器初始化為0.局部變量是編譯器永遠不會初始化的變量。如果沒有手動初始化,則為隨機值7局部變量和全局變量的差別局部變量也稱為內部變量.局部變量是在函數內說明的,其作用域僅限于函數內,離開該函數后再使用變量是非法的。右圖在函數f1內定義了3個變量,a為形參,b,c為一般變量,abc的作用于僅限于函數f1。局部變量說明主函數main定義的變量只在其中使用,不能用于其他函數。同時也不能使用其他函數的變量。C++語言應注意。形參變量屬于被調函數的局部變量,實參屬于主調函數的局部變量。在復合語句中可定義變量,其作用域只在復合語句范圍內。Intf1(inta){Intb,c;……}Main(){Intm,n;F1(m)}局部變量定義及特點局部變量例子7局部變量和全局變量的差別本程序在main中定義了i,j,k3個變量,其中k未賦初值。而在復合語句內有定義了一個變量k,并賦初值為8.請問兩個輸出語句的值分別是?Main(){Inti=2;j=3;k;k=i+j;{intk=8;If(i==3){printf(“%d\n”,k);}}Printf(“%d\n%d\n”,i,k)}7局部變量和全局變量的差別
全局變量也稱為外部變量,他是在函數外部定義的變量。他不屬于某一個函數,屬于一個源文件。全局變量例子程序要求:函數vs完成求長方形體積和3個面積,函數返回體積V。主函數完成長寬高的輸入及體積及三個面積的輸出。矛盾分析:C語言規(guī)定函數返回值只有一個,vs函數將體積返回給主函數,其余三個面積值怎么傳遞給主函數解決方法:定義三個面積為外部變量s1,s2,s3,其作用域為整個程序。主函函數也可以使用ints1,s2,s3;intvs(inta,intb,intc){intv;v=a*b*c;s1=a*b;s2=b*c;s3=a*c;returnv;}intmain(){intv,l,w,h;cout<<"inputlength,widthandheight\n";cin>>l>>w>>h;v=vs(l,w,h);cout<<v<<"\n"<<s1<<"\n"<<s2<<"\n"<<s3;}全局變量定義及特點函數的return語句只能返回一個值體積v需要輸出vs函數計算所得的四個值?聲明為全局變量,無需return語句傳回7局部變量和全局變量的差別全局變量說明:對于外部變量的說明和定義不是一回事。
外部變量定義:必須在所有函數之外,且只定義一次:[extern]inta,b;(可省略extern.)
外部變量說明:出現在要使用該外部變量的各個函數中,
在整個程序內可能出現多次:externinta,b;外部變量在定義時就分配了內存單元,外部變量定義可做初始賦值。外部變量說明不再賦初始值,只是說明。外部變量加強了函數模塊之間的聯系,同時使得函數依賴這些變量,因此導致函數獨立性低?;谀K化程序設計,在不必要的情況下盡可能少使用外部變量。同一源文件,允許全局變量和局部變量同名7局部變量和全局變量的差別變量特性
生存期靜態(tài)存儲變量變量定義就分配存儲單元直至整個程序結束。
靜態(tài)存儲變量有:extern全局變量靜態(tài)變量static
動態(tài)存儲變量程序執(zhí)行過程才分配存儲單元,使用完畢立即釋放動態(tài)存儲變量有:auto自動變量register寄存器變量靜態(tài)存儲變量是一直存在的,而動態(tài)存儲變量則時而存在時而消失。作用域全局作用域、局部作用域、語句作用域、類作用域、命名空間作用域、文件作用域。全局變量具有全局作用域,可以作用于所有源文件。不包含全局變量定義的源文件只需要用extern關鍵字再次聲明。靜態(tài)局部變量具有局部作用域,他只被初始化一次,直到程序運行結束都一直存在。只對定義自己的函數體始終可見。局部變量只有局部作用域,自動對象,在程序運行期間不是一直存在的,函數執(zhí)行期間存在,函數調用結束變量撤銷內存收回。靜態(tài)全局變量具有全局作用域,他只作用于他的文件中,不作用于其他文件(區(qū)別于全局變量)。即被static關鍵字修飾過的變量具有文件作用域。7局部變量和全局變量的差別Tips:A.若全局變量僅在單個C文件中訪問,則可以將這個變量修改為靜態(tài)全局變量,以降低模塊間的耦合度;B.若全局變量僅由單個函數訪問,則可以將這個變量改為該函數的靜態(tài)局部變量,以降低模塊間的耦合度;C.設計和使用訪問動態(tài)全局變量、靜態(tài)全局變量、靜態(tài)局部變量的函數時,需要考慮重入問題,因為他們都放在靜態(tài)數據存儲區(qū),全局可見;D.如果我們需要一個可重入的函數,那么,我們一定要避免函數中使用static變量(這樣的函數被稱為:帶“內部存儲器”功能的的函數)E.函數中必須要使用static變量情況:比如當某函數的返回值為指針類型時,則必須是static的局部變量的地址作為返回值,若為auto類型,則返回為錯指針。8:掌握變量定義的位置與時機tips:在定義變量時,要三思而后行,掌握變量定義的時機與位置,在合適的時機與合適的位置上定義變量。盡可能推遲變量的定義,直到不得不需要該變量為止;同時,為了減少變量名污染,提高程序可讀性,盡量縮小變量的作用域。總結:變量定義離使用越遠越好;盡量縮小變量作用域優(yōu)點:
避免構造和析構非必要對象,甚至避免默認構造函數執(zhí)行
避免命名污染8掌握變量定義的位置與時機std::stringChangToUpper(conststd::string&str){usingnamespacestd;stringupperStr;if(str.length()<=0){throwerror("Stringtobechangedisnull");}...//將字符變?yōu)榇髮?/p>
returnupperStr;
}結論:就算函數拋出了異常,因變量定義在先。仍然要為upperStr的構造與析構付出代價。所以變得精明些,把握變量定義的時機:盡量晚地去定義變量,直到不得不定義時。過早定義upperstr對象如果輸入字符串為空,函數拋出異常,upperstr對象就不會被使用。掌握變量定義的位置與時機std::stringChangToUpper(conststd::string&str){usingnamespacestd;if(str.length()<=0){throwerror("Stringtobechangedisnull");}stringupperStr;...//將字符變?yōu)榇髮?/p>
returnupperStr;}關于變量定義的位置,建議變量定義得越“l(fā)ocal”越好,盡量避免變量作用域的膨脹。這樣做不僅可以有效地減少變量名污染,還有利于代碼閱讀者盡快找到變量定義,獲悉變量類型與初始值,使閱讀代碼更容易。8掌握變量定義的位置與時機#include<iostream>#include<string>usingnamespacestd;stringgetSubStr(conststring&str,size_tiPos){stringstrSubStr;if(str.size()<iPos){throwlogic_error("iPosistoolarge");}strSubStr=str.substr(iPos);returnstrSubStr;}intmain(){strings1("hello");cout<<getSubStr(s1,7);return0;}#include<iostream>#include<string>usingnamespacestd;stringgetSubStr(conststring&str,size_tiPos){if(str.size()<iPos){throwlogic_error("iPosistoolarge");}stringstrSubStr;strSubStr=str.substr(iPos);returnstrSubStr;}intmain(){strings1("hello");cout<<getSubStr(s1,7);return0;}該語句根本就不會執(zhí)行,字符串對象trSubStr也沒有聲明的必要Hello總共6位,獲取從第8位開始的子字符串,一定出現異常推遲對象聲明,但是對象的默認構造函數仍會執(zhí)行#include<iostream>#include<string>usingnamespacestd;stringgetSubStr(conststring&str,size_tiPos){if(str.size()<iPos){throwlogic_error("iPosistoolarge");}stringstrSubStr(str.substr(iPos));returnstrSubStr;}intmain(){strings1("hello");cout<<getSubStr(s1,7);return0;}8掌握變量定義的位置與時機for(inti=0;i<N;i++){...//dosomething}...//somecodefor(inti=0;i<M;i++){...//doanotherthing}原因分析:這是因為在VC6.0中,i的作用域超出了本身的循環(huán)。存在變量名污染變量名污染著名例子明明兩個i都屬于各自for語句內的局部變量作用域已經足夠小上述代碼在VC6.0中是不能通過編譯的,編譯器會提示變量i重復定義。說明:1.微軟意識到了這個問題,在其后續(xù)的VC++系列產品中,i的作用域重新被限定在了for循環(huán)體2.變量名污染的本質就是同一作用域下的命名沖突9:引用難道只是別人的替身引用的定義及特點引用只是默認值的別名,對引用的唯一操作就是將其初始化。一旦引用初始化結束,引用就只是其默認值的另一種寫法。引用沒有地址,甚至不占用任何存儲空間。引用只可作為
變量的別名復雜的左值(有內存地址)表達式的別名引用的用途如果一個函數返回一個引用,這說明此函數的返回值可重新賦值引用的另一個用途即讓函數在其返回值之外傳遞幾個值。另外,指向數組的引用保留了數組的長度信息,而指針不會保留數組的長度信息。常量值不能給普通引用初始化,但是可以給const引用初始化:
constint&rInt=12;//正常通過;
int&rInt=12//錯誤9:引用難道只是別人的替身引用的用途int&put(intn);intvals[10];interror=-1;voidmain(){put(0)=10;put(9)=20;cout<<vals[0];cout<<vals[9];}int&put(intn){if(n>=0&&n<=9)returnvals[n];else{cout<<"subscripterror";returnerror;}}#include<iostream>#include<string>usingnamespacestd;intvs(inta,intb,intc,int&s1,int&s2,int&s3){intv;v=a*b*c;
s1=a*b;
s2=b*c;
s3=a*c;returnv;}intmain(){intl,w,h,v,s1,s2,s3;cout<<"inputlength,widthandheight\n";cin>>l>>w>>h;v=vs(l,w,h,s1,s2,s3);cout<<v<<"\n"<<s1<<"\n"<<s2<<"\n"<<s3;}只能返回體積,其余的三個面的面積怎么傳遞回主函數說明:如果一個函數返回一個引用,這說明此函數的返回值可重新賦值說明:引用的另一個用途即讓函數在其返回值之外傳遞幾個值。9:引用難道只是別人的替身引用的用途#include<iostream>#include<string>usingnamespacestd;voidArray_test1(int(&array)[3]){array[2]=3;}voidArray_test2(intarray[3]){array[2]=3;}intmain(){intn3[2]={2,4};Array_test1(n3);Array_test2(n3);}編譯不能通過編譯能通過指向數組的引用,保留數組的長度為3,卻傳來一個長度為3的數組指向數組的指針,只記住數組的首地址說明:指向數組的引用,可以保留數組長度9:引用難道只是別人的替身引用的用途#include<iostream>#include<iomanip>usingnamespacestd;intmain(){inta;int&b=a;constint&c=12.3;cout<<c;shorts=123;constint&rIntegrate=s;s=321;constint*ip=&rIntegrate;cout<<"rIntegrate="<<rIntegrate<<",s="<<s<<endl;cout<<"ip="<<ip<<",&s="<<&s;return0;引用初始化值為左值沒問題引用默認值不是左值,必須前面加constrInte與s值并不相同,說明rInte默認值并不是s,而是一個臨時對象說明:常量引用與臨時變量共存亡9引用難道只是別人的替身小心陷阱:如果初始化值是一個左值(可以取得地址),則可以初始化引用。如果初始化值不是一個左值,則只能對constT&(常量引用)賦值,且賦值過程包括3階段:首先將值隱式轉換到類型T,然后將這個轉換結果存放在一個臨時對象中,最后用這個臨時對象來初始化這個引用變量。constT&(常量引用)過程中使用的臨時變量會和constT&共“存亡”。constint&rInt=12;//對變量引用的任何操作都會影響匿名臨時變量,而不會影響常量12.并且編譯器會保證臨時對象生命期擴展到初始化后的引用存在的全部時域。Tips:若非必要請不要使用const引用,因為引用有時會伴隨著臨時對象的產生。在函數生命中,請盡量避免const引用形參聲明,使用非const引用形參替代以防因返回const引用生成的臨時變量而導致程序執(zhí)行錯誤。a=b+100;a就是左值,b+25就是一個右值。兩邊不可互換。13:typedef使用的陷阱Typydef與宏的混用陷阱#include<iostream>#definePSTR_MACROchar*typedefchar*PSTR;usingnamespacestd;intmain(intargc,char*argv[]){PSTRpiVar1,piVar2;PSTR_MACROpiVar3,piVar4;chariVar=100;piVar1=&iVar;piVar2=&iVar;piVar3=&iVar;piVar4=iVar;cout<<(void*)piVar1<<endl;cout<<(void*)piVar2<<endl;cout<<(void*)piVar3<<endl;cout<<piVar4<<endl;return0;}Typedef是給數據類型定義一個別名宏定義只是簡單的字符串替換13:typedef使用的陷阱typedef
string
NAME;
typedef
int
AGE;#define
MAC_NAME
string
#define
MAC_AGE
int
typedef
int*
PTR_INT1;
#define
int*
PTR_INT2
int
main()
{
PTR_INT1
pNum1,
pNum2;
PTR_INT2
pNum3,
pNum4;
int
year
=
2011;
pNum1
=
&year;
pNum2
=
&year;
pNum3
=
&year;
pNum4
=
&year;
cout<<pNum1<<"
"<<pNum2<<"
"<<pNum3<<"
"<<pNum4;
return
0;
}
Typydef與宏的混用陷阱stringa,b;intc,d;
stringa,b;intc,d;
MAC_NAMEa,b;MAC_AGEc,d;
stringa,b;intc,d;
PTR_INT1
pNum1,
pNum2;
PTR_INT2
pNum3,
pNum4;
int*pNum1,
*pNum2;int*pNum3,
pNum4;13:typedef使用的陷阱Typedef其他多種用途:(1)簡化代碼在部分較老的C代碼中,聲明struct對象時,必須帶上struct關鍵字,即采用“struct結構體類型結構體對象”的聲明格式。
為了在結構體使用過程中,少寫聲明頭部的struct,于是就有人使用了typedef:struct
tagRect
{
int
width;
int
length;
};
strcut
tagRect
rect;
typedef
struct
tagRect
{
int
width;
int
length;
}RECT;
RECT
rect;
說明:在現在的C++代碼中,這種方式已經不常見,因為對于結構體對象的聲明已經不需要使用struct了,可以采用“結構體類型結構體對象”的形式。13:typedef使用的陷阱(2)用typedef定義一些與平臺無關的類型。例如在標準庫中廣泛使用的size_t的定義:(3)為復雜的聲明定義一個簡單的別名。它可以增強程序的可讀性和標識符的靈活性,這也是它最突出的作用。說明:在typedef的使用過程中,還必須記?。簍ypedef在語法上是一個存儲類的關鍵字,類似于auto、extern、mutable、static、register等,雖然它并不會真正影響對象的存儲特性,#ifndef
_SIZE_T_DEFINED
#ifdef
_WIN64
typedef
unsigned
__int64
size_t;
#else
typedef
_W64
unsigned
int
size_t
#endif
#define
_SIZE_T_DEFINED
#endif
typedef
static
int
INT2;
//不可行,編譯將失敗
聲明:int*(*a[5])(int,char*);變量名為a,直接用一個新別名pFun替換a就可以了:
typedefint*(*pFun)(int,char*);
原聲明的最簡化版:pFuna[5];13:typedef使用的陷阱用途一:定義一種類型的別名,而不只是簡單的宏替換??梢杂米魍瑫r聲明指針型的多個對象。用途二:用在舊的C的代碼中(具體多舊沒有查),幫助struct。以前的代碼中,聲明struct新對象時,必須要帶上struct,即形式為:struct結構名對象名。用途三:用typedef來定義與平臺無關的類型。用途四:為復雜的聲明定義一個新的簡單的別名。Tips:區(qū)分typedef與#define之間的不同;不要用理解宏的思維方式對待typedef,typedef聲明的新名稱具有一定的封裝性,更易定義變量,而#define宏只是簡單的自讀替換。盡量用typedef實現那些復雜的聲明形式,以保證代碼清晰,易于閱讀。16:提防隱式轉換帶來的麻煩在C/C++中,類型轉換發(fā)生在這種情況下:為了實現不同類型的數據之間進行某一操作或混合運算,編譯器必須把數據轉換成為相同的數據類型。C/C++語言中的類型轉換可以分為兩種,一種為隱式轉換,特指編譯器完成的類型轉換;而另一種則為顯式強制轉型特指由開發(fā)人員顯式進行的數據類型轉換。顯式強制轉型在某種程度上還有一定的優(yōu)點,對于編寫代碼的人來說使用它能夠很容易地獲得所需類型的數據,對于閱讀代碼的人來說可以從代碼中獲知作者的意圖。而隱式轉換則不然,它讓發(fā)生的一切變得悄無聲息,在編譯時這一切由編譯程序按照一定規(guī)則自動完成,不需任何的人為干預。16:提防隱式轉換帶來的麻煩隱式轉換雖然帶來了一定的便利,使編碼更加簡潔,減少了冗余,但是這些并不足以讓我們完全接受它,因為隱式轉換所帶來的副作用不可小覷,它通常會使我們在調試程序時毫無頭緒。上述代碼片段中的函數調用不會出現任何錯誤,編譯器給出的僅僅是一個警告。可是細心的程序員一眼就能看出問題:函數Function(charc)的參數c是一個char型,256絕不會出現在其取值區(qū)間內。但是編譯器會自動地完成數據截斷處理。編譯器悄悄完成的這種轉換存在著很大的不確定性:一方面它可能是合理的,因為盡管類型long大于char,但para中很可能存放著char類型范圍內的數值;另一方面para的值的確可能是char無法容納的數據,這樣一不小心便會造成一個非常隱蔽、難以捉摸的錯誤。void
Function(char
c);
int
main()
{
long
para
=
256;
Function(para);
return
0;
}
16:提防隱式轉換帶來的麻煩C/C++隱式轉換主要發(fā)生在以下幾種情形。1、內置類型間的隱式轉換在混合類型的表達式中,操作數轉換成相同的類型;用作if語句或循環(huán)語句的條件時,被轉換為bool類型;用于switch語句時,被轉換為整數類型;用來初始化某個變量(函數實參、return語句)時,被轉換為變量的類型。內置類型的轉換級別:Double>float>longlong/unsignedlonglong>long/unsignedlong>int/unsignedint>short/unsignedshort>char/unsignedchar16:提防隱式轉換帶來的麻煩在編譯這段代碼時,編譯器會按照規(guī)則自動地將ival轉換為與dval相同的double類型。C語言規(guī)定的轉換規(guī)則是由低級向高級轉換。兩個通用的轉換原則是:(1)為防止精度損失,類型總是被提升為較寬的類型。(2)所有含有小于整型類型的算術表達式在計算之前其類型都會被轉換成整型。它最直接的害處就是有可能導致重載函數產生二義性。(右圖)參數0.5應該轉換為ival還是fval?這是編譯器沒法搞明白的一個問題。int
ival
=
3;
double
dval
=
3.1415
cout<<(ival
+
dval)<<endl;
//ival被提升為double類型:3.0
extern
double
sqrt(double);
sqrt(2);
//2被提升為double類型:
2.0void
Print(int
ival);
void
Print(float
fval);
int
ival
=
2;
float
fval
=
2.0f;
Print(ival);
//
OK,
int-version
Print(fval);
//
OK,
float-version
Print(1);
//
OK,
int-version
Print(0.5);
//
ERROR!!
16:提防隱式轉換帶來的麻煩non-explicitconstructor接受一個參數的用戶定義類對象之間隱式轉換。在上面的代碼(右圖)中,調用DoSomething()函數時會發(fā)現實參與形參類型不一致,但是因為類A的構造函數只含有一個int類型的參數,所以編譯器會以20為參數調用A的構造函數,以便構造臨時對象,然后傳給DoSomething()函數。不要為此而感到驚訝,其實編譯器比想像的還要聰明:當無法完成直接隱式轉換的時候,它不會罷休,它會嘗試使用間接的方式。所以,下面的代碼也是可以被編譯器接受的:classA{public:A(intx):m_data(x){}private:intm_data;}voidDoSomething(AaObject);DoSomething(20);voidDoSomething(AaObject);floatfval=20.0;DoSomething(fval);16:提防隱式轉換帶來的麻煩控制隱式轉換的兩條有效途徑之一:使用具名轉換函數為了避免該問題出現,建議使用自定義轉換具名函數代替轉換操作符ClassString{pubic:operatorconstchar*();//在需要時,string對象可以轉換成constchar*指針};//假設s1s2均是string類型的字符串Intx=s1-s2;//可編譯,單行為不確定Constchar*p=s1-5;//可編譯,但行為不確定P=s1+‘0’//可編譯,單不是開發(fā)人員期望的結果If(s1==“0”){……}’//可編譯,單不是開發(fā)人員期望的結果ClassString{pubic:constchar*as_char_pointer()const;//string對象轉換成constchar*指針};//假設s1s2均是string類型的字符串Intx=s1-s2;//編譯錯誤Constchar*p=s1-5;//編譯錯誤P=s1+‘0’//編譯錯誤If(s1==“0”){……}’//編譯錯誤16:提防隱式轉換帶來的麻煩控制隱式轉換的兩條有效途徑之二:使用explicit限制的構造函數這種方式針對的是具有一個單參數構造函數的用戶自定義類型。上述代碼片段(右圖)中,用戶自定義類型Widget的構造函數可以是一個參數,也可以是兩個參數。具有一個參數時,其參數類型可以是unsignedint,亦可以是char*。所以這兩種類型的數據均可以隱式地轉換為Widget類型。控制這種隱式轉換的方法很簡單:為構造函數加上explicit關鍵字:class
Widget
{
public:
Widget(
unsigned
int
factor);
Widget(
const
char*
name,
const
Widget*
other
=
NULL);
};
Widgetwidget1=100;//可通過編譯Widgetwidget1=“mywindow”//可通過編譯class
Widget
{
explicit
Widget(unsigned
int
factor);
explicit
Widget(const
char*
name,
const
Widget*
other
=
NULL);
};
Widgetwidget1=100;//編譯錯誤Widgetwidget1=“mywindow”//編譯錯誤16:提防隱式轉換帶來的麻煩提防隱式轉換所帶來的微妙問題,盡量控制隱式轉換的發(fā)生;通常采用的方式包括:(1)使用非C/C++關鍵字的具名函數,用operatoras_T()替換operatoT()(T為C++數據類型)。(2)為單參數的構造函數加上explicit關鍵字。在隱式轉換中,轉型并非僅僅將一種類型轉換為另外一種,參看p71實例1、實例2。Tips:在使用編譯器隱式類型轉換時,一定要注意,能減少隱式轉換使用時盡量減少隱式轉換的使用;除非明確知道隱式轉換時編譯器發(fā)生了什么,否則在編譯時不要對編譯器隱式轉換進行假設。17:正確區(qū)分void與void*void是“無類型”,所以它不是一種數據類型;void*則為“無類型指針”,即它是指向無類型數據的指針,也就是說它可以指向任何類型的數據。從來沒有人會定義一個void變量,如果真的這么做了,編譯器會在編譯階段清晰地提示,“illegaluseoftype‘void’”。void體現的是“有與無”的問題,要先“有”了,在非void的前提下才能去討論這個變量是什么類型的。void發(fā)揮的真正作用是限制程序的參數與函數返回值。voida;//定義無類型變量,無意義
void*p;//定義無類型指針,會有意義intb;p=&b;inta;(void)a;//強制轉換變量類型char*p;(void*)p//強制轉換指針類型17:正確區(qū)分void與void*在C/C++語言中,對void關鍵字的使用做了如下規(guī)定:(1)如果函數沒有返回值,那么應將其聲明為void類型。在C語言中,凡不加返回值類型限定的函數,就會被編譯器作為返回整型值處理。程序運行的結果為:2+3=5。這個結果更加明確地說明了函數返回值為int類型,而非void。為了避免出現混亂,在編寫C/C++程序時,必須對任何函數都指定其返回值類型。如果函數沒有返回值,則要聲明為void。這既保證了程序良好的可讀性,也滿足了編程規(guī)范性的要求。Add
(
int
a,
int
b
);
int
main()
{
printf
(
“2
+
3
=
%d",
Add
(
2,
3)
);
return
0;
}
Add
(
int
a,
int
b
)
{
return
a
+
b;
}
17:正確區(qū)分void與void*(2)如果函數無參數,那么聲明函數參數為void。如右圖代碼段,在C++編譯器中編譯代碼時則會出錯,提示“'TestFunction':functiondoesnottake1parameters”。在C/C++中,若函數不接受任何參數,一定要指明參數為void。int
TestFunction(void)int
TestFunction()//
{
return
1;
}
int
main()
{
int
thisYear
=
TestFunction(2);
//
processing
code
return
0;
}
17:正確區(qū)分void與void*(3)特殊指針類型void*。按照ANSI標準,對void指針進行算術操作是不合法的:ANSI標準之所以這樣認定,是因為只有在確定了指針指向數據類型的大小之后,才能進行算術操作。但是大名鼎鼎的GNU則有不同的規(guī)定,它指定void*的算法操作與char*一致。所以在上面代碼片段中出現錯誤的代碼在GNU編譯器中能順利通過編譯,并且能正確執(zhí)行。雖然GNU較ANSI更開放,提供了對更多語法的支持,但是ANSI標準更加通用,更加“標準”,所以在實際設計中,還是應該盡可能地迎合ANSI標準。void
*
pVoid;
pVoid
++;
//
VC++錯誤,error
C2036:
“pVoid*”:
未知的大小(GNU,正確)
pVoid
+=
1;
//
VC++錯誤
(GNU,正確)
int
*
pInt;
pInt
++;
//
正確,pInt指針增大sizeof(int)
pInt
+=
2;
//正確,
pInt指針增大2*sizeof(int)17:正確區(qū)分void與void*(3)特殊指針類型void*。在實際的程序設計中,為迎合ANSI標準,并提高程序的可移植性,可以采用以下方式進行代碼設計:(4)如果函數的參數可以是任意類型指針,那么應聲明其參數為void*.最典型的例子就是我們熟知的內存操作函數memcpy和memset的原型:任何類型的指針都可以傳入memcpy和memset中,傳出的則是一塊沒有具體數據類型規(guī)定的內存,這也真實地體現了內存操作函數的意義。如果類型不是void*,而是char*,那么這樣的memcpy和memset函數就會與數據類型產生明顯聯系,糾纏不清。void
*
pVoid;
(char
*)pVoid
++;
//
ANSI:正確;GNU:正確
(char
*)pVoid
+=
2;
//
ANSI:錯誤;GNU:正確
void
*
memcpy(void
*dest,
const
void
*src,
size_t
len);
void
*
memset
(
void
*
buffer,
int
c,
size_t
num
);
17:正確區(qū)分void與void*(5)void不能代表一個真實的變量。Void體現了一種抽象,他的出現只是因為一種抽象的需要,如果你正確的理解了面向對象的抽象基類的概念,也就很容易理解void數據類型。正如不能給一個抽象基類定義實例一樣,我們不能定義void變量。void與void*是一對極易混淆的雙胞胎兄弟,但是它們在骨子里卻存在著質的不同,區(qū)分它們,按照一定的規(guī)則使用它們,可以提高程序的可讀性、可移植性。void
a;
Function(voida)
//
錯誤
18:如何判定變量是否相等判斷兩個變量是否相等,有兩點非常重要:一是兩個變量分別都是什么類型。首先兩個變量的類型應該是一致的,如果不一致,那么判定其是否相等則無疑義。二是變量相等的判斷依據是什么?判斷兩個變量是否相等時,兩個變量類型必須相同。每種類型的變量,其判定依據各不相同,有些可判斷,有些無法判斷是否相等。只有那些允許判定是否相等的變量才可以判斷是否相等。變量類型:布爾變量、整型變量、浮點型變量、字符型變量、指針變量。18:如何判定變量是否相等Bool變量一般描述某一操作執(zhí)行成功與否,成功返回true,否則返回false。布爾類型的變量一般無法判定是否相等,判斷兩個布爾變量是否相等沒有意義。整形值包括(unsigned)char,short,int這幾種數據類型。判定兩個整型變量是否相等,一般包括以下兩個步驟:1、應保證兩個整型值為同一類型,如果不同,C++編譯器會默認將類型階低的變量轉換為階高的變量。即存在潛在的隱式轉換,會帶來意想不到的麻煩。(p78)而顯示轉換更不可取。2、如果類型同,通過“==”操作符即可進行兩個整型變量是否相等的判定。18:如何判定變量是否相等浮點型變量包括單精度浮點型和雙精度浮點型。浮點型的比較不能通過簡單的“==”進行判定,必須通過差值的絕對值的精度判定。這是由浮點數據在內存中存放的格式決定的。一個字符一般由多個字符組成。兩個字符串相等要滿足兩個條件:一是字符串的長度必須相等;而是兩個字符串的每個字符必須相等。18:如何判定變量是否相等C語言標準庫提供幾個標準函數,可比較兩個字符串是否相等,它們是strmp和strmpi.。strcmp對兩個字符串進行大小寫敏感的比較,strcmpi對兩個字符串進行大小寫不敏感的比較。指針變量是C++中功能最強大,也是出現問題最多的地方,比較時,必須保證是同一類型的指針變量。且無法進行大于小于的比較。18:如何判定變量是否相等#include<iostream>usingnamespacestd;intmain(){char
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
- 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
- 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025年度智能電網建設股東合作協議3篇
- 電影劇組化妝師聘用合同
- 城市監(jiān)控系統(tǒng)防水涂料施工合同
- 保險公司租賃合同
- 太陽能電站水電路施工合同
- 2024年跨境投資代持協議書3篇
- 國有企業(yè)采購項目招標
- 臨時珠寶鑒定師聘用協議模板
- 跨界合作項目櫥窗施工合同
- 醫(yī)療診所場所租賃合約
- 政治-2025年八省適應性聯考模擬演練考試暨2025年四川省新高考教研聯盟高三年級統(tǒng)一監(jiān)測試題和答案
- 2024年中國醫(yī)藥研發(fā)藍皮書
- 坍塌、垮塌事故專項應急預案(3篇)
- 品管圈PDCA獲獎案例-心內科降低心肌梗死患者便秘發(fā)生率醫(yī)院品質管理成果匯報
- 2023年初級會計師《初級會計實務》真題及答案
- 2024-2025學年三年級上冊道德與法治統(tǒng)編版期末測試卷 (有答案)
- 2025蛇年學校元旦聯歡晚會模板
- 2024版《糖尿病健康宣教》課件
- 期末 (試題) -2024-2025學年人教PEP版英語四年級上冊
- GB/T 44127-2024行政事業(yè)單位公物倉建設與運行指南
- 2021-2022學年遼寧省大連市沙河口區(qū)北師大版五年級上冊期末測試數學試卷【含答案】
評論
0/150
提交評論