C++类型转换、IO流与特殊类的设计方法实例
<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>一、类型转换</li><ul class="second_class_ul"><li>1、内置类型与内置类型之间转换</li><ul class="third_class_ul"><li>(1)隐式类型转换</li><li>(2)显式类型转换(也叫强制转换)</li></ul><li>2、自定义类型与内置类型之间的转换</li><ul class="third_class_ul"><li>(1)内置类型→\to→自定义类型</li><li>(2)自定义类型→\to→内置类型</li><li>(3)自定义类型→\to→自定义类型</li></ul><li>3、C++新增的类型转换(了解即可)</li><ul class="third_class_ul"><li>(1)static_cast</li><li>(2)reinterpret_cast</li><li>(3)const_cast</li><li>(4)dynamic_cast</li></ul></ul><li>二、IO流</li><ul class="second_class_ul"><li>1、C++标准IO流</li><ul class="third_class_ul"><li>用法</li><li>效率</li></ul><li>2、C++文件IO流</li><ul class="third_class_ul"><li>ofstream</li><li>ifstream</li><li>文本读写与二进制读写</li></ul></ul><li>三、特殊类的设计</li><ul class="second_class_ul"><li>1、不能被拷贝的类</li><ul class="third_class_ul"></ul><li>2、不能被继承的类</li><ul class="third_class_ul"></ul><li>3、只能在堆上创建对象的类</li><ul class="third_class_ul"></ul><li>4、只能实例化一个对象的类</li><ul class="third_class_ul"></ul></ul><li>总结 </li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>一、类型转换</h2><p class="maodian"></p><h3>1、内置类型与内置类型之间转换</h3>
<p class="maodian"></p><h4>(1)隐式类型转换</h4>
<p>整形、浮点数、字符之间可互相隐式类型转换</p>
<div class="jb51code"><pre class="brush:cpp;">int i = 3.14; // double ->int
int i1 = 'A'; // char ->int
double d = 10;// int ->double
char c = 97; // int ->char
</pre></div>
<p class="maodian"></p><h4>(2)显式类型转换(也叫强制转换)</h4>
<p>指针与整形,指针与指针之间可以强制类型转换</p>
<div class="jb51code"><pre class="brush:cpp;">// int* pi = 100; // err, int不可隐式转换为int*
int* pi = (int*)100; // int可强转为int*
// char* pc = pi; // err, int*不可隐式转换为char*
char* pc = (char*)pi; // int*可强转为char*
</pre></div>
<p class="maodian"></p><h3>2、自定义类型与内置类型之间的转换</h3>
<p class="maodian"></p><h4>(1)内置类型→\to→自定义类型</h4>
<p>通过<strong>构造函数</strong>,内置类型可以隐式转换为自定义类型</p>
<div class="jb51code"><pre class="brush:cpp;">class A
{
int _a;
public:
A(int a)
:_a(a)
{}
};
void test02()
{
string s = "xxxxx"; // const char* -> string
A a = 20;// int -> A
}
</pre></div>
<p class="maodian"></p><h4>(2)自定义类型→\to→内置类型</h4>
<p>通过<code>operator 内置类型</code>,自定义类型可以转换为内置类型</p>
<div class="jb51code"><pre class="brush:cpp;">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
}
</pre></div>
<p>比较典型的应用就是在OJ刷题中,可能会遇到<strong>输入数据个数未知</strong>的情况<br />C语言写法:</p>
<div class="jb51code"><pre class="brush:cpp;">int t; //t是你要输入的数据
while (scanf("%d", &t) != EOF)
{
//...
}
</pre></div>
<p>C++写法:</p>
<div class="jb51code"><pre class="brush:cpp;">int t; //t是你要输入的数据
while (cin >> t)
{
//...
}
</pre></div>
<p>C++写法本质上依赖下面两个函数</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202512/2025121313590816.png" /></p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202512/2025121313590864.png" /></p>
<p><code>cin >> t</code>返回值仍然是<code>istream</code>对象,<code>istream</code>类型的对象可以转化为<code>bool</code>类型</p>
<p class="maodian"></p><h4>(3)自定义类型→\to→自定义类型</h4>
<p>也是通过构造函数支持</p>
<div class="jb51code"><pre class="brush:cpp;">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类型
}
</pre></div>
<p class="maodian"></p><h3>3、C++新增的类型转换(了解即可)</h3>
<p>C++祖师爷觉得C语言的类型转换不够规范,于是就引入了下面四种类型转换操作符</p>
<p class="maodian"></p><h4>(1)static_cast</h4>
<p><code>static_cast</code>对应<strong>隐式类型转换</strong></p>
<div class="jb51code"><pre class="brush:cpp;">double d = 3.14;
int i = static_cast<int>(d);
// 相当于 int i = d;
</pre></div>
<p class="maodian"></p><h4>(2)reinterpret_cast</h4>
<p><code>reinterpret_cast</code>对应<strong>强制类型转换</strong></p>
<div class="jb51code"><pre class="brush:cpp;">int i = 10;
char* p = reinterpret_cast<char*>(&i);
// 相当于 char* p = (char*)&i;
</pre></div>
<p class="maodian"></p><h4>(3)const_cast</h4>
<p><code>const_cast</code>最常用的用途就是删除变量的<code>const</code>属性</p>
<div class="jb51code"><pre class="brush:cpp;">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
}
</pre></div>
<p>注意不要像下面那样去掉<code>const</code>属性</p>
<div class="jb51code"><pre class="brush:cpp;">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);
}
</pre></div>
<p>运行结果</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202512/2025121313590868.png" /></p>
<p>这个结果很是意外,<code>p</code>存储的就是<code>a</code>的地址,但<code>*p</code>的结果却与<code>a</code>不同!这与编译器优化有关,实践中不要写这样的代码。</p>
<p class="maodian"></p><h4>(4)dynamic_cast</h4>
<p><code>dynamic_cast</code>用于多态类型的向下转换(运行时检查)</p>
<p>向上转换:子类指针/引用 <span><span><span>→\to</span><span><span><span>→</span></span></span></span></span> 父类指针/引用(赋值兼容规则)</p>
<p>向下转换:父类指针/引用 <span><span><span>→\to</span><span><span><span>→</span></span></span></span></span> 子类指针/引用(用<code>dynamic_cast</code>更安全)</p>
<p>注:<code>dynamic_cast</code>只能用于<strong>父类含有虚函数的类</strong></p>
<div class="jb51code"><pre class="brush:cpp;">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);
}
</pre></div>
<p>运行结果</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202512/2025121313590884.png" /></p>
<p class="maodian"></p><h2>二、IO流</h2>
<p>这里只介绍一些常用的</p>
<p class="maodian"></p><h3>1、C++标准IO流</h3>
<p class="maodian"></p><h4>用法</h4>
<p>1.流插入、流提取</p>
<div class="jb51code"><pre class="brush:cpp;">cout << t: 把变量t里的内容写入到终端
cin >> t:从终端读取数据,并将其放到变量t。注意: 该过程会自动忽略掉空白字符!
</pre></div>
<p>2.<code>get</code>与<code>put</code></p>
<div class="jb51code"><pre class="brush:cpp;">int get(); 从终端读取单个字符,返回其ASCII码值
istream& get (char& c); 从终端读取单个字符并把其放到c中,返回cin(istream只有一个对象cin)
// get()函数不会忽略掉空白字符!
ostream& put (char c); 把字符c写入到终端
</pre></div>
<p>举例</p>
<div class="jb51code"><pre class="brush:cpp;">void test()
{
char c;
c = cin.get(); // 输入ab, 然后按回车
cout << c << endl;
cin.get(c);
cout << c << endl;
cout.put('x');
}
</pre></div>
<p>运行结果</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202512/2025121313590869.png" /></p>
<p class="maodian"></p><h4>效率</h4>
<p>为了兼容C语言的IO流,<code>cin</code>与<code>cout</code>的效率会<strong>略低于</strong><code>printf</code>与<code>scanf</code>(只有输入/输出数据量达到 <strong>10<sup>6</sup></strong> 及以上时,才会有明显差距)<br />打比赛时,如果你只想用<code>cin</code>与<code>cout</code>,可以加上下面代码关闭流同步从而提高<code>cin</code>与<code>cout</code>的效率</p>
<div class="jb51code"><pre class="brush:cpp;">ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
// 加上上面代码后,就不能将C++的IO与 C的IO混用(例如一会用cout,一会用printf)
</pre></div>
<p class="maodian"></p><h3>2、C++文件IO流</h3>
<p class="maodian"></p><h4>ofstream</h4>
<p><code>ofstream</code>用于把内容<strong>写入</strong>到文件。</p>
<p>示例:</p>
<div class="jb51code"><pre class="brush:cpp;">#include<fstream> // 文件操作要包含这个头文件
void test01()
{
ofstream fout("test.txt");
// 创建一个ofstream对象fout,并将其连接到文件test.txt
// 若test.txt不存在,则会新建该文件;若test.txt存在,则会清空其内容
fout << "abcdef"; // 把abcdef写入到test.txt
fout << "你好!"; // 把你好!写入到test.txt
// fout的用法与cout类似。(因为ofstream继承了ostream)
// fout析构时会自动关闭文件。
}
</pre></div>
<p>运行后查看<code>test.txt</code>文件</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202512/2025121313590892.png" /></p>
<p>如果想在原文件末尾追加内容,需加上<code>ios::app</code></p>
<div class="jb51code"><pre class="brush:cpp;">// test.txt内容:abcdef你好!
ofstream fout("test.txt", ios::app); // ios::app表示在文件末尾追加内容
fout << "xxxx";
</pre></div>
<p>运行后查看文件</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202512/2025121313590873.png" /></p>
<p class="maodian"></p><h4>ifstream</h4>
<p><code>ifstream</code>用于从文件中<strong>读取</strong>数据</p>
<p>示例1</p>
<div class="jb51code"><pre class="brush:cpp;">void test02()
{
// test.txt的内容:abcdef你好!
ifstream fin("test.txt");
// 创建一个ifstream对象fin,并将其连接到文件test.txt
string s;
fin >> s; // 从test.txt中读取数据并放入到s
cout << s;
// fin的用法与cin类似。(因为ifstream继承了istream)
// fin析构时会自动关闭文件。
}
</pre></div>
<p>运行结果</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202512/2025121313590829.png" /></p>
<p>示例2</p>
<div class="jb51code"><pre class="brush:cpp;">void test02()
{
ifstream fin("m.cpp"); // 创建一个ifstream对象fin,并将其连接到文件m.cpp
// is_open返回值:成功打开返回true,否则返回false。
if(!fin.is_open())// 也可这样写:if(!fin),因为有operator bool函数
{
cout << "打开失败" << endl;
return;
}
// 把m.cpp的代码全部打印到终端
char c;
while(fin.get(c)) // 不要用 fin >> c, 会忽略掉空白字符
cout << c;
// 或者写成下面那样,与上面等价,效率更高
// cout << fin.rdbuf();
}
</pre></div>
<p>运行结果</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202512/2025121313590926.png" /></p>
<p class="maodian"></p><h4>文本读写与二进制读写</h4>
<p><strong>(1)文本读写</strong></p>
<p>上述介绍的文件操作都是对<strong>文本</strong>进行操作,该操作其实会把数据转换为<strong>字符流</strong>。</p>
<div class="jb51code"><pre class="brush:cpp;">ofstream fout("test.txt");
int t = 123456;
fout << t;
</pre></div>
<p><code>t</code>是<code>int</code>类型,占四个字节,值为<code>123456</code>,在内存中的存储方式是<code>00000000 00000001 11100010 01000000</code>。<br /><code>fout << t</code>是把<code>t</code>写入到<code>test.txt</code>文件,难道是把<code>00000000 00000001 11100010 01000000</code>写进去吗?<br />答:并非如此,而是先把整数<code>123456</code>转换为字符串<code>"123456"</code>,然后再写入到<code>test.txt</code>。<br />(你想一想,二进制文件全是<code>0101...</code>多难读懂啊,转化为字符流不就好多了)</p>
<div class="jb51code"><pre class="brush:cpp;">// test.txt内容:123456
ifstream fin("test.txt");
int t;
fin >> t;
</pre></div>
<p><code>test.txt</code>文件的内容都是字符,实际存储的内容是字符串<code>123456</code>。<br /><code>fin >> t</code>是先把字符串<code>"123456"</code>转为整数<code>123456</code>,再放到变量<code>t</code>中</p>
<p><strong>(2)二进制读写文件</strong></p>
<p>文本读写会把数据转化为字符流,而二进制读写不会。</p>
<ul><li>二进制读写:数据在内存中怎么存,就怎么写。</li><li>二进制读写需要用到以下两个函数:</li></ul>
<div class="jb51code"><pre class="brush:cpp;">ostream& write (const char* s, streamsize n);
// 把地址从s开始、共有n个字节的数据写入到文件
istream& read (char* s, streamsize n);
// 从文件中读取n个字节的数据,放到地址为s的位置
</pre></div>
<p>示例</p>
<div class="jb51code"><pre class="brush:cpp;">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内容读取到d1
cout << d1.year << ' ' << d1.month << ' ' << d1.day; // 输出:2025 12 12
}
</pre></div>
<p>打开<code>test.txt</code>,发现是乱码。因为记事本无法识别二进制文件</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202512/2025121313590873.png" /></p>
<p>图片、音频、游戏存档等文件是以二进制方式存储的,此时就需要用二进制读写文件。<br />现在我有一张图片,路径是<code>C:\E\Furina.jpg</code>,我想把它拷贝到当前写代码的文件下</p>
<div class="jb51code"><pre class="brush:cpp;">void test03()
{
// 注意:\是转义字符,\\ 表示单个斜杠
ifstream fin("C:\\E\\Furina.jpg", ios::binary);
ofstream fout("fufu.jpg", ios::binary);
char c;
while(fin.get(c)) // 本质是逐字节读取数据
fout.put(c);
// 也可以这样写,效率更快:
// fout << fin.rdbuf();
}
</pre></div>
<p>运行一下,就成功把照片复制过来了</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202512/2025121313590983.png" /></p>
<p class="maodian"></p><h2>三、特殊类的设计</h2>
<p class="maodian"></p><h3>1、不能被拷贝的类</h3>
<p>将其拷贝构造与赋值重载用<code>delete</code>修饰即可</p>
<div class="jb51code"><pre class="brush:cpp;">class A
{
// ...
public:
// 加上delete
A(const A&) = delete;
A& operator=(const A& ) = delete;
// ...
};
</pre></div>
<p>例如库里面的cin、cout对象就不能被拷贝</p>
<p class="maodian"></p><h3>2、不能被继承的类</h3>
<p>加上<code>final</code>即可</p>
<div class="jb51code"><pre class="brush:cpp;">class Afinal
{
// ....
};
</pre></div>
<p class="maodian"></p><h3>3、只能在堆上创建对象的类</h3>
<p>学习下面两部分前,需回顾一下前置知识</p>
<div class="jb51code"><pre class="brush:cpp;">#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(); // 这里可以访问私有成员函数吗?
}
};
int main()
{
A::test();
return 0;
}
</pre></div>
<p>运行结果</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202512/2025121313590819.png" /></p>
<p>你可以这样理解:类中的静态成员函数是该类的友元。</p>
<p>回过头再来设计只能在堆上创建对象的类:</p>
<p>将构造函数设为私有,然后禁用拷贝构造与赋值重载。再提供静态create函数用于返回堆上的对象指针</p>
<div class="jb51code"><pre class="brush:cpp;">class HeapOnly
{
// ... 省略成员变量
private:
HeapOnly()
{
// ...
};
public:
static HeapOnly* create()
{
return new HeapOnly;
// 构造函数是private,而new会调用其构造函数,这里不会报错吗?
// 答:不会,你可以理解为create函数是HeapOnly类的友元
}
HeapOnly(const HeapOnly&) = delete;
HeapOnly& operator=(const HeapOnly&) = delete;
};
void test()
{
HeapOnly* h1 = HeapOnly::create();
}
</pre></div>
<p class="maodian"></p><h3>4、只能实例化一个对象的类</h3>
<p>将构造函数设为私有,然后禁用拷贝构造与赋值重载。再提供静态getObj函数用于返回单一对象</p>
<div class="jb51code"><pre class="brush:cpp;">class SingleObj
{
// ... 省略成员变量
private:
SingleObj()
{
// ...
};
public:
SingleObj(const SingleObj&) = delete;
SingleObj& operator=(const SingleObj&) = delete;
static SingleObj& getObj()
{
static SingleObj obj; // 局部的静态,第一次运行到这里才会被初始化
return obj;
}
void func()
{
cout << "void func()" << endl;
}
};
void test()
{
SingleObj& s = SingleObj::getObj();
s.func();
}
</pre></div>
<p class="maodian"></p><h2>总结 </h2>
<p>到此这篇关于C++类型转换、IO流与特殊类的设计的文章就介绍到这了,更多相关C++类型转换、IO流与特殊类设计内容请搜索琼殿技术社区以前的文章或继续浏览下面的相关文章希望大家以后多多支持琼殿技术社区!</p>
<div class="art_xg">
<b>您可能感兴趣的文章:</b><ul><li>C++类型转换和IO流操作处理教程</li></ul>
</div>
</div>
<!--endmain-->
頁:
[1]