[C++]:特殊类的设计

news2024/11/24 6:12:22

1. 不可拷贝类

我们知道,某些资源只能有一个对象持有,拷贝可能导致资源混乱。例如智能指针std::unique_ptr独占管理动态分配对象,文件句柄、网络套接字、数据库连接等资源通常也是独占的,不允许拷贝。

在C++11之前,要创建一个不可拷贝的类,通常的做法是将拷贝构造函数和赋值运算符重载声明为private,并且只进行声明而不提供定义。例如下面这个NonCopyable类:

class NonCopyable
{
public:
    NonCopyable() {}
private:
    // C++11之前,只声明不实现
    NonCopyable (const NonCopyable&)
    {}
    NonCopyable& operator=(const NonCopyable&)
    {}
};

当代码中某个地方尝试对该类的对象进行拷贝构造时,由于拷贝构造函数和赋值运算符重载是私有的,在类外部无法访问这些私有成员函数,所以从访问权限层面就阻止了拷贝行为。而只声明不实现的原因在于,如果只是声明为私有但不小心提供了定义,那么在类内部或者友元函数中还是有可能进行拷贝操作的,从而彻底禁止拷贝行为。

C++11引入了delete关键字,利用这个关键字可以更清晰、简洁地实现不可拷贝的类。像下面这样的NonCopyable类定义:

class NonCopyable {
public:
    NonCopyable() = default;
    // 使用delete关键字禁用拷贝构造函数和赋值运算符
    NonCopyable(const NonCopyable&) = delete;
    NonCopyable& operator=(const NonCopyable&) = delete;
    void doSomething() {
        //...
    }
};

通过将拷贝构造函数和赋值运算符重载直接标记为= delete,明确告知编译器要禁用这两个函数。当代码中出现对该类对象进行拷贝构造或者赋值的操作时,编译器会直接报错,指出对应的函数已被删除,不允许调用,从而实现禁止拷贝的功能。

我们还可以定义一个通用的不可拷贝的基类(如nocopy类),然后让具体需要禁止拷贝的类去继承这个基类,示例代码如下:

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

// 继承
class NonCopyable:public nocopy
{
public:
    NonCopyable() {};
private:
};

当派生类(如NonCopyable类)的对象进行拷贝构造或者赋值操作时,在派生类的拷贝构造函数和赋值运算符重载的实现过程中(即使没有显式定义,编译器也会默认生成相应的函数,尝试调用基类的对应函数来处理基类部分的拷贝和赋值逻辑),会先在初始化列表中去调用基类的拷贝构造函数和赋值运算符重载。但由于基类(nocopy类)已经通过delete关键字(或者类似在C++11之前将其设为私有等方式)禁止了这些操作,所以派生类也就无法顺利完成拷贝构造和赋值操作,从而实现了不可拷贝的效果。

2. 只能在堆上创建的类

对于一些对象大小在编译时未知或生命周期不确定时,动态内存分配更合适。并且多个模块或对象需共享同一实例,堆上对象可通过指针或引用共享。例如大型数据结构(大数组、链表、树等)、资源管理器(文件处理类、数据库连接类等)、单例模式(确保对象唯一性和生命周期管理)。所以我们有时需要设计一个只能在堆上创建的类,一般我们有两种方式实现:

以下是关于只能在堆上创建的类的详细介绍,包含其不同实现方式、原理、优缺点以及使用时的注意事项等内容:

首先第一种方式,我们可以通过私有构造函数结合静态创建方法实现

class HeapOnly {
public:
    // 静态成员函数用于在堆上创建对象并返回指针
    static HeapOnly* create() {
        return new HeapOnly();
    }
private:
    // 构造函数私有化
    HeapOnly() {}
    // 拷贝构造函数私有化,禁止拷贝构造
    HeapOnly(const HeapOnly&) = delete;
    // 拷贝赋值运算符私有化,禁止赋值操作
    HeapOnly& operator=(const HeapOnly&) = delete;
};

由于构造函数是私有的,像HeapOnly obj;这样直接在栈上创建对象的语句就无法通过编译,因为编译器会检查到在类外部没有权限调用构造函数。而对于拷贝构造函数和拷贝赋值运算符也进行了私有化或者删除的处理,避免了通过拷贝操作来间接创建对象的可能性,保证了对象创建的唯一性和可控性,只能按照规定的静态create函数在堆上创建。

第二种方式我们可以通过私有化析构函数实现

  • 实现方式
