天天看點

C++類的學習(進階篇)類的學習基礎篇類的學習進階篇

類的學習基礎篇

類的學習基礎篇

類的學習進階篇

首先來看一個執行個體

執行個體一

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;
}
           

靜态成員

靜态成員變量

  1. 無論建立了多少對象,程式都隻建立一個靜态變量副本。
  2. 不能在類聲明中初始化靜态成員變量(靜态變量為const除外),因為聲明描述了如何配置設定記憶體,但并不配置設定記憶體。
  3. 對于靜态類成員變量,可以在類聲明之外使用單獨的語句來進行初始化,因為靜态類成員是單獨存儲的,而不是對象的組成部分。

靜态成員函數

  1. 不能通過對象調用靜态成員函數,可以使用類名和作用域解析運算符來調用。
  2. 靜态成員函數隻能使用靜态成員變量,因為靜态成員函數不與特定的對象相關聯。
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);
           

何時調用複制構造函數

  1. 建立一個對象并将其初始化為同類現有對象時,複制構造函數都将被調用。
Mystring str1("hello world");
Mystring str2(str1);    // 調用複制構造函數
Mystring str3 = str1;   // 調用複制構造函數
Mystring str4 = Mystring(str1);         // 調用複制構造函數
Mystring *str5 = new Mstring(str1);     // 調用複制構造函數
           
  1. 每當程式生成了對象副本時,編譯器都将使用複制構造函數。
    • 當函數按值傳對象時,将會使用複制構造函數産生臨時對象。
    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;
}
           
  1. 先判斷是否是自我指派;
  2. 再釋放掉原來已初始化好的記憶體;
  3. 再進行深拷貝;
  4. 最後傳回重新拷貝好的本對象引用。

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;    // 将在對象建立後進行初始化
}
           

注意

  1. 成員初始化清單隻能用于構造函數;
  2. 必須用這種格式來初始化非靜态const資料成員;
  3. 必須用這種格式來初始化引用的資料成員;
  4. 資料成員被初始化的順序與它們出現在類聲明中的順序相同,與成員初始化清單排序順序無關。

繼續閱讀