C++筆記

Mackie-Yang發表於2020-12-28

C++筆記

基本語法

物件:

物件具有狀態和行為。例如:一隻狗的狀態-顏色、名稱、品種,行為-搖動、叫喚、吃。物件是類的例項。

類:

類可以定義為描述物件行為/狀態的模板/藍圖。

方法:

從基本上說,一個方法表示一種行為。一個類可以包含多個方法。可以在方法中寫入邏輯、運算元據以及執行所有的動作。

即時變數 :

每個物件都有其獨特的即時變數。物件的狀態是由這些即時變數的值建立的。

例子:輸出HelloWorld!

#include <iostream>
using namespace std;

int main()
{
    cout << "Hello World!" << endl;
    system("pause");
    return 0;
}

修飾符型別

signed

unsigned

long

short

例項:

#include <iostream>
using namespace std;

int mian()
{
    short int i;
    short unsigned int j;
    j = 50000;
    i = j;
    cout << i << " " << j << endl;
    system("pause");
    return 0;
}

儲存類

auto

宣告變數時根據初始化表示式自動推斷該變數的型別、宣告函式時函式返回值的佔位符。

例項化:

auto f = 3.14; //double
auto s("Hello"); //const char*
auto z = new auto(9); //int*
auto x1 = 5, x2 = 5.0, x3 = 'r'; //錯誤,初始化必須為同一型別

register

register儲存類用於定義儲存在暫存器中而不是 RAM 中的區域性變數。這意味著變數的最大尺寸等於暫存器的大小(通常是一個詞),且不能對它應用一元的 ‘&’ 運算子(因為它沒有記憶體位置)。

例項化:

{
    register int miles;
}

static

static儲存類指示編譯器在程式的生命週期內保持區域性變數的存在,而不需要在每次它進入和離開作用域時進行建立和銷燬。因此,使用 static 修飾區域性變數可以在函式呼叫之間保持區域性變數的值。

static 修飾符也可以應用於全域性變數。當 static 修飾全域性變數時,會使變數的作用域限制在宣告它的檔案內。

在 C++ 中,當 static 用在類資料成員上時,會導致僅有一個該成員的副本被類的所有物件共享。

例項化:

#include <iostream>
using namespace std;

void func(void);
static int count = 10;

int main()
{
    while(count--) 
    {
        func();
    }
    system("pause");
    return 0;
}

void func(void)
{
    static int i = 5;
    i++;
    std::cout << "變數i為" << i;
    std::cout << ",變數count為" << count << std::endl;
}

extern

extern儲存類用於提供一個全域性變數的引用,全域性變數對所有的程式檔案都是可見的。當您使用 ‘extern’ 時,對於無法初始化的變數,會把變數名指向一個之前定義過的儲存位置。

當您有多個檔案且定義了一個可以在其他檔案中使用的全域性變數或函式時,可以在其他檔案中使用 extern 來得到已定義的變數或函式的引用。可以這麼理解,extern 是用來在另一個檔案中宣告一個全域性變數或函式。

extern 修飾符通常用於當有兩個或多個檔案共享相同的全域性變數或函式的時候,如下所示:

第一個檔案:main.cpp

#include <iostream>

int count;
extern void write_extern();

int main()
{
    count = 5;
    write_extern();
}

第二個檔案:support.cpp

#include <iostream>

extern int count;
void write_extern(void)
{
    std::cout << "Count is" << count << std::endl;
}

函式

函式是一組一起執行一個任務的語句。每個c++程式至少有一個函式,即主函式main(),所有簡單的程式都可以定義其他額外的函式。

定義函式

一般形式為:

return_ytpe function_name(oarameter list)
{
    body of the function
}

返回型別

一個函式可以返回一個值。return_type是函式返回值的資料型別。有些函式執行所需的操作而不返回值,在這種情況下,return_type的關鍵字是void。

函式名稱

這是函式的實際名稱。函式名和引數列表一起構成了函式簽名。

引數

引數就像是站位符。當函式被呼叫時,您向引數傳遞一個值,這個值被稱為實際引數。引數列表包括函式引數的型別、順序、數量。引數是可選的,也就是說,函式可以不包含引數。

例項:比較大小的函式

int max(int num1, int num2)
{
    if (num1 > num2)
    {
        return num1;
    } else 
    {
        return num2;
    }
}

呼叫函式

建立 C++ 函式時,會定義函式做什麼,然後通過呼叫函式來完成已定義的任務。

當程式呼叫函式時,程式控制權會轉移給被呼叫的函式。被呼叫的函式執行已定義的任務,當函式的返回語句被執行時,或到達函式的結束括號時,會把程式控制權交還給主程式。

呼叫函式時,傳遞所需引數,如果函式返回一個值,則可以儲存返回值。例如:

#include <iostream>
using namespace std;

int max(int num1, int num2);

int main()
{
    int a = 100;
    int b = 200;
    int ret;
    
    ret = max(a, b);
    
    cout << "Max value is : " << ret << endl;
    
    system("pause");
    return 0;
}

