【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路

news2024/11/17 7:40:01

文章目录

  • C++ `string` 类的模拟实现:从构造到高级操作
    • 前言
    • 第一章:为什么要手写 C++ `string` 类?
      • 1.1 理由与价值
    • 第二章:实现一个简单的 `string` 类
      • 2.1 基本构造与析构
        • 2.1.1 示例代码:基础的 `string` 类实现
        • 2.1.2 解读代码
      • 2.2 浅拷贝与其缺陷
        • 2.2.1 示例代码:浅拷贝问题
      • 2.3 深拷贝的解决方案
        • 2.3.1 示例代码:实现深拷贝
    • 第三章:赋值运算符重载与深拷贝
      • 3.1 为什么需要重载赋值运算符?
      • 3.2 实现赋值运算符重载
        • 3.2.1 示例代码:赋值运算符重载
        • 3.2.2 解读代码
    • 第四章:迭代器与字符串操作
      • 4.1 迭代器的实现
        • 4.1.1 示例代码:实现 `string` 类的迭代器
    • 第五章:字符串的常见操作
      • 5.1 查找操作
        • 5.1.1 示例代码:实现字符和子字符串查找
      • 5.1.2 静态 `const` 成员变量初始化规则详解
        • 5.1.2.1 静态成员变量属于类,而不属于对象
        • 5.1.2.2 `const` 修饰符的作用
        • 5.1.2.3 整型和枚举类型的特殊处理
        • 5.1.2.4 复杂类型为什么不能在类内初始化?
        • 5.1.2.5 为什么 `static const size_t npos = -1` 可以在类内初始化?
        • 5.1.2.6 总结:为什么静态 `const` 的复杂类型不能在类内初始化
      • 5.2 插入操作
        • 5.2.1 示例代码:实现字符串插入
      • 5.3 删除操作
        • 5.3.1 示例代码:实现字符串删除
    • 读者须知与结语

C++ string 类的模拟实现:从构造到高级操作

💬 欢迎讨论:在实现 string 类的过程中,如果有任何疑问,欢迎在评论区交流!我们一起探讨如何一步步实现它。

👍 支持一下:如果这篇文章对你有所帮助,记得点赞、收藏并分享给更多对 C++ 感兴趣的小伙伴吧!你们的支持是我创作的动力!


前言

在 C++ 标准库中,string 类是用于字符串操作的一个非常常见和重要的类,它极大地简化了开发者处理字符串的过程。然而,为了深入理解 C++ 的核心机制,特别是内存管理、深拷贝与浅拷贝的差异、运算符重载等底层细节,自己实现一个简易的 string 类是一个很好的练习。

通过本篇博客,我们将一步步实现一个简单的 string 类,并且深入探讨与之相关的现代 C++ 特性,包括内存管理、深拷贝与浅拷贝、移动语义等。我们会从最基础的构造函数开始,逐步扩展功能。


第一章:为什么要手写 C++ string 类?

1.1 理由与价值

在面试或者一些学习场景中,手写 string 类不仅仅是对字符串操作的考察,更多的是考察程序员对 C++ 内存管理的理解。例如,深拷贝与浅拷贝的实现,如何正确重载赋值运算符,如何避免内存泄漏,这些都是需要掌握的核心技能。

实现一个简易的 string 类可以帮助我们更好地理解:

  1. C++ 中动态内存管理:如何正确地分配与释放内存。
  2. 深拷贝与浅拷贝的区别:当对象之间共享资源时,如何避免潜在问题。
  3. 运算符重载的实现:尤其是赋值运算符和输出运算符的重载。
  4. 现代 C++ 特性:包括移动语义、右值引用等。

接下来,我们会从一个简单的 string 类开始,逐步扩展。


第二章:实现一个简单的 string

2.1 基本构造与析构

我们先实现 string 类的基础部分,包括构造函数、析构函数、字符串存储、内存管理等基础操作。在最初的实现中,我们将模拟 C++ 标准库 string 类的基本行为,让其能够存储字符串,并在析构时正确释放内存。

2.1.1 示例代码:基础的 string 类实现
#include <iostream>
#include <cstring>   // 包含 strlen 和 strcpy 函数
#include <cassert>   // 包含 assert 函数

