C++ 煉氣期之基本結構語法中的底層邏輯

2022-06-10 12:05:47

1. 前言

從語言的分類角度而言,C++是一種非常特殊的存在。屬於高階語言範疇,但又具有低階語言的直接存取硬體的能力,這也成就了C++語言的另類性,因保留有其原始特性,其語法並不象其它高階語言一樣易理解,但處理能力卻比其它語言高很多。

從語言的處理能力和速度而言,讓人愛;從語法體系角度而言,對於學習者並不友好。

但對於專業開發者,建議學好C++語言,C++的底層特性對於理解其它語言的高階封裝原理有很大的幫助。

本文將從一個簡單的Hello world C++程式開始,以此程式中出現的基礎知識為匯入點,深入探討這些知識的底層邏輯

好!現在!開始C++之旅……

2. 基本結構語法

先從下面的Hello World程式開始,逐一解釋這幾行程式碼中所包含的程式微觀世界中的結構邏輯。

#include <iostream>

using namespace std;
int main(int argc, char** argv) {
	cout<<"Hello World"<<endl; 
	return 0;
}

所謂 「一葉而知秋」 ,上述的Hello World程式雖然只是簡單的寥寥幾行程式碼,但是卻完整地詮釋了一個標準的C++程式所需要具備的基礎邏輯結構。

幾行程式碼和多行程式碼的程式的區別在於所要實現的功能不同,其核心的組織結構都有同工異曲之地。

當規模較大時,程式結構無非在遵循基本結構的主導思想上進行分、再分、繼續分……

如同一個大家庭分成幾個小家庭,但每一個家庭的基本結構相似。

2.1 預處理指令

Hello World程式中的第一行程式碼:

#include <iostream>

語法解釋:

  • #C++預處理指令識別符號號,表示後面緊跟著的是 預處理指令
  • 不同的預處理指令有不同的功能。

預處理指令在編寫C++程式時是否是必須的?

答案:不是必須,那麼什麼時候需要預處理指令

要了解什麼時候需要新增預處理指令,則需要理解此行程式碼的語法用意。

高階語言機品語言的區別之一是高階語言會提供大量的已經編好的功能程式碼,這些功能性程式碼統稱為API(應用程式呼叫介面)

對於不同語言而講,其提供的名稱略有不同,如 JAVA 中以類庫方式提供,PYTHON語言中以模組方式提供,C++則是以標頭檔案方式提供……其本質一樣。

編寫程式時,如果需要用到語言提供的功能程式碼時,則需要遵循不同語言的呼叫語法匯入後方能使用。

#include 指令的作用:指定程式中需要包含的標頭檔案。欲在程式中使用C++提供的API,因API龐大繁複,C++對其API以分類方式儲存在不同的檔案中,這些檔案稱為標頭檔案#include 後需要指定標頭檔案名稱。

理論上講,在程式中可以不使用 #include指令。但在實際程式中幾乎是不可能的,否則,並不能發揮高階語言的優勢,請直接使用機器語言便可。

#include 語法

include是一個匯入或包含標頭檔案的指令,還有另一個語意,預設情況下,C++執行系統會建立一個名為include的目錄,存放所有的自帶標頭檔案。此目錄也稱為預定義目錄

#include <標頭檔案名>

在匯入C語言的標頭檔案名時,需要指定標頭檔案的擴充套件名h,匯入c++標準中的標頭檔案時,可以不指定擴充套件名。

//匯入 C 語言的標頭檔案需要指定擴充套件名
#include <stdio.h>
//匯入 C++ 標準中的標頭檔案時可以不指定擴充套件名
#include <iostream>

#include還有另外一種使用語法:

#include "標頭檔案名"

使用雙引號和使用尖括號包含標頭檔案的區別:

  • 使用#include <檔名>指令時,編譯器會直接從include目錄中查詢對應的標頭檔案。
  • 使用#include "檔名" 指令時,則是先在當前檔案所在的目錄搜尋,沒有則到Include目錄裡去找對應的檔案。

