c++ 盡量不要使用#define 而是用const、enum、inline替換。
更新時(shí)間:2013年01月02日 16:23:35 作者:
為什么這么說(shuō)呢?或許很多程序員已經(jīng)習(xí)慣在文件開始使用大量的#define語(yǔ)句
例如:這里程序文件開頭有如下#define語(yǔ)句
#define N 10
#define PI 3.14
#define MAX 10000
#define Heigth 6.65
...
...
假設(shè)這里程序運(yùn)行出錯(cuò)誤,而且就是在我們使用這些常量有錯(cuò)誤,此時(shí)編輯器應(yīng)該會(huì)拋出錯(cuò)誤信息。如果該信息提示6.65這里有錯(cuò)誤,Ok如果你運(yùn)氣好你正好記得或者程序簡(jiǎn)單一眼能找到6.65表示什么,如果程序很復(fù)雜,而且報(bào)出6.65的文件是引用該文件,不記得,那么你會(huì)困惑這是什么?或許會(huì)花大量時(shí)間去追蹤6.65是什么?進(jìn)而定位問(wèn)題。
為什么錯(cuò)誤信息是6.65呢?而不是Heith呢?因?yàn)樵陬A(yù)編譯階段,已經(jīng)用5.65來(lái)代替Heigth,Height沒(méi)有進(jìn)入記號(hào)表(system table)內(nèi)。解決之道是可以使用下面語(yǔ)句進(jìn)行替換
const double treeHeight=5.68;
作為一個(gè)語(yǔ)言常量,treeHeight肯定會(huì)被編譯器獲知,并且進(jìn)入記號(hào)表內(nèi)。報(bào)出的錯(cuò)誤不在是數(shù)字而是變量名稱,這樣有利于定位問(wèn)題。
這里特別說(shuō)明一下常量替換#define有兩種特殊情況。
第一個(gè)是定義常量指針。這里要將指針定義為常量指針,同時(shí)該指針也是指向一個(gè)常量,所以是下面的形式:
const char * const HZ="Hang Zhou";
在C++中最好使用string對(duì)象來(lái)替換掉char*形式:
const std::string HZ ("Hang Zhou");
第二個(gè)值得注意的就是class專屬常量。首先將作用于限制到類內(nèi),必須將其聲明為其成員。其次確保此常量至多只有一份實(shí)體,必須讓它稱為static成員。例如:
class People
{
private:
static const int Number=10;
int phoneNumbers[Number];
......
}
這看到的是聲明式,而非定義式。通常C++要求你對(duì)使用的任何東西提供一個(gè)定義式。 或者使用enum,對(duì)于形式函數(shù)的宏,盡可能用inline或者template來(lái)代替。但是如果它是個(gè)class專屬常量又是static且為整數(shù)類型(int,char,bool)則需特殊處理。只要不娶它們地址,則只用聲明而不用提供定義式子。但是如果取class專屬常量地址,縱使不取其地址編譯器就要你提供定義式子。
static const int People::Number
這里定義不設(shè)初始值,是因?yàn)槁暶鞯臅r(shí)候已經(jīng)獲取了初值。
這里可以使用enum完成類似的功能
class People
{
private:
enum { Number = 10 };
int phoneNumbers[Number];
....
}
enum比較像#define而不像const。因?yàn)槿onst的地址是合法的,取一個(gè)enum的地址就不合法,取#define地址通常就不合法。所以可以通過(guò)enum來(lái)實(shí)現(xiàn)不讓他人取得某個(gè)常量的地址。
下面介紹一道筆試題目
#define PRODUCT(a,b) a*b
....
int a=5,b=3,c;
c=PRODUCT(a+3,b+4);
那么c的值為多少?c=5+3*3+4=18而不是程序員預(yù)想的56,如果想達(dá)到預(yù)期的結(jié)果必須這樣寫
#define PRODUCT(a,b) ((a)*(b))
或許這樣你還會(huì)堅(jiān)持會(huì)寫宏函數(shù),因?yàn)槟阆胝f(shuō)只用寫一個(gè)宏函數(shù)就能完成int,flaot,double等類型的乘積運(yùn)算。那么在看看下面例子
#define MAX(a,b) ((a)>(b)?(a):(b))
int a=5,b=3
MAX(++a,b); //a被加了兩次
MAX(++a,b+4); //a被加了一次
a被加的結(jié)果可能不是預(yù)期的,完全可以用template inline函數(shù)達(dá)到宏的預(yù)期效果,并且效率與宏差不多。
template<typename T>
inline void Max(const T& a,const T& b)
{
f(a>b?a:b);
}
inline函數(shù)是一種編譯機(jī)制,有點(diǎn)從代碼上是看不出來(lái)的,但是從程序的執(zhí)行效率上有差別,通常編譯器對(duì)函數(shù)調(diào)用的處理是一種類似中斷的方式,即當(dāng)執(zhí)行到函數(shù)調(diào)用語(yǔ)句時(shí),會(huì)將當(dāng)前所有信息存儲(chǔ)到寄存器中,在去執(zhí)行函數(shù)的代碼,執(zhí)行完后取回寄存器值,恢復(fù)到調(diào)用函數(shù)開始的狀態(tài),繼續(xù)執(zhí)行代碼。聲明為 inline 函數(shù)后,編譯器不把函數(shù)編譯成調(diào)用函數(shù)而是將代碼拷貝到被調(diào)用的地方。所以效率上要比普通函數(shù)高一些,少了存寄存器與取寄存器信息的步驟。
另外需要注意的是inline函數(shù)最好寫到.h文件中去,或者直接寫到類中去。
const允許程序員指定一個(gè)語(yǔ)義約束,即不能被改動(dòng),編譯器會(huì)強(qiáng)制實(shí)施這項(xiàng)約束。它表示被它修飾的值是不變的。const可以在classes外部修飾global或namespace作用域中的常量,或修飾文件、函數(shù)或者static對(duì)象以及指針。在const應(yīng)用在指針中要注意關(guān)鍵字const出現(xiàn)在“*”的什么地方,如果在左邊表示被指向的值是常量,如果在右邊表示指針自身是常量。出現(xiàn)兩邊表示兩者的功能的并集。這里特別說(shuō)以下幾點(diǎn):
(1)迭代器中的cosnt
const std::vector<int>::iterator iter=vec.begin(); //相當(dāng)于iter不能改變
std::vector<int>::const_iterator citer=vec.begin(); //iter所指向的內(nèi)容無(wú)法改變
(2)將函數(shù)返回值聲明為常量,不僅可以降低因程序員錯(cuò)誤造成的不可預(yù)料的情況,并且不用放棄安全性和高效性。例如:
const operater *(const &lhs,const &rhs);
if((a * b = c);//本意是if(a*b==c)因程序員馬虎而寫成這樣
如果a和b都是內(nèi)置類型,此代碼就不合理,但是是我們自定義類型,就可能行得通,如果聲明返回值是cosnt,就可以預(yù)防這么做了。
(3)const成員函數(shù),它是為了確認(rèn)成員函數(shù)可作用于const對(duì)象。而且兩個(gè)成員函數(shù)如果只是常量性不同,是可以重載的。成員函數(shù)后面跟const,表示該函數(shù)不能更改類的成員變量(下面有代碼驗(yàn)證,如果嘗試對(duì)其成員賦值,編譯器會(huì)爆出錯(cuò)誤)。原理就是編譯器將其認(rèn)為是只讀變量。并且大多數(shù)const對(duì)象用于引用傳遞或者指針傳遞。
#include <iostream>
#include <string>
class People
{
public:
People():m_sName(""),m_iAge(0){}
People(std::string name,int age):m_sName(name),m_iAge(age){}
void set(int age)
{
this->m_iAge=age;
}
void set2(int age) const
{
this->m_iAge=age;
}
int get()
{
return this->m_iAge;
}
private:
std::string m_sName;
int m_iAge;
};
int main(int argc,char **argv)
{
People* p=new People("sky",8);
p->set(10);
std::cout<<p->get()<<std::endl;
p->set2(12);
std::cout<<p->get()<<std::endl;
delete p;
return 0;
}
編譯該文件會(huì)報(bào)以下錯(cuò)誤信息
const_test.cpp: In member function `void People::set2(int) const':
const_test.cpp:16: error: assignment of data-member `People::m_iAge' in read-only structure
const_test.cpp:36:2: warning: no newline at end of file
cosnt重載(注意:僅當(dāng)形參是引用或者指針時(shí)候,形參是否為const才會(huì)有影響)??梢栽囈辉囄覀儗⑾旅娲a的&去掉,傳入const_int其實(shí)調(diào)用的是void set(int age)函數(shù),說(shuō)明形參的const是沒(méi)有起作用的。下面是驗(yàn)證代碼
#include <iostream>
#include <string>
class People
{
public:
People():m_sName(""),m_iAge(0){}
People(std::string name,int age):m_sName(name),m_iAge(age){}
void set(const int& age) const
{
std::cout<<"this is const"<<std::endl;
}
void test(int& age)
{
std::cout<<"this is non-const"<<std::endl;
}
void test(short age)
{
std::cout<<"this is non-const"<<std::endl;
}
int get()
{
return this->m_iAge;
}
private:
std::string m_sName;
int m_iAge;
};
int main(int argc,char **argv)
{
People* p=new People("sky",8);
const int const_int=12;
p->test(const_int);
std::cout<<p->get()<<std::endl;
delete p;
}
(4)關(guān)于重載函數(shù)代碼重復(fù)的問(wèn)題。由經(jīng)驗(yàn)可以得出我們通過(guò)const重載的函數(shù)往往有大量的代碼是重復(fù),甚至是一樣的。如果大部分重復(fù)代碼,我們可以將這些重復(fù)的代碼寫成一個(gè)函數(shù),在由它們分別調(diào)用。如果是一樣的,如下代碼,我們就可以在non-const函數(shù)中調(diào)用const函數(shù),來(lái)解決代碼重復(fù)。
class People
{
public:
People():m_sName(""),m_iAge(0){}
People(std::string name,int age):m_sName(name),m_iAge(age){}
void eat(const People & Person) const
{
std::cout<<"this person info is:{age ="<<Person.m_iAge()<<",name ="<<Person.m_sName()<<std::endl;
std::cout<<"eating"<<std::endl;
std::cout<<"end"<<std::endl;
}
void eat ( People & Person)
{
std::cout<<"this person info is:{age ="<<Person.m_iAge()<<",name ="<<Person.m_sName()<<std::endl;
std::cout<<"eating"<<std::endl;
std::cout<<"end"<<std::endl;
}
private:
std::string m_sName;
int m_iAge;
};
然后在non-const eat函數(shù)中首先將*this類型由People&顯示的轉(zhuǎn)化為const People&讓其調(diào)用const函數(shù),即函數(shù)重載
#include <iostream>
#include <string>
class People
{
public:
People():m_sName(""),m_iAge(0){}
People(std::string name,int age):m_sName(name),m_iAge(age){}
void eat(const People & Person) const
{
std::cout<<"this person info is:{age ="<<Person.m_iAge<<",name ="<<Person.m_sName<<"}"<<std::endl;
std::cout<<"eating"<<std::endl;
std::cout<<"end"<<std::endl;
}
void eat(People & Person)
{
static_cast<const People&>(*this).eat(Person);
}
private:
std::string m_sName;
int m_iAge;
};
int main(int argc,char **argv)
{
People Person("sky",8);
Person.eat(Person);
}
運(yùn)行的結(jié)果為
this person info is:{age =8,name =sky
eating
end
復(fù)制代碼 代碼如下:
#define N 10
#define PI 3.14
#define MAX 10000
#define Heigth 6.65
...
...
假設(shè)這里程序運(yùn)行出錯(cuò)誤,而且就是在我們使用這些常量有錯(cuò)誤,此時(shí)編輯器應(yīng)該會(huì)拋出錯(cuò)誤信息。如果該信息提示6.65這里有錯(cuò)誤,Ok如果你運(yùn)氣好你正好記得或者程序簡(jiǎn)單一眼能找到6.65表示什么,如果程序很復(fù)雜,而且報(bào)出6.65的文件是引用該文件,不記得,那么你會(huì)困惑這是什么?或許會(huì)花大量時(shí)間去追蹤6.65是什么?進(jìn)而定位問(wèn)題。
為什么錯(cuò)誤信息是6.65呢?而不是Heith呢?因?yàn)樵陬A(yù)編譯階段,已經(jīng)用5.65來(lái)代替Heigth,Height沒(méi)有進(jìn)入記號(hào)表(system table)內(nèi)。解決之道是可以使用下面語(yǔ)句進(jìn)行替換
const double treeHeight=5.68;
作為一個(gè)語(yǔ)言常量,treeHeight肯定會(huì)被編譯器獲知,并且進(jìn)入記號(hào)表內(nèi)。報(bào)出的錯(cuò)誤不在是數(shù)字而是變量名稱,這樣有利于定位問(wèn)題。
這里特別說(shuō)明一下常量替換#define有兩種特殊情況。
第一個(gè)是定義常量指針。這里要將指針定義為常量指針,同時(shí)該指針也是指向一個(gè)常量,所以是下面的形式:
const char * const HZ="Hang Zhou";
在C++中最好使用string對(duì)象來(lái)替換掉char*形式:
const std::string HZ ("Hang Zhou");
第二個(gè)值得注意的就是class專屬常量。首先將作用于限制到類內(nèi),必須將其聲明為其成員。其次確保此常量至多只有一份實(shí)體,必須讓它稱為static成員。例如:
復(fù)制代碼 代碼如下:
class People
{
private:
static const int Number=10;
int phoneNumbers[Number];
......
}
這看到的是聲明式,而非定義式。通常C++要求你對(duì)使用的任何東西提供一個(gè)定義式。 或者使用enum,對(duì)于形式函數(shù)的宏,盡可能用inline或者template來(lái)代替。但是如果它是個(gè)class專屬常量又是static且為整數(shù)類型(int,char,bool)則需特殊處理。只要不娶它們地址,則只用聲明而不用提供定義式子。但是如果取class專屬常量地址,縱使不取其地址編譯器就要你提供定義式子。
static const int People::Number
這里定義不設(shè)初始值,是因?yàn)槁暶鞯臅r(shí)候已經(jīng)獲取了初值。
這里可以使用enum完成類似的功能
復(fù)制代碼 代碼如下:
class People
{
private:
enum { Number = 10 };
int phoneNumbers[Number];
....
}
enum比較像#define而不像const。因?yàn)槿onst的地址是合法的,取一個(gè)enum的地址就不合法,取#define地址通常就不合法。所以可以通過(guò)enum來(lái)實(shí)現(xiàn)不讓他人取得某個(gè)常量的地址。
下面介紹一道筆試題目
復(fù)制代碼 代碼如下:
#define PRODUCT(a,b) a*b
....
int a=5,b=3,c;
c=PRODUCT(a+3,b+4);
那么c的值為多少?c=5+3*3+4=18而不是程序員預(yù)想的56,如果想達(dá)到預(yù)期的結(jié)果必須這樣寫
#define PRODUCT(a,b) ((a)*(b))
或許這樣你還會(huì)堅(jiān)持會(huì)寫宏函數(shù),因?yàn)槟阆胝f(shuō)只用寫一個(gè)宏函數(shù)就能完成int,flaot,double等類型的乘積運(yùn)算。那么在看看下面例子
#define MAX(a,b) ((a)>(b)?(a):(b))
int a=5,b=3
MAX(++a,b); //a被加了兩次
MAX(++a,b+4); //a被加了一次
a被加的結(jié)果可能不是預(yù)期的,完全可以用template inline函數(shù)達(dá)到宏的預(yù)期效果,并且效率與宏差不多。
復(fù)制代碼 代碼如下:
template<typename T>
inline void Max(const T& a,const T& b)
{
f(a>b?a:b);
}
inline函數(shù)是一種編譯機(jī)制,有點(diǎn)從代碼上是看不出來(lái)的,但是從程序的執(zhí)行效率上有差別,通常編譯器對(duì)函數(shù)調(diào)用的處理是一種類似中斷的方式,即當(dāng)執(zhí)行到函數(shù)調(diào)用語(yǔ)句時(shí),會(huì)將當(dāng)前所有信息存儲(chǔ)到寄存器中,在去執(zhí)行函數(shù)的代碼,執(zhí)行完后取回寄存器值,恢復(fù)到調(diào)用函數(shù)開始的狀態(tài),繼續(xù)執(zhí)行代碼。聲明為 inline 函數(shù)后,編譯器不把函數(shù)編譯成調(diào)用函數(shù)而是將代碼拷貝到被調(diào)用的地方。所以效率上要比普通函數(shù)高一些,少了存寄存器與取寄存器信息的步驟。
另外需要注意的是inline函數(shù)最好寫到.h文件中去,或者直接寫到類中去。
const允許程序員指定一個(gè)語(yǔ)義約束,即不能被改動(dòng),編譯器會(huì)強(qiáng)制實(shí)施這項(xiàng)約束。它表示被它修飾的值是不變的。const可以在classes外部修飾global或namespace作用域中的常量,或修飾文件、函數(shù)或者static對(duì)象以及指針。在const應(yīng)用在指針中要注意關(guān)鍵字const出現(xiàn)在“*”的什么地方,如果在左邊表示被指向的值是常量,如果在右邊表示指針自身是常量。出現(xiàn)兩邊表示兩者的功能的并集。這里特別說(shuō)以下幾點(diǎn):
(1)迭代器中的cosnt
const std::vector<int>::iterator iter=vec.begin(); //相當(dāng)于iter不能改變
std::vector<int>::const_iterator citer=vec.begin(); //iter所指向的內(nèi)容無(wú)法改變
(2)將函數(shù)返回值聲明為常量,不僅可以降低因程序員錯(cuò)誤造成的不可預(yù)料的情況,并且不用放棄安全性和高效性。例如:
const operater *(const &lhs,const &rhs);
if((a * b = c);//本意是if(a*b==c)因程序員馬虎而寫成這樣
如果a和b都是內(nèi)置類型,此代碼就不合理,但是是我們自定義類型,就可能行得通,如果聲明返回值是cosnt,就可以預(yù)防這么做了。
(3)const成員函數(shù),它是為了確認(rèn)成員函數(shù)可作用于const對(duì)象。而且兩個(gè)成員函數(shù)如果只是常量性不同,是可以重載的。成員函數(shù)后面跟const,表示該函數(shù)不能更改類的成員變量(下面有代碼驗(yàn)證,如果嘗試對(duì)其成員賦值,編譯器會(huì)爆出錯(cuò)誤)。原理就是編譯器將其認(rèn)為是只讀變量。并且大多數(shù)const對(duì)象用于引用傳遞或者指針傳遞。
復(fù)制代碼 代碼如下:
#include <iostream>
#include <string>
class People
{
public:
People():m_sName(""),m_iAge(0){}
People(std::string name,int age):m_sName(name),m_iAge(age){}
void set(int age)
{
this->m_iAge=age;
}
void set2(int age) const
{
this->m_iAge=age;
}
int get()
{
return this->m_iAge;
}
private:
std::string m_sName;
int m_iAge;
};
int main(int argc,char **argv)
{
People* p=new People("sky",8);
p->set(10);
std::cout<<p->get()<<std::endl;
p->set2(12);
std::cout<<p->get()<<std::endl;
delete p;
return 0;
}
編譯該文件會(huì)報(bào)以下錯(cuò)誤信息
const_test.cpp: In member function `void People::set2(int) const':
const_test.cpp:16: error: assignment of data-member `People::m_iAge' in read-only structure
const_test.cpp:36:2: warning: no newline at end of file
cosnt重載(注意:僅當(dāng)形參是引用或者指針時(shí)候,形參是否為const才會(huì)有影響)??梢栽囈辉囄覀儗⑾旅娲a的&去掉,傳入const_int其實(shí)調(diào)用的是void set(int age)函數(shù),說(shuō)明形參的const是沒(méi)有起作用的。下面是驗(yàn)證代碼
復(fù)制代碼 代碼如下:
#include <iostream>
#include <string>
class People
{
public:
People():m_sName(""),m_iAge(0){}
People(std::string name,int age):m_sName(name),m_iAge(age){}
void set(const int& age) const
{
std::cout<<"this is const"<<std::endl;
}
void test(int& age)
{
std::cout<<"this is non-const"<<std::endl;
}
void test(short age)
{
std::cout<<"this is non-const"<<std::endl;
}
int get()
{
return this->m_iAge;
}
private:
std::string m_sName;
int m_iAge;
};
int main(int argc,char **argv)
{
People* p=new People("sky",8);
const int const_int=12;
p->test(const_int);
std::cout<<p->get()<<std::endl;
delete p;
}
(4)關(guān)于重載函數(shù)代碼重復(fù)的問(wèn)題。由經(jīng)驗(yàn)可以得出我們通過(guò)const重載的函數(shù)往往有大量的代碼是重復(fù),甚至是一樣的。如果大部分重復(fù)代碼,我們可以將這些重復(fù)的代碼寫成一個(gè)函數(shù),在由它們分別調(diào)用。如果是一樣的,如下代碼,我們就可以在non-const函數(shù)中調(diào)用const函數(shù),來(lái)解決代碼重復(fù)。
復(fù)制代碼 代碼如下:
class People
{
public:
People():m_sName(""),m_iAge(0){}
People(std::string name,int age):m_sName(name),m_iAge(age){}
void eat(const People & Person) const
{
std::cout<<"this person info is:{age ="<<Person.m_iAge()<<",name ="<<Person.m_sName()<<std::endl;
std::cout<<"eating"<<std::endl;
std::cout<<"end"<<std::endl;
}
void eat ( People & Person)
{
std::cout<<"this person info is:{age ="<<Person.m_iAge()<<",name ="<<Person.m_sName()<<std::endl;
std::cout<<"eating"<<std::endl;
std::cout<<"end"<<std::endl;
}
private:
std::string m_sName;
int m_iAge;
};
然后在non-const eat函數(shù)中首先將*this類型由People&顯示的轉(zhuǎn)化為const People&讓其調(diào)用const函數(shù),即函數(shù)重載
復(fù)制代碼 代碼如下:
#include <iostream>
#include <string>
class People
{
public:
People():m_sName(""),m_iAge(0){}
People(std::string name,int age):m_sName(name),m_iAge(age){}
void eat(const People & Person) const
{
std::cout<<"this person info is:{age ="<<Person.m_iAge<<",name ="<<Person.m_sName<<"}"<<std::endl;
std::cout<<"eating"<<std::endl;
std::cout<<"end"<<std::endl;
}
void eat(People & Person)
{
static_cast<const People&>(*this).eat(Person);
}
private:
std::string m_sName;
int m_iAge;
};
int main(int argc,char **argv)
{
People Person("sky",8);
Person.eat(Person);
}
運(yùn)行的結(jié)果為
this person info is:{age =8,name =sky
eating
end
相關(guān)文章
C++數(shù)據(jù)結(jié)構(gòu)之雙向鏈表
這篇文章主要為大家詳細(xì)介紹了C++數(shù)據(jù)結(jié)構(gòu)之雙向鏈表,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05字典樹的基本知識(shí)及使用C語(yǔ)言的相關(guān)實(shí)現(xiàn)
這篇文章主要介紹了字典樹的基本知識(shí)及使用C語(yǔ)言的相關(guān)實(shí)現(xiàn),這也是ACM等計(jì)算機(jī)考試和競(jìng)賽題目的基本知識(shí),需要的朋友可以參考下2015-08-08C++中priority_queue的使用與模擬實(shí)現(xiàn)
本文主要介紹了C++中priority_queue的使用與模擬實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02