namespace W
{
    class string
    {
    public:
        // 默认构造函数
        string(const char* str = "") {
            _size = strlen(str);  // 计算字符串长度
            _capacity = _size;
            _str = new char[_capacity + 1];  // 动态分配内存
            strcpy(_str, str);  // 复制字符串内容
        }

        // 析构函数
        ~string() {
            if (_str) {
                delete[] _str;  // 释放动态分配的内存
                _str = nullptr;
            }
        }

    private:
        char* _str;  // 存储字符串的字符数组
        size_t _capacity;  // 分配的内存容量
        size_t _size;  // 当前字符串的有效长度
    };
}

int main() {
    W::string s("Hello, World!");
    return 0;  // 程序结束时,析构函数自动释放内存
}
2.1.2 解读代码

在这个简单的 string 类中,我们实现了两个重要的函数:

  • 构造函数:为字符串动态分配内存,并将传入的字符串内容复制到新分配的空间中。
  • 析构函数:使用 delete[] 释放动态分配的内存,以避免内存泄漏。

接下来,我们将讨论拷贝构造函数以及浅拷贝带来的潜在问题。


2.2 浅拷贝与其缺陷

当前版本的 string 类只支持基本的构造和析构操作。如果我们通过另一个 string 对象来构造新的对象,默认情况下会发生浅拷贝,即对象共享同一块内存。这会带来潜在的内存管理问题,特别是当对象被销毁时,会导致多个对象同时试图释放同一块内存,进而导致程序崩溃。

2.2.1 示例代码:浅拷贝问题
void TestString() {
    W::string s1("Hello C++");
    W::string s2(s1);  // 浅拷贝,s1 和 s2 共享同一块内存
    // 当程序结束时,析构函数会尝试两次释放同一块内存,导致程序崩溃
}

问题分析:浅拷贝的默认行为只复制指针的值,即 s1s2 都指向同一个内存区域。因此,当程序执行析构函数时,会尝试两次释放同一块内存,导致程序崩溃。


2.3 深拷贝的解决方案

为了避免浅拷贝带来的问题,我们需要在拷贝构造函数中实现深拷贝。深拷贝确保每个对象都有自己独立的内存空间,不会与其他对象共享内存。

2.3.1 示例代码:实现深拷贝
namespace W
{
    class string
    {
    public:
        // 构造函数
        string(const char* str = "") {
            _size = strlen(str);
            _capacity = _size;
            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }

        // 深拷贝构造函数
        string(const string& s) {
            _size = s._size;
            _capacity = s._capacity;
            _str = new char[_capacity + 1];  // 分配新的内存
            strcpy(_str, s._str);  // 复制字符串内容
        }

        // 析构函数
        ~string() {
            delete[] _str;
        }

    private:
        char* _str;
        size_t _capacity;
        size_t _size;
    };
}

void TestString() {
    W::string s1("Hello C++");
    W::string s2(s1);  // 深拷贝,s1 和 s2 拥有独立的内存
}

第三章:赋值运算符重载与深拷贝

3.1 为什么需要重载赋值运算符?

在C++中,当我们将一个对象赋值给另一个对象时,默认情况下,编译器会为我们生成一个浅拷贝的赋值运算符。这意味着赋值后的对象和原对象会共享同一个内存空间,这会导致和浅拷贝相同的潜在问题,特别是在一个对象被销毁时,另一个对象继续使用该内存区域会引发错误。

为了解决这个问题,我们需要手动重载赋值运算符,确保每个对象都拥有自己独立的内存空间。

3.2 实现赋值运算符重载

在赋值运算符重载中,我们需要考虑以下几点:

  1. 自我赋值:对象是否会被赋值给自己,避免不必要的内存释放和分配。
  2. 释放原有资源:在赋值前,我们需要释放被赋值对象原有的内存资源,避免内存泄漏。
  3. 深拷贝:为目标对象分配新的内存,并复制内容。
3.2.1 示例代码:赋值运算符重载
namespace W
{
    class string
    {
    public:
        // 构造函数
        string(const char* str = "") {
            _size = strlen(str);
            _capacity = _size;
            _str = new char[_capacity + 1];
            strcpy(_str, str);
        }

        // 深拷贝构造函数
        string(const string& s) {
            _size = s._size;
            _capacity = s._capacity;
            _str = new char[_capacity + 1];
            strcpy(_str, s._str);
        }

