C++ 深入挖掘"Hello, world!"
前言
(該文技術性比較強,最初發(fā)布在CSDN上,后來做了刪改后發(fā)到這里)
每個人學編程的時候,第一個接觸的大概率都是Hello World,比如說Python:
再比如說,Java:
C++的版本則是:
B站代碼塊無法輸入< >,所以用了數(shù)學符號里面的< >,以下代碼都是這樣
如果不算上 { } 的兩行,這個代碼一共只有3行,但是幾乎沒多少人會去一行行的詳細挖掘其中的內(nèi)容,所以今天我就準備更加深入的探索其中的每一行的細節(jié)。

1. Pre-processor
第一行:
是將iosteam這個庫引入到我們的代碼中,#include是一個所謂的pre-processor directives里面的一個(其他的還有#define, #pragma等等)。這個preprocess也是C、C++與眾不同的一個地方。
如果你在某一行里面定義了preprocessor,那么你這一行里面不能再放其他非preprocessor的代碼,但是preprocessor的語句可以橫跨多行。preprocess末尾也不需要添加分號。
#include 我們知道有兩種格式,一個是#include <library>,另一個是#include “l(fā)ibrary.h"。如果我們的library已經(jīng)在我們的開發(fā)環(huán)境里面的話,我們用< >來導入,否則就用" "。如果你用的是Visual Studio的話,有關的設定可以在下圖的地方看到

如果你的項目里面同一個library被include多次,那么你很可能在編譯程序的時候會出錯。所以很多情況下,我們用#pragma once解決重復include的問題。或者我們可以通過#ifndef和#define這兩個語句。(詳細內(nèi)容可參見:https://en.wikipedia.org/wiki/Include_guard)

每一個C++都有一個main函數(shù):
在執(zhí)行C++程序的時候,程序會自動找到main函數(shù)的所在位置,然后從main開始執(zhí)行。
如果我們的程序里面沒有main函數(shù),絕大多數(shù)情況下,我們的程序不會被編譯。(記得誰講過,C++很熱門的一個話題就是——Will it compile?)
這個main函數(shù),還有另一個我們熟知的版本:
argv (Command line argument) 用來傳輸用戶給的參數(shù),因為argv是一個數(shù)組,所以我們需要argc (argument count) 來定義這個數(shù)組有多長。
此外,還有一個細節(jié)就是,這個main函數(shù),需要返回一個int類型的數(shù)據(jù),有時候我們也可以返回void,但編譯器會返回如下的警告:
Warning C4326 return type of ‘main’ should be ‘int’ instead of ‘void’
但是這里我想說的其實是——注意我們的main函數(shù)下面沒有return。一般而言,如果我們有一個函數(shù)需要返回int或者其他類型的數(shù)據(jù),而我們沒有return語句的話,編譯器會返回如下的錯誤:
Error C4716 ‘Some Function’: must return a value
但是唯獨main是一個特例,如果我們沒有return語句的話,程序會自動幫我們return 0
最后,由于C++非Type safe,所以我們甚至可以返回一個Boolean類型或者char類型的數(shù)據(jù),例如:
這個在Type safe的語言里面是不允許的。

接下來的一句:
我們都知道是輸出Hello world到我們的屏幕上??此坪唵危瞧渲袇s包含著很多復雜的東西。
首先我們從最簡單的開始說起。std是一個所謂的namespace
namespace的存在意義就是為了避免名字上面的沖突。比方說我手里有兩個長得一模一樣的兩個函數(shù),都叫做咖啡,那么這個時候我就用Starbucks和Dunkin’ Donuts將這兩杯咖啡區(qū)分開來。(這里沒有給這兩家店做廣告的意思 lol)
其次,cout來自于iostream這個庫,所以我們必須要導入這個庫,才能使用cout。而cout并不是唯一可以將內(nèi)容輸出到屏幕的方式,我們也可以用printf來替代cout
printf屬于C里面的一個函數(shù),而cout屬于C++里面的函數(shù),具體的性能差異可以忽略不計,printf還可以輸出格式化的內(nèi)容,如:
但cout卻沒有這個功能。此外,printf還有其他親戚,如fprintf。除了printf以外,C++內(nèi)還有puts,puts和printf比較接近。
以上三個方式的性能差異,都可以忽略不計,具體使用哪一個主要還是看每個程序員的習慣。
接下來,是<<這個符號,這個符號是將比特向左移動(bitwise left shift),a<<b就是將a的比特朝左移動b的距離。當然在這里,是運算符重載來賦予<<其他的意義——即將后面的那一串字符串print到我們的控制臺里面。
此處運算符重載的格式為:
此處只列舉float,其他參數(shù)還包括int, double, bool等十多種。這里留意這句語句的返回類型。
再接下來,一個非常復雜的問題是,cout到底是一個什么東西?
很明顯,不像printf和puts,cout它不是一個函數(shù)(如果是函數(shù)那怎么能運算符重載呢?),它是一個std::ostream類型的object(ostream是一個class),并且這個實體名字叫做cout。
那么既然cout是一個實體的object,那么為什么我們不需要在代碼里面創(chuàng)建實體,比如說:
這里值得一提的是,你的確可以創(chuàng)建一個std::ostream類型的實體,然后通過那個實體來執(zhí)行ostream下某些功能:
但因為cout這個實體,我們的庫已經(jīng)幫我們創(chuàng)建好了,通過extern使得我們的cout成為一個全局可見的變量:
這樣的話就剩得我們再去創(chuàng)建一個實體了。

接下來是ostream,它的原名叫做basic_ostream:
這里的using是給我們的某一個數(shù)據(jù)類型起一個別名,typedef也能完成相同的任務:
這個basic_ostream的定義如下:
這里的_Elem里面放char type,比如說char, wchar之類的,而_Traits里面基本上是放std::char_traits<_Elem>(即,如果是char,那么就放std::char_traits<char>)。
而至于這個class本身,我目前還沒有完全搞懂其中的機制??傊?,它的作用就是操作所謂的流(stream),流由連續(xù)的byte組成,這個流可以是formatted(比如說int, boolean, 字符串類型的數(shù)據(jù)),也可以是unformatted(純粹的binary數(shù)據(jù)),我們的"Hello, world"字符串就是一個流。
最后,如果我們要讓cout輸出一個自定義的內(nèi)容的話,我們可以用以下的方式:
正如我們之前運算符重載那樣,這里我們只是再額外重載一個,這里我們必須返回一個地址(reference),并且我們的參數(shù)里面必須傳輸一個ostream的地址,因為我們的cout是一個object。

后記
本來這個內(nèi)容是打算在愚人節(jié)做成視頻發(fā)出來博大家一樂的,后來發(fā)現(xiàn)這個真的是笑不出來
參考資料:
CPP Reference: https://en.cppreference.com/w/
Microsoft Docs C++ Language Reference: https://docs.microsoft.com/en-us/cpp/cpp/cpp-language-reference?view=msvc-160

THE END.