26.特殊类的设计(设计不能被拷贝的类、只能在堆上创建对象的类、只能在栈上创建对象的类、不能被继承的类/只能创建一个对象(单例模式))

1.设计一个类,不能被拷贝

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

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

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

class CopyBan
{//......private:CopyBan(const CopyBan&); CopyBan& operator=(const CopyBan&);//......
};

原因:

  1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了

  2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

  • 方式二:C++11

C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。

class CopyBan
{// ...// 这里禁止编译器默认生成拷贝构造函数和赋值运算符重载CopyBan(const CopyBan&)=delete;CopyBan& operator=(const CopyBan&)=delete;//...
};

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

实现方式:

  1. 将类的构造函数设置为私有成员函数,拷贝构造声明成私有成员函数。防止别人调用拷贝在栈上生成对象。

  2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>using namespace std;// 只能在堆上创建对象的类
class HeapOnly
{
public:// 静态成员函数,不需要通过类对象来调用,通过类域就可以进行调用static HeapOnly* CreateObj(){// 在静态成员函数中创建一个HeapOnly类对象,通过关键词new在堆上申请空间return new HeapOnly;}
private:// 将构造函数设置为私有// 这样,用户无法在类外创建HeapOnly类对象(因为无法调用构造函数进行初始化)HeapOnly(){}// 防止拷贝防止别人调用拷贝在栈上生成对象HeapOnly(const HeapOnly&) = delete;
};int main()
{// 在静态成员函数中完成堆对象的创建// 对于静态成员函数,是不需要先创建对象,通过类域就可以直接进行调用HeapOnly* php4 =  HeapOnly::CreateObj();// 已经进行了防拷贝,因此不能够进行拷贝// HeapOnly hp5(*php4);delete php4;cout << sizeof(php4) << endl;cout << sizeof(size_t) << endl;return 0;
}

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

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>using namespace std;class StackOnly
{
public:static StackOnly CreateObj(){// 在堆上调用构造函数,创建一个匿名对象,并返回return StackOnly();}void Print() const{cout << "StackOnly::Print()" << endl;}private:StackOnly(){}
};int main()
{StackOnly::CreateObj().Print();// 在栈上创建StackOnly so1 = StackOnly::CreateObj();// 无法禁止掉在静态区创建static StackOnly so4 = StackOnly::CreateObj();return 0;
}

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

  • C++98方式
// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承class NonInherit
class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}private:// 将构造函数私有化NonInherit(){}
}
  • c++11的方式

在C++11中,final 是用来修饰类、虚函数和虚继承的关键字,用于表示某个类或虚函数是最终版本,不能被继承或覆盖。

  1. 修饰类:在类的定义中,使用 final 关键字可以阻止其他类继承它。例如:
class Base final {// ...
};class Derived : public Base { // 错误,Base 类被声明为 final,不能被继承// ...
};
  1. 修饰虚函数:在虚函数的声明或定义中,使用 final 关键字可以阻止子类重写该虚函数。例如:
class Base {
public:virtual void foo() final {// ...}
};class Derived : public Base {
public:// 错误,无法重写 final 函数 foo// void foo() override {//     // ...// }
};
  1. 修饰虚继承:在虚继承的声明中,使用 final 关键字可以阻止子类继承该虚基类。例如:
class Base {// ...
};class Derived : public virtual Base final {// ...
};class MostDerived : public Derived {// 错误,无法继续继承 final 虚基类 Base
};

总之,final 关键字用于在类层次结构中显式地指定某个类、虚函数或虚继承关系不可被修改或继承。

5. 请设计一个类,只能创建一个对象(单例模式)

设计模式:

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。

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

单例模式:

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

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

