天天看點

C++學習 第九章

1.單獨編譯

和C語言一樣,C++允許甚至鼓勵将元件函數放在獨立的檔案中,可以單獨編譯這些檔案,然後把它們連結成可執行的程式。如果隻修改了一個檔案,則可以隻重新編譯該檔案,然後與其他檔案的編譯版本進行連結,這會使得大程式的管理更加友善。

例如我們要将如下程式分解,将支援函數放在一個獨立的檔案中,我們需要怎麼做呢?

#include<iostream>
using namespace std;
struct fruit
{
	int apple;
	int banana;
};
fruit fruitSum (fruit f1, fruit f2);
void showFruit (fruit f);
int main()
{
	fruit f1 = {15, 20};
	fruit f2 = {20, 30};
	showFruit(f1);
	showFruit(f2);
	showFruit(fruitSum(f1, f2));
}
fruit fruitSum (fruit f1, fruit f2)
{
	fruit fsum;
	fsum.apple = f1.apple + f2.apple;
	fsum.banana = f1.banana + f2.banana;
	return fsum;
}
void showFruit (fruit f)
{
	cout << f.apple << endl;
	cout << f.banana << endl;
}

           

該程式是一個将結構内容相加,并顯示結果,算上main()一共有三個函數,不能簡單地以main()函數為界,簡單的将原來的檔案分為兩個,因為main()和其他兩個函數都使用了同一個結構聲明,是以兩個檔案都應該包括結構聲明,簡單地将他們輸入進去無疑會增加麻煩,因為需要修改結構的時候,需要對兩個檔案中的結構都進行修改,這簡直比每次使用new的時候都使用delete都讓人困擾。是以C++的開發人員提供了#include的方式。我們将函數原型,結構聲明放入到頭檔案中,然後在每個檔案中引入頭檔案的内容即可,是以原來的程式被分為了三個部分。

  • 頭檔案:包含結構聲明和使用這些結構的函數的原型
  • 源代碼檔案:包含與結構有關函數的代碼
  • 源代碼檔案:包含調用與結構相關的函數的代碼

注意:不要将函數定義或變量聲明放到頭檔案中,這回導緻一個程式中有兩個相同變量或函數的定義,除非是内聯函數,否則這種行為會報錯。

  • 頭檔案中可以包含的内容

    1.函數原型

    2.使用#define或const定義的符号常量

    3.結構聲明

    4.類聲明

    5.模闆聲明

    6.内斂函數

注意:#include語句中" "以及 < >之間的差別。

  • #include"iostream" 檔案名包含在雙引号中,則編譯器将首先查找目前的工作目錄或源代碼目錄,如果在那裡沒有找到頭檔案,則在标準位置查找。是以包含自己的頭檔案時,應該用" "号。
  • #include<iostream>檔案名包含在尖括号中,則編譯器将在存儲标準頭檔案的主機系統的檔案系統中查找,是以引用标準庫的頭檔案時,應該使用<>号。

注意:在IDE中,不要把頭檔案加入到項目清單中,也不要在源代碼檔案中使用#include來包含其他源代碼檔案。

下面展示将上述代碼分割為三個檔案的檔案内容:

//檔案名:coordin.h
//頭檔案
#ifndef COORDIN_H_
#define COORDIN_H_
struct fruit
{
	int apple;
	int banana;
};
fruit fruitSum (fruit f1, fruit f2);
void showFruit (fruit f);
#endif
           
//檔案1 file1.cpp
#include<iostream>
#include"coordin.h"
using namespace std;
int main()
{
	fruit f1 = {15, 20};
	fruit f2 = {20, 30};
	showFruit(f1);
	showFruit(f2);
	showFruit(fruitSum(f1, f2));
	return 0;
}
           
//檔案2 file2.cpp
#include<iostream>
#include<cmath>
#include"coordin.h"
fruit fruitSum (fruit f1, fruit f2)
{
	fruit fsum;
	fsum.apple = f1.apple + f2.apple;
	fsum.banana = f1.banana + f2.banana;
	return fsum;
}
void showFruit (fruit f)
{
	cout << f.apple << endl;
	cout << f.banana << endl;
}
           
  • 在linux中編譯兩個源代碼檔案指令:g++ file1.cpp file2.cpp -o fruit

    輸入指令之後,編譯器會将兩個檔案進行編譯,首先将#include的内容轉化成對應的文本内容,然後生成一個新的cpp檔案來存放他們,之後編譯器建立每一個源代碼檔案的目标代碼檔案,這種檔案格式可以放入整個的大程式之中,生成這種目标代碼檔案之後,連結程式将目标代碼檔案,庫代碼和啟動代碼進行合并,生成可執行檔案。

  • 頭檔案管理

    C++中規定,在同一個檔案中隻能将同一個頭檔案包含一次,因為很有可能在不知情的情況下包含同一個頭檔案多次,比如使用了包含另一個頭檔案的頭檔案,為了防止這種錯誤的産生,C++有一種技術可以避免多次包含同一個頭檔案。它是基于預處理器編譯指令#ifndef(if not defind)的。

//本代碼塊意味着,僅當以前沒有使用預處理器編譯指令#define定義名稱COORDIN_H_,才處理其中的語句
#ifndef COORDIN_H_
#define COORDIN_H_
...
#endif
//當首次編譯器遇到該檔案時,名稱COORDIN_H_未定義
//在這種情況下,編譯器将檢視#ifndef 與 #endif之間的内容
//并且會讀取#define這一行
//如果在同檔案中再遇到其他包含該頭檔案的代碼
//系統就知道COORDIN_H_已經被定義過了,直接回調到#endif
//注意:這種方法并不能防止編譯器将檔案包含兩次,但是會讓他忽略第一次以外所包含的内容
           

注意:在C++标準中使用術語"翻譯單元",而不是檔案。也就是說我們上述的檔案,标準稱呼為翻譯單元。

  • 多個庫的連結問題

    C++标準允許每個編譯器設計人員以他認為合适的方式實作名稱修飾,也就是說不同編譯器建立的二進制子產品很可能沒有辦法正确的連結,也就是說兩個編譯器很可能為同一個函數時候生成不同的修飾名稱。名稱的不同将使連結器無法将一個編譯器生成的函數調用與另一個編譯器生成的函數定義比對,為了防止出現這種問題,如果我們擁有源代碼,我們可以自己的編譯器重新編譯源代碼以防止連結錯誤。

2.存儲持續性

  • 四種存儲方式的存儲持續性

    1.自動存儲:在函數定義中聲明的變量(包括函數參數)的存儲持續性是自動的。他們在程式開始執行其所屬函數或代碼塊時被建立,在執行完函數或者代碼塊後被銷毀

    2.靜态存儲:在函數定義外定義的變量和使用關鍵字static定義的變量的存儲持續性都為靜态。他們在程式整個運作過程中都存在

    3.線程存儲:C++11中出現了并行多線程程式設計,這在多核處理器普及的今天很常見,程式能将計算放在可并行處理的不同線程中,生命周期和線程一樣長。

    4.動态存儲:用new運算符配置設定的記憶體将一直存在,直到使用delete運算符将其釋放或程式結束為止。這種記憶體的存儲持續性為動态,有時被成為自由存儲區或者堆。

  • 作用域和連結

    作用域:作用域描述了名稱在檔案的多大範圍内可見,例如,函數中定義的變量可在該函數中使用,但是不能在其他函數中使用;而在檔案中的函數定義之前定義的變量則可以在所有函數中使用。

    連結性描述了名稱如何在不同單元間共享。連結性為外部的名稱可以在檔案間共享,連結性為内部的名稱隻能由一個檔案中的函數共享。自動變量沒有連結性,是以他們不能共享。

  • 作用域的分類

    1.局部變量作用域:隻在定義他的代碼塊中可用。

    2.全局變量作用域:從定義位置一直到檔案結束可用。

    3.類成員的作用域:整個類。

    4.名稱空間變量的作用域:整個名稱空間。

    5.函數原型參數的作用域:隻在包含參數清單的括号中可用

    6.函數的作用域可以是整個類或者整個名稱空間,而不能是局部的。

3.自動存儲的持續性

在預設情況下,在函數中聲明的函數參數和變量的存儲持續性都為自動,作用域為局部,沒有連結性。也就是說在main()函數中建立x變量,并且在函數show()中也建立了x變量,兩個變量都會被獨立的配置設定記憶體,而且之間并沒有任何聯系,是兩個獨立的變量,并且隻在定義他們的函數中可使用。