int max(int num1, int num2)
{
    if (num1 > num2)
    {
        return num1;
    } else 
    {
        return num2;
    }
}

引數的預設值

當您定義一個函式,您可以為引數列表中後邊的每一個引數指定預設值。當呼叫函式時,如果實際引數的值留空,則使用這個預設值。

這是通過在函式定義中使用賦值運算子來為引數賦值的。呼叫函式時,如果未傳遞引數的值,則會使用預設值,如果指定了值,則會忽略預設值,使用傳遞的值。請看下面的例項:

#include <iostream>
using namespace std;

int sum(int a, int b = 20)
{
    return a + b;
}

int main()
{
    int a = 100;
    int b = 200;
    int result;
    result = sum(a, b);
    cout << "Total value is " << result << endl;
    
    result = sum(a);
    cout << "Total value is " << result << endl;
    
    system("pause");
    return 0;
}

陣列

初始化陣列

double blance[] = {12, 2.1, 32, 1000}

訪問陣列元素

陣列元素可以通過陣列名稱加索引進行訪問。元素的索引是放在方括號內,跟在陣列名稱的後邊。例如:

double salary = balance[9];

上面的語句將把陣列中第 10 個元素的值賦給 salary 變數。下面的例項使用了上述的三個概念,即,宣告陣列、陣列賦值、訪問陣列:

例項:

#inclued <iostream>
using namespace std;

#include <iomanip>
using std::setw;

int main()
{
    int n[10];
    for (int i = 0; i < 10; i++)
    {
        n[i] = i + 100;
    }
    cout << "Element" << setw(13) << "value" << endl;
    
    for (int j = 0; j < 10; j++)
    {
        cout << setw(7) << j << setw(13) << n[j] << endl;
    }
    system("pause");
    return 0;
}

指標

學習 C++ 的指標既簡單又有趣。通過指標,可以簡化一些 C++ 程式設計任務的執行,還有一些任務,如動態記憶體分配,沒有指標是無法執行的。所以,想要成為一名優秀的 C++ 程式設計師,學習指標是很有必要的。

正如您所知道的,每一個變數都有一個記憶體位置,每一個記憶體位置都定義了可使用連字號(&)運算子訪問的地址,它表示了在記憶體中的一個地址。請看下面的例項,它將輸出定義的變數地址:

#include <iostream>
using namespace std;

int main()
{
    int var1;
    char var2[10];
    
    cout << "var1變數的地址:";
    cout << &var1 << endl;
    cout << "var2變數的地址:";
    cout << &var2 << endl;
    
    system("pause");
    return 0;
}

怎麼使用指標

使用指標時會頻繁進行以下幾個操作:定義一個指標變數、把變數地址賦值給指標、訪問指標變數中可用地址的值。這些是通過使用一元運算子 ***** 來返回位於運算元所指定地址的變數的值。下面的例項涉及到了這些操作:

#include <iostream>
using namespace std;

int mian()
{
    int var = 20;
    int *ip;
    
    ip = &var;
    
    cout << "Value of var variablr: " << var << endl;
    cout << "Address stored in ip variable: " << ip << endl;
    cout << "Value is *ip variable: " << *ip << endl;
    
    system("pause");
    return 0;
}

C++ 指標詳解

在 C++ 中,有很多指標相關的概念,這些概念都很簡單,但是都很重要。下面列出了 C++ 程式設計師必須清楚的一些與指標相關的重要概念:

概念描述
C++ Null 指標C++ 支援空指標。NULL 指標是一個定義在標準庫中的值為零的常量。
C++ 指標的算術運算可以對指標進行四種算術運算:++、–、+、-
C++ 指標 vs 陣列指標和陣列之間有著密切的關係。
C++ 指標陣列可以定義用來儲存指標的陣列。
C++ 指向指標的指標C++ 允許指向指標的指標。
C++ 傳遞指標給函式通過引用或地址傳遞引數,使傳遞的引數在呼叫函式中被改變。
C++ 從函式返回指標C++ 允許函式返回指標到區域性變數、靜態變數和動態記憶體分配。

引用

引用變數是一個別名,也就是說,它是某個已存在變數的另一個名字。一旦把引用初始化為某個變數,就可以使用該引用名稱或變數名稱來指向變數。

引用vs指標

引用很容易與指標混淆,它們之間有三個主要的不同:

  • 不存在空引用。引用必須連線到一塊合法的記憶體。
  • 一旦引用被初始化為一個物件,就不能被指向到另一個物件。指標可以在任何時候指向到另一個物件。
  • 引用必須在建立時被初始化。指標可以在任何時間被初始化。

建立引用

試想變數名稱是變數附屬在記憶體位置中的標籤,您可以把引用當成是變數附屬在記憶體位置中的第二個標籤。因此,您可以通過原始變數名稱或引用來訪問變數的內容。例如:

int i = 17;

我們可以為 i 宣告引用變數,如下所示:

int&  r = i;
double& s = d;

在這些宣告中,& 讀作引用。因此,第一個宣告可以讀作 “r 是一個初始化為 i 的整型引用”,第二個宣告可以讀作 “s 是一個初始化為 d 的 double 型引用”。下面的例項使用了 int 和 double 引用:

#include <iostream>
using namespace std;

int main()
{
    int i;
    double d;
    
    int& r = i;
    double& s = d;
    
    i = 5;
    cout << "Value of i : " << i << endl;
    cout << "Value of i reference : " << r << endl;
    
    d = 11.2;
    cout << "Value of d : " << d << endl;
    cout << "Value of d reference : " << s << endl;
    
    system("pause");
    return 0;
}

引用通常用於函式引數列表和函式返回值。下面列出了 C++ 程式設計師必須清楚的兩個與 C++ 引用相關的重要概念:

概念描述
把引用作為引數C++ 支援把引用作為引數傳給函式,這比傳一般的引數更安全。
把引用作為返回值可以從 C++ 函式中返回引用,就像返回其他資料型別一樣。

時間&日期

C++ 標準庫沒有提供所謂的日期型別。C++ 繼承了 C 語言用於日期和時間操作的結構和函式。為了使用日期和時間相關的函式和結構,需要在 C++ 程式中引用 標頭檔案。

有四個與時間相關的型別:clock_t、time_t、size_ttm。型別 clock_t、size_t 和 time_t 能夠把系統時間和日期表示為某種整數。

結構型別 tm 把日期和時間以 C 結構的形式儲存,tm 結構的定義如下:

struct tm {
  int tm_sec;   // 秒,正常範圍從 0 到 59,但允許至 61
  int tm_min;   // 分,範圍從 0 到 59
  int tm_hour;  // 小時,範圍從 0 到 23
  int tm_mday;  // 一月中的第幾天,範圍從 1 到 31
  int tm_mon;   // 月,範圍從 0 到 11
  int tm_year;  // 自 1900 年起的年數
  int tm_wday;  // 一週中的第幾天,範圍從 0 到 6,從星期日算起
  int tm_yday;  // 一年中的第幾天,範圍從 0 到 365,從 1 月 1 日算起
  int tm_isdst; // 夏令時
};

當前時間和日期

下面的例項獲取當前系統的日期和時間,包括本地時間和協調世界時(UTC)。

#include <iostream>
#include <ctime>
using namespace std;

int main()
{
    time_t new = time(0);
    char* dt = ctime(&now);
    cout << "本地時間和日期:" << dt << endl;
    
    tm *gmtm = gmtime(&now);
    dt = asctime(gmtm);
    cout << "UTC日期和時間是:" << dt << endl;
}

資料結構

C/C++ 陣列允許定義可儲存相同型別資料項的變數,但是結構是 C++ 中另一種使用者自定義的可用的資料型別,它允許您儲存不同型別的資料項。

結構用於表示一條記錄,假設您想要跟蹤圖書館中書本的動態,您可能需要跟蹤每本書的下列屬性:

  • Title :標題
  • Author :作者
  • Subject :類目
  • Book ID :書的 ID

定義結構

為了定義結構,您必須使用 struct 語句。struct 語句定義了一個包含多個成員的新的資料型別,struct 語句的格式如下:

struct type_name {
member_type1 member_name1;
member_type2 member_name2;
member_type3 member_name3;
.
.
} object_names;

type_name 是結構體型別的名稱,member_type1 member_name1 是標準的變數定義,比如 int i; 或者 float f; 或者其他有效的變數定義。在結構定義的末尾,最後一個分號之前,您可以指定一個或多個結構變數,這是可選的。下面是宣告一個結構體型別 Books,變數為 book

struct Books
{
   char  title[50];
   char  author[50];
   char  subject[100];
   int   book_id;
} book;

訪問結構成員

為了訪問結構的成員,我們使用成員訪問運算子(.)。成員訪問運算子是結構變數名稱和我們要訪問的結構成員之間的一個句號。

下面的例項演示了結構的用法:

#include <iostream>
#include <cstring>

using namespace std;

struct Books
{
    char title[50];
    char author[50];
    char subject[100];
    int book_id;    
};

int main()
{
    Books Book1;
    Books Book2;
    strcpy(Book1.title, "c++ 教程");
    strcpy(Book1.author, "Runoob");
    strcpy(Book1.subject, "程式語言");
    Book1.book_id = 12345;
    
    strcpy(Book2.title, "CSS 教程");
    strcpy(Book2.author, "Runoob");
    strcpy(Book2.subject, "前端技術");
    Book2.book_id = 12346;
    
    cout << "第一本書標題:" << Book1.title << endl;
    cout << "第一本書作者:" << Book1.author << endl;
    cout << "第一本書類目:"<< Book1.subject << endl;
    cout << "第一本書ID:"<< Book1.book_id << endl;
    
    cout << "第二本書標題:" << Book2.title << endl;
    cout << "第二本書作者:" << Book2.author << endl;
    cout << "第二本書類目:"<< Book2.subject << endl;
    cout << "第二本書ID:"<< Book2.book_id << endl;
    
    system("pause");
    return 0;
}

結構作為函式引數

可以把結構作為函式引數,傳參方式與其他型別的變數或指標類似。可以使用上面例項中的方式來訪問結構變數:

