C++:单例模型、强制类型转换

目录

  • 特殊类的设计
    • 不能被拷贝的类
    • 实现一个类,只能在堆上实例化的对象
    • 实现一个类,只能在栈上实例化的对象
    • 不能被继承的类
  • 单例模式
    • 饿汉模式
    • 懒汉模式
    • 饿汉模式与懒汉模式的对比
      • 饿汉优缺点
      • 懒汉优缺点
      • 懒汉模式简化版本(C++11)
    • 单例释放问题
  • C++强制类型转换
    • static_cast 静态转换
    • reinterpret_cast 不同类型转换
    • const_cast 去除变量的const属性
    • dynamic_cast 动态转换

特殊类的设计

不能被拷贝的类

一个类能不能实现拷贝功能,要看两个默认成员函数:拷贝构造赋值重载

要实现一个不能被拷贝的类,就要防止拷贝构造赋值重载这两个函数的生成。

在类中,如果我们不去手动实现默认成员函数的话,编译器会自动生成默认的成员函数(像 构造、析构 拷贝构造 与 赋值重载等)

在C++11之前,为了避免编译器自动生成 拷贝构造赋值重载,可以将这两个函数直接进行声明,不去实现。但是直接声明的方式,防不住类外的人进行定义实现。

对此,可以直接将 拷贝构造赋值重载 声明设置为私有。

class CopyBan
{
public:
	CopyBan(){}

private:
	//将拷贝构造与赋值重载设置为私有
	CopyBan(const CopyBan&);
	CopyBan& operator=(const CopyBan&);
};

int main()
{
	CopyBan cb1;
	CopyBan cb2(cb1); //error
	return 0;
}

在这里插入图片描述

C++11标准出来后,拓展了 delete 关键字 的使用。如果要让编译器不生成默认的成员函数,直接在这个成员函数后加上 delete 关键字即可。对此,上面的代码可以改写为:

class CopyBan
{
public:
	CopyBan(){}
	
	//让编译器删除掉默认成员函数
	CopyBan(const CopyBan&) = delete;
	CopyBan& operator=(const CopyBan&) = delete;
};

int main()
{
	CopyBan cb1;
	CopyBan cb2(cb1); //error
	return 0;
}

在这里插入图片描述
这样就实现了一个不能被拷贝的类了。

实现一个类,只能在堆上实例化的对象

实现这个类有两种方法,先来介绍第一种:将析构函数设置为私有

class HeapOnly
{
public:
	HeapOnly(int x = 0)
		:_x(x)
	{}

private:
	~HeapOnly() //设置为私有
	{
		cout << "~HeapOnly()" << endl; 
	}
	int _x;
};

int main()
{
	HeapOnly hoy(1); //error 会调用析构
	return 0;
}

在这里插入图片描述
hoy 对象在程序运行结束后会调用析构函数,因为析构函数是私有的,调不动。对此,只能在堆上实例化对象:

int main()
{
	HeapOnly* hoy = new HeapOnly(1);
	return 0;
}

但是,如何去释放资源呢?

可以这样,在类中实现一个成员函数去调用析构函数:

class HeapOnly
{
public:
	HeapOnly(int x = 0)
		:_x(x)
	{}

	void DestroyHeap()
	{
		delete this; //调用析构
	}

private:
	~HeapOnly()
	{
		cout << "~HeapOnly()" << endl;
	}

	int _x;
};
int main()
{
	HeapOnly* hoy = new HeapOnly(1);
	hoy->DestroyHeap(); //利用成员函数去调用析构函数
	return 0;
}

在这里插入图片描述

下面来介绍第二种方法:将构造函数设置为私有


class HeapOnly
{
public:
	~HeapOnly()
	{
		cout << "~HeapOnly()" << endl;
	}
private:
	HeapOnly(int x = 0)  //将构造函数设置为私有
		:_x(x)
	{}
	int _x;
};

注意:这个方法会造成,在用 new 的时候也实例化不出对象

int main()
{
	HeapOnly hoy1(1); 				    //error
	static HeapOnly hoy2(1);			//error
	HeapOnly* hoy3 = new HeapOnly(1);   //error
	return 0;
}

在这里插入图片描述
解决办法:通过成员函数来调用构造函数,只不过要将这个成员函数要设置为静态的