有種情況需要特殊聲明,在main()函數中定義了變量x,在main()函數中有一個代碼塊,也定義了變量x。此時C++并不認為程式有文法錯誤,C++同樣認為兩個x并不是同一個變量,并且在這種情況下,在代碼塊中所使用的x就是代碼塊中定義的x,在代碼塊外使用的x就是代碼塊外定義的x。這并不代表在代碼塊外定義的變量無法在代碼塊内使用,隻是說明在命名一緻的情況下,代碼塊裡的變量會将代碼塊外的變量暫時隐藏起來。

4.自動變量的初始化

//可以使用任何在聲明時,其值已知的表達式來對自動變量進行初始化
//舉例:初始化變量x,y,z
int w;
int x = 5;
int big = INT_MAX - 1; //<climits>
int y = 2 * x;
cin >> w;
int z = 3 * w; 
           

5.自動變量和棧

由于自動變量的數目随着函數的開始和結束而增減,是以程式必須在運作時對自動變量進行管理。最常用的一個方法就是留出一段記憶體,将其視為棧,所謂的棧就是一段後進先出的記憶體。程式會用兩個指針來追蹤棧,其中棧頂一個指針,表示下一個可用的記憶體單元,棧底一個指針,表示棧的開始位置。當函數被調用的時候,所需要的自動變量一次存放入棧中。函數結束後,棧頂的指針重新指回到函數使用前的位置。也就是說,新加入的變量并沒有被删除,而是不在被标記,被下一次放入棧中的變量替代掉。

6.寄存器變量

關鍵字register是從C語言中引入的,意味着它建議編譯器使用CPU寄存器來存儲自動變量。目的是提高通路自動變量的速度。不過在C++11中,register關鍵字已經淪為顯式的聲明該變量為自動變量的關鍵字,也就是曾經auto的用法。不删除這個關鍵字是防止現有的代碼非法。

7.靜态持續變量定義

C++為靜态存儲持續性變量提供了三種連結性,即外部連結性,内部連結性,無連結性。這三種連結性都在整個程式執行期間存在,與自動變量相比,他們的壽命更長。

由于靜态變量的數目在程式中是不變的,是以程式不需要使用特殊的裝置來管理他們。編譯器将配置設定固定的記憶體塊來存儲所有的靜态變量,這些變量在整個程式執行期間一直存在。

另外,如果沒有顯示地初始化靜态變量,編譯器會将他設定為0。在預設情況下,靜态數組和結構将每一個元素或成員都設定為0。

8.如何建立這三種靜态持續變量

//建立連結性為外部的靜态持續變量 --- 在代碼塊外部聲明它
//建立連結性為内部的靜态持續變量 --- 在代碼塊外部聲明它,并且使用static限定符
//建立連結性為無連結性的靜态持續變量 --- 必須在代碼塊内聲明它,并且使用static限定符
//下面舉例子
int global = 100; //連結性為外連結性的靜态持續變量
static int one = 100; //連結性為内連接配接的靜态持續變量
int main()
{
	...
}
void show()
{
	static int count = 0; //連結性為無連結的靜态持續變量
}

           

9.三種靜态持續變量的差別

  • 無連結性的靜态持續變量:

    雖然它确實在整個程式執行期間都存在,但是他隻能在函數内部進行使用,就好像自動變量一樣。不過和自動變量不同的是,他在函數執行結束之後并不會被銷毀,仍然會存在在記憶體中,等待下一次的調用。

  • 内部連結性的靜态持續變量:

    這種變量的作用域和無連結性的靜态持續變量不同,無連結性的靜态持續變量的作用域為局部,而内部連結性的靜态持續變量作用域為整個檔案。即從檔案聲明位置開始到檔案結尾範圍内都可以被使用,不過由于連結性為内部連結,是以隻能在包含上述代碼的檔案中使用它,不能在程式的其他檔案中使用。

  • 外部連結性的靜态持續變量:

    這種變量的作用域和内壁連結性的靜态持續變量相同,都是整個檔案,不過外部連結性的靜态變量也可以在程式的别的檔案中使用,并不是像内部連結性的靜态持續變量那樣,隻可以在聲明的檔案中進行使用。