在匯入系統提供的標頭檔案時,建議使用尖括號 。

在匯入自定義標頭檔案時,建議使用雙引號。

Hello World程式中,匯入了iostream檔案,則意味著程式需要iostream檔案中提供的API,那麼又是什麼?有什麼作用?

這個問題稍後回答。

2.2 主函數

C++是程式導向的程式語言,所謂過程指程式碼以函數為基本單位進行組織,當然,函數還有更多特性,關於函數的細節,另行文再聊。

這裡聊聊主函數的功能和語法結構。

int main(int argc, char** argv) {
    //自己的程式碼
	return 0;
}

主函數功能描述:

  • 主函數是整個程式的入口。當執行程式時,C++執行系統會查詢程式中是否有一個符合系統要求的主函數語法結構。
  • 如果找到,則從此函數的第一行程式碼進行指令解析。
  • 如果沒有找到,則呼叫失敗。

類似於要去某一個小區拜訪朋友,首先第一步要找到小區的入口大門。

小區也許會有多個入口大門,但C++只有主函數這麼一個入口。

主函數的語法結構:

雖然上文的主函數中包含較多的組成元素,如返回型別引數……因C++有向下相容性。只要保證函數名為 main其它元素都可以省略,對於C++執行系統而言,可以只認main 函數名

如下去頭剔尾之後的主函數,C++執行系統依然認識。

main() {
//自己的程式碼
}

C++可理解為C語言的plus版本,C++在發展過程中,有很多標準,所以C++新標準都會向後相容。

編寫程式碼時,主函數儘可能遵循當前C++的新標準。

2.3 邏輯結構

麻雀雖小,五臟俱全Hello World程式雖然看似簡陋,但縮影了任何其它功能強大程式的基本邏輯結構。

無論程式的規模大小,程式的本質都是用來處理資料。從全域性角度來講,任何程式的邏輯結構都是如下幾部分元件:

  • 資料。可以說,程式開拔,資料先行,無資料無程式。資料的來源有多種,如已知資料互動資料外部儲存裝置中的資料網路資料……對於Hello World程式而言,功能是輸出Hello worldHello World便是程式中的資料(已知資料)。
  • 邏輯處理Hello World程式只是演示程式,沒有資料處理這一環節,但是在開發實際可用的程式時必須有資料處理環節,否則,吃進去又直接吐出來,是沒有意義和營養的程式。
  • 輸出或展示資料。程式總是通過處理資料,生成結果資料。結果資料需要通過某一種途徑告訴使用者,從而指導使用者的行為和認知。這便是輸出的意義。

可以說,編寫程式,就是如何掌控資料的輪迴和重生。

cout<<"Hello World"<<endl; 

如上程式碼,Hello World資料的存在形態在C++語法中稱為常數或字面值資料。

coutc++提供的專用於輸出的指令,其包含在iostream檔案中,如此,應該明白為何要在程式的第一行新增:

#include <iostream>

cout 語法:

cout<<"資料";

cout是一個輸出指令,但其語意是指代一個標準的輸出裝置,其底層是以一個抽象名的方式連線到了一個具體的輸出硬體裝置資源,這個裝置往往指的就是顯示器。或稱其為標準輸出裝置。

cout 和資料之間有一個<<,這是一個重定向運運算元,表示資料通過 <<流向標準輸出裝置。至於怎麼流的,可能就要查閱原始碼,其實現過程非一言二語能說清。這也是高階語言的特性之一,遮蔽底層邏輯,讓開發者只關注於自身的高階業務邏輯。

在使用 cout指令時,還有一個名稱空間的概念。再回頭,檢視上文最初給出的完整的Hello Wolrd 程式中,其中有一行程式碼:

using namespace std;

如果沒有這一行程式碼,不好意思,cout不能工作,或者說,根本找不到cout。可以開啟iostream的原始碼看一看。

#define _GLIBCXX_IOSTREAM 1

#pragma GCC system_header

#include <bits/c++config.h>
#include <ostream>
#include <istream>