        // 赋值运算符重载
        string& operator=(const string& s) {
            if (this != &s) {  // 避免自我赋值
                delete[] _str;  // 释放原有内存
                _size = s._size;
                _capacity = s._capacity;
                _str = new char[_capacity + 1];  // 分配新内存
                strcpy(_str, s._str);  // 复制内容
            }
            return *this;
        }

        // 析构函数
        ~string() {
            delete[] _str;
        }

    private:
        char* _str;
        size_t _capacity;
        size_t _size;
    };
}

void TestString() {
    W::string s1("Hello");
    W::string s2("World");
    s2 = s1;  // 调用赋值运算符重载
}
3.2.2 解读代码
  1. 自我赋值检查:自我赋值是指对象在赋值时被赋值给自己,例如 s1 = s1。在这种情况下,如果我们没有进行检查,就会先删除对象的内存,然后再试图复制同一个对象的内容,这样会导致程序崩溃。因此,重载赋值运算符时,自我赋值检查是非常必要的。

  2. 释放原有内存:在分配新内存之前,我们必须先释放旧的内存,以防止内存泄漏。

  3. 深拷贝:通过分配新的内存,确保目标对象不会与源对象共享内存,避免浅拷贝带来的问题。


第四章:迭代器与字符串操作

4.1 迭代器的实现

迭代器是一种用于遍历容器(如数组、string 等)的工具,它允许我们在不直接访问容器内部数据结构的情况下遍历容器。通过迭代器,可以使用范围 for 循环等简便的方式遍历 string 对象中的字符。

在我们的 string 类中,迭代器一般会被实现为指向字符数组的指针

4.1.1 示例代码:实现 string 类的迭代器
namespace W
{
    class string
    {
    public:
        // 非const迭代器
        typedef char* iterator;
        // const迭代器
        typedef const char* const_iterator;

        // 构造函数与析构函数等...
        
        // 非const迭代器接口
        iterator begin() { return _str; }
        iterator end() { return _str + _size; }

        // const迭代器接口(针对const对象)
        const_iterator begin() const { return _str; }
        const_iterator end() const { return _str + _size; }

    private:
        char* _str;
        size_t _capacity;
        size_t _size;
    };

}

void TestIterator() {
    W::string s("Hello World!");

    // 非const对象使用迭代器
    for (W::string::iterator it = s.begin(); it != s.end(); ++it) {
        *it = toupper(*it);  // 转换为大写
    }
    std::cout << s << std::endl;  // 输出:HELLO WORLD!

    // const对象使用const迭代器
    const W::string cs("Const String!");
    for (W::string::const_iterator it = cs.begin(); it != cs.end(); ++it) {
        std::cout << *it;  // 只能读取,不能修改
    }
    std::cout << std::endl;

	for (auto& ch : s) {
        ch = tolower(ch);  // 转换为小写
    }
    std::cout << s << std::endl;  // 输出:hello world!

    // 范围for循环遍历const对象
    for (const auto& ch : cs) {
        std::cout << ch;  // 只能读取,不能修改
    }
    std::cout << std::endl;
}


第五章:字符串的常见操作

在 C++ 标准库 string 类中,提供了很多方便的字符串操作接口,如查找字符或子字符串、插入字符、删除字符等。我们也需要在自定义的 string 类中实现这些操作。接下来,我们将逐步实现这些功能,并进行测试。


5.1 查找操作

C++ 中 string 类的 find() 函数用于查找字符串或字符在当前字符串中的位置。如果找到了字符或子字符串,find() 会返回其位置;如果找不到,则返回 string::npos

我们将在自定义的 string 类中实现类似的功能。

5.1.1 示例代码:实现字符和子字符串查找
namespace W
{
    class string
    {
    public:
        // 构造函数与析构函数等...

        // 查找字符在字符串中的第一次出现位置
        size_t find(char c, size_t pos = 0) const {
            assert(pos < _size);
            for (size_t i = pos; i < _size; ++i) {
                if (_str[i] == c) {
                    return i;
                }
            }
            return npos;  // 如果没有找到,返回 npos
        }

        // 查找子字符串在字符串中的第一次出现位置
        size_t find(const char* str, size_t pos = 0) const {
            assert(pos < _size);
            const char* p = strstr(_str + pos, str);
            if (p) {
                return p - _str;  // 计算子字符串的位置
            }
            return npos;  // 如果没有找到,返回 npos
        }

