C++開始使用C ++

備註

“Hello World”程序是一個常見的示例,可以簡單地用於檢查編譯器和庫的存在。它使用C ++標準庫,使用來自<iostream> std::cout ,並且只有一個文件可以編譯,從而最大限度地減少了編譯期間可能出現用戶錯誤的可能性。


編譯器和操作系統之間編譯C ++程序的過程本質上是不同的。主題Compiling and Building包含有關如何在不同平台上為各種編譯器編譯C ++代碼的詳細信息。

版本

標準發布日期
C ++ 98 ISO / IEC 14882:1998 1998-09-01
C ++ 03 ISO / IEC 14882:2003 2003-10-16
C ++ 11 ISO / IEC 14882:2011 2011-09-01
C ++ 14 ISO / IEC 14882:2014 情節中字
C ++ 17 TBD 2017年1月1日
C ++ 20 TBD 2020年1月1日

你好,世界

這個程序打印Hello World! 到標準輸出流:

#include <iostream>

int main()
{
    std::cout << "Hello World!" << std::endl;
}
 

在Coliru現場觀看

分析

讓我們詳細研究一下這段代碼的每一部分:

  • #include <iostream> 是一個預處理程序指令 ,包含標準C ++頭文件iostream

    iostream標準庫頭文件 ,包含標準輸入和輸出流的定義。這些定義包含在std 命名空間中,如下所述。

    標準輸入/輸出(I / O)流為程序提供從外部系統(通常是終端)輸入和輸出的方法。

  • int main() { ... } 定義了一個名為main 的新函數 。按照慣例,在執行程序時調用main 函數。在C ++程序中必須只有一個main 函數,並且它必須始終返回一些int 類型。

    這裡, int 是所謂的函數的返回類型main 函數返回的值是退出代碼。

    按照慣例,程序退出代碼0EXIT_SUCCESS 被執行程序的系統解釋為成功。任何其他返回代碼都與錯誤相關聯。

    如果不存在return 語句,則main 函數(以及程序本身)默認返回0 。在這個例子中,我們不需要顯式寫入return 0;

    除返回void 類型的函數外,所有其他函數必鬚根據其返回類型顯式返回值,否則根本不能返回。

  • std::cout << "Hello World!" << std::endl; 打印“Hello World!”到標準輸出流:

    • std 是一個名稱空間::作用域解析運算符 ,它允許在名稱空間中按名稱查找對象。

      有許多名稱空間。在這裡,我們使用:: 來表示我們想要從std 命名空間中使用cout 。有關更多信息,請參閱範圍解析操作員 - Microsoft文檔

    • std::cout 是在iostream 定義的標準輸出流對象,它打印到標準輸出( stdout )。

    • << 就是, 在這種情況下流插入操作 ,所謂的,因為它插入對象插入到對象。

      標準庫定義<< 運算符,用於將某些數據類型的數據插入到輸出流中。 stream << contentcontent 插入到流中並返回相同但更新的流。這允許鏈流插入: std::cout << "Foo" << " Bar"; 將“FooBar”打印到控制台。

    • "Hello World!"字符串文字 ,或“文字文字”。字符串文字的流插入運算符在文件iostream 定義。

    • std::endl 是一個特殊的I / O流操縱器對象,也在文件iostream 定義。將操縱器插入流中會更改流的狀態。

      流操縱器std::endl 做了兩件事:首先插入行尾字符,然後刷新流緩衝區以強製文本顯示在控制台上。這可確保插入到流中的數據實際顯示在控制台上。 (流數據通常存儲在緩衝區中,然後批量“刷新”,除非您立即強制刷新。)

      避免刷新的另一種方法是:

      std::cout << "Hello World!\n";
       

      其中\n 是換行符的字符轉義序列

    • 分號( ; )通知編譯器語句已結束。所有C ++語句和類定義都需要一個結束/終止分號。

評論

註釋是一種將任意文本放在源代碼中的方法,而不需要C ++編譯器將其解釋為具有任何功能意義。註釋用於深入了解程序的設計或方法。

C ++中有兩種類型的註釋:

單行評論

雙正斜杠序列// 將標記所有文本,直到換行作為註釋:

int main()
{
   // This is a single-line comment.
   int a;  // this also is a single-line comment
   int i;  // this is another single-line comment
}
 

C風格/塊評論

序列/* 用於聲明註釋塊的開頭,序列*/ 用於聲明註釋的結束。即使文本是有效的C ++語法,開始和結束序列之間的所有文本都被解釋為註釋。這些有時被稱為“C風格”註釋,因為這個註釋語法繼承自C ++的前身語言C:

int main()
{
   /*
    *  This is a block comment.
    */
   int a;
}
 

在任何塊註釋中,您可以編寫任何您想要的內容。當編譯器遇到符號*/ ,它終止塊註釋:

int main()
{
   /* A block comment with the symbol /*
      Note that the compiler is not affected by the second /*
      however, once the end-block-comment symbol is reached,
      the comment ends.
   */
   int a;
}
 

上面的例子是有效的C ++(和C)代碼。但是,在塊註釋中添加/* 可能會導致某些編譯器發出警告。

塊註釋也可以一行開始和結束。例如:

void SomeFunction(/* argument 1 */ int a, /* argument 2 */ int b);
 

評論的重要性

與所有編程語言一樣,註釋提供了幾個好處:

  • 明確的代碼文檔,使其更易於閱讀/維護
  • 解釋代碼的目的和功能
  • 有關代碼背後的歷史或推理的詳細信息
  • 直接在源代碼中放置版權/許可證,項目說明,特別感謝,貢獻者積分等。

但是,評論也有其缺點:

  • 必須維護它們以反映代碼中的任何更改
  • 過多的註釋往往會降低代碼的可讀性

通過編寫清晰的自我記錄代碼可以減少對註釋的需求。一個簡單的例子是對變量,函數和類型使用解釋性名稱。將邏輯相關的任務分解為離散函數與此相輔相成。

註釋標記用於禁用代碼

在開發過程中,註釋還可用於快速禁用部分代碼而不刪除它。這通常對測試或調試有用,但對於臨時編輯以外的任何其他方式都不是好的樣式。這通常被稱為“評論”。

類似地,將一段代碼的舊版本保留在註釋中用於參考目的是不受歡迎的,因為與通過版本控制系統探索代碼的歷史相比,它在提供很少價值的同時使文件變得混亂。

功能

函數是代表一系列語句的代碼單元。

函數可以接受參數或值並返回單個值(或不返回 )。要使用函數, 函數調用將用於參數值,函數調用本身的使用將替換為其返回值。

每個函數都有一個類型簽名 - 其參數的類型和返回類型的類型。

函數的靈感來自過程和數學函數的概念。

  • 注意:C ++函數本質上是過程,不遵循數學函數的確切定義或規則。

功能通常用於執行特定任務。並且可以從程序的其他部分調用。必須先聲明和定義函數,然後才能在程序中的其他位置調用它。

  • 注意:流行的函數定義可能隱藏在其他包含的文件中(通常為了方便和在許多文件中重用)。這是頭文件的常見用法。

功能聲明

函數聲明聲明存在一個函數及其名稱和類型簽名給編譯器。語法如下:

int add2(int i); // The function is of the type (int) -> (int)
 

在上面的示例中, int add2(int i) 函數向編譯器聲明以下內容:

  • 返回類型int
  • 該函數的名稱add2
  • 函數的參數個數為1:
    • 第一個參數是int 類型。
    • 第一個參數將在函數的內容中通過名稱i 引用。

參數名稱是可選的;該功能的聲明也可以如下:

int add2(int); // Omitting the function arguments' name is also permitted.
 

根據單定義規則 ,具有特定類型簽名的函數只能在C ++編譯器可見的整個C ++代碼庫中聲明或定義一次。換句話說,具有特定類型簽名的函數無法重新定義 - 它們只能定義一次。因此,以下是無效的C ++:

int add2(int i);  // The compiler will note that add2 is a function (int) -> int
int add2(int j);  // As add2 already has a definition of (int) -> int, the compiler
                  // will regard this as an error.
 

如果函數不返回任何內容,則其返回類型將寫為void 。如果不帶參數,則參數列表應為空。

