【C++ 泛型程式設計01:模板】函數模板與類別範本

2023-02-05 12:00:54

【模板】

  • 除了OOP外,C++另一種程式設計思想稱為 泛型程式設計 ,主要利用的技術就是模板

  • C++提供兩種模板機制:函數模板類別範本

函數模板

函數模板作用

建立一個通用函數,其函數返回值型別和形參型別可以不具體制定,用一個虛擬的型別來代表。

語法

template<typename T>
函數宣告或定義

解釋

template --- 宣告建立模板

typename --- 表面其後面的符號是一種資料型別,可以用class代替

T --- 通用的資料型別,名稱可以替換,通常為大寫字母

例子

舉個例子,我們要寫一些交換資料的函數

#include<iostream>
using namespace std;

//兩個整形交換的函數
void swapInt(int& a, int& b) {
	int temp = a;
	a = b;
	b = temp;
}

//交換浮點型函數
void swapDouble(double& a, double& b) {
	double temp = a;
	a = b;
	b = temp;
}

void test01() {
	int a = 10;
	int b = 20;

	swapInt(a, b);

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
}

int main() {
	test01();

	system("pause");
	return 0;
}

很簡單,但是像上面那樣寫函數,那交換不同的資料交換就要有對應的函數,很冗餘

如果可以先不告訴函數輸入引數的型別,用的時候再確定,就可以抽象一個通用的交換函數

這就是模板的用途,於是上面的例子便可以寫成:

#include<iostream>
using namespace std;

//函數模板
template<typename T> //宣告一個模板,後面程式碼裡面用T的時候不要報錯,T為通用資料型別
void MySawp(T& a, T& b) {
	T temp = a;
	a = b;
	b = temp;
}

void test01() {
	int a = 10;
	int b = 20;

	//模板有兩種使用方式
	//1、自動型別推導資料型別
	//MySawp(a, b);

	//2、顯式指定資料型別
	MySawp<int>(a, b);
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
}


int main() {
	test01();

	system("pause");
	return 0;
}

總結

  • 函數模板利用關鍵字 template
  • 使用函數模板有兩種方式:自動型別推導、顯示指定型別
  • 模板的目的是為了提高複用性,將型別引數化

注意事項

  • 自動型別推導,必須推匯出一致的資料型別T,才可以使用

  • 模板必須要確定出T的資料型別,才可以使用

例子
#include<iostream>
using namespace std;

template<class T> //typename可以替換為class
void MySawp(T& a, T& b) {
	T temp = a;
	a = b;
	b = temp;
}

//1、自動型別推導,必須推匯出一致的資料型別T,才可以使用
void test01() {
	int a = 10;
	int b = 20;
	char c = 'c';

	//利用函數模板交換
	//兩種方式
	//1、自動型別推導
	MySawp(a, b);//對
	//MySawp(a, c);//錯,推導不出一致的T型別

	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
}
// 2、模板必須要確定出T的資料型別,才可以使用
template<class T>
void func()//func寫在template宣告後面就已經是一個函數模板了
{//不管模板裡面用沒用T,都必須給T一個資料型別,func才可以被呼叫
	cout << "func 呼叫" << endl;
}

void test02()
{
	//func(); //錯誤,模板不能獨立使用,必須確定出T的型別
	func<int>(); //利用顯示指定型別的方式,給T一個型別,才可以使用該模板
}

int main() {
	test01();

	system("pause");
	return 0;
}

範例:排序函數封裝

案例描述

  • 利用函數模板封裝一個排序的函數,可以對不同資料型別陣列進行排序
  • 排序規則從大到小,排序演演算法為選擇排序
  • 分別利用char陣列int陣列進行測試

程式碼

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


//交換的函數模板
template<typename T>
void mySwap(T& a, T& b)
{
	T temp = a;
	a = b;
	b = temp;
}