10.5種變量的存儲方式

存儲描述 持續性 作用域 連結性 如何聲明
自動變量 自動 代碼塊 無連結性 在代碼塊中
寄存器變量 自動 代碼塊 無連結性 在代碼塊中使用register
無連結靜态持續變量 靜态 代碼塊 無連結性 在代碼塊中使用static
内部連結靜态持續變量 靜态 檔案 内部連結 在代碼塊外使用static
外部連結靜态持續變量 靜态 檔案 内部連結 在代碼塊外

11.靜态變量的初始化

除了預設的零初始化以外,還對靜态變量執行常量表達式初始化和動态初始化。初始化的形式是,首先先對所有的靜态變量實行零初始化,無論是否他被顯式的初始化,然後使用常量表達式初始化的變量,編譯器僅根據檔案内容就可計算表達式,編譯器将執行常量表達式初始化。如果沒有足夠的資訊,變量會被動态初始化。

#include<cmath>
int x; //被零初始化
int y = 15; //被靜态初始化
const double pi = 4 * atan(1.0); //由于需要進行函數運算,會被動态初始化
           

12.靜态持續性,外部連結性

連結性為外部的變量被稱為外部變量,也被成為全局變量,他們的存儲持續性為靜态,作用域為整個檔案,因為外部變量的連結性為外部,也就是在程式的所有檔案中都可以使用它,那麼為了使用這種外部變量就需要在使用它的檔案中進行聲明。而C++有“單定義規則”,這個規則指出,一個變量隻能一次定義。為了滿足這種要求,C++提供了兩種變量聲明,一種是定義聲明,也叫作定義。另一種是引用聲明,或者簡稱為聲明。引用聲明不配置設定存儲空間,引用以前的變量。引用聲明使用關鍵字extern,且不能進行初始化,如果進行初始化,則為定義聲明,配置設定存儲空間。

//定義聲明
int a = 100;
extern int a = 100;
//引用聲明
extern int a;
           

注意:單定義規則并不意味着不能有多個名相同的變量。你仍然可以在不同的函數中使用相同的名稱來命名自動變量,他們仍然是不同變量,擁有彼此獨立的位址。單定義規則說明,雖然程式中可能有同名變量,但是每個變量隻有一個定義。下面舉一個例子:

//file1.cpp
#include<iostream>
using namespace std;
double warming = 0.3; //定義一個全局變量
void update(double dt);
void local();
int main()
{
	cout << "Global warming is " << warming << "degrees.\n";
	update(0.1);
	cout << "Global warming is " << warming << "degrees.\n";
	local();
	cout << "Global warming is " << warming << "degrees.\n";
	return 0;
}
           
#include<iostream>
extern double warming;
void update(double dt);
void local();
using std::cout;
void update(double dt)
{
	extern double warming;
	warming += dt;
	cout << "updating global warming to" << warming;
	cout << "degrees.\n"; 
}
void local()
{
	double warming = 0.8;
	cout << "Local warming =" << warming << "degrees.\n";
	cout << "But global warming = " << ::warming << " degrees.\n";
}
           

注意:update()函數中的extern double warming;意思是使用外部變量中的warming。這條語句并不是一定要寫的,因為前面已經有過一個聲明在函數外。local()函數表明定義與全局變量同名的局部變量後,局部變量會将全局變量隐藏。使用作用域解析運算符放在變量前面的時候,該運算符表示使用變量的全局版本,這就是local()中使用全局變量warming的方式。::warming是一個很好的方式,安全易懂。

13.全局變量和局部變量的選擇

能夠使用局部變量都使用局部變量,這樣能讓程式更加的穩定安全。一般使用全局變量存儲常量資料。例如使用const char * const months[12];來存儲十二個月份,其中第一個const保證字元串不被修改,第二個const保證指針指向位置不會被修改。

14.靜态持續性,内部連結性

将static限定符用于作用域為整個檔案的變量時,該變量的連結性将為内部的。在多檔案的程式中每部連結性的靜态變量隻能在定義它的檔案中使用。

注意:如果已經定義了一個外部變量,要在另一個檔案中定義一個同名的變量,省略extern這種方式是錯誤的,因為違反了單定義原則。要想實作這種結果需要做的是使用static關鍵字,将要定義的靜态變量聲明為靜态外部變量。在該檔案中,靜态外部變量會隐藏外部同名變量。