void do_something(); // The function takes no parameters, and does not return anything.
                     // Note that it can still affect variables it has access to.
 

功能調用

聲明後可以調用一個函數。例如,以下程序在main 函數中調用值為2 add2

#include <iostream>

int add2(int i);    // Declaration of add2

// Note: add2 is still missing a DEFINITION.
// Even though it doesn't appear directly in code,
// add2's definition may be LINKED in from another object file.

int main()
{
    std::cout << add2(2) << "\n";  // add2(2) will be evaluated at this point,
                                   // and the result is printed.
    return 0;  
}
 

這裡, add2(2) 是函數調用的語法。

功能定義

函數定義 *類似於聲明,除了它還包含在函數體內調用函數時執行的代碼。

add2 的函數定義示例可能是:

int add2(int i)       // Data that is passed into (int i) will be referred to by the name i
{                     // while in the function's curly brackets or "scope."
                    
    int j = i + 2;    // Definition of a variable j as the value of i+2.
    return j;         // Returning or, in essence, substitution of j for a function call to
                      // add2.
}
 

功能重載

您可以創建具有相同名稱但不同參數的多個函數。

int add2(int i)           // Code contained in this definition will be evaluated
{                         // when add2() is called with one parameter.
    int j = i + 2;
    return j;
}

int add2(int i, int j)    // However, when add2() is called with two parameters, the
{                         // code from the initial declaration will be overloaded,
    int k = i + j + 2 ;   // and the code in this declaration will be evaluated
    return k;             // instead.
}
 

這兩個函數都使用相同的名稱add2 調用,但調用的實際函數直接取決於調用中參數的數量和類型。在大多數情況下,C ++編譯器可以計算要調用的函數。在某些情況下,必須明確說明類型。

默認參數

函數參數的默認值只能在函數聲明中指定。

int multiply(int a, int b = 7); // b has default value of 7.
int multiply(int a, int b)
{
    return a * b;               // If multiply() is called with one parameter, the
}                               // value will be multiplied by the default, 7.
 

在此示例中,可以使用一個或兩個參數調用multiply() 。如果只給出一個參數,則b 默認值為7.默認參數必須放在函數的後一個參數中。例如:

int multiply(int a = 10, int b = 20); // This is legal 
int multiply(int a = 10, int b);      // This is illegal since int a is in the former
 

特殊功能調用 - 操作員

在C ++中存在特殊的函數調用,它們具有與name_of_function(value1, value2, value3) 不同的語法。最常見的例子是運營商。

某些特殊字符序列將被編譯器簡化為函數調用,例如!+-*%<< 以及更多。這些特殊字符通常與非編程用法相關聯或用於美學(例如, + 字符通常被認為是C ++編程和初等數學中的加法符號)。

C ++使用特殊語法處理這些字符序列;但實際上,每次出現的運算符都會縮減為函數調用。例如,以下C ++表達式:

3+3
 

相當於以下函數調用:

operator+(3, 3)
 

所有操作員函數名稱都以operator 開頭。

雖然在C ++的前一個C中,通過提供具有不同類型簽名的附加定義,操作符函數名稱不能被賦予不同的含義,在C ++中,這是有效的。在一個唯一的函數名稱下“隱藏”其他函數定義在C ++中稱為運算符重載 ,並且是C ++中相對常見但不通用的約定。

預處理器

預處理器是編譯器的重要組成部分

它編輯源代碼,削減一些比特,改變其他部分,並添加其他東西。

在源文件中,我們可以包含預處理器指令。這些指令告訴預處理器執行特定操作。指令以新行上的#開頭。例:

#define ZERO 0
 

您將遇到的第一個預處理器指令可能是

#include <something>
 

指示。它的作用是需要的所有something ,並將其插入你的文件,其中的指令了。 hello world程序從該行開始

#include <iostream>
 

此行添加了允許您使用標準輸入和輸出的函數和對象。

C語言也使用預處理器,沒有與C ++語言一樣多的頭文件 ,但在C ++中,您可以使用所有C頭文件。


下一個重要指令可能是

#define something something_else
 