#include <iostream>
#include <cstring>

using namespace std;
void printBook(struct Books book);

struct Books
{
    char title[50];
    char author[50];
    char subject[100];
    int book_id;    
};

int main()
{
    Books Book1;
    Books Book2;
    strcpy(Book1.title, "c++ 教程");
    strcpy(Book1.author, "Runoob");
    strcpy(Book1.subject, "程式語言");
    Book1.book_id = 12345;
    
    strcpy(Book2.title, "CSS 教程");
    strcpy(Book2.author, "Runoob");
    strcpy(Book2.subject, "前端技術");
    Book2.book_id = 12346;
    
    printBook(Book1);
    printBook(Book2);
        
    system("pause");
    return 0;
}

void printBook(struct Books book)
{
    cout << "書標題:" << book.title << endl;
    cout << "書作者:" << book.author << endl;
    cout << "書類目:"<< book.subject << endl;
    cout << "書ID:"<< book.book_id << endl;
   
}

指向結構的指標

讓我們使用結構指標來重寫上面的例項,有助於理解結構指標的概念:

#include <iostream>
#include <cstring>

using namespace std;
void printBook(struct Books *book);

struct Books
{
    char title[50];
    char author[50];
    char subject[100];
    int book_id;
};

int main()
{
    Books Book1;
    Books Book2;
    strcpy(Book1.title, "c++ 教程");
    strcpy(Book1.author, "Runoob");
    strcpy(Book1.subject, "程式語言");
    Book1.book_id = 12345;
    
    strcpy(Book2.title, "CSS 教程");
    strcpy(Book2.author, "Runoob");
    strcpy(Book2.subject, "前端技術");
    Book2.book_id = 12346;
    
    printBook(&Book1);
    printBook(&Book2);
    
    system("pause");
    return 0;
}

void printBook(Books *book)
{
    cout << "書標題:"<< book->title << endl;
    cout << "書作者:"<< book->author << endl;
    cout << "書類目:" << book->subject << endl;
    cout << "書ID:" << book->book_id << endl;
}

類&物件

C++ 在 C 語言的基礎上增加了物件導向程式設計,C++ 支援物件導向程式設計。類是 C++ 的核心特性,通常被稱為使用者定義的型別。

類用於指定物件的形式,它包含了資料表示法和用於處理資料的方法。類中的資料和方法稱為類的成員。函式在一個類中被稱為類的成員。

類定義

定義一個類,本質上是定義一個資料型別的藍圖。這實際上並沒有定義任何資料,但它定義了類的名稱意味著什麼,也就是說,它定義了類的物件包括了什麼,以及可以在這個物件上執行哪些操作。

img

類定義是以關鍵字 class 開頭,後跟類的名稱。類的主體是包含在一對花括號中。類定義後必須跟著一個分號或一個宣告列表。例如,我們使用關鍵字 class 定義 Box 資料型別,如下所示:

class Box
{
   public:
      double length;   // 盒子的長度
      double breadth;  // 盒子的寬度
      double height;   // 盒子的高度
};

關鍵字 public 確定了類成員的訪問屬性。在類物件作用域內,公共成員在類的外部是可訪問的。

定義物件

類提供了物件的藍圖,所以基本上,物件是根據類來建立的。宣告類的物件,就像宣告基本型別的變數一樣。下面的語句宣告瞭類 Box 的兩個物件:

Box Box1;          // 宣告 Box1,型別為 Box
Box Box2;          // 宣告 Box2,型別為 Box

物件 Box1 和 Box2 都有它們各自的資料成員。

訪問資料成員

類的物件的公共資料成員可以使用直接成員訪問運算子 . 來訪問。

img

為了更好地理解這些概念,讓我們嘗試一下下面的例項:

#include <iostream>
using namespace std;

class Box
{
public:
    double length;
    double breadth;
    double height;
    
    double get(void);
    void set(double length, double breadth, double height);
};

double Box::get(void)
{
    return length * breadth * height;
}

void Box::set(double length, double breadth, double height)
{
    length = length;
    breadth = breadth;
    height = height;
}

int main()
{
    Box Box1;
    Box Box2;
    Box Box3;
    double valume = 0.0;
    
    Box1.length = 5.0;
    Box1.breadth = 6.0;
    Box1.height = 7.0;
    
    Box2.length = 10.0;
    Box2.breadth = 12.0;
    Box2.height = 13.0;
    
    valume = Box1.length * Box1.breadth * Box1.height;
    cout << "Box1 的體積:" << valume << endl;
    
    velume = Box2.length * Box2.breadth * Box2.height;
    cout << "Box2 的體積:" << valume << endl;
    
    Box3.set(16.0, 8.0, 12.0);
    valume = Box3.get();
    cout << "Box3 的體積:" << valume << endl;
    
    system("pause");
    return 0;
}

需要注意的是,私有的成員和受保護的成員不能使用直接成員訪問運算子 (.) 來直接訪問。我們將在後續的教程中學習如何訪問私有成員和受保護的成員。

類 & 物件詳解

