当前位置: 首页 > news >正文

C++——特殊类设计 类型转换 IO流

目录

特殊类设计

类型转换

IO流

二进制读写

文本读写

stringstream


特殊类设计

设计一个类,不能被拷贝

        C++11之前实现时是把拷贝构造进行私有化

class NoCopy
{
public:NoCopy() {}
private:NoCopy(const NoCopy&) {};NoCopy& operator=(const NoCopy&) {};
};

        C++11使用 delete 关键字显示删除指定函数(一般用于类的构造,拷贝构造,赋值重载)

class NoCopy
{
public:NoCopy() {}NoCopy(const NoCopy&) = delete;NoCopy& operator=(const NoCopy&) = delete;
};

设计一个类,只能在栈上创建

        方法一

  • 提供一个构造对象方法;
  • 把构造函数私有化,拷贝构造,赋值重载私有化或者 delete 都可以
class StackOnly
{
public:static StackOnly GetStackOnly(){return StackOnly();}
private:StackOnly() {};StackOnly(const StackOnly&) {};StackOnly& operator=(const StackOnly&) {};
};

        拷贝构造,赋值重载私有化是为了不能间接 new 对象时在实现在堆上开辟

StackOnly c = StackOnly::GetStackOnly();
StackOnly* c1 = new StackOnly(c);

        所以可以把 operator new,operator delete 私有或者设置delete即可

class StackOnly
{
public:static StackOnly GetStackOnly(){return StackOnly();}//new不出来void* operator new (size_t size) = delete;void operator delete(void* ptr) = delete;
private:StackOnly() {};
};

设计一个类,只能在堆上开辟空间

        与上一个类似

class HeapOnly
{
public:static HeapOnly* GetHeapOnly(){return new HeapOnly;}
private:HeapOnly() {};HeapOnly(const HeapOnly&) {};HeapOnly& operator=(const HeapOnly&) {};
};

设计一个类,不能被继承

        C++11之前是将构造函数私有化;C++11提供 finial 关键字修饰使其不能被继承

设计一个类,只能创建一个对象

        使用单例模式来设计,它分为两种:一种是饿汉模式:先创建对象后调用,一种是懒汉模式:在创建对象时调用

        饿汉模式的实现:

  • 先在类中创建对象(静态对象成员在类中声明,在类外实现)
  • 设计一个函数返回地址
  • 把构造,拷贝构造,赋值重载私有化
class hungry
{
public://使用指针返回,传值返回要拷贝构造static hungry* GetInstance(){return &_h;}
private:hungry() {cout << "hungry()" << endl;};hungry(const hungry&) {};hungry& operator=(const hungry&) {};static hungry _h;//声明
};
hungry hungry::_h;//定义后分配空间

        调试时在进入main函数之前就把对象构造出来了

        饿汉模式的缺点

  • 在main之前先初始化资源,可能初始化的资源太多(可能有些暂时不需要用到的也初始化了),导致进入mian函数时间变长
  • 使用饿汉模式实现的两个对象之间有依赖关系:比如一个对象用来连接数据库,另一个对象用来访问数据库,谁先初始化完成是不确定的:如果用来访问数据库的对象先完成初始化则会访问数据库失败

        而懒汉模式则完成解决上述两个问题,共有两种方法实现

        第一种:(C++11)函数内部定义静态对象返回,每次返回都是同一个对象

class lazy
{
public:static lazy& GetInstance(){//C++11才能使用(保证线程安全)static lazy la;return la;}
private:lazy() {};lazy(const lazy&) {};lazy& operator=(const lazy&) {};
};

        第二种:(C++11之前)调用时,如果对象没有创建就 new,创建了就返回;也可能是多线程同时来创建对象导致对象创建两份,有线程安全问题需要加锁来保证

class lazy
{
public:static lazy* GetInstance(){//Double Check:效率得到保证if (_la == nullptr){//这里加锁虽然保证线程安全,但没有效率(下次来还要等待)lock_guard<mutex> mtx(_mtx);if (_la == nullptr){_la = new lazy;}}return _la;}
private:lazy() {};lazy(const lazy&) {};lazy& operator=(const lazy&) {};static lazy* _la;static mutex _mtx;
};
lazy* lazy::_la = nullptr;
mutex lazy::_mtx;

类型转换

