string类的详细模拟实现

string类的模拟实现

文章目录

  • string类的模拟实现
    • 前言
    • 1. 类的框架设计
    • 2. 构造函数与析构函数
    • 3. 拷贝构造与重载赋值运算符函数
    • 4. 运算符重载
    • 5. 成员函数
    • 6. 迭代器的实现
    • 7. 非成员函数
    • 8. 单元测试
    • 总结

前言

​ 在现代编程中,字符串处理是每个程序员都会遇到的基本任务。C++标准库提供了强大的std::string类,但了解其背后的原理对于深入理解语言特性至关重要。本文将介绍如何从零开始实现一个简单的String类,这不仅能够帮助我们更好地理解C++,还能够提升我们解决问题的能力。


1. 类的框架设计

​ 首先需要确定成员变量有哪些,结合“先描述,再组织”的方针,要对String类进行描述,那么就必须先确定成员变量,下一步才有资格谈论功能实现(函数声明与定义)。这里我们需要实现的string本质上是一个可实现自动扩容的字符数组!所以成员变量采用char*声明的字符数组+字符串长度大小+字符数组容量,通过字符数组实现存储,字符串长度和字符数组容量共同作为控制扩容逻辑的参考值。

​ 当然,在确定成员变量后接着就需要定夺功能实现了,首先必须要有的是构造、析构函数,拷贝构造、重载复制运算符函数(这两者拷贝函数有时需要显式delete避免调用),特定的运算符重载,基本功能函数等。直接一次性声明出所有的类内成员变量与函数是不现实的,很难免会出现遗漏功能或逻辑不自洽。所以一般都是一边完善类的声明,一边实现函数定义,下面我便于后续描述直接给出了本文涉及的所有函数功能声明。

(注:仅挑选实现了std::string类中部分高使用率的函数)

class String
{
public:// 基础构造函数String(const char* str = "");// 拷贝构造函数String(const String& s);// 赋值运算符函数String& operator=(const String& s);// 析构函数~String();// 重载运算符friend std::ostream& operator<<(std::ostream& out, const String& s);friend std::istream& operator>>(std::istream& in, const String& s);bool operator==(const String& s);char operator[](size_t index)const;char& operator[](size_t index);String operator+(const String& s)const;String operator+(const char c)const;String operator+(const char* str)const;String& operator+=(const String& s);String& operator+=(const char c);String& operator+=(const char* str);// 成员函数size_t size()const;size_t capacity()const;const char* c_str()const;void reserve(size_t n);void resize(size_t n, char c = '\0');void push_back(const char c);void append(const char* str);void insert(size_t pos, const char c, size_t n = 1);void insert(size_t pos, const char* str);void erase(size_t pos, size_t n = npos);void swap(String& s);size_t find(const String& s, size_t pos = 0)const;size_t find(const char* str, size_t pos = 0)const;size_t find(const char c, size_t pos = 0, size_t n = 0)const;String substr(size_t pos = 0, size_t len = npos)const;// (仿)迭代器typedef char* iterator;typedef const char* const_iterator;iterator begin();iterator end();const_iterator cbegin();const_iterator cend();private:char* m_str;size_t m_size;size_t m_capacity;
private:static const int npos;
};

在实现具体函数功能前需要声明:

任何类内非静态函数,其参数列表第一个参数永远是类自身的self指针!(例如这里实现String类,即为:String* self)

​ 这是后面函数实现中有时显式无参却能操作类内对象、仅有一个参数却能实现两对象之间的对比亦或其他操作的理论基础!

2. 构造函数与析构函数

String::String(const char* str):m_size(strlen(str))		// 注意缺省参数在声明和定义处仅出现一次
{m_capacity = m_size;m_str = new char[m_capacity + 1];	// 多一个字节存放'\0'strcpy(m_str, str);
}String::~String()
{delete[] m_str;m_str = nullptr;m_size = m_capacity = 0;
}

注意这里的构造函数使用了全缺省参数实现,通过这种方法可实现通过一个函数兼任无参构造与单参数构造函数。定义内容上,在String类实例化对象时实现堆区创建指针数组,以便后续对其扩容。

注意:函数参数使用缺省值实现时,缺省值定义的出现在声明和定义中仅能出现一次!

析构函数中需要注意delete的对象是数组,所以需要相应使用delete[] 数组名的方式对数组申请的堆区空间进行释放。

