開啟進入C++的大門

命名空間

命名空間的定義與訪問

在說明命名空間之前,我們要先了解「名稱衝突」。
C++中的名稱衝突是指程序的不同部分對變量、函數或類別使用相同的名稱,從而導致編譯器混淆。
爲了解決命名衝突的問題,因此C++引入了「命名空間」。
命名空間是一種功能,它提供了一種將相關標識符(例如變量、函數和類別)分組到單個名稱下的方法。它提供了一個空間,我們可以在其中定義或聲明標識符,例如變量、方法和類別。本質上,定義一個命名空間就相當於定義了一個作用域。

// 命名空間的定義
namespace name{ // 利用關鍵字 namespace +  空間名稱 就可以定義一個命名空間
  int num = 5;
}
/*
如果想要在外部訪問該命名空間中的內容,我們可以藉由「作用域解析運算符::」來訪問或使用 using namespace 空間名稱 (但這相當於把命名空間域的內容暴露於全局,因此不推薦)。
-
在這裏我們要在進一步的瞭解編譯時是如何去找一個變量的聲明/定義。
預設情況下:先在當前的作用域找 => 當前的作用域找不到就會去全局域找,如果全局域也找不到,則報錯。
*/

// 補充: ::num => 沒有在作用域解析運算符前指定作用域 相當於訪問全局域的num變量 

int main(){
  // 訪問 name 這個命名空間中的 num
  int num = 0;
  cout << num << endl; // 0
  cout << name::num << endl; // 5
  return 0;
}

嵌套命名空間

我們也可以在一個命名空間內部嵌套命名空間。

namespace outer{
  void fun(){
    cout << "Inside outer namespace" << endl;
  }
  namespace inner{
    void fun(){
      cout << "Inside inner namespace" << endl;
    }
  }
}
int main()
{
  outer::fun(); // Inside outer namespace
  outer::inner::fun(); // Inside inner namespace
  return 0;
}

命名空間的拓展

在C++中,擴展命名空間意味着向現有命名空間添加更多功能(如函數、變量或類別),即使該命名空間是在其他地方定義的(如在庫或其他文件中)。

using namespace std;

namespace Extending_Namespace
{
    void func1()
    {
        cout << "你可以對我進行拓展" << endl;
    }
}

namespace Extending_Namespace
{
    void func2()
    {
        cout << "我是新定義的函數" << endl;
    }
}

int main()
{
    Extending_Namespace::func1();
    Extending_Namespace::func2();
    return 0;
}

命名空間的別名

// 要定義一個命名空間的別名

namespace Namespace_Alias // 要有別名的命名空間要先存在
{
    void func1()
    {
        cout << "Namespace_Alias" << endl;
    }
}

namespace NA = Namespace_Alias; // 定義一個命名空間的別名

int main()
{
    Namespace_Alias::func1();
    NA::func1();
    return 0;
}

匿名命名空間

沒有名稱的命名空間稱爲匿名命名空間。它確保未被命名的命名空間中的內容僅限於該文件。

// 匿名空間
namespace
{
  int value = 10;
}

int main()
{
  // 訪問匿名空間的內容
  cout << value;
  /*
  注意:如果在全局中再定義 value 則會報錯 
  */
  return 0;
}

資料類型

資料類型分類

指定變量可以存儲的資料類別型。在 C++ 中,每當定義一個變量時,編譯器都會根據該變量聲明的資料類別型爲其分配一些記憶體,因爲每種資料類別型所需的記憶體量都不同。

  1. 基本資料類型(內置類型) => int、float、double、char、bool、void
  2. 派生資料類型(Derived Data Type) => 數組、指針、引用、函數
  3. 自定義類型 => 類別、結構、聯合 (union)、枚舉(enum)、typedef、using

類型轉換

類型轉換發生於

  • 對不同資料類型進行運算
  • 將參數傳遞給需要不同資料類型的函數
  • 將一種資料類型的值賦給另一種資料類型的變量

類型轉換可以分爲

  1. 隱式類型轉換:指編譯器在需要時自動將一種類型的資料轉換爲另一種類型的操作

    隱式類型轉換通常會轉換爲類型較大的類型
    類型大小從小到大排序:bool -> char -> short int -> int -> unsigned int -> long -> unsigned -> long long -> float -> double -> long double

    #include <iostream>
    using namespace std;
    
    int main() {
    
     int i = 10;
     char c = 'a';
     // 'a' 的ASCII碼爲 97
     i = i + c; // 107
    
     // x is implicitly converted to float
     float f = i + 1.0;
    
     cout << "i = " << i << endl // 107
          << "c = " << c << endl
          << "f = " << f; // 108 => int 被隱式類型轉換爲 float (107.0) 
    
     return 0;
    }
  2. 顯示類型轉換:也稱爲強制轉換

    #include <iostream>
    using namespace std;
    
    int main() {
      double x = 1.2;
    
      int sum = (int)x + 1; // 變量 x 被強制轉換成int類型
    
      cout << sum;
    
      return 0;
    }