class HeapOnly {
public:
    HeapOnly() = default;
    void Destroy()
    {
        delete this;
    }
private:
    // 析构函数私有化
    ~HeapOnly() {}
    // 拷贝构造函数私有化,禁止拷贝构造
    HeapOnly(const HeapOnly&) = delete;
    // 拷贝赋值运算符私有化,禁止赋值操作
    HeapOnly& operator=(const HeapOnly&) = delete;
};

对于栈上对象,编译器会自动在对象生命周期结束(比如离开作用域)时调用析构函数进行资源清理等操作,但因为析构函数是私有的,在类外部代码中没有访问权限去执行这个操作,所以直接在栈上创建对象的语句就无法编译通过。而在堆上创建对象时,内存分配由new操作符完成,只是后续需要手动释放内存,通过提供Destroy函数来解决手动释放的问题,从而强制使对象只能在堆上创建并按照规定的方式进行内存释放管理。

3. 只能在栈上创建的类

栈上创建对象的生命周期与包含函数的作用域相同,函数结束时自动销毁,无需显式调用析构函数。并且栈上内存分配比堆上快,无需额外内存管理开销。例如锁类、作用域守卫类常设计为只能在栈上创建。所以有时候我们也需要设计一个只能在栈上创建的类。

我们首先肯定想到的是将构造函数或析构函数声明为私有,提供静态方法创建对象并返回。

//错误示例
class StackOnly {
public:
    static StackOnly Create()
    {
        static StackOnly obj;
        return obj;
    }
private:
    // 构造函数私有化
    StackOnly() {}
};
int main()
{
    //StackOnly* obj = new StackOnly(); error
    StackOnly obj = StackOnly::Create(); 
    //间接在堆上创建对象
    StackOnly* n = new StackOnly(obj); //ok
    return 0;
}

但此方法不能完全防止使用new创建对象,因为静态工厂方法返回对象时可能会用到拷贝构造函数(编译器可能优化),所以不能禁用/私有拷贝构造函数。而一旦不能禁止拷贝构造我们可以使用类似 StackOnly* obj = new(栈上创建的对象),调用拷贝构造间接创建一个堆上的对象,所以这种方式其实是不可取的。

所以我们可以换种思路, 既然要禁止在堆上开辟空间来创建类对象,我们可以采取直接禁止使用newdelete操作符的思路。因为在C++中,newdelete默认会调用全局的operator newoperator delete函数来进行内存分配和释放操作。所以,我们只需在类内对operator new进行重载,并通过显式删除的方式将其禁用,同时也把operator delete删除掉,如此一来,就能确保无法通过newdelete这两个操作符去创建该类的对象了。

class StackOnly {
public:
    StackOnly() = default;
    ~StackOnly() = default;

private:
    // 显式删除operator new操作符,禁止在堆上通过new创建对象
    void* operator new(std::size_t) = delete;
    // 显式删除operator delete操作符,禁止在堆上释放对象
    void operator delete(void*) = delete;
    // 同样也可以删除数组形式的new和delete操作符,防止通过new[]和delete[]来操作对象
    void* operator new[](std::size_t) = delete;
    void operator delete[](void*) = delete;
};

int main() {
    // 正确的创建方式,在栈上创建对象
    StackOnly obj;
    // 以下方式会报错,因为new和delete操作符被删除,不能在堆上创建和释放对象
    // StackOnly* ptr = new StackOnly();
    // delete ptr;
    // StackOnly* arr = new StackOnly[5];
    // delete[] arr;
    return 0;
}

4. 不可被继承的类

在C++98标准下,可以通过将类的构造函数声明为private来阻止类被继承。因为当一个类想要继承另一个类(作为子类)时,子类的构造函数会默认(隐式或显式地)调用父类的构造函数来初始化从父类继承下来的那部分成员。如果父类的构造函数是private的,子类就无法访问它,也就没办法完成这个初始化过程,从而导致编译错误,实现了禁止继承的效果。

以如下代码为例:

class NonInherit
{
public:
    static NonInherit GetInstance()
    {
        return NonInherit();
    }
private:
    NonInherit()
    {}
};

这里定义了NonInherit类,它的构造函数是私有的,唯一对外提供了一个静态成员函数GetInstance用于获取该类的实例(返回一个临时的NonInherit对象)。

C++11引入了final关键字,当在类定义时使用final关键字修饰该类,就明确告知编译器这个类是不允许被继承的,例如:

class A final
{
    // 类的成员定义等内容
};

如果后续有其他类试图去继承这个被final修饰的类,像下面这样:

class B : public A
{
    // 编译会报错,因为A类不能被继承
};

编译器会直接报错,提示类Afinal的,不允许作为基类被继承,这种方式更加直观、明确地表达了类不可继承的意图,而且语法简洁明了。

5. 控制可创建对象的类

有时候我们也需要一个可以控制可创建对象的类:

首先我们将禁止构造函数,拷贝构造函数以及赋值重载。并单独提供一个静态方法来创建对象,方便我们管理。

class LimitedObjectCreator {
public:
    // 静态方法用于创建对象,返回指向创建对象的指针
    static LimitedObjectCreator* create() {
        if (count > 0) {
            --count;
            std::cout << "对象创建成功,还可创建的对象数量:" << count << std::endl;
            return new LimitedObjectCreator();
        }
        else {
            std::cerr << "已超出对象创建限制数量,无法创建对象。" << std::endl;
            return nullptr;
        }
    }

    // 析构函数,用于释放对象资源,这里可以添加具体的资源释放逻辑(如果有需要)
    ~LimitedObjectCreator() {
        ++count;
        std::cout << "对象已销毁,还可创建的对象数量:" << count << std::endl;
    }

    static void setObjectLimit(int num) {
        count = num;
    }

private:
    // 将构造函数声明为私有,禁止外部直接调用构造函数创建对象
    LimitedObjectCreator() {}
    // 拷贝构造函数也声明为私有,禁止拷贝构造
    LimitedObjectCreator(const LimitedObjectCreator&) = delete;
    // 赋值运算符重载同样声明为私有,禁止赋值操作
    LimitedObjectCreator& operator=(const LimitedObjectCreator&) = delete;

    static int count;
};

// 初始化静态成员变量
int LimitedObjectCreator::count = 0;

int main() {
    LimitedObjectCreator::setObjectLimit(3);
    LimitedObjectCreator* obj1 = LimitedObjectCreator::create();
    LimitedObjectCreator* obj2 = LimitedObjectCreator::create();
    LimitedObjectCreator* obj3 = LimitedObjectCreator::create();
    LimitedObjectCreator* obj4 = LimitedObjectCreator::create();
    if (obj1 != nullptr) {
        delete obj1;
    }
    if (obj2 != nullptr) {
        delete obj2;
    }
    if (obj3 != nullptr) {
        delete obj3;
    }
    if (obj4 != nullptr) {
        delete obj4;
    }
    return 0;
}

我们可以通过一个静态成员变量 count来控制可创建对象的个数,其可以通过专门的函数 setObjectLimit设置,每次创建时可创建对象个数会减一--count,释放资源时可创建对象个数会相应增一++count

6. 单例模式

单例模式是一种设计模式,其核心特点是一个类只能创建一个对象,该模式可以保证系统中该类仅有一个实例,并提供一个访问它的全局访问点,此实例能被所有程序模块共享。

例如在服务器程序中,服务器的配置信息存放在文件里,可由一个单例对象统一读取配置数据,服务进程中的其他对象再通过这个单例对象获取配置信息,这简化了复杂环境下的配置管理。

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

6.1 饿汉模式

饿汉模式的核心思想是在程序启动阶段(也就是 main 函数执行之前)就创建单例类的唯一实例对象,无论后续程序运行过程中是否会实际使用到这个实例。这种方式就好像一个人很饿,提前把食物都准备好,不管后面吃不吃。

  • 优点:实现起来较为简单直观,代码结构清晰,不需要考虑复杂的线程同步问题,因为实例在一开始就创建好了,后续只是获取这个已存在的实例而已。
  • 缺点:可能会导致进程启动变慢,尤其是当单例对象的构造过程比较复杂、耗时(例如需要加载大量配置文件、初始化很多资源等),在程序启动时就执行这些操作会拖慢启动速度。另外,当存在多个单例类对象实例时,它们的创建顺序是不确定的,这在一些对实例初始化顺序有严格要求的场景下可能会带来问题。
namespace Hungry_Man {
    // 饿汉模式--main函数之前就创建对象
    class Singleton {
    public:
        static Singleton* GetInstance() {
            return &_inst;
        }
    private:
        Singleton(const Singleton&) = delete;
        Singleton& operator=(const Singleton&) = delete;
        Singleton() {}
        static Singleton _inst;
    };
    Singleton Singleton::_inst;
}

在这个代码中,Singleton 类定义了一个私有的静态成员变量 _inst,它就是单例类的唯一实例。在类外进行了 Singleton::_inst 的定义,这使得在程序启动阶段,这个实例就会被创建出来(在 main 函数执行之前就已经存在了)。

