一 模板
1.1 模板概論
以下圖為例子,提供了三個西裝的證件照,誰都可以取拍照,可以是小孩,男女人,也可以是某些動物等等等。n那么我們這個模板也是這樣,它可以是任何類型,基礎類型,class型,等等等等。且會根據你的指定類型編程相對類型(配對)
模板的特點:
模板不可以直接使用,它只是一個框架
模板的通用并不是萬能的
1.2 函數模板
1.2.1 函數模板概念及應用
語法:
template<typename T>
//函數聲明或定義
解釋:
template --- 聲明創建模板
typename --- 表面其后面的符號是一種數據類型,可以用class代替
T --- 通用的數據類型,名稱可以替換,通常為大寫字母
代碼示例:
此定義了兩個int的變量賦值成功了,這時的T就成為了int類型的
#include <iostream>
using namespace std;template <typename T>
int test(T &a, T &b)
{return a + b;
}int main(int argc, char const *argv[])
{// 原代碼傳遞的是臨時常量,而函數模板參數是引用,需要傳遞變量int num1 = 1;int num2 = 2;// test(num1, num2);// 原函數模板要求兩個參數類型一致,這里將 num2 轉換為 int 類型以匹配參數列表cout << test(num1, num2) << endl;return 0;
}
?注意事項:
函數模板 會編譯兩次:
第一次:是對函數模板本身編譯
第二次:函數調用處將T的類型具體化
函數模板目標:模板是為了實現泛型,可以減輕編程的工作量,提高函數的重用性
1.2.2 函數模板重載
重載,即在同一作用域下,函數名相同,類型or參數什么的不同。
// 函數模板重載
template <typename T>
void printer(T a, T b)
{cout << "a=" << a << " b=" << b << endl;cout << "2個參數函數模板執行" << endl;
}
// 這樣重新聲明模板類型T,因為一次聲明只對當前函數生效
template <typename T>
void printer(T a)
{cout << "a=" << a << endl;cout << "1個參數函數模板執行" << endl;
}
int main()
{int a = 10;int b = 20;printer(a);printer(a,b);
}
1.2.3 函數模板與普通函數區別
普通函數調用時可以發生自動類型轉換(隱式類型轉換)
函數模板調用時,如果利用自動類型推導,不會發生隱式類型轉換
如果利用顯示指定類型的方式,可以發生隱式類型轉換
總結:建議使用顯示指定類型的方式,調用函數模板,因為可以自己確定通用類型T
#include <iostream>
using namespace std;// 普通函數
int myAdd01(int a, int b)
{return a + b;
}// 函數模板
template <class T>
T myAdd02(T a, T b)
{return a + b;
}// 使用函數模板時,如果用自動類型推導,不會發生自動類型轉換,即隱式類型轉換
void test01()
{int a = 10;int b = 20;char c = 'c';cout << myAdd01(a, c) << endl; // 正確,將char類型的'c'隱式轉換為int類型 'c' 對應 ASCII碼 99// myAdd02(a, c); // 報錯,使用自動類型推導時,不會發生隱式類型轉換myAdd02<int>(a, c); // 正確,如果用顯示指定類型,可以發生隱式類型轉換
}int main(int argc, char const *argv[])
{test01();system("pause");return 0;
}
1.3 類模板
1.3.1 類模板基本概念
類模板作用:
建立一個通用類,類中的成員 數據類型可以不具體制定,用一個虛擬的類型來代表。
語法:
template<typename T>
//類
解釋:
template --- 聲明創建模板
typename --- 表面其后面的符號是一種數據類型,可以用class代替
T --- 通用的數據類型,名稱可以替換,通常為大寫字母
示例:
#include <iostream>
using namespace std;// 函數模板
template <class Name, class Age>
class demo
{
public:demo(Name name, Age age){this.name = name;this.age = age;}~demo();
};int main(int argc, char const *argv[])
{system("pause");return 0;
}
1.3.2 類模板成員函數類外實現
1.3.3?類模板做函數參數
學習目標:
類模板實例化出的對象,向函數傳參的方式
一共有三種傳入方式:
指定傳入的類型 --- 直接顯示對象的數據類型
參數模板化 --- 將對象中的參數變為模板進行傳遞
整個類模板化 --- 將這個對象類型 模板化進行傳遞
總結:
通過類模板創建的對象,可以有三種方式向函數中進行傳參
使用比較廣泛是第一種:指定傳入的類型
?
#include <iostream>
#include <string>
using namespace std;
// 類模板
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("孫悟空", 100);printPerson1(p);
}// 2、參數模板化
template <class T1, class T2>
void printPerson2(Person<T1, T2> &p)
{p.showPerson();std::string typeName1 = "未知類型";std::string typeName2 = "未知類型";if (typeid(T1) == typeid(std::string)){typeName1 = "std::string";}if (typeid(T2) == typeid(int)){typeName2 = "int";}cout << "T1的類型為: " << typeName1 << endl;cout << "T2的類型為: " << typeName2 << endl;
}
void test02()
{Person<string, int> p("豬八戒", 90);printPerson2(p);
}// 3、整個類模板化
template <class T>
void printPerson3(T &p)
{std::string typeName = "未知類型";if (typeid(T) == typeid(Person<std::string, int>)){typeName = "Person<std::string, int>";}cout << "T的類型為: " << typeName << endl;p.showPerson();
}
void test03()
{Person<string, int> p("唐僧", 30);printPerson3(p);
}int main()
{test01();test02();test03();system("pause");return 0;
}
1.3.4?類模板與繼承
當類模板碰到繼承時,需要注意一下幾點:
當子類繼承的父類是一個類模板時,子類在聲明的時候,要指定出父類中T的類型
如果不指定,編譯器無法給子類分配內存
如果想靈活指定出父類中T的類型,子類也需變為類模板
1.3.4.1 普通類繼承
#include <iostream>
#include <string>
using namespace std;
// 父類模板
template <class T>
class Base
{T m;
};// class Son:public Base //錯誤,c++編譯需要給子類分配內存,必須知道父類中T的類型才可以向下繼承
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;}
};void test02()
{Son2<int, char> child1;
}int main()
{test01();test02();system("pause");return 0;
}
1.3.6 類模板頭文件和源文件分離問題
學習目標:
掌握類模板成員函數分文件編寫產生的問題以及解決方式
問題:
類模板中成員函數創建時機是在調用階段,導致分文件編寫時鏈接不到
解決:
解決方式1:直接包含.cpp源文件
解決方式2:將聲明和實現寫到同一個文件中,并更改后綴名為.hpp,hpp是約定的名稱,并不是強制
推薦寫hpp文件
#pragma once
#include <iostream>
using namespace std;
#include <string>template<class T1, class T2>
class Person {
public:Person(T1 name, T2 age);void showPerson();
public: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;
1.3.7 友元類模板
直接舉例說明,不做過多描述
template <class T1,class T2>
class Demo1
{template <class T3, class T4> friend void test1(Demo1<T3, T4> obj);
private:T1 a;T2 b;
public:Demo1(T1 a, T2 b){this->a = a;this->b = b;}
};
// 全局函數模板
template <class T3, class T4>
void test1(Demo1<T3, T4> obj)
{cout << obj.a << " " << obj.b << endl;
}
1.3.7.1?全局函數模板作為類模板的友元
template <class T1,class T2>
class Demo1
{template <class T3, class T4> friend void test1(Demo1<T3, T4> obj);
private:T1 a;T2 b;
public:Demo1(T1 a, T2 b){this->a = a;this->b = b;}
};
// 全局函數模板
template <class T3, class T4>
void test1(Demo1<T3, T4> obj)
{cout << obj.a << " " << obj.b << endl;
}
1.3.7.2?類中成員函數作為類模板的友元
template <class T1, class T2>
class Demo1;class Demo2
{
public:template<class T5, class T6>void test2(Demo1<T5, T6> obj){cout << "類中函數模板打印:" << obj.a << " " << obj.b << endl;}
};template <class T1,class T2>
class Demo1
{template<class T5, class T6> friend void Demo2::test2(Demo1<T5, T6> obj);
private:T1 a;T2 b;
public:Demo1(T1 a, T2 b){this->a = a;this->b = b;}
};
1.3.7.3?普通函數作為類模板的友元
template <class T1,class T2>
class Demo1
{friend void test2(Demo1<int, int> obj);
private:T1 a;T2 b;
public:Demo1(T1 a, T2 b){this->a = a;this->b = b;}
};void test2(Demo1<int,int> obj)
{cout << "普通全局函數打印:" << obj.a << " " << obj.b << endl;
}
1.4 綜合練習
設計一個數組模板類(MyArray),完成對不同類型元素的管理。不僅能操作基本數據類型,還可以操作自定義數據類型
還是一個動態數組,可以擴容等操作
二 泛型(與Java對比)?
詳述在之前寫的文章,感興趣的同學們,可以去看看。
zz?????????????Java 自定義異常(案例)throws與throw的區別_throw的例子分數不合法-CSDN博客
Java 異常處理之Throwable的常用成員方法的概述_throwable getmessage get detail-CSDN博客
Java 自定義異常(案例)throws與throw的區別_throw的例子分數不合法-CSDN博客
三 異常處理
3.1 基本語法
// 寫一個計算兩個數相除的算法
int division(int a, int b)
{if (b == 0){// 對于函數的返回值是可以忽略的,但是異常不可以//return -1; // -1表示參數有問題throw - 1; // 拋出異常,throw是關鍵字,-1是異常值,異常值可以隨便寫// 如果拋出異常,不會返回-1,而是該函數直接在這個地方結束,進入到catch}return a / b; // 否則就返回正常的除法結果
}
void fun1()
{int a = 10;int b = 0;// 拋出異常,就要捕獲異常,否則程序將直接崩潰int res;try{// 把會拋出異常的語句放到try中res = division(a, b);}catch (int e)// 異常值類型 異常值{//并要在catch中處理捕獲到異常以后要做的事情res = 0;cerr << "int類型異常" << endl;}// 當然也可以寫多個catch塊catch (char e){res = 0;cerr << "char類型異常" << endl;}catch (...) // 捕獲其他所有類型異常{res = 0;cerr << "其他類型異常" << endl;}int res1 = res * 100;cout << "(10/0)*100=" << res1 << endl;
}
3.2 棧解旋(unwinding)
從進入 try 塊起,到異常被拋擲前,這期間在棧上構造的所有對象,都會被自動析構。析構的順序與構造的順序相反,這一過程稱為棧的解旋(unwinding).
class Demo
{
public:int a;
public:Demo(){cout << "無參構造" << endl;}Demo(int a){this->a = a;cout << "有參構造;a=" << this->a << endl;}~Demo(){cout << "析構函數;a=" << this->a << endl;}
};void fun1()
{try{Demo ob1(10);Demo ob2(20);Demo ob3(30);// 拋出異常時,當前程序就會自動進入catch處理異常,因此要將局部變量 ob3 ob2 ob1依次釋放throw 1;}catch (int){cout << "int類型異常" << endl;}catch (...){cout << "其他類型異常" << endl;}}
2.3 函數不能拋出異常- 了解
// 使用 noexcept 規格來指定函數是否可以拋出異常
// 該函數不允許拋出異常
void test04()noexcept(true)
{throw 1;
}
// 該函數可以拋出異常
void test05()noexcept(false)
{throw 1;
}
void fun1()
{try{test05();}catch (int){cout << "int類型異常" << endl;}catch (char){cout << "char類型異常" << endl;}catch (const char *){cout << "const char *類型異常" << endl;}catch (float){cout << "float類型異常" << endl;}catch (...){cout << "其他類型異常" << endl;}
}
異常接口說明在不同的編譯器,編譯結果是不一樣的。
3.4 異常的多態使用
// 定義一個基本的父類異常類
class BaseException
{
public:virtual void printExceptionMsg() {};
};
// 定義一個空指針異常,繼承于BaseException
class NullPointerException :public BaseException
{
public:void printExceptionMsg(){cout << "null pointer exception!" << endl;}
};
// 定義一個數組下標越界異常,繼承于BaseException
class IndexOutOfException :public BaseException
{
public:void printExceptionMsg(){cout << " Index out of range!" << endl;}
};
void fun1()
{try{throw IndexOutOfException();}// 如果要處理多種異常,沒必要在這個地方寫N多個catch// 讓它們繼承于同一個父類,然后在這個地方用父類引用接收不同子類對象就可以// 這就是之前講過的多態catch (BaseException &e){e.printExceptionMsg();}
}