C++基礎教程面向物件(學習筆記2)
1.1類和類成員
前面發了兩篇似乎是無關緊要的,但是我希望還是可以看看,畢竟介紹了我們接下來要學的內容以及我的一些中肯的建議。
雖然C ++提供了許多基本資料型別(例如char,int,long,float,double等等),這些型別通常足以解決相對簡單的問題,但僅使用這些型別解決複雜問題可能很困難。C ++的一個更有用的功能是能夠定義自己的資料型別,更好地對應於正在解決的問題。您已經瞭解了列舉型別和結構如何用於建立自己的自定義資料型別。
以下是用於儲存日期的結構體示例:
struct DateStruct { int year; int month; int day; };
列舉型別和僅資料結構(僅包含變數的結構)代表傳統的非面向物件程式設計世界,因為它們只能儲存資料。在C ++ 11中,我們可以按如下方式建立和初始化此結構:
DateStruct today { 2020, 10, 14 }; // 用統一初始化的方式初始化
現在,如果我們想要將日期列印到螢幕上(我們可能想要做很多事情),編寫一個函式來執行此操作是有意義的。這是一個完整的程式:
#include <iostream> struct DateStruct { int year; int month; int day; }; void print(DateStruct &date) { std::cout << date.year << "/" << date.month << "/" << date.day; } int main() { DateStruct today { 2020, 10, 14 }; //用統一初始化的方式初始化 today.day = 16; // 使用成員變數選擇符號來選擇你的結構成員變數 print(today); return 0; }
該程式列印:
2020年10月16日
類
在面向物件程式設計的世界中,我們經常希望我們的型別不僅可以儲存資料,還可以提供與資料一起使用的函式。在C ++中,這通常通過class關鍵字完成。使用class關鍵字定義一個名為class的新使用者定義型別。
在C ++中,類非常類似於僅資料結構,除了類提供更多的功能和靈活性。實際上,以下結構和類實際上是相同的:
struct DateStruct { int year; int month; int day; }; class DateClass { public: int m_year; int m_month; int m_day; };
請注意,唯一重要的區別是類中的關鍵字public。我們將在下一課中討論此關鍵字的功能。 就像結構宣告一樣,類宣告不會宣告任何記憶體。它只定義了類的外觀。 警告:就像使用結構一樣,在C ++中最簡單的錯誤之一是在類宣告結束時忘記分號。這將導致下一行程式碼出現編譯器錯誤。像Visual Studio 2013這樣的現代編譯器會向您顯示您可能忘記了分號,但較舊或較不復雜的編譯器(例如:VC++6.0)可能沒有,這可能使實際錯誤難以找到。 就像使用結構一樣,要使用類,必須宣告該類型別的變數:
DateClass today { 2020, 10, 14 }; // 宣告一個 DateClass的類
在C ++中,當我們定義一個類的變數時,我們稱它為例項化類。變數本身稱為類的例項。類型別的變數也稱為物件。就像定義內建型別的變數(例如int x,double y等等)為該變數分配記憶體一樣,例項化一個物件(例如上述DateClass)為該物件分配記憶體。
成員功能
除了儲存資料外,類還可以包含函式!(我認為這是C++擴充套件的最棒的部分)在類中定義的函式稱為成員函式(或有時稱為方法)。可以在類定義的內部或外部定義成員函式。我們現在將在類中定義它們(為簡單起見),並展示如何在類之外定義它們。 這是我們的Date類,其中包含用於列印日期的成員函式:
class DateClass
{
public:
int m_year;
int m_month;
int m_day;
void print() // 定義一個名為: print()
{
std::cout << m_year << "/" << m_month << "/" << m_day;
}
};
就像結構的成員一樣,使用成員選擇器運算子(.)訪問類的成員(變數和函式):
#include
class DateClass { public: int m_year; int m_month; int m_day;
void print()
{
std::cout << m_year << "/" << m_month << "/" << m_day;
}
};
int main() { DateClass today { 2020, 10, 14 };
today.m_day = 16; // 用選擇成員操作符來選擇類中的成員變數
today.print(); // 用選擇成員操作符來選擇類中的成員函式
return 0;
} 這列印:
2020年10月16日 注意這個程式與我們上面編寫的struct版本有多相似。
但是,存在一些差異。在上面示例的print()的DateStruct版本中,我們需要將struct本身作為第一個引數傳遞給print()函式。否則,print()將不知道我們想要使用的DateStruct。然後我們必須明確地在函式內引用這個引數。
成員函式的工作方式略有不同:所有成員函式呼叫必須與類的物件相關聯。當我們呼叫“today.print()”時,我們告訴編譯器呼叫與today物件關聯的print()成員函式。
現在讓我們再看一下列印成員函式的定義:
void print() // 定義一個名為: print()的函式
{
std::cout << m_year << "/" << m_month << "/" << m_day;
}
m_year,m_month和m_day實際上是指什麼?它們引用相關物件(由呼叫者確定)。
因此,當我們呼叫“today.print()”時,編譯器將m_dayas today.m_day,m_monthas today.m_month和m_yearas 解釋為today.m_year。如果我們呼叫“tomorrow.print()”,m_day則會引用tomorrow.m_day。
以這種方式,關聯物件基本上隱式地傳遞給成員函式。因此,它通常被稱為隱式物件。
我們將在本章的後續課程中詳細討論隱式物件傳遞的工作原理。
關鍵點在於,對於非成員函式,我們必須將資料傳遞給要使用的函式。使用成員函式,我們可以假設我們總是有一個類的隱式物件來使用!
使用成員變數的“m_”字首有助於將成員變數與成員函式內的函式引數或區域性變數區分開來。由於幾個原因,這很有用。首先,當我們看到帶有“m_”字首的變數的賦值時,我們知道我們正在改變類的狀態。其次,與函式中宣告的函式引數或區域性變數不同,成員變數在類定義中宣告。因此,如果我們想知道如何宣告帶有“m_”字首的變數,我們知道我們應該檢視類定義而不是函式內部。
按照慣例,類名應以大寫字母開頭。
規則:將您的類命名為以大寫字母開頭。
這是一個類的另一個例子:
#include <iostream>
#include <string>
class Employee
{
public:
std::string m_name;
int m_id;
double m_wage;
// 將員工資訊列印到螢幕上
void print()
{
std::cout << "Name: " << m_name <<
" Id: " << m_id <<
" Wage: $" << m_wage << '\n';
}
};
int main()
{
// Declare two employees
Employee alex { "Alex", 1, 25.00 };
Employee joe { "Joe", 2, 22.25 };
// 將員工資訊輸出到螢幕上
alex.print();
joe.print();
return 0;
}
這會產生輸出:
姓名:Alex Id:1工資:25美元 姓名:Joe Id:2工資:22.25美元 與普通函式不同,定義成員函式的順序無關緊要!
關於C ++結構和類的解釋:
在C中,結構只能儲存資料,並且沒有關聯的成員函式。在C ++中,在設計類(使用class關鍵字)之後,Bjarne Stroustrup花了一些時間考慮結構(從C繼承)是否應該被賦予相同的功能。在考慮之後,他確定他們應該部分地為兩者制定統一的規則。因此,儘管我們使用class關鍵字編寫了上述程式,但我們可以使用struct關鍵字。
許多開發人員(包括我自己)認為這是不正確的決定,因為它可能導致危險的假設:例如,假設一個類將在自身之後清理是正確的(例如,分配記憶體的類將在被釋放之前釋放它(),但假設一個結構將是不安全的。因此,我們建議將struct關鍵字用於僅資料結構,並使用class關鍵字來定義需要將資料和函式捆綁在一起的物件。
規則:對僅資料結構使用struct關鍵字。對具有資料和函式的物件使用class關鍵字。
您已經在不知情的情況下使用了類
事實證明,C ++標準庫中充滿了為您的利益而建立的類。std :: string,std :: vector和std :: array都是類型別!因此,當您建立任何這些型別的物件時,您將例項化一個類物件。當你使用這些物件呼叫一個函式時,你正在呼叫一個成員函式。
#include <string>
#include <array>
#include <vector>
#include <iostream>
int main()
{
std::string s { "Hello, world!" }; // 初始化一個字串類的物件
std::array<int, 3> a { 1, 2, 3 }; // 初始化陣列類物件
std::vector<double> v { 1.1, 2.2, 3.3 }; //初始化向量類物件
std::cout << "length: " << s.length() << '\n'; // 呼叫一個成員函式
return 0;
}
結論:
class關鍵字允許我們在C ++中建立一個可以包含成員變數和成員函式的自定義型別。類構成了面向物件程式設計的基礎,我們將花費本章的其餘部分以及未來的許多章節來探索它們所提供的所有內容!
Quiz time: 題目要求:建立一個名為IntPair的類,它包含兩個整數。這個類應該有兩個成員變數來儲存整數。您還應該建立兩個成員函式:一個名為“set”,用於為整數賦值,另一個名為“print”,用於列印變數值。 問題:為什麼我們要使用類而不是用結構體?(請先思考,答案在最後) 應執行以下主要功能:
int main()
{
IntPair p1;
p1.set(1, 1); // 設定p1的值為 (1, 1)
IntPair p2{ 2, 2 }; //初始化 p2 的值為 (2, 2)
p1.print();
p2.print();
return 0;
}
併產生輸出: (1,1) (2,2)
解決方案的程式碼:
#include <iostream>
class IntPair
{
public:
int m_first;
int m_second;
void set(int first, int second)
{
m_first = first;
m_second = second;
}
void print()
{
std::cout << "Pair(" << m_first << ", " << m_second << ")\n";
}
};
int main()
{
IntPair p1;
p1.set(1, 1);
IntPair p2{ 2, 2 };
p1.print();
p2.print();
return 0;
}
為什麼我們要為IntPair而不是結構使用類? 回答:該物件包含成員資料和成員函式,因此我們應該使用一個類。我們不應該對具有成員函式的物件使用結構。