【十】【C++】string类的模拟实现

浅拷贝

浅拷贝(Shallow Copy)是对象复制的一种方式,其中复制对象的过程仅仅复制对象的值,而不复制引用所指向的实际对象或数据。这意味着原始对象和拷贝对象会共享相同的引用或指针指向的数据。

浅拷贝的特点:

共享内存:拷贝对象和原始对象共享相同的内存地址指向的数据。

快速复制:由于不需要复制引用指向的实际数据,浅拷贝的过程通常比深拷贝更快。

潜在风险:如果原始对象或拷贝对象修改了共享的数据,这种变化会影响到另一个对象。这可能导致数据不一致、意外的副作用或内存泄漏等问题。

 
class SimpleClass {
public:int* data;SimpleClass(int value) {data = new int(value);}// 浅拷贝构造函数SimpleClass(const SimpleClass& other) {data = other.data; // 只复制指针,不复制指针指向的数据}~SimpleClass() {delete data;}
};

在这个例子中,拷贝构造函数执行浅拷贝,仅复制data指针而不复制指针所指向的int值。这意味着拷贝后的对象和原始对象共享同一个int值。如果一个对象被销毁(调用析构函数),它也会释放共享的内存,这可能导致另一个对象访问已释放的内存,从而引发未定义行为。

浅拷贝可以理解为简单的等号赋值,如果对指针或者引用进行浅拷贝,此时指针和引用会与原指针和引用共用同一地址空间,即存储的地址空间是相同的,也就是只是简单的把值进行复制,而指向的空间,空间上的值不会复制。。

深拷贝

深拷贝(Deep Copy)是对象复制的一种方式,它不仅复制对象本身的值,还包括对象引用的所有内容到新的内存地址中。这意味着原始对象和拷贝对象在物理上完全独立,它们不会共享任何内存地址指向的数据。

深拷贝的特点:

独立性:拷贝对象和原始对象不会共享任何数据。对拷贝对象的修改不会影响原始对象,反之亦然。

资源复制:深拷贝会递归地复制所有对象,包括对象中的所有引用指向的数据。

更高的开销:由于需要复制所有的数据到新的内存地址,深拷贝通常比浅拷贝有更高的时间和空间开销。

 
class DeepCopyClass {
public:int* data;DeepCopyClass(int value) {data = new int(value);}// 深拷贝构造函数DeepCopyClass(const DeepCopyClass& other) {data = new int(*other.data); // 复制指针所指向的数据到新的内存地址}~DeepCopyClass() {delete data; // 释放分配的内存}
};

在这个例子中,拷贝构造函数通过new操作符分配新的内存,并复制原始对象data指针所指向的值到这块新的内存。这确保了拷贝对象和原始对象在物理上是完全独立的。

深拷贝不仅会复制指针和引用的值,还会把对象的所有内容放到一个新的内存地址上。

初始化列表的初始化器---浅拷贝

 
/*初始化列表初始化器---浅拷贝浅拷贝---简单的“=”赋值*/
#include <iostream>
using namespace std;
class A {private:int _a;int* _p;int& _b;public:A(int a, int* p, int &b): _a(a), _p(p), _b(b){}void Show() {cout << "&_a:" << &_a << endl;cout << "_p:" << _p << endl;cout << "&_b:" << &_b << endl;}};
int main() {int a = 10;int b = 30;int* p = new int(20);A x(a, p, b);x.Show();cout << "&a:"<<&a << endl;cout << "p:"<<p << endl;cout << "&b:"<<&b << endl;}

浅拷贝,用a,p,b对_a,_p,_b进行浅拷贝,可以理解为简单的“=”赋值,即_a=a,_p=p,_b=b

此时_p指针指向p的地址,_b引用是b的别名。

string类(简易版)传统写法简单实现

 
/*string类传统写法简单实现*/
#include <iostream>
using namespace std;
#include <string.h>
class String {public:String(const char* str = "") {if (nullptr == str)str = "";_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* temp = new char[strlen(s._str) + 1];strcpy(temp, s._str);delete[] _str;_str = temp;}return *this;}~String() {if (_str) {delete[] _str;_str = nullptr;}}private:char* _str;};
int main() {return 0;}