    public:
        static const size_t npos = -1;  // 定义 npos 为 -1,表示未找到

    private:
        char* _str;
        size_t _capacity;
        size_t _size;
    };
}

void TestFind() {
    W::string s("Hello, World!");

    // 查找字符
    size_t pos = s.find('W');
    if (pos != W::string::npos) {
        std::cout << "'W' found at position: " << pos << std::endl;
    } else {
        std::cout << "'W' not found." << std::endl;
    }

    // 查找子字符串
    size_t subPos = s.find("World");
    if (subPos != W::string::npos) {
        std::cout << "'World' found at position: " << subPos << std::endl;
    } else {
        std::cout << "'World' not found." << std::endl;
    }
}

看到这里细心的小伙伴可能发现了,我们在声明npos的时候直接给了初始值,但是之前我们在【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解里明确说过静态成员变量只能在类外初始化,以及const修饰的变量只能在初始化列表初始化,但这里却可以

这是为什么呢?不得不承认这是一看到就令人困惑的语法😂让我们来梳理一下

5.1.2 静态 const 成员变量初始化规则详解

5.1.2.1 静态成员变量属于类,而不属于对象

静态成员变量是在类层次上定义的,而不是在对象层次上。换句话说,静态成员变量是所有对象共享的,且只会有一份实例存在。因此,静态成员变量的内存是在程序的全局区域分配的,而不是在每个对象的内存中分配。

  • 静态变量需要在全局范围内被初始化,以确保在所有对象中共享的唯一实例具有一致的值。

5.1.2.2 const 修饰符的作用
  • const 表示变量的值在其生命周期内不能被修改。因此,const 静态成员变量的值必须在类加载时确定,并且在整个程序运行过程中保持不变。
  • 但是 const 静态成员的值不能在对象实例化时通过构造函数来提供,必须直接在类级别初始化。

5.1.2.3 整型和枚举类型的特殊处理

C++ 允许整型(如 intchar)和枚举类型const 静态成员变量在类内部进行初始化。这是因为这些类型的值可以在编译时完全确定,编译器不需要等待运行时计算或分配内存。

class MyClass {
public:
    static const int value = 42;  // 可以直接在类内初始化
};
  • 编译器可以将 value 当作编译时常量,它可以直接内联到使用它的代码中,不需要单独的存储空间。这种优化适用于常量表达式。

5.1.2.4 复杂类型为什么不能在类内初始化?

对于复杂类型(如 doublefloat 或自定义类等),这些类型的初始化可能涉及到运行时的计算或需要分配更多的内存。C++ 的设计者为了避免复杂类型的静态成员在类内初始化时增加不必要的复杂性,要求这些变量必须在类外进行初始化。

class MyClass {
public:
    static const double pi;  // 在类内声明,但不能直接初始化
};

// 在类外初始化
const double MyClass::pi = 3.14159;

5.1.2.5 为什么 static const size_t npos = -1 可以在类内初始化?

size_t 是一种整型类型,尽管其大小和符号位取决于平台,但它仍然是整型常量的一种。因此,npos 的初始化类似于前面提到的整型静态成员变量。由于 -1 可以表示为 size_t 的最大值,这个值在编译时就可以确定,因此它符合类内初始化的条件。

class String {
public:
    static const size_t npos = -1;  // 可以在类内初始化
};
  • 总结:因为 npos 是整型常量,并且编译器可以在编译时确定其值,符合在类内部初始化的条件。

5.1.2.6 总结:为什么静态 const 的复杂类型不能在类内初始化
  • 整型和枚举类型const 静态成员变量可以在类内初始化,因为它们是编译时常量,编译器可以直接替换为常量值。
  • 复杂类型(如 double 或对象类型)需要在类外初始化,因为这些类型的初始化可能依赖运行时的条件或动态内存分配。
  • 这是 C++ 设计者在保证效率和复杂性之间做出的权衡,允许简单类型进行编译时优化,但要求复杂类型在类外显式初始化,以确保其初始化的灵活性和正确性。

没啥好说的,人家设计的,记住就行了🥲🥲


5.2 插入操作

C++ 中的 string 类允许我们在字符串的任意位置插入字符或子字符串。接下来,我们将在自定义的 string 类中实现类似的插入功能。

5.2.1 示例代码:实现字符串插入

其他没啥,注意下面这个问题:

无符号整型的易错问题👍

//注意:下面这个写法当pos==0时会出现死循环问题哦
            /*for (size_t i = _size; i >= pos; --i) {
                _str[i + len] = _str[i];
            }*/
namespace W
{
    class string
    {
    public:
        // 构造函数与析构函数等...

        // 在指定位置插入字符
        string& insert(size_t pos, char c) {
            assert(pos <= _size);  // 确保插入位置合法

            if (_size == _capacity) {
                reserve(_capacity * 2);  // 如果容量不够,扩展容量
            }

            // 将 pos 位置后的字符后移一位
            for (size_t i = _size; i > pos; --i) {
                _str[i] = _str[i - 1];
            }

            _str[pos] = c;
            ++_size;
            _str[_size] = '\0';

            return *this;
        }

        // 在指定位置插入字符串
        string& insert(size_t pos, const char* str) {
            assert(pos <= _size);

            size_t len = strlen(str);
            if (_size + len > _capacity) {
                reserve(_size + len);  // 如果容量不够,扩展容量
            }

            // 将 pos 位置后的字符后移 len 位
            //注意:下面这个写法当pos==0时会出现死循环问题哦
            /*for (size_t i = _size; i >= pos; --i) {
                _str[i + len] = _str[i];
            }*/
			//采用这种
			for (size_t i = _size; i + 1 > pos; --i) {
   				_str[i + len] = _str[i];
			}


            // 复制要插入的字符串
            memcpy(_str + pos, str, len);
            _size += len;
            _str[_size] = '\0';

            return *this;
        }

    private:
        char* _str;
        size_t _capacity;
        size_t _size;
    };
}

void TestInsert() {
    W::string s("Hello World!");

    // 在第 5 位置插入逗号
    s.insert(5, ',');
    std::cout << s << std::endl;

    // 在第 6 位置插入字符串
    s.insert(6, " Beautiful");
    std::cout << s << std::endl;
}

5.3 删除操作

string 类允许我们删除指定位置的字符或子字符串。接下来,我们实现字符串的删除功能。

5.3.1 示例代码:实现字符串删除
namespace W
{
    class string
    {
    public:
        // 在指定位置删除若干字符
        string& erase(size_t pos, size_t len = npos) {
            assert(pos <= _size);  // 确保删除的位置合法

            if (len == 0 || pos == _size) {
                // 如果 len 为 0 或 pos 已经到达字符串末尾,无需执行任何操作
                return *this;
            }

            if (len == npos || pos + len > _size) {
                len = _size - pos;  // 确保不越界删除
            }

            // 将 pos 后 len 位字符前移
            for (size_t i = pos; i + len < _size; ++i) {
                _str[i] = _str[i + len];
            }

            _size -= len;
            _str[_size] = '\0';  // 更新字符串末尾

            return *this;
        }

    private:
        char* _str;
        size_t _capacity;
        size_t _size;

    public:
        static const size_t npos = -1;  // 定义 npos 为 -1 表示无效位置
    };
}

void TestErase() {
    W::string s("Hello, Beautiful World!");

    // 删除第 5 位置后的 9 个字符
    s.erase(5, 9);
    std::cout << s << std::endl;  // 输出:Hello World!
}


读者须知与结语

在本文中,我们手写了一个简易版的 string 类,实现了诸如字符串插入、删除、查找等基本功能。然而,这个实现仍然是非常简陋的,使用了大量 C 风格的字符串函数,如 strlenstrcpy。这些函数都假设字符串是以 '\0' 结尾的字符数组,这意味着如果字符串中间出现 '\0',程序的行为将不可预期——它会错误地认为字符串已经结束。
此外,这个简易 string 类在面对一些复杂的情况时也会显得捉襟见肘。例如,我们并没有考虑多线程安全性异常处理等高级特性,而标准库的 std::string 早已针对这些问题进行了优化。标准库中的 string 类还支持更多的操作,并且在效率和内存管理上做了大量优化,因此我们的实现和真正的 std::string 相比可谓天差万别
但这并不是我们这篇文章的初衷。我们的目的是通过手写一个 string 类,让你深入理解底层的内存管理、拷贝控制、动态分配等核心概念。这些基础知识对于深入学习 C++ 编程和理解 STL 容器的实现原理至关重要。通过这个简化版的实现,希望你能更加透彻地理解 std::string 背后的机制。


