string类(详解)

news2025/5/20 14:30:34
【本节目标】
1. 为什么要学习string类
2. 标准库中的string类
3. string类的模拟实现
4. 扩展阅读

1. 为什么学习string类?

1.1 C语言中的字符串

C 语言中,字符串是以 '\0' 结尾的一些字符的集合,为了操作方便, C 标准库中提供了一些 str 系列的库函数, 但是这些库函数与字符串是分离开的,不太符合OOP 的思想,而且底层空间需要用户自己管理,稍不留神可 能还会越界访问。

2. 标准库中的string

2.1 string(了解)

string类的文档cplusplus.com/reference/string/string/?kw=string

string 类的文档介绍
1. 字符串是表示字符序列的类
2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作 单字节字符字符串的设计特性。
3. string 类是使用 char( 即作为它的字符类型,使用它的默认 char_traits 和分配器类型 ( 关于模板的更多信 息,请参阅basic_string)
4. string 类是 basic_string 模板类的一个实例,它使用 char 来实例化 basic_string 模板类,并用 char_traits 和allocator 作为 basic_string 的默认参数 ( 根于更多的模板信息请参考 basic_string)
5. 注意,这个类独立于所使用的编码来处理字节 : 如果用来处理多字节或变长字符 ( UTF-8) 的序列,这个 类的所有成员( 如长度或大小 ) 以及它的迭代器,将仍然按照字节 ( 而不是实际编码的字符 ) 来操作。
总结:
1. string 是表示字符串的字符串类
2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作 string 的常规操作。
3. string 在底层实际是: basic_string 模板类的别名, typedef basic_string<char, char_traits, allocator> string;
4. 不能操作多字节或者变长字符的序列。
使用 string 类时,必须包含 #include 头文件以及 using namespace std ;

2.2 string类的常用接口说明(注意下面我只讲解最常用的接口)

1. string类对象的常见构造

string构造函数的使用:

string s1;

string s2("hello");

string s4(s2);

string s3(10,'*');

string s2("hello");
string s5(s2,2,2);
cout << s2 << endl;
cout << s5 << endl;

npos这个值是一个静态成员变量后面还有一个-1的缺省值,很多人可能好奇为什么缺省值给-1大家别忘了这是一个无符号类型无符号类型-1就是整型的最大值。意味着如果你不写第三个参数,那么这个函数会把pos位置后面的字符全部拿走。

当然这里能用流插入的原因是string这个库的非成员函数重载了流插入符号。

同时这个文档里也有对其函数的讲解

这里我们可以再看三个函数
push_back只能尾插单个字符,这时候我们就能使用append函数,它既可以尾插单个字符也能尾插字符串。

当然我们也能用一个运算符重载
int main()
{
	string s1("hello");
	s1.push_back(' ');
	s1.append("world");
	cout << s1 << endl;
	string s2("hello");
	s2 += ' ';
	s2 += "world";
	cout << s2 << endl;
	return 0;
}

2. string 类对象的容量操作
注意:
1. size() length() 方法底层实现原理完全相同,引入 size() 的原因是为了与其他容器的接口保持一
致,一般情况下基本都是用 size()
2. clear() 只是将 string 中有效字符清空,不改变底层空间大小。
3. resize(size_t n) resize(size_t n, char c) 都是将字符串中有效字符个数改变到 n 个,不同的是当字 符个数增多时:resize(n) 0 来填充多出的元素空间, resize(size_t n, char c) 用字符 c 来填充多出的 元素空间。注意:resize 在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大 小,如果是将元素个数减少,底层空间总大小不变(但是会删除里面的数据,例如:resize(20),就只要前20的数据,但是不会改变空间大小)。
4. reserve(size_t res_arg=0) :为 string 预留空间,不改变有效元素个数,当 reserve 的参数小于
string 的底层空间总大小时, reserver 不会改变容量大小,如果用clear进行的数据清除,那么当给的reserve参数小于已经扩容空间的大小,就会进行缩容,简单来说就是有数据就不会缩容,没数据就可能会缩容。在vs2019下,会比你指定的空间大一些。在Linux下一般指定多少给多少。
5.capacity()会扩容,扩容方式会不一样,在vs2019先是2倍扩容,后面是1.5倍,在Linux下一直是2倍扩容。
3. string 类对象的访问及遍历操作
注:operator[ ]有断言检查数组越界的错误。
4. string 类对象的修改操作
注意:
1. string 尾部追加字符时, s.push_back(c) / s.append(1, c) / s += 'c' 三种的实现方式差不多,一般情况下string 类的 += 操作用的比较多, += 操作不仅可以连接单个字符,还可以连接字符串。
2. string 操作时,如果能够大概预估到放多少字符,可以先通过 reserve 把空间预留好。
5. string 类非成员函数
6. vs g++ string 结构的说明
注意:下述结构是在 32 位平台下进行验证, 32 位平台下指针占 4 个字节。
vs string 的结构
string 总共占 28 个字节 ,内部结构稍微复杂一点,先是 有一个联合体,联合体用来定义 string 中字
符串的存储空间
当字符串长度小于 16 时,使用内部固定的字符数组来存放
当字符串长度大于等于 16 时,从堆上开辟空间
union _Bxty
{ // storage for small buffer or pointer to larger one
	value_type _Buf[_BUF_SIZE];
	pointer _Ptr;
	char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;
这种设计也是有一定道理的,大多数情况下字符串的长度都小于 16 ,那 string 对象创建好之后,内
部已经有了 16 个字符数组的固定空间,不需要通过堆创建,效率高。
其次:还有 一个 size_t 字段保存字符串长度,一个 size_t 字段保存从堆上开辟空间总的容量
最后:还 有一个指针 做一些其他事情。
故总共占 16+4+4+4=28 个字节。
g++ string 的结构
G++ 下, string 是通过写时拷贝实现的, string 对象总共占 4 个字节,内部只包含了一个指针,该指
针将来指向一块堆空间,内部包含了如下字段:
空间总大小
字符串有效长度
引用计数
struct _Rep_base
{
	size_type _M_length;
	size_type _M_capacity;
	_Atomic_word _M_refcount;
};
指向堆空间的指针,用来存储字符串。
迭代器的了解:
#include <iostream>
#include <string>
using namespace std;

int main()
{   
	string s1("hello world");
	//迭代器
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}

可以看到迭代器与指针十分相像。

#include <iostream>
#include <string>
using namespace std;

int main()
{   
	string s1("hello world");
	//迭代器
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		(*it)--;
		++it;
	}
	it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	return 0;
}

同时它也能通过解引用修改里面的数据。

当然迭代器的意义不止于此

#include <iostream>
#include <string>
using namespace std;

int main()
{   
	string s1("hello world");
	//迭代器
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		(*it)--;
		++it;
	}
	it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	//范围for
	//底层替换成迭代器
	for (auto& ch : s1)
	{
		ch++;
	}

	for (auto ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
	return 0;
}

范围for的底层就会替换成迭代器,想要支持范围for就要先支持迭代器。

#include <iostream>
#include <string>
#include <vector>
#include <list>
using namespace std;

int main()
{   
	string s1("hello world");
	//迭代器
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		(*it)--;
		++it;
	}
	it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	//范围for
	//底层替换成迭代器
	for (auto& ch : s1)
	{
		ch++;
	}

	for (auto ch : s1)
	{
		cout << ch << " ";
	}

	cout << endl;
	//迭代器还支持容器
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	vector<int>::iterator vit = v.begin();
	while (vit != v.end())
	{
		cout << *vit << " ";
		++vit;
	}
	cout << endl;
	list<int> lt;
	lt.push_back(10);
	lt.push_back(20);
	lt.push_back(3);
	lt.push_back(4);

	list<int>::iterator lit = lt.begin();

	while (lit != lt.end())
	{
		cout << *lit << " ";
		++lit;
	}
	cout << endl;
	return 0;
}