利用静态成员函数,去调用构造函数,返回这个对象的指针即可:

class HeapOnly
{
public:
	static HeapOnly* CreateObje(int x) //利用静态成员函数去调用构造
	{
		HeapOnly* hoy = new HeapOnly(1);
		return hoy;
	}
	~HeapOnly()
	{
		cout << "~HeapOnly()" << endl;
	}
private:
	HeapOnly(int x = 0)
		:_x(x)
	{}
	int _x;
};

int main()
{
	HeapOnly* hoy = HeapOnly::CreateObje(1); //调用静态成员函数
	delete hoy; //释放资源

	return 0;
}

在这里插入图片描述

上面这个方法还需要考虑一个拷贝问题:

int main()
{
	HeapOnly* hoy = HeapOnly::CreateObje(1);
	HeapOnly hoy2(*hoy); //拷贝,在栈上开辟
	
	delete hoy;
	return 0;
}

构造被封死,但是拷贝构造没有。上面代码没有实现拷贝构造,但是编译器会自动生成一个。拷贝实例化出的对象是在栈上开辟,不符合案例的要求。对此,可以直接将拷贝与赋值直接禁用:

class HeapOnly
{
public:
	static HeapOnly* CreateObje(int x)
	{
		HeapOnly* hoy = new HeapOnly(1);
		return hoy;
	}
	~HeapOnly()
	{
		cout << "~HeapOnly()" << endl;
	}
private:
	HeapOnly(int x = 0)
		:_x(x)
	{}
	//禁用拷贝与赋值
	HeapOnly(const HeapOnly& hop) = delete;
	HeapOnly& operator=(const HeapOnly& hop) = delete;

	int _x;
};

实现一个类,只能在栈上实例化的对象

使用 new 关键字会调用构造函数,为了防止在堆上开辟对象。可以直接将拷贝构造设置为私有,通过静态成员的方式去调用构造函数:

class StackOnly
{
public:
	//利用静态成员函数进行实例化对象
	static StackOnly CreateObje(int x)
	{
		return StackOnly(x); //返回对象
	}
	
	//拷贝
	StackOnly(const StackOnly& soy)
		:_x(soy._x)
	{}

	~StackOnly()
	{
		cout << "~StackOnly()" << endl;
	}

private:
	StackOnly(int x)  //构造函数设置为私有
		:_x(x)
	{}
	int _x;
};

int main()
{
	//StackOnly* soy = new StackOnly(1);  //error new会调用构造
	StackOnly soy = StackOnly::CreateObje(1); //调用构造+拷贝构造
	return 0;
}

在这里插入图片描述
上面这样的方式防不住创建静态的对象:

int main()
{
	StackOnly soy = StackOnly::CreateObje(1); //调用构造+拷贝构造
	static StackOnly soy1 = soy; //会利用拷贝构造生成一个静态的对象
	
	return 0;
}

在这里插入图片描述
有小伙伴就会说,将拷贝构造禁用,就可以避免静态对象的生成了。

如果将拷贝构造禁用了,利用静态成员函数去实例化的方法会失效,因为用到的传值返回,需要用到拷贝构造。

传值返回是一个将亡值,那么可以生成一个移动构造不就解决了吗?

下面来实现一下:

class StackOnly
{
public:
	//利用静态成员函数进行实例化对象
	static StackOnly CreateObje(int x)
	{
		return StackOnly(x);
	}
	StackOnly(StackOnly&& soy) // 移动构造
		:_x(soy._x)
	{}
	~StackOnly()
	{
		cout << "~StackOnly()" << endl;
	}

private:
	StackOnly(const StackOnly& soy) = delete; //禁用拷贝
	StackOnly(int x)
		:_x(x)
	{}

	int _x;
};

int main()
{
	StackOnly soy = StackOnly::CreateObje(1); //移动构造
	//static StackOnly soy1 = soy; //error
	return 0;
}

在这里插入图片描述

但是,使用移动构造真的解决问题了吗?传值返回是一个将亡值,会去调用移动构造。但是,防不住有些人这样操作:

int main()
{
	StackOnly soy = StackOnly::CreateObje(1); //拷贝构造
	static StackOnly soy1 = move(soy); //将soy设置为将亡值,调用移动构造
	return 0;
}

在这里插入图片描述

