C++編程思想學習筆記---第十章 名字控制

第十章 名字控制

10.1 來自C語言中的靜態元素

在C和C++的定義中,static都有兩種基本含義:

1) 在固定的地址上進行存儲分配,對象在靜態區創建,而不是每次調用函數時在堆棧上產生。  ===> 生存期:在每次進入定義該變量的函數時

2) 如果static變量是定義在某個文件中,不在任何函數內,則它在該文件中是全局的。 ===>作用域:僅在該文件中可用(除非用extern在別的文件聲明)


10.1.1 函數內部的靜態變量

 C++允許在函數內部定義一個靜態的內建類型變量,程序只在第一次進入時對其初始化,之後保持之前的值。

而如果沒有爲一個內建類型的靜態變量提供一個初始值的話,編譯器也會確保在程序開始時它被初始化爲0,包括指針。


10.1.1.1 函數內部的靜態對象

與上面的靜態變量相比,不同的一點在於:零賦值只對內建類型有效,用戶自定義類型必須用構造函數來初始化。因此,如果在定義一個靜態對象時沒有指定構造函數參數,這個類就必須有默認的構造函數。

#include<iostream>
using namespace std;

class X
{
	int i;
public:
	X(int ii = 0) : i(ii)
	{
		cout << "i = " << i << endl;
	}
	~X()
	{
		cout << "This is ~X()" << endl;
	}
};

void f()
{
	static X x1(5);
	static X x2;
}

int main()
{
	f();
}
上面成的打印爲:

i = 5
i = 0
This is ~X()
This is ~X()

程序控制第一次轉到對象的定義點時,才需要執行構造函數。


10.1.1.2 靜態對象的析構函數

靜態對象的析構函數:在程序從Main()中退出來時,或者從標準的C庫函數exit()被調用時才被調用;而多數情況下,main函數的結尾也是調用了exit()來結束程序的。這一點也非常重要,這警告了:不要在類的析構函數中調用exit(),有可能會引起無窮的遞歸調用

看下面這段代碼

#include<iostream>
using namespace std;

class A
{
private:
	int i;
public:
	A(int ii): i(ii) {cout << "This is A() i = " << i << endl;}
	~A() {cout << "This is ~A() i = " << i << endl;}
};

A a(1);

void f()
{
	static A ff;
}

int main()
{
	cout << "Main starts" << endl;
	A aa(2);
	cout << "Main middle" << endl;
	static A aaa(3);
	cout << "Main ends" << endl;
}
編譯後程序的輸出爲:

This is A() i = 1
Main starts
This is A() i = 2
Main middle
This is A() i = 3
Main ends
This is ~A() i = 2
This is ~A() i = 3
This is ~A() i = 1

這說明了以下幾點:

1、靜態對象的銷燬也是按與初始化時相反的順序進行的。

2、全局靜態對象比main函數先初始化,main函數退出後才進行析構。參考全局對象a,其構造函數在main函數開始之前被調用

3、在main函數中,即便局部靜態對象aaa比aa晚初始化,但它並沒有比aa先調析構函數。並且他也是在main函數退出之後才調用的析構函數。所以有以下結論:
1) 構造函數的調用順序: 全局靜態對象 >  進入main函數  > (局部靜態對象  、臨時對象)。括號中的對象的調用順序要看代碼中調用的順序
2) 析構函數的調用順序:main函數退出 >臨時對象 > 局部靜態對象 > 全局靜態對象。同類的對象的析構按與構造函數的調用順序相反的順序調用。
這給了我們一種能力:再進入main()之前執行一段代碼,並且可以在退出main()之後用析構函數執行代碼。


10.1.2 控制連接

這部分主要描述了變量的程序中的可見性問題。先看兩個概念:

1、外部鏈接:一般情況下,在文件作用域內的所有名字在程序中對所有翻譯單元都是可見的。因爲在連接時這個名字對連接器來說是可見的,對單獨的翻譯單元來說,它是外部的。全局變量和普通函數都有外部連接。

2、內部連接:在文件作用域內,一個被明確聲明爲static的對象或函數的名字對翻譯單元來說是局部於該單元的,這些名字有內部連接。


10.1.2.1 衝突問題