//file1
int errors = 20; //外部變量
//file2
//符合文法 定義的靜态外部變量
//隻能在file2檔案之中使用 并且在file2中隐藏file1中的errors
static int errors = 15; 
           

15.靜态存儲持續性,無連結性

這種變量将static限定符用于代碼塊中定義的變量,代碼塊中使用static時,将導緻局部變量的存儲持續性為靜态的。這說明雖然變量隻在代碼塊中可用,但是他在該代碼塊不處于活動狀态的情況下仍然存在。兩次調用之間,該變量的值不會改變。而且該變量隻有第一次運作的時候初始化,之後不再進行初始化。

#include<iostream>
const int Arsize = 10;
void strcount(const char * str);
int main()
{
	using namespace std;
	char input[Arsize];
	char next;
	cin.get(input,Arsize);
	//使用cin.get(char*,int)讀取空行會傳回false來結束讀取
	while (cin)
	{
		cin.get(next);
		while(next != '\n')
			cin.get(next);
		strcount(input);
		cin.get(input,Arsize);
	}
	return 0;
}
void strcount(cosnt char * str)
{
	using namespace std;
	static int total = 0; //定義靜态變量 隻在代碼塊内使用 隻有第一次使用時進行初始化
	int count = 0;
	cout << "\"" << str << "\" contains";
	while(*str++)
		count++;
	total += count;
	cout << count << endl;
	cout << total << endl; 
}
           

16.說明符和限定符

  • 存儲說明符

    1.auto (C++11版本以前是存儲說明符,C++11的時候不再是存儲說明符)

    2.register

    3.static

    4.extern

    5.thread_local(C++11新增的)

    6.mutable

    注意:在同一個聲明中不能使用多個說明符,thread_local除外,他可以與static或者extern結合使用。C++11之前,register表示使用CPU寄存器存儲,C++11中他表示使用的是自動類型變量。static被用于作用域為整個檔案的聲明中,表示内部連結性;被用于局部聲明中,表示局部變量的存儲類型是靜态的。extern關鍵字表示引用聲明,即使用的是其他地方定義的變量。thread_local關鍵字指出變量的持續性與其所屬線程的持續性相同。mutable關鍵字在後面介紹。

  • cv-限定符

    1.const

    2.volatile

    cv所代表的就是const和volatile的首字母。最常用的cv-限定符就是const,他表明,記憶體被初始化之後,程式變不能再對它進行修改。而volatile我們并不是很熟悉,volatile關鍵字表明,及時程式代碼沒有對記憶體單元進行修改,其值也可能發生變化。這種變化可能來自硬體的修改,或者兩個程式之間的互相影響。該關鍵字的作用是改善編譯器的優化能力,例如,假設編譯器發現,程式在幾條語句中兩次使用了某個變量的值,則編譯器可能不是讓程式查找整個值兩次,而是将這個值存放在寄存器中,下一次從寄存器中取用。這種優化假設變量值在兩次取用之間不會變化,而volatile限定符則是告訴編譯器,拒絕使用這種優化。

  • mutable

    他可以用來指出,即使結構變量為const,其某個成員也可以被修改。

struct data
{
	char name[30];
	mutable int access;
	...
};
cosnt data veep = {"SHIYUQI", 0, ...};
strcpy(veep.name, "Joye");  //不允許
veep.access++; //允許
           

veep的const限定符禁止程式修改veep的成員,但access成員的mutable說明符使之不受這個限制。

17.const限定符

  • const說明符對于預設存儲類型稍有影響,在預設情況下,全局變量的連結性為外部的,但是使用const限定符修飾的全局變量連結性為内部,也就好像是添加了static說明符一樣。
  • 為啥要這麼做呢,這麼做有啥好處呢?

    1.C++修改了常量類型的規則,可以讓程式員更加輕松,例如:假設有一組常量存放在頭檔案中,使用的是const int Arsize = 5; 并在同一個程式的多個檔案中使用這個頭檔案,如果const int的類型術語全局變量,那麼在一個程式的不同檔案中引入頭檔案就相當于對于這個外部變量進行多次定義,這是違反C++中的單定義規則的,而連結性為内部的變量則不會造成這種麻煩事。

    2.内部連結性還意味着每個檔案都有自己的一組常量,而不是所有檔案都共享這組常量,每個定義都是其所屬檔案私有的,這就是能夠将常量定義放在頭檔案中的原因。這樣隻要兩個源代碼檔案包含同一個頭檔案,則他們獲得同一組常量。