使用静态成员函数去调用构造函数,通过传值返回对象的方式,很难去防止静态对象生成。很难禁掉,本质就是前者需要用到拷贝构造

不能被继承的类

在C++11之前,可以将类的构造函数设置为私有。子类对象如果继承了这个类的话,在实例化阶段就调不动父类的构造函数,从而达到无法继承的效果

C++11之后,推出了 final 关键字。final关键字修饰过类,不能被继承

class NonInherit final //final关键字修饰的类不能被继承
{
	//...
}

单例模式

  • 单例模式:一个类只能创建一个唯一的对象

该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享

实现单例模式的方式有两种:饿汉模式、懒汉模式

饿汉模式

在程序运行起来就实例化出这个对象,且为唯一的一份

记住单例模式要的是创建一个唯一的对象

举个示例,实现一个简单的饿汉模式:

要限制这个类实例化对象,将构造函数设置为私有、拷贝和赋值禁用

#include <iostream>
#include <string>
#include <vector>

class Singleton
{
public:
	~Singleton() //析构
	{
		std::cout << "~Singleton()" << std::endl;
	}
private:
	Singleton(){} //设置为私有,防止类外实例化对象
	Singleton(const Singleton& stn) = delete;
	Singleton& operator=(const Singleton& stn) = delete;
private:
	vector<string> _v;
	int _n = 0;
};

下面来考虑一下,如何实例化出唯一的一份对象。

可以定义一个Singleton* _ins静态成员指针,当然也可以是Singleton _ins对象(静态成员在整个类都是唯一的)。

在这里定义一个 Singleton* _ins的静态成员指针,在类中进行声明,在类外进行实例化。当然还要实现一个静态成员函数,用于获取这个静态成员指针。

为了方便演示,下面还增添一些功能代码:

#include <iostream>
#include <string>
#include <vector>

class Singleton
{
public:
	~Singleton() //析构
	{
		std::cout << "~Singleton()" << std::endl;
	}
	static Singleton* GetInstance() //用于获取对象指针
	{
		return _ins; //返回静态成员的指针,方便在类外部使用
	}
	
	void AddString(const string& str){ _v.push_back(str);}

	void Print()
	{
		for (auto& e : _v)
			cout << e << endl;
	}
private:
	Singleton(){} //设置为私有,防止类外实例化对象
	Singleton(const Singleton& stn) = delete;
	Singleton& operator=(const Singleton& stn) = delete;
private:
	vector<string> _v;
	int _n = 0;
	static Singleton* _ins; //声明静态成员指针
};

Singleton* Singleton::_ins = new Singleton; //初始化

int main()
{
	Singleton* sln = Singleton::GetInstance(); //获取对象的指针,唯一的
	sln->AddString("苹果");
	sln->AddString("香蕉");
	sln->AddString("梨");
	sln->AddString("西瓜");
	sln->Print();
	return 0;
}

在这里插入图片描述

一开始就通过静态成员指针或者是对象直接在类外初始化的方式被饿汉模式,也就是在 main 函数之前进行初始化。

饿汉模式 相对应的就是 懒汉模式

懒汉模式

懒汉模式与饿汉模式不同,并不是一上来就进行初始化。而是在第一次访问实例的时候进行初始化

对上述代码进行修改,变成懒汉模式。下面只展示修改的代码,方便对比:

class Singleton
{
public:
	~Singleton()
	{
		cout << "~Singleton()" << endl;
	}
	//懒汉处理
	static Singleton* GetInstance()
	{
		if (_ins == nullptr)
		{
			_ins = new Singleton; //懒汉模式的初始化
		}
		return _ins;
	}
	//... 其他成员函数
private:
	Singleton() {} //设置为私有,防止类外实例化对象
	Singleton(const Singleton& stn) = delete;
	Singleton& operator=(const Singleton& stn) = delete;
	
	vector<string> _v;
	static Singleton* _ins; //定义静态Singleton指针
};

//懒汉模式
Singleton* Singleton::_ins = nullptr; 

懒汉模式并不是一上来就进行初始化,而是将静态成员对象的指针的初始化放到静态成员函数内部进行

下面来测试一下懒汉模式:

int main()
{
	Singleton* sln = Singleton::GetInstance(); 
	sln->AddString("苹果");
	sln->AddString("香蕉");
	sln->AddString("梨");
	sln->AddString("西瓜");
	sln->Print();
	return 0;
}