  • 饿汉模式

就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include<map>using namespace std;// 单例模式的类:全局只有一个唯一对象
// 饿汉模式:因为对象是全局对象,因此程序刚一开始(main函数之前)就创建对象
// 缺点:1、单例对象初始化时数据太多,导致启动慢(需要先对全局对象进行创建)
//      2、多个单例类有初始化依赖关系,饿汉模式无法控制
//      假设有两个类,A类和B类,要求先初始化A类,再初始化B类,B类依赖于A类,但是饿汉模式无法控制顺序
class InfoSingleton
{
public:static InfoSingleton& GetInstance(){ // 返回静态成员变量return _sins;}void Insert(string name, int salary){_info[name] = salary;}void Print(){for (auto kv : _info){cout << kv.first << ":" << kv.second << endl;}cout << endl;}private:// 将构造函数设置为私有成员函数InfoSingleton(){}// 禁止生成默认拷贝函数和默认赋值运算重载InfoSingleton(const InfoSingleton& info) = delete;InfoSingleton& operator=(const InfoSingleton& info) = delete;map<string, int> _info;// ...private:// 静态成员变量(这是这个类的对象),在类里面进行声明,在类外进行初始化static InfoSingleton _sins;
};// 进行定义(静态成员变量或者函数,必须在类外进行定义),这是一个全局变量
// 可以通过类内静态成员来创建对象
// 类内静态成员也属于全局部变量
// 通过这种方式,则只能够创建一个对象,即_sins
InfoSingleton InfoSingleton::_sins;int main()
{// 这里无法创建,因为构造函数是私有成员函数// InfoSingleton info1;// InfoSingleton info2;// InfoSingleton info3;// InfoSingleton::GetInstance() 返回静态的InfoSingleton类对象InfoSingleton::GetInstance().Insert("张三", 10000);InfoSingleton& infosl = InfoSingleton::GetInstance();infosl.Insert("李四", 15000);infosl.Insert("赵六", 12000);infosl.Insert("王五", 8000);infosl.Print();InfoSingleton::GetInstance().Insert("张三", 13000);InfoSingleton::GetInstance().Print();// 已经禁用了拷贝构造和赋值拷贝,因此是不能够进行赋值的/*InfoSingleton copy = InfoSingleton::GetInstance();copy.Insert("孙七", 50000);copy.Print();*/infosl.Print();return 0;
}
  • 懒汉模式

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

#include<iostream>
#include<mutex>
#include<map>using namespace std;// 锁的类模板
// RAII锁管理类
template<class Lock>
class LockGuard
{
public:// 锁的构造函数LockGuard(Lock& lk):_lk(lk){_lk.lock();}// 锁的析构函数~LockGuard(){_lk.unlock();}private:Lock& _lk;
};//懒汉模式:第一次获取单例对象的时候创建对象
//	  1、对象在main函数之后才会创建,不会影响启动顺序
//    2、可以主动控制创建顺序
class InfoSingleton
{
public:// 多个线程一起调用GetInstance,存在线程安全的风险// 如果t1 t2 两个线程同时调用new InfoSingleton时,那么可能就会出现线程安全问题static InfoSingleton& GetInstance(){// 第一次获取单例对象的时候创建对象// 为了保证线程安全,因此此时需要进行加锁// 静态成员函数,没有this指针,只能调用静态成员变量// 因此,将成员变量的锁设置为静态锁,这样静态成员函数就可以直接调用,而不需要通过this指针// 双检查加锁(减少多次加锁的消耗,)// 对象new出来以后,避免每次都加锁的检查,提高性能if (_psins == nullptr)  {// t1  t2// 我们自定义的RAII锁,和库里面的RAII锁效果是一样的,使用一个即可// 除了当前的作用域块之后,锁的声明周期结束,会自动调用其析构函数释放资源LockGuard<mutex> lock(_smtx);// 库里面的RAII锁,出作用域之后,锁会被自动解锁// 使用这种RAII锁,是为了防止new时,抛异常,锁没有被解锁,导致内存泄漏// lock_guard<mutex> lock(_smtx);if (_psins == nullptr)  // 保证线程安全且只new一次{_psins = new InfoSingleton;}	}return *_psins;}// 一般单例对象不考虑释放(一般情况下,会经常使用),当进程结束后,会被释放,资源会被回收// 单例对象不用时,必须手动处理,一些资源需要保存static void DelInstance(){// 保存数据到文件// ...// 加锁,防止多个线程多次释放std::lock_guard<mutex> lock(_smtx);if (_psins){delete _psins;_psins = nullptr;}}// 也可以让InfoSingleton的单例对象自己在程序结束时,自动回收申请的资源// class GC是class InfoSingleton的内部类// 内部类是外部类的友元,因此GC可以调用InfoSingleton的成员变量,以及成员函数// 当定义的GC对象,出其作用域之后,则会调用析构函数,也就会执行DelInstance()// 保存数据到文件,再释放对象_psinsclass GC{public:~GC(){if (_psins){cout << "~GC()" << endl;DelInstance();}}};void Insert(string name, int salary){_info[name] = salary;}void Print(){for (auto kv : _info){cout << kv.first << ":" << kv.second << endl;}cout << endl;}private:// 构造函数(私有成员函数)InfoSingleton(){}// 禁止拷贝构造函数和赋值重载函数InfoSingleton(const InfoSingleton& info) = delete;InfoSingleton& operator=(const InfoSingleton& info) = delete;map<string, int> _info;private:// InfoSingleton类对象指针static InfoSingleton* _psins;// 静态的锁static mutex _smtx;// 自动回收资源,保存数据的类static GC _gc;
};// 此时全局变量初始化的是一个指针,并不会创建对象
InfoSingleton* InfoSingleton::_psins = nullptr;// 对静态成员变量,锁进行初始化
mutex InfoSingleton::_smtx;// GC是InfoSingleton的内部类,因此类的类型也需要指明作用域
InfoSingleton::GC InfoSingleton::_gc;int main()
{InfoSingleton::GetInstance().Insert("张三", 10000);InfoSingleton& infosl = InfoSingleton::GetInstance();infosl.Insert("李四", 15000);infosl.Insert("赵六", 12000);infosl.Insert("王五", 8000);infosl.Print();InfoSingleton::GetInstance().Insert("张三", 13000);InfoSingleton::GetInstance().Print();// 如果我们自己释放了InfoSingleton的单例对象的资源,那么则不会再被内部类对象的析构函数GC释放,因为我们自己释放后,_psins = nullptr// GC的内部有判断,_psins为空,则不会再次释放// InfoSingleton::DelInstance();return 0;
}

6.内部类和外部类