如果你有任何问题或者更好的想法,欢迎在评论区分享你的观点。你们的反馈和支持是我创作的最大动力!

💬 欢迎讨论:如果你在学习过程中遇到问题,欢迎在评论区交流!

👍 支持一下:如果这篇文章对你有所帮助,请点赞、收藏并分享!你们的支持是我创作的动力!


以上就是关于【C++篇】手撕 C++ string 类:从零实现到深入剖析的模拟之路的内容啦,各位大佬有什么问题欢迎在评论区指正,或者私信我也是可以的啦,您的支持是我创作的最大动力!❤️

在这里插入图片描述

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

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

相关文章

电池快充协议芯片

1&#xff1a;18650充电快充规则 电池知识 | 东莞市恒帝电子科技有限公司 (heldee.com) 锂电池快速充电知识【钜大锂电】 (juda.cn)18 锂电池和18650锂电池能不能快速充电&#xff0c;四种充电方式讲解 | 东莞市恒帝电子科技有限公司 (heldee.com) 2&#xff1a;国产厂家 …

数据库课程 CMU15-445 2023 Fall Project-2 Extendible Hash Index

0 实验结果 tips:完成项目的前提不需要一定看视频 1 数据结构&#xff1a;扩展哈希 解释下这张图&#xff1a; 图中header的最大深度2&#xff0c;directory最大深度2&#xff0c;桶的容量2。 最开始的时候只有一个header。 插入第一个数据&#xff0c;假设这个数据对应的哈希…

安谋科技发布全新自研“玲珑”多媒体处理器

当前&#xff0c;受视频直播、AR/VR、智驾智舱等新兴应用场景和使用人群的飞速增长&#xff0c;视频编解码及显示处理领域呈现出旺盛需求&#xff0c;进而带动了下游设备数量不断攀升。以智能汽车为例&#xff0c;根据盖世汽车研究院的产业报告显示&#xff0c;预计2025年国内车…

【GeekBand】C++设计模式笔记4_Strategy_策略模式

1. “组件协作”模式 现代软件专业分工之后的第一个结果是“框架与应用程序的划分”&#xff0c;“组件协作”模式通过晚期绑定&#xff0c;来实现框架与应用程序之间的松耦合&#xff0c;是二者之间协作时常用的模式。典型模式 Template MethodStrategyObserver / Event 2.…

如何确定SAP 某些凭证或者单号的号码编码范围的 OBJECT 是什么?

在SAP的运维或者项目实施中&#xff0c;有时会如何确定SAP 某些凭证或者单号的号码 OBJECT 是什么&#xff1f; 一般一下常用的可以通过事务代码 例如&#xff1a; XDN1 Create Number Ranges for Customer Accounts&#xff0c;定义客户编码FBN1查看维护会计凭证号范围 我…

【项目】多设计模式下的同步异步日志系统

文章目录 项目介绍开发环境核心技术日志系统介绍为什么需要日志系统日志系统技术实现同步写日志异步写日志 相关技术知识补充不定参函数不定参宏函数的使用C中不定参函数的使用C中不定参函数的使用 设计模式单例模式工厂模式建造者模式代理模式 日志系统框架设计模块划分日志等…

springboot+阿里云物联网教程

需求背景 最近有一个项目,需要用到阿里云物联网,不是MQ。发现使用原来EMQX的代码去连接阿里云MQTT直接报错,试了很多种方案都不行。最终还是把错误分析和教程都整理一下。 需要注意的是,阿里云物联网平台和MQ不一样。方向别走偏了。 概念描述 EMQX和阿里云MQTT有什么区别…

springboot整合openfeign

文章目录 准备一、引入必要依赖二、写一个feign client并暴露到注册中心2.1 client2.2 开启Feign客户端功能 三、别的服务引入IProductClient并调用方法3.1 建一个order-service&#xff0c;引入IProductClient所在模块3.2 注入IProductClient&#xff0c;并调用方法 四、启动服…

Github优质项目推荐-第一期

文章目录 Github优质项目推荐一、【free-for-dev】&#xff0c;88.4k stars二、【linux-command】&#xff0c;31.5k stars三、【system-design-primer】&#xff0c;270k stars四、【GitHub-Chinese-Top-Charts】&#xff0c;99.1k stars五、【Docker-OSX】&#xff0c;46k st…