GetInstance 函数很简单,它只是返回这个已经创建好的实例的地址。由于构造函数被声明为私有,外部无法随意创建 Singleton 类的其他对象,保证了整个程序中只有这一个实例存在,符合单例模式的要求。而且通过 delete 关键字删除了拷贝构造函数和赋值运算符重载函数,防止了通过拷贝或赋值的方式产生额外的对象实例。

6.2 懒汉模式

懒汉模式采取的是延迟加载的策略,只有在第一次真正需要使用单例对象时才去创建它,就好比一个人很懒,等到要吃东西了才去准备食物。这种模式适用于单例对象构造比较耗时或者占用资源较多(比如加载插件、初始化网络连接、读取文件等情况),并且程序运行时有可能根本不会用到该对象的场景,这样可以避免在程序启动阶段就消耗不必要的资源。

  • 优点:因为是在第一次使用时才创建对象,所以不会影响程序启动的速度,进程启动时没有额外的负载。同时,多个单例实例的启动顺序可以根据实际使用的先后情况自由控制,比较灵活。
  • 缺点:实现相对复杂一些,需要充分考虑线程安全问题,在多线程环境下,如果多个线程同时尝试获取单例对象,要保证只有一个线程能够创建实例,避免重复创建。另外,还需要考虑对象的释放问题,要合理地进行内存管理,确保单例对象在合适的时候被正确释放,避免内存泄漏等问题。
namespace Lazy_MAN {
    // 懒汉模式
    class Singleton {
    public:
        static Singleton* GetInstance() {
            // 双检查加锁方式,
            if (_pinst == nullptr) {             // 第一次判断是防止对象创建好以后,还要每次加锁,就浪费了
                unique_lock<mutex> lock(_mtx);
                if (_pinst == nullptr) {       // 第二次判断是为了防止多个线程一起写不安全现象
                    _pinst = new Singleton;
                }
            }
            return _pinst;
        }

        static void DelInstance() {
            delete _pinst;
            _pinst = nullptr;
        }
    class GC
    {
    public:
        ~GC()
        {
            Singleton::DelInstance();
        }
    };
    private:
        Singleton() {}
        Singleton(const Singleton&) = delete;
        Singleton& operator=(const Singleton&) = delete;
        static Singleton* _pinst;
        static mutex _mtx;
        static GC gc;// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
    };
    Singleton* Singleton::_pinst = nullptr;
    mutex Singleton::_mtx;
}

值得强调的是在 GetInstance 函数中采用了双检查加锁机制:

  • 第一次 if (_pinst == nullptr) 判断:从性能优化角度出发,如果单例对象已经被创建(即 _pinst 不为 nullptr),那么直接返回已存在的实例即可,无需再进行加锁和后续创建实例的操作。因为加锁解锁本身是有一定开销的,如果每次调用 GetInstance 都进行加锁,会影响程序性能,尤其是在单例对象已经创建好的情况下,这种开销是不必要的。
  • 第二次 if (_pinst == nullptr) 判断:主要是从线程安全角度考虑,在多线程环境下,可能会出现多个线程同时通过了第一次 if 判断(因为此时 _pinst 确实为 nullptr,单例对象还未创建),然后这些线程都尝试获取锁并进入到临界区(由 unique_lock 保护的代码块)。如果没有第二次 if 判断,那么每个线程都会执行 _pinst = new Singleton; 这一语句,从而导致创建多个单例对象,违背了单例模式的初衷。而第二次判断确保只有一个线程能够真正执行创建单例对象的操作,其他线程在等待锁释放后,再次检查 _pinst 时,会发现单例对象已经被创建,从而避免重复创建,保证了在多线程环境下单例对象的唯一性。

除此之外,我们还定义了一个内部类 GC,其析构函数中调用了 DelInstance 函数,这样在程序结束时,系统会自动调用 GC 的析构函数,进而释放单例对象,解决了对象释放的问题,避免内存泄漏。同时,通过将拷贝构造函数和赋值运算符重载函数声明为 delete,同样防止了外部创建多个对象实例的情况,保证单例模式的正确实现。

当然我们也可以采用如下方式实现一个简单的懒汉单例模式:

class Singleton
{
public:
    static Singleton& GetInstance()
    {
        //C++11之后局部静态变量是线程安全的
        static Singleton inst;
        return inst;
    }
private:
    Singleton() = default;
    Singleton(const Singleton&) = delete;
    Singleton&operator=(const Singleton&) = delete;
};
int main()
{
    Singleton &inst = Singleton::GetInstance();
    return 0;
}