到目前為止,我們已經對 C++ 的類和物件有了基本的瞭解。下面的列表中還列出了其他一些 C++ 類和物件相關的概念,可以點選相應的連結進行學習。

概念描述
類成員函式類的成員函式是指那些把定義和原型寫在類定義內部的函式,就像類定義中的其他變數一樣。
類訪問修飾符類成員可以被定義為 public、private 或 protected。預設情況下是定義為 private。
建構函式 & 解構函式類的建構函式是一種特殊的函式,在建立一個新的物件時呼叫。類的解構函式也是一種特殊的函式,在刪除所建立的物件時呼叫。
C++ 拷貝建構函式拷貝建構函式,是一種特殊的建構函式,它在建立物件時,是使用同一類中之前建立的物件來初始化新建立的物件。
C++ 友元函式友元函式可以訪問類的 private 和 protected 成員。
C++ 行內函數通過行內函數,編譯器試圖在呼叫函式的地方擴充套件函式體中的程式碼。
C++ 中的 this 指標每個物件都有一個特殊的指標 this,它指向物件本身。
C++ 中指向類的指標指向類的指標方式如同指向結構的指標。實際上,類可以看成是一個帶有函式的結構。
C++ 類的靜態成員類的資料成員和函式成員都可以被宣告為靜態的。

繼承

物件導向程式設計最重要的一個概念是繼承。繼承允許我們依據另一個類來定義一個類,這使得我們建立和維護一個應用程式變得更容易。這樣做,也達到了重用程式碼功能和提高執行效率的效果。

當建立一個類時,你不需要誠信新的成員資料和成員函式,只需要指定新建的類繼承一個已有類的成員即可。這個已有的類稱為基類,新建的類稱為派生類。

繼承代表了is a 關係。例如,哺乳動物是動物,狗是哺乳動物,因此,狗是動物,等等。

img

程式碼如下:

//基類
class Animal
{
//set() 方法
//get() 方法
};
//派生類
class Dog : public Animal
{
//bark() 函式
};

基類&派生類

一個類可以派生自多個類,這意味著,它可以從多個基類繼承資料和函式。定義一個派生類,我們使用一個類派生列表來指定基類。類派生列表以一個或多個基類命名,形式如下:

class derived-class: access-specifier base-class

其中,訪問修飾符 access-specifier 是 public、protectedprivate 其中的一個,base-class 是之前定義過的某個類的名稱。如果未使用訪問修飾符 access-specifier,則預設為 private。

假設有一個基類 ShapeRectangle 是它的派生類,如下所示:

#include <iostream>
using namespace std;

class Shape
{
public:
    void setWidth(int w)
    {
        width = width;
    }
    void setHeight(int h)
    {
        height = height;
    }
protected:
    int width;
    int height;
};

class Rectangle: public Shape
{
public:
    int getArea()
    {
        return (width * height);
    }
};

int main(void)
{
    Rectangle Rectangle;
    
    Rectangle.setWidth(5);
    Rectangle.setHeight(7);
    
    cout << "Total area: " << Rectangle.getArea() << endl;
    
    system("pause");
    return 0;
}

控制訪問與繼承

派生類可以訪問基類所有的非私有成員。因此基類成員如果不想被派生類的成員函式訪問,則應在基類中宣告為private。

我門可以根據訪問許可權總結出不同的訪問型別,如下所示:

訪問publicprotectedprivate
同一個類yesyesyes
派生類yesyesno
外部的類yesnoon

一個派生類繼承了所有的基類方法,,但下列方法除外:

  • 基類的建構函式、解構函式和拷貝建構函式。
  • 基類的過載運算子。
  • 基類的友元函式。

繼承型別

當一個類派生自基類,該基類可以被繼承為publicprotectedprivate幾種型別。

我們幾乎不適用protected或private,通常使用public繼承。當使用不同型別的繼承時,遵循以下幾個規則:

  • 公有繼承:當一個類派生自公有基類時,基類的公有成員也是派生類的公有成員,基類的保護成員也是派生類的保護成員,基類的私有成員不能直接被派生類訪問,但是可以通過呼叫基類的公有和保護成員來訪問。
  • 保護繼承:當一個類派生自保護基類時,基類中的公有和保護成員將成為派生類的保護成員。
  • 私有繼承:當一個派生類生自私有基類時,基類的公有和保護成員將成為派生類的私有成員。

多繼承

多繼承即一個子類可以有多個父類,它繼承了多個父類的特性。

例項:

#include <iostream>
using namespace std;

class Shape
{
public:
    setWidth(int w)
    {
        width = w;
    }
    
    setHeight(int h)
    {
        heigth = h;
    }

protected:
    int width;
    int height;
};

class PaintCost
{
public:
    int getCost(int area)
    {
        return area * 70;
    }
};

class Rectangle: public Shape, public PaintCost
{
public:
    int getArea()
    {
        return height * width;
    }
};

int main(void)
{
    Rectangle Rectamgle;
    int area;
    
    Rectangle.setWidth(7);
    Rectangle.setHeight(5);
    
    area = Rectangle.getArea();
    
    cout << "Total area: " << Rectangle.getArea() << endl;
    cout << "Total paint cost: $" << Rectangle.getCost(area) << endl;
    
    system("pause");
    return 0;
}

