【模板】
-
除了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;
}
類别範本與函式模板區別
類别範本與函式模板區別主要有兩點:
- 類别範本沒有自動型別推導的使用方式
- 類别範本在模板引數列表中可以有預設引數
總結
類别範本和函式模板語法相似,在宣告模板template後面加類,此類稱為類别範本
類别範本物件做函式引數
類别範本例項化出的物件,作為引數傳向函式時,一共有三種傳入方式:
- 指定傳入的型別 --- 直接顯示物件的資料型別
- 引數模板化 --- 將物件中的引數變為模板進行傳遞
- 整個類别範本化 --- 將這個物件型別 模板化進行傳遞
指定傳入的型別
#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;
}