運算符

算數運算符

operator.png

  • %只能用於整數
  • a++ => 先用a再++ ; ++a => 先++後再用a

關係運算符

關係運算符.png

邏輯運算符

邏輯運算符.png

賦值運算符

賦值運算符.png

位運算符

只有char類型和整型家族的類型的可以使用位運算符
位運算.png

#include <iostream>
using namespace std;

int main() {
  int a = 6, b = 4;

  // Binary AND operator
  cout << "a & b is " << (a & b) << endl; // 4
  /*
  a 的二進制:00000000 00000000 00000000 00000110
  b 的二進制:00000000 00000000 00000000 00000100

  a & b => 4 => 00000000 00000000 00000000 00000100 ( A AND B )
  */

  // Binary OR operator
  cout << "a | b is " << (a | b) << endl; // 6
  /*
  a 的二進制:00000000 00000000 00000000 00000110
  b 的二進制:00000000 00000000 00000000 00000100

  a | b => 00000000 00000000 00000000 00000110 => 6  ( A OR B )
  */

  // Binary XOR operator
  cout << "a ^ b is " << (a ^ b) << endl;

  /*
  a 的二進制:00000000 00000000 00000000 00000110
  b 的二進制:00000000 00000000 00000000 00000100

  a ^ b => 00000000 00000000 00000000 00000010 => 2  ( A XOR B )
  */

  // Left Shift operator
  cout << "a << 1 is " << (a << 1) << endl; // Left Shift => *= 2

  /*
  a 的二進制:00000000 00000000 00000000 00000110

  a << 1 => 00000000 00000000 00000000 00001100 => 12  ( a << 1 )
  */

  // Right Shift operator
  cout << "a >> 1 is " << (a >> 1) << endl;
  /*
  a 的二進制:00000000 00000000 00000000 00000110

  a >> 1 => 00000000 00000000 00000000 00000011 => 3 ( a >> 1)
  */
  
  // One’s Complement operator
  cout << "~(a) is " << ~(a);
  /*
    這裏說明一下,~(a) 輸出是 ~(a) 的補碼 (-7) => 就是~(6) - 1
  */
  return 0;
}

三元運算符

Expression1 ? Expression2 : Expression3 
// 若 Expression1 爲真 則執行 Expression 反之執行 Expression3

運算符優先級

priority.png

  • 運算符結合性表示如果表達式中有多個具有相同優先級的運算符,則計算從右到左或從左到右進行。
  • C++中作用域解析運算符有着最高優先級。

    // Ex
    50/25*2 =1
    被認爲是:(50 / 25)* 2 // *和除爲同一級優先級 結合性爲left (左到右結合)

函數

函數是程序的構建塊,它包含一組在調用時執行的語句。它可以接收一些輸入資料,執行給定的任務,並返回一些結果。函數可以在程序的任何位置調用,並且可以調用任意次數,從而提高了模塊性和複用性。

函數的定義與調用

// 函數返回值類型 函數名稱(){ 函數體 }
------------------------------------------------------------------------------------------------------
  /*
函數體:當函數被調用時,執行大括號{}中的語句
*/
  void printHello(){ 
  cout << "Hello Geeks";
}
int main(){
  printHello(); // 函數調用 函數名稱+()
  // result: Hello Geeks
  return 0;
}
/*
以上函數的返回值類型:void ( 如果返回值類型爲void 函數體中可以不寫return )
函數名稱:printHello
*/
------------------------------------------------------------------------------------------------------
  /*
main 函數就是一個函數 其返回值爲int類型 這也是爲什麼main函數最後都會寫 return 0;
*/
  ------------------------------------------------------------------------------------------------------
  /*
函數也可以接收一些輸入資料進行處理。這些值被稱爲函數參數,在函數調用時傳遞給函數。
爲了接收這些值,我們在函數定義的括號內定義佔位符變量,稱爲參數。這些佔位符變量應該指定其類型和名稱。
*/
  void printNum(int n){
  cout << n << endl;
}

