生活随笔
收集整理的這篇文章主要介紹了
C/C++学习之路: 模板和异常
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
C/C++學習之路: 模板和異常
目錄
模板 類型轉換 異常
1. 模板
1. 模板概述
c++提供了函數模板(function template),函數模板實際上是建立一個通用函數,其函數類型和形參類型不具體制定,用一個虛擬的類型來代表,這個通用函數就成為函數模板。 凡是函數體相同的函數都可以用這個模板代替,不必定義多個函數,只需在模板中定義一次即可。 在調用函數時系統會根據實參的類型來取代模板中的虛擬類型,從而實現不同函數的功能。 c++提供兩種模板機制:函數模板和類模板 類屬 - 類型參數化,又稱參數模板 總結: 模板把函數或類要處理的數據類型參數化,表現為參數的多態性,成為類屬。 模板用于表達邏輯結構相同,但具體數據元素類型不同的數據對象的通用行為。
2. 函數模板
用模板是為了實現泛型,可以減輕編程的工作量,增強函數的重用性。
void SwapInt ( int & a
, int & b
) { int temp
= a
; a
= b
; b
= temp
;
}
void SwapChar ( char & a
, char & b
) { char temp
= a
; a
= b
; b
= temp
;
}
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
; MySwap ( a
, b
) ; cout
<< "a:" << a
<< " b:" << b
<< endl
; char c1
= 'a' ; char c2
= 'b' ; cout
<< "c1:" << c1
<< " c2:" << c2
<< endl
; 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' ; 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
< string
, int > P1 ( "德瑪西亞" , 18 ) ; P1
. showPerson ( ) ;
}
2. 類模板派生普通類
template < class T >
class MyClass {
public : MyClass ( T property
) { this -> mProperty
= property
; } public : T mProperty
;
} ;
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
< 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 { friend void PrintPerson ( Person
< T1
, T2
> & p
) { cout
<< "Name:" << p
. mName
<< " Age:" << p
. mAge
<< endl
; } friend void PrintPerson2
< > ( Person
< T1
, T2
> & p
) ; 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
) ;
} int main ( ) { 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 ;
}
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 ;
}
void test02 ( ) { Animal
* animal01
= NULL ; Dog
* dog01
= new Dog
; Animal
* animal02
= dynamic_cast < Animal
* > ( dog01
) ; animal02
-> ShowName ( ) ;
}
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 ;
}
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 ;
}
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宏或者函數返回值,調用者都有可能忘記檢查,從而沒有對錯誤進行處理,結果造成程序莫名其面的終止或出現錯誤的結果。 整型返回值沒有任何語義信息。而異常卻包含語義信息,有時你從類名就能夠體現出來。 整型返回值缺乏相關的上下文信息。異常作為一個類,可以擁有自己的成員,這些成員就可以傳遞足夠的信息。 異常處理可以在調用跳級。這是一個代碼編寫時的問題:假設在有多個函數的調用棧中出現了某個錯誤,使用整型返回碼要求你在每一級函數中都要進行處理。而使用異常處理的棧展開機制,只需要在一處進行處理就可以了,不需要每級函數都處理。
int A_MyDivide ( int a
, int b
) { if ( b
== 0 ) { return - 1 ; } return a
/ b
;
}
int B_MyDivide ( int a
, int b
) { int ba
= a
+ 100 ; int bb
= b
; int ret
= A_MyDivide ( ba
, bb
) ; return ret
;
}
int C_MyDivide ( ) { int a
= 10 ; int b
= 0 ; int ret
= B_MyDivide ( a
, b
) ; if ( ret
== - 1 ) { return - 1 ; } else { return ret
; }
}
2. 異常語法
int A_MyDivide ( int a
, int b
) { if ( b
== 0 ) { throw 0 ; } return a
/ b
;
}
int B_MyDivide ( int a
, int b
) { int ba
= a
; int bb
= b
; int ret
= A_MyDivide ( ba
, bb
) + 100 ; return ret
;
}
int C_MyDivide ( ) { int a
= 10 ; int b
= 0 ; int ret
= 0 ;
# if 1 ret
= B_MyDivide ( a
, b
) ;
# else try { ret
= B_MyDivide ( a
, b
) ; } catch ( int e
) { cout
<< "C_MyDivide Call B_MyDivide 除數為:" << e
<< endl
; }
# endif return 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
; 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 ;
}
void TestFunction02 ( ) throw ( int , char , char * ) { string exception
= "error!" ; throw exception
;
}
void TestFunction03 ( ) throw ( ) { throw 10 ;
} int main ( ) { try { } 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 ( ) ;
} 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++学习之路: 模板和异常 的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔 網站內容還不錯,歡迎將生活随笔 推薦給好友。