過載運算子和過載函式

C++ 允許在同一作用域中的某個函式運算子指定多個定義,分別稱為函式過載運算子過載

過載宣告是指一個與之前已經在該作用域內宣告過的函式或方法具有相同名稱的宣告,但是它們的引數列表和定義(實現)不相同。

當您呼叫一個過載函式過載運算子時,編譯器通過把您所使用的引數型別與定義中的引數型別進行比較,決定選用最合適的定義。選擇最合適的過載函式或過載運算子的過程,稱為過載決策

函式過載

在同一個作用域內,可以宣告幾個功能類似的的通明函式,但是這些同名函式的形式引數(指引數的個數、型別或順序)必須不同。

例項:

#include <iostream>
using namespace std;

class printData
{
public:
    void print(int i)
    {
        cout << "整數為:" << i << endl;
    }
    
    void print(double f)
    {
        cout << "浮點數為:" << f << endl;
    }
    
    void print(char c[])
    {
        cout << "字串為:" << c << endl;
    }
};

int main(void)
{
    printData pd;
    
    pd.print(5);
    pd.print(500.21);
    pd.print("Hello c++");
    
    system("pause");
    return 0;
}

過載運算和過載函式

C++ 允許在同一作用域中的某個函式運算子指定多個定義,分別稱為函式過載運算子過載

過載宣告是指一個與之前已經在該作用域內宣告過的函式或方法具有相同名稱的宣告,但是它們的引數列表和定義(實現)不相同。

當您呼叫一個過載函式過載運算子時,編譯器通過把您所使用的引數型別與定義中的引數型別進行比較,決定選用最合適的定義。選擇最合適的過載函式或過載運算子的過程,稱為過載決策

函式過載

在同一個作用域內,可以宣告幾個功能類似的同名函式,但是這些同名函式的形式引數(指引數的個數、型別或者順序)必須不同。您不能僅通過返回型別的不同來過載函式。

例項:

#include <iostream>
using namespace std;

class printData
{
public:
    void print(int i)
    {
        cout << "整數為:" << i << endl;
    }
    
    void print(double f)
    {
        cout << "浮點數為:" << f << endl;
    }
    
    void print(char c[])
    {
        cout << "字串為:" << c << endl;
    }
};

int main(void)
{
    printData pd;
    
    pd.print(19);
    pd.print(345.21);
    char c[] = "Hello c++";
    pd.print(c);
    
    system("pause");
    return 0;
}

運算子過載

我們可以重定義或過載大部分 C++ 內建的運算子。這樣,您就能使用自定義型別的運算子。

過載的運算子是帶有特殊名稱的函式,函式名是由關鍵字 operator 和其後要過載的運算子符號構成的。與其他函式一樣,過載運算子有一個返回型別和一個引數列表。

Box operator+(const Box&);

宣告加法運算子用於把兩個 Box 物件相加,返回最終的 Box 物件。大多數的過載運算子可被定義為普通的非成員函式或者被定義為類成員函式。如果我們定義上面的函式為類的非成員函式,那麼我們需要為每次操作傳遞兩個引數,如下所示:

Box operator+(const Box&, const Box&);

例項:

#include <iostream>
using namespace std;

class Box
{
public:
    double getVolume(void)
    {
        return length * breadth * height;
    }
    
    void setLength(double len)
    {
        length = len;
    }
    
    void setBreadth(double bre)
    {
        breadth = bre;
    }
    
    void setHeight(double hei)
    {
        height = hei;
    }
};

Box poerator+(const Box& b)
{
    Box box;
    box.length = this->b.length;
    box.breadth = this->breadth + b.breadth;
    box.height = this->height + b.height;
    return box;
}
private:
    double length;      
    double breadth;     
    double height;      
};

int main( )
{
   Box Box1;                
   Box Box2;                
   Box Box3;                
   double volume = 0.0;     
 
   Box1.setLength(6.0); 
   Box1.setBreadth(7.0); 
   Box1.setHeight(5.0);

   Box2.setLength(12.0); 
   Box2.setBreadth(13.0); 
   Box2.setHeight(10.0);

   volume = Box1.getVolume();
   cout << "Volume of Box1 : " << volume <<endl;
 
   volume = Box2.getVolume();
   cout << "Volume of Box2 : " << volume <<endl;

   Box3 = Box1 + Box2;

   volume = Box3.getVolume();
   cout << "Volume of Box3 : " << volume <<endl;
 
   system("pause");
   return 0;
}

可過載運算子/不可過載運算子

下面是可過載運算子列表:

名稱符號
雙目關係運算子+(加),-(減),*(乘),/(除)
關係運算子==(等於),!=(不等於),<(小於),>(大於),<=(小於等於),>=(大於等於)
邏輯運算子||(邏輯或),&&(邏輯與),!(邏輯非)
單目運算子+(正),-(負),*(指標),&(取地址)
自增自減運算子++(自增),–(自減)
位運算子|(按位或),&(按位與),~(按位取反),^(按位異或),<<(左移),>>(右移)
賦值運算子=,+=,-=,*=,/=,&=,|=,^=,<<=,>>=
空間申請與釋放new[],delete,new[],delete[]
其他運算子()(函式呼叫),->(成員訪問),,(逗號),[](下標)

