C++類型轉換和IO流操作處理教程
前言
首先我們看看C語言中的類型轉換:
在 C 語言中,如果 賦值運算符左右兩側類型不同,或者形參與實參類型不匹配,或者返回值類型與 接收返回值類型不一致時,就需要發(fā)生類型轉化 , C 語言中總共有兩種形式的類型轉換: 隱式類型 轉換和顯式類型轉換 。
1. 隱式類型轉化:編譯器在編譯階段自動進行,能轉就轉,不能轉就編譯失敗
2. 顯式類型轉化:需要用戶自己處理
int main() { int i = 1; // 隱式類型轉換 double d = i; printf("%d, %.2f\n", i, d); int* p = &i; // 顯示的強制類型轉換 int address = (int)p; printf("%x, %d\n", p, address); }
對于上面這種C語言的類型轉換其實是有一些缺陷的,轉換的可視性比較差,所有的轉換形式都是以一種相同形式書寫,難以跟蹤錯誤的轉換。
下面我們看看C++中對于類型轉換的修改
一、C++的四種類型轉換
為什么C++需要重新改進類型轉換呢?
C 風格的轉換格式很簡單,但是有不少缺點的:
1. 隱式類型轉化有些情況下可能會出問題:比如數(shù)據(jù)精度丟失
2. 顯式類型轉換將所有情況混合在一起,代碼不夠清晰
因此 C++ 提出了自己的類型轉化風格,注意 因為 C++ 要兼容 C 語言,所以 C++ 中還可以使用 C 語言的 轉化風格 。
標準 C++ 為了加強類型轉換的可視性,引入了四種命名的強制類型轉換操作符:
static_cast 、 reinterpret_cast 、 const_cast 、 dynamic_cast
第一種:static_cast
static_cast 用于非多態(tài)類型的轉換(靜態(tài)轉換),編譯器隱式執(zhí)行的任何類型轉換都可用
static_cast ,但它不能用于兩個不相關的類型進行轉換
下面我們用代碼演示一下:
int main() { int i = 1; double d = static_cast<double>(i); float c = static_cast<float>(d); return 0; }
我們只需要記?。簊tatic_cast適用于可以隱式轉換的類型。
第二種:reinterpret_cast
reinterpret_cast 操作符通常為操作數(shù)的位模式提供較低層次的重新解釋,用于將一種類型轉換 為另一種不同的類型 。
下面我們用代碼演示一下:
int main() { int i = 1; int* p = &i; int t = reinterpret_cast<int>(p); cout << t << endl; int c = 10; int* d = reinterpret_cast<int*>(c); cout << d << endl; return 0; }
對于reinterpret_cast的使用我們只需要記住適用于不同類型之間的轉換即可。
第三種:const_cast
const_cast 最常用的用途就是刪除變量的 const 屬性,方便賦值.
int main() { const int a = 2; int* p = const_cast<int*>(&a); cout << *p << endl; *p = 10; cout << *p << endl; return 0; }
對于const類型的常變量一般是不能直接修改的,但是可以像我們上面那樣間接的修改,主要還是因為常變量是放在棧中的不是放在常量區(qū)的,注意:常量區(qū)是一定不可以修改的。
下面我們看一道??嫉念}:
int main() { const int a = 2; int* p = const_cast<int*>(&a); *p = 3; cout << a << endl; cout << *p << endl; return 0; }
上面這段代碼的運行結果是什么?很多人都會以為是3和3,因為p指向a的空間,修改*p那么a中的內容也會被修改,思路沒錯,但是沒有考慮到編譯器的優(yōu)化,這道題的正確答案是2 3。
由于const變量在編譯器看來是不會被修改的,所以本來應該先從內存讀數(shù)據(jù)到寄存器結果被編譯器優(yōu)化為直接從寄存器拿數(shù)據(jù)。對于這種情況我們只需要讓編譯器不優(yōu)化,這樣的話編譯器就會從內存中讀取a的內容打印了:
可以看到這次的結果就正確了。所以對于const_cast轉化是將原本const屬性去掉單獨分出來,這個時候我們就應該小心了,const變量盡量不要去改變。
第四種:dynamic_cast
dynamic_cast 用于將一個父類對象的指針 / 引用轉換為子類對象的指針或引用 ( 動態(tài)轉換 )
向上轉型:子類對象指針 / 引用 -> 父類指針 / 引用 ( 不需要轉換,賦值兼容規(guī)則 )
向下轉型:父類對象指針 / 引用 -> 子類指針 / 引用 ( 用 dynamic_cast 轉型是安全的 )
注意:
1. dynamic_cast 只能用于父類含有虛函數(shù)的類
2. dynamic_cast 會先檢查是否能轉換成功,能成功則轉換,不能則返回 0
我們可以看到父類指針是天然可以接收子類的指針或者引用的,那么如果是將父類給子類呢?
可以看到如果我們不加類型轉化的話連編譯都過不了,那么我們用類型轉換再試試:
可以看到經過類型轉換后是沒問題的,并且dynamic_cast在轉換中會保證安全性。下面我們看看如果不用dynamic_cast轉化會出現(xiàn)什么情況:
class A { public: virtual void f() {} int _a = 0; }; class B : public A { public: int _b = 0; }; void Func(A* ptr) { B* btr = (B*)ptr; cout << btr << endl; btr->_a++; btr->_b++; cout << btr->_a << endl; cout << btr->_b << endl; } int main() { A aa; B bb; Func(&aa); Func(&bb); return 0; }
我們可以看到直接出錯了,下面我們用安全轉換試一下:
我們可以看到當子類接收父類指針造成越界的時候安全轉化會檢查能否成功轉化,對于不能成功轉化的直接返回0就像上圖一樣。
總結:
Func中的ptr如果是指向子類對象,那么轉回子類類型是沒問題的。
ptr如果是指向父類對象,那么轉回子類類型是存在越界風險的。
注意 強制類型轉換關閉或掛起了正常的類型檢查 ,每次使用強制類型轉換前,程序員應該仔細考慮是 否還有其他不同的方法達到同一目的,如果非強制類型轉換不可,則應限制強制轉換值的作用 域,以減少發(fā)生錯誤的機會。 強烈建議:避免使用強制類型轉換
以上就是C++類型轉換的全部內容了,下面我們進入IO流的學習。
二、C++IO流
C 語言的輸入與輸出:
C 語言中我們用到的最頻繁的輸入輸出方式就是 scanf () 與 printf() 。 scanf(): 從標準輸入設備 ( 鍵 盤 ) 讀取數(shù)據(jù),并將值存放在變量中 。 printf(): 將指定的文字 / 字符串輸出到標準輸出設備 ( 屏幕 ) 。 注意寬度輸出和精度輸出控制。
語言借助了相應的緩沖區(qū)來進行輸入與輸出。
對 輸入輸出緩沖區(qū) 的理解:
1. 可以 屏蔽掉低級 I/O 的實現(xiàn) ,低級 I/O 的實現(xiàn)依賴操作系統(tǒng)本身內核的實現(xiàn),所以如果能夠屏 蔽這部分的差異,可以 很容易寫出可移植的程序 。
2. 可以 使用這部分的內容實現(xiàn) “ 行 ” 讀取的行為 ,對于計算機而言是沒有 “ 行 ” 這個概念,有了這 部分,就可以定義 “ 行 ” 的概念,然后解析緩沖區(qū)的內容,返回一個 “ 行 ” 。
流是什么: “
流 ” 即是流動的意思,是物質從一處向另一處流動的過程 ,是對一種 有序連續(xù) 且 具有方向性 的 數(shù) 據(jù) ( 其單位可以是bit,byte,packet )的 抽象描述。 C++ 流是指信息從外部輸入設備(如鍵盤)向計算機內部(如內存)輸入和從內存向外部輸出設 備(顯示器)輸出的過程。這種輸入輸出的過程被形象的比喻為 “ 流 ” 。
它的 特性 是: 有序連續(xù) 、 具有方向性 為了實現(xiàn)這種流動, C++ 定義了 I/O 標準類庫,這些每個類都稱為流 / 流類,用以完成某方面的功 能。 C++IO流: C++ 系統(tǒng)實現(xiàn)了一個龐大的類庫,其中 ios 為基類,其他類都是直接或間接派生自 ios 類。
C++標準IO流 :
C++ 標準庫提供了 4 個全局流對象 cin 、 cout 、 cerr 、 clog ,使用 cout 進行標準輸出,即數(shù)據(jù)從內 存流向控制臺 ( 顯示器 ) 。使用 cin 進行標準輸入即數(shù)據(jù)通過鍵盤輸入到程序中 ,同時 C++ 標準庫還 提供了 cerr 用來進行標準錯誤的輸出 ,以及 clog 進行日志的輸出 ,從上圖可以看出, cout 、 cerr 、 clog 是 ostream 類的三個不同的對象,因此這三個對象現(xiàn)在基本沒有區(qū)別,只是應用場景不 同。
在使用時候必須要包含文件并引入 std 標準命名空間。
注意:
1. cin 為緩沖流。 鍵盤輸入的數(shù)據(jù)保存在緩沖區(qū)中,當要提取時,是從緩沖區(qū)中拿 。如果一次輸 入過多,會留在那兒慢慢用, 如果輸入錯了,必須在回車之前修改,如果回車鍵按下就無法 挽回了 。 只有把輸入緩沖區(qū)中的數(shù)據(jù)取完后,才要求輸入新的數(shù)據(jù) 。
2. 輸入的數(shù)據(jù)類型必須與要提取的數(shù)據(jù)類型一致 ,否則出錯。出錯只是在流的狀態(tài)字 state 中對 應位置位(置 1 ),程序繼續(xù)。
3. 空格和回車都可以作為數(shù)據(jù)之間的分格符,所以多個數(shù)據(jù)可以在一行輸入,也可以分行輸 入。但如果是 字符型和字符串,則空格( ASCII 碼為 32 )無法用 cin 輸入,字符串中也不能有 空格 。回車符也無法讀入。
4. cin 和 cout 可以直接輸入和輸出內置類型數(shù)據(jù),原因: 標準庫已經將所有內置類型的輸入和 輸出全部重載了 :
下面我們看看OJ中的循環(huán)輸入:
int main() { string str; while (cin >> str) { cout << str << endl; } return 0; }
不知道我們是否會有疑問>>符號是如何像邏輯判斷操作符那樣在循環(huán)體中進行判斷的呢?
我們可以看到>>符號的返回值是istream&,也不是bool類型,下面我們看文檔:
其實在文檔中我們發(fā)現(xiàn)不管是C++11還是C++98都重載了operator bool,重載的目的就是支持自定義類型隱式轉換為自定義類型,也就是說支持將istream&轉化為bool類型。當然其實日常使用中我們看到最多的是內置類型隱式轉換成自定義類型,比如下面這樣:
class A { public: A(int a) :_a1(1) , _a2(2) {} private: int _a1; int _a2; }; int main() { A aa = 1; return 0; }
上圖中我們的aa就是自定義類型,1是內置類型,將1給aa的過程中發(fā)生了隱式類型轉化,由內置類型轉化為自定義類型。
上圖中我們可以看到,正常情況下我們是不能將自定義類型轉化為內置類型的,但是當我們重載了int的轉換后就可以了:
operator int() { return _a1 + _a2; }
下面我們將日期類實現(xiàn)為像>>一樣可以判斷的:
class Date { friend ostream& operator << (ostream& out, const Date& d); friend istream& operator >> (istream& in, Date& d); public: Date(int year = 1, int month = 1, int day = 1) :_year(year) , _month(month) , _day(day) {} operator bool() { if (_year > 0) { return true; } else { return false; } } private: int _year; int _month; int _day; }; istream& operator >> (istream& in, Date& d) { in >> d._year >> d._month >> d._day; return in; } ostream& operator << (ostream& out, const Date& d) { out << d._year << " " << d._month << " " << d._day; return out; } int main() { Date d1(0, 5, 30); while (d1) { cout << d1 << endl; } return 0; }
我們重載bool類型的時候直接用year做判斷了這里只是為了演示,對于年份為0的日期進入while循環(huán)后會直接退出,如果是年份非0的日期則會死循環(huán)的打印。
C++文件IO流:
C++ 根據(jù)文件內容的數(shù)據(jù)格式分為 二進制文件 和 文本文件 。采用文件流對象操作文件的一般步驟:
1. 定義一個文件流對象 ifstream ififile( 只輸入用 ) ofstream ofifile( 只輸出用 ) fstream iofifile( 既輸入又輸出用 )
2. 使用文件流對象的成員函數(shù)打開一個磁盤文件,使得文件流對象和磁盤文件之間建立聯(lián)系
3. 使用提取和插入運算符對文件進行讀寫操作,或使用成員函數(shù)進行讀寫
4. 關閉文件
struct ServerInfo { char _address[32]; int _port; Date _date; }; struct ConfigManager { public: ConfigManager(const char* filename) :_filename(filename) {} void WriteBin(const ServerInfo& info) { ofstream ofs(_filename, ios_base::out | ios_base::binary); ofs.write((const char*)&info, sizeof(info)); } void ReadBin(ServerInfo& info) { ifstream ifs(_filename, ios_base::in | ios_base::binary); ifs.read((char*)&info, sizeof(info)); } void WriteText(const ServerInfo& info) { ofstream ofs(_filename); ofs << info._address << " " << info._port<< " "<<info._date; } void ReadText(ServerInfo& info) { ifstream ifs(_filename); ifs >> info._address >> info._port>>info._date; } private: string _filename; // 配置文件 };
上面是一個文件管理的類,里面封裝了二進制讀寫和文本讀寫的接口,由于C++IO流屬于了解性的內容所以我們就不再詳細的講解每個函數(shù),有不會的接口大家自行查文檔即可。下面我們用一個實例使用一下以上的接口:
int main() { ServerInfo winfo = { "192.0.0.1", 80, { 2022, 4, 10 } }; // 二進制讀寫 ConfigManager cf_bin("test.bin"); cf_bin.WriteBin(winfo); ServerInfo rbinfo; cf_bin.ReadBin(rbinfo); cout << rbinfo._address << " " << rbinfo._port <<" " <<rbinfo._date << endl; // 文本讀寫 ConfigManager cf_text("test.text"); cf_text.WriteText(winfo); ServerInfo rtinfo; cf_text.ReadText(rtinfo); cout << rtinfo._address << " " << rtinfo._port << " " << rtinfo._date << endl; return 0; }
C++ 文件流的優(yōu)勢就是可以對內置類型和自定義類型,都使用 一樣的方式,去流插入和流提取數(shù)據(jù),當然這里自定義類型Date 需要重載 >> 和 << 。
istream& operator >> (istream& in, Date& d)
ostream& operator << (ostream& out, const Date& d)
stringstream 的簡單介紹 :
在 C 語言中,如果想要將一個整形變量的數(shù)據(jù)轉化為字符串格式,如何去做?
1. 使用 itoa() 函數(shù)
2. 使用 sprintf() 函數(shù)
但是兩個函數(shù)在轉化時,都得 需要先給出保存結果的空間 ,那空間要給多大呢,就不太好界定, 而且 轉化格式不匹配時,可能還會得到錯誤的結果甚至程序崩潰 。
在 C++ 中,可以使用 stringstream 類對象來避開此問題。
在程序中如果想要使用 stringstream ,必須要包含頭文件 。
在該頭文件下,標準庫三個類: istringstream 、 ostringstream 和 stringstream ,分別用來進行流的輸入、輸出和輸入輸出操 作,下面主要介紹 stringstream 。
stringstream 主要可以用來:
1. 將數(shù)值類型數(shù)據(jù)格式化為字符串
#include<sstream> int main() { int a = 12345678; string sa; // 將一個整形變量轉化為字符串,存儲到string類對象中 stringstream s; s << a; s >> sa; // clear() // 注意多次轉換時,必須使用clear將上次轉換狀態(tài)清空掉 // stringstreams在轉換結尾時(即最后一個轉換后),會將其內部狀態(tài)設置為badbit // 因此下一次轉換是必須調用clear()將狀態(tài)重置為goodbit才可以轉換 // 但是clear()不會將stringstreams底層字符串清空掉 // s.str(""); // 將stringstream底層管理string對象設置成"", // 否則多次轉換時,會將結果全部累積在底層string對象中 s.str(""); s.clear(); // 清空s, 不清空會轉化失敗 double d = 12.34; s << d; s >> sa; string sValue; sValue = s.str(); // str()方法:返回stringsteam中管理的string類型 cout << sValue << endl; return 0; }
2. 字符串拼接
int main() { stringstream sstream; // 將多個字符串放入 sstream 中 sstream << "first" << " " << "string,"; sstream << " second string"; cout << "strResult is: " << sstream.str() << endl; // 清空 sstream sstream.str(""); sstream << "third string"; cout << "After clear, strResult is: " << sstream.str() << endl; return 0; }
3. 序列化和反序列化結構數(shù)據(jù)
struct ChatInfo { string _name; // 名字 int _id; // id Date _date; // 時間 string _msg; // 聊天信息 }; int main() { // 結構信息序列化為字符串 ChatInfo winfo = { "張三", 135246, { 2022, 4, 10 }, "晚上一起看電影吧" }; ostringstream oss; oss << winfo._name << " " << winfo._id << " " << winfo._date << " " << winfo._msg; string str = oss.str(); cout << str << endl<<endl; // 我們通過網絡這個字符串發(fā)送給對象,實際開發(fā)中,信息相對更復雜, // 一般會選用Json、xml等方式進行更好的支持 // 字符串解析成結構信息 ChatInfo rInfo; istringstream iss(str); iss >> rInfo._name >> rInfo._id >> rInfo._date >> rInfo._msg; cout << "-------------------------------------------------------" << endl; cout << "姓名:" << rInfo._name << "(" << rInfo._id << ") "; cout <<rInfo._date << endl; cout << rInfo._name << ":>" << rInfo._msg << endl; cout << "-------------------------------------------------------" << endl; return 0; }
注意:
1. stringstream 實際是在其底層維護了一個 string 類型的對象用來保存結果 。
2. 多次數(shù)據(jù)類型轉化時,一定要用 clear() 來清空,才能正確轉化 ,但 clear() 不會將 stringstream 底層的 string 對象清空。
3. 可以 使用 s. str("") 方法將底層 string 對象設置為 "" 空字符串 。
4. 可以 使用 s.str() 將讓 stringstream 返回其底層的 string 對象 。
5. stringstream 使用 string 類對象代替字符數(shù)組,可以避免緩沖區(qū)溢出的危險,而且其會對參 數(shù)類型進行推演,不需要格式化控制,也不會出現(xiàn)格式化失敗的風險 ,因此使用更方便,更 安全。
總結
C++IO流這部分知識大多都是偏了解性的知識,所以我們沒有在細細的講解,實際上學到這一部分很多人對C++都基本入門了,這個時候是完全有能力通過官方文檔來自己運用這部分內容。
到此這篇關于C++類型轉換和IO流操作處理的文章就介紹到這了,更多相關C++類型轉換和IO流內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++?protobuf中對不同消息內容進行賦值的方式總結(set_、set_allocated_、mutable_、
這篇文章主要給大家介紹了關于C++?protobuf中對不同消息內容進行賦值的方式總結,主要使用的是set_、set_allocated_、mutable_、add_,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2023-03-03