////1、先寫一個選擇排序的函數
//void sort(int num[],int len) {
//	
//	for (int i = 0; i < len; i++) {
//		//以第一個元素作為初始最大值
//		int max = i;
//		//遍歷找出最大值(的下標)
//		for (int j = i + 1; j < len; j++) {
//			if (num[j] > num[max]) {
//				max = j;
//			}
//		}
//		//max不等於i,出現新的max值
//		//更新最大值
//		if (max != i) {
//			mySwap(num[max], num[i]);
//		}
//
//	}
//}

template<class T> // 也可以替換成typename
//利用選擇排序,進行對陣列從大到小的排序
void mySort(T arr[], int len)
{
	for (int i = 0; i < len; i++)
	{
		int max = i; //最大數的下標
		for (int j = i + 1; j < len; j++)
		{
			if (arr[max] < arr[j])
			{
				max = j;
			}
		}
		if (max != i) //如果最大數的下標不是i,交換兩者
		{
			mySwap(arr[max], arr[i]);
		}
	}
}

//氣泡排序,但是是從小到大
template<class T>
void bubleSort(T arr[], int len) {
	T temp;
	for (int i = 0; i < len - 1; i++) {
		for (int j = 0; j < len - 1 - i; j++) {
			if (arr[j] > arr[j + 1]) {
				temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}

//列印
template<typename T>
void printArray(T arr[], int len) {

	for (int i = 0; i < len; i++) {
		cout << arr[i] << " ";
	}
	cout << endl;
}
void test01()
{
	//測試char陣列
	char charArr[] = "bdcfeagh";
	int num = sizeof(charArr) / sizeof(char);
	mySort(charArr, num);
	printArray(charArr, num);
}

void test02() {
	//測試int陣列
	int intArr[] = { 7, 5, 8, 1, 3, 9, 2, 4, 6 };
	int num = sizeof(intArr) / sizeof(int);
	mySort(intArr, num);
	printArray(intArr, num);
}

void test03() {
	//測試int陣列冒泡
	int intArr[] = { 7, 5, 8, 1, 3, 9, 2, 4, 6 };
	int num = sizeof(intArr) / sizeof(int);
	bubleSort(intArr, num);
	printArray(intArr, num);
}

int main() {
	test03();

	system("pause");
	return 0;
}

區別

普通函數與函數模板區別:

  • 普通函數呼叫時可以發生自動型別轉換(隱式型別轉換)
  • 函數模板呼叫時,如果利用自動型別推導,不會發生隱式型別轉換
  • 如果利用顯示指定型別的方式,可以發生隱式型別轉換

建議使用顯示指定型別的方式,呼叫函數模板,因為可以自己確定通用型別T

類別範本

類別範本作用

建立一個通用類,類中的成員 資料型別可以不具體制定,用一個虛擬的型別來代表。

語法

template<typename T>
類

解釋

template --- 宣告建立模板

typename --- 表面其後面的符號是一種資料型別,可以用class代替

T --- 通用的資料型別,名稱可以替換,通常為大寫字母

例子

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

//給出類中成員屬性的通用資料型別,可以直接給個預設值,後面就不用再寫了
//Person為類別範本,有NameType、AgeType兩個通用資料型別
template<class NameType, class AgeType = int>
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->mName = name;
		this->mAge = age;
	}
	void showPerson()
	{
		cout << "name: " << this->mName << " age: " << this->mAge << endl;
	}
public:
	NameType mName;
	AgeType mAge;
};

void test01()
{
	// 指定NameType 為string型別,AgeType 為 int型別
	Person<string, int>P1("jk", 999);
	P1.showPerson();
}

//類別範本沒有自動型別推導,必須指定資料型別
void test02()
{
	// 指定NameType 為string型別,AgeType 為 int型別
	Person<string> P1("dk", 9);
	P1.showPerson();
}

int main() {
	test01();

	system("pause");
	return 0;
}

類別範本與函數模板區別

類別範本與函數模板區別主要有兩點:

  1. 類別範本沒有自動型別推導的使用方式
  2. 類別範本在模板參數列中可以有預設引數

總結

類別範本和函數模板語法相似,在宣告模板template後面加類,此類稱為類別範本

類別範本物件做函數引數

類別範本範例化出的物件,作為引數傳向函數時,一共有三種傳入方式:

  1. 指定傳入的型別 --- 直接顯示物件的資料型別
  2. 引數模板化 --- 將物件中的引數變為模板進行傳遞
  3. 整個類別範本化 --- 將這個物件型別 模板化進行傳遞

指定傳入的型別

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

//類別範本
template<class NameType, class AgeType = int>
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->mName = name;
		this->mAge = age;
	}
	void showPerson()
	{
		cout << "name: " << this->mName << " age: " << this->mAge << endl;
	}
