1. 程式人生 > >資料的儲存方案

資料的儲存方案



C++中,根據資料儲存在記憶體中的時間長短,分為四種不同的方案來儲存資料。

1.自動儲存持續性

2.靜態儲存持續性

3.執行緒儲存持續性(C++11),不介紹。

4.動態儲存持續性

—————————————————————————————————————————————————————

然而在介紹這四種不同的方案之前,先介紹兩個名詞。

1.作用域:描述了物件或者函式在多大的範圍內可見。

2.連線性:連線性描述了變數名稱如何在不同單元間共享,連線性為外部的的名稱可以在檔案間共享,連線性為內部的名稱則只能在同一個檔案中的函式共享。

—————————————————————————————————————————————————————

一、自動儲存持續性

>1.自動變數(區域性變數)

在函式或者程式碼塊中宣告定義的變數儲存持續性為自動,作用域為區域性,沒有連線性,由系統棧分配記憶體。

自動變數具有隱藏(hide)同名變數的特性

[cpp] view plaincopyprint?
  1. int main()  
  2. {  
  3.     int number = 5;  
  4.     {  
  5.         cout << "number is: " << number << " number at: " <<&number << endl;  
  6.         int
     number = 10;  
  7.         cout << "number is: " << number << " number at: " <<&number << endl;  
  8.     }  
  9.     return 0;  
  10. }  
int main()
{
    int number = 5;
    {
        cout << "number is: " << number << " number at: " <<&number << endl;
        int number = 10;
        cout << "number is: " << number << " number at: " <<&number << endl;
    }
    return 0;
}

兩個number雖然名稱相同,但其實作用於不同,第一個number作用域一直到return語句,而第二個number從定義處開始一直到大括號,並且在第二個number作用時,第一個number被隱藏。

同樣,自動變數還可以隱藏全域性變數,不過可以通過作用域解析運算子來特指使用全域性變數:

[cpp] view plaincopyprint?
  1. int number = 5;  
  2. int main()  
  3. {  
  4.     {  
  5.         cout << "number is: " << number << " number at: " << &number << endl;  
  6.         int number = 10;  
  7.         cout << "number is: " << number << " number at: " << &number << endl;  
  8.         cout << "number is: " << ::number << " number at: " <<&(::number) << endl;  
  9.     }  
  10.     return 0;  
  11. }  
int number = 5;
int main()
{

    {
        cout << "number is: " << number << " number at: " << &number << endl;
        int number = 10;
        cout << "number is: " << number << " number at: " << &number << endl;
        cout << "number is: " << ::number << " number at: " <<&(::number) << endl;
    }
    return 0;
}

>2.暫存器變數:特性同自動變數,但是比普通的自動變數要快,儲存在暫存器中。C++11中不在進行這種特殊處理。

二、靜態儲存持續性:

>1.全域性變數:

全域性變數也屬於靜態儲存持續性,因為有連線性,所以作用域大大擴充套件,可以在多個檔案中使用,由系統棧分配記憶體。

例如在file1中定義了一個全域性變數:

[cpp] view plaincopyprint?
  1. int number = 5;//definition in file1 
int number = 5;//definition in file1 
可以通過外加extern關鍵詞的方式在其他檔案中使用:(這種方式稱為引用宣告) [cpp] view plaincopyprint?
  1. externint number;//use it in file2
extern int number;//use it in file2
注意,這裡不是 [cpp] view plaincopyprint?
  1. externint number = 5;  
extern int number = 5;
這種方式是對number進行了重定義,而不是引用宣告。

>2.區域性靜態變數:

同自動變數,是在函式或者程式碼塊中定義的變數,但是前邊加有static關鍵詞,這種區域性靜態變數沒有連線性,作用域也只在函式或者程式碼塊中,不同於自動變數的是,無論是否呼叫該函式都會事先建立該變數,靜態變數不是由系統棧分配記憶體的,而是一塊單獨的記憶體。同時,這也意味著即使多次呼叫函式,靜態區域性變數也有且僅有一次初始化,數值被保留。

>3.全域性靜態變數

同全域性變數,只是在變數名稱前加static關鍵詞。全域性靜態變數連線性為內部,只在一個檔案中共享,其作用域為該檔案,由專門分配靜態變數的記憶體管理。

全域性靜態變數的作用很大,值得注意的是,通過全域性變數可以更好的理解連線性為內部是究竟是什麼意思。

情景:在多個檔案中,想使用相同名稱的但是初始值不同的全域性變數該如何?

解決方案1:

[cpp] view plaincopyprint?
  1. int number = 5;//file1
  2. externint number = 10;//file2;
int number = 5;//file1
extern int number = 10;//file2;
由於引用變數給了初始值(10),又由於全域性變數連線性為外部可以在多個檔案中共享,所以這裡相當於一個重定義。正確的方法應當是使用static全域性變數,即:
[cpp] view plaincopyprint?
  1. int number = 5;//file1
  2. staticint number = 10;//file2,exists in file2 only
int number = 5;//file1
static int number = 10;//file2,exists in file2 only
且明顯這兩個number的地址是不同的。

值得一提的是,如果全域性變數前加const,那麼const全域性變數的連結性為內部,相當於一個static靜態全域性變數。