        C语言中有两种类型转换,一种是隐式类型转换:常见于整形之间转换,不是整形截断就是整形提升;它是在编译阶段自动转换,能转就转,不能转就编译失败

int i = 10;
char j = i;
long long k = i;

        还有一种是显示类型转换:常见于指针之间,整形与指针的转换;需要用户自行处理类型

int i = 10;
int* p1 = &i;
int j = (int)p1;
char* p2 = (char*)p1;

        C++有了自定义类型(同时为了兼容C语言),可以进行

  • 内置类型转自定义类型
class A
{
public:A(int a1=0,int a2=0):_a1(a1),_a2(a2){}
private:int _a1;int _a2;
};int main()
{A a = 1;//C++11A aa = { 1,2 };
}
  • 自定义类型转内置类型
class A
{
public:A(int a1=0,int a2=0):_a1(a1),_a2(a2){}//类型转换operator int(){return _a1;}
private:int _a1;int _a2;
};int main()
{A a = 1;int i = a;
}
  • 自定义类型转自定义类型
class A
{
public:A(int a1=0,int a2=0):_a1(a1),_a2(a2){}int get() const{return _a1 + _a2;}
private:int _a1;int _a2;
};class  B
{
public://通过构造来转化B(const A& a):_b1(a.get()){}private:int _b1;
};int main()
{A a = { 1,2 };B b = a;
}

        C++为了类型转换的可视性,提供了四个类型转换操作符