3. 拷贝构造与重载赋值运算符函数

String::String(const String& s)
{m_size = s.m_size;m_capacity = s.m_capacity;m_str = new char[m_capacity + 1];strcpy(m_str, s.m_str);
}
String& String::operator=(const String& s)
{char* tmp = new char[s.m_capacity];strcpy(tmp, s.m_str);delete[] this->m_str;this->m_str = tmp;this->m_size = s.m_size;this->m_capacity = s.m_capacity;return *this;
}

实现类的拷贝功能类型函数时,特别需要注意深浅拷贝的问题,很好的辨别方法是观察成员变量是否有是在堆区创建的,如果有,一般都需要避免浅拷贝的问题。在复制给新对象时,需要对该成员变量在堆区新开辟空间,接着才能进行堆空间之间内容的值拷贝。

对于拷贝赋值运算符而言,返回值一般以 self& 形式存在,很大一部分原因在于通过这种方法可以实现链式调用,例如:

String s1("haha");
String s2;
String s3;s3 = s2 = s1;

由于赋值运算符的运算逻辑是自右向左的,所以仅需一条语句就可将s1的值拷贝给s2、s3对象。

4. 运算符重载

  • 流插入运算符和流提取运算符
std::ostream& operator<<(std::ostream& out, const String& s)
{out << s.m_str;return out;
}std::istream& operator>>(std::istream& in, String& s)
{char ch;ch = in.get();int i = 0;char buff[128]{ 0 };while (ch != '\n' && ch != ' '){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i != 127){buff[i] = '\0';s += buff;}return in;
}

当我们使用流提取(<<)时,由于不会导致String对象的成员变量(字符数组)内数组大小发生改变,并不会发生扩容,所以可以采用简单的直接提取字符数组进行输出(out << s.m_str)

然而使用流插入(>>)时, 很难免传入的新字符串大小超过原字符数组的容量承载极限,有可能导致扩容,所以我们需要对插入的数据进行分批从缓冲区获取,分批插入字符数组,如此一来当我们调用插入之类的函数时就可以一边插入一边扩容,这里采用了 += 运算符实现插入操作,当然也可以使用 insertpush_back之类的函数实现。当然,后面我们会给出这类插入函数的实现。

  • 双等号运算符
bool String::operator==(const String& s)
{return strcmp(m_str, s.m_str) == 0;
}

这里采用了C语言库中的strcmp函数,通过对两个String对象的字符数组进行对比判断两对象是否等价。

  • 下标运算符
char String::operator[](size_t index)const
{assert(index < m_size);return m_str[index];
}
char& String::operator[](size_t index)
{assert(index < m_size);return m_str[index];
}

提供了重载实现:

  • 如果const String对象调用[]运算符,那么返回的 char 类型不会因其改变影响对象内的字符数组内值的变化。
  • 如果String对象(non_const)调用时,返回指定下标位置的引用,外部对其进行修改时可以引起字符数组内指定位置值同步发生改变
  • 加号运算符
String String::operator+(const String& s)const
{String tmp;size_t capacity_sum = capacity() + s.capacity();tmp.reserve(capacity_sum);	// 开空间// 字符串拷贝strcpy(tmp.m_str, m_str);strcat(tmp.m_str, s.m_str);tmp.m_size = size() + s.size();return tmp;
}
String String::operator+(const char c)const
{String tmp;		// 初始化和定义tmp.reserve(capacity() + 1);size_t size = this->size();// 赋值strcpy(tmp.m_str, m_str);tmp.m_str[size] = c;tmp.m_str[size + 1] = '\0';tmp.m_size = size + 1;return tmp;
}
String String::operator+(const char* str)const
{assert(str);String tmp;size_t size_1 = size();size_t size_2 = strlen(str);tmp.reserve(size_1 + size_2 + 1);// 赋值strcpy(tmp.m_str, c_str());strcat(tmp.m_str, str);return tmp;
}

可以看到重载了三种加号运算符,分别实现了追加String对象、char字符、char*类型字符串,从内部实现来看,无一不是调用reserve()函数实现扩容,具体的reserve()函数定义将会在后面给出。不过可以确定的是,+运算符也属于插入类型的运算符函数,不可避免的存在扩容的问题,所以模型都属于扩容+赋值

  • 加等运算符