namespace std _GLIBCXX_VISIBILITY(default)
{
_GLIBCXX_BEGIN_NAMESPACE_VERSION

  //@{
  extern istream cin;		/// Linked to standard input
  extern ostream cout;		/// Linked to standard output
  extern ostream cerr;		/// Linked to standard error (unbuffered)
  extern ostream clog;		/// Linked to standard error (buffered)

#ifdef _GLIBCXX_USE_WCHAR_T
  extern wistream wcin;		/// Linked to standard input
  extern wostream wcout;	/// Linked to standard output
  extern wostream wcerr;	/// Linked to standard error (unbuffered)
  extern wostream wclog;	/// Linked to standard error (buffered)
#endif
  //@}

  // For construction of filebuffers for cout, cin, cerr, clog et. al.
  static ios_base::Init __ioinit;

_GLIBCXX_END_NAMESPACE_VERSION
} // namespace

#endif /* _GLIBCXX_IOSTREAM */

原始碼中有一行:

namespace std _GLIBCXX_VISIBILITY(default)

C++引入了名稱空間這一概念。

什麼是名稱空間?

就是起到一個邏輯分類的作用。

一個班上如果有 2 個同姓名的學生怎麼辦?

在姓名前面再新增一個標識就可以了,如大張三小張三,這裡的有就類似於名稱空間。

C++可以使用名稱空間作為附加資訊來區分不同庫中相同名稱的函數、類、變數等。

也就是說為了避免其它的標頭檔案中有 coutiosteam 為自己的cout前面新增了字首 std。當然除了使用如下的語法。

using namespace std;

也可以直接在cout前面新增 std名稱空間描述符。

#include <iostream>
int main(int argc, char** argv) {
	std::cout<<"Hello World"<<std::endl; 
	return 0;
}

注意使用語法,名稱空間後面有::

endl是一個換行指令。也是定義在iostream檔案中的std名稱空間中。

3. 執行程式

遵循C++語法編寫的程式碼稱為原始碼,原始碼以標準擴充套件名cpp的檔案儲存,稱此檔案為原始碼檔案。

Tip: 原始碼檔案的擴充套件名不一定是cpp。不同的平臺上的C++擴充套件名可能不一樣,如果擴充套件名不是cpp,只要檔案內容符合C++語法標準,此檔案依然是C++原始碼檔案。

原始碼並不能直接被計算機識別,需要請一個專業翻譯員把原始碼翻譯成計算機能理解的二進位制指令和資料,翻譯後的程式碼稱為目的碼

翻譯員在翻譯時有 2 種可選的翻譯模式:

  • 解釋模式:逐行翻譯原始碼。顯然,其速度較慢,但易於偵錯和找出程式中的邏輯錯誤。
  • 編譯模式:把原始碼一次性翻譯成目的碼。顯然,其速度較快。現代編譯系統已經具備很好的偵錯功能。

所以,執行C++程式之前,需要安裝C++執行系統,此係統中至少要包含C++提供的API和翻譯員,C++選擇的編譯模式。

安裝C++執行系統,最簡單的方式直接安裝類似於帶有執行環境的 dev-c++ IDE開發工具。

如何安裝,本文不做贅述。

編譯器的執行流程:

  • 編譯成目標檔案:檢查原始碼中是否存在語法錯誤,然後把源程式編譯成擴充套件名為 obj目標檔案,目標檔案並不是最終編譯產物,也不能執行。
  • 連結標頭檔案:因程式中會使用到C++的各種 API,會包含各種標頭檔案,則需要將目標檔案和各種必須的庫(標頭檔案的集合)連結在一起生成最終的可執行檔案。
  • 可執行檔案:在windows平臺中,可執行檔案的擴充套件名為exe,原始碼被編譯後的最終執行檔名預設為 a.exe

本文使用dev-c++編輯和編譯程式。

4.總結

本文從一個簡單的C++程式入手,講解C++程式的基本邏輯結構。程式雖小,卻是所有可執行程式的縮影。

當然,規模不同,其要使用到的C++相關知識會更多,但全域性宏觀結構是相似的。