public:
	NameType mName;
	AgeType mAge;
};

//1、指定傳入的型別
void printPerson1(Person<string, int>& p)
{
	p.showPerson();
}
void test01()
{
	Person <string, int >p("jk", 100);
	printPerson1(p);
}

int main() {
	test01();
    
	system("pause");
	return 0;
}

引數模板化

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

//類別範本
template<class NameType, class AgeType = int>
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->mName = name;
		this->mAge = age;
	}
	void showPerson()
	{
		cout << "name: " << this->mName << " age: " << this->mAge << endl;
	}
public:
	NameType mName;
	AgeType mAge;
};

//2、引數模板化
template <class T1, class T2>
void printPerson2(Person<T1, T2>& p)
{
	p.showPerson();
	cout << "T1的型別為: " << typeid(T1).name() << endl;
	cout << "T2的型別為: " << typeid(T2).name() << endl;
}
void test02()
{
	Person <string, int >p("nnd", 90);
	printPerson2(p);
}

int main() {
	test02();

	system("pause");
	return 0;
}

整個類別範本化

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

//類別範本
template<class NameType, class AgeType = int>
class Person
{
public:
	Person(NameType name, AgeType age)
	{
		this->mName = name;
		this->mAge = age;
	}
	void showPerson()
	{
		cout << "name: " << this->mName << " age: " << this->mAge << endl;
	}
public:
	NameType mName;
	AgeType mAge;
};

//3、整個類別範本化
template<class T>
void printPerson3(T& p)
{
	cout << "T的型別為: " << typeid(T).name() << endl;
	p.showPerson();

}
void test03()
{
	Person <string, int >p("sb", 30);
	printPerson3(p);
}

int main() {

	test03();

	system("pause");
	return 0;
}
  • 通過類別範本建立的物件,可以有三種方式向函數中進行傳參
  • 使用比較廣泛是第一種:指定傳入的型別

類別範本與繼承

當類別範本碰到繼承時,需要注意一下幾點:

  • 當子類繼承的父類別是一個類別範本時,子類在宣告的時候,要指定出父類別中T的型別
  • 如果不指定,編譯器無法給子類分配記憶體
  • 如果想靈活指定出父類別中T的型別,子類也需變為類別範本

例子

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

template<class T>
class Base{
	T m;
};

//class Son:public Base  //錯誤,c++編譯需要給子類分配記憶體,必須知道父類別中T的型別才可以向下繼承
//簡單來說,繼承需要用到父類別Base,Base是個類別範本,那就必須指定Base中的通用資料型別
class Son :public Base<int>{ //必須指定一個型別
};

void test01(){
	Son c;
}

//類別範本繼承類別範本 ,可以用T2指定父類別中的T型別
template<class T1, class T2>
class Son2 :public Base<T2>{
public:
	Son2(){
		cout << typeid(T1).name() << endl;
		cout << typeid(T2).name() << endl;
	}
	T1 obj;
};

void test02(){	
	//class T1 == int,指定Son2維護的obj為int型別
	//class T2 == char,即指定Base中的通用資料型別為char
	Son2<int, char> child1;
}