下面是不可過載運算子:

  • .:成員訪問運算子
  • .*, ->*:成員指標訪問運算子
  • :::域運算子
  • sizeof:長度運算子
  • ?::條件運算子
  • #: 預處理符號

多型

多型按字面的意思就是多種形態。當類之間存在層次結構,並且類之間是通過繼承關聯時,就會用到多型。

C++ 多型意味著呼叫成員函式時,會根據呼叫函式的物件的型別來執行不同的函式。

下面的例項中,基類 Shape 被派生為兩個類,如下所示:

#include <iostream>
using namespace std;

class Shape
{
public:
    Shape(int a = 0, int b = 0)
    {
        width = a;
        heigth = b;
    }
    
    int area()
    {
        cout << "Parent class area: " << endl;
        return 0;
    }
    
protected:
    int width;
    int height;
};

class Rectangle: public Shape
{
public:
    Rectangle(int a = 0, int b = 0):Shape(a, b) { }
    
    int area()
    {
        cout << "Rectangle class area: " << endl;
        return width * height;
    }
};

class Triangle: public Shape
{
public:
    Triangle(int a = 0, int b = 0):Shape(a, b) { }
    
    int area()
    {
        cout << "Trisngle class area: " << endl;
        return (width * height) / 2;
    }
};

int main()
{
    Shape *shape;
    Rectangle rectangle(10, 5);
    Triangle triangle(10, 5);
    
    shape = &rectangle;
    shape->area();
    
    shape = &triangle;
    shape->area();
    
    system("pause");
    return 0;
}

這裡我們會發現它的輸出結果是:

Parent class area:
Parent class area:

這裡只是引用了父類的函式

導致錯誤輸出的原因是,呼叫函式 area() 被編譯器設定為基類中的版本,這就是所謂的靜態多型,或靜態連結 - 函式呼叫在程式執行前就準備好了。有時候這也被稱為早繫結,因為 area() 函式在程式編譯期間就已經設定好了。

但現在,讓我們對程式稍作修改,在 Shape 類中,area() 的宣告前放置關鍵字 virtual,如下所示:

#include <iostream>
using namespace std;

class Shape
{
public:
    Shape(int a = 0, int b = 0)
    {
        width = a;
        height = b;
    }
    
    virtual int area()
    {
        cout << "Parent class area: " << endl;
        return 0;
    }
    
protected:
    int width;
    int height;
};

class Rectangle: public Shape
{
public:
    Rectangle(int a = 0, int b = 0):Shape(a, b) { }
    
    int area()
    {
        cout << "Rectangle class area: " << endl;
        return width * height;
    }
};

class Triangle: public Shape
{
public:
    Triangle(int a = 0, int b = 0):Shape(a, b) { }
    
    int area()
    {
        cout << "Triangle class area: " << endl;
        return (width * height) / 2;
    }
};

int main()
{
    Shape *shape;
    Rectangle rectangle(10, 5);
    Triangle triangle(10, 5);
    
    shape = &rectangle;
    shape->area();
    
    shape = &triangle;
    shape->area();
    
    system("pause");
    return 0;
}

修改後,我們發現它的輸出結果改變了:

Rectangle class area:
Triangle class area:

虛擬函式

虛擬函式 是在基類中使用關鍵字 virtual 宣告的函式。在派生類中重新定義基類中定義的虛擬函式時,會告訴編譯器不要靜態連結到該函式。

我們想要的是在程式中任意點可以根據所呼叫的物件型別來選擇呼叫的函式,這種操作被稱為動態連結,或後期繫結

純虛擬函式

我們可能想要在基類中定義虛擬函式,以便在派生類中重新定義該函式更好地適用於物件,但是您在基類中又不能對虛擬函式給出有意義的實現,這個時候就會用到純虛擬函式。

我們可以把基類中的虛擬函式 area() 改寫如下:

#include <iostream>
using namespace std;

class Shape
{
public:
    Shape(int a = 0, int b = 0)
    {
        width = a;
        height = b;
    }
    
    virtual int area() = 0;
    
protected:
    int width;
    int height;
};

class Rectangle: public Shape
{
public:
    Rectangle(int a = 0, int b = 0):Shape(a, b) { }
    
    int area()
    {
        cout << "Rectangle class area: " << endl;
        return width * height;
    }
};

class Triangle: public Shape
{
public:
    Triangle(int a = 0, int b = 0):Shape(a, b) { }
    
    int area()
    {
        cout << "Triangle class area: " << endl;
        return (width * height) / 2;
    }
};

int main()
{
    Shape *shape;
    Rectangle rectangle(10, 5);
    Triangle triangle(10, 5);
    
    shape = &rectangle;
    shape->area();
    
    shape = &triangle;
    shape->area();
    
    system("pause");
    return 0;
}

資料抽象