3. string类的模拟实现

3.1 经典的string类问题

上面已经对 string 类进行了简单的介绍,大家只要能够正常使用即可。在面试中,面试官总喜欢让学生自己 来模拟实现string 类,最主要是实现 string 类的构造、拷贝构造、赋值运算符重载以及析构函数。大家看下以 下string 类的实现是否有问题?
// 为了和标准库区分,此处使用String
class String
{
public:

		/*String()
		:_str(new char[1])
		{*_str = '\0';}
		*/
		//String(const char* str = "\0") 错误示范
		//String(const char* str = nullptr) 错误示范
		String(const char* str = "")
	{
		// 构造String类对象时,如果传递nullptr指针,可以认为程序非
		if (nullptr == str)
		{
			assert(false);
			return;
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};
// 测试
void TestString()
{
	String s1("hello bit!!!");
	String s2(s1);
}

说明:上述 String 类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用 s1 s2 时,编译器会调用默认的拷贝构造。最终导致的问题是, s1 s2 共用同一块内存空间,在释放时同一块 空间被释放多次而引起程序崩溃 ,这种拷贝方式,称为浅拷贝。

3.2 浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来 。如果 对象中管理资源 ,最后就会 导致多个对象共 享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为 还有效,所以当继续对资源进项操作时,就会发生发生了访问违规
就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一不想分享就 你争我夺,玩具损坏。
可以采用深拷贝解决浅拷贝问题,即: 每个对象都有一份独立的资源,不要和其他对象共享 。父母给每个孩 子都买一份玩具,各自玩各自的就不会有问题了

3.3 深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情 况都是按照深拷贝方式提供。

3.3.1 传统版写法的String

class String
{
public:
	String(const char* str = "")
	{
		// 构造String类对象时,如果传递nullptr指针,可以认为程序非
		if (nullptr == str)
		{
			assert(false);
			return;
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	String(const String& s)
		: _str(new char[strlen(s._str) + 1])
	{
		strcpy(_str, s._str);
	}
	String& operator=(const String& s)
	{
		if (this != &s)
		{
			char* pStr = new char[strlen(s._str) + 1];
			strcpy(pStr, s._str);
			delete[] _str;
			_str = pStr;
		}
		return *this;
	}
	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
};

3.3.2 现代版写法的String

class String
{
public:
	String(const char* str = "")
	{
		if (nullptr == str)
		{
			assert(false);
			return;
		}
		_str = new char[strlen(str) + 1];
		strcpy(_str, str);
	}
	String(const String& s)
		: _str(nullptr)
	{
		String strTmp(s._str);
		swap(_str, strTmp._str);
	}
	// 对比下和上面的赋值那个实现比较好?
	String& operator=(String s)
	{
		swap(_str, s._str);
		return *this;
	}
	/*
	String& operator=(const String& s)
	{
	if(this != &s)
	{
	String strTmp(s);
	swap(_str, strTmp._str);
	}
	return *this;
 }
 */
	~String()
	{
		if (_str)
		{
			delete[] _str;
			_str = nullptr;
		}
	}
private:
	char* _str;
}

3.3 写时拷贝(了解)

写时拷贝就是一种拖延症,是在浅拷贝的基础之上增加了引用计数的方式来实现的。
引用计数:用来记录资源使用者的个数。在构造时,将资源的计数给成 1 ,每增加一个对象使用该资源,就给 计数增加1 ,当某个对象被销毁时,先给该计数减 1 ,然后再检查是否需要释放资源,如果计数为 1 ,说明该 对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

string类的实现

#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
namespace xyl

{
   
    class string
    {

    public:

        typedef char* iterator;
        typedef const char* const_iterator;

    public:


        iterator begin()
        {
            return _str;
        }

        iterator end()
        {
            return (_str + _size);
        }
        const_iterator begin() const
        {
            return _str;
        }

        const_iterator end() const
        {
            return (_str + _size);
        }

        void reserve(size_t n)
        {
            if (n > _capacity)
            {
                char* tmp = new char[n + 1];
                //strcpy(tmp, _str);
                memcpy(tmp, _str, _size + 1);
                _capacity = n;
                delete[] _str;
                _str = tmp;
            }
        }

        string(const char* str = "")
  
        {
            _size = strlen(str);
            _capacity = _size;
            _str = new char [_capacity+1];
            //strcpy(_str, str);
            memcpy(_str, str, _size + 1);
        }

         string(const string& s)
        {
             _size = s._size;
             _capacity = s._capacity;
             _str = new char[s._capacity + 1];
            // strcpy(_str, s._str);
             memcpy(_str, s._str, s._size + 1);
        }

        string& operator= (const string& s)
         {
            if (*this != s)
            {
                _size = s._size;
                _capacity = s._capacity;
                _str = new char[s._capacity + 1];
                //strcpy(_str, s._str);
                memcpy(_str, s._str, s._size + 1);
            }
           
            return *this;
         }

        ~string()
        {
            _size = 0;
            _capacity = _size;
            delete[]_str;
        }
      


        void push_back(char c)
        {
            if (_size  == _capacity)
            {
                reserve(_capacity == 0 ? _capacity = 4 : _capacity * 2);
            }
            

                _str[_size++] = c;
                _str[_size] = '\0';
                _capacity = _size;
            
        }

        string& operator+=(char c)
        {
            push_back(c);

            return *this;
        }

        void append(const char* str)
        {
            size_t len = strlen(str);
            if (_size + len > _capacity)
            {
                reserve(_size + len);
            }
            //strcpy(_size + _str, str);
            memcpy(_size + _str, str, len+1);
            _size += len;
        }

        string& operator+=(const char* str)
        {
            append(str);
            return *this;
        }

        void clear()
        {
            _str[0] = '\0';
            _size = 0;

        }

        void swap(string& s)
        {
    
            string tmp(s);
            std::swap(_str, s._str);
            std::swap(_size, s._size);
            std::swap(_capacity, s._capacity);
         
        }

        const char* c_str()const
        {
            return _str;
        }



        

        size_t size()const
        {
            return _size;
        }

        size_t capacity()const
        {
            return _capacity;
        }

        bool empty()const
        {
            return _size == 0;
        }

        void resize(size_t n, char c = '\0')
        {
    
            if (n < _size)
            {
                _size = n;
                _str[_size] = '\0';
            }
            else
            {
                reserve(n);

                for (size_t i = _size;i < n;i++)
                {
                    _str[i] = c;
                }
                _str[n] = '\0';
                _size = n;
            }

        }

        





         access


        char& operator[](size_t index)
        {
            return _str[index];
        }

        const char& operator[](size_t index)const
        {
            return _str[index];
        }



        //relational operators

       bool operator<(const string& s)
        {
           size_t i1= 0;
           size_t i2 = 0;
           while (i1 < _size && i2 < s._size)
           {
               if (_str[i1] < s._str[i2])
               {
                   return true;
               }
               else if(_str[i1]>s._str[i2])
               { 

                   return false;
               }
               else
               {
                   i1++;
                   i2++;
               }
           }
       // "hello" "hello"   false
       // "helloxx" "hello" false
       // "hello" "helloxx" true

           if (i1 == _size && i2 != s._size)
           {
               return true;
           }
           else
           {
               return false;
           }
        
        }
        bool operator==(const string& s)
        {
            return _size == s._size
                && memcmp(_str, s._str, _size) == 0;
        }
        bool operator<=(const string& s)
        {
            return *this < s || *this == s;
        }

        bool operator>(const string& s)
        {
            return !(*this <= s);
        }

        bool operator>=(const string& s)
        {
            return *this > s || *this == s;
        }

      

        bool operator!=(const string& s)
        {
            return !(*this == s);
        }



        // 返回c在string中第一次出现的位置

        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;
            }
  

        // 返回子串s在string中第一次出现的位置

        size_t find(const char* s, size_t pos = 0) const
        {
            assert(pos < _size);
            const char* ptr = strstr(_str + pos, s);
            if (ptr)
            {
                return ptr - _str;
            }
            else
            {
                return npos;
            }
        }

        // 在pos位置上插入字符c/字符串str,并返回该字符的位置

        void insert(size_t pos, size_t n, char ch)
        {
            assert(pos <= _size);

            if (_size + n > _capacity)
            {
                // 至少扩容到_size + len
                reserve(_size + n);
            }

            // 挪动数据
            /*int end = _size;
            while (end >= (int)pos)
            {
                _str[end + n] = _str[end];
                --end;
            }*/


            // 添加注释最好
            size_t end = _size;
            while (end >= pos && end != npos)
            {
                _str[end + n] = _str[end];
                --end;
            }

            for (size_t i = 0; i < n; i++)
            {
                _str[pos + i] = ch;
            }

            _size += n;
        }
        string& insert(size_t pos, char c)
        {
            assert(pos<=_size);
            if (_size + 1 > _capacity)
            {
                reserve(_capacity + 1);
            }
            size_t end = _size;
            while (end >= pos && end != npos)
            {
                _str[end + 1] = _str[end];
                --end;
            }

            _str[pos] = c;

            _size += 1;
            return *this;
        }

        string& insert(size_t pos, const char* str)
        {
            size_t n = strlen(str);
            assert(pos <= _size);
            if (_size + n > _capacity)
            {
                reserve(_size + n);
            }
            size_t end = _size;
            while (end>=pos&&end!=npos)
            {
                _str[end + n] = _str[end];
                end--;
            }
            for (int i = 0;i < n;i++)
            {
                _str[pos + i] = str[i];
            }
            return *this;
        }



        // 删除pos位置上的元素,并返回该元素的下一个位置

        string& erase(size_t pos, size_t len=npos)
        {
            assert(pos <= _size);
            if (len == npos || len + pos >= _size)
            {
                _str[pos] = '\0';
            }
            else
            {
                size_t end = pos + len;
                while (end <= _size)
                {
                    _str[pos++] = _str[end++];
                }
                _size -= len;
            }
        }
       

    private:

        char* _str;

        size_t _capacity;

        size_t _size;

       public:
        const static size_t npos;


    };
    const size_t string::npos = -1;
};


ostream& operator<<(ostream& out, const xyl::string& s)
{
    for (auto ch : s)
    {
        out << ch;
    }
    
    return out;
}
istream& operator>>(istream& in,  xyl::string& s)
{
    s.clear();
    char ch = in.get();
    while (ch == ' ' || ch == '\n')
    {
        ch = in.get();
    }
    char buff[128] = {'\0'};
    int i = 0;
    while (buff[i] != ' ' && ch != '\n')
    {
        buff[i++] = ch;
        if (i == 127)
        {
            buff[i] = '\0';
            s += buff;
            int i = 0;
        }
        ch = in.get();
    }
    if (i != 0)
    {
        buff[i] = '\0';
        s += buff;
    }
    return in;
}

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

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

相关文章

MATLAB | R2025a 更新了哪些有趣的东西?

千呼万唤始出来&#xff0c;MATLAB R2025A 来见面&#xff0c;这次更新比往常晚了两个月&#xff0c;让我们看看更了哪些好玩的新东西叭&#xff1a;首先下载更新启动一气呵成&#xff0c;映入眼帘的是&#xff1a; 1 基本界面 基本界面变得和 MATLAB 网页版一模一样了&#…

前缀和——和为K的子数组

作者感觉本题稍稍有点难度&#xff0c;看了题解也思考了有一会TWT 显然&#xff0c;暴力我们是不可取的&#xff0c;但这里我们可以采取一种新的遍历数组形式&#xff0c;从后向前&#xff0c;也就是以i位置为结尾的所有子数组&#xff0c;这个子数组只统计i位置之前的。 然后…

深入理解 ZAB:ZooKeeper 原子广播协议的工作原理

目录 ZAB 协议&#xff1a;ZooKeeper 如何做到高可用和强一致&#xff1f;&#x1f512;ZAB 协议的核心目标 &#x1f3af;ZAB 协议的关键概念 &#x1f4a1;ZAB 协议的运行阶段 &#x1f3ac;阶段一&#xff1a;Leader 选举 (Leader Election) &#x1f5f3;️阶段二&#xff…

GraphPad Prism项目的管理

《2025新书现货 GraphPad Prism图表可视化与统计数据分析&#xff08;视频教学版&#xff09;雍杨 康巧昆 清华大学出版社教材书籍 9787302686460 GraphPadPrism图表可视化 无规格》【摘要 书评 试读】- 京东图书 GraphPad Prism统计数据分析_夏天又到了的博客-CSDN博客 项目…

驱动-Linux定时-timer_list

了解内核定时相关基础知识 文章目录 简要介绍timer_list 特点API 函数实验测试程序 - timer_mod.c编译文件-Makefile实验验证 注意事项总结 简要介绍 硬件为内核提供了一个系统定时器来计算流逝的时间&#xff08;即基于未来时间点的计时方式&#xff0c; 以当前时刻为计时开始…

STM32F103_LL库+寄存器学习笔记22 - 基础定时器TIM实现1ms周期回调

导言 如上所示&#xff0c;STM32F103有两个基本定时器TIM6与TIM7&#xff0c;所谓「基本定时器」&#xff0c;即功能最简单的定时器。 项目地址&#xff1a; github: LL库: https://github.com/q164129345/MCU_Develop/tree/main/stm32f103_ll_library22_Basic_Timer寄存器方…

5个yyds的.Net商城开源项目

今天一起来盘点下5个商城开源项目。 1、支持多语言、多商店的商城&#xff0c;.Net7 EF7领域驱动设计架构&#xff08;Smartstore&#xff09; 项目简介 Smartstore 支持桌面和移动平台、多语言、多商店、多货币的商城&#xff0c;并支持SEO优化&#xff0c;支持无限数量的…

[项目深挖]仿muduo库的并发服务器的解析与优化方案

标题&#xff1a;[项目深挖]仿muduo库的并发服务器的优化方案 水墨不写bug 文章目录 一、buffer 模块&#xff08;1&#xff09;线性缓冲区直接扩容---->环形缓冲区定时扩容&#xff08;只会扩容一次&#xff09;&#xff08;2&#xff09;使用双缓冲&#xff08;Double Buf…

国标GB28181视频平台EasyGBS校园监控方案:多场景应用筑牢安全防线,提升管理效能

一、方案背景​ 随着校园规模不断扩大&#xff0c;传统监控系统因设备协议不兼容、数据分散管理&#xff0c;导致各系统之间相互独立、数据无法互通共享。在校园安全防范、教学管理以及应急响应过程中&#xff0c;这种割裂状态严重影响工作效率。国标GB28181软件EasyGBS视频云…

SHIMADZU岛津 R300RC300 Operation Manual

SHIMADZU岛津 R300RC300 Operation Manual

使用 Docker 部署 React + Nginx 应用教程

目录 1. 创建react项目结构2. 创建 .dockerignore3. 创建 Dockerfile4. 创建 nginx.conf5. 构建和运行6. 常用命令 1. 创建react项目结构 2. 创建 .dockerignore # 依赖目录 node_modules npm-debug.log# 构建输出 dist build# 开发环境文件 .git .gitignore .env .env.local …

API Gateway REST API 集成 S3 服务自定义 404 页面

需求分析 使用 API Gateway REST API 可以直接使用 S3 作为后端集成对外提供可以访问的 API. 而当访问的 URL 中存在无效的桶, 或者不存在的对象时, API Gateway 默认回向客户端返回 200 状态码. 而实际上这并不是正确的响应, 本文将介绍如何自定义返回 404 错误页面. 基本功…

关于systemverilog中在task中使用force语句的注意事项

先看下面的代码 module top(data);logic clk; inout data; logic temp; logic sampale_data; logic [7:0] data_rec;task send_data(input [7:0] da);begin(posedge clk);#1;force datada[7];$display(data);(posedge clk);#1;force datada[6]; $display(data); (posed…

Python Day26 学习

继续NumPy的学习 数组的索引 一维数组的索引 创建及输出 arr1d np.arange(10) # 数组: [0 1 2 3 4 5 6 7 8 9] arr1d array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) 取出数组的第一个元素&#xff0c;最后一个元素 代码实现 arr1d[0] arr1d[-1] 取出数组中索引为3&#x…

解决:npm install报错,reason: certificate has expired

目录 1. 问题分析2. 问题解决2.1 查看配置的镜像2.2 修改镜像源 种一棵树最好的时间是10年前&#xff0c;其次就是现在&#xff0c;加油&#xff01; --by蜡笔小柯南 1. 问题分析 启动前…

中科固源Wisdom平台发现NASA核心飞行控制系统(cFS)通信协议健壮性缺陷!

中科固源Wisdom平台发现NASA核心飞行控制系统(cFS)通信协议健壮性缺陷&#xff0c;接下来内容将进行核心要点概述&#xff0c;分别从地位、重要性和应用场景三方面进行简明阐述&#xff1a; cFS&#xff08;core Flight System&#xff09;是NASA戈达德太空飞行中心&#xff08…

嵌入式学习笔记DAY23(树,哈希表)

一、树 1.树的概念 之前我们一直在谈的是一对一的线性结构&#xff0c;现实中&#xff0c;还存在很多一对多的情况需要处理&#xff0c;一对多的线性结构——树。 树的结点包括一个数据元素及若干指向其子树的分支&#xff0c;结点拥有的子树数称为结点的度。度为0的结点称为叶…

仓颉开发语言入门教程:搭建开发环境

仓颉开发语言作为华为为鸿蒙系统自研的开发语言&#xff0c;虽然才发布不久&#xff0c;但是它承担着极其重要的历史使命。作为鸿蒙开发者&#xff0c;掌握仓颉开发语言将成为不可或缺的技能&#xff0c;今天我们从零开始&#xff0c;为大家分享仓颉语言的开发教程&#xff0c;…

Axure中继器高保真交互原型的核心元件

Axure作为一款强大的原型设计工具&#xff0c;中继器无疑是打造高保真交互原型的核心利器。今天&#xff0c;就让我们深入探讨一下Axure中继器的核心地位、操作难点&#xff0c;以及如何借助优秀案例来提升我们的中继器使用技能。 一、核心地位 中继器在Axure中的地位举足轻重…

【SpringBoot】✈️整合飞书群机器人发送消息

&#x1f4a5;&#x1f4a5;✈️✈️欢迎阅读本文章❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;本篇文章阅读大约耗时3分钟。 ⛳️motto&#xff1a;不积跬步、无以千里 &#x1f4cb;&#x1f4cb;&#x1f4cb;本文目录如下&#xff1a;&#x1f381;&#x1f381;&am…