int main() {
	test01();
	test02();

	system("pause");
	return 0;
}

如果父類別是類別範本,子類需要指定出父類別中T的資料型別

類別範本分檔案編寫(以及類外實現)

單個檔案的寫法

例子,直接在單個檔案中編寫程式碼

#pragma once
#include<iostream>
using namespace std;
#include <string>

template<class T1, class T2>
class Person {
public:
	Person(T1 name, T2 age){
	this->m_Name = name;
	this->m_Age = age;
	}

	void showPerson(){
	cout << "姓名:" << this->m_Name << "年齡:" << this->m_Age << endl;
	}

	T1 m_Name;
	T2 m_Age;
};

void test01() {
	Person<string, int>p1("jk", 18);
	p1.showPerson();

}

int main() {
	test01();

	system("pause");
	return 0;
}
類別範本類外實現成員函數
#pragma once
#include<iostream>
using namespace std;
#include <string>

template<class T1, class T2>
class Person {
public:
	Person(T1 name, T2 age);

	void showPerson();

	T1 m_Name;
	T2 m_Age;
};

//類外實現
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age){
	this->m_Name = name;
	this->m_Age = age;
}

template<class T1, class T2>
void Person<T1, T2>::showPerson() {
	cout << "姓名:" << this->m_Name << "年齡:" << this->m_Age << endl;
}

void test01() {
	Person<string, int>p1("jk", 18);
	p1.showPerson();

}

int main() {
	test01();

	system("pause");
	return 0;
}

注意:加在類外實現的資料型別後面的初始化列表,裡面不要再寫class

問題

類別範本中成員函數建立時機是在呼叫階段,導致分檔案編寫時連結不到

分檔案的寫法

  • 解決方式1:直接包含.cpp原始檔
  • 解決方式2:將宣告和實現寫到同一個檔案中,並更改字尾名為.hpp,hpp是約定的名稱,並不是強制
直接包含.cpp原始檔

第一種解決方式是直接包含.cpp檔案,這要直接include整個.cpp檔案

錯誤寫法

按照以前的分檔案編寫思路:

.h檔案中要寫函數、類的宣告

.cpp檔案通過include獲取宣告並實現對應函數

例如,

person.h

#pragma once
#include<iostream>
using namespace std;
#include <string>

//宣告類別範本
template<class T1, class T2>
class Person {
public:
	Person(T1 name, T2 age);

	void showPerson();

	T1 m_Name;
	T2 m_Age;
};

person.cpp

#include "person.h"

//類外實現
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
	this->m_Name = name;
	this->m_Age = age;
}

template<class T1, class T2>
void Person<T1, T2>::showPerson() {
	cout << "姓名:" << this->m_Name << "年齡:" << this->m_Age << endl;
}

主函數

#pragma once
#include<iostream>
using namespace std;
#include <string>
//第一種解決方式:直接包含.cpp檔案
#include "person.cpp"

void test01() {
	Person<string, int>p1("jk", 18);
	p1.showPerson();
}

int main() {
	test01();

	system("pause");
	return 0;
}

像上面這樣分檔案編寫模板就會遇到問題(不涉及模板就是正確的),原因如下:

如果包含的是.h,那麼編譯器就只知道person.h中宣告的成員函數,而沒有person.cpp中的實現,肯定報錯,連結不上

正確寫法

既然導致錯誤的原因是編譯器沒有讀到person.cpp中對應的函數實現,那直接讓它讀到不就完了

因此,一種簡單粗暴的方法是:將函數的宣告和實現都寫在一塊,然後在寫有主函數的檔案中通過include匯入

實際上就是將單一檔案編寫的程式拆分了一下又合起來

person.cpp

#pragma once
#include<iostream>
using namespace std;
#include <string>