如果在進入Main()之前定義一個變量 int a = 0; 它等於 extern int a = 0; 說明了兩點:1、a是全局可見的,即外部連接的。2、a的值存儲在靜態區,當main()函數結束後,它纔會被釋放。

但是如果定義 static int a = 0; 僅改變上述中的第1點,他現在是本文件中可見的,也就是說內部連接的,無論在另一個文件中是否聲明extern int a; 都不能引用到該變量。而它依然存儲在靜態區。


10.1.3

其他存儲類型說明符: auto --- 自動變量,可由編譯器根據上下文推導出,所以一般用不到。register -- 寄存器類型的變量。即使在程序中聲明一個變量爲register類,也不能保證它一直存在於寄存器中,這部分工作仍然由編譯器完成。


10.2 C++中的靜態成員

作用:爲某個類的所有對象分配一個單一的存儲空間

類的靜態成員擁有一塊單獨的存儲區,不管創建了多少個對象。靜態數據屬於類而不屬於某個特定的對象。

10.3.1 定義靜態數據成員的存儲

方法如下:

在類的定義處,一般是頭文件中,在想使之成爲靜態成員的變量前加上static,如果定義一個靜態的全局或局部變量一樣。這只是聲明

class A{
    static int i;
public:
    //...
};
然後在實現文件中定義,需要使用作用域運算符。

int A::i = 1;

10.3.1.1 靜態數組的初始化

1) 內建類型的初始化

//in header
class Values
{
    static const int scInts[];
    static char scChars[];
};
//in cpp
const int Values::scInts[] = "1, 2, 3, 4, 5";
char Values::scChars[] = {'C', 'P', 'L', 'U', 'S'};
可以不在聲明時指定數組的大小,而利用自動計數功能確定數組的大小。

2) 類的數組

你可能想既然寫好了類的構造函數,利用內聯語法在聲明時定義另一個類的靜態對象或數組,然而這樣是不行的,必須像上面一樣寫在具有靜態變量的類的外部。


10.3.2 嵌套類和局部類

可以把一個靜態數據成員放在另一個類的嵌套類中,而不能在局部類中定義靜態數據成員。比如在一個局部函數內定義一個類的靜態成員。理由很簡單,局部函數不一定會被調用,其中的數據結構不一定會被初始化。而且靜態變量是分配在堆上的,而局部變量時分配在棧上的,這也是矛盾之處。


10.3.3 靜態成員函數

其意義與靜態數據成員一樣,都是爲整個類服務的,在整個進程的內存空間只存在一個該函數的存儲區。

特點:靜態成員函數沒有this指針,因此它只能訪問這個類之中的其他靜態數據成員和調用靜態成員函數。見下面的代碼段

<span style="font-size:14px;">//c10: StaticMemberFunctions.cpp
#include <iostream>

class X{
	int i;
public:
	static int j;
	X(int ii = 0): i(ii)
	{
		j = i;
		std::cout << "constuctor j = " << j << std::endl; 
	}
	int val() const
	{
		return i;
	}
	static int incr()
	{
		//++i;    //not allowed, static member func can only call static member data
		++j;
		std::cout << "incr j = " << j << std::endl;
		return j;
	}
	static int f()
	{
		//! val(); //error: static member function cannot access non-static member functions
		int tmp = incr();
		std::cout << "f() tmp j = " << tmp << std::endl;
		return tmp;
	}
};

int X::j = 0;

int main()
{
	std::cout << "before constructor X::j = " << X::j << std::endl;
	X x(5);
	std::cout << "after constructor X::j = " << X::j << std::endl;
	X* xp = &x;
	x.f();
	xp->f();
	X::f();
	return 0;
}</span>
注意在原書中,靜態變量j是private的,但是我將他改爲了public,這樣方便打印操作。

打印如下:

before constructor X::j = 0
constuctor j = 5
after constructor X::j = 5
incr j = 6
f() tmp j = 6
incr j = 7
f() tmp j = 7
incr j = 8
f() tmp j = 8

這說明:1、靜態數據成員在進入主函數之前就已經被賦值

    2、構造函數是可以修改靜態數據成員的值的

    3、靜態成員函數可以用對象的方式調用,也可以用類名的方式調用。

由此可以引申出一個常用的設計模式,即單例模式。整個進程的內存空間內只有一份該類的內存映像,保證了數據的唯一性。

發佈了27 篇原創文章 · 獲贊 6 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章