C和C++中的名字空間和作用域
一、C語言有名字空間這個概念嗎?
提到名字空間(或者可能更普遍的叫法,名稱空間),很可能先想到的是C++,甚至是C#。C中沒有名字空間吧?一開始我也是這樣認為的,直到我看了C primer plus這本書,才直到C語言中其實也有名字空間的概念!而為什麼我們更熟悉C++中的名字空間呢?能是因為我們一些C++程式,不過知不知道為什麼,總是要加上一句using namespace std吧。其實C語言中也是有名字空間的概念的,只不過C語言中不能自定義名字空間,而C++中,我們可以定義自己的名字空間。
二、C語言中的名字空間和作用域
在網上看過很多資料,很多人都認為名字空間是作用域的一個補充,認為名字空間是為了區分同一作用域下相同的識別符號,解釋的也有一定道理。
但是在C primer plus中理解的是作用域是對名字空間的一個補充
。我是這樣理解的:名字空間之間是相互獨立的,但是作用域之間卻有包含的關係
,比如說一個全域性變數和一個函式內部的區域性變數,全域性變數的作用域是檔案作用域,而區域性變數的作用域是塊作用域,但是在函式內部全域性變數就消失了嗎?沒有呀,我們依然可以訪問全域性變數,只不過當區域性變數和全域性變數同名時,全域性變數被隱藏了而已
。可能有點糊塗,沒關係,往下看。
三、C語言中有4中名字空間
C語言中的四種名字空間分別為:
- 所有的標籤l(label)都屬於同一個名字空間
- struct、union、和enum的名稱,在C99中稱之為tag,所有的tag屬於同一個名稱空間。也就是說,如果你已經宣告struct A { int a }; 就不能再宣告 union A { int a };
說明:之所以讓所有的tag組成一個名稱空間,由於tag前面總是帶struct、union和enum關鍵字,所以編譯器可以將它們與其他的識別符號區分開。 - struct和union的成員位於它們各自struct或union名稱空間下,相互獨立互不影響,並且可以形成遞迴的名稱空間(如struct中再定義struct)。
例如:如果你已經宣告 struct A { int a };其成員的名稱為a,你仍然可以宣告 struct B { int a}; 或者 union B { int a };
說明:之所以讓struct和union成員各自成為一個名稱空間,是因為它們的成員訪問時,需要通過".“或”->"運算子,而不會單獨使用,所以編譯器可以將它們與其他的識別符號區分開。由於列舉型別enum的成員可以單獨使用,所以列舉型別的成員不在這一名稱空間類。 - 其他所有的識別符號,屬於同一個名稱空間。包括變數名、函式、函式引數,巨集定義、typedef的型別名、enum的成員等等。
四、C語言中有四種作用域
C語言中四種作用域為:
- 塊作用域
塊作用域作用域整個大括號中,比如一個函式中的區域性變數就具有塊作用域。還要注意,函式頭中的形式引數也是塊作用域,它的作用範圍也是整個函式體 - 檔案作用域
檔案作用域也叫全域性作用域,作用範圍是整個檔案。全域性作用域有連結屬性一說,分為內部連結屬性(靜態連結屬性)和外部連結屬性。當全域性變數被static修飾的時候,有內部連結屬性,也就是作用域為本.c檔案,在其他.c檔案中是不可見的。而當全域性變數被extern修飾的時候(也是預設的情況,如果不寫,就預設extern),有外部連結屬性,也就是不僅作用域本.c檔案,也作用域其他.c檔案。之所以叫連結屬性,是因為C語言的編譯單元為一個.c檔案,也就是說,如果在不同的.c檔案中含有同名的全域性變數,在編譯的時候是不會發現錯誤的,因為不同的.c檔案時分別編譯的,編譯時期是相互獨立的。但是在連結階段就會報錯。 - 函式作用域
注意和塊作用域相互區分,函式體中的區域性變數具有塊作用域,而不是函式作用域。所謂函式作用域,只針對“標號”。什麼意思呢?我們知道C語言有個goto語句(當然由於goto語句使程式的邏輯混亂,所以C++中摒棄了goto語句),那麼我們要goto到什麼地方呢?這個地方是用一個叫“標號”的東西表示的。比如我們用goto語句實現一個迴圈。
int i,sum=0;
i=1;
loop: if(i<=100)
{
sum=sum+i;
i++;
goto loop;
}
其中loop就是標號,他的作用域叫函式作用域,在函式內部有效。
- 函式原型作用域
函式原型作用域就是在函式原型宣告時,形參的作用域。
比如 void fun(int a,int b);
其中a和b的作用域就是函式原型作用域,作用域小括號內部。注意和函式定義時,形參的作用域相區別,定義時,函式形參的作用域是塊作用域,在函式體內有效。
重點是檔案作用域和塊作用域。
五、同一名字空間中的同一個作用域中,名字(識別符號)只能唯一
直接看例子吧,來的比較直接一些。
#include <stdio.h>
int fun = 10;
void fun()
{
printf("hahaha\n");
}
int main()
{
return 0;
}
結果:編譯時出錯!
gec@ubuntu:~/linux/nfs/struct$ gcc main.c -o main
main.c:5:6: error: ‘fun’ redeclared as different kind of symbol
void fun()
^~~
main.c:3:5: note: previous definition of ‘fun’ was here
int fun = 10;
^~~
原因就是 全域性變數fun和函式fun有著相同的名字空間,都是位於第4種名字空間中,而且兩者的作用域都是檔案作用域,同一名字空間和同一作用域中是不能夠有相同的識別符號的。
再來看一個例子:
#include <stdio.h>
struct fun{
int a;
int b;
};
void fun()
{
printf("hahaha\n");
}
int main()
{
return 0;
}
結果:通過編譯,沒有問題。
這是因為,雖然struct fun和函式fun有著相同的作用域,都是檔案作用域,但是有著不同的名字空間,struct fun屬於第二種名字空間,而函式fun屬於第四種名字空間。
再看一個例子:
#include <stdio.h>
struct fun{
int a;
int b;
};
void fun()
{
printf("hahaha\n");
}
int main()
{
struct fun fun;
fun.a = 10;
fun.b = 20;
return 0;
}
結果:編譯通過
我們來看 struct fun fun;這個語句,兩個fun並不衝突,因為他們有著不同的名字空間。第一個fun位於第二種名字空間中,而第二個fun位於第三種名字空間中,所以不衝突。
再看一個例子:
#include <stdio.h>
struct fun{
int a;
int b;
};
enum fun{
A,
B,
C
};
int main()
{
return 0;
}
結果:編譯錯誤
gec@ubuntu:~/linux/nfs/struct$ gcc main.c -o main
main.c:11:6: error: ‘fun’ defined as wrong kind of tag
enum fun{
^~~
原因:struct fun和enum fun中的fun有著相同的名字空間,都是位於第二種名字空間中,而且他們的作用域都是檔案作用域,所以一樣啦。
再來看一個例子(不常用):
#include <stdio.h>
int main()
{
int loop = 10;
int i = 0;
loop:i++;
if(i < 10)
goto loop;
return 0;
}
結果:通過編譯,沒有問題
原因,變數loop和標號loop位於不同的名字空間,變數loop位於第四種名字空間,而標號loop位於第一種名字空間。
最後再看一個例子(最常用):
#include <stdio.h>
int a = 10;
int b = 20;
int main()
{
int b = 30;
a = 50;
printf("a = %d,b = %d\n",a,b);
return 0;
}
結果,編譯通過:且輸出為:
gec@ubuntu:~/linux/nfs/struct$ gcc main.c -o main
gec@ubuntu:~/linux/nfs/struct$ ./main
a = 50,b = 30
原因 雖然全域性變數和區域性變數位於同一個名字空間中,都是位於第4中名字空間中,但是全域性變數b的作用域為檔案作用域,而區域性變數b的作用域為塊作用域,作用域小的b會將作用域大的b隱藏掉。
其實這就是我認為,作用域是名字空間的一個補充
,的原因。
兩個b有著同樣的名字空間,但是有著大小不同的作用域,實際上檔案作用域的作用範圍是包含塊作用域的作用範圍的,這也是為什麼在函式內部依然可以訪問全域性變數a的原因。作用域實際上是包含的關係,小的作用域會將大的作用域隱藏掉。但是名字空間之間卻是相互獨立的,
這一點在C++中,自定義名字空間時,會體會的更深刻。
再次表明一下我的觀點,我認為,作用域是對名字空間的一個補充,當在同一個名字空間中,用作用域來描述名字(也就是識別符號)的可見性,小的作用域會隱藏大的作用域。
六、下面說說C++中的名字空間
首先,C++中繼承了C語言中的名字空間,也就是那四類名字空間在C++中依然適用。需要說明的是,類的名字,位於第二種名字空間中,類中的成員的名字位於第三種名字空間中,這一點和struct、enum等類似。
C++中允許我們自定義名字空間,自定義的方法就是適用namespace關鍵字。
先考慮這樣一個問題:
一個工程中有多個.c檔案,其中一個.c檔案讓柯南完成,另外一個.c檔案讓小蘭完成。這兩個人心有靈犀,都定義了一個叫conan的全域性變數,這個時候當工程連結的時候就會報錯。原因是兩個conan有同樣的名字空間,都是位於第4種名字空間中,也有著同樣的作用域,都是檔案作用域,於是這兩個conan就衝突了。
怎麼解決呢?
前面提到過,只有同一個名字空間中同一作用域下,相同的名字才會衝突。所以要解決衝突無非就是修改名字空間或者作用域
。首先說修改作用域,前面提到過,檔案作用域的識別符號有一個連結屬性,static修飾的,的作用域僅僅限於本.c檔案,而extern(或者預設情況下)作用域是所有.c檔案,所以我們可以個其中一個conan加上static修飾,這樣就改變了作用域,就不會衝突了,但是問題是,我們既然定義成全域性變數,通常情況下,我們都希望它有外部連結屬性。我們之所以定義成全域性變數,很可能就是為了讓其他.c檔案使用。
所以static雖然解決了衝突,但是沒有達到我們的目的。那麼我們只能夠用另一種方法解決衝突了,就是修改名字空間,這在C語言中是不可行的,因為C語言中就那4中名字空間。但是在C++中是可行的,因為C++可以自定義名字空間。
於是柯南將他的int conan;
寫成了:
namespace nan
{
int conan;
}
小蘭將他的int conan;
寫成了:
namespace lan
{
int conan;
}
這樣柯南的conan就在名字空間nan中了,而小蘭conan就在名字空間lan中了,就不衝突了。
但是需要注意,在其他檔案中引用時,需要帶上名字空間名比如,lan.conan;
至於namespace的詳細用法,以及using namespace的使用,就不寫了,大家看書就好。
七、最後提一下C語言中怎樣解決命名衝突。
C語言中怎樣解決命名衝突呢?C語言並不支援自定義名字空間,所以解決命名衝突貌似只能是:1、 如果可能的話用static修飾(有些情況是不能能用static修飾的,比如提供給外部使用的函式
)2、在命名上下功夫,比如使用字首,比如libnids庫中的所有函式都是nids_開頭的。
相關文章
- .NET C#基礎(6):名稱空間 - 有名字的作用域C#
- Python作用域和名稱空間Python
- Python3 名稱空間和作用域Python
- C++中struct的空間計算C++Struct
- 作用域(scope), 定義空間(declaration space) 和 生存期(lifetime)
- C++ 煉氣期之變數的生命週期和作用域C++變數
- C++中::的作用C++
- C++中的&和&&C++
- js的作用域和作用域鏈JS
- C和C++中的staticC++
- C++名稱空間C++
- C++標準庫名字和標頭檔案--表C++
- c++中字元、字串和數字間的轉換C++字元字串
- JavaScript中變數和作用域JavaScript變數
- 對js中執行環境、作用域和作用域鏈的理解JS
- c語言中作用域和儲存期的區別C語言
- C++ 中的 volatile 和 atomicC++
- C++中&和*的含義C++
- C++中的NULL和nullptrC++Null
- 在C++中申請堆區空間與在C中申請堆區空間的異同點C++
- c++ 中.h 和.cppC++
- c++系列:匿名名稱空間C++
- C++ opencv中的namedWindow和imshowC++OpenCV
- SpringMVC(3)-request域和session域的作用和區別SpringMVCSession
- 聊聊 C++ 和 C# 中的 lambda 玩法C++C#
- C/C++中的實參和形參C++
- C++中break和continue的用法和區別C++
- 使用p名稱空間和c名稱空間的XML快捷方式XML
- 深入理解JavaScript作用域和作用域鏈JavaScript
- 7-3 名字查詢與類的作用域
- js 的詞法作用域和 thisJS
- C++系列: 巢狀名稱空間C++巢狀
- C++中的return和exit區別C++
- C++中堆和棧的完全解析C++
- C++中的運算子和表示式C++
- aardio教程四) 理解名字空間(namespace)namespace
- c++中string類物件和字元陣列之間的相互轉換C++物件字元陣列
- 物件的使用處理,作用域的和ajax中this的理解物件