int main() {
  int num1 = 10;
  int num2 = 99;
  printNum(num1); // 調用函數並傳入參數 => 10
  printNum(num2); // 99
  
  return 0;
}

缺省參數 ( 預設參數 )

C++ 提供了可以爲函數中的每一個參數提供缺省參數,當函數調用中沒有爲該參數傳遞值時,將使用該預設值。

void Print(int Num = 5){
  cout << Num << endl;
}
int main(){
    Print(10); // Print => 10
  Print(5); // Print => 5
    return 0;
}

函數重載

函數重載的定義是在同一作用域中出現同名函數,但是要求這些同名函數的形參不同,可以是參數個數不同或者類型不同。

void num(int a)
{
    cout << a << endl;
}
void num(int a, double b)
{
    cout << a << b << endl;
}
int main()
{
    int a = 20;
    double b = 30;
    num(a);
    num(a, b);
    return 0;
}

函數重載需注意

  1. 返回值不同不能作爲構成重載的條件
  2. 缺省參數不作爲重載的判定 => 避免重載函數中都給缺省值
  3. 重載是在同一作用域下 ( 這很重要,因爲後面還有不同作用域函數同名的情況 )
  4. 函數重載條件 ( 滿足其中一項即可 ) ==(1) 參數個數不同 (2) 參數類型不同 (3) 參數順序不同

內聯函數

內聯函數是在函數的返回值類型前面加上關鍵字 inline
內聯函數的特點在於內聯函數提供了一種通過減少函數調用相關開銷來優化程序性能的方法。
當一個函數被指定爲內聯函數時,內聯函數的整個程式碼將在編譯期間在調用的地方展開,而不是使用常規的函數調用機制。
inline.png

需要注意的是inline對於編譯器而言只是一個建議,實際上會不會在函數調用點展開取決於編譯器。
inline適用於頻繁調用的短小函數,對於遞歸函數,程式碼相對多一些的函數,加上inline也會被編譯器忽略。

內聯函數 vs. 宏定義函數

宏的優點

  1. 增加帶碼的複用性
  2. 提高性能

宏的缺點

  1. 不方便調試 ( 因爲預處理階段就進行了宏替換 )
  2. 對於有些操作,宏定義會比較複雜,導致程式碼可讀性差、可維護性差
  3. 沒有類型安全檢查

相較於宏定義函數,inline 函數更 安全、可調試、可優化,並且可以與模板結合使用。


指標

指標用來儲存一個地址。指標可以用於任何資料類型,包括基本類型(例如 int、char)、數組,甚至自定義類型(例如類別和結構體)。

指標的定義

int main(){
  int var = 10;
  int* ptr = &var; // ptr是指標變量 類型是int類型的指標,其儲存var的地址 ( & => 如果跟着指標,代表取地址 )

  /*
  如果要訪問 var 的地址中的內容 => *指標變量 => 解引用
  */
  cout << &var << endl;
  cout << ptr << endl;
  cout << var << endl;
  cout << *ptr << endl;
  /*
  輸出
  0x16dd16d78
    0x16dd16d78
    10
    10
  */
  return 0;
}
/*
指標的大小
64 位系統爲 8 個字節 
32 位系統爲 4 個字節
*/

pointers-in-c.png

特殊指標

野指標:指標創建時,它只包含一個隨機地址,該地址可能有效,也可能無效。這種類型的指標被稱爲野指標。

#include <iostream>
using namespace std;
int main() {
 // 野指標
 int *ptr; //解引用此指標可能會導致錯誤。因此,建議都對指標初始化
 return 0;
}

空指標:其不指向任何有效記憶體位置,而是指向 NULL 的指標。它通常用於初始化指標。
void指標:void 指標是使用void關鍵字聲明的指標。它與常規指標不同,它用於指向未指定資料類型的資料。它可以指向任何類型的資料,因此也稱爲「通用指標」。

#include <iostream>
using namespace std;

int main()
{
int a = 10;
// void指標
void* myptr = &a;
cout << "The value of a is: " << a << endl;
cout << "The Address of a is  " << myptr << endl;
}

函數指標:函數指標通常引用在回調函數(作爲參數傳遞給其他函數的函數)。

typedef int (*Operation)(int&, int&);

// 函數指標的定義 返回類型 (*指標變量名稱)()

int add(int &a, int &b)
{
return a + b;
}
int mul(int &a, int &b)
{
return a * b;
}

int calculator(int &a, int &b, Operation op)
{
return op(a, b);
}

int main()
{
int a = 5, b = 10;
cout << calculator(a, b, add);
return 0;
}

引用

引用 vs. 指標

引用指標比較.png