在这里插入图片描述
有老铁就会说,这个 懒汉模式 和 饿汉模式 也没有多少变化啊。只是初始化方式不同而已。

但真的只是初始化有所不同吗?

饿汉模式与懒汉模式的对比

饿汉优缺点

先来分析饿汉模式缺点:

  1. 当一个单例对象很大
  • 当这个单例对象不需要使用时,会占用资源(main函数之前就要进行资源的申请)
  • 会影响程序的启动(程序在还没有启动前就要初始化一段时间)
  1. 当一个程序中有两个单例对象都是使用的饿汉模式,并且相互之间存在依赖关系。要求单例1对象先创建,单例2对象后创建。饿汉模式无法控制创建的顺序

饿汉模式的优点:创建单例对象很简单(相较于懒汉模式)

懒汉优缺点

优点:懒汉模式完美解决了饿汉模式的缺点

缺点:懒汉模式会造成线程安全问题

回过头来看看懒汉模式下的初始化那段代码,同样的为了方便查阅,只展示局部代码:

//懒汉处理
static Singleton* GetInstance()
{
	if (_ins == nullptr)
	{
		_ins = new Singleton; //懒汉模式的初始化
	}
	return _ins;
}

有没有这样的一种情况:在多线程下,有两个甚至多个线程同时进行 new 对这个单例进行初始化,造成 _ins 指针被多次赋值

线程执行总得有个先后顺序,假设线程一先执行了初始化的工作,正在调用其他函数执行其他的功能。此时,线程二来了,直接进行 new 操作申请了一块新的资源空间,然后赋值给 _ins。线程一前面执行的函数(像是读写操作),就会被覆盖了做无用功。

相对比懒汉模式,饿汉模式就没有线程安全问题,因为饿汉模式在 main 程序执行前直接就进行了初始化。

对此,实现懒汉模式的单例需要引入互斥锁来解决线程安全问题。

通过双检查加锁来处理线程安全问题:只需要保证第一次加锁的情况

class Singleton
{
public:
	~Singleton()
	{
		cout << "~Singleton()" << endl;
	}
	static Singleton* GetInstance()
	{
		//懒汉线程安全问题的处理
		//双检查加锁:解决线程安全问题
		if (_ins == nullptr)
		{
			//上锁
			_mtx.lock();
			if (_ins == nullptr)
			{
				_ins = new Singleton; //保证一次创建
			}
			//解锁
			_mtx.unlock();
		}

		return _ins;
	}
private:
	Singleton() {} //设置为私有,防止类外实例化对象
	Singleton(const Singleton& stn) = delete;
	Singleton& operator=(const Singleton& stn) = delete;
	
	vector<string> _v;
	static mutex _mtx;
	static Singleton* _ins; //定义静态Singleton指针
};

Singleton* Singleton::_ins = nullptr;
mutex Singleton::_mtx; //初始化互斥锁

双检查加锁:

  • 第一次 if 判断是为了提高效率,避免多个线程在访问这块代码的时候,多次进行上锁解锁操作
  • 第二个 if 判断是为了保证线程安全,只进行一次资源的申请

正是因为线程安全问题,懒汉模式对单例的初始化就变得复杂起来(对比饿汉模式)

懒汉模式简化版本(C++11)

前提声明:简化版本的懒汉模式只适合C++11,C++11之前使用这个版本的懒汉模式不能保证线程安全问题

C++11之后,局部的静态成员变量在进行初始化,当其他线程来获取时会进入阻塞状态,直到初始化成功,从而保证了线程安全问题。

下面来实现一个简化版本的懒汉模式:

实现不需要用到互斥锁,也不需要定义静态的单例对象的指针。在静态成员函数中定义一个静态的单例对象,使用的时候,返回这个静态的单例对象地址即可(当然也可以返回这个对象的引用,看个人的喜欢)

class Singleton
{
public:
	~Singleton()
	{
		cout << "~Singleton()" << endl;
	}
	static Singleton* GetInstance()
	{
		//懒汉模式的简化实现
		static Singleton inst;
		return &inst; //返回这个对象的地址
	}
	
	//...其他成员函数
private:
	Singleton() {} 
	Singleton(const Singleton& stn) = delete;
	Singleton& operator=(const Singleton& stn) = delete;
	