內部連線性還意味著,每個檔案都有自己的一組常量,而不是所有檔案共享一組常量。每個定義都是其所屬檔案私有的,也就是能夠將常量定義放在標頭檔案中的原因。這樣,只要在兩個原始碼檔案中包含同一個標頭檔案,那麼他們就將使用同一組常量。

三、動態儲存持續性

動態儲存持續性,顧名思義就是使用動態記憶體為變數分配記憶體,在C++中體現的是用new與delete。

動態分配的記憶體,由堆來管理,它不受作用域和連線性的規則控制。

一旦被new分配記憶體後,只要不使用delete釋放將一直存在直到程式結束。

—————————————————————————————————————————————————————

通常,new負責在堆中找到一個可以滿足要求的記憶體塊,不過C++提供了一種new運算子的變體,稱為定位new運算子

定位new運算子,允許程式設計師手動的管理分配記憶體,要使用這種特性,首先要包括標頭檔案<new>

基本的語法如下:

[cpp] view plaincopyprint?
  1. value_point = new (buffer) value_size;  
value_point = new (buffer) value_size;
從記憶體池buffer中分配一塊value_size這麼大的記憶體給value_point,其中value_point是一個指標
看一個例項,能很好的明白“手動的管理分配記憶體”的含義。
[cpp] view plaincopyprint?
  1. #include <iostream>
  2. #include <new>
  3. usingnamespace std;  
  4. constint maxn = 512;  
  5. int main()  
  6. {  
  7.     int buffer[maxn]= {0};  
  8.     int *p1,*p2,*p3,*p4;  
  9.     p1 = newint[5];//use heap;
  10.     p2 = new(buffer) int[5];//use buffer
  11.     cout << "heap at: " << p1 <<" static buffer at: " << (void*)buffer << endl;  
  12.     cout << "\nThe first change" << endl;  
  13.     for(int i = 0 ; i < 5 ; ++i){  
  14.         p1[i] = i * 1 + 10;  
  15.         p2[i] = i * 2 + 10;  
  16.     }  
  17.     for(int i = 0 ; i < 5 ; ++i)  
  18.         cout << p1[i] << " at " << &p1[i] << " ; " << p2[i] << " at " << &p2[i] << endl;  
  19.     cout << "\nThe second change" << endl;  
  20.     p3 = newint[5];//use heap,differ from p1
  21.     p4 = new(buffer) int[5];//rewrite the old data,same as p2
  22.     for(int i = 0 ; i < 5 ; ++i){  
  23.         p3[i] = i * 3 + 10;  
  24.         p4[i] = i * 4 + 10;  
  25.     }  
  26.     for(int i = 0 ; i < 5 ; ++i)  
  27.         cout << p3[i] << " at " << &p3[i] << " ; "<< p4[i] << " at " << &p4[i] << endl;  
  28.     delete[] p1;  
  29.     delete[] p3;  
  30.     //p2,p4 can't delete
  31.     return 0;  
  32. }  
#include <iostream>
#include <new>
using namespace std;
const int maxn = 512;

int main()
{
    int buffer[maxn]= {0};
    int *p1,*p2,*p3,*p4;

    p1 = new int[5];//use heap;
    p2 = new(buffer) int[5];//use buffer

    cout << "heap at: " << p1 <<" static buffer at: " << (void*)buffer << endl;
    cout << "\nThe first change" << endl;
    for(int i = 0 ; i < 5 ; ++i){
        p1[i] = i * 1 + 10;
        p2[i] = i * 2 + 10;
    }
    for(int i = 0 ; i < 5 ; ++i)
        cout << p1[i] << " at " << &p1[i] << " ; " << p2[i] << " at " << &p2[i] << endl;
    cout << "\nThe second change" << endl;
    p3 = new int[5];//use heap,differ from p1
    p4 = new(buffer) int[5];//rewrite the old data,same as p2
    for(int i = 0 ; i < 5 ; ++i){
        p3[i] = i * 3 + 10;
        p4[i] = i * 4 + 10;
    }
    for(int i = 0 ; i < 5 ; ++i)
        cout << p3[i] << " at " << &p3[i] << " ; "<< p4[i] << " at " << &p4[i] << endl;

    delete[] p1;
    delete[] p3;
    //p2,p4 can't delete
    return 0;
}

下面是程式的執行結果:


常規new運算子與定位new運算子的不同:

1.p1,.p3是常規new運算子分配,需用delete釋放。p2,p4是由buffer分配,而buffer本身是一塊靜態記憶體由棧管理,因此不可以用delete釋放。

2.p2,p4是定位new運算子分配,注意實際上buffer可以為任意型別的資料分配記憶體(只要記憶體空間足夠)而不一定是int型別的,且不是在堆中。

3.p3常規運算子分配,它的地址與p1不同,而p4的地址與p2是相同的

  定位new運算子使用傳遞給它的地址,但是不跟蹤這些記憶體哪些已經被使用,也不查詢未使用的記憶體,將這些記憶體管理的任務移交給程式設計師。

可以使用以下語句進行調配:

[cpp] view plaincopyprint?
  1. p4 = new(buffer + 5 * sizeof(int)) int[5];  
p4 = new(buffer + 5 * sizeof(int)) int[5];