C++【string的模拟实现】

在前文我们讲解了string类接口使用(C++【string类的使用】(上),C++【string类的使用】(下)),本片文章就来模拟实现string类。
注:本文实现的是string的部分重点内容,目的是为了更好的了解string,而不是实现出一个功能一模一样的string

文章目录

  • 1. 三个文件的链接
    • 1.1 string.h
    • 1.2 string.cpp
    • 1.3 test.cpp
  • 2. 正式实现
    • 2.1 string的内部成员变量
    • 2.2 构造函数
    • 2.3 析构函数
    • 2.4 拷贝构造
      • 1.深浅拷贝问题
      • 2. 传统写法
      • 3. 现代写法
    • 2.5 operator=
      • 传统写法
      • 现代写法
    • 2.6 常量成员对象npos
    • 2.7 提供C接口 c_str
    • 2.8 容量接口
    • 2.9 operator[]
    • 2.10 迭代器
    • 2.11 修改容量 reserve
    • 2.12 字符串的增加与删除
      • push_back
      • append
      • operator+=
      • insert
      • erase
    • 2.13 查找 find
    • 2.14 字串 substr
    • 2.15 string之间的比较
    • 2.16 重载输出流 operator<<
    • 2.17 重载输入流 operator>>
  • 3. 完整代码
    • string.h
    • string.cpp
    • test.cpp
  • 4. 结语

1. 三个文件的链接

1.1 string.h

负责string类函数的声明(一些短小且频繁调用的函数会在类里实现)
在这里插入图片描述

1.2 string.cpp

负责string类函数的实现
在这里插入图片描述

1.3 test.cpp

负责测试函数是否成功
在这里插入图片描述

2. 正式实现

2.1 string的内部成员变量

class string
{private:char* _str;size_t _size;size_t _capacity;const static size_t npos;
};
  • _str:是string类的关键,用来存放被开辟空间的地址
  • _size:非空字符的个数(string类的长度)
  • _capacity:string类当前的容量
  • _npos:静态成员函数,用来当缺省值

2.2 构造函数

我们就实现两个构造:无参构造与字符串构造。

注意:无参构造不能给_str一个nullptr,因为string本质上还是读取字符串,而字符串只有遇到\0才会停止读取,如果你给了一个空指针,那么在查找_str的时候就查找不到\0,就会一直找下去,这时的程序就已经无法使用了。

不管是无参构造,还是带参构造,都要有\0,但是我们的_capacity并不包含\0,所以我们在开空间的时候是要多开一个空间的。

		string():_str(new char[1]{'\0'}),_size(0),_capacity(0){}string(const char* str){_size = strlen(str);_capacity = _size; //capacity不包含 '/0'_str = new char[_size + 1];strcpy(_str, str);}

我们也可以将这两个函数合二为一,给const char* str一个缺省值就好了。

string(const char* str = ""){_size = strlen(str);_capacity = _size; //capacity不包含 '/0'_str = new char[_size + 1];strcpy(_str, str);}

注意:缺省值不需要给成const char* str ="\0",因为一个空字符串里面就默认包含了\0,如果我们在多给一个,里面就会存储两个\0,那就有点画蛇添足了。

2.3 析构函数

我们看到_str是申请了空间,那么编译器默认生成的析构就无法满足我们的需求了,我们就需要自己手搓一个析构函数。

其实析构就很简单,就是将申请的空间释放掉,将_str设置为空指针,你的_size和_capacity要不要置0都无所谓。

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

既然有了析构,那么拷贝构造与operator=也需要我们自己手搓(他们是配套出现的)

2.4 拷贝构造

1.深浅拷贝问题

我们先看看没有手动实现拷贝构造,使用编译器默认的拷贝构造会发生什么事情。
在这里插入图片描述

在这里插入图片描述
可以看到发生了报错,那这么是为什么呢,我们结合图来一探究竟。
在这里插入图片描述
我们可以看到,这两个对象中_str的地址是一样的。
在这里插入图片描述
这是因为编译器默认生成的拷贝构造是每个字节每个字节的拷贝,这也就导致了s1,s2的_str都指向了同一块空间,当test函数结束,要需要销毁s1和s2,就会先销毁掉s2的_str,当s2的_str被销毁后,这个_str所指向的空间就已经被释放了,这时在销毁s1,就会使一个空间被释放两次,就会发生崩溃。
这种拷贝方式被称为浅拷贝。

浅拷贝就像一个家里有两个孩子,但只有一个玩具,当他们都想玩玩具,且只想自己玩的时候,就会你争我抢,导致玩具损坏。
在这里插入图片描述
可以用深拷贝来解决这个问题,,即:每个对象都有一份独立的资源,不要和其他对象共享。父母给每个孩子都买一份玩具,各自玩各自的就不会有问题了

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

2. 传统写法

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

和构造函数思路一样,都是开辟一块新空间,然后拷贝数据

3. 现代写法

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

这种写法非常巧妙,我们先用 s 的 _str 初始化一个临时变量tmp,然后将 tmp 的 _str 与 this 的 _str 交换,这样就完成了拷贝构造,同时也是深拷贝。
大致过程如下图:
在这里插入图片描述

2.5 operator=

和拷贝构造类似,也是有传统写法与现代写法。

传统写法

	string string::operator=(const string& s){if (this != &s)//判断是不是自己{delete[] _str;_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}return *this;}

现代写法

	string string::operator=(string s){swap(_str,s._str);swap(_size, s._size);swap(_capacity, s._capacity);return *this;}

注意:这里函数参数string s,是传值传参,发生了拷贝构造,这是为了不影响 = 右边的对象。

传统写法中,我们还需要手动的销毁原空间,创建新空间,拷贝数据;但是在现代写法,这些都不需要了,新空间新数据已经被 s 开好了,我们只需要交换就好了。

接下来就开始实现常量成员对象npos与一些常用接口

2.6 常量成员对象npos

//常量成员对象
const size_t string::npos = -1;

npos的作用就是用来当缺省值,用来表示“到字符串结束”。

2.7 提供C接口 c_str

//只读字符串const char* c_str() const{return _str;}

提供只读字符串,返回 _str 即可, 这个_str 被const修饰,无法改变。

2.8 容量接口

查询容量,实现size()和capacity()即可。

		size_t size() const{return _size;}size_t capacity() const{return _capacity;}

2.9 operator[]

我们在使用那部分也讲过,返回对应位置的引用即可。

		char& operator[](size_t pos){assert(pos <= _size);return _str[pos];}//支持const版本char& operator[](size_t pos) const{assert(pos <= _size);return _str[pos];}

2.10 迭代器

之前已经提到过,现阶段我们可以将迭代器理解成一种指针,通过解引用来访问数据元素。我们定义string类时,使用指针来模拟迭代器。迭代器定义如下:

		typedef char* iterator;typedef const char* const_iterator;

我们只实现普通迭代器与const迭代器,反向迭代器后面再讲解。

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

要想支持范围for,我们的名字必须和库里的名字一样,因为范围for是傻瓜式替换。

我们现在试验下我们自己实现的访问

	void test_string1(){string s1;string s2("zhuo zhuo zhuo");//cout << s1.c_str() << endl;//cout << s2.c_str() << endl;cout << "下标访问遍历:";for (int i = 0; i < s2.size(); i++){cout << s2[i] << ' ';}cout << endl;string::iterator sit = s2.begin();cout << "  迭代器遍历:";while (sit != s2.end()){cout << *sit << ' ';sit++;}cout << endl;cout << " 范围for遍历:";for (auto e : s2){cout << e << ' ';}}

在这里插入图片描述

2.11 修改容量 reserve

这个虽然不属于字符串的增加与删除,但是每个增加元素的函数都会用到他,所以我就把它提取到这个模块了

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

设计思路和vs是一样的,比原来的_capacity大我就扩容,比_capacity小都不扩容。

注意:到了C++,我们就要舍弃realloc()这个函数来扩容,全部都自己写,因为realloc无法自动调用构造函数,而new可以。

2.12 字符串的增加与删除

push_back

	void string::push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size++] = ch;_str[_size] = '\0';}

append

append有很多接口,我们实现一个尾插字符串就可以了

	void string::append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){size_t NewCapacity = 2 * _capacity;reserve(_size + len > NewCapacity ? _size + len : NewCapacity);}strcpy(_str + _size, str);_size += len;}