构造函数

 
String(const char* str = "") {if (nullptr == str)str = "";_str = new char[strlen(str) + 1];strcpy(_str, str);
}

这是String类的构造函数,它接受一个C风格字符串str作为参数,默认为空字符串""

""表示一个字符大小的空间上,只存储'\0'

如果传入的strnullptr,为了防止访问空指针,将str设置为空字符串。

使用new为成员变量_str分配足够的内存来存储传入的字符串(包括结尾的空字符'\0')。

strlen()从头扫描字符串,直到遇到'\0'停止,计数,不包括'\0'

使用strcpy将传入的字符串复制到_str指向的内存中。

strcpy复制字符串,同时会把'\0'复制过去。

拷贝构造函数

 
String(const String& s): _str(new char[strlen(s._str) + 1]) {strcpy(_str, s._str);
}

这是String类的拷贝构造函数,它接受另一个String对象s作为参数。

使用new_str分配足够的内存,以存储s._str指向的字符串。

使用strcpy复制字符串,同时会把'\0'复制过去。

赋值操作符

 
String& operator=(const String& s) {if (this != &s) {char* temp = new char[strlen(s._str) + 1];strcpy(temp, s._str);delete[] _str;_str = temp;}return *this;
}

这是String类的赋值操作符重载,允许将一个String对象赋值给另一个String对象。

首先检查自赋值的情况,如果不是自赋值,则继续。

创建一个新的临时字符数组temp,并复制源字符串s._strtemp

释放_str当前指向的内存,避免内存泄漏。

_str指向新分配并已复制的内存。

返回当前对象的引用,以支持链式赋值。

析构函数

 
~String() {if (_str) {delete[] _str;_str = nullptr;}
}

这是String类的析构函数,负责释放_str指向的动态分配的内存,防止内存泄漏。

检查_str是否非空,如果是,则使用delete[]释放内存,并将_str设置为nullptr

