C++有三種管理資料記憶體的方式:自動存儲,靜态存儲和動态存儲(C++11中還有一種,就是線程存儲)
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHLwUEVNFTQq5EeRpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL0QTN5ITNygTMyEzMwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
-
自動存儲
在函數内部定義的正常變量使用自動存儲空間,被稱為自動變量(局部變量,作用域隻是限于包含它的代碼塊)
這就意味着它們在所屬的函數被調用時自動産生,在該函數結束時消亡。
-
靜态存儲
靜态存儲是整個程式執行期間都存在的存儲方式。使變量成為靜态的方式有兩種:
- 是在函數外面定義它(全局變量)
- 聲明時使用static
-
動态存儲
new和delete提供了一種比自動變量和靜态變量更靈活的方法,他們管理一個記憶體池,自由存儲空間(free store)或棧(heap)
該記憶體池同樣用于靜态變量和自動變量的記憶體是分開的。可以在一個函數中new,在另一個函數中delete, 進行釋放。
-
存儲持續性,作用域及連結性
在C++11中,使用四種方案來存儲資料
- 自動存儲持續性
在函數定義中聲明的變量的存儲持續性為自動的。
- 靜态存儲持續性
在函數外定義的變量和使用關鍵字static定義的變量。
- 線程存儲持續性
再論
- 動态存儲持續性
用new運算符配置設定的記憶體将一直存在,直到使用delete運算符将其釋放或程式結束為止。
存儲持續性描述變量是如何存儲的。
作用域(scope)描述了名稱在檔案的多大範圍可見。
連結性(linkage)描述了名稱如何在不同單元間共享。
下面具體介紹存儲方式的特征。
自動存儲持續性:
在函數中聲明的函數參數和變量,存儲持續性為自動,作用域為局部,沒有連結性。
以上概念很好了解,當出現特例時,比如使用兩個同名變量,一個處于外部代碼塊中,一個處于内部代碼塊中
情況如下:
- 自動變量和棧
因為自動變量的數目随函數的開始和結束而增減,是以程式必須在運作時對自動變量進行管理
常用的方法是留出一段記憶體,将其視為棧。之是以稱為棧,是因為新資料被象征性地放在原有資料的上面,當程式使用
兩個指針來跟蹤棧,一個指針指向棧底——棧開始的地方,另一個指向棧頂——下一個記憶體單元。當函數被調用時,
其自動變量将被加入到棧中,棧頂指針指向變量後的下一個可用的記憶體單元。
函數結束時,棧頂指針被重置為函數被調用前的值,進而釋放新變量使用的記憶體。
棧是LIFO先進後出,後進先出,即最後加入到棧中的變量首先被彈出。
靜态持續變量
C++的靜态存儲持續性提供了三種連結性:
- 外部連結性(必須在代碼塊的外面聲明它)【可在其他檔案中通路】
- 内部連結性(必須在代碼塊的外面聲明它,并使用static限定符)【隻能在目前檔案中通路】
- 無連結性(必須在代碼塊内部聲明它,并使用static限定符)【隻能在目前函數或代碼塊中通路】
注意:靜态變量的數目在程式運作期間是不變的,是以,程式不需要使用特殊的裝置(如棧)來管理他們。
且靜态變量預設初始化為零。
...
int global = 1000;//stctic duration,external linkage
static int one_file = 50;//static duration,internal linkage
int main()
{
...
}
void funct1(int n)
{
static int count = 0;//stctic duration,no linkage
int llama = 0;
}
總結:
靜态持續性,外部連結性
連結性為外部的變量通常稱為外部變量,他們的存儲持續性為靜态,作用域為整個檔案。
相對于局部的自動變量,外部變量也可以稱為全局變量。
-
單定義規則與引用聲明
C++提出了兩種變量聲明的方式:
- 定義聲明簡稱定義
- 引用聲明簡稱聲明
關于單定義規則:變量隻能有一次定義。
extern一般是使用在多檔案之間需要共享某些代碼時
關于聲明:使用關鍵字extern,它不給變量配置設定空間,因為它引用已有的變量。
如果多個檔案需要使用同一個變量,隻需要在某一個檔案中進行定義即可,在其他檔案中進行引用聲明即可。
//file01.cpp
extern int v = 2;//如果指派,就是定義,不指派,就是引用
int v = 2; //這兩個語句效果完全一樣,都是v的定義
int pr = 2;
int ceu;
//file02.cpp
extern int v;
extern int pr;
//file03.cpp
extern int v;
extern int pr;
extern int ceu;
靜态持續性,内部連結性
将static限定符用于作用域為整個檔案的變量時,該變量的連結性将為内部的。
可以将static了解為錨,固定作用域。
靜态持續性,無連結性
将static限定符用于作用域為代碼塊内的變量時,該變量的連結性将為内部的。
可以将static了解為錨,固定作用域
twofile1.cpp
/
#include<iostream>
int tom = 3;//靜态存儲持續性,外部連結性
int dick = 30;//靜态存儲持續性,外部連結性
static int harry = 300;//靜态存儲持續性,内部連結性
void remote_access();
int main()
{
using namespace std;
cout<<&tom<<" = &tom "<<&dick<<" = &dick";
cout<<&harry<<" = harry";
remote_access();
return 0;
}
twofile2.cpp
#include<iostream>
extern int tom;//引用聲明
static int dick = 30; //靜态存儲持續性,内部連結性
int harry = 300;//靜态存儲持續性,外部連結性
void remote_access()
{
using namespace std;
cout<<&tom<<" = &tom "<<&dick<<" = &dick";
cout<<&harry<<" = harry";
}
結果
020 = &tom 024 = &dick 028 = &harry
020 = &tom 450 = &dick 454 = &harry
兩個檔案使用了同一個tom變量
所有函數的存儲持續性都自動為靜态。
Tips:
關于全局變量和局部變量
結論:盡量使用局部變量,全局變量固然好,随時都可以通路,但是易于通路的代價很大,程式不可靠,而且降低程式運作的速度。程式越能避免對資料進行不必要的通路,就越能保持資料的完整性。
類作用域:
在類中定義的名稱(如類資料成員名和類成員函數名)的作用域都為整個類,作用域為整個類的名稱隻在該類中是已知的
在類外是不可知的。
是以,可以在不同類中使用相同的類成員名而不會引起沖突。
特别的,指針通路類成員,使用間接成員運算符(->),普通變量,使用成員運算符(.)
Ik * pr = new Ik;
Ik ee = Lk(8);//構造函數初始化
ee.show();
pr->show();
名稱空間namespace(避免名稱沖突)
在C++中,名稱可以是變量,函數,結構,枚舉,類及結構的成員。
當随着項目的增大,名稱互相沖突的可能性也将增加。
使用多個廠商的類時,難免會出現名稱沖突。
C++提供了名稱空間工具,以便更好地控制名稱的作用域。
明确概念:
- 聲明區域(declaration region):聲明區域是可以在其中進行聲明的區域。
- 潛在作用域(potential scope):變量的潛在作用域從聲明點開始,到其聲明區域的結尾。
- 名稱空間(namespace):通過定義一種新的聲明區域來建立命名的名稱空間,提供一個聲明名稱的區域。
namespcae Jack{
double pail;
void fetch();
int pal;
struct well{...};
}
namespace Jill
{
double bucket(double n);
double fetch;
int pal;
struct Hill{...};
}
名稱空間是全局的
當然也可以位于另一個名稱空間中,但不能位于代碼塊中。
是以名稱空間中聲明的名稱的連結性為外部的。
任何名稱空間中的名稱都不會和其他名稱空間中的名稱發生沖突。
往名稱空間中添加元素:
namespace Jill{
char * goose(const char *);
}
也可以為fetch()函數提供原型:(可以在檔案的後面甚至另一個檔案中,連結性為外部)
namespcae Jack
{
void fetch()
{
....
}
}
當我們需要對名稱空間中的變量進行通路,需要做什麼工作呢?
使用作用域解析運算符::
Jack::pail = 12.34;
Jill::Hill mole;
Jack::fetch();
//Qt中的名稱空間
namespace Ui {
class MainWindow;
}//名稱空間的名稱為Ui,内部包含類MainWindow
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
double pr[4][5];
private:
Ui::MainWindow *ui;
//因為沒有使用using聲明或者using編譯指令
//直接使用Ui及作用域運算符
};
-
using聲明和using編譯指令
C++使用using聲明和using編譯指令兩種方法來簡化對名稱空間中的名稱的使用。
- 首先介紹using聲明(簡化了表達方式)
将特定的名稱添加到它所屬的聲明區域中,可以是全局聲明區域,也可以是局部聲明區域。
此時,再通路變量時,就可以省略Jill::了
進一步聲明兩個變量:
- 全局名稱空間(global namespace)
- 局部名稱空間(local namespace)
局部聲明區域示範:
namespace Jill
{
double bucket (double n){...};
double fetch;
struct Hill{...};
}
char fetch;//global namespace
int main()
{
using Jill::fetch;//局部聲明區域,進入局部名稱空間
double fetch;//錯誤,因為已經有上一句了,局部的聲明區域已經定義了double fetch;
cin>>fetch;//read a value into Jill::fetch
cin>>::fetch;//調用的是global 中的fetch
//局部變量會覆寫同名的全局變量
}
全局變量示範:
namespace Jill
{
double bucket (double n){...};
double fetch;
struct Hill{...};
}
using Jill::fetch//全局聲明區域,進入全局聲明空間
void other();
int main()
{
cin>>fetch;//read value into Jill::fetch
other();
}
void otther()
{
cout <<fetch;//display Jill::fetch
}
- using編譯指令
using聲明隻是對namespace中的一個名稱有用,而using編譯指令對所有的名稱都可用。
它使名稱空間中的所有名稱都可用
#include<iostream>
using namespace std;//名稱空間中的所有名稱均可直接調用,不需要作用域解析運算符::
- using聲明和using編譯指令的差別
//using聲明
namespace Jill
{
double bucket (double n){...};
double fetch;
struct Hill{...};
}
char fetch;//global namespace
int main()
{
using Jill::fetch;//局部聲明區域,進入局部名稱空間
double fetch;//錯誤,因為已經有上一句了,局部的聲明區域已經定義了double fetch;
cin>>fetch;//read a value into Jill::fetch
cin>>::fetch;//調用的是global 中的fetch
//局部變量會覆寫同名的全局變量
}
//using編譯指令
namespace Jill
{
double bucket (double n){...};
double fetch;
struct Hill{...};
}
char fetch;//global namespace
int main()
{
using namespace Jill;//局部聲明區域,進入局部名稱空間
double water = bucker(2);//可用直接調用而不需要使用“::”
Hill Thrill;
double fetch;//這個沒錯,如果是在using中就是錯的,而且還會覆寫掉Jill::fetch
cin>>fetch;//調用的是上面的fetch
cin>>::fetch;//調用的是global 中的fetch
cin>>Jill::fetch;//read a value into Jill::fetch
}
總上:
- using聲明,在作用域内,絕對不允許有和using聲明的名稱同名的變量。
- using編譯指令下,允許,而且會覆寫原來同名稱的變量。
- 同理:通路global變量時,都需要使用"::fetch"。