目錄
- 引言
- 測試代碼
- 如何避免
-
- 1. 使用#ifndef
- 2. 使用#pragma once
- 3. 使用匿名命名空間(拓展)
- 三種連結屬性(拓展)
- 反思
-
-
- 參考連結
-
引言
重定義,簡而言之就是同一個變量定義了兩次或者多次。如下面的代碼所示,在test.h中定義了一個類,但是我在add.h中和main.cpp中都引用了test.h檔案,這時候在編譯的過程中會産生錯誤。
測試代碼
/*****test.h*****/
class Test{
public:
int i;
};
/*****add.h*****/
#include "test.h"
int Add1(int a,int b);
/*****add.cpp*****/
#include <iostream>
#include "add.h"
int Add1(int a, int b)
{
Test test_add;
std::cout<<"This Add1 function"<<std::endl;
return a+b;
}
/*****main.cpp*****/
#include <iostream>
#include "add.h"
#include "test.h"
int main()
{
Test test_main;
std::cout<<"Run main"<<std::endl;
std::cout<<"1+1="<<Add1(1,1)<<std::endl;
return 0;
}
如何避免
1. 使用#ifndef
使用過程中可以采用#ifndef宏指令來避免這種問題,使用方式如下:
#ifndef MY_TEST_TEST_H
#define MY_TEST_TEST_H
class Test{
public:
int i;
};
#endif //MY_TEST_TEST_H
ifndef這套條件編譯是為了防止同一個cpp檔案中包含多個相同.h檔案的(比如有一個main.cpp檔案包含了test.h,add.h又包含了test.h,那麼當你在main.cpp引用Add函數時,會#include<test.h>(因為有上面的條件編譯的關系而不會出錯)。因為編譯器在編譯的時候是按照cpp檔案為機關編譯的,每個cpp檔案編譯成.o檔案,然後再将這些.o檔案連結起來,最後與運作庫連結形成可執行檔案。你在兩個cpp檔案中包含了同一個.h檔案。這根本和條件編譯沒什麼關系。你在.h檔案中定義了Test類。那麼就表示你兩個cpp檔案中,每個cpp檔案中都有這樣的全局變量定義:class Test;當每個cpp檔案單獨編譯當然沒有問題,但是當它們連結起來的時候就出現問題了:兩個cpp檔案中都有變量Test類,是以當然是重複定義啊。問題是出在連結的時候的。
雖然說使用條件編譯可以解決這個問題,但是随着檔案的增多也會導緻宏的明明會重複,這時候就會導緻條件編譯失效的問題。而且編譯器每次都需要打開頭檔案才能判定是否有重複定義,是以在編譯大型項目時,#ifndef會使得編譯時間相對較長,是以一些編譯器逐漸開始支援#pragma once的方式。
2. 使用#pragma once
使用過程中可以采用#pragma once宏指令來避免這種問題,使用方式如下:
#pragma once
class Test{
public:
int i;
};
你無法對一個頭檔案中的一段代碼作#pragma once聲明,而隻能針對檔案。其好處是,你不必再擔心宏名沖突了,當然也就不會出現宏名沖突引發的奇怪問題。大型項目的編譯速度也是以提高了一些。在跨平台的時候,有些平台不支援這種使用方式,目前來說我還沒有遇到。
3. 使用匿名命名空間(拓展)
使用方法如下:
namespace {
class Test{
public:
int i;
};
}
編譯器在内部會為這個命名空間生成一個唯一的名字,而且還會為這個匿名的命名空間生成一條using指令。是以上面的代碼在效果上等同于:
namespace __UNIQUE_NAME_ {
class Test{
public:
int i;
};
}
using namespace __UNIQUE_NAME_;
為什麼使用匿名空間也可以防止變量的重定義問題,是因為匿名空間具有internal連結屬性,這和聲明為static的全局名稱的連結屬性是相同的,即名稱的作用域被限制在目前檔案中,無法通過在另外的檔案中使用extern聲明來進行連結。如果不提倡使用全局static聲明一個名稱擁有internal連結屬性,則匿名命名空間可以作為一種更好的達到相同效果的方法。
三種連結屬性(拓展)
連結屬性(linkage)分為三種——外部(external)、内部(internal)、無(none)。
外部屬性:外部連結屬性就意味着,一個辨別符,不僅可以在目前的源檔案中使用,還可以在程式的其他檔案中使用。
内部屬性:使用static修飾的辨別符具有内部連結屬性,隻在本源檔案中有效。const修飾的也預設是内部連結屬性,當出現extern const時,extern的屬性壓過const的屬性,成為外部連結屬性。
無屬性:局部變量。
反思
為什麼會寫這篇文章。這是因為在實踐過程中出現了變量的重定義,在test.h中使用了const char* str=“test”;當時我在這個頭檔案中使用了#pragma once,但是沒有作用,我采用了匿名空間才解決了問題。這是為什麼呢?
頭檔案一般存放變量的聲明,不應該存放變量的定義。而我當時存放的是變量的定義,并且多個檔案引用,導緻出現了報錯,出現問題的時候還在納悶,加了#pragma once,還會出現這種問題,沒有道理呀!!!如果你讀完這篇文章,那麼你還會問上面說的const修飾的也是内部連結屬性,那我使用const char *為什麼還會報錯?
const修飾的變量是内部連結屬性,這時毫無疑問的。出錯的原因就是因為我所使用的是const char *,此時的const修飾的是char,而非指針變量str,是以str在這裡是全局的。如果const修飾的是指針變量p(char const str),這時候str對外連結才是不可見的,是以我遇到的問題還可以講const char變為char *const 或者const std::string(這時候使用std::string就有點大材小用了)
參考連結
匿名空間:https://www.cnblogs.com/youxin/p/4308364.html
連結屬性:https://blog.csdn.net/u011285208/article/details/94738969
const内部連結屬性:https://www.cnblogs.com/sheshiji/p/3427896.html