template<class T1, class T2>
class Person {
public:
	Person(T1 name, T2 age){
	this->m_Name = name;
	this->m_Age = age;
	}

	void showPerson(){
	cout << "姓名:" << this->m_Name << "年齡:" << this->m_Age << endl;
	}

	T1 m_Name;
	T2 m_Age;
};

主函數

#pragma once
#include<iostream>
using namespace std;
#include <string>
//第一種解決方式:直接包含.cpp檔案
#include "person.cpp"

void test01() {
	Person<string, int>p1("jk", 18);
	p1.showPerson();

}

int main() {
	test01();

	system("pause");
	return 0;
}

顯然,這種寫法不夠優雅

使用.hpp作為類別範本的存放檔案

這時候有小可愛就想了,那我把宣告和實現都寫在.h裡面不就優雅了嗎?

什麼你覺得還不夠優雅?那把這樣的.h檔案改名叫.hpp,以後大家都這樣寫類別範本,夠優雅了吧?

(ps:脫褲子放屁)

於是便有了下面的寫法,這也是涉及類別範本時,常用的分檔案編寫方式

person.hpp

#include <string>

template<class T1, class T2>
class Person {
public:
	Person(T1 name, T2 age);

	void showPerson();

	T1 m_Name;
	T2 m_Age;
};

//類外實現
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
	this->m_Name = name;
	this->m_Age = age;
}

template<class T1, class T2>
void Person<T1, T2>::showPerson() {
	cout << "姓名:" << this->m_Name << "年齡:" << this->m_Age << endl;
}

主函數

通過include匯入這些實現

#pragma once
#include<iostream>
using namespace std;
#include <string>
//第二種解決方式:將.h和.cpp中內容寫到一起,字尾改為.hpp
#include "person.hpp"

void test01() {
	Person<string, int>p1("jk", 18);
	p1.showPerson();
}

int main() {
	test01();
    
	system("pause");
	return 0;
}

類別範本與友元

全域性函數類內實現 - 直接在類內宣告友元即可

全域性函數類外實現 - 需要提前讓編譯器知道全域性函數的存在

全域性函數類內實現

#pragma once
#include<iostream>
using namespace std;
#include <string>

template<class T1,class T2>
class Person {
	//全域性函數,類內實現
	friend void printPerson(Person<T1, T2> &p) {
		cout << "姓名: " << p.m_Name << " 年齡:" << p.m_Age << endl;
	}

public:
	Person(T1 name,T2 age){
		this->m_Name = name;
		this->m_Age = age;
	}

private:
	T1 m_Name;
	T2 m_Age;
};

void test01() {
	Person<string, int>p1("jk", 18);
	printPerson(p1);

}

int main() {
	test01();

	system("pause");
	return 0;
}

全域性函數類外實現

#pragma once
#include<iostream>
using namespace std;
#include <string>

//2、全域性函數配合友元  類外實現 - 先做函數模板宣告,下方在做函數模板定義,再做友元
template<class T1, class T2> class Person;

//如果宣告了函數模板,可以將實現寫到後面,否則需要將實現體寫到類的前面讓編譯器提前看到
//template<class T1, class T2> void printPerson2(Person<T1, T2> & p); 

template<class T1, class T2>
void printPerson2(Person<T1, T2>& p)
{
	cout << "類外實現 ---- 姓名: " << p.m_Name << " 年齡:" << p.m_Age << endl;
}


template<class T1,class T2>
class Person {
	//全域性函數,類外實現
	// friend void printPerson2(Person<T1, T2>& p);//記得加「<>」
	// 如果類外實現,需要讓編譯器提前知道該函數的存在
	friend void printPerson2<>(Person<T1, T2>& p);

public:
	Person(T1 name,T2 age){
		this->m_Name = name;
		this->m_Age = age;
	}

private:
	T1 m_Name;
	T2 m_Age;
};

