一文帶你分清C++的定義,聲明和初始化

定義

變量的定義用於為變量分配存儲空間,還可以為變量指定初始值。

int units_sold;
double sales_price, avg_price;
std::string title;
Sales_item curr_book; // class Sales_item

初始化

C++ 支持兩種初始化變量的形式:復制初始化和直接初始化。復制初始化語法用等號(=),直接初始化則是把初始化式放在括號中。

int ival(1024); // direct-initialization
int ival = 1024; // copy-initialization

初始化不是賦值。初始化指創建變量並給它賦初始值,而賦值則是擦除對象的當前值並用新值代替。

當定義沒有初始化式的變量時,系統有時候會幫我們初始化變量。

1.內置類型變量

(Built-in Types,即int,float,double,void,char,bool等。註意string是標準庫定義的類型,不是內置類型)

在函數體外定義的變量都初始化成 0,在函數體裡定義的內置類型變量不進行自動初始化。

2.類

類通過定義一個或多個構造函數來控制類對象的初始化。創建類類型的新對象,都要執行構造函數,保證每個對象的數據成員具有合適的初始值。

構造函數可以包含一個構造函數初始化列表,以一個冒號開始,接著是一個以逗號分隔的數據成員列表,每個數據成員後面跟一個放在圓括號中的初始化式。與任意的成員函數一樣,構造函數可以定義在類的內部或外部。

構造函數初始化隻在構造函數的定義中而不是聲明中指定。

//將 isbn 成員初始化為 book 形參的值,將 units_sold 和 revenue 初始化為 0。
Sales_item::Sales_item(const string &book):
          isbn(book), units_sold(0), revenue(0.0) { }

如果沒有提供初始化式,那麼就會使用默認構造函數。如果類具有默認構造函數,那麼就可以在定義該類的變量時不用顯式地初始化變量。例如,string 類定義瞭默認構造函數來初始化 string 變量為空字符串。

string a;
cout << "a: " << a <<endl;

輸出:

a: 

此外,省略初始化列表在構造函數的函數體內對數據成員賦值是合法的。

Sales_item::Sales_item(const string &book)
     {
         isbn = book;
         units_sold = 0;
         revenue = 0.0;
     }

不管成員是否在構造函數初始化列表中顯式初始化,類類型的數據成員總是在初始化階段初始化。初始化發生在計算階段開始之前。(也就是函數體執行以前)→ 這裡似乎有些難以理解,通過後文的實例也許你能明白

在構造函數初始化列表中沒有顯式提及的每個成員,使用與初始化變量相同的規則來進行初始化。運行該類型的默認構造函數,來初始化類類型的數據成員。內置或復合類型的成員的初始值依賴於對象的作用域:在局部作用域中這些成員不被初始化,而在全局作用域中它們被初始化為 0。

未初始化的變量

局部作用域的內置類型變量將不被自動初始化,這可能導致其成為未初始化的變量。這是常見的程序錯誤,但編譯器無法檢測出所有未初始化變量的使用。→ 你肯定可以理解這可能導致的災難性後果瞭(這竟然不可以運行,為什麼呢?這竟然可以運行,為什麼呢?.jpg)再稍微解釋一下原因:問題出在未初始化的變量事實上都有一個值。編譯器把該變量放到內存中的某個位置,而把這個位置的無論哪種位模式都當成是變量初始的狀態。當被解釋成整型值時,任何位模式都是合法的值——雖然這個值不可能是程序員想要的。因為這個值合法,所以使用它也不可能會導致程序崩潰。可能的結果是導致程序錯誤執行和/或錯誤計算。

聲明

聲明用於向程序表明變量的類型和名字。定義也是聲明:當定義變量時我們聲明瞭它的類型和名字。

可以通過使用extern關鍵字聲明變量名而不定義它。extern 聲明不是定義,也不分配存儲空間。事實上,它隻是說明變量定義在程序的其他地方。程序中變量可以聲明多次,但隻能定義一次。

extern int i; // declares but does not define i
int i; // declares and defines i

隻有當聲明也是定義時,聲明才可以有初始化式,因為隻有定義才分配存儲空間。初始化式必須要有存儲空間來進行初始化。如果聲明有初始化式,那麼它可被當作是定義,即使聲明標記為 extern:

extern int i = 10; //defines i

在 C++ 語言中,變量必須且僅能定義一次,而且在使用變量之前必須定義或聲明變量。

實例

#include <iostream>
#include <string>
using namespace std;

//類x的聲明
//如果這一部分放在main()函數後面,報錯:error: 'x' was not declared in this scope
//在實際工程中,這部分聲明將放在頭文件(.h)中,而構造函數及成員函數的定義則放在.cpp文件中
class x{
public:
    x(int a, int b, string c);
    void print_data();
private:
    //類數據成員的變量名最好在開頭加一個字母m(即member)
    int ma; 
    int mb;
    string mc;

};

int main(){
    int a1(2); //直接初始化
    int b1 = 3; //復制初始化
    string c1; //默認構造函數初始化string變量為空字符串
    c1 = "dwkw"; //賦值
    x data(a1, b1, c1); //調用構造函數初始化
    return 0;
    print_data();
}

//構造函數定義
x::x(int a, int b, string c):ma(a), mb(b), mc(c){}
//成員函數定義
void x::print_data(){
    cout << "ma: " << ma << endl;
    cout << "mb: " << mb << endl;
    cout << "mc: " << mc << endl;
}

輸出:

ma: 2
mb: 3
mc: dwkw

聲明時提供初值

如果在類的聲明中就對數據成員提供初值,而不在初始化列表中提供,程序可以執行,輸出ma的值為1。

class x{
public:
    x(int a, int b, string c);
    void print_data();
private:
    int ma = 1; //聲明時提供初值
    int mb;
    string mc;
};
//去掉初始化列表
x::x(int a, int b, string c):mb(b), mc(c){}

這一做法在早期版本不予支持,但從c++11就可以瞭。[2]

不過這破壞瞭類的抽象性,並不建議這樣做。

查看c++版本的方法:[3]

cout << __cplusplus << endl; //輸出c++版本

在構造函數內賦初值,而不用列表

前面提到省略初始化列表在構造函數的函數體內對數據成員賦值是合法的。

//去掉初始化列表,在構造函數體內賦值
//其它代碼保持不變
x::x(int a, int b, string c){
    cout << "賦值前: " << endl;
    print_data();
    cout << "賦值後: " << endl;
    ma = 4;
    mb = 5;
    mc = "ser";
}

輸出:

賦值前: 
ma: 4199744
mb: 0
mc: 
賦值後: 
ma: 4
mb: 5
mc: ser

實際上我就沒寫初始化列表,但系統它就會在這裡執行初始化。總之就會在執行構造函數體內的語句之前初始化(如果它可以自動初始化),即使根本沒寫初始化列表。→ 嘖,我就像在說繞口令,希望你能明白我的意思

但是ma和mb都是局部作用域(我不確定類作用域是否是局部作用域,但從輸出來看,ma不是0,所以應該沒有能夠初始化)的內置類型變量,不進行自動初始化;mc有默認構造函數,自動初始化為空字符串。

而後,執行構造函數體內部的語句,將對ma和mb進行初始化(我想這裡應該是初始化而不是賦值),對mc賦值。

總結

本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!  

推薦閱讀: