【十】【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>头文件。

结尾

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

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

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

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

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

相关文章

Python tkinter (16) —— Progressbar

本文主要介绍Python tkinter 进度条Progressbar应用及示例。 目录 系列文章 进度条Progressbar 基本概念 参数&#xff1a; mode参数 基本应用 动画设计 引入time 具体实现 start/step/stop step(amount)&#xff1a; start(interval)&#xff1a; stop()&#xff…

react中hook封装一个table组件

目录 react中hook封装一个table组件依赖CommonTable / index.tsx使用组件效果 react中hook封装一个table组件 依赖 cnpm i react-resizable --save cnpm i ahooks cnpm i --save-dev types/react-resizableCommonTable / index.tsx import React, { useEffect, useMemo, use…

VMware虚拟机安装openEuler系统(一)(2024)

目录 一、下载ISO镜像 二、开始创建虚拟机 通过实践是学习openEuler开源Linux系统的最佳方式。因此我们首先得搭建一个openEuler实战环境&#xff0c;文章是在Windows系统上使用VMware Workstation虚拟化软件&#xff0c;安装和学习openEuler开源Linux操作系统。 使用虚拟机…

新型RedAlert勒索病毒针对VMWare ESXi服务器

前言 RedAlert勒索病毒又称为N13V勒索病毒&#xff0c;是一款2022年新型的勒索病毒&#xff0c;最早于2022年7月被首次曝光&#xff0c;主要针对Windows和Linux VMWare ESXi服务器进行加密攻击&#xff0c;到目前为止该勒索病毒黑客组织在其暗网网站上公布了一名受害者&#x…

人工智能专题:量子汇编语言和量子中间表示发展白皮书

今天分享的是人工智能系列深度研究报告&#xff1a;《人工智能专题&#xff1a;量子汇编语言和量子中间表示发展白皮书》。 &#xff08;报告出品方&#xff1a;量子信息网络产业联盟&#xff09; 报告共计&#xff1a;78页 量子计算与量子编程概述 随着社会生产力的发展&am…

vue3 之 商城项目—home

home—整体结构搭建 根据上面五个模块建目录图如下&#xff1a; home/index.vue <script setup> import HomeCategory from ./components/HomeCategory.vue import HomeBanner from ./components/HomeBanner.vue import HomeNew from ./components/HomeNew.vue import…

手势检测跟踪解决方案

美摄科技&#xff0c;作为业界领先的人工智能技术提供商&#xff0c;致力于为企业提供先进的手势检测与跟踪解决方案&#xff0c;以推动企业在智能化、高效化的道路上阔步前行。 一、手势检测与跟踪技术的优势 手势检测与跟踪技术作为人机交互的重要一环&#xff0c;具有以下…

Flask 入门7:使用 Flask-Moment 本地化日期和时间

如果Web应用的用户来自世界各地&#xff0c;那么处理日期和时间可不是一个简单的任务。服务器需要统一时间单位&#xff0c;这和用户所在的地理位置无关&#xff0c;所以一般使用协调世界时&#xff08;UTC&#xff09;。不过用户看到 UTC 格式的时间会感到困惑&#xff0c;他们…

雨云EPYC7702服务器上线了!适合幻兽帕鲁开服的VPS!雨云EPYC7702高防VPS性能测评

雨云游戏云上线了AMD EPYC 7702的VPS服务器&#xff0c;中等水平的单核性能&#xff0c;适合开幻兽帕鲁和我的世界1.17以下版本的服务器。 AMD Epyc 7702是一款64核心128线程&#xff0c;基础频率2.00 GHz加速频率高达3.35 GHz处理器&#xff0c;凭借着7 nm工艺及新一代Rome (…

SpringBoot:@Profile注解和Spring EL

&#x1f3e1;浩泽学编程&#xff1a;个人主页 &#x1f525; 推荐专栏&#xff1a;《深入浅出SpringBoot》《java对AI的调用开发》 《RabbitMQ》《Spring》《SpringMVC》 &#x1f6f8;学无止境&#xff0c;不骄不躁&#xff0c;知行合一 文章目录 前言一、Prof…

5年前端仔的2023年终总结

突然发现已经有好几个月没有写过博客总结过什么&#xff0c;小小辩解一下&#xff0c;其实并不是笔者停止的学习和总结&#xff0c;随着在前端这个行业的逐年深入&#xff0c;渐渐的很多收获不再是像之前简单的技术点的确定性描述讲解了&#xff0c;而是某个领域的知识体系的串…

数智文旅:智慧文旅中的数字化转型

在数字化浪潮席卷全球的今天&#xff0c;旅游业作为传统服务业的代表&#xff0c;正面临着前所未有的转型压力与机遇。智慧文旅&#xff0c;作为旅游业与数字技术深度融合的产物&#xff0c;不仅标志着旅游业进入了全新的发展阶段&#xff0c;更预示着未来旅游业将朝着更加智能…