  • 案例1

在C++中,内部类指的是在另一个类内部定义的类。内部类可以访问外部类的私有成员,并且可以被外部类的成员函数使用。

以下是内部类的基本用法示例:

#include <iostream>class Outer {
private:int outer_private;// 内部类的定义class Inner {public:void display(Outer& obj) {// 内部类可以访问外部类的私有成员std::cout << "Outer private member: " << obj.outer_private << std::endl;}};public:// 外部类的成员函数void access_inner() {Inner inner; // 内部类对象inner.display(*this); // 调用内部类的成员函数}
};int main() {Outer outer;outer.access_inner(); // 调用外部类的成员函数来访问内部类return 0;
}

在上面的示例中,InnerOuter 的内部类,Outer 的成员函数 access_inner() 创建了 Inner 类的对象,并调用了其 display() 成员函数来访问 Outer 的私有成员 outer_private

内部类可以在外部类的成员函数中进行实例化和访问,但不能独立于外部类之外实例化。内部类的作用范围被限制在外部类的作用域内。

  • 案例2

在C++中,内部类是定义在另一个类的内部的类。内部类可以访问外部类的所有成员,包括私有成员,而外部类也可以访问内部类的成员。内部类的作用范围被限制在外部类的作用域内,这意味着外部类的成员函数可以直接实例化内部类,并访问其成员,但在外部类之外,不能直接访问内部类。

让我们通过一个示例来详细解释:

#include <iostream>class Outer {
private:int outer_private;// 内部类的定义class Inner {public:void display(Outer& obj) {// 内部类可以访问外部类的私有成员std::cout << "Outer private member: " << obj.outer_private << std::endl;}};public:// 外部类的成员函数void access_inner() {Inner inner; // 内部类对象inner.display(*this); // 调用内部类的成员函数}
};int main() {Outer outer;outer.access_inner(); // 调用外部类的成员函数来访问内部类// 在外部类之外,无法直接访问内部类// Inner inner; // 错误!内部类不能在外部类之外直接实例化return 0;
}

在上面的示例中,InnerOuter 的内部类。在 Outer 的成员函数 access_inner() 中,我们可以直接实例化 Inner 类,并调用其 display() 成员函数来访问 Outer 的私有成员 outer_private。然而,在 main() 函数中,我们不能直接实例化 Inner 类,因为 Inner 类的作用范围被限制在 Outer 类的作用域内,不能在外部类之外直接访问。

  • 案例3

内部类的析构函数在其对象生命周期结束时被调用。具体来说,内部类对象的析构函数在包含它的外部类对象的析构函数被调用时被调用。这是因为内部类的生命周期通常是依赖于外部类对象的,当外部类对象被销毁时,它包含的所有内部类对象也会被销毁,从而触发内部类对象的析构函数调用。

