如果一個程序足夠大,代碼功能很多,可以想象,不可能把代碼寫在一個cpp文件里。我們需要模塊化,這樣的好處很多,方便分工合作,可讀性提高,調用也方便。
這個要怎么做呢?
很簡單直接當前cpp文件目錄下再新建一個test.cpp,如下:
里面就一句代碼:
int a=50;
就行;
然后ConsoleAppliciation1.cpp主程序代碼:
#include <iostream>
#include <string>
using namespace std;
extern int a;int main()
{cout << a;
}
然后編譯運行(編譯器會自動編譯所有文件):
很明顯,認到了外部變量a;?
關鍵是這個語句 extern int a;聲明a是個外部變量。起了作用,如果沒有這個聲明,則程序無法正常執行。
這個是針對多文件的嗎?
不只是,這個是聲明int a?是個外部變量。
只要在main函數里,訪問變量a前,按上下順序沒有看到變量的a的定義,都需要用這個extern聲明一下,否則編譯器不認識。
比如,如下語句:
#include <iostream>
#include <string>
using namespace std;
extern int a;int main()
{cout << b;
}
int b = 60;
可以看到?編譯直接報錯,如下:
不能識別變量“b",所以我們也要在最前面加上extern int b;即使在同一個文件中。
這樣再次編譯就正常了。?
那么函數也是同樣的道理,比如test.cpp?如下代碼:
#include <iostream>
void test()
{std::cout << 100;}
主文件:
#include <iostream>
#include <string>
using namespace std;
void test();
int main()
{test();
}
可以看到在最前面有void test();聲明,那么這里為什么沒有extern關鍵字?其實這里你加上也是可以的,因為跟變量不同,函數聲明可以省略extern,默認是extern;
那么同理,你的函數定義是main函數后面,?即使在同一個文件中,你也是要聲明一下函數的。
那么現在有一個問題,既然訪問其它文件的變量需要extern聲明一下,如果不加聲明,是否可以在兩個cpp文件里定義了一個同名的變量。
比如test.cpp
int a;
主文件cpp:
#include <iostream>using namespace std;
int a;
int main() {}
答案是不行,會報錯,如下:
如果你很疑惑,那么看下例代碼,同樣是未聲明extern;你就能理解了,跟這個差不多的道理:
#include <iostream>using namespace std;
int a;
int main() {}
int a;
結果都是不能重定義,這一點要明白。
最終的結果是兩個cpp,不能定義一樣的變量。但這并不是因為全局變量的作用域于所有cpp文件,全局變量作用域只是針對當前cpp文件。
不能定義是因為鏈接obj時是不能有相同的變量名。所以取消extern聲明,也是不能定義相同的變量。
這種現象如果很難理解,你可以在單文件里也找到類似現象。
#include <iostream>using namespace std;
extern int a;
int main() {}
int a;
下面的那個int a的作用域是從定義處開始,到文件結束,不是對于整個CPP文件的,但你不能說,根據作用域,就可以在最上面再定義一個int a了(取消extern聲明),這很顯然會引起沖突的。?
只不過一個是編譯時報錯,不能重定義,而兩個cpp里不能重定義,是編譯通過了(因為編譯時都是一次次單獨編譯CPP文件的),變成中間文件obj?鏈接的時候會報錯,不能有相同的外部鏈接變量。
那么,可以想象,如果分工合作,在多個cpp文件中,定義全局變量,總會不小心定義了相同的全局變量。
?要如何解決:
1.可以使用static修飾變量,這樣的全局變量,就只針對單cpp文件,不能被其它cpp文件訪問,也不會引起沖突,如:test.h
static int a;
這樣就沒問題了。?
2.可以使用命名空間的方法,這里就不示例代碼了,只是提供一種思路。
3.那么函數,跟變量是同樣的道理,不能在多個cpp文件里重復定義,只能定義一次,然后extern聲明引用。如果實在要定義,加上static關鍵字。聲明不被外部鏈接。只在當前cpp文件里有效。
明白了上面這些,我們可能需要使用大量的聲明,變量和函數(以后還會有類)
如果要有多個cpp文件要使用這些,那么每次都打這么一串代碼,實在麻煩。而且閱讀體驗也較差。
所以我們可以把這些聲明做成一個頭文件。
比如現在有test.cpp文件,如下代碼:
#include<iostream>
using namespace std;
int a = 5;
void test()
{cout << "\ntest函數\n";
}
然后是test.h頭文件:
void test();extern int a;
主文件ConsoleApplication1.cpp?包含頭文件,然后調用:
#include <iostream>
#include"test.h"
using namespace std;int main() {cout << "a值:" << a;test();
}
運行結果:
這里的#include"test.h",實際是預處理命令,即:將test.h里的代碼插入當前位置(非編譯).
所以實際是這樣:
#include <iostream>
void test();
extern int a;
using namespace std;int main() {cout << "a值:" << a;test();
}
本質跟我們之前手動聲明是一樣的。
那么這里又有幾個問題,既然#include只是簡單的插入代碼,
或者為什么test.h和test.cpp聲明和定義要分開寫呢?我全寫在test.h里。
首先回答test.h里是可以定義變量,或者具體函數代碼,但十分不建議這樣寫,如果你定義了變量a,前面說過了,多個文件#include test.h,鏈接時會報重復定義。這樣極容易造成混亂。
所以標準的做法是分開寫,如果一個cpp里的變量和函數要被其它文件使用,聲明部分要分開寫成一個頭文件。以表示共用。
#ifndef
講到頭文件,這里不得不提一下ifndef?宏定義相關。
比如,將上面test.h代碼改成這樣:
#ifndef TEST_H
#define TEST_H
void test();
extern int a;#endif
上面的語句意思是,如果沒有定義宏TEST_H,則執行#endif之前的語句,就是定義宏TEST_H,然后聲明函數test和變量a.
這樣做的好處是防止代碼重復包含,比如一個cpp里多次引用了頭文件,它只會插入一次代碼,如果識別到了宏TEST_H.?
如類似這樣:
#include <iostream>using namespace std;
#include"test.h"
#include"test.h"
int main() {test();
}
注意這里的宏定義只是對當前cpp文件進行判斷,因為本質上宏定義也是預處理,只是單純替換文本。
所以
#ifndef TEST_H
#define TEST_H
#endif
這些語句,就像你在一個文件里#define max 100
然后你在另一個文件里使用max,是不合法的,只作用于當前文件,所以如果在另一個文件用#ifndef?判斷max,肯定也是未定義的。?
引申:那么如果你在test.h里面定義了一個變量a,而不是聲明,就不要指望這個#ifndef?幫你解決前面多個文件包含test.h沖突的問題,而且這樣即使解決了(比如編譯器改了邏輯,宏列表對所有cpp有效),也只是假象,因為它會把代碼都清掉了(除了第一次文件),其它文件根本就訪問不到這個變量a,自然也不會有沖突了。
我們要做的就是按規矩寫代碼。這里就告訴了我們為什么要這樣做。這樣就能避免很多問題。