C++聲明、定義,分別編譯、鏈接的語法機制

C/C++作為強類型語言,其重要特徵就是通過類型檢查特別是編譯期檢查確保其類型安全(最早期的語言和最底層其實是不區分類型的)。

變量和函數都要區分類型(函數通過返回值區分,同時函數的參數也需區分類型)。

C/C++考量到效率和單元測試的需要,使用“分別編譯”(separate compilation)和鏈接的語法機制。

C++ 語言支持“分別編譯”(separate compilation)。也就是說,一個程序所有的內容,可以分成不同的部分分別放在不同的 .cpp 文件裡。.cpp 文件裡的東西都是相對獨立的,在編譯(compile)時不需要與其他文件互通,只需要在編譯成目標文件後再與其他的目標文件做一次鏈接(link)就行了。比如,在文件 a.cpp 中定義了一個全局函數 "void a(){}",而在文件 b.cpp 中需要調用這個函數。即使這樣,文件 a.cpp 和文件 b.cpp 並不需要相互知道對方的存在(b.cpp只需聲明一下voida();),而是可以分別地對它們進行編譯,編譯成目標文件之後再鏈接,整個程序就可以運行了。

在編譯階段,變量和函數的使用都要有類型和標識符的提前聲明,以確保一致性,如:

<code>

extern

int

g_i;

int

add

(

int

a,

int

b); /<code>

在鏈接階段,變量和函數的使用都要有類型和標識符的提前定義,以確保一致性,如:

<code>

int

g_i;

int

add

(

int

a,

int

b) {    

return

a+b; }/<code>

在編譯階段,編譯器可以檢查出語法錯誤(Syntax errors)和類型錯誤(Type errors)。

在鏈接階段,鏈接器可以檢查出鏈接錯誤(也就是找不定聲明的定義或聲明與定義不匹配的情況)。以及運行期錯誤(runtime errors)和邏輯錯誤(logic errors),通常程序的debugging時間和testing時間大大超過coding時間。

定義如同聲明一樣,包括了一個類型與一個標識符的引入,定義相對於聲明來說,多了一個動作,對於變量來說,定義會確保內存的分配,對於函數來說,定義要求包含函數體,同樣的,在內存的代碼區會對應一段內存空間。所以說定義也是聲明,而聲明僅僅是聲明,未涉及到內存的分配。

通常將聲明寫在一個.cpp文件的頭部或一個單獨的.h文件中,再include進.cpp文件的頭部。

而變量(全局變量)和函數的定義則可以寫在同一工程的其它.cpp文件中。

簡單地說,"定義"就是把一個符號完完整整地描述出來:它是變量還是函數,返回什麼類型,需要什麼參數等等。而"聲明"則只是聲明這個符號的存在,即告訴編譯器,這個符號是在其他文件中定義的,我這裡先用著,你鏈接的時候再到別的地方去找找看它到底是什麼吧。定義的時候要按 C++ 語法完整地定義一個符號(變量或者函數),而聲明的時候就只需要寫出這個符號的原型了。需要注意的是,一個符號,在整個程序中可以被聲明多次,但卻要且僅要被定義一次。試想,如果一個符號出現了兩種不同的定義,編譯器該聽誰的?這種機制給 C++ 程序員們帶來了很多好處,同時也引出了一種編寫程序的方法。考慮一下,如果有一個很常用的函數 "void f() {}",在整個程序中的許多 .cpp 文件中都會被調用,那麼,我們就只需要在一個文件中定義這個函數,而在其他的文件中聲明這個函數就可以了。

來看Bjarne Stoustrup對一些相關術語的定義:

bit the basic unit of information in a computer. A bit can have the value 0 or the value 1.

byte the basic unit of addressing in most computers. Typically, a byte holds 8 bits.

word a basic unit of memory in computer, usually the unit used to hold an integer or a pointer. On many machines, an object must be aligned on a word boundary for acceptable performance.

object (1) an initialized region of memory of a known type which holds a value of that type; (2) a region of memory.

function a named unit of code that can be invoked (called) from different parts of a program; a logical unit of computation.

value a set of bits in memory interpreted according to a type.

declaration the specification of a name with its type in a program.

definition a declaration of an entity that supplies all information necessary to complete a program using the entity. Simplified definition: a declaration that allocates memory.

type something that defines a set of possible values and a set of operations for an object.

name equence of letters and digits started by a letter, used to identify ("name") user-defined entities in program text. An underscore is considered a letter. Names are case sensitive. The standard imposes no upper limit on the length of names.

operation something that can perform some action, such as a function and an operator.

parameter a declaration of an explicit input to a function or a template. When called, a function can access the arguments passed through the names of its parameters.

argument a value passed to a function or a template, in which it is accessed through a parameter.

expression combination of operators and names producing a value .

const a attribute of a declarations that makes the entity to which it refers readonly.

header a file containing declarations used to share interfaces between parts of a program.

interface a declaration or a set of declarations specifying how a piece of code (such as a function or a class) can be called.

聲明通常都寫在頭文件中,對於庫文件來說,頭文件是庫設計者和使用者的接口。

.h與.cpp如何include?

一個原則就是,需要某標識符時,就包含有這一標識符的聲明(不管是庫還是自己寫的.h,但是要可能避免在.h文件中include)。

<code> 

int

main

()

{

while

(

1

);

return

0

; }/<code>

需要使用std::cout、std::endl時,就需要包含其所在頭文件(一個聲明集):

<code> 
 

int

main

()

{

std

::

cout

"hello!"

std

::

endl

;

while

(

1

);

return

0

; }/<code>

再增加一個add()函數:

<code> 
 

int

add

(

int

a,

int

b) {

return

a+b; }

int

main

()

{

std

::

cout

<3,

4

)

std

::

endl

;

while

(

1

);

return

0

; }/<code>

分別寫到不同的文件中:

<code> 
 
 

int

main

()

{

std

::

cout

<3,

4

)

std

::

endl

;

while

(

1

);

return

0

; }

int

add

(

int

a,

int

b);

int

add

(

int

a,

int

b) {

return

a+b; } /<code>

再增加一個函數:

<code> 
 
 

using

namespace

std

;

int

main

()

{

std

::

cout

<3,

4

)

std

::

endl

;

string

dst =

"abcdefghidef"

; replaceAll(dst,

"def"

,

"123"

);

std

::

cout

<: class="hljs-built_in">endl;

while

(

1

);

return

0

; }

int

add

(

int

a,

int

b);

using

namespace

std

;

string

&

replaceAll

(

string

& context,

const

string

& from,

const

string

& to);

int

add

(

int

a,

int

b) {

return

a+b; }

string

&

replaceAll

(

string

& context,

const

string

& from,

const

string

& to) {

size_t

lookHere =

0

;

size_t

foundHere;

while

((foundHere = context.find(from, lookHere)) !=

string

::npos) { context.replace(foundHere, from.size(), to); lookHere = foundHere + to.size(); }

return

context; } /<code>

如果在implementation.cpp中include頭文件head.h是一種更便捷的寫法,因為在head.h中的聲明和已有的包含,很大可能也是implementation.cpp所需要的,特別是關係到函數的嵌套調用時。如果不包含head.h,就需要在implementation.cpp中單獨聲明所需要的標識符。至於main.cpp如何找到函數的定義,是因為在鏈接階段需要將全部工程中的文件鏈接到一起的,linker.exe會尋找聲明的定義,如果找不到,會報告一個鏈接錯誤。

ref

-End-


分享到:


相關文章: