1. 程式人生 > >必須掌握的C++常用關鍵字彙總

必須掌握的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=100const 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;

  <