C++類型轉換、IO流與特殊類的設計方法實例
一、類型轉換
1、內(nèi)置類型與內(nèi)置類型之間轉換
(1)隱式類型轉換
整形、浮點數(shù)、字符之間可互相隱式類型轉換
int i = 3.14; // double -> int int i1 = 'A'; // char -> int double d = 10; // int -> double char c = 97; // int -> char
(2)顯式類型轉換(也叫強制轉換)
指針與整形,指針與指針之間可以強制類型轉換
// int* pi = 100; // err, int不可隱式轉換為int* int* pi = (int*)100; // int可強轉為int* // char* pc = pi; // err, int*不可隱式轉換為char* char* pc = (char*)pi; // int*可強轉為char*
2、自定義類型與內(nèi)置類型之間的轉換
(1)內(nèi)置類型→\to→自定義類型
通過構造函數(shù),內(nèi)置類型可以隱式轉換為自定義類型
class A
{
int _a;
public:
A(int a)
:_a(a)
{}
};
void test02()
{
string s = "xxxxx"; // const char* -> string
A a = 20; // int -> A
}
(2)自定義類型→\to→內(nèi)置類型
通過operator 內(nèi)置類型,自定義類型可以轉換為內(nèi)置類型
class A
{
int _a;
public:
A(int a)
:_a(a)
{}
// 將A類對象轉為int類型。不需要寫返回值
operator int()
{
return 10 * _a;
}
// 將A類對象轉為bool類型
operator bool()
{
if(_a % 2 == 0)
return true;
else
return false;
}
};
void test03()
{
A a(9);
int i = a; // 等價于 int i = a.operator int();
cout << i << endl; // 輸出 90
bool t = a; // 等價于 bool t = a.operator bool();
cout << t << endl; // 輸出 0
}
比較典型的應用就是在OJ刷題中,可能會遇到輸入數(shù)據(jù)個數(shù)未知的情況
C語言寫法:
int t; //t是你要輸入的數(shù)據(jù)
while (scanf("%d", &t) != EOF)
{
//...
}
C++寫法:
int t; //t是你要輸入的數(shù)據(jù)
while (cin >> t)
{
//...
}
C++寫法本質上依賴下面兩個函數(shù)


cin >> t返回值仍然是istream對象,istream類型的對象可以轉化為bool類型
(3)自定義類型→\to→自定義類型
也是通過構造函數(shù)支持
class A
{
int _a;
public:
A(int a)
:_a(a)
{}
int get() { return _a; }
};
class B
{
int _b;
public:
B(int b)
:_b(b)
{}
// 提供了A類對象的構造
B(A a)
:_b(a.get())
{}
};
void test04()
{
A a(999);
B b = a; // A類型 -> B類型
}
3、C++新增的類型轉換(了解即可)
C++祖師爺覺得C語言的類型轉換不夠規(guī)范,于是就引入了下面四種類型轉換操作符
(1)static_cast
static_cast對應隱式類型轉換
double d = 3.14; int i = static_cast<int>(d); // 相當于 int i = d;
(2)reinterpret_cast
reinterpret_cast對應強制類型轉換
int i = 10; char* p = reinterpret_cast<char*>(&i); // 相當于 char* p = (char*)&i;
(3)const_cast
const_cast最常用的用途就是刪除變量的const屬性
void test05()
{
int a = 10;
const int* p1 = &a;
int* p2 = const_cast<int*>(p1); // const int* 轉 int*
// 等價于 int* p2 = (int*)p1;
*p2 = 20;
cout << a << endl; // 20
cout << *p1 << endl; // 20
cout << *p2 << endl; // 20
}
注意不要像下面那樣去掉const屬性
void test05()
{
const int a = 10;
int* p = (int*)&a; // a被const修飾,這里卻用非const指針接收
*p = 999;
cout << a << endl;
cout << *p << endl;
printf("%p\n", &a);
printf("%p\n", p);
}
運行結果

這個結果很是意外,p存儲的就是a的地址,但*p的結果卻與a不同!這與編譯器優(yōu)化有關,實踐中不要寫這樣的代碼。
(4)dynamic_cast
dynamic_cast用于多態(tài)類型的向下轉換(運行時檢查)
向上轉換:子類指針/引用 →\to→ 父類指針/引用(賦值兼容規(guī)則)
向下轉換:父類指針/引用 →\to→ 子類指針/引用(用dynamic_cast更安全)
注:dynamic_cast只能用于父類含有虛函數(shù)的類
class A
{
public :
int _a; // 為了方便觀察,設為共有
A(int a)
:_a(a)
{}
virtual void f(){}
};
class B : public A
{
public:
int _b; // 為了方便觀察,設為共有
B(int b)
:A(999)
,_b(b)
{}
};
void func_ptr(A* p)
{
// 若p指向是B類對象(或者B的子類),則可以轉換;否則轉換失敗,返回nullptr
B* pb = dynamic_cast<B*>(p); // A* 轉 B*
if(pb)
cout << pb->_a << ' ' << pb->_b << endl;
else
cout << "轉換失敗" << endl;
}
void func_ref(A& r)
{
try{
// 若r引用的是B類對象(或者B的子類),則可以轉換;否則轉換失敗,拋異常std::bad_cast
B& rb = dynamic_cast<B&>(r); // A& 轉 B&
cout << rb._a << ' ' << rb._b << endl;
}
catch(const std::bad_cast& e)
{
cout << "catch(const std::bad_cast& e)" << endl;
}
}
void test06()
{
A a(10);
B b(20);
func_ptr(&a);
func_ptr(&b);
func_ref(a);
func_ref(b);
}
運行結果