注意:如果你實在希望某個常量的連結性為外部的可以使用extern來對其進行修飾。在這種情況下,必須在所有使用該常量的的檔案中使用extern來聲明它。

18.函數和連結性

和變量一樣,函數也有連結性,C++不允許在一個函數中定義另一個函數,是以C++中所有函數的存儲持續性都是自動為靜态的,即在整個程式執行期間都一直存在。在預設情況下,函數的連結性為外部,即可以在檔案中共享。可以使用extern關鍵字說明函數是在另一個檔案中定義的,還可以使用static将函數的連結性設定為内部的,使之隻能在一個檔案中使用(必須在原型和定義同時使用該關鍵字)。

static int private(double x);
...
static int private(double x)
{
...
}
           

使用static修飾的函數意味着隻在這個檔案中可見,還意味着其他檔案中可以定義同名的函數。和變量一樣,在定義靜态函數的檔案中,靜态函數将覆寫外部定義,是以即使在外部定義了同名的函數,該檔案仍使用靜态函數。

單定義規則也适用于非内聯函數,是以對于每一個非内聯函數,程式隻能包含一個定義。對于連結性為外部的函數來說,這意味着在多檔案程式中,隻能由一個檔案包含該函數的定義,但使用該函數的每個檔案都應該包含其原型。

内斂函數不受該規則限制,這允許程式員将内聯函數放在頭檔案中。這樣包含頭檔案的每個檔案都有内聯函數的定義,然而C++要求同一個内聯函數的定義必須相同。

19.語言連結性

連結程式要求每個不同的函數獨有不同的函數名,在C語言中這一點很好實作,因為C語言中不允許出現相同名字的函數,而在C++中允許函數重載,是以會有相同名稱的函數出現,是以C++中引入了名稱修飾的方法,編譯器會給函數重新改名字,為重載函數提供不同的符号名稱,這會導緻C語言中的名稱不再适用于C++之中,為了解決這個問題我們可以在函數原型中指出要使用的約定。

extern "C" void spiff(int); //使用C語言約定
extern void spiff(int); //使用C++約定
extern "C++" void spiff(int); //顯式使用C++約定
           

20.存儲方案和動态配置設定

//使用new配置設定的記憶體稱為動态記憶體
float *pt = new float[20];
//由new配置設定的80個位元組(假設float為4位元組)将永久儲存在記憶體中
//直到使用delete将其釋放
//當包含該聲明的語句塊執行完畢之後,pt指針将會消失
           
  • 使用new運算符初始化
//在C++98中
int *pt = new int(6); //初始化為6
double* pd = new double(6.77);//初始化為6.77
//在C++11中 清單初始化
int * pa = new int[5] {1,2,3,4,5};
int * pb = new int{6};  
           
  • new失敗時

    new如果找不到請求的記憶體量,這種情況下會引發異常 std::bad_alloc。

  • new:運算符,函數和替換函數
//運算符new和new[]調用函數
//這些函數被稱為配置設定函數,他們位于全局名稱空間中
//std::size_t是一個type_def對應合适的整型
void * operator new(std::size_t);
void * operator new[](std::size_t);
//delete delete[]釋放函數
void operator delete(void *);
void operator new[](void *);

//例子

//等價
int *pi = new int;
int *pa = new(sizeof(int));

//等價
int *pe = new int[40];
int *pb = new(40*sizeof(int));

//等價
delete pi;
delete(pi);
           
  • 定位new運算符

    new運算符常用語在堆中找到一個足以能夠滿足要求的記憶體塊。new還有一種變體,被稱之為定位new運算符,他的作用是讓您能夠指定要使用的位置。

    下面舉一個例子,介紹定位new運算符的使用方法:

//要使用定位new的特性,首先要在頭檔案中包含new
#include<new>
struct chaff
{
	char dross[20];
	int slag;
};
char buffer1[50];
char buffer2[500];
int main()
{
	chaff *p1,*p2;
	int *p3,*p4;
	//将new運算符用于提供了所需位址的參數
	//除了需要指定參數以外,句法和正常的new運算符相同
	//正常的new
	p1 = new chaff;
	p3 = new int[20];
	//定位new
	//表示從buffer1中配置設定位址給chaff,從buffer2中配置設定空間給int型的數組
	p2 = new (buffer1) chaff;
	p4 = new (buffer2) int[20];
}
           
  • 定位運算符的其他形式
int *p1 = new (buffer) int;
int *pi = new(sizeof(int),buffer);
           

21.名稱空間

  • 聲明區域

    聲明區域是可以在其中進行聲明的區域。例如,可以在函數外面聲明全局變量,對于這種變量,其聲明區域為其聲明所在的檔案,對于函數中聲明的變量,其聲明區域為其聲明所在的代碼塊。

  • 潛在作用域

    變量的潛在作用域從其聲明點開始,到期聲明區域結尾。是以潛在作用域要比聲明區域小。

  • 新的名稱空間特性

    通過定義一種新的聲明區域來建立命名的名稱空間,這樣做的目的之一是提供一個聲明名稱的區域。一個名稱空間中的名稱不會與另外一個名稱空間的相同名稱發生沖突,同時允許程式的其他部分使用該名稱空間中聲明的東西,下面的代碼使用了新的關鍵字namespace建立了兩個名稱空間:

//使用namespace建立名稱空間
namespace Jack{
	double pail;
	int pal;
	struct Well{...};
	void fetch();
}
namespace Jill{
	double bucket(double n){...}
	double fetch();
	int pal;
	struct Hill{...};
}
           

1.名稱空間可以是全局的,也可以位于另一個名稱空間中,但是不能在代碼塊内。

2.預設情況下,聲明的名稱連結性為外部的,除非他引用了常量。

3.除了使用者聲明的空間,還有一種全局名稱空間,他對對應于檔案級的聲明區域,之前說的全局變量,也可以了解成在全局名稱空間中。

4.名稱空間是開放的,支援将名稱加入到已有的名稱空間中,而且這種方法很常用。

  • 幾種通路命名空間中名稱的方法

    1.使用作用域運算符

    通路給定名稱空間中的名稱,最簡單的方法是通過作用域解析運算符::,來使用名稱空間來限定該名稱。

    2.使用using聲明指令

    using聲明可以使特定的辨別符可用,由uisng關鍵字和被限定的名稱組成

    3.使用using編譯指令

    using編譯可以使全部名稱空間可用,由using關鍵字和名稱空間的名稱組成

//1.使用作用域運算符
Jack::pail = 12.34;
Jill::Hill mole;

//2.使用using聲明指令
char fetch;
//在聲明區域外定義的變量使用fetch名稱并不會進行報錯
int main()
{
	using Jill::fetch;
	double fetch; 
	//這條語句是錯誤的,因為using聲明将名稱添加到局部聲明區域中
	//程式對于再對fetch進行定義報錯
	cin >> fetch;
	cin >> ::fetch;
}
//using聲明将特定的名稱添加到它的所屬的聲明區域中
//例如上哪的代碼就将using聲明Jill::fetch将fetch添加到main()定義的聲明區域中
//完成該聲明後fetch就可代替Jill::fetch
//和其他局部變量一樣,fetch會覆寫所有的全局變量
//可以使用 :: 作用域解析運算符來使用全局變量
//如果是在函數外進行using聲明,相當于将名稱添加到全局名稱空間中

//3.使用uisng編譯指令
using namespace Jack;
//編譯指令使得名稱空間中所有的名稱都可用
//使用的時候也不再需要作用域解析運算符
#include<iostream>
using namespace Jack;
//上述語句将使Jack名稱空間的名稱能全局可用
int main()
{
	using namespace Jack;
	...
}
//在函數中使用using編譯指令,使得其中名稱在函數内可用
           

注意:使用using編譯和using聲明,會增加名稱沖突的可能性。也就是說,如果名稱空間有Jack以及Jill,在代碼中使用作用域解析運算符則不會出現二義性。而如果使用using聲明,則系統可能分不清使用的是哪個命名空間中的内容。