////還不能寫在這裡,必須寫在開頭讓編譯器先看見,要不然報錯
//template<class T1, class T2>
//void printPerson2(Person<T1, T2>& p) {
//	cout << "類外實現的 姓名: " << p.m_Name << " 年齡:" << p.m_Age << endl;
//}

void test02() {
	Person<string, int>p2("dk", 16);
	printPerson2(p2);
}

int main() {

	test02();

	system("pause");
	return 0;
}

總結

這裡又一次體現了C++作者對於套娃和"萬能編譯器"的喜愛

忘了傻逼的全域性函數類外實現吧(僅限涉及模板時)

就老老實實用全域性函數做類內實現就好

類別範本案例:實現通用的陣列類

案例描述

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

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

實現模式

分檔案寫法:.hpp+.cpp主函數

那麼主要的工作應該都在.hpp中完成,具體功能則在.cpp的主函數中測試

程式碼

myArray.hpp

先編寫整體架構,提供有參建構函式和解構函式

有參建構函式和解構函式
//自定義通用陣列類
#pragma once
#include<iostream>
using namespace std;
#include <string>

//定義類別範本MyArry
template<class T>
class MyArry {

public:
	//有參構造,傳入容量
	MyArry(int capacity) {
		//cout << "MyArry有參構造" << endl;
		this->m_Capacity = capacity;
		this->m_Size = 0;
		this->pAddress = new T[this->m_Capacity];
	}

	//涉及在堆中開闢空間,要寫一下解構函式
	//釋放記憶體
	~MyArry() {
		//cout << "MyArry解構函式" << endl;
		if (this->pAddress != NULL) {
			delete[] this->pAddress;
			this->pAddress = NULL;//防止野指標
		}
	}

private:
	T* pAddress;//指標指向堆區開闢的真實陣列

	int m_Capacity;//陣列容量
	int m_Size;//陣列大小
};
拷貝建構函式和過載運運算元

接下來逐步新增功能,上述程式碼已經實現了:

  • 將陣列中的資料儲存到堆區
  • 建構函式中可以傳入陣列的容量

接下來要實現:對內建資料型別以及自定義資料型別的資料進行儲存

這裡要考慮淺拷貝問題,因此可以與第四點(拷貝構造)一塊實現

關於淺拷貝問題,可以看看這篇,後續我計劃再用一篇部落格討論討論

言歸正傳

//自定義通用陣列類
#pragma once
#include<iostream>
using namespace std;
#include <string>

//定義類別範本MyArry
template<class T>
class MyArry {

public:
	//有參構造
	MyArry(int capacity) {...}
    
    //防止淺拷貝問題
	//拷貝構造
	MyArry(const MyArry& arr) {
		//cout << "MyArry拷貝構造" << endl;
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		/*this->pAddress = arr.pAddress;*/


		//按傳進來的陣列大小重新在堆區開闢空間
		//深拷貝
		this->pAddress = new T[arr.m_Capacity];

		//將arr中的資料都拷貝過來
		for (int i = 0; i < this->m_Size; i++) {
			this->pAddress[i] = arr.pAddress[i];
		}
	}
    
    //過載賦值運運算元,防止出現淺拷貝問題
    //防止寫連等號時(類似這種arr[10] = arr[3])報錯,所以返回型別是MyArry&,要對MyArry物件進行操作
	MyArry& operator=(const MyArry& arr) {
		//cout << "MyArry的operator=" << endl;
		//先判斷原來堆區是否有資料,有就先釋放
		if (this->pAddress != NULL) {
			delete[] this->pAddress;
			this->pAddress = NULL;
			this->m_Size = 0;
		}

		//深拷貝
		//按傳進來的陣列的屬性初始化新的陣列
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		this->pAddress = new T[arr.m_Capacity];

		//將arr中的資料都拷貝過來
		for (int i = 0; i < this->m_Size; i++) {
			this->pAddress[i] = arr.pAddress[i];
		}
		return *this;//返回自身
	}