string类(简易版)现代写法简单实现

 
/*string类现代写法简单实现*/
#include <iostream>
using namespace std;
#include <string.h>
// 深拷贝实现方式二:简洁版/现代版
class String
{
public:String(const char* str = ""){if (nullptr == str)str = "";_str = new char[strlen(str) + 1];strcpy(_str, str);}String(const String& s): _str(nullptr){// 调用构造函数创建一个新对象String strTemp(s._str);swap(_str, strTemp._str);}String& operator=(String s){swap(_str, s._str);return *this;}~String(){if (_str){delete[] _str;_str = nullptr;}}private:char* _str;};

构造函数

 
String(const char* str = "") {if (nullptr == str)str = "";_str = new char[strlen(str) + 1];strcpy(_str, str);
}

这是String类的构造函数,它接受一个C风格字符串str作为参数,默认为空字符串""

如果传入的strnullptr,为了防止访问空指针,将str设置为空字符串。

使用new为成员变量_str分配足够的内存来存储传入的字符串(包括结尾的空字符'\0')。

使用strcpy将传入的字符串复制到_str指向的内存中。

拷贝构造函数

 
String(const String& s)
: _str(nullptr) {String strTemp(s._str);swap(_str, strTemp._str);
}

这是String类的拷贝构造函数。首先,它初始化_strnullptr

然后,它创建一个临时String对象strTemp,使用传入的对象s的字符串数据初始化。

通过swap函数交换当前对象的_str成员和strTemp_str成员。这样,当前对象获得了一个新的字符串副本,而strTemp将在构造函数结束时自动销毁,并释放原来的字符串内存。

赋值操作符

 
String& operator=(String s) {swap(_str, s._str);return *this;
}

这是String类的赋值操作符重载。它接受一个String对象作为参数,但是这里的参数不是引用,而是按值传递,这意味着调用这个操作符时,会自动创建参数s的副本(通过拷贝构造函数)。

然后,它通过swap函数交换当前对象的_str和参数s_str。由于s是一个副本,当赋值操作完成后,s会被销毁,同时负责释放之前当前对象所持有的字符串内存。

这种方法被称为**拷贝-交换(Copy-and-Swap)**技术,它简化了代码,同时自然地处理了自赋值情况,并提供了异常安全保证。

析构函数

 
~String() {if (_str) {delete[] _str;_str = nullptr;}
}

这是String类的析构函数,负责释放_str指向的动态分配的内存,防止内存泄漏。

检查_str是否非空,如果是,则使用delete[]释放内存,并将_str设置为nullptr

拷贝构造函数与赋值操作符的异同

拷贝构造函数中s.str必须是引用,出了作用域不会调用析构函数。所以必须自己创建一个临时对象,这样出作用域之后这个临时对象就会调用析构函数,此时只要临时对象是s.str的深拷贝即可,只需要利用拷贝构造创建深拷贝的临时对象即可,然后交换指针,出作用域后自动调用析构处理原_str的空间,此时_str完成深拷贝。

赋值操作符函数中s.str可以不是引用,我们直接传值传参即可,这样s.str就是临时对象,出作用域之后自动调用析构函数,此时只需要交换指针即可,出作用域后自动调用析构处理原_str的空间,此时_str完成深拷贝。

迭代器

迭代器是一种访问容器(如数组、链表、树等数据结构)中元素的对象,它提供了一种方法来顺序访问容器内的元素而不需要暴露容器的内部表示。迭代器抽象了容器元素的访问机制,使得对元素的访问独立于容器的实现方式。

迭代器通常通过容器提供的方法获得,如begin()end()方法。begin()方法返回指向容器第一个元素的迭代器,而end()方法返回指向容器最后一个元素之后位置的迭代器,用于标示容器的末端。

 
#include <vector>
#include <iostream>int main() {std::vector<int> vec = {1, 2, 3, 4, 5};// 使用迭代器遍历vectorfor (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {std::cout << *it << " ";}std::cout << std::endl;// 使用C++11范围基于for循环和auto关键字简化遍历for (auto& value : vec) {std::cout << value << " ";}std::cout << std::endl;return 0;
}

sting类(升级版)简单实现

 
#include <crtdbg.h>
#include <assert.h>
using namespace std;
#include <iostream>
#include <string.h>
namespace Mystring {class string {public:typedef char* iterator;typedef char* reverse_iterator;/// 构造函数string(const char* str = "") {if (nullptr == str)str = "";_size = strlen(str);_str = new char[_size + 1];strcpy(_str, str);_capacity = _size;}//拷贝构造string(const string& s): _str(nullptr), _size(0), _capacity(0) {string strTemp(s._str);this->swap(strTemp);}//构造函数重载,n个ch字符string(size_t n, char ch) {_size = n;_str = new char[n + 1];memset(_str, ch, n);_str[n] = '\0';_capacity = n;}//等号运算符重载string& operator=(string s) {this->swap(s);return *this;}//析构函数~string() {if (_str) {delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}}///// 迭代器iterator begin() {return _str;}iterator end() {return _str + _size;}reverse_iterator rbegin() {return end();}reverse_iterator rend() {return begin();}// 容量size_t size()const {return _size;}size_t capacity()const {return _capacity;}bool empty()const {return 0 == _size;}void clear() {_size = 0;_str[0] = '\0';}void resize(size_t newsize, char ch) {size_t oldsize = size();if (newsize > oldsize) {size_t oldcap = capacity();if (newsize > oldcap) {reserve(newsize);}memset(_str + _size, ch, newsize - oldsize);}_size = newsize;_str[_size] = '\0';}void resize(size_t newsize) {resize(newsize, 0);}void reserve(size_t newcapacity) {size_t oldcapacity = capacity();if (newcapacity > oldcapacity) {char* temp = new char[newcapacity + 1];strcpy(temp, _str);delete[] _str;_str = temp;_capacity = newcapacity;}}/// 元素访问char& operator[](size_t index) {
//                assert(index < _size);return _str[index];}const char& operator[](size_t index)const {
//                assert(index < _size);return _str[index];}// 修改void push_back(char ch) {if (_size == _capacity)reserve(_capacity * 2);_str[_size] = ch;_size++;_str[_size] = '\0';}string& operator+=(char ch) {push_back(ch);return *this;}string& operator+=(const string& s) {*this += s._str;return *this;}string& operator+=(const char* s) {size_t len = strlen(s);char* temp = new char[_size + len + 1];strcpy(temp, _str);strcat(temp, s);_size += len;delete[] _str;_str = temp;_capacity = _size;return *this;}// 特殊操作const char* c_str()const {return _str;}size_t find(char ch, size_t pos = 0) {for (size_t i = pos; i < _size; ++i) {if (_str[i] == ch)return i;}return npos;}size_t rfind(char ch, size_t pos = npos) {pos = pos < _size ? pos : _size - 1;for (int i = pos; i >= 0; --i) {if (_str[i] == ch)return i;}return npos;}string substr(size_t pos = 0, size_t n = npos) {if (n == npos)n = _size;if (pos + n >= _size) {n = _size - pos;}char* temp = new char[n + 1];strncpy(temp, _str + pos, n);temp[n] = '\0';string strRet(temp);delete[] temp;return strRet;}//void swap(string& s) {std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}friend ostream& operator<<(ostream& _cout, const string& s) {_cout << s._str;return _cout;}private:char* _str;size_t _size;size_t _capacity;public:static size_t npos;};size_t string::npos = -1;}
void TestString1() {Mystring::string s1;Mystring::string s2("hello");Mystring::string s3(s2);Mystring::string s4(10, 'A');cout << s2 << endl;for (size_t i = 0; i < s3.size(); ++i) {cout << s3[i];}cout << endl;for (auto e : s4)cout << e;cout << endl;auto it = s4.begin();while (it != s4.end()) {cout << *it;++it;}cout << endl;}void TestString2() {Mystring::string s("hello");s.clear();cout << s.size() << endl;cout << s.capacity() << endl;if (s.empty()) {cout << "ok" << endl;} else {cout << "error" << endl;}}void TestString3() {Mystring::string s("hello");s.reserve(10);cout << s.size() << endl;cout << s.capacity() << endl;s.reserve(20);cout << s.size() << endl;cout << s.capacity() << endl;s.reserve(15);cout << s.size() << endl;cout << s.capacity() << endl;s.reserve(5);cout << s.size() << endl;cout << s.capacity() << endl;}void TestString4() {Mystring::string s("hello");s.resize(10, '!');cout << s.size() << endl;cout << s.capacity() << endl;cout << s << endl;s.resize(20, 'A');cout << s.size() << endl;cout << s.capacity() << endl;cout << s << endl;s.resize(15);cout << s.size() << endl;cout << s.capacity() << endl;cout << s << endl;s.resize(5);cout << s.size() << endl;cout << s.capacity() << endl;cout << s << endl;}void TestString5() {Mystring::string s1("hello");s1[0] = 'H';const Mystring::string s2(s1);cout << s2[0] << endl;}void TestString6() {Mystring::string s("hello");s.push_back('!');cout << s << endl;s += "world";cout << s << endl;Mystring::string ss("!!!");s += ss;cout << s << endl;}void TestString7() {Mystring::string s("hellohellohello");size_t pos = 0;while (true) {pos = s.find('h', pos);if (pos == Mystring::string::npos)break;cout << pos << endl;pos++;}}void TestString8() {Mystring::string s("aaabbbbbccc.txt");int start = s.find('b');int end = s.rfind('b');Mystring::string ret = s.substr(start, end - start + 1);cout << ret << endl;cout << s.substr(s.rfind('.') + 1) << endl;}int main() {// TestString1();// TestString2();// TestString3();// TestString4();// TestString5();// TestString6();// TestString7();TestString8();_CrtDumpMemoryLeaks();return 0;}

成员变量

_str:指向动态分配的字符数组,用于存储字符串数据。

_size:当前字符串的长度。

_capacity:分配的存储容量,即_str可以容纳的最大字符数。

构造函数

有参与无参构造函数

默认构造函数接受一个C风格字符串,默认为空字符串""。如果传入的是nullptr,则使用空字符串来初始化_str

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

无参构造函数和有参构造函数结合,无参构造函数等价于全缺省,把有参构造函数的所有参数写成全缺省的形式,这样就把无参构造函数和有参构造函数结合起来一起写了。

无参构造函数表示创建一个空字符串,空字符串表示一个字符大小的空间,里面仅仅存放'\0'。注意空字符串与nullptr是不一样的,如果使用nullptr充当空字符串,此时调用strlen函数程序会崩溃,因为strlen遇到'\0'停止,如果对nullptr对象调用strlen系统就会崩溃。

strlen计算的是字符串字符的个数,并不包括'\0',因此申请空间的时候需要多申请一个空间存放'\0'

之后利用strcpy函数拷贝里面的数据即可,注意strcpy会把'\0'也拷贝过去。

最后修改_capacity即可。

拷贝构造函数

拷贝构造函数使用"拷贝-交换"技术,先创建一个临时string对象strTemp,然后通过swap函数交换临时对象和当前对象的成员变量,实现深拷贝。

 
string(const string& s): _str(nullptr), _size(0), _capacity(0) {string strTemp(s._str);this->swap(strTemp);
}

swap函数是string编写的一个交换函数,用来交换string的所有成员变量。

swap作用的对象是两个string类,系统默认的swap只能交换内置类型,我们想要交换自定义类型的成员变量就必须自己定义swap函数。

拷贝构造函数,利用已经存在的string类,进行深拷贝,由于传入的参数必须是引用,所以我们选择创建一个临时对象,再交换他们的成员变量。我们编写好的有参无参构造函数就是一个深拷贝。

临时对象出作用域之后会自动调用析构函数,此时临时对象的指针指向的空间被消灭,指针本身也被消灭。

构造函数重载,n个ch字符

另一个构造函数接受字符的数量和字符本身,用于创建包含重复字符的字符串。

 
//构造函数重载,n个ch字符
string(size_t n, char ch) {_size = n;_str = new char[n + 1];memset(_str, ch, n);_str[n] = '\0';_capacity = n;
}

memset函数逐字节赋值,对于_str的前n个字节用字符ch的字节代替,_str中每一个元素占用一个字节,因此_str中前n个元素就是ch字符。

注意不要忘记对最后一个位置添上'\0',并修改_capacity的值。

赋值操作符

使用"拷贝-交换"技术,参数按值传递,这意味着自动调用拷贝构造函数来创建s的副本。然后,使用swap函数交换副本和当前对象的成员变量。

 
//等号运算符重载
string& operator=(string s) {this->swap(s);return *this;
}

传入的参数s可以不传引用类型,说明可以直接值传参,传入的参数本身就是临时对象,此时我们就不需要再创建临时对象,直接交换所有成员变量即可。

s是临时对象,出作用域之后会自动调用析构函数,此时s成员变量中的指针指向的空间会被消灭,指针本身也会被消灭。

析构函数

负责释放_str指向的动态分配的内存,避免内存泄露。

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

迭代器支持

beginend方法返回指向字符串起始和结束位置的迭代器。

rbeginrend方法提供了逆序遍历的支持,但实现可能有误,因为它们应该返回逆序迭代器的类型,而不是直接返回endbegin的结果。

 
typedef char* iterator;
typedef char* reverse_iterator;
iterator begin() {return _str;
}iterator end() {return _str + _size;
}reverse_iterator rbegin() {return end();
}reverse_iterator rend() {return begin();
}

容量和大小管理

提供了sizecapacityemptyclearresizereserve等方法,用于管理和查询字符串的大小和容量。

resize指定字符

 
void resize(size_t newsize, char ch) {size_t oldsize = size();if (newsize > oldsize) {size_t oldcap = capacity();if (newsize > oldcap) {reserve(newsize);}memset(_str + _size, ch, newsize - oldsize);}_size = newsize;_str[_size] = '\0';
}

resize重新设定_size成员变量,_capacity一定比_size大,重新设定的newsize的值有三种可能,比_size小,介于_size_capacity之间,或者比_capacity大。

resize操作不会影响_capacity

如果newsize_size小,那么只需要把_size设置为newsize,再把最后一个位置赋值为'\0'即可。

如果newsize介于_size_capacity之间,只需要把多出来的部分赋值成ch字符即可,利用memset快速赋值。

如果newsize_capacity大,还需要进行增容操作,之后再把多出来的部分赋值成ch字符即可。

resize不指定字符

 
void resize(size_t newsize) {resize(newsize, 0);
}

复用指定字符的函数,把指定字符改为'\0'即可。

reserve

 

void reserve(size_t newcapacity) {size_t oldcapacity = capacity();if (newcapacity > oldcapacity) {char* temp = new char[newcapacity + 1];strcpy(temp, _str);delete[] _str;_str = temp;_capacity = newcapacity;}
}

重新设定_capacity成员变量。newcapacity如果比_capacity小或者相等,那么不会有任何变化,也就是使用reserve重新设定_capacity只允许增大。

如果newcapacity_capacity大,申请一个新的空间大小,把旧的空间消灭然后指向新的空间大小即可。

元素访问

重载了下标操作符[],允许访问和修改指定位置的字符。

 
char& operator[](size_t index) {
//                assert(index < _size);return _str[index];
}const char& operator[](size_t index)const {
//                assert(index < _size);return _str[index];
}

字符串修改

提供了push_backoperator+=方法,用于向字符串末尾添加字符或另一个字符串。

 
string& operator+=(const char* s) {size_t len = strlen(s);char* temp = new char[_size + len + 1];strcpy(temp, _str);strcat(temp, s);_size += len;delete[] _str;_str = temp;_capacity = _size;return *this;
}

string类型对象+=一个字符数组,strcpy拷贝原数据,strcat在后面添加新数据。

 
void push_back(char ch) {if (_size == _capacity)reserve(_capacity * 2);_str[_size] = ch;_size++;_str[_size] = '\0';
}string& operator+=(char ch) {push_back(ch);return *this;
}string& operator+=(const string& s) {*this += s._str;return *this;
}

特殊操作

c_str方法返回一个C风格字符串,即以'\0'终止的字符数组。

findrfind方法用于在字符串中查找给定字符的第一次或最后一次出现的位置。

substr方法提取字符串的一个子串。

 
const char* c_str()const {return _str;
}size_t find(char ch, size_t pos = 0) {for (size_t i = pos; i < _size; ++i) {if (_str[i] == ch)return i;}return npos;
}size_t rfind(char ch, size_t pos = npos) {pos = pos < _size ? pos : _size - 1;for (int i = pos; i >= 0; --i) {if (_str[i] == ch)return i;}return npos;
}
string substr(size_t pos = 0, size_t n = npos) {if (n == npos)n = _size;if (pos + n >= _size) {n = _size - pos;}char* temp = new char[n + 1];strncpy(temp, _str + pos, n);temp[n] = '\0';string strRet(temp);delete[] temp;return strRet;
}

findrfind遍历所有元素,找指定字符。

find是从pos位置往后遍历,而rfind是从pos位置往前遍历。

strencpy_str+pos位置开始,后面的n个字符依次复制给temp,并且不会把'\0'复制过去,因此复制完需要手动添加'\0'

需要返回string类对象,利用构造函数创建对象。

交换

swap方法交换两个string对象的成员变量。

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

友元函数

重载了插入运算符<<,允许将string对象直接输出到标准输出流。

 
friend ostream& operator<<(ostream& _cout, const string& s) {_cout << s._str;return _cout;
}

友元函数是独立于string类的函数,不属于string类,即使友元函数在string类内部。

内存泄漏检测

_CrtDumpMemoryLeaks()在程序结束时检查内存泄漏,这是特定于Visual Studio的调试功能。

使用_CrtDumpMemoryLeaks()需要引用#include <crtdbg.h>头文件。

反向迭代器探究

 
#include<iostream>
using namespace std;
#include<string>
int main(){string s={"abcdefg"};auto it=s.rbegin();while(it!=s.rend()){cout<<*it<<endl;it++;}}

string类中的反向迭代器rbegin指向的是最后一个元素后一个位置,但是解引用却会输出最后一个元素的值,我认为这是编译器自动优化的操作,在反向迭代器中,解引用会自动调用迭代器++后的解引用。

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

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

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

相关文章

中创ET4410 台式LCR数字电桥 简单开箱测评

最近买了一台LCR电桥&#xff0c;完善一下自己实验室的设备&#xff0c;选了中创ET4410&#xff0c;这款性价比高一点。 1199元在PDD买的&#xff0c;好像胜利的VC4090C也是找中创代工的。 ET4410介绍 本系列LCR数字电桥是采用自动平衡电桥原理设计的元件参数分析仪&#xf…

【Linux】学习-深入了解文件的读与写

深入了解语言级别(C语言)文件操作的"读"与"写" 在学习前&#xff0c;我们先要知道在Linux下的一个原则&#xff1a;一切皆是文件 如何理解呢&#xff1f;举个外设的例子&#xff0c;比如键盘和显示器&#xff0c;这两个外设也可以其实本质上也是文件&…

强敌环伺:金融业信息安全威胁分析——整体态势

从早期的Zeus和其他以银行为目标的特洛伊木马程序&#xff0c;到现在的大规模分布式拒绝服务&#xff08;DDoS&#xff09;攻击&#xff0c;再到新颖的钓鱼攻击和勒索软件&#xff0c;金融服务业已成为遭遇网络犯罪威胁最严重的行业之一。金融服务业的重要性不言而喻&#xff0…

[office] excel如何计算毛重和皮重的时间间隔 excel计算毛重和皮重时间间隔方法 #笔记#学习方法

excel如何计算毛重和皮重的时间间隔 excel计算毛重和皮重时间间隔方法 在日常工作中经常会到用excel&#xff0c;有时需要计算毛重和皮重的时间间隔&#xff0c;具体的计算方式是什么&#xff0c;一起来了解一下吧 在日常工作中经常会到用excel&#xff0c;在整理编辑过磅数据…

Debezium发布历史120

原文地址&#xff1a; https://debezium.io/blog/2022/04/07/read-only-incremental-snapshots/ 欢迎关注留言&#xff0c;我是收集整理小能手&#xff0c;工具翻译&#xff0c;仅供参考&#xff0c;笔芯笔芯. Read-only Incremental Snapshots for MySQL April 7, 2022 by K…

软件应用实例分享,电玩计时计费怎么算,佳易王PS5游戏计时器系统程序教程

软件应用实例分享&#xff0c;电玩计时计费怎么算&#xff0c;佳易王PS5游戏计时器系统程序教程 一、前言 以下软件教程以 佳易王电玩计时计费管理系统软件V17.9为例说明 软件文件下载可以点击最下方官网卡片——软件下载——试用版软件下载 点击开始计时后&#xff0c;图片…

k8s-资源限制与监控 15

资源限制 上传实验所需镜像 Kubernetes采用request和limit两种限制类型来对资源进行分配。 request(资源需求)&#xff1a;即运行Pod的节点必须满足运行Pod的最基本需求才能 运行Pod。 limit(资源限额)&#xff1a;即运行Pod期间&#xff0c;可能内存使用量会增加&#xff0…

泛娱乐社交出海洞察,Flat Ads解锁海外增长新思路

摘要:解读泛娱乐社交应用出海现状与趋势,解锁“掘金”泛娱乐社交出海赛道新思路。 根据全球舆情监测机构 Meltwater 和社交媒体机构We are Social最新发布数据显示,全球社交媒体活跃用户数量已突破50亿,约占世界人口总数62.5%。庞大的用户数量意味着广阔的增量空间,目前,随着全…

无人机图像识别技术研究及应用,无人机AI算法技术理论,无人机飞行控制识别算法详解

在现代科技领域中&#xff0c;无人机技术是一个备受瞩目的领域。随着人们对无人机应用的需求在不断增加&#xff0c;无人机技术也在不断发展和改进。在众多的无人机技术中&#xff0c;无人机图像识别技术是其中之一。 无人机图像识别技术是利用计算机视觉技术对无人机拍摄的图像…

【java】简单的Java语言控制台程序

一、用于文本文件处理的Java语言控制台程序示例 以下是一份简单的Java语言控制台程序示例&#xff0c;用于文本文件的处理。本例中我们将会创建一个程序&#xff0c;它会读取一个文本文件&#xff0c;显示其内容&#xff0c;并且对内容进行计数&#xff0c;然后将结果输出到控…

Maven进阶

一、分模块开发与设计 1. 分模块开发的意义 问题导入 分模块开发对工程有什么好处&#xff1f; 模块拆分原则 目的&#xff1a;项目的扩展性变强了&#xff0c;方便其他项目引用相同的功能。 将原始模块按照功能拆分成若干个子模块&#xff0c;方便模块间的相互调用&#…

知识图谱与图神经网络融合:构建智能应用的新前沿

目录 前言1 知识图谱表示学习1.1 典型模型1.2 下游任务 2 图神经网络与知识图谱表示学习2.1 Compgcn&#xff1a;合成图卷积模型2.2 知识图谱嵌入在归纳设置下的推进 3 图神经网络与知识图谱构建3.1 关系抽取的进阶应用3.2 结构信息补全与知识图谱的完整性 4 图神经网络与知识图…

Vue事件中如何使用 event 对象

在Vue中&#xff0c;事件处理函数常常需要获取事件触发时的相关信息&#xff0c;比如鼠标位置、按键信息等。而要获取这些信息&#xff0c;就需要使用event对象。那么在Vue的事件中如何正确使用event对象呢&#xff1f;接下来就来详细介绍一下。 首先&#xff0c;在Vue的事件中…

算法练习-二叉搜索树中的搜索(思路+流程图+代码)

难度参考 难度&#xff1a;中等 分类&#xff1a;二叉树 难度与分类由我所参与的培训课程提供&#xff0c;但需要注意的是&#xff0c;难度与分类仅供参考。且所在课程未提供测试平台&#xff0c;故实现代码主要为自行测试的那种&#xff0c;以下内容均为个人笔记&#xff0c;旨…

C语言:操作符详解

创作不易&#xff0c;给个三连吧&#xff01;&#xff01; 一、算术操作符 C语言中为了方便计算&#xff0c;提供了算数操作符&#xff0c;分别是:,-,*,/,% 由于这些操作符都是有两个操作数&#xff08;位于操作符两边&#xff09;&#xff0c;所以这种操作符也叫做双目操作…

通过 docker-compose 部署 Flink

概要 通过 docker-compose 以 Session Mode 部署 flink 前置依赖 Docker、docker-composeflink 客户端docker-compose.yml version: "2.2" services:jobmanager:image: flink:1.17.2ports:- "8081:8081"command: jobmanagervolumes:- ${PWD}/checkpoin…

使用clearml监控模型训练过程

安装依赖 pip install clearml依赖安装好后登陆clearml官网 创建一个工作空间 点击Create new credentials 点击后将api整块复制出来&#xff0c;随后需要在当前终端环境中初始化这个clearml的账户信息 终端输入&#xff1a; clearml-init 在出现的Paste copied configurat…

10个常考的前端手写题,你全都会吗?(上)

前言 &#x1f4eb; 大家好&#xff0c;我是南木元元&#xff0c;热爱技术和分享&#xff0c;欢迎大家交流&#xff0c;一起学习进步&#xff01; &#x1f345; 个人主页&#xff1a;南木元元 今天来分享一下10个常见的JavaScript手写功能。 目录 1.实现new 2.call、apply、…

【OpenVINO™】在 MacOS 上使用 OpenVINO™ C# API 部署 Yolov5 (下篇)

在 MacOS 上使用 OpenVINO™ C# API 部署 Yolov5 &#xff08;下篇&#xff09; 项目介绍 YOLOv5 是革命性的 "单阶段"对象检测模型的第五次迭代&#xff0c;旨在实时提供高速、高精度的结果&#xff0c;是世界上最受欢迎的视觉人工智能模型&#xff0c;代表了Ult…

医院挂号预约|医院挂号预约小程序|基于微信小程序的医院挂号预约系统设计与实现(源码+数据库+文档)

医院挂号预约小程序目录 目录 基于微信小程序的医院挂号预约系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、小程序用户端 2、系统服务端 &#xff08;1&#xff09; 用户管理 &#xff08;2&#xff09;医院管理 &#xff08;3&#xff09;医生管理 &…