首页 前端知识 【C 高阶】解锁C 的深层魅力——探索特殊类的奥秘

【C 高阶】解锁C 的深层魅力——探索特殊类的奥秘

2024-10-13 19:10:30 前端知识 前端哥 497 492 我要收藏

📝个人主页🌹:Eternity._
⏩收录专栏⏪:C++ “ 登神长阶 ”
🤡往期回顾🤡:C++ 类型转换
🌹🌹期待您的关注 🌹🌹

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

❀C++特殊类

  • 📒1. 不能被拷贝的类
  • 📙2. 只能在堆上创建对象
  • 📚3. 只能在栈上创建对象
  • 📜4. 不能被继承的类
  • 📝5. 只能创建一个对象(单例模式)
    • 🌞设计模式
    • ⭐单例模式
  • 📖6. 总结


前言:在C++这片浩瀚的编程海洋中,基础语法与常用库如同岛屿与浅滩,引领着每一位初学者逐步前行。然而,当我们的航程逐渐深入,便会发现那些隐藏于波涛之下的特殊类,它们如同深海中的宝藏,等待着勇敢的探索者去发掘

特殊类,作为C++语言中的高级特性之一,不仅承载着对面向对象编程思想的深刻体现,更是解决复杂问题、优化程序性能的利器。它们包括但不限于模板类、智能指针、迭代器、多态基类(抽象类)、以及那些利用高级特性(如RAII、类型萃取等)设计的独特类。这些特殊类以其独特的设计理念和强大的功能,在C++的各个领域发挥着不可替代的作用

现在我将带领大家一同踏上这场探索之旅,通过详细解析C++中的特殊类,揭示它们的设计原理、应用场景以及使用技巧。我们将从基础概念出发,逐步深入到高级特性,通过实例演示和代码分析,帮助读者逐步掌握这些特殊类的精髓

让我们一同揭开C++特殊类的神秘面纱,领略其独特的魅力与力量吧!


📒1. 不能被拷贝的类

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载

实现方式:

因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可

  • C++98:将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可

原因:

  • 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不
    能禁止拷贝了
  • 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写
    反而还简单,而且如果定义了就不会防止成员函数内部拷贝了
  • C++11:C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数

代码示例 (C++):

//不能被拷贝的类
//C++98
class CopyBan
{
	// ...
private:
	CopyBan(const CopyBan&);
	CopyBan& operator=(const CopyBan&);
	//...
};

//C++11
class CopyBan
{
	// ...
	CopyBan(const CopyBan&) = delete;
	CopyBan& operator=(const CopyBan&) = delete;
	//...
};

📙2. 只能在堆上创建对象

实现方式:

将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象
提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建


方案一:析构函数私有化:

正常创建对象,会自动调用析构函数,将析构函数私有化,外部代码将无法直接调用delete来销毁对象,因为这将尝试访问一个不可访问的析构函数,要想销毁对象就得自己定义成员函数来操作

代码示例 (C++):

//只能在堆上创建对象
class HeapOnly
{
public:
	// 对象的销毁
	/*static void Destroy(HeapOnly* ptr)
	{
		delete ptr;
	}*/
	
	// 对象的销毁
	void Destroy()
	{
		delete this;
	}

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

int main()
{
	HeapOnly* ptr = new HeapOnly;

	//HeapOnly::Destroy(ptr);

	ptr->Destroy();
	return 0;
}

方案二:构造函数私有化:

通过将构造函数私有化,我们可以限制对象创建的方式,从而实现只能在堆上创建对象

代码示例 (C++):

class HeapOnly
{
public:
	// 静态的成员函数,完成完成堆对象的创建
	static HeapOnly* CreateObj()
	{
		return new HeapOnly;
	}

private:
	HeapOnly()
	{
		cout << "HeapOnly()" << endl;
	}
	
	// 防止别人调用拷贝在栈上生成对象
	//C++98
	HeapOnly(const HeapOnly& copy);
	
	//C++11
	HeapOnly(const HeapOnly& copy) = delete;
};

int main()
{
	HeapOnly* ptr = HeapOnly::CreateObj();

	//HeapOnly copy(*ptr);

	return 0; 
}

📚3. 只能在栈上创建对象

实现方式:

将类的构造函数私有,提供一个静态的成员函数,在该静态成员函数中完成栈对象的创建

思考一下:这里的实现方法和上面似乎有点相同,但是我们在实现时,是否也要像上面一样delete掉拷贝构造呢?

静态成员函数 -> 完成栈对象的创建:

static StackOnly CreateObj()
	{
		StackOnly obj;
		return obj;
	}
// 这里会让对象在堆上创建
StackOnly* ptr = new StackOnly(obj);
// 如果我们 delete 拷贝构造,会发现CreateObj()也会受到影响,所以我们只能另辟蹊径
// 我们要从 new 下手让我们不能用 new 来创建对象

实现专属的operator new

void* operator new(size_t size)
{
	cout << "void* operator new(size_t size)" << endl;
	return malloc(size);
}

当我们new这类对象时,我们会优先调用operator new,而不是全局(就近原则),因此我们delete掉我们自己实现的operator new,我们就不能在使用new创建对象

代码示例 (C++):

// 只能在栈上创建的类
class StackOnly
{
public:
	static StackOnly CreateObj()
	{
		StackOnly obj;
		return obj;
	}

	// 如果我们将拷贝构造delete,那么我们就无法创建对象,因为在CreateObj()中也会发生拷贝构造
	//StackOnly(const StackOnly& copy) = delete;

	void* operator new(size_t size) = delete;