指示。這告訴預處理器,當它沿著文件傳遞時,它應該用something 替換每一個出現的something_else 。它也可以使功能類似,但可能算作高級C ++。

不需要something_else ,但是如果你定義的something 沒什麼,那麼在預處理器指令之外,所有出現的something 都會消失。

由於#if#else#ifdef 指令,這實際上很有用。這些格式如下:

#if something==true
//code
#else
//more code
#endif

#ifdef thing_that_you_want_to_know_if_is_defined
//code
#endif
 

這些指令插入true位中的代碼,並刪除false位。這可以用於包含僅包含在某些操作系統上的代碼,而無需重寫整個代碼。

標準的C ++編譯過程

可執行C ++程序代碼通常由編譯器生成。

編譯器是一種程序,它將代碼從編程語言轉換為另一種形式,這種形式(更多)可直接為計算機執行。使用編譯器來轉換代碼稱為編譯。

C ++從其“父”語言C繼承了其編譯過程的形式。下面是一個列表,顯示了C ++中編譯的四個主要步驟:

  1. C ++預處理器將任何包含的頭文件的內容複製到源代碼文件中,生成宏代碼,並將使用#define定義的符號常量替換為它們的值。
  2. 由C ++預處理器生成的擴展源代碼文件被編譯為適合該平台的彙編語言。
  3. 由編譯器生成的彙編程序代碼被組裝成適合該平台的目標代碼。
  4. 彙編程序生成的目標代碼文件與用於生成可執行文件的任何庫函數的目標代碼文件鏈接在一起。
  • 注意:某些已編譯的代碼鏈接在一起,但不能創建最終程序。通常,這種“鏈接”代碼也可以打包成其他程序可以使用的格式。這個“打包的,可用的代碼包”是C ++程序員所稱的庫。

許多C ++編譯器也可以合併或取消合併編譯過程的某些部分,以方便或進行其他分析。許多C ++程序員將使用不同的工具,但是當他們參與程序的製作時,所有這些工具通常都會遵循這個通用的過程。

下面的鏈接擴展了這個討論,並提供了一個很好的圖形來幫助。 [1]: http//faculty.cs.niu.edu/~mcmahon/CS241/Notes/compile.html

函數原型和聲明的可見性

在C ++中,必須在使用前聲明或定義代碼。例如,以下內容產生編譯時錯誤:

int main()
{
  foo(2); // error: foo is called, but has not yet been declared
}

void foo(int x) // this later definition is not known in main
{
}
 

有兩種方法可以解決這個問題:在main() 使用foo() 的定義或聲明之前。這是一個例子:

void foo(int x) {}  //Declare the foo function and body first

int main()
{
  foo(2); // OK: foo is completely defined beforehand, so it can be called here.
}
 

但是,也可以通過在使用之前僅放置“原型”聲明來“轉發聲明”該函數,然後再定義函數體:

void foo(int);  // Prototype declaration of foo, seen by main
                // Must specify return type, name, and argument list types
int main()
{
  foo(2); // OK: foo is known, called even though its body is not yet defined
}

void foo(int x) //Must match the prototype
{
    // Define body of foo here
}
 

原型必須指定返回類型( void ),函數名稱( foo )和參數列表變量類型( int ),但不需要參數名稱

將其集成到源文件組織中的一種常用方法是創建包含所有原型聲明的頭文件:

// foo.h
void foo(int); // prototype declaration
 

然後在別處提供完整的定義:

// foo.cpp --> foo.o
#include "foo.h" // foo's prototype declaration is "hidden" in here
void foo(int x) { } // foo's body definition
 

然後,一旦編譯,將相應的目標文件foo.o 鏈接到編譯目標文件中,在鏈接階段main.o 使用它!

// main.cpp --> main.o
#include "foo.h" // foo's prototype declaration is "hidden" in here
int main() { foo(2); } // foo is valid to call because its prototype declaration was beforehand.
// the prototype and body definitions of foo are linked through the object files
 

當函數原型調用存在時,會發生“未解析的外部符號”錯誤,但未定義函數 。由於編譯器在最後的鏈接階段之前不會報告錯誤,並且它不知道在代碼中跳轉到哪一行來顯示錯誤,因此解決這些問題會更加棘手。