二、IO流
這里只介紹一些常用的
1、C++標準IO流
用法
1.流插入、流提取
cout << t: 把變量t里的內(nèi)容寫入到終端 cin >> t: 從終端讀取數(shù)據(jù),并將其放到變量t。注意: 該過程會自動忽略掉空白字符!
2.get與put
int get(); 從終端讀取單個字符,返回其ASCII碼值 istream& get (char& c); 從終端讀取單個字符并把其放到c中,返回cin(istream只有一個對象cin) // get()函數(shù)不會忽略掉空白字符! ostream& put (char c); 把字符c寫入到終端
舉例
void test()
{
char c;
c = cin.get(); // 輸入ab, 然后按回車
cout << c << endl;
cin.get(c);
cout << c << endl;
cout.put('x');
}
運行結果

效率
為了兼容C語言的IO流,cin與cout的效率會略低于printf與scanf(只有輸入/輸出數(shù)據(jù)量達到 106 及以上時,才會有明顯差距)
打比賽時,如果你只想用cin與cout,可以加上下面代碼關閉流同步從而提高cin與cout的效率
ios::sync_with_stdio(0); cin.tie(0); cout.tie(0); // 加上上面代碼后,就不能將C++的IO與 C的IO混用(例如一會用cout,一會用printf)
2、C++文件IO流
ofstream
ofstream用于把內(nèi)容寫入到文件。
示例:
#include<fstream> // 文件操作要包含這個頭文件
void test01()
{
ofstream fout("test.txt");
// 創(chuàng)建一個ofstream對象fout,并將其連接到文件test.txt
// 若test.txt不存在,則會新建該文件;若test.txt存在,則會清空其內(nèi)容
fout << "abcdef"; // 把abcdef寫入到test.txt
fout << "你好!"; // 把你好!寫入到test.txt
// fout的用法與cout類似。(因為ofstream繼承了ostream)
// fout析構時會自動關閉文件。
}
運行后查看test.txt文件

如果想在原文件末尾追加內(nèi)容,需加上ios::app
// test.txt內(nèi)容:abcdef你好!
ofstream fout("test.txt", ios::app); // ios::app表示在文件末尾追加內(nèi)容
fout << "xxxx";
運行后查看文件

ifstream
ifstream用于從文件中讀取數(shù)據(jù)
示例1
void test02()
{
// test.txt的內(nèi)容:abcdef你好!
ifstream fin("test.txt");
// 創(chuàng)建一個ifstream對象fin,并將其連接到文件test.txt
string s;
fin >> s; // 從test.txt中讀取數(shù)據(jù)并放入到s
cout << s;
// fin的用法與cin類似。(因為ifstream繼承了istream)
// fin析構時會自動關閉文件。
}
運行結果

示例2
void test02()
{
ifstream fin("m.cpp"); // 創(chuàng)建一個ifstream對象fin,并將其連接到文件m.cpp
// is_open返回值:成功打開返回true,否則返回false。
if(!fin.is_open()) // 也可這樣寫:if(!fin),因為有operator bool函數(shù)
{
cout << "打開失敗" << endl;
return;
}
// 把m.cpp的代碼全部打印到終端
char c;
while(fin.get(c)) // 不要用 fin >> c, 會忽略掉空白字符
cout << c;
// 或者寫成下面那樣,與上面等價,效率更高
// cout << fin.rdbuf();
}
運行結果