	//釋放記憶體
	~MyArry() {...}

private:
	T* pAddress;//指標指向堆區開闢的真實陣列

	int m_Capacity;//陣列容量
	int m_Size;//陣列大小
};
尾插法和尾刪法CRUD

沒什麼好說的

//自定義通用陣列類
#pragma once
#include<iostream>
using namespace std;
#include <string>

//定義類別範本MyArry
template<class T>
class MyArry {

public:
	//有參構造
	MyArry(int capacity) {...}
    
	//拷貝構造
	MyArry(const MyArry& arr) {...}
    
    //過載賦值運運算元,防止出現淺拷貝問題
	MyArry& operator=(const MyArry& arr) {...}

    //尾插法
	//輸入是T型別資料,且為了防止被修改,要const修飾
	void Push_Back(const T& val) {
		//判斷容量是否等於大小
		if (this->m_Capacity == this->m_Size) {
			cout << "容量過大,拷不進來" << endl;
			return;
		}
		//往陣列最後一個位置插資料,即維護的this->m_Size
		this->pAddress[this->m_Size] = val;
		this->m_Size++;//更新陣列大小
	}
	//尾刪法
	void Pop_Back() {
		//讓使用者存取不到最後一個元素即可,邏輯刪除
		//判斷當前陣列是否還有資料
		if (this->m_Size == 0) {
			cout << "沒東西刪" << endl;
			return;
		}
		this->m_Size--;//遮蔽調對最後一個數的存取
	}
    
	//釋放記憶體
	~MyArry() {...}

private:
	T* pAddress;//指標指向堆區開闢的真實陣列

	int m_Capacity;//陣列容量
	int m_Size;//陣列大小
};
下標存取陣列中元素

以及剩下的功能

//自定義通用陣列類
#pragma once
#include<iostream>
using namespace std;
#include <string>

//定義類別範本MyArry
template<class T>
class MyArry {

public:
	//有參構造
	MyArry(int capacity) {...}
	//拷貝構造
	MyArry(const MyArry& arr) {...}
    
    //過載賦值運運算元,防止出現淺拷貝問題
	MyArry& operator=(const MyArry& arr) {...}
    //尾插法
	//輸入是T型別資料,且為了防止被修改,要const修飾
	void Push_Back(const T& val) {...}
	//尾刪法
	void Pop_Back() {...}
    
    //通過下標的方式存取陣列中的元素
	//如果呼叫完之後還想作為左值存在,即arr[0] = 100
	//返回型別應該是T的參照,返回數的本身
	T& operator[](int index) {

		//返回陣列中index出的元素
		return this->pAddress[index];
		
	}

	//獲取陣列容量
	int getCapacity()
	{
		return this->m_Capacity;
	}

	//獲取陣列大小
	int	getSize()
	{
		return this->m_Size;
	}
    
    
	//釋放記憶體
	~MyArry() {...}

private:
	T* pAddress;//指標指向堆區開闢的真實陣列

	int m_Capacity;//陣列容量
	int m_Size;//陣列大小
};
完整程式碼
//自定義通用陣列類
#pragma once
#include<iostream>
using namespace std;
#include <string>


template<class T>
class MyArry {

public:
	//有參構造
	MyArry(int capacity) {
		//cout << "MyArry有參構造" << endl;
		this->m_Capacity = capacity;
		this->m_Size = 0;
		this->pAddress = new T[this->m_Capacity];
	}

	//防止淺拷貝問題
	//拷貝構造
	MyArry(const MyArry& arr) {
		//cout << "MyArry拷貝構造" << endl;
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		/*this->pAddress = arr.pAddress;*/


		//按傳進來的陣列大小重新在堆區開闢空間
		//深拷貝
		this->pAddress = new T[arr.m_Capacity];

		//將arr中的資料都拷貝過來
		for (int i = 0; i < this->m_Size; i++) {
			this->pAddress[i] = arr.pAddress[i];
		}
	}