String& String::operator+=(const String& s)
{this->append(s.m_str);return *this;
}
String& String::operator+=(const char c)
{this->push_back(c);return *this;
}
String& String::operator+=(const char* str)
{this->append(str);return *this;
}

由于operator+=的功能和append()函数基本相同,所以采用代码复用的方式实现,后面给出 append() 函数的具体实现。

5. 成员函数

  • sizecapacity
size_t String::size()const
{return m_size;
}size_t String::capacity()const
{return m_capacity;
}

出于对数据的保护,使用接口对外提供访问。

  • c_str
const char* String::c_str()const
{return m_str;
}

便于外部直接访问字符数组,尤其是外部C语言函数调用时,不接受String类型参数,只能通过 c_str() 函数进行传递,注意返回值类型为 const char* ,可以避免外部对指针的操作引起内部数组变化。

  • reserveresize
void String::reserve(size_t n)		// 设定容量,不影响size值
{if (n > m_size){char* cmp = new char[n + 1];strcpy(cmp, m_str);delete[] m_str;m_str = cmp;m_capacity = n;}
}
void String::resize(size_t n, char c)
{if (n <= m_size){m_str[n] = c;m_size = n;}else{if (n >= m_capacity)	// 考虑扩容{reserve(n);}for (size_t i = m_size; i < n; i++)	// 填充值{m_str[i] = c;}m_str[n] = '\0';m_size = n;}
}

reserve():控制容量

resize(): 控制size大小,可能影响容量(需要扩容)

  • push_backappend
void String::push_back(const char c)
{if (m_size == m_capacity)	// 考虑扩容{m_capacity = (m_capacity == 0) ? (4) : (m_capacity * 2);reserve(m_capacity);}m_str[m_size++] = c;m_str[m_size] = '\0';
}void String::append(const char* str)
{assert(str);const size_t size = strlen(str);if (size >= m_capacity - m_size)	// 考虑扩容{m_capacity = size + m_size + 1;reserve(m_capacity);}// 拷贝strcpy(m_str + this->size(), str);		// 从中间部分开始拷贝m_size += size;
}

都属于引起字符数组size增大的函数,存在扩容的可能,所以内部对size与capacity值进行判断,如有需要先扩容再插入。

  • inserterase
void String::insert(size_t pos, const char c, size_t n)
{assert(pos <= m_size);while(n >= m_capacity - m_size)		// 避免n过大内存分配不够{m_capacity = (m_capacity == 0) ? (n+1) : (m_capacity * 2);reserve(m_capacity);}size_t i = m_size + n;for (; i >= n + pos && i != 0; --i)	// i!=0避免溢出 内置if语句拖慢运行时间{m_str[i] = m_str[i - n];}if (i == 1 && pos == 0)	// 处理pos=0(或i=0)的情况{m_str[n] = m_str[n - 1];}//m_str[m_size + n] = '\0';	// 注意结尾的'\0' (如果上面for循环定义i = m_size + n - 1时)for (size_t j = pos; j < pos + n; ++j){m_str[j] = c;}m_size += n;	// 切记更新size值
}
void String::insert(size_t pos, const char* str)
{assert(str && pos <= m_size);size_t length = strlen(str);while (length >= m_capacity - m_size){m_capacity = (m_capacity == 0) ? (4) : (m_capacity * 2);reserve(m_capacity);}// 字符串右移size_t i = m_size + length;for (; i >= length + pos && i != 0; --i)	// i!=0避免溢出 内置if语句拖慢运行时间{m_str[i] = m_str[i - length];}if (i == 1 && pos == 0)	// 处理pos=0(或i=0)的情况{m_str[length] = m_str[length - 1];}for (size_t j = pos, i = 0; j < pos + length; ++j, ++i){m_str[j] = str[i];}m_size += length;
}void String::erase(size_t pos, size_t n)
{assert(pos < m_size);if (n == npos || n >= m_size - pos){m_str[pos] = '\0';m_size = pos;}else{for (size_t i = pos; i <= m_size; ++i)		//i <= pos + n连带复制'\0'{m_str[i] = m_str[i + n];}m_size -= n;}
}

提供了两个重载的 Insert() 函数,可实现指定位置插入n个char值或在指定位置插入字符串(const char*)。而 erase() 函数也实现了从pos位置向后删除n个字符的功能。两者都采用了缺省值参数,如不指定就插入1个字符,而 erase() 函数的n如果未指定代表极大的值,相当于删除pos值以后的所有字符。

  • swapfind
void String::swap(String& s)
{std::swap(m_str, s.m_str);std::swap(m_size, s.m_size);std::swap(m_capacity, s.m_capacity);
}size_t String::find(const String& s, size_t pos)const
{assert(pos < m_size);const char* sub_str = strstr(m_str + pos, s.m_str);if (sub_str != nullptr){return sub_str - m_str;}return npos;
}
size_t String::find(const char* str, size_t pos)const
{assert(pos < m_size && str != nullptr);const char* sub_str = strstr(m_str + pos, str);if (sub_str != nullptr){return sub_str - m_str;}return npos;
}
size_t String::find(const char c, size_t pos, size_t n)const
{assert(pos < m_size && n <= m_size - pos);for (size_t i = pos; i <= m_size - n; ++i){if (m_str[i] == c){size_t j = 0;for (; j < n; ++j, ++i){if (m_str[i] != c){break;}}if (j == n) { return i - n; }}}return npos;
}

使用最简单朴素的方法实现字符串查找。

  • substr
String String::substr(size_t pos, size_t len)const
{assert(pos < m_size);String tmp;if (len > m_size - pos)		// 取到字符串结尾{for (size_t i = pos; i <= m_size; ++i){tmp.push_back(m_str[i]);}}else{for (size_t i = pos; i < pos + len; ++i){tmp.push_back(m_str[i]);}tmp.push_back('\0');}return tmp;
}

实现字符串切割,如果 len 值过大呢?我们将其视为取pos 往后全部字符串的功能要求,所以需要设置一个极大的值赋给 len,我们在类内定义一个静态成员变量(size_t npos = -1),由于 size_t 属于无符号整形,所以 -1 代表的值极大,一般字符串都不会超过这个长度。

6. 迭代器的实现

  • beginend
char* String::begin()
{return m_str;
}
char* String::end()
{return m_str + m_size;
}

这里需要C语言的基础,指针变量+整数代表指针后移整数个指向的变量位置。

  • cbegincend
const char* String::cbegin()
{return m_str;
}
const char* String::cend()
{return m_str + m_size;
}

上面提供两种迭代器的原因是外部利用迭代器访问时,如果是 const 对象就调用 “cbegin(), cend()” 函数,反之调用 “begin(), end()” 函数。返回非const迭代器可以实现外部改变类内字符数组的值。

7. 非成员函数

  • getlineswap
istream& getline(istream& in, String& s, char delim = '\n')	// 空格不作为读取终止符
{char ch = in.get();int i = 0;char buff[128]{ 0 };while (ch != delim){buff[i++] = ch;if (i == 127){buff[127] = '\0';s += buff;i = 0;		// 重置buff}ch = in.get();	// 循环读取}if (i != 127){buff[i] = '\0';s += buff;}return in;
}void swap(String& s1, String& s2)
{s1.swap(s2);
}

8. 单元测试

void test1()
{const String s1("xxxxx");String s2(s1);String s3 = s1 + s2;String s4 = s1 + 'w';String s5 = s1 + "hahaha";cout << s3 << endl;cout << s4 << endl;cout << s5 << endl;// 测试扩容String ss1;ss1.push_back('s');ss1.push_back('s');ss1.push_back('s');ss1.push_back('s');ss1.push_back('s');cout << ss1 << endl;
}

运行结果:

在这里插入图片描述

void test2()
{String s1;s1.append("hahahaha");cout << s1 << endl;s1.insert(0, 'q', 50);cout << s1 << endl;String s2 = s1;s2.insert(0, "wwwwwwwwww");cout << s2 << endl;s2.erase(0, 10);cout << s2 << endl;
}

运行结果:

在这里插入图片描述

void test3()
{String s1("wewewewewe");String s2("hhhhh");s1 += s2;s1 += '6';s1 += "vocal";cout << s1 << endl;
}

运行结果:

在这里插入图片描述

void test4()
{String s1;String s2;cin >> s1 >> s2;cout << s1 << endl;cout << s2 << endl;String s3;getline(cin, s3);cout << s3 << endl;
}

运行结果:

在这里插入图片描述

void test5()
{String s1("hahaha");String s2("jiarenmen");s1.swap(s2);cout << "s1:" << s1 << '\t' << "s2:" << s2 << endl;swap(s1, s2);cout << "s1:" << s1 << '\t' << "s2:" << s2 << endl;
}

运行结果:

在这里插入图片描述

void test6()
{String s("hawrwcrwwwwcrw");size_t found = s.find('w', 0, 4);cout << found << endl;found = s.find("wwcrw");cout << found << endl;String s2("wc");found = s.find(s2);cout << found << endl;
}

运行结果:

在这里插入图片描述

void test7()
{String s("hawwwcrwwwwcrw");String s2 = s.substr(2);cout << s2 << endl;String s3 = s.substr(s.find("cr"), 4);cout << s3 << endl;
}

运行结果:

在这里插入图片描述


总结

​ 通过实现自己的String类,我们不仅加深了对C++内存管理、对象生命周期和运算符重载的理解,还提高了我们的编程技能和问题解决能力。虽然std::string类在功能和性能上可能更加成熟,但自己动手实现一个String类无疑是一次宝贵的学习经历。在未来的编程实践中,我们可以继续探索更多的优化方法和高级特性,使我们的String类更加健壮和高效。

在这里插入图片描述

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

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

相关文章

家用路由器和企业路由器的区别?

一、家用路由器 家用路由器路由器交换机 它只有一个WAN口和一个LAN口&#xff0c;WAN口接公网一个地址&#xff0c;LAN口接你电脑一个IP地址&#xff0c;完全符合路由器的设计&#xff0c;而因为家里如果用了&#xff0c;说明要接多个电脑&#xff0c;那么如果还需要对每个接口…

pandas的综合练习

事先说明&#xff1a; 由于每次都要导入库和处理中文乱码问题&#xff0c;我都是在最前面先写好&#xff0c;后面的代码就不在写了。要是copy到自己本地的话&#xff0c;就要把下面的代码也copy下。 # 准备工作import pandas as pd import numpy as np from matplotlib impor…

卷积篇 | YOLOv8改进之主干网络中引入可变形卷积DConv

前言:Hello大家好,我是小哥谈。可变形卷积模块是一种改进的卷积操作,它可以更好地适应物体的形状和尺寸,提高模型的鲁棒性。可变形卷积模块的实现方式是在标准卷积操作中增加一个偏移量offset,使卷积核能够在训练过程中扩展到更大的范围,从而实现对尺度、长宽比和旋转等各…

Linux系统下——PS1、PS2、PS3、PS4变量详解

目录 前言 一、PS1变量 1.PS1变量详解 2.PS1变量可用参数 3.彩色提示符 二、PS2变量 三、PS3变量 1.不使用PS3变量 2.使用PS3变量 四、PS4变量 前言 在Linux系统中&#xff0c;PS1、PS2、PS3和PS4是特定的环境变量&#xff0c;它们各自在控制提示符和菜单提示信息…

OceanMind海睿思入选中国信通院《2023高质量数字化转型技术解决方案集》

近日&#xff0c;由中国信息通信研究院“铸基计划”编制的《2023高质量数字化转型技术解决方案集&#xff08;第一版&#xff09;》正式发布。 中新赛克海睿思 凭借卓越的产品力以及广泛的行业实践&#xff0c;成功入选该方案集的数据分析行业技术解决方案。 为促进数字化转型…

RIPGeo代码理解(六)main.py(运行模型进行训练和测试)

​代码链接:RIPGeo代码实现 ├── preprocess.py # 预处理数据集并为模型运行执行IP聚类 ├── main.py # 运行模型进行训练和测试 ├── test.py #加载检查点,然后测试 一、导入各种模块和数据库 import torch.nnfrom lib.utils import * import argparse i…

前端制作计算器

用htmlcssjs完成计算器的基本功能&#xff0c;代码如下&#xff1a; HTML代码 <div id"four"> <div class"evaluator"><div class"input"><input type"text"></div><table><tr><td>…

谧林涓露门禁

原神武器升级材料谧林涓露和门禁好像聂。 difference(){union(){cylinder(2, 10,10, $fn365);hull(){translate([15,0,0])cylinder(1,2,2,$fn365);cylinder(1,10,10,$fn365);}}translate([15,0,-1])cylinder(4,1,1,$fn365); }

modelsim与quartus联合仿真ROM读不出数据

modelsim与quartus联合仿真ROM没有数据被读出&#xff0c;很是纳闷。 原因&#xff1a;hex或者mif文件放的不对&#xff0c;放在与db放在同一个文件夹下。modelsim在这个目录查找mif文件或hex。 这是我遇到的问题。当然可能还有其他的问题&#xff1a; 1、mif文件的格式不对&a…

双系统安装03--在已有麒麟KOS基础上安装Windows10

原文链接&#xff1a;双系统安装03–在已有麒麟KOS基础上安装Windows10 Hello&#xff0c;大家好啊&#xff01;继我们之前讨论的关于双系统安装的系列文章之后&#xff0c;今天我将带给大家这个系列的第三篇——在已有的麒麟桌面操作系统上安装Windows 10。对于想要在使用麒麟…

docker安装ES7.1.1(单机版)+ik分词器+es-head可视化

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 Elasticsearch 是一…

力扣236 二叉树的最近公共祖先 Java版本

文章目录 题目描述代码 题目描述 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先的定义为&#xff1a;“对于有根树 T 的两个节点 p、q&#xff0c;最近公共祖先表示为一个节点 x&#xff0c;满足 x 是 p、q 的祖先且 x 的深度尽可能大&…

FlyControls 是 THREE.js 中用于实现飞行控制的类,它用于控制摄像机在三维空间中的飞行。

demo演示地址 FlyControls 是 THREE.js 中用于实现飞行控制的类&#xff0c;它用于控制摄像机在三维空间中的飞行。 入参&#xff1a; object&#xff1a;摄像机对象&#xff0c;即要控制的摄像机。domElement&#xff1a;用于接收用户输入事件的 HTML 元素&#xff0c;通常…

C++函数参数传递

目录 传值参数 指针形参 传引用参数 使用引用避免拷贝 使用引用形参返回额外信息 const形参和实参 指针或引用形参与const 数组形参 管理指针形参 使用标记指定数组长度 使用标准库规范 显式传递一个表示数组大小的形参 数组形参和const 数组引用形参 传递多维数…

Django缓存(一)

一、缓存的介绍 官网:Django 缓存框架 | Django 文档 | Django 为什么要什么缓存? 为了减少服务器的计算开销 Django框架自带有一个强大的缓存系统,可以保存动态页面,因此不必为每个请求计算它们。为了方便,Django提供不同级别的缓存粒度:可以缓存特定视图的输出,可以只…

Web核心简介

简介 web&#xff1a;全球广域网&#xff0c;也称万维网(www)&#xff0c;能够通过浏览器访问的网站 JavaWeb&#xff1a;是用Java技术来解决相关web互联网领域的技术栈 JavaWeb技术栈 B/S架构&#xff1a;Browser/Server&#xff0c;浏览器/服务器架构模式&#xff0c;它的…

走迷宫----bfs再矩阵图里的应用模版

对于之前走迷宫的那个题 回忆一下dfs的代码 #include <bits/stdc.h> using namespace std; int a[110][110]; bool check[110][110]; int n,m; int ans1e9; int nxt[4][2]{{1,0},{0,-1},{-1,0},{0,1}}; void dfs(int x,int y,int step){if(xn&&ym){ansmin(ans,…

IntelliJ IDEA集成git配置账号密码

1 背景说明 刚使用IDEA,本地也安装Git,在提交和拉取代码的时候,总提示登录框,而且登录框还不能输入账号密码,只能输入登录Token。如下: 从而无法正常使用IDEA的Git功能,很苦恼。 2 解决方法 2.1 安装Git 进入官网地址 https://git-scm.com/,点击下载: 浏览器直接…

机器学习算法那些事 | 使用Transformer模型进行时间序列预测实战

本文来源公众号“机器学习算法那些事”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;使用Transformer模型进行时间序列预测实战 时间序列预测是一个经久不衰的主题&#xff0c;受自然语言处理领域的成功启发&#xff0c;transfo…

WPS制作甘特图

“ 甘特图&#xff08;Gantt chart&#xff09;又称为横道图、条状图&#xff08;Bar chart&#xff09;&#xff0c;通过条状图来显示项目、进度和其他时间相关的系统进展的内在关系随着时间进展的情况。” 设置基础样式 设置行高 设置宽度 准备基础数据 计算持续时间 …