文本讀寫與二進制讀寫
(1)文本讀寫
上述介紹的文件操作都是對文本進行操作,該操作其實會把數(shù)據(jù)轉換為字符流。
ofstream fout("test.txt");
int t = 123456;
fout << t;
t是int類型,占四個字節(jié),值為123456,在內(nèi)存中的存儲方式是00000000 00000001 11100010 01000000。fout << t是把t寫入到test.txt文件,難道是把00000000 00000001 11100010 01000000寫進去嗎?
答:并非如此,而是先把整數(shù)123456轉換為字符串"123456",然后再寫入到test.txt。
(你想一想,二進制文件全是0101...多難讀懂啊,轉化為字符流不就好多了)
// test.txt內(nèi)容:123456
ifstream fin("test.txt");
int t;
fin >> t;
test.txt文件的內(nèi)容都是字符,實際存儲的內(nèi)容是字符串123456。fin >> t是先把字符串"123456"轉為整數(shù)123456,再放到變量t中
(2)二進制讀寫文件
文本讀寫會把數(shù)據(jù)轉化為字符流,而二進制讀寫不會。
- 二進制讀寫:數(shù)據(jù)在內(nèi)存中怎么存,就怎么寫。
- 二進制讀寫需要用到以下兩個函數(shù):
ostream& write (const char* s, streamsize n); // 把地址從s開始、共有n個字節(jié)的數(shù)據(jù)寫入到文件 istream& read (char* s, streamsize n); // 從文件中讀取n個字節(jié)的數(shù)據(jù),放到地址為s的位置
示例
struct Date
{
int year;
int month;
int day;
};
void test03()
{
ofstream fout("test.txt", ios::binary); // ios::binary 表示以二進制模式
Date d = { 2025,12,12 };
fout.write((const char*)&d, sizeof(d)); // 把d以二進制形式寫到test.txt
fout.close(); // 關閉文件,防止與下面的fin沖突
Date d1;
ifstream fin("test.txt", ios::binary);
fin.read((char*)&d1, sizeof(d1)); // 把test.txt內(nèi)容讀取到d1
cout << d1.year << ' ' << d1.month << ' ' << d1.day; // 輸出:2025 12 12
}
打開test.txt,發(fā)現(xiàn)是亂碼。因為記事本無法識別二進制文件

圖片、音頻、游戲存檔等文件是以二進制方式存儲的,此時就需要用二進制讀寫文件。
現(xiàn)在我有一張圖片,路徑是C:\E\Furina.jpg,我想把它拷貝到當前寫代碼的文件下
void test03()
{
// 注意:\是轉義字符,\\ 表示單個斜杠
ifstream fin("C:\\E\\Furina.jpg", ios::binary);
ofstream fout("fufu.jpg", ios::binary);
char c;
while(fin.get(c)) // 本質是逐字節(jié)讀取數(shù)據(jù)
fout.put(c);
// 也可以這樣寫,效率更快:
// fout << fin.rdbuf();
}
運行一下,就成功把照片復制過來了

三、特殊類的設計
1、不能被拷貝的類
將其拷貝構造與賦值重載用delete修飾即可
class A
{
// ...
public:
// 加上delete
A(const A&) = delete;
A& operator=(const A& ) = delete;
// ...
};
例如庫里面的cin、cout對象就不能被拷貝
2、不能被繼承的類
加上final即可
class A final
{
// ....
};
3、只能在堆上創(chuàng)建對象的類
學習下面兩部分前,需回顧一下前置知識
#include<iostream>
using namespace std;
class A
{
private:
int _val = 888;
void func()
{
cout << "void func()" << endl;
}
public:
static void test()
{
A a;
cout << a._val << endl; // 這里可以訪問私有成員變量嗎?
a.func(); // 這里可以訪問私有成員函數(shù)嗎?
}
};
int main()
{
A::test();
return 0;
}
運行結果

你可以這樣理解:類中的靜態(tài)成員函數(shù)是該類的友元。
回過頭再來設計只能在堆上創(chuàng)建對象的類:
將構造函數(shù)設為私有,然后禁用拷貝構造與賦值重載。再提供靜態(tài)create函數(shù)用于返回堆上的對象指針
class HeapOnly
{
// ... 省略成員變量
private:
HeapOnly()
{
// ...
};
public:
static HeapOnly* create()
{
return new HeapOnly;
// 構造函數(shù)是private,而new會調(diào)用其構造函數(shù),這里不會報錯嗎?
// 答:不會,你可以理解為create函數(shù)是HeapOnly類的友元
}
HeapOnly(const HeapOnly&) = delete;
HeapOnly& operator=(const HeapOnly&) = delete;
};
void test()
{
HeapOnly* h1 = HeapOnly::create();
}
4、只能實例化一個對象的類
將構造函數(shù)設為私有,然后禁用拷貝構造與賦值重載。再提供靜態(tài)getObj函數(shù)用于返回單一對象
class SingleObj
{
// ... 省略成員變量
private:
SingleObj()
{
// ...
};
public:
SingleObj(const SingleObj&) = delete;
SingleObj& operator=(const SingleObj&) = delete;
static SingleObj& getObj()
{
static SingleObj obj; // 局部的靜態(tài),第一次運行到這里才會被初始化
return obj;
}
void func()
{
cout << "void func()" << endl;
}
};
void test()
{
SingleObj& s = SingleObj::getObj();
s.func();
}
總結
到此這篇關于C++類型轉換、IO流與特殊類的設計的文章就介紹到這了,更多相關C++類型轉換、IO流與特殊類設計內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
詳解VS2019 dumpbin查看DLL的導出函數(shù)
這篇文章主要介紹了詳解VS2019 dumpbin查看DLL的導出函數(shù),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-08-08