	//過載賦值運運算元,防止出現淺拷貝問題
	MyArry& operator=(const MyArry& arr) {//防止寫連等號時報錯,所以返回型別是MyArry&
		//cout << "MyArry的operator=" << endl;
		//先判斷原來堆區是否有資料,有就先釋放
		if (this->pAddress != NULL) {
			delete[] this->pAddress;
			this->pAddress = NULL;
			this->m_Size = 0;
		}

		//深拷貝
		//按傳進來的陣列的屬性初始化新的陣列
		this->m_Capacity = arr.m_Capacity;
		this->m_Size = arr.m_Size;
		this->pAddress = new T[arr.m_Capacity];

		//將arr中的資料都拷貝過來
		for (int i = 0; i < this->m_Size; i++) {
			this->pAddress[i] = arr.pAddress[i];
		}
		return *this;//返回自身
	}

	//尾插法
	//輸入是T型別資料,且為了防止被修改,要const修飾
	void Push_Back(const T& val) {
		//判斷容量是否等於大小
		if (this->m_Capacity == this->m_Size) {
			cout << "容量過大,拷不進來" << endl;
			return;
		}
		//往陣列最後一個位置插資料,即維護的this->m_Size
		this->pAddress[this->m_Size] = val;
		this->m_Size++;//更新陣列大小
	}

	//尾刪法
	void Pop_Back() {
		//讓使用者存取不到最後一個元素即可,邏輯刪除
		//判斷當前陣列是否還有資料
		if (this->m_Size == 0) {
			cout << "沒東西刪" << endl;
			return;
		}
		this->m_Size--;//遮蔽調對最後一個數的存取
	}


	//通過下標的方式存取陣列中的元素
	//如果呼叫完之後還想作為左值存在,即arr[0] = 100
	//返回型別應該是T的參照,返回數的本身
	T& operator[](int index) {

		//返回陣列中index出的元素
		return this->pAddress[index];
		
	}

	//獲取陣列容量
	int getCapacity()
	{
		return this->m_Capacity;
	}

	//獲取陣列大小
	int	getSize()
	{
		return this->m_Size;
	}



	//涉及在堆中開闢空間,要寫一下解構函式
	//釋放記憶體
	~MyArry() {
		//cout << "MyArry解構函式" << endl;
		if (this->pAddress != NULL) {
			delete[] this->pAddress;
			this->pAddress = NULL;//防止野指標
		}
	}

private:
	T* pAddress;//指標指向堆區開闢的真實陣列

	int m_Capacity;//陣列容量
	int m_Size;//陣列大小
};

類別範本-通用陣列類.cpp

在該類中進行呼叫測試(自定義類的就不測了,懶)

#include<iostream>
using namespace std;
#include <string>
#include "MyArray.hpp"

void printIntArray(MyArry<int>& arr){
	for (int i = 0; i < arr.getSize(); i++) {
		cout << arr[i] << " ";
	}
	cout << endl;
}

void test01() {
	MyArry<int> arr1(5);
	/*MyArry<int> arr2(arr1);
	MyArry<int> arr3(15);*/
	//arr3 = arr1;

	for (int i = 0; i < 10; i++)
	{
		arr1.Push_Back(i);//利用尾插法向陣列中插數
	}
	cout << "array1列印輸出:" << endl;
	printIntArray(arr1);
	cout << "array1的大小:" << arr1.getSize() << endl;
	cout << "array1的容量:" << arr1.getCapacity() << endl;

	cout << "--------------------------" << endl;

	MyArry<int> arr2(arr1);
	arr2.Pop_Back();
	cout << "array2列印輸出:" << endl;
	printIntArray(arr2);
	cout << "array2的大小:" << arr2.getSize() << endl;
	cout << "array2的容量:" << arr2.getCapacity() << endl;

}

int main() {
	test01();

	system("pause");
	return 0;
}