operator+=

这个直接复用push_back()append()就好了。

string += 字符

	string& string::operator+= (char ch){push_back(ch);return *this;}

string += 字符串

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

operator+=,push_back,append这三个都是在string尾部插入元素,要想直线在任意位置插入元素,那就需要insert这个函数。

insert

我们同样只实现两个版本,插入字符与插入字符串,这两个版本都是在指定位置之前插入元素

	    string类内部的声明void insert(size_t pos, char ch);//插入字符void insert(size_t pos, const char* str);//插入字符串
  1. insert插入字符

既然要在之前插入字符,那挪动数据的时候,也需要挪动原pos位置的元素。

	void string::insert(size_t pos, char ch){assert(pos <= _size);if (pos == _size){push_back(ch);}else{if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}//挪动数据size_t end = _size;while (end != pos-1){_str[end] = _str[end-1];--end;}_str[pos] = ch;_str[++_size] = '\0';

流程如下图
在这里插入图片描述

  1. insert插入字符串
	void string::insert(size_t pos, const char* str){assert(pos <= _size);if (pos == _size){append(str);}else{size_t len = strlen(str);if (_size + len > _capacity){size_t NewCapacity = 2 * _capacity;reserve(_size + len > NewCapacity ? _size + len : NewCapacity);}//挪动数据size_t end = _size+len;while (end != pos + len - 1){_str[end] = _str[end - len];--end;}//覆盖for (int i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;}}

注意:我们在挪动数据的时候,我们的结束条件一定不能写 e n d ≥ p o s end \ge pos endpos,因为在头插环境会无线循环。
在这里插入图片描述

在这里插入图片描述
所以就会无限循环

把 end 换成 int 类型行不行呢?
答案是不行的,这是C语言留下来的坑,当级别低的与级别高作为操作符的操作数时,级别低的变量会发生整形提升。
也就是说当end与pos位置比较的时候,end会自动提升为 size_t (unsigned_int) 类型

如果你把 pos 也改成 int 类型就能成功运行,但是我们要结合使用场景,如果我们要插入的字符位置是比int正整数最大值大一点的数字,那么我们就会插入到负输上,但字符串是没有负数这个概念的,所以将 end 和 pos 都换成 int 是不行的。

正确方法就是上文代码的方法while (end != pos-1)while (end != pos + len - 1),!= 这个操作符不需要比较位置之间的大小,只需要看是否相等,如果相等,就停下来跳出循环。

而 -1 是为了插入字符串,因为我们要在指定位置前插入字符串,如果不 -1,那么就会少挪动一位,导致原本的数据丢失一位。

erase

void erase(size_t pos, size_t len = npos);
	void string::erase(size_t pos, size_t len){assert(pos < _size);if (len > _size - pos){_str[pos] = '\0';_size = pos;}else{for (int i = pos + len; i <= _size; i++){_str[i - len] = _str[i];}_size -= len;}}

当要删除的字符个数大于_size后字符个数,直接在将pos位置置为\0,然后将_szie更新。

如果小于,那就把后面的值往前覆盖,长度-=要删减的字符个数。

2.13 查找 find

这个就很简单了,直接遍历string就好了,如果是查找字符串,那就直接调用C语言string库的strstr

	//查找字符size_t string::find(char ch, size_t pos){assert(pos < _size);for (int i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}//查找字符串size_t string::find(const char* str, size_t pos){assert(pos < _size);const char* ptr = strstr(_str, str);if (ptr == nullptr){return npos;}else{return ptr - _str;}} 

2.14 字串 substr

直接在函数里创建一个临时变量,让他来拷贝子串,然后出函数的时候再进行构造函数来进行深拷贝

	string string::substr(size_t pos, size_t len) const{assert(pos < _size);if (len > _size - pos){len = _size - pos;}string sub;sub.reserve(len);for (int i = 0; i < len; i++){sub += _str[i + pos];}return sub;}

2.15 string之间的比较

我们直接调用strcmp就可以了,重载==加任意一个判断运算符(除!=),其他就可以直接复用。

	bool operator>(const string& s1, const string& s2){return strcmp(s1.c_str(), s2.c_str()) > 0;}bool operator>=(const string& s1, const string& s2){return (s1 > s2) || (s1 == s2);}bool operator<(const string& s1, const string& s2){return !(s1 >= s2);}bool operator<=(const string& s1, const string& s2){return !(s1 > s2);}bool operator==(const string& s1, const string& s2){return strcmp(s1.c_str(), s2.c_str()) == 0;}bool operator!=(const string& s1, const string& s2){return !(s1 == s2);}

2.16 重载输出流 operator<<

	ostream& operator<<(ostream& out, string& s){ //out << s.c_str();for (auto e : s){out << e;}return out;}

2.17 重载输入流 operator>>

	istream& operator>>(istream& in, string& s){char ch;ch = in.get();while (ch != ' ' && ch != '\n'){s += ch;ch = in.get();}return in;}

in.get()是输入流的一个函数,功能是在缓冲区中拿到空格和换行(一般的插入是不会读取的)。

3. 完整代码

string.h

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once#include<iostream>
#include<assert.h>
using namespace std;
#include<algorithm>namespace zhuo
{class string{public:typedef char* iterator;typedef const char* const_iterator;//string()//	:_str(new char[1]{'\0'})//	,_size(0)//	,_capacity(0)//{}//string(const char* str)//{//	_size = strlen(str);//	_capacity = _size; //capacity不包含 '/0'//	_str = new char[_size + 1];//	strcpy(_str, str);//}string(const char* str = ""){_size = strlen(str);_capacity = _size; //capacity不包含 '/0'_str = new char[_size + 1];strcpy(_str, str);}//string(const string& s)//{//	_str = new char[s._capacity + 1];//	strcpy(_str, s._str);//	_size = s._size;//	_capacity = s._capacity;//}string(const string& s):_str(nullptr){string tmp(s._str);swap(_str, tmp._str);swap(_size, tmp._size);swap(_capacity, tmp._capacity);}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator cbgin() const{return _str;}const_iterator cend() const{return _str + _size;}const char* c_str() const{return _str;}char& operator[](size_t pos){assert(pos <= _size);return _str[pos];}char& operator[](size_t pos) const{assert(pos <= _size);return _str[pos];}size_t size() const{return _size;}size_t capacity() const{return _capacity;}void reserve(size_t);//增删void push_back(char ch);void append(const char* str);string& operator+= (char ch);string& operator+= (const char* str);void insert(size_t pos, char ch);void insert(size_t pos, const char* str);void erase(size_t pos, size_t len = npos);//查找size_t find(char ch, size_t pos = 0);size_t find(const char* str, size_t pos = 0);string substr(size_t pos = 0, size_t len = npos) const;//string operator=(const string& s);string operator=(string s);private:char* _str;size_t _size;size_t _capacity;const static size_t npos;};bool operator>(const string& s1, const string& s2);bool operator>=(const string& s1, const string& s2);bool operator<(const string& s1, const string& s2);bool operator<=(const string& s1, const string& s2);bool operator==(const string& s1, const string& s2);bool operator!=(const string& s1, const string& s2);ostream& operator<<(ostream& out, string& s);istream& operator>>(istream& in, string& s);
}

string.cpp

#include"string.h"namespace zhuo
{const size_t string::npos = -1;void string::reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}else;}void string::push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size++] = ch;_str[_size] = '\0';}void string::append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){size_t NewCapacity = 2 * _capacity;reserve(_size + len > NewCapacity ? _size + len : NewCapacity);}strcpy(_str + _size, str);_size += len;}string& string::operator+= (char ch){push_back(ch);return *this;}string& string::operator+= (const char* str){append(str);return *this;}void string::insert(size_t pos, char ch){assert(pos <= _size);if (pos == _size){push_back(ch);}else{if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}//挪动数据size_t end = _size;while (end != pos-1){if (end == 0);_str[end] = _str[end-1];--end;}_str[pos] = ch;_str[++_size] = '\0';}}void string::insert(size_t pos, const char* str){assert(pos <= _size);if (pos == _size){append(str);}else{size_t len = strlen(str);if (_size + len > _capacity){size_t NewCapacity = 2 * _capacity;reserve(_size + len > NewCapacity ? _size + len : NewCapacity);}//挪动数据size_t end = _size+len;while (end >= pos + len - 1){_str[end] = _str[end - len];--end;}//覆盖for (int i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;}}void string::erase(size_t pos, size_t len){assert(pos < _size);if (len > _size - pos){_str[pos] = '\0';_size = pos;}else{for (int i = pos + len; i <= _size; i++){_str[i - len] = _str[i];}_size -= len;}}size_t string::find(char ch, size_t pos){assert(pos < _size);for (int i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}size_t string::find(const char* str, size_t pos){assert(pos < _size);const char* ptr = strstr(_str, str);if (ptr == nullptr){return npos;}else{return ptr - _str;}} string string::substr(size_t pos, size_t len) const{assert(pos < _size);if (len > _size - pos){len = _size - pos;}string sub;sub.reserve(len);for (int i = 0; i < len; i++){sub += _str[i + pos];}return sub;}//string string::operator=(const string& s)//{//	if (this != &s)//	{//		delete[] _str;//		_str = new char[s._capacity + 1];//		strcpy(_str, s._str);//		_size = s._size;//		_capacity = s._capacity;//	}//	return *this;//}string string::operator=(string s){swap(_str,s._str);swap(_size, s._size);swap(_capacity, s._capacity);return *this;}bool operator>(const string& s1, const string& s2){return strcmp(s1.c_str(), s2.c_str()) > 0;}bool operator>=(const string& s1, const string& s2){return (s1 > s2) || (s1 == s2);}bool operator<(const string& s1, const string& s2){return !(s1 >= s2);}bool operator<=(const string& s1, const string& s2){return !(s1 > s2);}bool operator==(const string& s1, const string& s2){return strcmp(s1.c_str(), s2.c_str()) == 0;}bool operator!=(const string& s1, const string& s2){return !(s1 == s2);}ostream& operator<<(ostream& out, string& s){ //out << s.c_str();for (auto e : s){out << e;}return out;}istream& operator>>(istream& in, string& s){char ch;ch = in.get();while (ch != ' ' && ch != '\n'){s += ch;ch = in.get();}return in;}
}