	vector<string> _v;
};
int main()
{
	Singleton* sln = Singleton::GetInstance();
	return 0;
}

在这里插入图片描述
局部的静态变量有一个特点:只会初始化一次

局部的静态变量只有当程序运行到才会初始化;全局的静态变量在程序运行起来之前就进行初始化

单例释放问题

一般情况下,全局都要使用单例对象,所以单例对象不需要显示的释放。程序结束,资源也就被回收了。

但是有些特殊情况还是会考虑单例对象资源释放的。可以提供一个静态成员函数去调用析构函数;

当然,如果怕内存泄漏的话可以提供一个内部类。在单例类中定义内部类的对象,当内部类对象生命周期结束后,析构函数去调用单例的提供的释放资源的函数。从而达到单例对象指针的资源释放的功能。

具体可以参考以下代码:

class Singleton
{
public:
	static Singleton* GetInstance()
	{
		// 双检查加锁
		if (_ins == nullptr) 
		{
			_imtx.lock();
			if (_ins == nullptr)  
			{
				_ins = new Singleton;
			}
			_imtx.unlock();
		}

		return _ins;
	}

	static void DelInstance() //释放资源的函数
	{
		_imtx.lock();
		if (_ins)
		{
			delete _ins;
			_ins = nullptr;
		}
		_imtx.unlock();
	}

	// 内部类:单例对象回收
	class GC
	{
	public:
		~GC()
		{
			DelInstance();
		}
	};

	~Singleton()
	{
		// 持久化
		// 比如要求程序结束时,将数据写到文件,单例对象析构时持久化就比较好
	}

private:
	// 限制类外面随意创建对象
	Singleton()
	{}

	// 防拷贝
	Singleton(const Singleton& s) = delete;
	Singleton& operator=(const Singleton& s) = delete;

private:
	vector<string> _v;
	static GC _gc;

	static Singleton* _ins;
	static mutex _imtx;
};

单例模式就介绍到这里,下面来介绍一下C++的类型转换:

C++强制类型转换

在 C语言 中如果遇到 赋值运算符左右两侧类型不同,或者形参与实参类型不匹配,或者返回值类型与接收返回值类型不一致时,就需要发生类型转化

C语言的类型类型转换可以分成两种:隐式类型转换强制类型转换

  • 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败
  • 显式类型转化:需要用户自己处理

C语言的隐式类型转换有时候会造成一些莫名的 BUG,来看这样的一段代码:

假设这段代码是完成数组的插入功能

void Insert(size_t pos, char ch)
{
	int end = _size; // _size是数组个数
	while(end >= pos)
	{
		//...
		--end;
	}
}

上面这段代码中,乍一看好像没有什么问题,指定 pos 位置进行字符的插入。

但是,仔细一点可以看到 end 变量是 int 类型,pos 是 size_t 类型。在进行大小比较的时候就会发生类型转化,end 变量会转换为 size_t 类型

一般的,pos是大于0的数还好,一旦 pos为 0 时,end 是无符号整型,当end减到0时,循环体再进行end-- 操作,end 会变成负1吗?不会,end 会变成一个很大的无符号整数。程序就进入了死循环

像上面隐式类型转换很难去察觉。

C语言的显式类型转换将所有情况混合在一起,也会造成代码不够清晰。

面对C语言类型转换出现的缺点,C++要说将C语言这套类型转换全部丢掉是不可能的,因为C++要兼容C语言。

对此,为了规范类型转换,C++搞出了属于自己的一套类型转换方式。

标准C++为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:

static_castreinterpret_castconst_castdynamic_cast

static_cast 静态转换

  • static_cast 用于非多态类型的转换(静态转换),static_cast 它不能用于两个不相关的类型进行转换

示例:

int main()
{
	//静态转换
	double b = 11.22;
	int a = static_cast<int>(b); //a、b变量为相似类型可以进行转换
	cout << a << endl;
	
	return 0;
}

在这里插入图片描述

如果两个不相近的类型使用 static_cast会怎么样?

int main()
{
	//静态转换
	double b = 11.22;
	int a = static_cast<int>(b); //a、b变量为相似类型可以进行转换
	int *p = static_cast<int*>(a);//p是指针、a是整形类型不相近
	
	return 0;
}