让我们通过一个示例来说明:

#include <iostream>class Outer {
private:class Inner {public:~Inner() {std::cout << "Inner destructor called" << std::endl;}};Inner inner; // 内部类对象public:~Outer() {std::cout << "Outer destructor called" << std::endl;}
};int main() {Outer outer; // 外部类对象return 0;
}

在这个示例中,Outer 类包含了一个名为 Inner 的内部类。当 Outer 类的对象 outer 被创建时,它同时创建了一个 Inner 类的对象 inner。当 outer 对象的生命周期结束时(即 main() 函数结束时),它的析构函数被调用,而在 Outer 类的析构函数中,它包含的 Inner 类对象的析构函数也会被调用。因此,上述示例会先打印 “Inner destructor called”,然后打印 “Outer destructor called”。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

OpenAI 或将推出多模态人工智能数字助理;研究发现部分 AI 系统已学会「说谎」丨 RTE 开发者日报 Vol.203

开发者朋友们大家好&#xff1a; 这里是 「RTE 开发者日报」 &#xff0c;每天和大家一起看新闻、聊八卦。我们的社区编辑团队会整理分享 RTE&#xff08;Real Time Engagement&#xff09; 领域内「有话题的 新闻 」、「有态度的 观点 」、「有意思的 数据 」、「有思考的 文…

技术支持-KA指标提升

一 提升关键客户&#xff08;Key Account, KA&#xff09;问题解决效率与体验 建立快速响应机制&#xff1a; 设立专门的KA服务团队&#xff0c;确保客户问题能被即时接收和响应。实施多渠道接入&#xff08;如电话、短信、邮件、在线聊天等&#xff09;&#xff0c;让客户可以…

黄啤:醇厚与经典的传承

啤酒的历史悠久&#xff0c;种类繁多&#xff0c;而黄啤作为其中的一种经典风格&#xff0c;一直备受人们的喜爱。Fendi club黄啤作为精酿啤酒的代表&#xff0c;完善地传承了黄啤的醇厚与经典&#xff0c;为消费者带来了与众不同的口感体验。 Fendi club黄啤在酿造过程中&…

【C++风云录】深度剖析:人体解剖学与医学影像处理

计算机视觉与医学成像&#xff1a;六种工具的比较 前言 本文旨在通过详尽而深入的介绍&#xff0c;让读者更好地理解和使用六种重要的程序库&#xff1a;ITK-SNAP、Slicer、VTK、OpenCV、dcm4che以及DCMTK。每一个程序库的部分均包含了它们的简介&#xff0c;安装方法&#x…

转移插槽笔记

4.3.4.转移插槽 我们要将num存储到7004节点&#xff0c;因此需要先看看num的插槽是多少&#xff1a; 如上图所示&#xff0c;num的插槽为2765. 我们可以将0~3000的插槽从7001转移到7004&#xff0c;命令格式如下&#xff1a; 具体命令如下&#xff1a; 建立连接&#xff1a;…

消息 队列

使用消息队列实现的2个终端之间的互相聊天&#xff0c;并使用信号控制消息队列的读取方式。 当键盘按ctrlc的时候&#xff0c;切换消息读取方式&#xff0c;一般情况为读取指定编号的消息&#xff0c;按ctrlc之后&#xff0c;指定的编号不读取&#xff0c;读取其他所有编号的消…

【C++ 】红黑树

1.1 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路 径会比其他路径长出俩倍&#xff…

信息系统安全选择题 1-10章 汇总【太原理工大学】

第一章 1.信息未经授权不能改变的安全特性称为_D b.有效性 a.保密性 d.完整性 c.可控性 2.信息系统防止信息非法泄露的安全特性称为_D b.有效性 a.完整性 d.保密性 c.可控性 6.《国际通用信息安全评价标准ISO/IEC 15408》在安全保证要求中定义了7个评价保证等级&…

上海理工大学程序设计 D方块游戏

原题链接&#xff1a;D-方块游戏 本来以为是搜索&#xff0c;没想到是数学。 //冷静&#xff0c;冷静&#xff0c;冷静 //调不出来就重构 #pragma GCC optimize(2) #pragma GCC optimize("O3") #include<bits/stdc.h> #define endl \n using namespace std;…

R语言:肿瘤突变负荷分析