最后懒汉模式和饿汉模式的区别总结如下:

  • 懒汉模式需要考虑线程安全和释放问题,实现相对复杂;饿汉模式不存在这些问题,实现简单。
  • 懒汉是懒加载模式,在需要时初始化创建对象,不影响程序启动;饿汉模式在程序启动阶段就创建初始化实例对象,可能导致程序启动慢,影响体验。
  • 如果有多个单例类且存在依赖关系(如B依赖A,要求A单例先创建初始化,B单例再创建初始化),则不能用饿汉模式(无法保证创建初始化顺序),这时懒汉模式可手动控制。

而且在实际应用中,懒汉模式通常更实用。

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

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

相关文章

【大数据学习 | Spark-Core】Spark提交及运行流程

spark的集群运行结构 我们要选择第一种使用方式 命令组成结构 spark-submit [选项] jar包 参数 standalone集群能够使用的选项。 --master MASTER_URL #集群地址 --class class_name #jar包中的类 --executor-memory MEM #executor的内存 --executor-cores NUM # executor的…

【CSP CCF记录】201903-2第16次认证 二十四点

题目 样例1输入 10 934x3 54x5x5 7-9-98 5x6/5x4 3579 1x19-9 1x9-5/9 8/56x9 6x7-3x6 6x44/5 样例1输出 Yes No No Yes Yes No No No Yes Yes 样例1解释 思路 参考&#xff1a;CCF小白刷题之路---201903-2 二十四点&#xff08;C/C 100分&#xff09;_ccf认证小白-CSDN博客 …

多目标粒子群优化(Multi-Objective Particle Swarm Optimization, MOPSO)算法

概述 多目标粒子群优化&#xff08;MOPSO&#xff09; 是粒子群优化&#xff08;PSO&#xff09;的一种扩展&#xff0c;用于解决具有多个目标函数的优化问题。MOPSO的目标是找到一组非支配解&#xff08;Pareto最优解&#xff09;&#xff0c;这些解在不同目标之间达到平衡。…

tomcat 后台部署 war 包 getshell

1. tomcat 后台部署 war 包 getshell 首先进入该漏洞的文件目录 使用docker启动靶场环境 查看端口的开放情况 访问靶场&#xff1a;192.168.187.135:8080 访问靶机地址 http://192.168.187.135:8080/manager/html Tomcat 默认页面登录管理就在 manager/html 下&#xff0c…

4.6 JMeter HTTP信息头管理器

欢迎大家订阅【软件测试】 专栏&#xff0c;开启你的软件测试学习之旅&#xff01; 文章目录 前言1 HTTP信息头管理器的位置2 常见的HTTP请求头3 添加 HTTP 信息头管理器4 应用场景 前言 在 JMeter 中&#xff0c;HTTP信息头管理器&#xff08;HTTP Header Manager&#xff09…

NVR管理平台EasyNVR多品牌NVR管理工具的流媒体视频融合与汇聚管理方案

随着信息技术的飞速发展&#xff0c;视频监控已经成为现代社会安全管理和业务运营不可或缺的一部分。无论是智慧城市、智能交通、还是大型企业、校园安防&#xff0c;视频监控系统的应用都日益广泛。NVR管理平台EasyNVR&#xff0c;作为功能强大的流媒体服务器软件&#xff0c;…

【大数据学习 | Spark-Core】Spark的改变分区的算子

当分区由多变少时&#xff0c;不需要shuffle&#xff0c;也就是父RDD与子RDD之间是窄依赖。 当分区由少变多时&#xff0c;是需要shuffle的。 但极端情况下&#xff08;1000个分区变成1个分区)&#xff0c;这时如果将shuffle设置为false&#xff0c;父子RDD是窄依赖关系&…

微代码-C语言如何分配内存并自动清零?(calloc)

背景 在C语言中&#xff0c;calloc 函数用于分配内存&#xff0c;并且会自动将所有位初始化为零。calloc 的原型定义在 stdlib.h 头文件中&#xff0c;其函数原型如下&#xff1a; void *calloc(size_t num, size_t size);使用例子&#xff1a; #include <stdio.h> #i…

自动语音识别(ASR)与文本转语音(TTS)技术的应用与发展

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

ARM 架构(Advanced RISC Machine)精简指令集计算机(Reduced Instruction Set Computer)

