類型轉換(cast)是將一種數類型轉成另一種數類型。例如,將一個整型值賦值給一個浮點型的變量,編譯器會暗地裡將其轉換成浮點型。
轉換是非堂有用的。但是它也會帶來一些問題,比在轉指計時,我們很可能將其轉一個比它大的類型·但這可能破壞其他的數據。該小心轉換.因為轉換也就相當於對編譯器說:忘記類型檢查,把它看做其他的類型。
一般況下,儘量少的去使用類型轉換,除非用解決非常特殊的問題。
標準c++提供一個顯示的轉的法,來替代舊的C風格的類型轉換。
使用C風格的強制轉可以把想要的任何東西轉成我們需要的型·那為什麼還需
要一個新的c++類型來強制轉化呢?
新類型的強制換船可以提供更妤的控轉換的過程。允許控制種不同種類的強制轉換。C++風格的強制轉其他的好處是,它們能清晰的表明它們要幹什麼。程式設計師只要掃一眼這樣的代碼,就立即知道一個強制轉換的目的。
7.1 靜態轉換(static_cast)用於類層次適構中基類(父)和派生(子)之間指針或引用的轉換。
用於基本數據類型之間的轉換,如把int成char,把char轉轉成int.這種轉換的安全性也要開發人來保證。
基本類型轉換:
char a = 'c';
double d = static_cast<double>(a);
cout << d << endl;自定義數據類型轉換:
Base* base = nullptr;
Son* son = nullptr;
Other* ot = nullptr;
// 將父類轉換為子類, 不安全
Son* son2 = static_cast<Son*>(base);
// 將子類轉換為父類,向上轉為,安全
Base* base2 = static_cast<Base*>(son);
// 將base轉換為other
// 沒有父子關係之間的類型,無法轉換成功。
// Other* oth2 = static_cast<Other*>(base);7.2 動態類型轉換(dynamic_cast)dynamic-cast主要用於類層之間的上行和下行轉換。
// 不支持內置類型轉換
// char c = 'c';
// double d = dynamic_cast<double>(c);
Base* base = nullptr;
Son* son = nullptr;
Other* ot = nullptr;
// 不安全,轉換不成功
// Son* son2 = dynamic_cast<Son*>(base);
// 將子類轉換為父類,向上轉為,安全
Base* base2 = dynamic_cast<Base*>(son);
// 將base轉換為other
// 沒有父子關係之間的類型,無法轉換成功。
// Other* oth2 = static_cast<Other*>(base
Base* base3 = new Son();
// Son* son3 = dynamic_cast<Son*>(base3);dynamic_cast不支持內置數據類型轉換。
多態總是安全的,因為多態按照子類來申請空間。
7.3 常量轉換該運算符從來修飾類型的const指針。
注意:不能直接對非指針和非引用的變量使用const_cast操作符去除它的const。
const int* p = nullptr;
int * np = const_cast<int*>(p);
const int* pp = const_cast<const int*>(np);
int num = 10;
int& b = num;
const int& c = const_cast<const int&>(b);7.4 重新解釋轉換(reinterpret_cast)這是最不安全的一種機制,最有可能出現問題。
主要用於將一種數據類型轉換成另外一種數據類型。他可以將一個指針轉換成一個整數,也可以將一個整數轉換成一個指針。
int a = 10;
int *p = reinterpret_cast<int*>(a);8 c++異常一種思想:在所有支持異常處理的程式語言中(例java),要認識到的一個思想:在異常處理過程中,由問題檢測代碼可以拋的出一個對象給問題處理代碼,通過這個的的對象和類型,實際上完成了兩個部分的通信,通信的內容是「出現了什麼錯誤。當然,各種語言對異常的具體實現有著或多或少的區別。但是這個通信的思想是不變的。
一句話:
異常處理就是處理長鬚中的錯誤。所謂錯誤是指在程序運行過程中發生的異常事件(如:除0溢出,數組下標越界,所要讀取的文件不存在,空指針,內存不足等)。
c++相比c語言處理異常的優勢:
函數的返回值可以忽略,但異常不可忽略。如果程序出現異常,但是沒有被捕獲,程序就會終止,這多少會促使程式設計師開發出來的程序更健壯一點。而如果使用C語言的error宏或者函數返回值,調用者都有可能忘記檢查,從而沒有對錯誤進行處理,結果造成程序莫名其面的終止或出現錯誤的結果。。
整型返回值沒有任何語義信息而異常卻包含語義信息有時你從類名就能夠體現出來。
整型回值缺乏相關的上下文信息,異常作為一個類,可以擁有自己的成員,這些成員就可以傳遞足夠的信息。。
異常處理可以在調用跳級。這是一個代碼編寫時的問題:假設在有多個幽數的調用棧中出現了某個錯誤,使用整型返回碼要求你在每一級函數中都要進行處理。而使用異常處理的棧展開機制只需要在一處進行處理就可以了,不需要每級函數都處 理。
自定義異常對象:
// 自定義異常類
class MyException{
public:
void printError(){
cout << "Myself excetion!" << endl;
}
};定義異常:
int devide(int a, int b){
if (b==0){
// 棧解旋:從try代碼塊開始起,到throw拋出異常前,所有棧上的對象都被釋放掉,釋放的順序與構造的順序相反
Person p1 = Person();
Person p2 = Person();
cout << "aaa" << endl;
throw MyException(); //拋出一個表示MyException匿名對象
}
return a/b;
}捕獲異常:
int a = 10;
int b = 0;
try{
devide(a, b);
}catch(MyException e){
e.printError();
}catch只注重類型,而不注重具體的值。
其他的異常用:
catch(...){
其他異常
}繼續向上拋,使用throw();
異常必須有人要處理,如果沒有任何的處理。程序會自動調用一個terminate函數,讓函數中斷。
總結:
若有異常則通過throw操作創建一個異常對象並拋出。。
將可能拋出異常的程序段放到try塊之中。
如果在try段執行期間沒有引起異常,那麼跟在try後面的catch字句就不會執行。。
catch子句會根據出現的先後順序被檢查,匹配的catch語句捕獲並處理異常(或繼續 拋出異常)
如果匹配的處理未找到,則運行函數terminate將自動被調用,其預設功能調用abort 終止程序。
處理不了的異常,可以在catch的最後一個分支,使用throw,向上拋。
c++異常處理使得異常的引發和異常的處理不必在一個函數中這樣底層的函數可以著重解決具體問題,而不必過多的考慮異常的處理。上層調用者可以在適當的位置設計對不同類型異常的處理。。
8.1 自定義異常類class MyException{
public:
void printError(){
cout << "Myself excetion!" << endl;
}
};捕獲異常:
try{
devide(a, b);
}catch(MyException e){
e.printError();
}8.2 棧解旋定義一個類:
class Person{
public:
Person(){cout <<"Person 構造函數" << endl;};
~Person(){cout <<"Person 析構函數" << endl;};
};在異常函數中調用:
if (b==0){
// 棧解旋:從try代碼塊開始起,到throw拋出異常前,所有棧上的對象都被釋放掉,釋放的順序與構造的順序相反
Person p1 = Person();
Person p2 = Person();
cout << "aaa" << endl;
throw MyException(); //拋出一個表示MyException匿名對象
}
return a/b;輸出:
Person 構造函數
Person 構造函數
aaa
Person 析構函數
Person 析構函數
Myself excetion!棧解旋:從try代碼塊開始起,到throw拋出異常前,所有棧上的對象都被釋放掉,釋放的順序與構造的順序相反
8.3 異常接口聲明為了加強程序的可讀性,可以在函數聲明中列出可能拋出異常的所有類型,例如:void functhrow(AB這個函數func能夠且只能拋出類型ABC及其子類型的異常。。
如果在因數聲明中沒有包含異常接口聲明,則此因數可以拋任何類型的異常,例如:void func()。
一個不拋任何類型異常的函數可聲明為void func)throw()
如果一個函數拋出了它的異常接目聲明所不允許拋出的異常unexcepted 函數會被調用,該函數默認行為調用terminate函數中斷程序。
8.4 異常的接口聲明
void func() throw(double){
throw 3;
}
int main(){
try{
func();
}catch(double){
cout << "my exception " << endl;
}
return 0;
}
允許函數體中拋出某種類型的異常,可以使用異常的接口聲明技術。
void func() throw(類型),如果throw(),表示不允許拋出異常;
8.5 異常變量的生命周期catch (MyExcetion e) 拷貝構造的過程中,如果以值的方式去接受的話,會調用拷貝構造函數。
using namespace std;
class MyException{
public:
MyException(){cout << "constructor"<< endl;};
MyException(const MyException& e){cout << "copy constructor"<< endl;};
~MyException(){cout << "delete constructor"<< endl;};
};
void doWork(){
throw MyException();
}
int main(){
try{
doWork();
}catch (MyException e) { // 以值的方式去接受會調用拷貝構造
cout <<"catch exception" << endl;
}
return 0;
}
列印輸出案例:
構造
拷貝構造
catch exception
delete constructor
delete constructorcatch (MyExcetion & e) 這樣不會調用拷貝構造函數。
構造
catch exception
delete constructor為了提高效率,提倡在catch的時候以引用的方式去接受,這樣節省開支;
通過指針接收:
void doWork(){
throw new MyException();
}
int main(){
try{
doWork();
}catch (MyException *e) {
cout <<"catch exception" << endl;
delete e;
}
return 0;
}創建在堆區的話,需要手動釋放;
constructor
catch exception
delete constructorMyExcetion *e指針方式接收,拋出&MyEcetion() 匿名對象,對象被釋放掉,不可以在操作e了;
void doWork(){
throw &MyException();
}
int main(){
try{
doWork();
}catch (MyException *e) {
cout <<"catch exception" << endl;
}
return 0;
}8.6 異常的多態提供了一個統一的接口,讓子類對接口進行重寫的操作;
// 異常基類
class BaseException{
public:
virtual void printerror()=0;
};
class NullPointerException: public BaseException{
void printerror(){
cout << "null pointer exception" << endl;
}
};
class OutOfRangeException: public BaseException{
void printerror(){
cout << "outof range exception" << endl;
}
};
void doWork(){
// 拋出具體異常
throw NullPointerException();
}
int main(){
try{
doWork();
}catch(BaseException & e){ // 用父類的引用去接受,相當於多態
e.printerror();
}
return 0;
};
拋出具體異常
使用父類去接收,發生多態;
8.7 使用系統標準異常類標準庫裡面也提供了很多的異常類,他們是通過類繼承組織起來的,異常類繼承層級如下:
每個類所在的頭文件在圖下方標識出來。
標準異常類的成員:
每個類都提供了構造函數,賦值構造函數和複製操作符的重載
logic_error 及其子類,runtime_error類及其子類,他們的構造函數都是接受一個string類型的形式參數,返回異常信息的描述;
所有的異常類都有一個what()方法,返回const char*類型的值,描述異常信息;
標準異常類的具體描述:
包含標準異常頭文件
#include<stdExcept> // 系統標準異常類頭文件使用系統標準的異常類
class Person{
public:
Person(int age){
if (age <0 || age >=150){
throw out_of_range("age out of rage");
}
this->m_Age = age;
}
private:
int m_Age;
};
int main(){
try{
Person p1 = Person(162);
}catch(out_of_range &e){
cout << e.what() << endl;
}
return 0;
}包含頭文件 #include<std except>
使用系統異常類 out_of_range("char *")
捕獲 catch(exception& e) {e.what();}
8.8 編寫自己的異常類小知識點:
將string轉const char*
string.to_str();要去掉const,使用const_cast()
將char* 轉string,相當於調用string的有參構造函數:
string(char*)創建自己的異常類
// 聲明自己的異常, 重寫父類中virtual關鍵字
class myOutofRange: public exception{
public:
myOutofRange(string errorInfo){
// 調用string的有參數構造函數
this->m_errorinfo = errorInfo;
}
// 重寫虛析構
~myOutofRange() {};
// 重寫what函數, 常函數修飾this指針
const char* what() const noexcept{
// 如何將string轉const char*
return this->m_errorinfo.c_str();
}
private:
string m_errorinfo;
};拋出異常
class Person{
public:
Person(int age){
if (age <0 || age >=150){
throw myOutofRange("my exception");
}
this->m_Age = age;
}
private:
int m_Age;
};主函數捕獲異常
int main(){
try{
Person p1 = Person(162);
}catch(myOutofRange &e){
cout << e.what() << endl;
}
return 0;
}
使用多態接收異常:
int main(){
try{
Person p1 = Person(162);
}catch(exception &e){
cout << e.what() << endl;
}
return 0;
}注意重寫的what函數是:
const char* what() const noexcept
9. 標準輸入輸出流9.1 流的概念和流類庫的結構程序的輸入指的是從輸入文件將數據傳送給程序,程序的輸出指的是將數據傳送給輸出文件。
c++的輸入和輸出主要包括三方面的內容:
對系統指定的標準設備的輸入和輸出。即從鍵盤輸入數據,輸出到顯示器屏幕。這種輸入輸出為標準的輸入輸出,簡稱標準I/O;
以外存磁碟文件未對象進行輸入和輸出,即從磁碟文件輸入數據,數據輸出到磁碟文件。以外存文件為對象的輸入輸出稱為文件的輸入和輸出,簡稱文件I/O;
對內存中指定的空間進行輸入和輸出。通常指定一個字符數組作為存儲空間(實際上可以利用該空間存儲任何信息),這種輸入和輸出稱之為字符串輸入和輸出,簡稱串I/O.
c++編譯系統提供了輸入輸出的iostream類庫。iostream這個單詞由3個部分組成的,即i-o-stream,意思為輸入輸出流。
在iostream類庫中包含了許多用於輸入輸出的類,常見的見表:
ios是抽象基類,由它派生出istream類和ostream類,兩個類名中的第一個字母i和o分別代表輸入(input)和輸出(output).istream 類支持輸入操作,ostream類支持輸出操作,iostream支持輸出和輸出操作
iostream類是從istream和ostream通過多重繼承而派生的類。其繼承層次間上圖。
c++對文件的輸入和輸出需要用ifstream和ofstream類,兩個類名中的第一個字母分別代表輸入和輸出。第2個字母f代表文件(file)。ifstream支持對文件的輸入操作,ofstream支持對文件的輸出操作。類ifstream繼承了istream,類ofstream繼承了類ostream,類fstream繼承了iostream,如:
I/O類庫中還有一些其他的類,但是對於一般的用戶來說,以上這些已經能夠滿足需求了。
與iostream類庫有關的頭文件
iostream類中不同的類的聲明被放在不同的頭文件中,用戶在自己的程序中用#include命令包含了有關的頭文件就相當於在本程序中聲明了所需要用到的類.可以換一種種說法:頭文件是程序與類庫的接囗,iostream類的接囗分別由不同的頭文件來實現,常用的由
iostream包含了對輸入輸出流進行
fstream用於用戶管理的文件的I/O作·
strstream用於字符傳流I/O.
stdiostream用於混臺使用C和C++的I/O機制時,例如將C程序轉變為C++程序
iominip在使用式化I/O時應包含此頭文件.
在iostream頭文中寧義的流對象
在iostream頭文件中定義的類有ios,istream,ostream,iostream,istream等·
在iostream頭文件中不僅定義了關的類,還定義了4種流對象
對象含義對應應設備對應的類c語言總相應的標準文件cin標準輸入流 屏幕 ostream_withassignstdincout 標準輸出流屏幕 ostream_withassignstdoutcerr 標準錯誤流屏幕 ostream_withassignstderrclog 標準錯誤流屏幕 ostream_withassignstderr在iostream頭文件中定義以上4個流對象用以下的形式(以cout為例):
ostream cout(stdout);•
在義cout為ostream流類對象時,把標準出設番stdout作為參數,這樣它就與標準輸出設備(顯示器)聯繫起來,如果有:
cout<<3;
就會在示的屏上輸出3
在iostream頭文件中重載運算符
「<<「和">>"在C++中是被定義為左位移運算和石位移運算符的, 由於在iostream頭文件中對它們進行了重載,使它們能用作標準類型數據的輸入和的出運算符。所以,在用它們的程序中之須用#include命令把iOStream包含到中。
*include<iostream>
>>a表示將輸入存入到a對象中;
<<a表示將a對象中存儲的數據拿出。
9.2 標準I/O流
讀和寫是站在應用程式的角度;
9.3 標準輸入流標準輸入流對象cin,重點掌握幾個函數:
cin.get() // 一次只能讀取一個字符
cin.get(一個參數) // 讀一個字符
cin.get(兩個參數) // 可以讀字符串
cin.getline()
cin.ignore()
cin.peek()
cin.putback()
char c = cin.get();
cout << c << endl;當cin.get()的數量多於輸入字符串的長度+1時,系統會繼續等待輸入;
char buf[1024];
cin.get(buf, 1024);
cout << buf << endl;cin.get(兩個參數)讀取字符串時,不會把換行符拿走,遺留在緩衝區中。
cin.getline()
char buf[1024];
cin.getline(buf, 1024);
cout << buf <<endl;讀取換行符,並且把換行符扔掉;
cin.ignore()
// 輸入測試
cin.ignore();
char c = cin.get();
cout << c << endl;
// 輸入測試
cin.ignore(2);
char c = cin.get();
cout << c << endl;參數N代表忽略緩衝區當前的n個字符,然後等待讀取下一個字符;
cin.peek()
char c = cin.peek();
cout << c << endl;
c = cin.get();
cout << c << endl;輸出:
a
a偷看一眼,然後在放回到緩衝區中,緩衝區的數據未變動;
cin.putback()
char c = cin.get();
cin.putback(c);
char buf[1027];
cin.getline(buf, 1024);
cout << buf << endl;把當前字符拿出來進行操作之後,又放回去。
小練習:判斷用戶輸入的內容還是字符串還好是數字
void exercise(){
cout << "請輸入一個字符串或者數字:" <<endl;
char c = cin.peek();
if (c >= '0' && c <= '9') {
int num;
cin >> num; // 將緩衝區中的數字放在num中
cout << "您輸入的是數字:" << num <<endl;
} else {
char buf[1024];
cin.getline(buf, 1024);
cout << "您輸入的是字符:" << buf <<endl;
}
}輸入一個數字判斷是否在給定的區間內,如果不是,一致輸入判斷:
緩衝區標識位
cin.fail()
0 代表正常,1代表異常;
清空標誌位:
cin.clear();
// 清空標誌位,並且刷新緩衝區
cin.sync();
如果沒有做真正的清空,需要做到忽略
cin.ignore();void exerciese2(){
cout << "input a num..." << endl;
int num;
while (true){
cin >> num;
if (num >= 0 && num <= 9) {
cout << num << endl;
break;
} else {
cout << "repeated input..." << endl;
}
// 清空緩衝區標誌位
cin.clear();
cin.sync();
}
}9.4 標準輸出流9.4.1 標準輸出流cout.flush() // 刷新緩衝區 Linux有效
cout.put() // 向緩衝區寫字符
cout.write() // 從buffer中寫num個字節到當前輸出流中。
9.4.2 格式化輸出在輸出數據時,為簡便見,往往不指定輸出的格式,由系統根據數據的類型採取默的格式,但有時希望數按指定的式輸出,如要求以十六進位八進位形式蝓出一個整數,對輸出的小數隻保留兩位小數等。
有兩種方法可以達到此目的·
使用控制符的方法
使用對象的有關成員函數·
通過流對象的有關成員函數:
通過調用流對象cout中用於控制輸出格式的成員函數來控制蝓出格式.用於控制輸出格式的常用的成員數如下:
流成員函數setf和控制符setiosflags 括號中的參數表示格式狀態,它是通過格式標誌來指定的。格式標誌在類ios中被定義為枚舉值。因此在引用這些格式標誌時,前面要加上類名ios和域運算符"::"。
setf 設置輸出格式函數
void test2(){
int num = 99;
cout.width(20); // 總共設置20個長度
cout.fill('*'); // 左邊*填充
cout.setf(ios::left); //在寬度範圍內,向左對齊;在寬度範圍內向右對齊
cout.unsetf(ios::dec); // 卸載10進位
cout.setf(ios::hex); // 設置16進位
cout.setf(ios::showbase); // 在前面加0x
cout.unsetf(ios::hex);
cout.setf(ios::oct);
cout << num << endl;
}#include<iomanip>
標準輸出流使用控制符方式格式化輸出的頭文件
void test3(){
int num = 99;
cout << setw(10)
<< setfill('~')
<< setiosflags(ios::showbase)
<< setiosflags(ios::left)
<< hex
<< num<< endl;
}輸出流的控制符
9.5 文件的讀寫9.5.1 文件流類和文件流對象輸入輸出是以系統指定的標準設備(輸入設備為鍵盤,蝓出設備為顯示器)為對象的。在實際應用中,常以磁碟文件作為對象·即從磁碟文件讀取數,將數據輸出到磁碟文件。和文件有關係的的入輸出類主要在fstream.h這個頭文件中被定義,在這個頭文件中主要被定義了三個類,由這三個類控制對文件的各種蝓入輸出作,他們分別是ifstream. ofstream、fstream, 其中fstream類由iostream派生的,地們之間的繼承關係見:
由於文件設備並不像顯示器屏幕與磁碟那樣是標準默認設備,所以它在fstream頭文件中是沒有像cout那樣預先定義的全局對象,所以我們它須目己定義一個該類的對象ifstream類,它是從istream類派生的,用支持從磁碟文件的輸入。ofstream類,它是從ostream類派生的.用於支持向磁碟文件的輸出。
fstream類,它是從iostream類派生的,用來支持對磁碟文件的輸入輸出。
9.5.2 c++打開文件所謂打開(open)文件是一種形象的說法,如同打開房門就可以進入房間活動一樣,打開文件是指在文件讀寫之前必要的準備工作,包括:
1)為文件流對象和指定的磁碟文件建立關聯,以便使文件流流向定的磁碟文件·
2)指定文件的工作方式,如:該文件是作為輸入入文件還是輸出文件,是ASCII文件還是二進位文件。
以上工作可以通過兩種不同的方法實現:
調用文件流的成員函數open
頭文件 fstream
寫ofstream類型對象
度 ifstream類型對象
輸出文件實例:
void exercise1(){
ofstream outfile; // 定義ofstream類(輸出文件流類)對象outfile
// 參數:1 文件路徑 2 打開方式
outfile.open("./f1.dat", ios::out | ios::trunc); // 使文件流與f1.dat文件建立關聯。
// 判斷是否打開成功
if (! outfile.is_open()) {
cout << "open faild!" << endl;
return ;
}
// 寫文件
outfile << "name: lilei" << "age 12" << endl;
// 關閉流對象
outfile.close();
}文件的讀操作:
void exercise2(){
ifstream infile;
infile.open("./f1.dat", ios::in ); // 設置打開方式
if (! infile){
cout << "open failed!" << endl;
return;
}
// 1 讀入方式
char buf[1024] = {0};
// 將每行數據讀入到緩衝區
while (infile >> buf) { // 按行讀取,直到讀到文件尾部
cout << buf << endl;
}
// getline寫出到緩衝區
while(! infile.eof()){
infile.getline(buf, sizeof(buf));
cout << buf << endl;
}
// 第三種:單個字符的讀取
char c;
while ( (c = infile.get()) != EOF) {
cout << c;
}
infile.close();
}