分布式计算框架

进入Scala模式 终端里输入Scala 创建一个新的Scala文件 vim 文件名.scala 复制粘贴代码 ctrlshift c/v 使用vim 先进入插入模式&#xff0c;可以通过按i键来实现&#xff0c;然后粘贴代码&#xff0c;完成后按Esc键退出插入模式&#xff0c;保存并退出可以通过输入:wq然后按…

HarmonyOS开发之利用TextPicker实现日期选择框只有【年】

效果图&#xff1a; 一&#xff1a;实现年份数组 function generateYearArray(startYear, endYear) {const yearArray [];for (let year startYear; year < endYear; year) {yearArray.push(year年);}return yearArray; } 二&#xff0c;设置年份区间&#xff08;1995,2…

每日论文2——用于锁相环应用的0.025%直流电流失配电荷泵

《A 0.025% DC Current Mismatch Charge Pump for PLL Applications 》2021 IEEE International Midwest Symposium on Circuits and Systems (MWSCAS) The Key Lab of micro-nano electronics and system integration of Xian city, Xian 本文结构主要不同是仅用了一个OPA&…

JAVA并发编程系列(11)线程池底层原理架构剖析

面试官&#xff1a;说说JAVA线程池的几个核心参数&#xff1f; 之前我们用了10篇文章详细剖析了synchronized、volatile、CAS、AQS、ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier、并发锁、Condition等各个核心基础原理&#xff0c;今天开始我们说说并发领域的各种…

基于单片机的精确电压表DA-AD转换

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于51单片机&#xff0c;采用DAC0832和ADC0832检测电压&#xff0c;0到8.5V&#xff0c;设计复位电路 LED管显示实际稳压值&#xff0c;初始电压0 二、硬件资源 基于KEIL5编写C代码&#xff0c…

《MATLAB项目实战》,专栏目录和介绍

文章目录 前言专栏介绍&#x1f393;一、 项目实战篇和GUI界面篇&#x1f393;二、 项目基础篇总结 前言 MATLAB 是一款强大且广泛应用的数值计算和数据可视化软件工具&#xff0c;它提供了一个高效、简洁的编程环境&#xff0c;使用户能够进行从简单的矩阵运算到复杂的多维数…

QT+ESP8266+STM32项目构建三部曲二--阿里云云端处理之云产品流转

一、创建一个新的产品 创建两个设备&#xff1a;一个用于stm32端连接并动态上传数据&#xff0c;一个用于上位机端订阅获取数据 添加功能&#xff0c;也就是物模型的的标签&#xff0c;这里根据自己在设计过程中需要的标签&#xff0c;自由设计 我这里定义了两个不同数据类型…

HBase DDL操作代码汇总(namespace+table CRUD操作)

HBase DDL操作 DDL操作主要是关于命名空间和表格的内容增删改查。 注&#xff1a;如果出现无法连接到zookeeper等的相关错误&#xff0c;可以将以下代码打jar包&#xff0c;在HMaster节点上执行 错误提示&#xff1a; Exception in thread “main” java.net.SocketTimeoutExc…

苹果更新过时产品:三款 Mac 成“古董”,九款 Mac 彻底“停产”

9 月 24 日消息苹果今天更新了“过时产品”名单&#xff0c;新增加了三款 Mac 型号&#xff0c;并将另外九款 Mac 型号从“过时产品”归为“停产产品”。 新入列的 Mac 过时产品&#xff1a; MacBook Air&#xff08;视网膜显示屏&#xff0c;13 英寸&#xff0c;2018 年&…

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-09-24

计算机前沿技术-人工智能算法-大语言模型-最新研究进展-2024-09-24 1. Enriching Datasets with Demographics through Large Language Models: What’s in a Name? K AlNuaimi, G Marti, M Ravaut, A AlKetbi, A Henschel… - arXiv preprint arXiv …, 2024 通过大型语言…

Centos下安装Maven(无坑版)

Linux 安装 Maven Maven 压缩包下载与解压 华为云下载源&#xff0c;自行选择版本 下面的示例使用的是 3.8.1 版本 wget https://repo.huaweicloud.com/apache/maven/maven-3/3.8.1/binaries/apache-maven-3.8.1-bin.tar.gz解压 tar -zxvf apache-maven-3.8.1-bin.tar.gz移…