文章目录 1、ARM 架构ARM 架构的特点ARM 架构的应用ARM 架构的未来发展 2、RISCRISC 的基本概念RISC 的优势RISC 的应用RISC 与 CISC 的对比总结 1、ARM 架构 ARM 架构是一种低功耗、高性能的处理器架构&#xff0c;广泛应用于移动设备、嵌入式系统以及越来越多的服务器和桌面…

戴尔 AI Factory 上的 Agentic RAG 搭载 NVIDIA 和 Elasticsearch 向量数据库

作者&#xff1a;来自 Elastic Hemant Malik, Dell Team 我们很高兴与戴尔合作撰写白皮书《戴尔 AI Factory with NVIDIA 上的 Agentic RAG》。白皮书是一份供开发人员参考的设计文档&#xff0c;概述了实施 Agentic 检索增强生成 (retrieval augmented generation - RAG) 应用…

Vue实训---0-完成Vue开发环境的搭建

1.在官网下载和安装VS Code编辑器 完成中文语言扩展&#xff08;chinese&#xff09;&#xff0c;安装成功后&#xff0c;需要重新启动VS Code编辑器&#xff0c;中文语言扩展才可以生效。 安装Vue-Official扩展&#xff0c;步骤与安装中文语言扩展相同&#xff08;专门用于为“…

POA-CNN-SVM鹈鹕算法优化卷积神经网络结合支持向量机多特征分类预测

分类预测 | Matlab实现POA-CNN-SVM鹈鹕算法优化卷积神经网络结合支持向量机多特征分类预测 目录 分类预测 | Matlab实现POA-CNN-SVM鹈鹕算法优化卷积神经网络结合支持向量机多特征分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述 1.Matlab实现POA-CNN-SVM鹈鹕算法…

(STM32)ADC驱动配置

1.ADC驱动&#xff08;STM32&#xff09; ADC模块中&#xff0c;**常规模式&#xff08;Regular Mode&#xff09;和注入模式&#xff08;Injected Mode&#xff09;**是两种不同的ADC工作模式 常规模式&#xff1a;用于普通的ADC转换&#xff0c;是默认的ADC工作模式。 注入…

flume-将日志采集到hdfs

看到hdfs大家应该做什么&#xff1f; 是的你应该去把集群打开&#xff0c; cd /export/servers/hadoop/sbin 启动集群 ./start-all.sh 在虚拟机hadoop02和hadoop03上的conf目录下配置相同的日志采集方案&#xff0c;‘ cd /export/servers/flume/conf 切换完成之后&#…

机器人SLAM建图与自主导航:从基础到实践

前言 这篇文章我开始和大家一起探讨机器人SLAM建图与自主导航 &#xff0c;在前面的内容中&#xff0c;我们介绍了差速轮式机器人的概念及应用&#xff0c;谈到了使用Gazebo平台搭建仿真环境的教程&#xff0c;主要是利用gmapping slam算法&#xff0c;生成一张二维的仿真环境…

在线解析工具链接

在线字数统计工具-统计字符字节汉字数字标点符号-计算word文章字数字数统计,字符统计,字节统计,字数计算,统计字数,统计字节数,统计字符数,统计word字数,在线字数统计,在线查字数,计算字数,字数统计工具,支持手机移动端查询多少字数,英文:Calculate the number of words,Count …

学习Servlet(含义,作用)

目录 前言 Servlet 的含义 Servlet 的作用 前言 一个完整的前后端项目&#xff0c;是需要前端和后端&#xff08;Java实现&#xff09;共同完成的。那应该如何实现前后端进行交互呢&#xff1f;答案&#xff1a;使用Servlet实现前后端交互 我会从了解Servlet的含义&…

从源码到应用:在线教育系统与教培网校APP开发实战指南

时下&#xff0c;各类教培网校APP逐渐成为教育机构的核心工具。那么&#xff0c;如何从源码出发&#xff0c;开发一套符合需求的在线教育系统与教培网校APP&#xff1f;本文将从架构设计、功能实现到部署上线&#xff0c;提供一份全面的开发实战指南。 一、在线教育系统的核心架…

Pyqt5的簡單教程

簡介 pyqt5是qt的Python版本&#xff0c;因為最近需要做一個有界面的程式&#xff0c;所以想到這個庫&#xff0c;這裡就稍微介紹它的安裝和使用教程 1.安裝qt5 可能需要安裝vs的c編譯組件 pip install pyQt52.使用拖拽組件編寫頁面 使用此工具打開組件 ctrls 生成.ui文件 …