Jack::pal = 3;
Jill::pal = 10;
//上述代碼系統能很清楚的明白
using Jack::pal;
using Jill::pal;
pal = 4; 
//系統會迷茫 你他喵到底用的是哪個名稱空間
           

22.using編譯和using聲明的比較

使用using編譯指令導入一個名稱空間中所有的名稱和使用多個using聲明是不一樣的。使用using編譯指令更像是大量使用作用域解析運算符。使用using聲明時,就好像聲明了對應名稱,而如果某個名稱已經在函數中聲明了,則不能再用using聲明導入相同的名稱。而using編譯則進行名稱解析,就想在包含using聲明和名稱空間本省的最小聲明區域中聲明了名稱一樣。

namespace Jill{
	double bucket(double n){...}
	double fetch;
	struct Hill{...};
}
char fetch;
int main()
{
	using namespace Jill;
	Hill Trill;
	double water = bucket(2);
	double fetch; //允許這種寫法
	cin >> fetch; //double fetch 剛剛定義的
	cin >> ::fetch; //char fetch; 全局變量
	cin >> Jill::fetch; //Jill中fetch
}
int foom()
{
	Hill top; //不允許 因為他并沒有使用名稱空間
}
           

一般來說,使用using聲明要比使用using編譯指令更安全,這是由于它隻是導入了指定名稱。如果該名稱與局部名稱沖突,編譯器會發生提示。如果使用using編譯導入所用名稱,可能包括并不需要的名稱,如果與局部名稱發生沖突,局部名稱會覆寫名稱空間版本,但是編譯器不會發生警告。這樣不安全。

//不建議使用using編譯
using namespace std;
//這樣并不是很好,因為可能會導緻局部變量覆寫名稱空間中的變量
//建議使用方式1
std::cout << "Hello World" << std::endl;
//建議使用方式2
using std::cout;
using std::endl;
cout << "Hello World" << endl;
           

23.嵌套名稱空間與傳遞性

//嵌套名稱空間
namespace elements{
	namespace fire{
		int flame;
	}
	float water;
}
//使用方式
//using聲明
using namespace elements::fire;
//作用域解析運算符
element::fire::flame = 5;

//名稱空間中使用uisng編譯和using聲明
namespace myth{
	using Jill::fetch;
	using namespace elements;
	using std::cin;
	using std::cout;
}

//通路方式
//因為using Jill::fetch 是以fetch也是在myth中可以使用如下語句
std::cin >> myth::fetch;
//當然fetch也在Jill中 如下語句也可以
std::cin >> Jill::fetch;
           
//命名空間的傳遞性
using namespace myth;
//等價于
using namespace myth;
using namespace elements;

//命名空間取别名
namespace my_very_favorite_things {...};
//下面的語句使mvft成為my_very_favorite_things 的别名
namespace mvft = my_very_favorite_things ;

//命名空間導入函數
namespace myth{
	void showPerson(const Person &);
}
using myth::showPerson;
//這裡并不加參數,原因是将所有名字為showPerson的函數全部引入到程式中
//因為也許有重載函數,是以根據不同的參數清單,來選對應的函數
           

24.未命名的名稱空間

//未命名名稱空間
namespace
{
	int ice;
	int fire;
}
//未命名名稱空間就好像跟着using編譯指令一樣
//該名稱空間中聲明的名稱潛在作用域為:從聲明點一直到聲明區域末尾。
//從這個方面看他與全局變量相似
//但是這種名稱空間沒有命名,是以不能顯式的使用using編譯或者using聲明
//是以不能保證他在其他位置可用
//具體來說就是不能在未命名名稱空間所屬檔案之外的檔案使用,就好像連結性為内部的靜态變量
static int counts;
//等價于
namespace 
{
	int counts;
}
           

25.一些命名空間指導原則

  • 使用在已命名名稱空間中聲明的變量,而不是外部全局變量
  • 使用在已命名名稱空間中聲明的變量,而不是靜态全局變量
  • 導入名稱時,優先使用作用域解析運算符或者using聲明的方法
  • 對于using聲明,首選将其作用域設定為局部而非全局
  • 不要在頭檔案中使用uisng編譯指令
  • 如果開發一個函數庫,應該将其放在名稱空間中。