日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C/C++学习之路: 模板和异常

發布時間:2024/4/11 c/c++ 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C/C++学习之路: 模板和异常 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

C/C++學習之路: 模板和異常


目錄

  • 模板
  • 類型轉換
  • 異常

  • 1. 模板

    1. 模板概述

  • c++提供了函數模板(function template),函數模板實際上是建立一個通用函數,其函數類型和形參類型不具體制定,用一個虛擬的類型來代表,這個通用函數就成為函數模板。
  • 凡是函數體相同的函數都可以用這個模板代替,不必定義多個函數,只需在模板中定義一次即可。
  • 在調用函數時系統會根據實參的類型來取代模板中的虛擬類型,從而實現不同函數的功能。
  • c++提供兩種模板機制:函數模板和類模板
  • 類屬 - 類型參數化,又稱參數模板
  • 總結:
  • 模板把函數或類要處理的數據類型參數化,表現為參數的多態性,成為類屬。
  • 模板用于表達邏輯結構相同,但具體數據元素類型不同的數據對象的通用行為。
  • 2. 函數模板

  • 用模板是為了實現泛型,可以減輕編程的工作量,增強函數的重用性。
  • //交換int數據 void SwapInt(int& a,int& b){int temp = a;a = b;b = temp; }//交換char數據 void SwapChar(char& a,char& b){char temp = a;a = b;b = temp; } //問題:如果我要交換double類型數據,那么還需要些一個double類型數據交換的函數 //繁瑣,寫的函數越多,當交換邏輯發生變化的時候,所有的函數都需要修改,無形當中增加了代碼的維護難度//如果能把類型作為參數傳遞進來就好了,傳遞int就是Int類型交換,傳遞char就是char類型交換 //我們有一種技術,可以實現類型的參數化---函數模板//class 和 typename都是一樣的,用哪個都可以 template<class T> void MySwap(T& a,T& b){T temp = a;a = b;b = temp; }void test01(){int a = 10;int b = 20;cout << "a:" << a << " b:" << b << endl;//1. 這里有個需要注意點,函數模板可以自動推導參數的類型MySwap(a,b);cout << "a:" << a << " b:" << b << endl;char c1 = 'a';char c2 = 'b';cout << "c1:" << c1 << " c2:" << c2 << endl;//2. 函數模板可以自動類型推導,那么也可以顯式指定類型MySwap<char>(c1, c2);cout << "c1:" << c1 << " c2:" << c2 << endl; }

    3. 函數模板和普通函數區別

  • 函數模板不允許自動類型轉化
  • 普通函數能夠自動進行類型轉化
  • //函數模板 template<class T> T MyPlus(T a, T b){T ret = a + b;return ret; }//普通函數 int MyPlus(int a,char b){int ret = a + b;return ret; }void test02(){int a = 10;char b = 'a';//調用函數模板,嚴格匹配類型MyPlus(a, a);MyPlus(b, b);//調用普通函數MyPlus(a, b);//調用普通函數 普通函數可以隱式類型轉換MyPlus(b, a);//結論://函數模板不允許自動類型轉換,必須嚴格匹配類型//普通函數可以進行自動類型轉換 }

    4. 函數模板和普通函數在一起調用規則

  • c++編譯器優先考慮普通函數
  • 可以通過空模板實參列表的語法限定編譯器只能通過模板匹配
  • 函數模板可以像普通函數那樣可以被重載
  • 如果函數模板可以產生一個更好的匹配,那么選擇模板
  • //函數模板 template<class T> T MyPlus(T a, T b) {T ret = a + b;return ret; }//普通函數 int MyPlus(int a, int b) {int ret = a + b;return ret; }void test03() {int a = 10;int b = 20;char c = 'a';char d = 'b';//如果函數模板和普通函數都能匹配,c++編譯器優先考慮普通函數cout << MyPlus(a, b) << endl;//如果我必須要調用函數模板,那么怎么辦?cout << MyPlus<>(a, b) << endl;//此時普通函數也可以匹配,因為普通函數可以自動類型轉換//但是此時函數模板能夠有更好的匹配//如果函數模板可以產生一個更好的匹配,那么選擇模板cout << MyPlus(c, d); }

    5. 模板機制解析

  • 編譯器并不是把函數模板處理成能夠處理任何類型的函數
  • 函數模板通過具體類型產生不同的函數
  • 編譯器會對函數模板進行兩次編譯,在聲明的地方對模板代碼本身進行編譯,在調用的地方對參數替換后的代碼進行編譯。
  • 6. 模板的局限性

  • 假設有如下模板函數:
  • template<class T>void f(T a, T b){}
  • 如果代碼實現時定義了賦值操作 a = b,但是T為數組,這種假設就不成立了
  • 同樣,如果里面的語句為判斷語句 if(a>b),但T如果是結構體,該假設也不成立,另外如果是傳入的數組,數組名為地址,因此它比較的是地址,而這也不是我們所希望的操作。
  • 總之,編寫的模板函數很可能無法處理某些類型,另一方面,有時候通用化是有意義的,但C++語法不允許這樣做。為了解決這種問題,可以提供模板的重載,為這些特定的類型提供具體化的模板。
  • 7. 類模板

  • 類模板和函數模板的定義和使用類似,有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。
  • 類模板用于實現類所需數據的類型參數化
  • 1. 類模板做函數參數

    template<class NameType, class AgeType> 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() {//Person P1("德瑪西亞",18); // 類模板不能進行類型自動推導 Person<string, int> P1("德瑪西亞", 18);P1.showPerson(); }

    2. 類模板派生普通類

    template<class T> class MyClass { public:MyClass(T property) {this->mProperty = property;}public:T mProperty; };//子類實例化的時候需要具體化的父類,子類需要知道父類的具體類型是什么樣的 //這樣c++編譯器才能知道給子類分配多少內存//普通派生類 class SubClass : public MyClass<int> { public:SubClass(int b) : MyClass<int>(20) {this->mB = b;}public:int mB; };

    3. 類模板派生類模板

    template<class T> class Base {T m; };template<class T> class Child2 : public Base<double> { //繼承類模板的時候,必須要確定基類的大小 public:T mParam; };void test2() {Child2<int> d2; }

    4. 類模板類內實現

    template<class NameType, class AgeType> 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() {//Person P1("德瑪西亞",18); // 類模板不能進行類型自動推導 Person<string, int> P1("德瑪西亞", 18);P1.showPerson(); }

    5. 類模板類外實現

    template<class T1, class T2> class Person { public:Person(T1 name, T2 age);void showPerson();public:T1 mName;T2 mAge; };//類外實現 template<class T1, class T2> Person<T1, T2>::Person(T1 name, T2 age) {this->mName = name;this->mAge = age; }template<class T1, class T2> void Person<T1, T2>::showPerson() {cout << "Name:" << this->mName << " Age:" << this->mAge << endl; }void test4() {Person<string, int> p("Obama", 20);p.showPerson(); }

    6. 模板類遇到友元函數

    template<class T1, class T2> class Person;//告訴編譯器這個函數模板是存在 template<class T1, class T2> void PrintPerson2(Person<T1, T2> &p);//友元函數在類內實現 template<class T1, class T2> class Person {//1. 友元函數在類內實現friend void PrintPerson(Person<T1, T2> &p) {cout << "Name:" << p.mName << " Age:" << p.mAge << endl;}//2.友元函數類外實現//告訴編譯器這個函數模板是存在friend void PrintPerson2<>(Person<T1, T2> &p);//3. 類模板碰到友元函數模板template<class U1, class U2>friend void PrintPerson(Person<U1, U2> &p);public:Person(T1 name, T2 age) {this->mName = name;this->mAge = age;}void showPerson() {cout << "Name:" << this->mName << " Age:" << this->mAge << endl;}private:T1 mName;T2 mAge; };void test01() {Person<string, int> p("Jerry", 20);PrintPerson(p); }// 類模板碰到友元函數 //友元函數類外實現 加上<>空參數列表,告訴編譯去匹配函數模板 template<class T1, class T2> void PrintPerson2(Person<T1, T2> &p) {cout << "Name2:" << p.mName << " Age2:" << p.mAge << endl; }void test02() {Person<string, int> p("Jerry", 20);PrintPerson2(p); //不寫可以編譯通過,寫了之后,會找PrintPerson2的普通函數調用,因為寫了普通函數PrintPerson2的聲明 }int main() {//test01();test02();system("pause");return EXIT_SUCCESS; }

    2. 類型轉換

  • 類型轉換(cast)是將一種數據類型轉換成另一種數據類型。
  • 例如,如果將一個整型值賦給一個浮點類型的變量,編譯器會暗地里將其轉換成浮點類型。
  • 轉換是非常有用的,但是它也會帶來一些問題,比如在轉換指針時,我們很可能將其轉換成一個比它更大的類型,但這可能會破壞其他的數據。
  • 所以應該小心類型轉換,因為轉換也就相當于對編譯器說:忘記類型檢查,把它看做其他的類型。
  • 一般情況下,盡量少的去使用類型轉換,除非用來解決非常特殊的問題。
  • 標準c++提供了一個顯示的轉換的語法,來替代舊的C風格的類型轉換。
  • 使用C風格的強制轉換可以把想要的任何東西轉換成我們需要的類型。那為什么還需要一個新的C++類型的強制轉換呢?
  • 新類型的強制轉換可以提供更好的控制強制轉換過程,允許控制各種不同種類的強制轉換。C++風格的強制轉換其他的好處是,它們能更清晰的表明它們要干什么。程序員只要掃一眼這樣的代碼,就能立即知道一個強制轉換的目的。
  • 1. 靜態轉換(static_cast)

  • 用于類層次結構中基類(父類)和派生類(子類)之間指針或引用的轉換。
  • 進行上行轉換(把派生類的指針或引用轉換成基類表示)是安全的;
  • 進行下行轉換(把基類指針或引用轉換成派生類表示)時,由于沒有動態類型檢查,所以是不安全的。
  • 用于基本數據類型之間的轉換,如把int轉換成char,把char轉換成int。這種轉換的安全性也要開發人員來保證。
  • class Animal { };class Dog : public Animal { };class Other { };//基礎數據類型轉換 void test01() {char a = 'a';double b = static_cast<double>(a); }//繼承關系指針互相轉換 void test02() {//繼承關系指針轉換Animal *animal01 = NULL;Dog *dog01 = NULL;//子類指針轉成父類指針,安全Animal *animal02 = static_cast<Animal *>(dog01);//父類指針轉成子類指針,不安全Dog *dog02 = static_cast<Dog *>(animal01); }//繼承關系引用相互轉換 void test03() {Animal ani_ref;Dog dog_ref;//繼承關系指針轉換Animal &animal01 = ani_ref;Dog &dog01 = dog_ref;//子類指針轉成父類指針,安全Animal &animal02 = static_cast<Animal &>(dog01);//父類指針轉成子類指針,不安全Dog &dog02 = static_cast<Dog &>(animal01); }//無繼承關系指針轉換 void test04() {Animal *animal01 = NULL;Other *other01 = NULL;//轉換失敗//Animal* animal02 = static_cast<Animal*>(other01); }

    2. 動態轉換

  • dynamic_cast主要用于類層次間的上行轉換和下行轉換;
  • 在類層次間進行上行轉換時,dynamic_cast和static_cast的效果是一樣的;
  • 在進行下行轉換時,dynamic_cast具有類型檢查的功能,比static_cast更安全;
  • class Animal { public:virtual void ShowName() = 0; };class Dog : public Animal {virtual void ShowName() {cout << "I am a dog!" << endl;} };class Other { public:void PrintSomething() {cout << "我是其他類!" << endl;} };//普通類型轉換 void test01() {//不支持基礎數據類型int a = 10;//double a = dynamic_cast<double>(a); }//繼承關系指針 void test02() {Animal *animal01 = NULL;Dog *dog01 = new Dog;//子類指針轉換成父類指針 可以Animal *animal02 = dynamic_cast<Animal *>(dog01);animal02->ShowName();//父類指針轉換成子類指針 不可以//Dog* dog02 = dynamic_cast<Dog*>(animal01); }//繼承關系引用 void test03() {Dog dog_ref;Dog &dog01 = dog_ref;//子類引用轉換成父類引用 可以Animal &animal02 = dynamic_cast<Animal &>(dog01);animal02.ShowName(); }//無繼承關系指針轉換 void test04() {Animal *animal01 = NULL;Other *other = NULL;//不可以//Animal* animal02 = dynamic_cast<Animal*>(other); }

    3. 常量轉換(const_cast)

  • const_cast運算符用來修改類型的const屬性。
  • 常量指針被轉化成非常量指針,并且仍然指向原來的對象;
  • 常量引用被轉換成非常量引用,并且仍然指向原來的對象;
  • 注意:不能直接對非指針和非引用的變量使用const_cast操作符去直接移除它的const.
  • //常量指針轉換成非常量指針 void test01() {const int *p = NULL;int *np = const_cast<int *>(p);int *pp = NULL;const int *npp = const_cast<const int *>(pp);const int a = 10; //不能對非指針或非引用進行轉換//int b = const_cast<int>(a); }//常量引用轉換成非常量引用 void test02() {int num = 10;int &refNum = num;const int &refNum2 = const_cast<const int &>(refNum); }

    4. 重新解釋轉換(reinterpret_cast)

  • 這是最不安全的一種轉換機制,最有可能出問題。
  • 主要用于將一種數據類型從一種類型轉換為另一種類型。
  • 它可以將一個指針轉換成一個整數,也可以將一個整數轉換成一個指針。

  • 3. 異常

    1. 異常基本概念

  • 異常處理就是處理程序中的錯誤。所謂錯誤是指在程序運行的過程中發生的一些異常事件(如:除0溢出,數組下標越界,所要讀取的文件不存在,空指針,內存不足等等)
  • c++異常機制相比C語言異常處理的優勢?
  • 函數的返回值可以忽略,但異常不可忽略。如果程序出現異常,但是沒有被捕獲,程序就會終止,這多少會促使程序員開發出來的程序更健壯一點。而如果使用C語言的error宏或者函數返回值,調用者都有可能忘記檢查,從而沒有對錯誤進行處理,結果造成程序莫名其面的終止或出現錯誤的結果。
  • 整型返回值沒有任何語義信息。而異常卻包含語義信息,有時你從類名就能夠體現出來。
  • 整型返回值缺乏相關的上下文信息。異常作為一個類,可以擁有自己的成員,這些成員就可以傳遞足夠的信息。
  • 異常處理可以在調用跳級。這是一個代碼編寫時的問題:假設在有多個函數的調用棧中出現了某個錯誤,使用整型返回碼要求你在每一級函數中都要進行處理。而使用異常處理的棧展開機制,只需要在一處進行處理就可以了,不需要每級函數都處理。
  • //如果判斷返回值,那么返回值是錯誤碼還是結果? //如果不判斷返回值,那么b==0時候,程序結果已經不正確 //A寫的代碼 int A_MyDivide(int a, int b) {if (b == 0) {return -1;}return a / b; }//B寫的代碼 int B_MyDivide(int a, int b) {int ba = a + 100;int bb = b;int ret = A_MyDivide(ba, bb); //由于B沒有處理異常,導致B結果運算錯誤return ret; }//C寫的代碼 int C_MyDivide() {int a = 10;int b = 0;int ret = B_MyDivide(a, b); //更嚴重的是,由于B沒有繼續拋出異常,導致C的代碼沒有辦法捕獲異常if (ret == -1) {return -1;} else {return ret;} }//所以,我們希望: //1.異常應該捕獲,如果你捕獲,可以,那么異常必須繼續拋給上層函數,你不處理,不代表你的上層不處理 //2.這個例子,異常沒有捕獲的結果就是運行結果錯的一塌糊涂,結果未知,未知的結果程序沒有必要執行下去

    2. 異常語法

    int A_MyDivide(int a, int b) {if (b == 0) {throw 0;}return a / b; }//B寫的代碼 B寫代碼比較粗心,忘記處理異常 int B_MyDivide(int a, int b) {int ba = a;int bb = b;int ret = A_MyDivide(ba, bb) + 100; //由于B沒有處理異常,導致B結果運算錯誤return ret; }//C寫的代碼 int C_MyDivide() {int a = 10;int b = 0;int ret = 0;//沒有處理異常,程序直接中斷執行 #if 1ret = B_MyDivide(a, b);//處理異常 #elsetry{ret = B_MyDivide(a, b); //更嚴重的是,由于B沒有繼續拋出異常,導致C的代碼沒有辦法捕獲異常}catch (int e){cout << "C_MyDivide Call B_MyDivide 除數為:" << e << endl;} #endifreturn ret; }int main() {C_MyDivide();system("pause");return EXIT_SUCCESS; }
  • 若有異常則通過throw操作創建一個異常對象并拋出。
  • 將可能拋出異常的程序段放到try塊之中。
  • 如果在try段執行期間沒有引起異常,那么跟在try后面的catch字句就不會執行。
  • catch子句會根據出現的先后順序被檢查,匹配的catch語句捕獲并處理異常(或繼續拋出異常)
  • 如果匹配的處理未找到,則運行函數terminate將自動被調用,其缺省功能調用abort終止程序。
  • 處理不了的異常,可以在catch的最后一個分支,使用throw,向上拋。
  • c++異常處理使得異常的引發和異常的處理不必在一個函數中,這樣底層的函數可以著重解決具體問題,而不必過多的考慮異常的處理。上層調用者可以在適當的位置設計對不同類型異常的處理。
  • 1. 異常嚴格類型匹配

  • 異常機制和函數機制互不干涉,但是捕捉方式是通過嚴格類型匹配。
  • void TestFunction() {cout << "開始拋出異常..." << endl;//throw 10; //拋出int類型異常//throw 'a'; //拋出char類型異常//throw "abcd"; //拋出char*類型異常string ex = "string exception!";throw ex;}int main() {try {TestFunction();}catch (int) {cout << "拋出Int類型異常!" << endl;}catch (char) {cout << "拋出Char類型異常!" << endl;}catch (char *) {cout << "拋出Char*類型異常!" << endl;}catch (string) {cout << "拋出string類型異常!" << endl;}//捕獲所有異常catch (...) {cout << "拋出其他類型異常!" << endl;}system("pause");return EXIT_SUCCESS; }

    2. 棧解旋(unwinding)

  • 異常被拋出后,從進入try塊起,到異常被拋擲前,這期間在棧上構造的所有對象,都會被自動析構。析構的順序與構造的順序相反,這一過程稱為棧的解旋(unwinding).
  • class Person { public:Person(string name) {mName = name;cout << mName << "對象被創建!" << endl;}~Person() {cout << mName << "對象被析構!" << endl;}public:string mName; };void TestFunction() {Person p1("aaa");Person p2("bbb");Person p3("ccc");//拋出異常throw 10; }int main() {try {TestFunction();}catch (...) {cout << "異常被捕獲!" << endl;}system("pause");return EXIT_SUCCESS; }

    3. 異常接口聲明

  • 為了加強程序的可讀性,可以在函數聲明中列出可能拋出異常的所有類型,例如:void func() throw(A,B,C);這個函數func能夠且只能拋出類型A,B,C及其子類型的異常。
  • 如果在函數聲明中沒有包含異常接口聲明,則此函數可以拋任何類型的異常,例如:void func()
  • 一個不拋任何類型異常的函數可聲明為:void func() throw()
  • 如果一個函數拋出了它的異常接口聲明所不允許拋出的異常,unexcepted函數會被調用,該函數默認行為調用terminate函數中斷程序。
  • //可拋出所有類型異常 void TestFunction01() {throw 10; }//只能拋出int char char*類型異常 void TestFunction02() throw(int, char, char *) {string exception = "error!";throw exception; }//不能拋出任何類型異常 void TestFunction03() throw() {throw 10; }int main() {try {//TestFunction01();//TestFunction02();//TestFunction03();}catch (...) {cout << "捕獲異常!" << endl;}system("pause");return EXIT_SUCCESS; }

    4. 異常變量生命周期

  • throw的異常是有類型的,可以是數字、字符串、類對象。
  • throw的異常是有類型的,catch需嚴格匹配異常類型。
  • class MyException { public:MyException() {cout << "異常變量構造" << endl;};MyException(const MyException &e) {cout << "拷貝構造" << endl;}~MyException() {cout << "異常變量析構" << endl;} };void DoWork() {throw new MyException(); //test1 2都用 throw MyExecption(); }void test01() {try {DoWork();}catch (MyException e) {cout << "捕獲 異常" << endl;} }void test02() {try {DoWork();}catch (MyException &e) {cout << "捕獲 異常" << endl;} }void test03() {try {DoWork();}catch (MyException *e) {cout << "捕獲 異常" << endl;delete e;} }

    5. 異常的多態使用

    //異常基類 class BaseException { public:virtual void printError() {}; };//空指針異常 class NullPointerException : public BaseException { public:virtual void printError() {cout << "空指針異常!" << endl;} };//越界異常 class OutOfRangeException : public BaseException { public:virtual void printError() {cout << "越界異常!" << endl;} };void doWork() {throw NullPointerException(); }void test() {try {doWork();}catch (BaseException &ex) {ex.printError();} }

    總結

    以上是生活随笔為你收集整理的C/C++学习之路: 模板和异常的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。