1. 程式人生 > 其它 >c++類模板的實際案例以及重要知識點

c++類模板的實際案例以及重要知識點

技術標籤:c++

一:案例分析

(本案例來自黑馬程式設計師老師的課)

案例描述: 實現一個通用的陣列類,要求如下:

  • 可以對內建資料型別以及自定義資料型別的資料進行儲存
  • 將陣列中的資料儲存到堆區
  • 建構函式中可以傳入陣列的容量
  • 提供對應的拷貝建構函式以及operator=防止淺拷貝問題
  • 提供尾插法和尾刪法對陣列中的資料進行增加和刪除
  • 可以通過下標的方式訪問陣列中的元素
  • 可以獲取陣列中當前元素個數和陣列的容量

檔案組織:

二:實際程式碼

1.My_array.hpp中的程式碼:

#pragma once    //防止重複編譯,微軟系編譯器獨有的
#include<iostream>
using namespace std;
//陣列模板,用來接受傳入的型別
template<class T>  
class My_array {
	int m_size;//陣列中實際儲存的元素個數
	int m_capacity;//陣列的最大容量
	T* pAddress;//根據傳入型別定義了一個指標型別,待會兒會在建構函式中在堆區建立一個數組並用這個指標指向那塊記憶體
public:
	My_array(int capacity); 
	int Get_size();
	int Get_capacity();
	void Push_Back(const T& val);//尾插法
	void Pop_Back();//尾刪法
	My_array(const My_array& array);//拷貝建構函式,注意深淺拷貝,這裡有指標型別,必須深拷貝
	~My_array();
	My_array& operator = (const My_array & array) //操作符過載必須是成員函式
	{
	this->m_size = array.m_size;
	this->m_capacity = array.m_capacity;
	this->pAddress = new T[this->m_capacity];
	for (int i = 0; i < this->m_size; i++) {
		this->pAddress[i] = array.pAddress[i];
	   }
	}
	T& operator[](int index){ //過載[]操作符
		return this->pAddress[index];
	}
};
//建構函式類外實現
template<class T>
My_array<T>::My_array(int capacity) {
	m_capacity = capacity;
	m_size = 0;
	pAddress = new T[this->m_capacity];
}
//m_size介面
template<class T>
int My_array<T>::Get_size() {
	return m_size;
}
//m_capacity介面
template<class T>
int My_array<T>::Get_capacity() {
	return m_capacity;
}
//拷貝建構函式
template<class T>
My_array<T>::My_array(const My_array& array) {
	if (this->pAddress != 0) {
		delete[]pAddress;
		this->m_size = 0;
		this->m_capacity = 0;
	}
	this->m_size = array.m_size;
	this->m_capacity = array.m_capacity;
	this->pAddress = new T[this->m_capacity];
	for (int i = 0; i < this->m_size; i++) {
		this->pAddress[i] = array.pAddress[i];
	}
	//這裡千萬不能寫返回值,return *this;
	//因為建構函式也好,拷貝建構函式也好,都不能有返回值,原因下面講
}
//尾插法
template<class T>
void My_array<T>::Push_Back(const T&val) {
	if (this->m_size == this->m_capacity) {
		cout << "陣列已滿,無法插入" << endl;
		return;
	}
	else
		this->pAddress[this->m_size] = val;
	    this->m_size++;
}
//尾刪法
template<class T>
void My_array<T>::Pop_Back() {
	if (this->m_size == 0) {
		cout << "陣列已刪除完畢" << endl;
		return;
	}
	else
		this->m_size--;//邏輯上的刪除,並未真的刪除
}
//解構函式
template<class T>
My_array<T>::~My_array() {
	if (this->pAddress != 0) {
		delete[]this->pAddress;
		pAddress = NULL;//指標只想空,防止野指標
		this->m_size = 0;
		this->m_capacity = 0;
	}
}

2.main.cpp中的程式碼:

#include<iostream>
#include<string>
#include"My_array.hpp"
using namespace std;
//自定義一個型別,custom表示自定義的意思
class custom {
public:
	string name;
	string age;
	custom() {};
	custom(string name, string age) {
		this->age = age;
		this->name = name;
	}
};
//類模板作函式引數
void print_int_array(My_array<int>&array) {
	for (int i = 0; i < array.Get_size(); i++) {
		cout << array[i]<<" ";
	}
	cout << endl;
}
void test_int() {
	My_array<int>array(4);
	for (int i = 0; i < 4; i++) {
		array.Push_Back(i);//測試尾插法
	}
	print_int_array(array);
	array.Pop_Back();//測試尾刪法
	print_int_array(array); 
	My_array<int>array1(array);//測試拷貝建構函式
	print_int_array(array1);
}

void print_custom_array(My_array<custom>&array) {
	for (int i = 0; i < array.Get_size(); i++) {
		cout << array[i].name << "  " << array[i].age << " ";
   }
	cout << endl;
}

void test_custom() {
	My_array<custom>array(3);

	custom c1("李四", "20");
	custom c2("王五", "15");
	custom c3("法外狂徒—張三", "10");

	array.Push_Back(c1);//測試尾插法
	array.Push_Back(c2);
	array.Push_Back(c3);
	print_custom_array(array);
	array.Pop_Back();//測試尾刪法
	print_custom_array(array);
	My_array<custom>array1(array);//測試拷貝建構函式
	print_custom_array(array1);
}
int main() 
{
	test_int();
	test_custom();
	return 0;
}

二:重要知識點

1.返回值問題

Q:為什麼建構函式(拷貝建構函式)無法返回值?

首先是規定,C++標準規定了構造/析構/自定義型別轉換符不可以指定返回型別。我個人的一個理解是建構函式是用來告訴編譯器,我想定義了一個物件,你編譯器得給我在棧或堆中分配記憶體,不然我往哪放?這個過程就相當於int a;沒那個必要返回值。

2.繼承過程中的建構函式問題

問題程式碼://會發現,當子類和父類都含有有參建構函式之後就會報錯,編譯器給出的錯誤原因是父類沒有預設建構函式,這就很令人迷惑了。

#include<iostream>
using namespace std;
class base {
public:
	base(int a){}
};
class derive :public base {
public:
	int a;
	derive(int a) {
		this->a = a;
	}
};
int main() {
	derive d(1);
}

構造原則如下:

1. 如果子類沒有定義構造方法,則呼叫父類的無引數的構造方法。

2. 如果子類定義了構造方法,不論是無引數還是帶引數,在建立子類的物件的時候,首先執行父類無引數的構造方法,然後執行自己的構造方法。 //這就能解釋上面的問題

3. 在建立子類物件時候,如果子類的建構函式沒有顯示呼叫父類的建構函式,則會呼叫父類的預設無參建構函式。

derive(int a) :base(a){} //這就屬於顯示的呼叫父類的建構函式

4. 在建立子類物件時候,如果子類的建構函式沒有顯示呼叫父類的建構函式且父類自己提供了無參建構函式,則會呼叫父類自己的無參建構函式。

5. 在建立子類物件時候,如果子類的建構函式沒有顯示呼叫父類的建構函式且父類只定義了自己的有參建構函式,則會出錯(如果父類只有有引數的構造方法,則子類必須顯示呼叫此帶參構造方法)。

6. 如果子類呼叫父類帶引數的構造方法,需要用初始化父類成員物件的方式