编译器会直接报错:
在这里插入图片描述
但是使用C语言的强制类型转换就可以:

int main()
{
	//静态转换
	double b = 11.22;
	int a = static_cast<int>(b); //a、b变量为相似类型可以进行转换
	cout << a << endl;
	
	int* p = (int*)a; //强制类型转换
	cout << p << endl;
	return 0;
}

在这里插入图片描述

使用C++的类型转换更能保证数据的安全性。C++的类型转换只是一个规范,并不是强制性的。不是强制性的就会造成有人会遵守,有人不会遵守。

reinterpret_cast 不同类型转换

  • reinterpret_cast 用于将一种类型转换为另一种不同的类型(不同类型之间的强制转换)

示例:

int main()
{
	//不同类型的转换
	int c = 20;
	int* p1 = reinterpret_cast<int*>(c);
	cout << p1 << endl;
	return 0;
}

reinterpret_cast后面跟的尖括号表示:要将变量转化为怎么样的类型

const_cast 去除变量的const属性

  • const_cast 用途就是删除变量的const属性,方便赋值

示例:

int main()
{
	//去除常量的属性
	const int x = 10;
	int* p = const_cast<int*>(&x);
	
	*p = 20;
	cout << x << " " << *p << endl;

	return 0;
}

运行前来猜一下,x 和 *p 的值是什么?

在这里插入图片描述
这是编译器的优化问题,x的值是直接在寄存器中获取的,*p 是在内存中获取的。寄存器的运行速度是远高于内存的,并不能说 x的值没有修改,而是修改了还没有来得及更新。可以使用 volatile 关键字,让 x 不要去寄存器中获取值。

int main()
{
	//去除常量的属性
	volatile const int x = 10;//volatile不让编译器去寄存器拿数据
	int* p = const_cast<int*>(&x);
	
	*p = 20;
	cout << x << " " << *p << endl;

	return 0;
}

此时再来看看打印的结果:
在这里插入图片描述
在使用 const_cast 的时候要注意,后跟尖括号要转换为对应的指针类型,不然编译器会报错

int main()
{
	//去除常量的属性
	volatile const int x = 10;
	int p = const_cast<int>(x); //error
	
	p = 20;
	cout << x << " " << p << endl;

	return 0;
}

在这里插入图片描述

dynamic_cast 动态转换

  • dynamic_cast 用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换)

前提声明:父类表示上,子类表示下

在这里引入两个词:向上转型向下转型

  1. 向上转型子类对象指针/引用 转换为 父类指针/引用(不需要转换,赋值兼容规则)

对向上转型不太理解,或者说对继承还不熟悉的小伙伴可以转站看看小编写的这篇文章:C++继承介绍,直接跳转到文章的 基类和派生类对象之间的赋值 进行查阅。

  1. 向下转型父类对象指针/引用 转换为 子类指针/引用(用dynamic_cast转型)

注意:dynamic_cast 只能用于父类含有虚函数的类dynamic_cast 转换成功会返回这个子类的指针/引用转换失败会返回0

示例:

class A
{
public:
	virtual void f(){}
};

class B : public A
{};

void fun(A* pa, const string& s)
{
	cout << "pa指向" << s << endl;
	B* pb = dynamic_cast<B*>(pa);
}

int main()
{
	A a;
	B b;

	fun(&a, "指向父类的指针");
	fun(&b, "指向子类的指针");

	return 0;
}