	//实现专属的operator new
	//当我们new这类对象时,我们会优先调用operator new,而不是全局(就近原则)
	/*void* operator new(size_t size)
	{
		cout << "void* operator new(size_t size)" << endl;
		return malloc(size);
	}*/

private:
	StackOnly()
	{
		cout << "StackOnly()" << endl;
	}
};

int main()
{
	StackOnly obj = StackOnly::CreateObj();

	// 发生拷贝构造是会在堆上创建类,但是我们能不能将拷贝构造delete呢?
	//StackOnly* ptr = new StackOnly(obj);

	return 0;
}

📜4. 不能被继承的类

实现方式:

  • C++98:中构造函数私有化,派生类中调不到基类的构造函数就无法继承

  • C++11:一个类如果不希望被其他类继承,可以通过将其声明为final类来实现

代码示例 (C++):

// 不能被继承的类
//C++98
class A
{
public:
	static A GetInstance()
	{
		return A();
	}
private:
	A()
	{}
};

// 不能被继承的类
//C++11
class A final
{
	// ....
};

📝5. 只能创建一个对象(单例模式)

🌞设计模式

设计模式(Design Patterns)是一种在软件开发中用于解决常见问题的可重用的解决方案。它们不是代码本身,而是关于如何组织代码和对象之间关系的一种描述和指导,是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模
式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样

设计模式是面向对象设计原则的体现,它们通过提供可重用的解决方案来帮助开发者更好地设计和实现软件系统。在实际开发中,可以根据具体的需求和场景选择合适的设计模式来解决问题


⭐单例模式

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

单例模式的核心在于控制实例的数目,节省系统资源,并允许全局访问

单例模式有两种实现模式:

  • 饿汉模式
  • 懒汉模式

  • 饿汉模式
    提前创建好程序,启动时就创建一个唯一的实例对象 -> (提前做好)

优点:实现简单
缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定

代码示例 (C++):

class A
{
public:
	static A* GetInstance()
	{
		return &_inst;
	}
	
	void Add(const string& key, const string& value)
	{
		_dict[key] = value;
	}

	void Print()
	{
		for (auto& kv : _dict)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
	}
	
private:
	A() // 私有构造函数  
	{}

	//限制类外随意创建对象
	A(const A& copy) = delete;
	A& operator=(const A& aa) = delete;

	map<string, string> dict;

	static A _inst;// 静态实例  
	
};
// 类的外部定义静态实例  
A A::_inst;

int main()
{
	A::GetInstance()->Add("sort", "排序");
	A::GetInstance()->Add("left", "左边");
	A::GetInstance()->Add("right", "右边");
	A::GetInstance()->Print();
	
	return 0;
}
  • 懒汉模式
    第一次用的时候在创建 -> (现吃现做)

如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取
文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,
就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好

优点:第一次使用实例对象时,创建对象,进程启动无负载。多个单例实例启动顺序自由控制
缺点:复杂

注意:new的懒汉对象一般不需要释放,进程正常结束会释放资源

代码示例 (C++):

class B
{
public:
	static B* GetInstance()
	{
		// 第一次为空的时候,创建对象
		if (_inst == nullptr)
		{
			_inst = new B;
		}
		return _inst;
	}

	void Add(const string& key, const string& value)
	{
		_dict[key] = value;
	}

	void Print()
	{
		for (auto& kv : _dict)
		{
			cout << kv.first << ":" << kv.second << endl;
		}
	}
	
	static void DelInstance()
	{
		if (_inst)
		{
			delete _inst;
			_inst = nullptr;
		}
	}
private:
	B()
	{}

	//限制类外随意创建对象
	B(const B& copy) = delete;
	B& operator=(const B& bb) = delete;

	map<string, string> _dict;

	static B* _inst; // 指针的形式

	class gc
	{
	public:
		~gc()
		{
			DelInstance();
		}
	};
	static gc _gc;
};

B* B::_inst = nullptr;
B::gc B::_gc;

int main()
{
	B::GetInstance()->Add("sort", "排序");
	B::GetInstance()->Add("left", "左边");
	B::GetInstance()->Add("right", "右边");

	B::GetInstance()->Print();
	return 0;
}

懒汉模式并非这么简单,后面还要牵扯到线程安全,线程安全需要额外处理


📖6. 总结

随着我们一同探索了C++中特殊类的广阔天地,相信你已经对这些高级特性有了更深的理解与感悟。特殊类不仅是C++语言复杂性和强大功能的体现,更是编程艺术的结晶,它们以独特的方式解决了传统编程中难以处理的问题,提升了代码的效率、安全性和可维护性

学习特殊类的过程,实际上是一个不断挑战自我、深化编程思维的过程。它要求我们不仅要掌握语法层面的知识,更要理解其背后的设计思想和实现原理。只有这样,我们才能真正驾驭这些特殊类,将它们灵活地应用于实际开发中,解决实际问题

用一句话总结一下就是:“掌握C++特殊类,就是掌握了一种强大的编程工具,它将助你在编程的世界里走得更远、更稳。”
愿你在未来的编程道路上,能够勇往直前、不断突破自我,创造出更多优秀的作品和成果!

在这里插入图片描述

希望本文能够为你提供有益的参考和启示,让我们一起在编程的道路上不断前行!
谢谢大家支持本篇到这里就结束了,祝大家天天开心!

在这里插入图片描述

转载请注明出处或者链接地址:https://www.qianduange.cn//article/18870.html
评论
发布的文章

安装Nodejs后,npm无法使用

2024-11-30 11:11:38

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!