> merge_maf <- function(metadata, path){ #通过合并path,还有sample sheet前两列得到每一个文件的完整路径 filenames <- file.path(path, metadata$file_id, metadata$file_name, fsep .Platform$file.sep) message (##############…

Vulnhub-wp 获取vulnhub靶机wp搜索工具

项目地址:https://github.com/MartinxMax/vulnhub-wp 简介 搜索Vulnhub平台的解题文章,之过滤返回出正确可访问的页面 使用 $ python3 vulnhubwp.py 支持模糊搜索 [] Query: kiop 进入选项4,获取wp地址 [] Choice options: 4

DML之操作数据表

1. 插入数据 (1). 前言 前文我们实现了如果创建表&#xff0c;接下来我们将学习如何向数据表中插入数据.插入有两种方式. (2). 方式1 : 情况1 : 使用该语法一次只能向表中插入一条记录.为表中的任意字段按默认的顺序插入数据.值列表中需要为表的每一个字段指定值.并且值…

网络库-libevent介绍

1.简介 libevent是一个事件驱动的网络库&#xff0c;主要用于构建可扩展的网络服务器。它提供了跨平台的API&#xff0c;支持多种事件通知机制&#xff0c;如select、poll、epoll、kqueue等。 主要组件 event: 表示一个具体的事件&#xff0c;包括事件类型、事件回调等。eve…

视觉检测系统,是否所有产品都可以进行视觉检测?

视觉检测系统作为一种先进的质检工具&#xff0c;虽然具有广泛的应用范围&#xff0c;但并非所有产品都适合进行视觉检测。本文将探讨视觉检测系统的适用范围及其局限性。 随着机器视觉技术的快速发展&#xff0c;视觉检测系统已广泛应用于各个行业&#xff0c;为产品质检提供…

【网络安全入门】你必须要有的学习工具(附安装包)零基础入门到进阶,看这一篇就够了!

工欲善其事必先利其器 在新入门网络安全的小伙伴而言。这些工具你必须要有所了解。本文我们简单说说这些网络安全工具吧&#xff01; Web安全类 Web类工具主要是通过各种扫描工具&#xff0c;发现web站点存在的各种漏洞如sql注入、xss等。从而获取系统权限&#xff0c;常用的…

iZotope RX 11 for Mac:音频修复的终极利器

在音频制作的浩瀚星海中&#xff0c;每一份声音都是珍贵的宝石&#xff0c;但往往被各种噪音、杂音所掩盖。此刻&#xff0c;iZotope RX 11 for Mac犹如一位专业的匠人&#xff0c;以其精湛的技术&#xff0c;将每一份声音雕琢至完美。 iZotope RX 11 for Mac&#xff0c;这是…

创新点!CNN与LSTM结合,实现更准预测、更快效率、更高性能!

推荐一个能发表高质量论文的好方向&#xff1a;LSTM结合CNN。 LSTM擅长捕捉序列数据中的长期依赖关系&#xff0c;而CNN则擅长提取图像数据的局部特征。通过结合两者的优势&#xff0c;我们可以让模型同时考虑到数据的时序信息和空间信息&#xff0c;减少参数降低过拟合风险&a…

MySQL—子查询

目录 ▐ 子查询概述 ▐ 准备工作 ▐ 标量子查询 ▐ 列子查询 ▐ 表子查询 ▐ 多信息嵌套 ▐ 子查询概述 • 子查询也称嵌套查询&#xff0c;即在一个查询语句中又出现了查询语句 • 子查询可以出现在from 后面 或where后面 • 出现在 from 后称表子查询&#xff0c;结…

远程终端协议TELNET

一、概述 TELNET是一个简单的远程终端协议&#xff0c;是互联网正式标准。 实现在本地对远程计算机进行操作&#xff1b;在本地键盘输入的字符通过应用层TELNET协议传输到远程服务器上&#xff0c;同时远程服务器把字符传送过来显示在本地的显示器上&#xff1b;TELNET协议采…

B - AtCoder Amusement Park题解

文章目录 原题翻译题目大意思路代码 原题翻译 翻译我已经在上篇翻译过了&#xff0c;详情 题目大意 有 n n n个小团体需要乘坐小船&#xff0c;第 i i i个团体有 a i a_i ai​个人&#xff0c;而小船只有 k k k个座位。同时&#xff0c;小团体需要按照给出的顺序来乘坐小船&…