類的學習基礎篇
類的學習基礎篇
類的學習進階篇
首先來看一個執行個體
執行個體一
Mystring.h
#ifndef MYSTRING_H_
#define MYSTRING_H_
#include <iostream>
#include <cstring>
using std::cout;
using std::endl;
class Mystring
{
private:
char *m_str; // 字元串
int m_len; // 表示字元串長度
static int m_count; // 靜态成員變量,統計對象個數
const static int CINLEN = 80; // 輸入的最大長度
public:
// 構造函數
Mystring(); // 預設構造函數
Mystring(const char *str); // 構造函數
Mystring(const Mystring &mystr); // 複制構造函數
// 析構函數
~Mystring(); // 析構函數
// 其他成員函數
int length() const;
// 運算符重載方法
Mystring& operator=(const Mystring &mystr); // 指派運算符
Mystring& operator=(const char *str); // 指派運算符
char & operator[](int i); // 重載中括号通路字元串,可讀寫對象
const char & operator[](int i) const; // 重載中括号通路字元串,隻讀
// 靜态成員函數
static int get_count(); // 靜态成員函數,傳回統計的對象個數
// 運算符重載的方法并作為類的友元
friend std::ostream& operator<<(std::ostream &os, const Mystring& mystr); // 輸出
friend bool operator<(const Mystring & mystr1, const Mystring & mystr2); // 小于
friend bool operator>(const Mystring & mystr1, const Mystring & mystr2); // 大于
friend bool operator==(const Mystring & mystr1, const Mystring & mystr2); // 等于
friend std::istream& operator>>(std::istream &is, Mystring& mystr); // 輸入
};
#endif
Mystring.cpp
#include "Mystring.h"
int Mystring::m_count = 0;
Mystring::Mystring()
{
m_count++;
cout << "調用構造函數,目前對象個數: " << m_count << endl;
m_len = 0;
m_str = new char[m_len + 1];
m_str[0] = '\0';
}
Mystring::Mystring(const char *str)
{
m_count++;
cout << "調用構造函數,目前對象個數: " << m_count << endl;
m_len = strlen(str);
m_str = new char[m_len + 1];
strcpy(m_str, str);
}
Mystring::~Mystring()
{
m_count--;
cout << "調用析構函數,目前對象個數: " << m_count << endl;
delete [] m_str;;
m_str = nullptr;
}
Mystring::Mystring(const Mystring &mystr)
{
m_count++;
cout << "調用複制構造函數,目前對象個數: " << m_count << endl;
m_len = mystr.m_len;
m_str = new char[m_len + 1];
strcpy(m_str, mystr.m_str);
}
int Mystring::length() const
{
return m_len;
}
Mystring& Mystring::operator=(const Mystring &mystr)
{
if(this == &mystr) // 如果指派的對象是自己
{
return *this;
}
delete [] m_str;
cout << "調用指派運算符,目前對象個數: " << m_count << endl;
m_len = mystr.m_len;
m_str = new char[m_len + 1];
strcpy(m_str, mystr.m_str);
return *this;
}
// 定義一個接受正常字元串的指派運算符,在調用時傳入正常字元串,可以省去接受類對象的函數所産生的臨時對象,以提高處理效率。
Mystring& Mystring::operator=(const char *str)
{
delete [] m_str;
cout << "調用指派運算符,目前對象個數: " << m_count << endl;
m_len = strlen(str);
m_str = new char[m_len + 1];
strcpy(m_str, str);
return *this;
}
std::ostream& operator<<(std::ostream &os, const Mystring& mystr)
{
os << mystr.m_str;
return os;
}
std::istream& operator>>(std::istream &is, Mystring& mystr)
{
char temp[Mystring::CINLEN];
is.get(temp, Mystring::CINLEN);
if(is)
{
mystr = temp;
}
while(is && is.get() != '\n');
return is;
}
bool operator<(const Mystring & mystr1, const Mystring & mystr2)
{
if(strcmp(mystr1.m_str, mystr2.m_str) < 0)
{
return true;
}
else
{
return false;
}
}
bool operator>(const Mystring & mystr1, const Mystring & mystr2)
{
return mystr2 < mystr1;
}
bool operator==(const Mystring & mystr1, const Mystring & mystr2)
{
return (strcmp(mystr1.m_str, mystr2.m_str) == 0);
}
char & Mystring::operator[](int i)
{
return m_str[i];
}
const char & Mystring::operator[](int i) const
{
return m_str[i];
}
int Mystring::get_count()
{
return m_count;
}
main.cpp
#include "Mystring.h"
// 按引用傳參
void print_string1(const Mystring& mystr);
// 按值傳參
void print_string2(const Mystring mystr);
// 按值傳回對象
Mystring return_string_by_value(const Mystring& mystr);
int main()
{
Mystring str1("hello world");
cout << str1 << endl;
cout << str1.length() << endl;
std::cin >> str1;
cout << str1 << endl;
return 0;
}
void print_string1(const Mystring& mystr)
{
cout << "按引用穿參: ";
cout << mystr << endl;
}
void print_string2(const Mystring mystr)
{
cout << "按值傳參: ";
cout << mystr << endl;
}
Mystring return_string_by_value(const Mystring& mystr)
{
cout << "按值傳回對象" << endl;
return mystr;
}
靜态成員
靜态成員變量
- 無論建立了多少對象,程式都隻建立一個靜态變量副本。
- 不能在類聲明中初始化靜态成員變量(靜态變量為const除外),因為聲明描述了如何配置設定記憶體,但并不配置設定記憶體。
- 對于靜态類成員變量,可以在類聲明之外使用單獨的語句來進行初始化,因為靜态類成員是單獨存儲的,而不是對象的組成部分。
靜态成員函數
- 不能通過對象調用靜态成員函數,可以使用類名和作用域解析運算符來調用。
- 靜态成員函數隻能使用靜态成員變量,因為靜态成員函數不與特定的對象相關聯。
class Mystring
{
...
private:
// 聲明靜态變量
static int m_count;
const static int CINLEN = 80; // const靜态變量可以在類聲明中初始化
public:
static int get_count(); // 靜态成員函數,傳回統計的對象個數
...
};
// 初始化靜态變量
int Mystring::m_count = 0;
// 定義
int Mystring::get_count()
{
return m_count; // 靜态成員函數隻能通路靜态成員變量
}
// 調用
int count = Mystring::get_count(); // 使用類名和作用域解析運算符來調用靜态成員函數
複制構造函數
複制構造函接受一個指向類對象的常量引用作為參數,用于将一個對象複制到新建立的對象中。
原型如下:
Class name(const Class_name &);
例如
Mystring(const Mystring &mystr);
何時調用複制構造函數
- 建立一個對象并将其初始化為同類現有對象時,複制構造函數都将被調用。
Mystring str1("hello world");
Mystring str2(str1); // 調用複制構造函數
Mystring str3 = str1; // 調用複制構造函數
Mystring str4 = Mystring(str1); // 調用複制構造函數
Mystring *str5 = new Mstring(str1); // 調用複制構造函數
- 每當程式生成了對象副本時,編譯器都将使用複制構造函數。
- 當函數按值傳對象時,将會使用複制構造函數産生臨時對象。
void print_string2(const Mystring mystr); // 按值傳對象
- 當函數按值傳回對象時,将會使用複制構造函數産生臨時對象。
Mystring return_string3_by_value(const Mystring& mystr); // 按值傳回對象
預設複制構造函數
如果類中沒有提供複制構造函數,C++會自動提供一個預設複制構造函數。
預設複制構造函數會逐個複制非靜态成員,也就是淺拷貝。
對于Mystring這個類來說,預設複制構造函數實作如下:
Mystring(const Mystring &mystr)
{
m_len = mystr.m_len;
m_str = mystr.m_str;
}
如果使用預設複制構造函數
Mystring str1("hello world");
Mystring str2 = str1; // 使用預設複制構造函數
首先,m_count這個靜态變量不會增加。
其次,str1中的m_str和str2中的m_str都指向相同的位址,當程式結束調用析構函數時,會将同一塊記憶體釋放兩次,進而可能導緻程式崩潰。
注意
當類中存在需要使用new來配置設定記憶體的變量,一定要定義一個顯示複制構造函數,來解決預設複制構造函數中存在的問題。
Mystring::Mystring(const Mystring &mystr)
{
m_count++;
cout << "調用複制構造函數,目前對象個數: " << m_count << endl;
m_len = mystr.m_len;
m_str = new char[m_len + 1]; // 深拷貝
strcpy(m_str, mystr.m_str);
}
指派運算符
當一個已初始化好的對象指派給另一個已初始化好的對象,将會調用指派運算符。
如果沒有顯示重寫指派運算符,C++将會提供一個預設的指派運算符。
Class name & operator=(const Class_name &);
接受并傳回一個指向類對象的引用。
預設指派運算符也是淺拷貝,是以也可能導緻預設複制構造函數一樣的問題。
重寫指派運算符
Mystring& operator=(const Mystring &mystr); // 接受一個Mystring的對象
Mystring& operator=(const char *str); // 接受正常字元串作為參數
Mystring& Mystring::operator=(const Mystring &mystr)
{
if(this == &mystr) // 如果指派的對象是自己
{
return *this;
}
delete [] m_str;
cout << "調用指派運算符,目前對象個數: " << m_count << endl;
m_len = mystr.m_len;
m_str = new char[m_len + 1];
strcpy(m_str, mystr.m_str);
return *this;
}
// 定義一個接受正常字元串的指派運算符,在調用時傳入正常字元串,可以省去接受類對象的函數所産生的臨時對象,以提高處理效率。
Mystring& Mystring::operator=(const char *str)
{
delete [] m_str;
cout << "調用指派運算符,目前對象個數: " << m_count << endl;
m_len = strlen(str);
m_str = new char[m_len + 1];
strcpy(m_str, str);
return *this;
}
- 先判斷是否是自我指派;
- 再釋放掉原來已初始化好的記憶體;
- 再進行深拷貝;
- 最後傳回重新拷貝好的本對象引用。
new和delete的配對使用
在構造函數中使用
new
來配置設定記憶體時,必須在相應的析構函數中使用
delete
來釋放記憶體。如果使用
new[]
來配置設定記憶體,則應使用
delete[]
來釋放記憶體。
比較成員函數
friend bool operator<(const Mystring & mystr1, const Mystring & mystr2); // 小于
friend bool operator>(const Mystring & mystr1, const Mystring & mystr2); // 大于
friend bool operator==(const Mystring & mystr1, const Mystring & mystr2); // 等于
将比較成員函數作為友元函數,可以使Mystring對象直接和正常字元串進行比較。
Mystring str("hello world");
if(str == "hello world")
{
// ...
}
if("hello world" == str)
{
// ...
}
使用中括号表示法通路字元
為了使Mystring可以像使用标準C風格字元串一樣通過中括号來擷取字元,可以重載中括号
char & operator[](int i); // 重載中括号通路字元串,可讀寫對象
const char & operator[](int i) const; // 重載中括号通路字元串,隻讀
char & Mystring::operator[](int i)
{
return m_str[i];
}
const char & Mystring::operator[](int i) const
{
return m_str[i];
}
Mystring str1 = "who";
str[0] = 'h'; // 讀寫,調用 char & operator[](int i);
const Mystring str2 = "who";
char a = str2[0]; // 隻讀,調用 const char & operator[](int i) const;
成員初始化清單
從概念上說,調用構造函數時,對象将在執行函數體中的代碼之前被建立,而變量則在函數體中進行初始化。
但使用初始化成員清單,可以使變量在對象建立的時候被初始化。
#include <iostream>
using std::cout;
using std::endl;
class Test
{
private:
int m_data1; // 普通變量
const int m_data2; // 非靜态const資料成員
int & m_data3; // 引用的資料成員
public:
Test(int data1, int data2, int data3);
};
int main()
{
Test t(1,2,3);
return 0;
}
// 使用初始化成員清單
Test::Test(int data1, int data2, int data3)
:m_data2(data2), m_data3(data3) // 将在對象建立時進行初始化
{
m_data1 = data1; // 将在對象建立後進行初始化
}
注意
- 成員初始化清單隻能用于構造函數;
- 必須用這種格式來初始化非靜态const資料成員;
- 必須用這種格式來初始化引用的資料成員;
- 資料成員被初始化的順序與它們出現在類聲明中的順序相同,與成員初始化清單排序順序無關。