必須掌握的C++常用關鍵字彙總
本文將對一些常見c++關鍵字做一個總結,共包括如下關鍵字:
const、extern、operator、sizeof、-static、new、volatile、union
一、const
1、定義常量
const修飾變數,以下兩種定義形式在本質上是一樣的。它的含義是:const修飾的型別為TYPE的變數value是不可變的。
TYPE const ValueName = value;
const TYPE ValueName = value;
1.1、const與巨集的區別
- 巨集是簡單的機械替換,不會存在型別檢查等工作;
- 但是const變數不一樣,這個存在型別檢查,所以更安全一些。
1.2、const變數儲存位置
- const全域性變數儲存在只讀資料段,編譯期最初將其儲存在符號表中,第一次使用時為其分配記憶體,在程式結束時釋放;
- const區域性變數儲存在棧中,程式碼塊結束時釋放,例如:val_j。
2、指標使用CONST
2.1、指標指向的值不可變
如果const位於星號的左側,則const就是用來修飾指標所指向的變數,即指標指向為常量。
char const * pContent;
const char * pContent;
舉個貼切點的例子,如果a是一名倉庫管理員的話,他所管理的倉庫,裡面的貨物(*a)是他沒有許可權更改的,倉庫裡的東西是什麼就一直是什麼。因此 :
int b=100;
const int*a=&b;
*a=200; //錯誤
怎麼改變值:
- 代打。自己不去改變,但別人可改變,可以讓別人去呀,最終值也是改變了;
- 換工作。自己不想幹當下得了,換個工作嘛,從新指向一個新的值。
代打是什麼意思了?首先回顧下指向常量的指標是什麼意思。
所謂的指向常量的指標僅僅要求的是,不能通過該指標改變物件的值,沒有規定此物件的值不能通過其他途徑更改。自己不幹壞事,但是別人你管不了啊。
也可以這樣想:所謂指向常量的指標或引用,不過是指標或引用“自以為是”罷了,它們是好人,不做壞事。他們覺得指向了常量,所以自覺地不去改變所指物件的值。
因此想改變*a的值,可以這樣:
int b=100;
const int *a=&b;
b=200;
cout<<*a<<endl; //得到200
換工作。要想改變*a的值還有一種方式,因為當前情況為指標指向的為常量,也就是說你指向的東西不能改變,但不代表你不能指向其它物件呀。
如果你覺得管理水果這活太累,你可以去管理蔬菜嘛,換一個倉庫不就行了。
int b=100,c=200;
const int *a=&b;
a=&c;
cout<<*a<<endl; //得到200
2.2、指標本身是常量不可變
如果const位於星號得右側,const就是修飾指標本身,即指標本身是常量。
char * const pContent;
指標本身為常量,不能對指標本身進行更改操作。
int b = 100,c=200;
int *const a = &b;
a = &c; //錯誤
cot << *a << endl;
舉個例子,a是一名倉庫管理員,分配他看守一個倉庫,那就是終身製得了,他就只能控制這個倉庫,要是去管其他倉庫就是錯誤的,因此a++這種操作也是錯誤的;
但是,對於倉庫裡的東西,他是可以隨便換的,想放水果就放水果,想放蔬菜就放蔬菜,(*a=200這種更改操作是允許的)。
2.3、兩者皆不可變
cosnt char * const pContent;
指標本身和所指向的內容均為常量。
int b = 100,c=200;
const int *const a = &b;
a = &c; //錯誤
*a = 200; //錯誤
cout << *a << endl;
a還是倉庫管理員,這個時候,他就是地地道道的看門的了,一點權力都沒有,他不能換工作,也不能動倉庫裡的東西。
3、函式中使用CONST
3.1、const修飾函式引數
1)傳遞過來的引數在函式內不可以改變
void function(const int Var);
如果在函式內試圖修改var的值,會出現錯誤,“表示式必須是可修改的左值”
2)引數指標所指內容為常量不可變
void function(const char* Var);
同樣,函式內改變指標指向的值,會出現錯誤,“表示式必須是可修改的左值”
3)引數指標本身為常量不可變
void function(char* const Var);
函式內改變指標的值,會出現錯誤,“表示式必須是可修改的左值”
4)引數為引用,為了增加效率同時防止修改。修飾引用引數時:
void function(const Class& Var); //引用引數在函式內不可以改變
如果A是我們自定義的一個數據型別,也就是非內部資料型別,當我們按照值傳遞的方式傳遞引數引數時,效率將非常低下。因為函式將複製一個該引數的臨時變數,而臨時變數的構造、複製、析構都很消耗時間。
引用傳遞傳的是地址不需要複製引數。
但是正因為傳的是地址,又存在一個隱患,引數的值有可能被我們改變。
為了解決這個問題,我們需要加上const修飾引數,因此函式最終成為void Func(const A&a);這是引數是非內部資料型別的情況,如果引數是內部型別,那就不用採用引用傳遞了,直接值傳遞就可以了,因為內部資料型別不存在構造、析構的過程,複製也很快。
3.2、const 修飾函式返回值
1)以“指標傳遞”方式的函式返回值加const 修飾
如果給以“指標傳遞”方式的函式返回值加const修飾,那麼函式返回值(即指標)的內容不能被修改,該返回值只能被賦給加const 修飾的同類型指標。
例如函式:
const char * GetString(void);
如下語句將出現編譯錯誤:
char *str = GetString();
正確的用法是
const char *str = GetString();
2)以“值傳遞”方式的函式返回值加const修飾
如果函式返回值採用“值傳遞方式”,由於函式會把返回值複製到外部臨時的儲存單元中,加const 修飾沒有任何價值。
例如:
不要把函式int GetInt(void) 寫成const int GetInt(void)。
同理,
不要把函式A GetA(void) 寫成const A GetA(void),其中A 為使用者自定義的資料型別。
如果返回值不是內部資料型別,將函式A GetA(void) 改寫為const A & GetA(void)的確能提高效率。但此時千萬千萬要小心,一定要搞清楚函式究竟是想返回一個物件的“拷貝”還是僅返回“別名”就可以了,否則程式會出錯。
函式返回值採用“引用傳遞”的場合並不多,這種方式一般只出現在類的賦值函式中,目的是為了實現鏈式表達。
4、類相關CONST
4.1、const修飾成員變數
const修飾類的成員函式,表示成員常量,不能被修改,同時它只能在初始化列表中賦值。
class A
{
…
const int nValue; //成員常量不能被修改
…
A(int x): nValue(x) { } ; //只能在初始化列表中賦值
}
4.2、const修飾成員函式
任何不會修改資料成員的函式都應該宣告為const 型別。如果在編寫const成員函式時,不慎修改了資料成員,或者呼叫了其它非const 成員函式,編譯器將指出錯誤,這無疑會提高程式的健壯性。
一般寫在函式的最後來修飾。
class A
{
…
void function()const; //常成員函式, 它不改變物件的成員變數.
//也不能呼叫類中任何非const成員函式。
}
關於Const函式的幾點規則:
a. const物件只能訪問const成員函式,而非const物件可以訪問任意的成員函式,包括const成員函式.
b. const物件的成員是不可修改的,然而const物件通過指標維護的物件卻是可以修改的.
c. const成員函式不可以修改物件的資料,不管物件是否具有const性質.它在編譯時,以是否修改成員資料為依據,進行檢查.
e. 然而加上mutable修飾符的資料成員,對於任何情況下通過任何手段都可修改,自然此時的const成員函式是可以修改它的。
4.3、const修飾類物件/物件指標/物件引用
const修飾類物件表示該物件為常量物件,其中的任何成員都不能被修改。對於物件指標和物件引用也是一樣。
const修飾的物件,該物件的任何非const成員函式都不能被呼叫,因為任何非const成員函式會有修改成員變數的企圖。
5、將const轉化為非const
採用const_cast 進行轉換。
用法:const_cast (expression)
二、extern
extern可以置於變數或者函式前,以標示變數或者函式的定義在別的檔案中,提示編譯器遇到此變數和函式時在其他模組中尋找其定義。此外extern也可用來進行連結指定。
1、作用:
當它與”C”一起連用時,如: extern “C” void fun(int a, int
b);則告訴編譯器在編譯fun這個函式名時按著C的規則去翻譯相應的函式名而不是C++的。當extern不與”C”在一起修飾變數或函式時,如在標頭檔案中: extern int
g_Int;它的作用就是宣告函式或全域性變數的作用範圍的關鍵字,其宣告的函式和變數可以在本模組或其他模組中使用,記住它是一個宣告不是定義!也就是說B模組(編譯單元)要是引用模組(編譯單元)A中定義的全域性變數或函式時,它只要包含A模組的標頭檔案即可,在編譯階段,模組B雖然找不到該函式或變數,但它不會報錯,它會在連線時從模組A生成的目的碼中找到此函式。
2、注意
2.1、extern “C”
在C++環境下使用C函式的時候,常常會出現編譯器無法找到obj模組中的C函式定義,從而導致連結失敗的情況,應該如何解決這種情況呢?
答案與分析:
C++語言在編譯的時候為了解決函式的多型問題,會將函式名和引數聯合起來生成一箇中間的函式名稱,而C語言則不會,因此會造成連結時找不到對應函式的情況,此時C函式就需要用extern “C”進行連結指定,這告訴編譯器,請保持我的名稱,不要給我生成用於連結的中間函式名。
2.2、extern變數
在一個原始檔裡定義了一個數組:char a[6];
在另外一個檔案裡用下列語句進行了宣告:extern char *a;請問,這樣可以嗎?
答案與分析:
不可以,程式執行時會告訴你非法訪問。原因在於,指向型別T的指標並不等價於型別T的陣列。extern char *a宣告的是一個指標變數而不是字元陣列,因此與實際的定義不同,從而造成執行時非法訪問。應該將宣告改為extern char a[ ]。
三、operator
過載的運算子是具有特殊名字的函式,他們的名字由關鍵字operator和其後要定義的運算子共同構成。和其他函式一樣,過載的運算子也包含返回型別、引數列表以及函式體。過載有兩種方式:成員函式和友元函式。成員函式的形式比較簡單,就是在類裡面定義了一個與操作符相關的函式。友元函式因為沒有this指標,所以形參會多一個。
語法形式:
函式型別 operator 運算子(形參表)
{
函式體;
}
1、運算子過載
1.1、關係運算符過載
關係運算符有==,!=,<,>,<=,>=,一般定義為非成員函式。
bool operator == (const A&a1, const A&a2);
bool operator != (const A&a1, const A&a2);
bool operator < (const A&a1, const A&a2);
bool operator <= (const A&a1, const A&a2 );
bool operator > (const A&a1, const A&a2 );
bool operator >= (const A&a1, const A&a2);
1.2、單目運算子過載
A& operator + (const A&a1, const A&a2);
A& operator - (const A&a1, const A&a2);
A* operator & (const A&a1, const A&a2);
A& operator * (const A&a1, const A&a2);
1.3、自增減運算子
注意前置和後置的區分,因為形式都一樣,所以後置帶個引數作為區分。
A& operator ++ ();//前置++
A operator ++ (int);//後置++
A& operator --();//前置--
A operator -- (int);//後置--
1.4、位運算子過載
A operator | (const A&a1, const A&a2);
A operator & (const A&a1, const A&a2);
A operator ^ (const A&a1, const A&a2 );
A operator << (int i);
A operator >> (int i);
A operator ~ ();
1.5、賦值運算子過載
這裡的賦值運算子指的是改變物件狀態的運算子
A& operator += (const A& );
A& operator -= (const A& );
A& operator *= (const A& );
A& operator /= (const A& );
A& operator %= (const A& );
A& operator &= (const A& );
A& operator |= (const A& );
A& operator ^= (const A& );
A& operator <<= (int i);
A& operator >>= (int i);
1.6、記憶體運算子
void *operator new(size_t size);
void *operator new(size_t size, int i);
void *operator new[](size_t size);
void operator delete(void*p);
void operator delete(void*p, int i, int j);
void operator delete [](void* p);
1.7、輸入輸出運算子
與標準庫相容的輸入輸出運算子必須是非成員函式,而為了能訪問類的私有物件,應申明為友元函式。因為如果不這樣的話,他們的左側運算子物件將是一個類物件,這顯然不行。
friend inline ostream &operator << (ostream&, A&);//輸出流
friend inline istream &operator >> (istream&, A&);//輸入流
1.8、不可過載的運算子
1) . (點符號)
2).*
3)::
4)?:
5)sizeof
1.9、不應該過載的運算子
有些運算子可以被過載,但是過載了之後對我們的使用就不太方便了。
1)物件求值順序有關的運算子:邏輯與(&&)、邏輯或(||);
2)特殊含義的運算子:逗號運算子、取地址運算子。
1.10、注意
1)內建型別的運算子無法過載;
2)只能過載已有運算子,不能發明新的運算子;
3)運算子定義成成員函式時,它的左側運算子物件必須是運算子所屬類的一個物件。
例如(string的成員‘+’);
4)過載不改變優先順序與結合性。
string s="asfs";
tring t=s+"sa"; //ok
string u="hi"+s; //error
2、過載的運算子是成員還是非成員函式?
定義過載運算子時,我們需要選擇一下是將其定義為成員運算子還是非成員運算子,有些運算子時必須定義為成員函式的。同時過載運算子作為成員函式和非成員函式時,它的引數也是不一樣的。通常一元運算子有一個引數,二元運算子有兩個引數,但是當過載預案算符是成員函式時,this繫結到左側運算子物件,成員運算子引數減一。
2.1、抉擇:
1)賦值,下標,呼叫,和成員訪問必須是成員;
2)複合賦值運算子一般來說是成員;
3)改變對想狀態的運算子或者給指定型別沒切相關的運算子,如遞增、遞減和解引用等 ,通常應該是成員;
4)具有對稱性的運算子可能轉換為任意一端的運算子物件,例如算數、相等性、關係和位運算子d,應該是非成員函式;
5)輸入輸出運算子應該是非成員函式。
2.2、舉例:
(a) % (b) %= (c) ++ (d) -> (e) << (f) && (g) == (h) ()
(a) %通常定義為非成員。
(b) %=通常定義為類成員,因為它會改變物件的狀態。
(c) ++通常定義為類成員,因為它會改變物件的狀態。
(d) ->必須定義為類成員,否則編譯報錯
(e) <<通常定義為非成員
(f) && 通常定義為非成員。
(g) ==通常定義為非成員。
(h) ()必須定義為類成員,否則編譯會報錯。
3、例項
通過自定義一個String類,名叫MyString,實現一些常用的運算子過載。
3.1、程式碼
class MyString
{
friend ostream &operator<<(ostream &os, MyString&mstr);
friend istream&operator>>(istream &is, MyString&mstr);
friend MyString &operator+(MyString &str1, MyString&str2);
friend bool operator==(MyString &str1, MyString&str2);
friend bool operator!=(MyString &str1, MyString&str2);
public:
//建構函式
MyString():length(0),size(0),mdata(NULL){}
MyString(const char *str);
//拷貝建構函式
MyString(const MyString &other);
//賦值運算子
MyString &operator=(const MyString &other);
//解構函式
~MyString();
char& operator[](int index);
MyString &operator+=(const MyString &str);
private:
int length;
int size;
char *mdata;
};
//建構函式
MyString::MyString(const char *str)
{
if (str != NULL)
{
length= strlen(str);
size = length + 1;
mdata = new char[size];
strcpy_s(mdata,size, str);
}
}
//拷貝建構函式
MyString::MyString(const MyString &other)
{
length = strlen(other.mdata);
size = length + 1;
mdata = new char[size];
strcpy_s(mdata, size, other.mdata);
}
//賦值運算子
MyString & MyString::operator=(const MyString &other)
{
//檢查自賦值
if (this != &other)
{
delete[]mdata;
//分配新的資源,並複製內容
length = strlen(other.mdata);
size = length + 1;
mdata = new char[size];
strcpy_s(mdata, size, other.mdata);
}
return *this;
}
//解構函式
MyString::~MyString()
{
if (mdata != NULL)
{
delete[]mdata;
mdata = NULL;
}
}
ostream &operator<<(ostream &os, MyString&mstr)
{
os << mstr.mdata;
return os;
}
istream&operator>>(istream &is, MyString&mstr)
{
char *temp=new char[1024];
gets_s(temp, strlen(temp));
if (mstr.mdata != NULL)
delete[]mstr.mdata;
mstr.length = strlen(temp);
mstr.size = mstr.length + 1;
mstr.mdata = new char[mstr.size];
strcpy_s(mstr.mdata, mstr.size, temp);
return is;
}
MyString &MyString::operator+=(const MyString &str)
{
this->length = this->length + str.length;
this->size = this->length + 1;
char* buffer = new char[this->size];
strcpy_s(buffer, this->size, this->mdata);
strcat_s(buffer, this->size, str.mdata);
delete[] this->mdata;
this->mdata = buffer;
return *this;
}
MyString &operator+(MyString &str1, MyString&str2)
{
MyString *temp = new MyString();
temp->length = str1.length + str2.length;
temp->size = temp->length + 1;
temp->mdata = new char[temp->size];
strcpy_s(temp->mdata, temp->size, str1.mdata);
strcat_s(temp->mdata, temp->size, str2.mdata);
return *temp;
}
bool operator==(MyString &str1, MyString&str2)
{
if (str1.size != str2.size) return false;
else if (strcmp(str1.mdata, str2.mdata) != 0)
return false;
return true;
}
bool operator!=(MyString &str1, MyString&str2)
{
return !(str1 == str2);
}
char& MyString::operator[](int index)
{
return this->mdata[index];
}
3.2、注意
1)賦值運算子
如果沒有賦值運算子,那麼以下情況就會出現記憶體洩露和double free問題,其實這就是深拷貝與淺拷貝的問題:
MyString str1("test1");
MyString str2("test2");
str1=str2;
因為str2的mdata和str1的mdata指向同一塊記憶體,那麼銷燬物件時就會釋放兩次,還有str2自己的記憶體沒發釋放。因此,必須要有自己定義的賦值運算子。
2)本例子那個輸入運算子並不好,需要更優解決方案。
4、總結
1、我們要注意哪些運算子可過載,那些不可過載;哪些可被過載,但是一般情況下不應該過載
2、過載時,我們應該申明為成員函式,還是非成員函式,兩者有什麼區別。
3、對於輸入輸出運算子要注意,判斷流是否成功,並且輸入時,記憶體的申請等。
四、sizeof
sizeof是一個操作符(operator)。
其作用是返回一個物件或型別所佔的記憶體位元組數。
其返回值型別為size_t。(size_t在標頭檔案stddef.h中定義,它依賴於編譯系統的值,一般定義為 typedef unsigned int size_t;)
1、語法
sizeof有三種語法形式:
1. sizeof (object); //sizeof (物件)
1. sizeof object; //sizeof 物件
1. sizeof (type_name); //sizeof (型別)
物件可以是各種型別的變數,以及表示式(一般sizeof不會對錶達式進行計算)。
sizeof對物件求記憶體大小,最終都是轉換為對物件的資料型別進行求值。
sizeof (表示式); //值為表示式的最終結果的資料型別的大小
例子:(32位機器下)
int i;
sizeof(int); //值為4
sizeof(i); //值為4,等價於sizeof(int)
sizeof i; //值為4
sizeof(2); //值為4,等價於sizeof(int),因為2的型別為int
sizeof(2 + 3.14); //值為8,等價於sizeof(double),因為此表示式的結果的型別為double
char ary[sizeof(int) * 10]; //OK,編譯無誤
最新的C99標準規定sizeof也可以在執行時刻進行計算。
如下面的程式在Dev-C++中可以正確執行:
int n;
n = 10; // n動態賦值
char ary[n]; // C99也支援陣列的動態定義
cout<<sizeof(ary); // ok. 輸出10
但在沒有完全實現C99標準的編譯器中就行不通了,上面的程式碼在VC6中就通不過編譯。所以我們最好還是認為sizeof是在編譯期執行的,這樣不會帶來錯誤,讓程式的可移植性強些。
2、基本資料型別的sizeof
這裡的基本資料型別是指short、int、long、float、double這樣的簡單內建資料型別。
由於它們的記憶體大小是和系統相關的,所以在不同的系統下取值可能不同。
3、結構體的sizeof
結構體的sizeof涉及到位元組對齊問題。
為什麼需要位元組對齊?計算機組成原理教導我們這樣有助於加快計算機的取數速度,否則就得多花指令週期了。為此,編譯器預設會對結構體進行處理(實際上其它地方的資料變數也是如此),讓寬度為2的基本資料型別(short等)都位於能被2整除的地址上,讓寬度為4的基本資料型別(int等)都位於能被4整除的地址上,依次類推。這樣,兩個數中間就可能需要加入填充位元組,所以整個結構體的sizeof值就增長了。
位元組對齊的細節和編譯器的實現相關,但一般而言,滿足三個準則:
1. 結構體變數的首地址能夠被其最寬基本型別成員的大小所整除。
1. 結構體的每個成員相對於結構體首地址的偏移量(offset)都是成員大小的整數倍,如有需要,編譯器會在成員之間加上填充位元組(internal adding)。
1. 結構體的總大小為結構體最寬基本型別成員大小的整數倍,如有需要,編譯器會在最末一個成員後加上填充位元組(trailing padding)。
注意:空結構體(不含資料成員)的sizeof值為1。試想一個“不佔空間“的變數如何被取地址、兩個不同的“空結構體”變數又如何得以區分呢,於是,“空結構體”變數也得被儲存,這樣編譯器也就只能為其分配一個位元組的空間用於佔位了。
例子:
struct S1
{
char a;
int b;
};
sizeof(S1); //值為8,位元組對齊,在char之後會填充3個位元組。
struct S2
{
int b;
char a;
};
sizeof(S2); //值為8,位元組對齊,在char之後會填充3個位元組。
struct S3
{
};
sizeof(S3); //值為1,空結構體也佔記憶體。
4、聯合體的sizeof
結構體在記憶體組織上市順序式的,聯合體則是重疊式,各成員共享一段記憶體;所以整個聯合體的sizeof也就是每個成員sizeof的最大值。
例子:
union u
{
int a;
float b;
double c;
char d;
};
sizeof(u); //值為8
5、陣列的sizeof
陣列的sizeof值等於陣列所佔用的記憶體位元組數。
注意:
1)當字元陣列表示字串時,其sizeof值將’/0’計算進去。
2)當陣列為形參時,其sizeof值相當於指標的sizeof值。
例子1:
char a[10];
char n[] = "abc";
cout<<"char a[10] "<<sizeof(a)<<endl;//陣列,值為10
cout<<"char n[] = /"abc/" "<<sizeof(n)<<endl;//字串陣列,將'/0'計算進去,值為4
例子2:
void func(char a[3])
{
int c = sizeof(a); //c = 4,因為這裡a不在是陣列型別,而是指標,相當於char *a。
}
void funcN(char b[])
{
int cN = sizeof(b); //cN = 4,理由同上。
}
6、 指標的sizeof
指標是用來記錄另一個物件的地址,所以指標的記憶體大小當然就等於計算機內部地址匯流排的寬度。
在32位計算機中,一個指標變數的返回值必定是4。
指標變數的sizeof值與指標所指的物件沒有任何關係。
例子:
char *b = "helloworld";
char *c[10];
double *d;
int **e;
void (*pf)();
cout<<"char *b = /"helloworld/" "<<sizeof(b)<<endl;//指標指向字串,值為4
cout<<"char *b "<<sizeof(*b)<<endl; //指標指向字元,值為1
cout<<"double *d "<<sizeof(d)<<endl;//指標,值為4
cout<<"double *d "<<sizeof(*d)<<endl;//指標指向浮點數,值為8
cout<<"int **e "<<sizeof(e)<<endl;//指標指向指標,值為4
cout<<"char *c[10] "<<sizeof(c)<<endl;//指標陣列,值為40
cout<<"void (*pf)(); "<<sizeof(pf)<<endl;//函式指標,值為4
7、函式的sizeof
sizeof也可對一個函式呼叫求值,其結果是函式返回值型別的大小,函式並不會被呼叫。
對函式求值的形式:sizeof(函式名(實參表))
注意:
1)不可以對返回值型別為空的函式求值。
2)不可以對函式名求值。
3)對有引數的函式,在用sizeof時,須寫上實參表。
例子:
#include <iostream>
using namespace std;
float FuncP(int a, float b)
{
return a + b;
}
int FuncNP()
{
return 3;
}
void Func()
{
}
int main()
{
cout<<sizeof(FuncP(3, 0.4))<<endl; //OK,值為4,sizeof(FuncP(3,0.4))相當於sizeof(float)
cout<<sizeof(FuncNP())<<endl; //OK,值為4,sizeof(FuncNP())相當於sizeof(int)
/*cout<<sizeof(Func())<<endl; //error,sizeof不能對返回值為空型別的函式求值*/
/*cout<<sizeof(FuncNP)<<endl; //error,sizeof不能對函式名求值*/
}
五、static
static的作用可以歸結為三條:
1、隱藏
當我們同時編譯多個檔案時,所有未加static字首的全域性變數和函式都具有全域性可見性。加了static後,變數或函式就只在本原始檔中可見。
為理解這句話,我舉例來說明。我們要同時編譯兩個原始檔,一個是a.c,另一個是main.c。
下面是a.c的內容
char a = 'A'; // global variable
void msg()
{
printf("Hello\n");
}
下面是main.c的內容
int main(void)
{
extern char a; // extern variable must be declared before use
printf("%c ", a);
(void)msg();
return 0;
}
程式的執行結果是:
A Hello
你可能會問:為什麼在a.c中定義的全域性變數a和函式msg能在main.c中使用?前面說過,所有未加static字首的全域性變數和函式都具有全域性可見性,其它的原始檔也能訪問。此例中,a是全域性變數,msg是函式,並且都沒有加static字首,因此對於另外的原始檔main.c是可見的。
如果加了static,就會對其它原始檔隱藏。例如在a和msg的定義前加上static,main.c就看不到它們了。利用這一特性可以在不同的檔案中定義同名函式和同名變數,而不必擔心命名衝突。Static可以用作函式和變數的字首,對於函式來講,static的作用僅限於隱藏,而對於變數,static還有下面兩個作用。
2、保持變數內容的持久。
儲存在靜態資料區的變數會在程式剛開始執行時就完成初始化,也是唯一的一次初始化。共有兩種變數儲存在靜態儲存區:全域性變數和static變數,只不過和全域性變數比起來,static可以控制變數的可見範圍,說到底static還是用來隱藏的。雖然這種用法不常見,但我還是舉一個例子。
#include <stdio.h>
int fun(void){
static int count = 10;
return count--;
}
int count = 1;
int main(void)
{
printf("global\t\tlocal static\n");
for(; count <= 10; ++count)
printf("%d\t\t%d\n", count, fun());
return 0;
}
程式的執行結果是:
global local static
1 10
2 9
3 8
4 7
5 6
6 5
7 4
8 3
9 2
10 1
3、預設初始化為0
其實全域性變數也具備這一屬性,因為全域性變數也儲存在靜態資料區。在靜態資料區,記憶體中所有的位元組預設值都是0x00,某些時候這一特點可以減少程式設計師的工作量。比如初始化一個稀疏矩陣,我們可以一個一個地把所有元素都置0,然後把不是0的幾個元素賦值。如果定義成靜態的,就省去了一開始置0的操作。再比如要把一個字元陣列當字串來用,但又覺得每次在字元陣列末尾加’\0’太麻煩。如果把字串定義成靜態的,就省去了這個麻煩,因為那裡本來就是’\0’。不妨做個小實驗驗證一下。
#include <stdio.h>
int a;
int main(void)
{
int i;
static char str[10];
printf("integer: %d; string: (begin)%s(end)", a, str);
return 0;
}
程式的執行結果如下
integer: 0; string: (begin)(end)
總結:
首先static的最主要功能是隱藏,其次因為static變數存放在靜態儲存區,所以它具備永續性和預設值0。
六、malloc與new
一般我們常用的申請記憶體的方法有new與malloc,new與delete配對使用,malloc與free配對使用。
1、 malloc
1.1、malloc函式定義
函式malloc 的原型如下:
void * malloc(size_t size);
函式free 的原型如下:
void free( void * memblock );
為什麼free 函式不象malloc 函式那樣複雜呢?
這是因為指標p 的型別以及它所指的記憶體的容量事先都是知道的,語句free(p)能正確地釋放記憶體。
如果p 是NULL 指標,那麼free對p 無論操作多少次都不會出問題;如果p 不是NULL 指標,那麼free 對p連續操作兩次就會導致程式執行錯誤。
1.2、 malloc例子
/* malloc example: random string generator*/
#include <stdio.h> /* printf, scanf, NULL */
#include <stdlib.h> /* malloc, free, rand */
int main ()
{
int i,n;
char * buffer;
printf ("How long do you want the string? ");
scanf ("%d", &i);
buffer = (char*) malloc (i+1);
if (buffer==NULL) exit (1);
for (n=0; n<i; n++)
buffer[n]=rand()%26+'a';
buffer[i]='\0';
printf ("Random string: %s\n",buffer);
free (buffer);
return 0;
}
2、new
new一般與delete配對使用,new申請記憶體,delete釋放記憶體。值得注意的是,我們要區分好new、operator new
2.1、new
不能被過載,其行為總是一致的。它先呼叫operator new分配內 存 , 然後呼叫構造 函式初始化那段記憶體。
new 操作符的執行過程:
1. 呼叫operator new分配記憶體 ;
2. 呼叫建構函式生成類物件;
3. 返回相應指標。
2.2、operator new
要實現不同的記憶體分配行為,應該過載operator new,而不是new。
operator new就像operator + 一樣,是可以過載的。如果類中沒有過載operator new,那麼呼叫的就是全域性的::operator new來完成堆的分配。
operator delete operator new[] operator delete[]也是可以過載的。
C++98
throwing (1) void* operator new (std::size_t size) throw (std::bad_alloc);
nothrow (2) void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) throw();
placement (3) void* operator new (std::size_t size, void* ptr) throw();
C++11
throwing (1) void* operator new (std::size_t size);
nothrow (2) void* operator new (std::size_t size, const std::nothrow_t& nothrow_value) noexcept;
placement (3) void* operator new (std::size_t size, void* ptr) noexcept;
throwing allocation
分配size的記憶體,如果成功返回一個指向記憶體首地址的指標,如果失敗丟擲異常。
nothrow allocation
其他同上,只是在失敗時,返回null指標而不是丟擲異常。
placement
placement new只是operator new過載的一個版本, 它並不分配記憶體, 只是返回指向 已經分配好的某段記憶體的一個指標。因此不能刪除它,但需要呼叫物件的解構函式。
placement new也就是允許你在一個已經分配好的記憶體中(棧或者堆中)構造一個新的物件。原型中void* ptr實際上就是指向一個已經分配好的記憶體緩衝區的的首地址。
placement 使用步驟:
1)快取提前分配
在堆上(new):
char *buffer=new [100];
在棧上(陣列):
char buffer[100];
直接使用通過地址 :
void* buffer = reinterpret_cast<void*> (0xB0EF);
2)構造物件
在已分配的快取上用placement new來構造物件。
A *a=new (buffer)A;
3)使用物件a
4)物件的析構:呼叫它的解構函式
a->~A();
5)釋放
可以反覆利用已分配好的記憶體buffer,如果用完了,就需要釋放它
delete[]buffer;
2.3、例子
// operator new example
#include <iostream> // std::cout
#include <new> // ::operator new
struct MyClass {
int data[100];
MyClass() {std::cout << "constructed [" << this << "]\n";}
};
int main () {
std::cout << "1: ";
MyClass * p1 = new MyClass;
// allocates memory by calling: operator new (sizeof(MyClass))
// and then constructs an object at the newly allocated space
std::cout << "2: ";
MyClass * p2 = new (std::nothrow) MyClass;
// allocates memory by calling: operator new (sizeof(MyClass),std::nothrow)
// and then constructs an object at the newly allocated space
std::cout << "3: ";
new (p2) MyClass;
// does not allocate memory -- calls: operator new (sizeof(MyClass),p2)
// but constructs an object at p2
// Notice though that calling this function directly does not construct an object:
std::cout << "4: ";
MyClass * p3 = (MyClass*) ::operator new (sizeof(MyClass));
// allocates memory by calling: operator new (sizeof(MyClass))
// but does not call MyClass's constructor
delete p1;
delete p2;
delete p3;
<