• C++类型转换+特殊类的设计+单例模式+IO流+空间配置器


    类型转换

    (1).C语言的类型转换

    两种类型转换

    • 隐式类型转换:编译器自动执行,相近类型才能成功

    • 显示的强制类型转换
      缺点

    • 隐式类型转换转换可能会出现精度的丢失

    • 显示类型转换将所有情况混合在一起,代码不够清晰

    (2).C++四种类型转换

    a.static_cast

    用于相近之间的类型,与上述隐式类型转换差不多。

    double a = 3.14;
    	int b = static_cast<int>(a);
    
    • 1
    • 2
    b.reinterpret_cast

    不相近之间的类型转换,对应C语言之前的强制类型转换

    double a = 3.14;
    	int b = static_cast<int>(a);
    	int* p = reinterpret_cast<int*>(b);
    
    • 1
    • 2
    • 3
    c.const_cast

    去掉对象const属性,方便赋值

    const int x = 2;
    	int* ptr = const_cast<int*>(&x);
    	*ptr = 3;
    
    • 1
    • 2
    • 3
    d.dynamic_cast

    (前提是必须要有虚函数)

    用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)
    向上转换:子类对象指针/引用——>父类对象指针/引用(切片)
    向下转换:用dynamic_cast更安全
    dynamic_cast只能用于含父类虚函数的类
    dynamic_cast会先检查受否转换成功,能成功则转换,不能则返回0

    应用场景:
    如果指针原本是指向子类的,然后由于切片传参,进入函数后再转换成子类是可以的,但是如果始终是指向父类的,如果不用dynamic_cast此时就是强制类型转换,会出些野指针的问题,用dynamic_cast会检查转换是否成功。

    class A
    {
    public:
    	virtual void f(){}
    };
    class B :public A
    {
    public:
    	virtual void f() {
    
    	}
    };
    void Fun(A* ptr)
    {
    	if (dynamic_cast<B*>(ptr))
    		cout << "转换成功" << endl;
    	else
    	{
    		cout << "转换失败" << endl;
    	}
    }
    
    int main()
    {
    	
    	A a;
    	B b;
    	A* pa = &a;
    	B* pb = &b;
    	Fun(pa);//转换失败
    	Fun(pb);//转换成功
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    volatile关键字(了解即可)

    是一个类型修饰符
    在这里插入图片描述

    RTTL

    RTTL(Run-time Type identification)运行时类型识别

    在这里插入图片描述

    特殊类的设计

    (1).设计一个类不能支持拷贝

    拷贝只会发生在两个场景中:拷贝构造和赋值运算符重载

    - 将拷贝构造和赋值运算符重载设计成私有并且只声明不定义
    设置成私有是为了防止用户在类外定义,不定义是为了万一定义了,在类内部调用

    - c++11直接在拷贝和赋值重载函数后面加delete,编译器删除该默认成员函数

    (2).设计一个类,只能在堆上创建对象

    class OnlyHeap
    {
    public:
    	static OnlyHeap* CreatObject()
    	{
    		return new OnlyHeap;
    	}
    	static void DelObj(OnlyHeap* ptr)
    	{
    		delete ptr;
    	}
    private:
    	//构造和拷贝构造都是设置为私有,此时无法调用
    	OnlyHeap(){}
    	OnlyHeap(const OnlyHeap& st);
    	~OnlyHeap() {}
         /*析构函数私有的话,栈和静态的对象就不能
    	 创建,因为此时其不能调用析构函数,同时
    	 堆创建的对象也无法delete,此时写一个静态
    	 析构函数如上即可*/
    };
    void test1()
    {
    	//OnlyHeap h1;//栈上
    	//static OnlyHeap h2;//静态区
    	//OnlyHeap* h3 = new OnlyHeap;//堆区
    	上述都要调用构造函数,此时将构造函数设置为私有即可
    	//OnlyHeap h4(h1);//拷贝构造在栈上生成对象、
    	此时将拷贝构造也设置为私有即可
    	OnlyHeap* h5 = OnlyHeap::CreatObject();//此时达到了只能在堆上创建对象
    	//delete h5;//此时delete也会调用私有的析构函数,一样报错
    	OnlyHeap::DelObj(h5);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    (3).设计一个类,只能在栈上创建对象

    class OnlyStack
    {
    public:
    	static  OnlyStack CreatObject()
    	{
    		return OnlyStack();
    	}
    	//禁掉operator new
    	/*如果类重载了operator new此时new这个对象时
    	就会走重载的operator而不会走库中的operator new
    	被禁掉了之后就无法再调用一个对象了。*/
    private:
    	//构造和拷贝构造都是设置为私有,此时无法调用
    	OnlyStack():_a(0) {}
    	OnlyStack(const OnlyHeap& st) = delete;
    	//删除拷贝构造是因为防止出现以下这种情况
    	//sattic OnlyStack copy(h1);//此时也是拷贝构造
    private:
    	int _a;
    };
    void test_Only_Stack()
    {
    	OnlyStack h1 = OnlyStack::CreatObject();//允许通过
    	OnlyStack* h2 = new OnlyStack;//报错
    	static OnlyStack h3;//报错
    	sattic OnlyStack copy(h1)//报错
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    (4).设计一个类,不能被继承

    1. 构造函数私有化,派生类不能调用基类构造函数,无法继承
    2. final关键字表示该类不能被继承
      eg
    class A final
    {};
    
    • 1
    • 2

    单例模式

    一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

    a.饿汉模式

    无论程序后面是否使用,程序启动时就创建一个唯一的实例对象。

    class SingLeton
    {
    public:
    	static SingLeton* GetInstance()
    	{
    		return  m_instance;
    	}
    	void Print()
    	{
    		cout << "单例对象调用" << endl;
    	}
    
    private:
    	//构造函数私有此时无法创建其他对象
    	SingLeton(int a):_a(a)
    	{
    		//信息输入
    	}
    	//防拷贝
    	SingLeton(const SingLeton& st) = delete;
    	SingLeton& operator=(const SingLeton& st) = delete;
    
    	static SingLeton* m_instance;//静态成员变量
    	int _a;
    };
    SingLeton* SingLeton::m_instance = new SingLeton(2);
    //静态成员变量定义在类域中不受访问限定符的限制
    //所以即使构造函数是私有的,此时单例对象依旧可以初始化
    void test_SingLeton()
    {
    	SingLeton::GetInstance();//这个类的单例对象
    	SingLeton::GetInstance()->Print();
    	/*SingLeton h1;
    	SingLeton *h2 = new SingLeton;
    	SingLeton copy(*SingLeton::GetInstance());
    	此时构造函数和拷贝构造都是没用的,保证
    	整个类中只有一个实例化对象*/
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    优点:不需要加锁,时间简单
    缺点:可能会导致进程启动慢,且多个单例类对象实例启动顺序不一定。

    eg:如果在main函数前就初始化单例对象,有可能构造函数需要进行的操作太多,这样的话,就可能会影响main函数的启动,所以一个大型程序启动慢的话,我们不清楚是构造函数太复杂(连接数据库什么的)还是整个程序卡死了。
    eg:要求先初始化数据库对象,再初始化缓存对象,饿汉模式可能因为控制不住初始化的顺序而报错,都是在main函数之前初始化,顺序不确定。
    懒汉模式可以控制——第一次调用时初始化

    b.懒汉模式

    一开始不创建对象,第一调用GetInstance再创建对象
    涉及到加锁问题,实现后面补上

    总结

    • 饿汉特点:简单一点,初始化顺序不确定,如果有依赖关系就会有问题,恶汉对象初始化慢且多个恶汉单例对象会影响程序启动
    • 懒汉特点:复杂一点,第一次调用时初始化,可以控制初始化顺序,延迟加载初始化,不影响程序启动推荐
    • 一般情况,单例模式不需要delete,因为整个程序中一直都要使用,程序结束了就会将空间还给操作系统。

    IO流

    (1).C语言的输入与输出

    • scanf:从标准输入设备(键盘)读取数据,并将值存放在变量中,printf():将指定的文字/字符串输入到标准输出设备(屏幕)C语言借助了相应的缓冲区来进行输入输出

    在这里插入图片描述
    理解缓冲区:

    • 可以屏蔽掉低级I/O的实现,低级I/O的实现依赖操作系统本身内核的实现,所以如果能够屏蔽这部分的差异,可以很容易写出可移植的程序
    • 可以实现"行"读取的行为,因为对计算机而言是没有"行"这个概念的,有了这部分就可以定义"行的概念"

    (2).what is 流

    即是流动的意思,是物质从一处向另一处流动的过程,是对一种有序连续且具有方向性的数据(其单位可以是bit,byte,packet)的抽象描述。
    C++流是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设备(显示器)输出的过程。
    特性:有序连续,具有方向性

    • 为了实现这种流动,c++定义了I/O标准库,这些每个类都被称为流/流类,以完成某方面的功能

    (3).C++标准IO流

    c++系统实现了一个庞大的类库,其中ios为基类,其他类都是直接或间接派生自ios类

    在这里插入图片描述
    c++标准库提供了四个全局对象cin,cout,cerr,clog
    cin:标准输入即数据通过键盘输入到程序中
    cout:标准输出即数据从内存流流向控制台(显示器)
    cerr:用来标准错误的输出
    clog:进行日志的输出
    其中cout,cerr,clog是ostream类的三个不同对象,因此三个对象现在基本没区别,指示应用场景不同。

    cout << "hello world" << endl;
    	cerr << "hello world" << endl;
    	clog << "hello world" << endl;
    	//三者输出都是一样的结果
    
    • 1
    • 2
    • 3
    • 4

    cin与cout可以直接输入和输出内置类型数据,标准库已经将所有内置类型的输入和输出全部重载了。
    在这里插入图片描述
    在这里插入图片描述

    为什么在大数据输入的时候cin要比scanf慢?
    因为cin是c++,它要同步C语言的输入流,如果输入的时候一下是cin,一下是scanf,两边的输入都不是直接到控制台,而是到缓存区,此时cin需要控制输入顺序什么的,而且scanf输入的时候类型%就确定了,而cin还需要推断。

    (4).C++文件IO流

    C++文件数据格式分为二进制文件文本文件
    ifstream ifile(只输入用)
    ofstream ofile(只输出用)
    fstream iofile(既输入用又输出用)
    在这里插入图片描述
    在这里插入图片描述

    struct ServerInfo
    {
    	char _address[32];
    	//string _address;
    	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);
    		//out:打开文件是为了写
    		//binary:以二进制的方式打开
    		//总体就是以二进制的方式将info写入_filename
    		ofs.write((const char*)&info, sizeof(info));
    	}
    	void ReadBin(ServerInfo& info)
    	{
    		ifstream ifs(_filename, ios_base::in | ios_base::binary);
    		//in:打开文件是为了读
    		//binary:以二进制的方式打开
    		//总体就是以二进制的方式将_filename中的内容读入info
    		ifs.read((char*)&info, sizeof(info));
    	}
    	void WriteText(const ServerInfo& info)
    	{
    		ofstream ofs(_filename);//默认是文本的方式
    		//将info中的内容以文本的方式写入到文件中
    		ofs << info._address << " " << info._port << " " << info._date;
    	}
    	void ReadText(ServerInfo& info)
    	{
    		ifstream ifs(_filename);
    		//将文件中的内容以文本的方式读入到info中
    		ifs >> info._address >> info._port >> info._date;
    	}
    private:
    	string _filename; // 配置文件
    };
    
    int main()
    {
    	ServerInfo winfo = { "192.0.0.1", 80, { 2021, 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    在这里插入图片描述

    (5).stringstream的简单了解

    包含头文件sstream
    该头文件下,标准库三个类:istringstream、ostringstream 和 stringstream,分别用来进行流的输入、输出和输入输出操作

    C语言中将整型变量转换成一个字符串

    1. 使用itoa()函数
    2. 使用sprint()函数
      两个函数转换前需要给出保存结果的空间,但空间给多大,不好界定,并且如果转换格式不匹配,可能会得到错误信息
    int n = 123456;
    	char s1[32];
    	_itoa(n, s1, 10);
    	char s2[32];
    	sprintf(s2, "%d", n);
    	char s3[32];
    	sprintf(s3, "%f", n);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. stringstream实际是在其底层维护了一个string类型的对象用来保存结果,因此多次数据类型转化时,一定要用clear()来清空才能正确转换,但clear不会将底层的string对象清空
    2. 因为底层就是string,所以stringstream常用来字符串的拼接,且使用s.str(“”)将底层对象设置为空字符串,此时用clear没用
      eg
    stringstream  s;
    	s << "a" << " " << "b";
    	cout << s.str() << endl;//输出a b
    	s.clear();
    	s << "c";
    	cout << s.str() << endl;//输出a bc
    	s.str("");
    	s << "d";
    	cout << s.str() << endl;//输出d
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    1. 使用s.str()返回stringstream底层的string对象
    2. stringstream使用string类对象代替字符数组,可以避免缓冲区溢出的危险,而且其会对参数类型进行推演,不需要格式化控制,也不会出现格式化失败的风险,因此使用更方便,更安全。
      在这里插入图片描述

    空间配置器

    (1).什么是空间配置器

    空间配置器,顾名思义就是为各个容器高效的管理空间(空间的申请与回收)的默默工作。

    在这里插入图片描述

    (2).空间配置器实现大致原理

    在这里插入图片描述
    在这里插入图片描述
    C++告一段落,开始Linux新篇章

  • 相关阅读:
    vueRouter 重定向 高亮 传参 嵌套 简单示例
    如何使用Jupyter Notebook
    想做FP独立站?众多平台他为何脱颖而出
    redux原理分享
    C++ Reference: Standard C++ Library reference: C Library: cctype: iscntrl
    累计注意力大模型
    错误模块路径: ...\v4.0.30319\clr.dll,v4.0.30319 .NET 运行时中出现内部错误,进程终止,退出代码为 80131506。
    Guava环境设置
    Compose的一些小Tips - 生命周期
    动动嘴即可搜视频 抖音与Siri达成合作
  • 原文地址:https://blog.csdn.net/cxy_zjt/article/details/127863312