  • static_cast 隐式类型转换
int a = 0;
char ch = static_cast<char>(a);
  • reinterpret_cast 显示类型转换
int i = 10;
int* a = &i;
int j = reinterpret_cast<int>(a);
  • const_cast 删除变量const属性
//volatile 修饰变量让CPU每次从内存取变量a
volatile const int a = 1;
int* ptr = const_cast<int*>(&a);//提示你变量a前加volatile
(*ptr)++;
  • dynamic_cast 安全进行父子类对象的转换,但只能使用父类引用/指针进行转化,且父类对象需要包含虚函数
class A
{
public:virtual void fun(){}int _a = 0;
};
class B :public A
{
public:int _b = 0;
};void fun(A* ptr)
{B* b = dynamic_cast<B*>(ptr);if (b == nullptr){cout << "转换失败" << endl;//b->_b++; 父转子没有子类的_b}else{	cout << ++b->_b << endl;}
}int main()
{A* p1 = new A;B* p2 = new B;fun(p1);//父转子预期成功 -> 切片fun(p2);//子转父预期失败return 0;
}

IO流

        在C语言中使用 scanf,printf,fscanf,fprintf...的方式进行(文件)输入输出,本质是把数据从一个文件转移到另一个文件中,我们把这一过程抽象为流的概念(数据流在文件中的流动)

        由于C语言是面向过程,提供各种函数来实现数据流动;C++则是使用IO流的体系,以对象的方式进行使用

        做题时如果有多个用例处理的逻辑,可以使用cin的方式持续读取

string str;
while(cin>>str)
{//...
}

        cin读取键盘的数据把数据流插入给str,这是调用了string对>>重载实现的

        为什么能够通过返回的 istream 对象作为 while 的条件判断?        因为 istram 中重载 bool 函数类型转换;返回的 istream 对象会自动调用该函数进行判断(与上面的自定义类型赋值给内置类型的实现方式类似)

如果要让循环结束怎么办?

        最暴力的方法是 ctrl + c 终止程序;但一般不这么做,使用 ctrl + z的方式让bool返回值为false从而终止循环,根据什么标准来判断返回true false?根据四个标记位:

        ctrl + z 就是设置了 eofbit 和 failbit

int main()
{string s;while (cin >> s){cout << cin.good() << endl;cout << cin.eof() << endl;cout << cin.fail() << endl;cout << cin.bad() << endl;cout << s << endl;}cout << cin.good() << endl;cout << cin.eof() << endl;cout << cin.fail() << endl;cout << cin.bad() << endl;return 0;
}

二进制读写

        准备 Date 类和 info 类

class Date
{
public:Date(int year = 0, int month = 0, int day = 0):_year(year), _month(month), _day(day){}friend ostream& operator <<(ostream& cout, const Date& d){cout << d._year << " " << d._month << " " << d._day;return cout;}
private:int _year;int _month;int _day;
};class info
{
public:info(const char* ip = "", int port = 0,const Date& d = Date()):_port(port),_d(d){strcpy(_ip, ip);}private:char _ip[128];int _port;Date _d;
};

        C语言的二进制读写

void fwrite_binary(info* in)
{FILE* f = fopen("C:/Users/29096/Desktop/log.txt", "wb");fwrite(in, sizeof(info), 1, f);fclose(f);
}
void fread_binary(info* in)
{FILE* f = fopen("C:/Users/29096/Desktop/log.txt", "rb");fread(in, sizeof(info), 1, f);fclose(f);
}

        C++的二进制读写,使用类来封装

void write_binary()
{ofstream ofs("C:/Users/29096/Desktop/log.txt", ostream::out | ostream::binary);ofs.write(reinterpret_cast<const char*>(this),sizeof(info));
}
void read_binary()
{ifstream ifs("C:/Users/29096/Desktop/log.txt", istream::in | istream::binary);ifs.read(reinterpret_cast<char*>(this), sizeof(info));
}

        二进制读写都是把对象在内存中储存的数据直接保存在文件中,在以相同的方式给读出来,在大部分情况下可以怎么做,但对象里面储存的是string呢?

class info
{
public:info(const char* ip = "", int port = 0,const Date& d = Date(), const string& s = string()):_port(port), _d(d), _s(s){strcpy(_ip, ip);}void write_binary(){//二进制写ofstream ofs("C:/Users/29096/Desktop/log.txt", ostream::out | ostream::binary);ofs.write(reinterpret_cast<const char*>(this),sizeof(info));}void read_binary(){//二进制读ifstream ifs("C:/Users/29096/Desktop/log.txt", istream::in | istream::binary);ifs.read(reinterpret_cast<char*>(this), sizeof(info));}void Print(){cout << _ip << " " << _port << " " << _d << endl;}
private:char _ip[128];int _port;Date _d;string _s;
};

        (第一个进程)写的时候没问题

        (第二个进程)读的时候程序直接就崩溃了

        写入的string是它的成员变量:_str,_size,_capacity:_str 是字符串地址,进程结束之后保存字符串的空间被销毁;后面读到该字符串地址是无效的(野指针),也就是说这个过程发生了浅拷贝的问题,在使用二进制读写时要特别注意该问题,尽量不要使用容器

文本读写

        使用C语言文本读写,使用 fscanf,fprintf

void fwrite()
{FILE* f = fopen("C:/Users/29096/Desktop/log.txt", "w");fprintf(f, "%s %d %d/%d/%d", _ip, _port, _d._year, _d._month, _d._day);fclose(f);
}
void fread()
{FILE* f = fopen("C:/Users/29096/Desktop/log.txt", "r");fscanf(f, "%s %d %d/%d/%d\n", &_ip, &_port, &_d._year, &_d._month,& _d._day);fclose(f);
}

        使用C语言比较麻烦,使用C++更便捷进行文本读写(使用流插入<<,流提取>>)

class Date
{
public:Date(int year = 0, int month = 0, int day = 0):_year(year), _month(month), _day(day){}friend ostream& operator <<(ostream& cout, const Date& d){cout << d._year << "/" << d._month << "/" << d._day;return cout;}friend istream& operator >>(istream& cin, Date& d){//文本读数据要把/去掉char op;cin >> d._year >> op;cin >> d._month >> op;cin >> d._day >> op;return cin;}
private:int _year;int _month;int _day;
};class info
{
public:info(const char* ip = "", int port = 0,const Date& d = Date(), const string& s = string()):_port(port), _d(d), _s(s){strcpy(_ip, ip);}void write(){ofstream ofs("C:/Users/29096/Desktop/log.txt");ofs << _ip << " " << _port << " " << _d << " " << _s;}void read(){ifstream ifs("C:/Users/29096/Desktop/log.txt");ifs >> _ip >> _port >> _d >> _s;}void Print(){cout << _ip << " " << _port << " " << _d << " " << _s << endl;}
private:char _ip[128];int _port;Date _d;string _s;
};

        写入数据文本数据可视化

stringstream

        根据用户输入的name,对sql语句的判断进行拼接

  • C语言可以使用数组与 sprintf 对数据进行处理

  • C++则是使用 string += 来完成

如果遇到的是对象比如拼接一个日期类,要怎么办?

        C语言获取到日期对象后,就只能 year month day 一个一个进行格式化输入;而C++则可以使用 ostringstream 来进行处理

        istringstream 与 ostringstream:一个支持所有流插入<<,一个支持所有流提取>>(前提是自定义类有进行重载实现),把所有string字符串看成字符流的形式(实现任意转换),但一般使用 stringstream(支持所有流插入流提取)

        使用 stringstream 拼接字符串与字符串解析

struct chat_info
{string _name;int _id;Date _d;string _info;
};int main()
{//张三给李四发消息chat_info c1 = { "张三",1,{2025,8,17},"今晚看电影吗?" };stringstream wss;//网络传输转字符串wss << c1._name << " " << c1._id << " " << c1._d << " " << c1._info;string s = wss.str();//对方电脑解析stringstream rss(s);chat_info c2;rss >> c2._name >> c2._id >> c2._d >> c2._info;cout << c2._name << " " << c2._id << " " << c2._d <<" "<< c2._info << endl;return 0;
}

以上便是全部内容,有问题欢迎在评论区指正,感谢观看!

http://www.dtcms.com/a/336071.html

相关文章:

  • Redis学习--集群 数据分片、哈希槽、集群配置、主从容错迁移、扩缩容
  • live555 rtsp server
  • 通达信【二板爆量涨停】副图/选股指标,首板次日继续强势封板,整合MACD和KDJ指标确保趋势向上,专注二板机会
  • 【计算机网络面试】TCP/IP网络模型有哪几层
  • Python中f - 字符串(f-string)
  • 软考 系统架构设计师系列知识点之杂项集萃(127)
  • 第2章 高并发IO的底层原理
  • 数据结构:二叉搜索树(Binary Search Tree)
  • 【Android】Activity创建、显式和隐式跳转、清单文件声明
  • Pytorch模型复现笔记-VGG讲解+架构搭建(可直接copy运行)+冒烟测试
  • MLArena:一款不错的AutoML工具介绍
  • 【股票数据API接口33】如何获取股票所属指数数据之Python、Java等多种主流语言实例代码演示通过股票数据接口获取数据
  • PCA 实现多向量压缩:首个主成分的深层意义
  • JZ57 和为S的两个数字
  • Traefik网关DNS解析超时问题优化
  • Agent开发进阶路线:从基础响应到自主决策的架构演进
  • C++类型转换详解:从C风格到C++风格
  • 如何理解事件循环和JS的异步?
  • LintCode第137-克隆图
  • PostgreSQL导入mimic4
  • SQL详细语法教程(四)约束和多表查询
  • C语言相关简单数据结构:双向链表
  • Rust Async 异步编程(五):执行器和系统 I/O
  • Effective C++ 条款47: 使用traits classes表现类型信息
  • 基于强化学习的柔性机器人控制研究
  • 【大模型微调系列-07】Qwen3全参数微调实战
  • 关于虾的智能养殖系统的开发与实现(LW+源码+讲解+部署)
  • 【LeetCode题解】LeetCode 33. 搜索旋转排序数组
  • 详解flink java基础(一)
  • 嵌入式软件--->任务间通信