实现一个简单的继承体系(B 继承 A,A实现了虚函数),通过 fun 函数的参数 pa 父类的指针,对被传入的参数进行 向上转型操作。然后在 fun 函数内部实现 向下转型(利用 dynamic_castpa 父类的指针转换为 pb 子类的指针

在这里插入图片描述

如果 父类对象的 指针(或者是引用)原先就是指向派生类的,在使用 dynamic_cast进行动态转换可以转换成功

如果 父类对象的 指针(或者是引用)原先指向的是基类的,在使用 dynamic_cast进行动态转换会转换失败

没错,使用 dynamic_cast 动态转换是有条件的。同样的,使用C语言的强制类型转化也可以:

class A
{
public:
	virtual void f(){}
};

class B : public A
{};

void fun(A* pa, const string& s)
{
	cout << "pa指向" << s << endl;
	
	//强制转换
	B* pb1 = (B*)pa;
	cout << "[强制转换]:pb1:" << pb1 << endl;
	//动态转换
	B* pb2 = dynamic_cast<B*>(pa);
	cout << "[dynamic_cast转换]:pb2:" << pb2 << endl;
}

int main()
{
	A a;
	B b;

	fun(&a, "指向父类的指针");
	fun(&b, "指向子类的指针");
	return 0;
}

在这里插入图片描述
强制转换不会管 pa 原先指向的是父类还是子类,我都给你转换,然后返回一个地址。这个地址可以使用吗?答案是不可以的,会造成越界访问。不相信的老铁可以试试,在这里就不演示了。

使用C语言的强制类型转换是不安全的,使用C++的 dynamic_cast 动态转换是安全的

为了代码的可视性,建议大家还是使用C++的强制类型转换的方式。虽然不是强制性的,但是可以避免很多的潜在 bug 发生。

这篇文章就介绍到这里,感谢大家的观看!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/648523.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

速看!打造专属数字化能力模型的七大关键!

在数字化浪潮中&#xff0c;企业如何打造适应自身发展的数字化能力模型&#xff1f;这是许多企业面临的重要课题。今天&#xff0c;通过众多企业使用蚓链数字化生态解决方案实践总结&#xff0c;为大家分享至关重要的七大经验&#xff0c;助你开启数字化转型之旅&#xff01; 1…

栈和队列OJ题详解

一.有效的括号&#xff1a; 20. 有效的括号 - 力扣&#xff08;LeetCode&#xff09; 首先拿到这个题目&#xff0c;我的第一个思路是利用双指针来走&#xff0c;看看是不是匹配的 但是这种情况就把双指针的这个思路直接pass了&#xff0c;明明是匹配的括号&#xff0c;用双指…

protobuf学习

学习了下protobuf这个工具&#xff0c;可以用来序列化数据结构&#xff0c;而且效率很高&#xff0c;数据可以压缩的更小。 记录下&#xff0c;我这里主要在C#里使用&#xff0c;从NuGet程序包安装以下两个 安装好后可以在该程序目录找到 packages\Google.Protobuf.Tools.3.26.…

【计算机毕业设计】安卓054基于Android校园助手

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

离线强化学习基础知识之offline MBRL和MFRL

1 离线强化学习介绍 离线强化学习&#xff08;也称为批量强化学习或完全脱策略强化学习&#xff09;仅依赖于先前收集的数据集&#xff0c;无需进一步交互。它提供了一种利用先前收集的数据集的方法以自动学习决策策略。 离线强化学习可以被定义为 data-driven 形式的强化学习…

一篇文章讲透排序算法之堆排序

1.前言 在学习这篇文章之前&#xff0c;请大家先学习堆这一数据结构中堆的概念&#xff0c;向下调整算法&#xff0c;向下调整建堆。 有关堆的实现方式请参考&#xff1a;堆的实现 堆排序就是利用堆里面学习过的知识点进行排序&#xff0c;如何进行排序呢&#xff1f; 2.堆…

拓扑排序(概念 + 模板 + 例题)

概念 : 拓扑排序只有有向图有 &#xff0c; 可以判断图中是否有环 ; Kahn(卡恩)算法 过程 : 模板 : vector<int> a[N] , res ; int d[N] ; // 存放每个结点的入度 int n , x ;bool toposort() {queue<int> q;for(int i 1; i < n; i) if(d[i] 0) q.push…

python中GUI之tkinter 模块

目录 1.tkinter 模块使用 tkinter 介绍 创建一个简单的 GUI 给窗口添加小构件 小构件种类 小构件参数说明 查看某个小构件的所有方法和属性 常用小构件用法 Button&#xff1a;按钮用法 Label&#xff1a;标签用法 Radiobutton&#xff1a;单选按钮用法 Checkbutto…

月薪5万是怎样谈的?

知识星球&#xff08;星球名&#xff1a;芯片制造与封测技术社区&#xff0c;星球号&#xff1a;63559049&#xff09;里的学员问&#xff1a;目前是晶圆厂的PE&#xff0c;但是想跳槽谈了几次薪水&#xff0c;都没法有大幅度的增长&#xff0c;该怎么办&#xff1f;“学得文武…

JavaWeb 请求响应路径调试

在使用mvc时&#xff0c;或许会遇到请求的页面响应不了&#xff0c;这种情况要对站下径。 站点根目录 启动服务器时&#xff0c;通常要知道哪个是站点根目录。相应在网页端的url的跟站点通常为http://localhost:8080/ &#xff0c;前端解析时用的是站点根目录。 <form act…

RT-Thread更改msh串口波特率

修改rt-thread文件下components下dirvers下serial.h文件里 #define RT_SERIAL_CONFIG_DEFAULT 里的默认波特率即可

这方法真牛B!论文降重从81%直降1.9%

目录 一、万字论文&#xff0c;从0到1&#xff0c;只需1小时二、获取途径三、论文从81&#xff05;降到1.9&#xff05;四、内容是别人的&#xff0c;话是自己的五、AI工具 --> 中文论文降重六、论文降重小技巧 一、万字论文&#xff0c;从0到1&#xff0c;只需1小时 通过O…

ROS2入门21讲__第20讲__RQT:模块化可视化工具

目录 前言 rqt介绍 日志显示 图像显示 发布话题数据/调用服务请求 绘制数据曲线 数据包管理 节点可视化 前言 ROS中的Rviz功能已经很强大了&#xff0c;不过有些场景下&#xff0c;我们可能更需要一些简单的模块化的可视化工具&#xff0c;比如只显示一个摄像头的图像…

INTERCONNECT模块中的 Circuit Layout Editor

INTERCONNECT模块中的 Circuit Layout Editor 正文 正文 打开 INTERCONNECT 模块后的工作界面如下&#xff1a; 我们可以通过 View->Windows 选取我们需要的工具窗口。 当然&#xff0c;用户也可以自己手动重新规划各个窗口的位置&#xff0c;但是此处&#xff0c;我们保…

反射获取方法的参数类型和参数名

如何获取方法的参数类型和参数名 示例&#xff0c;要获取的方法 获取参数类型和名称 Testpublic void testGetParamsName() throws Exception {LocalVariableTableParameterNameDiscoverer parameterNameDiscoverer new LocalVariableTableParameterNameDiscoverer();Method[…

抖音IP地址频繁变动:背后的原因与解读

在抖音这个短视频平台的日常使用中&#xff0c;不少用户可能注意到了自己的IP地址有时会频繁变动。这种现象不仅引起了用户的好奇&#xff0c;也引发了关于个人隐私、账号安全以及平台政策的一系列讨论。那么&#xff0c;抖音IP地址换来换去什么意思&#xff1f;这背后又隐藏着…

langchain进阶一:特殊的chain,轻松实现对话,与数据库操作,抽取数据,以及基于本地知识库的问答

特殊的chain langchain中的Chain有很多,能够轻松实现部分需求,极致简化代码,但是实现效果与模型智慧程度有关 会话链 效果与LLMChain大致相同 javascript 复制代码 from langchain.chains import ConversationChain from langchain_community.llms import OpenAI conversat…

从零构建vue3+ts+vite项目打包及项目依赖配置

❗️❗️❗️❗️ 写在最前: 本文是根据B站作者 月光分层 视频vuets 工程化配置以及作者笔记稍作整理 &#x1f496;&#x1f496;作者B站地址https://space.bilibili.com/14110850 &#x1f496;&#x1f496;视频教程地址vuets 工程化配置 &#x1f496;&#x1f496;作者微信…

Nacos 2.x 系列【10】配置管理

文章目录 1. 概述2. 配置管理2.1 CRUD2.2 版本管理2.3 灰度管理2.4 监听管理2.5 推送轨迹2.6 示例代码2.6 聚合数据 1. 概述 在Nacos的架构图中&#xff0c;配置管理包含了配置CRUD、版本管理、灰度管理、监听管理、推送轨迹、聚合数据等功能。 在上篇文档中&#xff0c;我们…

shell脚本编译成二进制文件shc

文章目录 1. 安装shc2. 使用shc编译Shell脚本3. 执行二进制文件4. 编译后执行效率 将Shell脚本转换为二进制执行文件&#xff0c;可以使用 shc工具。 shc是一个Shell编译器&#xff0c;它可以将Shell脚本编译成二进制文件。以下是详细步骤&#xff1a; 1. 安装shc 在大多数L…