資料抽象是指,只向外界提供關鍵資訊,並隱藏其後臺的實現細節,即只表現必要的資訊而不呈現細節。

資料抽象是一種依賴於介面和實現分離的程式設計(設計)技術。

讓我們舉一個現實生活中的真例項子,比如一臺電視機,您可以開啟和關閉、切換頻道、調整音量、新增外部元件(如喇叭、錄影機、DVD 播放器),但是您不知道它的內部實現細節,也就是說,您並不知道它是如何通過纜線接收訊號,如何轉換訊號,並最終顯示在螢幕上。

因此,我們可以說電視把它的內部實現和外部介面分離開了,您無需知道它的內部實現原理,直接通過它的外部介面(比如電源按鈕、遙控器、聲量控制器)就可以操控電視。

現在,讓我們言歸正傳,就 C++ 程式設計而言,C++ 類為資料抽象提供了可能。它們向外界提供了大量用於操作物件資料的公共方法,也就是說,外界實際上並不清楚類的內部實現。

例如,您的程式可以呼叫 sort() 函式,而不需要知道函式中排序資料所用到的演算法。實際上,函式排序的底層實現會因庫的版本不同而有所差異,只要介面不變,函式呼叫就可以照常工作。

在 C++ 中,我們使用來定義我們自己的抽象資料型別(ADT)。您可以使用類 iostreamcout 物件來輸出資料到標準輸出,如下所示:

#include <iostream>
using namespace std;

int main()
{
    cout << "Hello c++" << endl;
    
    system("pause");
    return 0;
}

訪問標籤強制抽象

在 C++ 中,我們使用訪問標籤來定義類的抽象介面。一個類可以包含零個或多個訪問標籤:

  • 使用公共標籤定義的成員都可以訪問該程式的所有部分。一個型別的資料抽象檢視是由它的公共成員來定義的。
  • 使用私有標籤定義的成員無法訪問到使用類的程式碼。私有部分對使用型別的程式碼隱藏了實現細節。

訪問標籤出現的頻率沒有限制。每個訪問標籤指定了緊隨其後的成員定義的訪問級別。指定的訪問級別會一直有效,直到遇到下一個訪問標籤或者遇到類主體的關閉右括號為止。

資料抽象的好處

資料抽象有兩個重要的優勢:

  • 類的內部受到保護,不會因無意的使用者級錯誤導致物件狀態受損。
  • 類實現可能隨著時間的推移而發生變化,以便應對不斷變化的需求,或者應對那些要求不改變使用者級程式碼的錯誤報告。

如果只在類的私有部分定義資料成員,編寫該類的作者就可以隨意更改資料。如果實現發生改變,則只需要檢查類的程式碼,看看這個改變會導致哪些影響。如果資料是公有的,則任何直接訪問舊錶示形式的資料成員的函式都可能受到影響。

例項:

#include <iostream>
using namespace std;

class Adder
{
public:
    Adder(int i = 0)
    {
        total = i;
    }
    
    void addNum(int number)
    {
        total += number;
    }
    
    int getTotal()
    {
        return total;
    }
    
private:
    int total;
};

int main()
{
    Adder a;
    
    a.addNum(10);
    a.addNum(20);
    a.addNum(30);
    
    cout << "Total " << a.getTotal() << endl;
    
    system("pause");
    return 0;
}

資料封裝

所有的 C++ 程式都有以下兩個基本要素:

  • **程式語句(程式碼):**這是程式中執行動作的部分,它們被稱為函式。
  • **程式資料:**資料是程式的資訊,會受到程式函式的影響。

封裝是物件導向程式設計中的把資料和運算元據的函式繫結在一起的一個概念,這樣能避免受到外界的干擾和誤用,從而確保了安全。資料封裝引申出了另一個重要的 OOP 概念,即資料隱藏

資料封裝是一種把資料和運算元據的函式捆綁在一起的機制,資料抽象是一種僅向使用者暴露介面而把具體的實現細節隱藏起來的機制。

C++ 通過建立來支援封裝和資料隱藏(public、protected、private)。我們已經知道,類包含私有成員(private)、保護成員(protected)和公有成員(public)成員。預設情況下,在類中定義的所有專案都是私有的。

為了使類中的成員變成公有的(即,程式中的其他部分也能訪問),必須在這些成員前使用 public 關鍵字進行宣告。所有定義在 public 識別符號後邊的變數或函式可以被程式中所有其他的函式訪問。

把一個類定義為另一個類的友元類,會暴露實現細節,從而降低了封裝性。理想的做法是儘可能地對外隱藏每個類的實現細節。

例項:

#include <iostream>
using namespace std;

class Adder
{
public:
    Addder(int i = 0)
    {
        total = i;
    }
    
    void addNum(int number)
    {
        totale += number;
    }
    
    int getTotal()
    {
        return total;
    }
    
private:
    int total;
};

int main()
{
    Adder a;
    
    a.addNum(10);
    a.addNum(20);
    a.addNum(30);
    
    cout << "Total " << a.getTotal() << endl;
    
    system("pause");
    return 0;
}

相關文章