test.cpp

#include"string.h"
#include<string>namespace zhuo
{void test_string1(){string s1;string s2("zhuo zhuo zhuo");//cout << s1.c_str() << endl;//cout << s2.c_str() << endl;cout << "下标访问遍历:";for (int i = 0; i < s2.size(); i++){cout << s2[i] << ' ';}cout << endl;string::iterator sit = s2.begin();cout << "  迭代器遍历:";while (sit != s2.end()){cout << *sit << ' ';sit++;}cout << endl;cout << " 范围for遍历:";for (auto e : s2){cout << e << ' ';}}void test_string2(){string s1;s1.push_back('a');s1 += 'b';cout << s1.c_str() << endl;s1.append(" hello");cout << s1.c_str() << endl;s1 += " world";cout << s1.c_str() << endl;s1.insert(0, 'c');cout << s1.c_str() << endl;s1.insert(0, "cccc");cout << s1.c_str() << endl;s1.erase(1, 100);cout << s1.c_str() << endl;string s2("hello world");s2.erase(1);cout << s1.c_str() << endl;string s3("0123456789");s3.erase(2,5);cout << s3.c_str() << endl;}void test_string3(){string s1("0123456789");size_t pos = s1.find('3');string s2 = s1.substr(pos);cout << s2.c_str() << endl;string s3("test.cpp.zip");pos = s3.find("cpp");string s4 = s3.substr(pos);cout << s4.c_str() << endl;}void test_string4(){string s1("hello world");cout << s1 << endl;cin >> s1;cout << s1 << endl;}void test_string5(){string s1("hello");string s2(s1);int s;}
}
int main()
{zhuo::test_string2();return 0;
}

4. 结语

那么这次的分享就到这里结束了~
string类还是比较简单的~
最后感谢您能阅读完此片文章~
如果您认为这篇文章对你有帮助的话,可以用你们的手点一个免费的赞并收藏起来哟~
如果有任何建议或纠正欢迎在评论区留言~
也可以前往我的主页看更多好文哦(点击此处跳转到主页)。

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

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

相关文章

zabbix安装配置与使用

zabbix Zabbix的工作原理如下: 监控部分: Zabbix Agent安装在各个需要监控的主机上,它以主配置的时间间隔(默认60s)收集主机各项指标数据,如CPU占用率、内存使用情况等。 通讯部分: Agent会把收集的数据通过安全通道(默认10051端口)发送到Zabbix Server。Server会存储这些数…

7.3、实验三:RIPv2的基本配置

源文件&#xff1a; 7.3、实验三&#xff1a;RIPv2的基本配置: https://url02.ctfile.com/d/61945102-63684790-45f44b?p2707 (访问密码: 2707) 一、目的 能够使用RIPv2路由协议 二、实验要求 1.要求 使用RIPv2协议&#xff0c;使得PC0 和 Service0能够通信&#xff0c;…

石岩田心村的地面停车点(月卡350)

​我之前一直以为城中村里的地面停车场会比上屋地铁口的联天停车场便宜一些。没想到这个田心村月卡也是350元哈。比对面的园岭村还贵&#xff0c;元岭村月卡我记得才260元。 田心村停车场标识牌 序号 收费项目 收费标准 1 小车临时停放 10元/小时&#xff0c;超过1小时加收…

大模型学习笔记------CLIP模型的再思考

大模型学习笔记------CLIP模型的再思考 1、CLIP模型与Prompt(提示)的思考2、CLIP模型与ResNet等分类模型的根本区别3、结束语 上文已经讲 CLIP&#xff08;Contrastive Language-Image Pretraining&#xff09;这个模型&#xff0c;也讲了我的一些思考。但是&#xff0c;随着深…

Spring之依赖注入(DI)和控制反转(IoC)——配置文件、纯注解

依赖注入 依赖注入(Dependency Injection&#xff0c;简称 DI)与控制反转(loC)的含义相同&#xff0c;只不过这两 个称呼是从两个角度描述的同一个概念。对于一个 Spring 初学者来说&#xff0c;这两种称呼很难理解, 下面我们将通过简单的语言来描述这两个概念。 当Java对象&…

外网访问 Immich 照片管理软件

Immich 是一个自托管的照片和视频备份的平台&#xff0c;它允许用户在私有服务器上存储、管理和分享他们的照片&#xff0c;视频等媒体文件。 第一步&#xff0c;本地部署安装 Immich 1&#xff0c;检查 Docker 服务状态&#xff0c;确保 Docker 正常运行。 systemctl statu…

Linux网络命令:它用于实时监控网络接口的状态变化的命令 ip monitor详解

目录 一、概述 二、使用 1、语法 2、对象类型 3、常用选项 4、获取帮助 三、 示例 1. 监视链路层变化 2. 监视所有的网络变化 3. 仅监视路由表的变化 4. 监视特定网络接口的状态变化&#xff1a; 5. 监视网络接口地址的变化 四、实际应用 五、其他事项 一、概述 …

QT仿QQ聊天项目,第三节,实现主界面(好友列表)

目录 一&#xff0c;主界面示例 二&#xff0c;主界面控件组成 三&#xff0c;好友列表实现 1&#xff0c;好友列表的实现原理 2&#xff0c;实现示例代码 一&#xff0c;主界面示例 二&#xff0c;主界面控件组成 三&#xff0c;好友列表实现 1&#xff0c;好友列表的实现…

查找连表的倒数第k个节点

居安思危 何解&#xff1f; 1、假如有1、2、3三个节点&#xff0c;找倒数第二个&#xff0c;实际是整数第几个&#xff1f; 3-21 2 &#xff1a; 及 length - k 1 ,所以先遍历找节点长度&#xff0c;在遍历找所需节点 // 今天这不是力扣的var findNode function(head , k){…

练习LabVIEW第三十九题

学习目标&#xff1a; 刚学了LabVIEW&#xff0c;在网上找了些题&#xff0c;练习一下LabVIEW&#xff0c;有不对不好不足的地方欢迎指正&#xff01; 第三十九题&#xff1a; 程序开始运行后要求用户输入密码&#xff0c;密码正确时字符串显示控件显示 “欢迎进入”&#x…

AI浪潮将席卷一切:本·霍洛维茨的AI战略与发展观点

近年来&#xff0c;人工智能&#xff08;AI&#xff09;的快速发展已经成为全球科技与经济变革的核心驱动力。从自驾汽车到生成式对话系统&#xff0c;AI正以前所未有的速度改变我们的生活、工作方式以及社会结构。然而&#xff0c;作为硅谷著名风险投资公司a16z的联合创始人&a…

windows下位机远程调试

一、前言 发布到下位机的程序在某种操作下出现异常&#xff0c;一种有效的远程调试手段能更快的帮助我们定位问题和解决问题。 VS结合Remote Debugger可以让下位机程序像运行在上位机上一样打断点调试&#xff0c;非常方便。 二、调试环境部署 1、设置下位机程序运行所在目录共…

C++ 多态原理

用一个题目引入&#xff1a; 现有代码&#xff1a; class Base { public:virtual void func(){cout << "Base:func()" << endl;} protected:int _a1;char _bx; }; void test1() {Base obj;cout << sizeof(obj) << endl; } 32位平台上输出的…

Javaweb梳理8——数据库设计

Javaweb梳理8——数据库设计 8 数据库设计8.1 数据库设计简介8.2 表关系(一对多)8.3 表关系&#xff08;多对多&#xff09;8.4 表关系&#xff08;一对一&#xff09; 8 数据库设计 8.1 数据库设计简介 软件的研发步骤 数据库设计概念 数据库设计就是根据业务系统的具体需…

TypeError: Cannot read properties of undefined (reading ‘__asyncLoader‘)

项目场景&#xff1a; vue3element-plus 项目场景&#xff1a;vue3element-plustsvite的技术栈开发的后台&#xff0c;一个后台列表页面&#xff0c;使用了ElTable组件 问题描述 页面提示报一个好像是异步的问题 runtime-core.esm-bundler.js:2261 Uncaught (in promise) Ty…

SAP ABAP开发学习——BADI增强操作步骤示例1

SAP ABAP开发学习——第三代增强&#xff08;BADI)-CSDN博客 SAP ABAP开发学习——BADI增强操作步骤示例2-CSDN博客 创建物料MM01的增强 首先进入SE24 打断点&#xff0c;运行事务MM01,启动debug,获得增强的名字 F8依次获得下一个增强的名字 继续获得增强 进入选择视图以及销…

odrive代码阅读笔记

电机参数 电流环带宽 atan2 // based on https://math.stackexchange.com/a/1105038/81278 float fast_atan2(float y, float x) {// a : min (|x|, |y|) / max (|x|, |y|)float abs_y fabsf(y);float abs_x fabsf(x);// inject FLT_MIN in denominator to avoid division …

C++多线程常见的数据竞争模式及示例分析

一、简单竞争 最简单的数据竞争是最常见的一种&#xff1a;两个线程在没有任何同步的情况下访问一个内置类型的变量。很多时候&#xff0c;这种竞争是良性的&#xff08;代码统计一些允许不精确的统计信息&#xff09;。 int var;void Thread1() { // 在一个线程中运行。var;…

Jest进阶知识:测试快照 - 确保组件渲染输出正确

在 React 应用开发中&#xff0c;确保组件的渲染输出正确是一项重要的测试任务。快照测试是一种有效的方法&#xff0c;可以帮助开发者捕捉并验证组件的渲染输出&#xff0c;确保其在不同的情况下保持一致。 什么是快照测试&#xff1f; 快照测试的基本思想是&#xff1a; 首…

【AI落地应用实战】HivisionIDPhotos AI证件照制作实践指南

最近在网上发现了一款轻量级的AI证件照制作的项目&#xff0c;名为HivisionIDPhotos。它利用AI模型实现对多种拍照场景的识别、抠图与证件照生成&#xff0c;支持轻量级抠图、多种标准证件照和排版照生成、纯离线或端云推理、美颜等功能。此外&#xff0c;项目还提供了Gradio D…