string 底层模拟实现常用接口

news2024/11/16 1:47:30

目录

前言

什么是string? 为什么要学习使用string?string的优势?

因此,string类的成员变量也如图顺序表一样,如下图所示: 

构造函数 

拷贝构造 

 析构函数

size()  、capacity() 

 operator[] 

 迭代器 与 范围for

 reserve

push_back 

append 

operator += 

insert 

erase 

resize 

 operator = 

 swap

find 

substr 

clear() 

 string的重载运算符

string类的 流插入 和  流提取 

前提须知:

实现: 

 流插入

流提取 

完整代码:

扩容机制解读: 

getline() 


 

前言

本篇博客是从底层角度来模拟实现STL的string,在C++的学习过程中string是一个非常重要的知识点,在之前的介绍讲解中,我们了解到string有多个接口,且每个接口的功能和接收参数都不一样,所以本篇将会重点介绍和讲解几个重要接口的常用方式。

前情提要:C++:string相关内容的简单介绍-CSDN博客 

什么是string? 为什么要学习使用string?string的优势?

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

C++中有string类来提供各种各样的函数接口,大大提高了办事效率。

学过数据结构的朋又应该能够理解,string的本质其实就相当于是存放数据类型为char的顺序表。

换句话说,string是表示字符串序列的类。

因此,string类的成员变量也如图顺序表一样,如下图所示: 

private:
  char* _str;//指针,表示string开辟空间的地址
  size_t _size;//字符串长度
  size_t _capacity;//string开辟的空间的空间大小
构造函数 

 在使用string时,我们通常会发现在讲一串字符使用string修饰时,我们无需亲自为字符开辟空间,这是因为在底层的逻辑中,字符串的空间早就被string类使用构造函数开辟了。

string (const char*str = "")
    :_size(strlen(str))
{  
    _capacity = _size;
    _str = new char[_capacity + 1];
   strcpy(_str,str);
 
}
 
以上的代码设立了缺省值为空,表名了这是一个即是无参又是有参的一个构造函数。

设立缺省值const char*str = ""好处在:
当传递了一个无参的参数时,该构造函数会因为缺省值传输参数,但是参数是无效的,
所以再_size时就会得到长度为0,从而应发空间为0,
但是会因为 string s; 这种代码的存在,空间还是需要被开辟的,所以还是需要开辟空间。

所以 _capacity + 1表示无空间时进行开辟,表明存在,有空间时表示为delete的标识符,方便删除
拷贝构造 

 拷贝构造在之前的学习过程中,分为了深拷贝和浅拷贝。编译器自动生成的默认构造函数使用的是浅拷贝,浅拷贝的拷贝方式与取别名 & 的功能有些类似,单纯的拷贝,就连空间地址也是一模一样的完美复制,所以string类中的拷贝构造使用的是开创新空间进行存放的深拷贝

使用了深拷贝方式进行拷贝
string(const string& s)
{
  _str  = new char[s._capacity +1];
  strcpy(_str,s._str);
  _size = s._size;
  _capacity = s._capacity;
}

 深浅拷贝相关介绍:C++ : 类的简单介绍(五)————— 拷贝构造函数 & 函数传参 & 运算符重载-CSDN博客 

 析构函数
~string()
{
   delete[]_str; //空间的释放
   _str = nullptr;//指针的释放
   _size=_capacity =0;//内容的清空
}
size()  、capacity() 
size_t size()const//const是为了方便const对象使用的
{
  return _size;
}


size_t capacity()const
{
  return _capacity;
}
 operator[] 

operator[]在C++语言中是一种特殊的遍历方式,使用的是重载运算符[]和C语言中的数组遍历,结合出的一种全新遍历方式,在string中可以使用operator[]对字符串中的字符进行遍历,甚至是改变其中的字符:

而根据operator[]在string内部的使用情况operator[]需要分为两种,一种是const使用的一种是非const使用的,而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];
}
 迭代器 与 范围for

在某一些的编辑器中,迭代器的底层逻辑其实就是指针,两个分别指向字符串首字符和字符串末尾\0的指针 begin与end,所以在一些罗普大众的教材中,通常会直接把迭代器和指针挂钩,在它们看来,迭代器只不过是char换了个名字,然而功能还是和char类型指针一样的指针:

typedef char* iterator
iterator begin()
{
  return _str;
}
 
iterator end()
{
  return _str+size;
}

与operator[]相同的是,作为迭代器的iterator也有分为const使用和非const使用的类别: 

typedef const char* const_iterator //const使用的
const_iterator begin()const
{
  return _str;
}
 
const_iterator end()const
{
  return _str+size;
}

同时在迭代器是否是指针的问题上,我们需要认识范围for,范围for其实就是一种迭代器,在C++的底层,范围for使用的真是迭代器的代码进行运转:

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

而且,如果我们手搓了迭代器,且名字只要是迭代器的名字 iterator、  begin、  end   、const_iterator 范围for就可以使用我们手搓的迭代器

但是这几者中间名字对不上那范围for就对不上,用不了,只是所以范围for就是一种傻傻的替换并不会灵活变通的迭代器。

 reserve

作为string的内部扩容函数,在底层中常常被其他插入函数作为扩容机制调用,比如较常使用的插入函数:push_back、append等等。

void reserve(size_t n)
{
 
  if(n>_capacity)//扩容机制
  {
   char*tmp = new char[n+1];
//当然因为delete的问题,开辟空间需要额外开辟一个标识符进行使用,所以需要n+1
//这个n+1并不是给\0使用的而是给为了让delete辨认而多开辟的标识符使用的
 
   strcpy(tmp,_str);//把原来空间的内容放到新空间内部
   
   //delete的删除机制,删除原来的空间
   delete[]_str;
   _str = tmp;//指针和地址的赋予
   _capacity = n;//表名空间已经完成
  }
 
}
push_back 

push_back的主要作用是在字符串的尾部插入一个字符,增长字符串的长度,以及可能会扩大字符串所在空间的大小等等,同时需要注意:push_back的扩容机制是二倍扩容。

void push_back(char ch)
{
   //进行二倍扩容
   if(_size == _capacity)//如果长度和空间大小一样就需要扩容
     {
        reserve(_capiacty == 0? 4:2*_capacity);//同时需要考虑到长度和空间大小都是0
       //但是需要插入字符,所以需要进行空间的扩容
      }
  
   _str[_size] = ch;//ch表示插入的字符,_size表示插入的长度也表示需要插入的位置
   ++_size;
   _str[_size] = '\0';//在插入字符后面加上\0表示字符插入完成
 
}
append 

 append最常用的功能是在字符串尾部插入字符或者字符串,而且所以和push_back不同的是,它的扩容机制不能遵循二倍扩容,因为如果字符串的长度过长,使用二倍的空间可能不够用,所以append扩大的空间可能需要遵循字符串的长度来扩充 

void append(const char* str)
{  
   //扩容
   size_t len = strlen(str);
   if(_size + len > _capacity)//使用插入的长度和字符串长度相加 和空间大小对比
     {
         reserve(_size + len);//进行空间的扩大
      }
   
   strcpy(_str + _size , str);//在扩大空间后的原字符串尾部插入新的字符串
   _size + = len;//表名插入了字符串
 
}
operator += 

operator += 最常用的两个功能在底层的逻辑上就是调用了刚才写的push_back 和 append 

insert 

insert 在指定位置插入字符 或者字符串,而根据insert在string内部底层的方式和效率来看,insert的底层是需要进行数据的挪动,以此达到插入字符串的操作。

和string的其他插入函数一样,都具有扩容的功能的同时还有判断是否越界的功能 

插入字符功能的insert:
 
void insert(size_t pos, char ch)
{
   assert(pos <= _size);//进行越界机制的断言
   
   if(_size == _capacity)//判断是否需要扩容操作
   { 
     reserve(_capacity == 0? 4:2*_capacity);
    }
  
   size_t end = _size+1;//进行底层的数据移动操作
   while(end>pos)
   {
      _str[end]  = _str[end-1];
      --end;
    }
 
}


插入字符串功能的insert:
 
void insert(size_t pos, const char *str)
{
   assert(pos <= _size);//进行越界机制的断言
  
   szie_t len = sizeof(str); //获取插入字符串的长度
   
   if(_size + len == _capacity)//判断是否需要扩容操作
   { 
     reserve(_size + len);
    }
  
   size_t end = _size+len;//进行底层的数据移动操作
   while(end > pos + len -1)
   {
      _str[end]  = _str[end - len];
      --end;
    }

   strncpy(_str + pos,str,len);
   _size + len;
 
}

strncpy 函数介绍:strncpy-CSDN博客 

erase 

 erase的主要功能是在指定位置删除指定个数的字符。

erase分为两种删除,而erase的删除需要根据 npos进行判断,所以需要加入前置条件:npos ,在整个类的外面加上 const int string::npos = -1,与此同时在类的成员变量中加入public的变量 static const int pos;这样 npos才能够正常的使用

void erase(size_t pos,size_t len = npos)
{

    assert(pos<_size); 
    if(len == npos || len >= _size-pos);
    {
       _str[pos] = '\0';
       _size = pos;
    }
    else
    {   
      strcpy(_str + pos , _str+pos +len);
      _size -= len;
     
    }

}

earse的删除分为两种
1.全部删除:
对于全部删除满足的条件是指定删除的字符个数大于要剩下的长度或者是没有直接说明删除的字符个数,
这两种情况相当于在指定位置上的字符直接变成\0

代码:
if(len == npos || len >= _size-pos);
    {
       _str[pos] = '\0';
       _size = pos;
    }


2.区间删除:
区间删除,就是把这段区间后面的字符覆盖区间需要删除的空间位置,
可以使用strcpy进行拷贝,把需要删除的区间的首个字符地址上传,
然后把区间之后的字符串的首个字符上传

代码:
  else
    {   
      strcpy(_str + pos , _str+pos +len);
      _size -= len;
     
    }

关于npos :

  • npos是C++标准库中的一个常量,它代表了一个无效的或者不存在的位置。具体来说,npos是一个static成员变量,它的值是一个特别大的无符号整数,通常是-1。在字符串和容器类中,npos常常用于表示查找失败或者无效位置的情况。
  • 在字符串中,npos常常与find()函数一起使用。当find()函数无法找到指定的子串时,它会返回npos作为结果。例如,如果我们使用string类的find()函数查找一个不存在的子串,那么它会返回npos。
  • 在容器类中,npos常常与find()函数、rfind()函数、find_first_of()函数、find_last_of()函数等一起使用。这些函数在查找元素或者子串时,如果找不到匹配项,就会返回npos。
  • 总之,npos是一个表示无效位置的常量,在字符串和容器类中经常用于表示查找失败或者无效位置的情况。
resize 

在之前的学习中我们知道resize接口是用于调整字符串长度的,且分为三种情况进行使用:

一:指定的长度比字符串长度小,删除多余的部分

二:指定的长度比字符串大,但是在空间大小范围内,使用\0进行填补到相对因的指定长度

三:指定的长度比字符串大,且比空间大小大,需要进行扩容,且其余的指定长度位置的范围内的空缺地方用\0填补 

void resize(size_t n , char ch ='\0')
{
   
    if( n <= _size)//情况一
    {
       _str[n] = '\0';
       _size = n;
    }
    else
    {
       
       reserve(n);//需要扩容进入情况三不需要进入情况二
       for(size_t i = _size ; i < n; i++)
       {
          _str[i] = ch;
        
       }
       _str[n] = '\0';
       _size = n;
     }

}

在进入情况二和情况三之前先使用reserve进行检查,
因为reserve有一个检查机制,可以检查我们指定的长度是否大于空间大小

如果比空间大则扩容进入情况三,如果比空间小则不变进入情况二,
检查之后就开始情况2和情况3都有的共同操作:填充\0

 operator = 

operator= 的赋值过程其实就是一个深拷贝的过程,需要开辟单独的空间讲数据进行复制转移 

string & operatro=(const string& s)
{
   char*tmp = new char[s.capacity];
   strcpy(tmp , s._str);
   delete[];
   _str = tmp;
   _size = s._size;
   _capacity = s._capacity;
   return *this;
} 
 swap

默认的或者说直接用C++里的swap进行字符串和字符串之间的交换较为的复杂,因为需要调用深拷贝以及析构函数等等,且拷贝的过程中字符串的转移需要进行至少三次,也就是三次的拷贝,同时还需要利用析构函数解决中间用来存储字符串的变量,于是就有了string专用的swap:

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

直接利用C++的swap进行一个地址交换,但是交换的是成员变量和地址
find 

 find的主要功能是从指定的位置(默认是索引为0的位置)查找字符或者字符串,如果找到了返回字符的位置,找不到返回npos,默认是从0开始查找字符,,如果是查找字符串,则找到了返回字字符串首个字符的位置,找不到就返回npos

查找字符:
size_t find(char ch ,size_t pos = 0)
{
   assert(pos < _size);//确认查找的位置是否存在
   for(size_t i =pos; i < _size; i++)
   {
      if(_str[i] == ch)//进行对照查找
         return i;
    }

    return npos;

}

查找字符串:
size_t find(const char* sub ,size_t pos = 0)const
{
   assert(pos < _size);
 
   const char* p =strstr(_str +pos ,sub);//借用了strstr的功能

   if(p)
   {
      return p - pos;//使用地址-地址的方式得到索引下标位置
   }
   else
   {
      return npos;
   }

}

strstr函数相关介绍:strstr函数-CSDN博客 

substr 

substr的功能是从指定位置获取字符,并可以指定获取字符的个数,如果没有指定获取字符的个数,那么将会从指定的位置获取字符,获取到原字符串的结尾。  

string substr(size_t pos = 0;size_t len = pos)
{
   string sub;
   
   if(len == npos || len >= _size - pos)//获取指定的个数大于剩下长度时
   {
       for(size_t i =pos;i < _size;i++)
       {
          sub += _str[i];//sub从pos位置获取到字符串尾部
       }
   
   }
   else
   {
       for(size_t i = pos;i < pos + len;i++)//获取指定个数小于剩下长度时
       {
          sub += _str[i];//sub只能获取指定长度的字符
       }
   
   }
   return sub;

}
clear() 

clear的主要功能就是清除数据,但仅仅只是数据的清除,空间并不一定清理掉,也就空间不会被释放或者空间不会被销毁!只是清除了空间内部的数据。

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

}
 string的重载运算符

string类的 流插入 和  流提取 
前提须知:

string的流插入和流提取是指将string对象与输入输出流进行交互的过程。通过流插入,可以将string对象的内容插入到输出流中;而通过流提取,可以将输入流中的内容提取到string对象中。

流插入操作使用输出流对象(如cout)和插入运算符(<<),将string对象的内容插入到输出流中。例如,可以使用以下方式将一个string对象插入到输出流中:

#include <iostream>
#include <string>

int main() {
    std::string str = "Hello, world!";
    std::cout << str;  // 将str插入到输出流中
    return 0;
}

 流提取操作使用输入流对象(如cin)和提取运算符(>>),将输入流中的内容提取到string对象中。例如,可以使用以下方式将用户输入的内容提取到string对象中:

#include <iostream>
#include <string>

int main() {
    std::string str;
    std::cout << "请输入一个字符串:";
    std::cin >> str;  // 将用户输入的内容提取到str中
    std::cout << "您输入的字符串是:" << str << std::endl;
    return 0;
}

上述代码中,std::cin >> str; 将用户输入的内容提取到字符串变量str中,并通过std::cout输出。

实现: 
 流插入

ostream& operator<<(ostream& out,const string& s)
{
   for(auto ch :s)//借用范围for 把string类型的字符串中的字符一个个传入ch中
                  //再用C++的<<进行传输
   {
      out << ch;
   }

   return out;

}
流提取 

在进行流提取之前,我们要知道string类的流提取底层模拟实现不能和流插入一样调用C++中的流提取符号,因为C++的默认流提取 >> 底层使用的 cin 是一个遇到空格符号或者回车符号终止提取和读取的函数

所以假设我们需要使用流提取进行连续的读取字符串,那么按照程序员写字符串的习惯,当需要写多个连续的字符串时,大多数程序员会使用空格或者回车作为多个字符串的分割标记。

这也就会导致使用cin的流提取会出现错误!因为cin遇到空格和回车会终止提取!

同时,对于流提取而言,它的本质上是遇到空格和回车就会停下读取,所以流提取一般不用于具有空格符号的字符串

并且流提取还附带覆盖的功能,当一个string类型的变量内部具有字符串且在进行 cin >> "hello" >>s;这种操作时,s字符串内部的东西需要被cin>>从键盘或者其他设备中提取的数据覆盖。

于是,string的流提取模拟实现需要完成,解决连续读取操作,解决覆盖操作,解决遇到空格和回车符号暂停读取的操作

同时,string会进行一种扩容操作,这是流提取的扩容机制,所以为了防止流提取时的频繁扩容,我们需要建立流提取的防止扩容频繁的操作 

完整代码:

扩容机制解读: 
  • 设立了一个足够大的字符数组,用字符数组进行扩容的相关操作。
  • 第二个if是表示如果字符串较短,那么将会在字符串的后面加上\0,在交给string 类型的s 并且使用+=符号读取字符串的内容方便string空间的开辟
  • 而+= 符号会自动读取数组内部的字符串的大小,开辟相对因的空间,进行空间的一次性开辟操作!
  • 其中第一个if表示如果字符串太长的话,那就分几次对string类型对象s的空间进行开辟
  • 如果等于127则127放置\0并且将这127个字符给string类型的s 使用+=进行空间的开辟,随后i=0 进入插入的循环,进行第二次轮的插入操作和空间的开辟操作!
getline() 

getline()的功能其实和流提取相差不多,不过不同的是getline()可以读取空格符号,所以getline()一般用于读取带有空格符号的字符串。

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

   return in;

}

 

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

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

相关文章

C语言数据结构之二叉堆

愿你千山暮雪 海棠依旧 不为岁月惊扰平添忧愁 &#x1f3a5;前期回顾-二叉树 &#x1f525;数据结构专栏 期待小伙伴们的支持与关注&#xff01;&#xff01;&#xff01; 目录 前期回顾 二叉堆的概念及结构 二叉堆的创建 顺序表的结构声明 顺序表的创建与销毁 二叉堆的插入 …

selenium常用操作汇总

本文总结使用selenium进行web/UI自动化时&#xff0c;会用到的一些常用操作。 定位元素 driver.find_element_by_xpath()#1、绝对路径 2、元素属性 3、层级和属性结合 4、使用逻辑运算符 driver.find_element_by_id()#根据id定位&#xff0c;HTML规定id属性在HTML文档中必须是唯…

Mysql -- 约束

注意:约束是作用于表中字段上的,可以在创建表/修改表的时候添加约束. -- ------------------------------------------------------------------- 约束演示 ---------------------------------------------- create table user(id int primary key auto_increment comment 主键…

CorelDRAW Standard2024适合业余爱好者和家庭企业的图形设计软件

CorelDRAW Standard 2024是一款功能强大的矢量图形设计软件&#xff0c;专为图形爱好者、家庭用户、微型企业和学生们设计。该软件在Windows平台上运行&#xff0c;并提供了智能对象、布局、插图和模板等功能&#xff0c;帮助用户快速创建高质量的设计作品。 CorelDRAW Standa…

seq2seq翻译实战-Pytorch复现

&#x1f368; 本文为[&#x1f517;365天深度学习训练营学习记录博客 &#x1f366; 参考文章&#xff1a;365天深度学习训练营 &#x1f356; 原作者&#xff1a;[K同学啊 | 接辅导、项目定制]\n&#x1f680; 文章来源&#xff1a;[K同学的学习圈子](https://www.yuque.com/…

ssm+vue的农业信息管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。

演示视频&#xff1a; ssmvue的农业信息管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;ssm vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&…

备考银行科技岗刷题笔记(持续更新版)

银行考试计算机部分复习 IEEE 802.11的帧格式 1.1 IEEE 802.11是什么&#xff1f; 802.11是国际电工电子工程学会&#xff08;IEEE&#xff09;为无线局域网络制定的标准。目前在802.11的基础上开发出了802.11a、802.11b、802.11g、802.11n、802.11ac。并且为了保证802.11更…

npm install没有创建node_modules文件夹

问题记录 live-server 使用时 报错&#xff1a;live-server : 无法将“live-server”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。 npm install 安装 但是 这时npm install没有创建node_modules文件夹&#xff0c;只生成package-lock.json文件 方法一&#xff1a; 手…

NineData与OceanBase完成产品兼容认证,共筑企业级数据库新生态

近日&#xff0c;云原生智能数据管理平台 NineData 和北京奥星贝斯科技有限公司的 OceanBase 数据库完成产品兼容互认证。经过严格的联合测试&#xff0c;双方软件完全相互兼容、功能完善、整体运行稳定且性能表现优异。 此次 NineData 与 OceanBase 完成产品兼容认证&#xf…

软考70-上午题-【面向对象技术2-UML】-UML中的图1

一、图的定义 图是一组元素的图形表示&#xff0c;大多数情况下把图画成顶点、弧的联通图。 顶点&#xff1a;代表事物&#xff1b; 弧&#xff1a;代表关系。 可以从不同的角度画图&#xff0c;UML提供了13种图&#xff1a;&#xff08;只看9种&#xff09; 类图&#xff…

学习c语言:顺序表

一、顺序表的概念和结构 1.1 线性表 线性表&#xff08; linearlist &#xff09;是n个具有相同特性的数据元素的有限序列。线性表是⼀种在实际中⼴泛使⽤的数据结构&#xff0c;常⻅的线性表&#xff1a;顺序表、链表、栈、队列、字符串... 线性表在逻辑上是线性结构&#x…

【网站项目】096实验室开放管理系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

15-单片机烧录FreeTOS操作系统后,程序的执行流程

任务创建 1、在系统上电后&#xff0c;第一个执行的是启动文件由汇编语言编写的复位函数 通过复位函数来初始化系统的时钟&#xff0c;然后再执行__main,初始化系统的堆和栈&#xff0c;然后跳转到main函数 2、在main函数中可以直接进行任务创建操作 因为在FreeRTOS中会自动…

c++ primer plus 第十五章笔记 友元,异常和其他

友元类&#xff1a; 两个类不存在继承和包含的关系&#xff0c;但是我想通过一个类的成员函数来修改另一个类的私有成员和保护成员的时候&#xff0c;可以使用友元类。 class A {private:int num;//私有成员//...public: //...friend class B;//声明一个友元类 }class…

SpringBootWeb(接收请求数据,返回响应结果,分层解耦,Spring的IOCDI)【详解】

目录 一、接收请求数据 1. 接收表单参数 1.原始方式【了解】 2.SpringBoot方式 3.参数名不一致RequestParam 2.实体参数 1.简单实体对象 2.复杂实体对象 3.数组集合参数 4.日期参数 3. JSON参数 1.Postman发送JSON数据 2.服务端接收JSON数据 4. 路径参数(rest风格…

httprunner结合pytest的关键字

1. 通用关键字 可参考官方文档&#xff1a; Write Testcase - HttpRunner V3.x Docs 2. 特别关键字 2.1. 步骤step前置 2.1.1. setup_hook 关键源码 def setup_hook(self, hook: Text, assign_var_name: Text None) -> "RunRequest":if assign_var_name:sel…

【Python】新手入门:全局变量和局部变量的概念、区别以及用法

【Python】新手入门&#xff1a;全局变量和局部变量的概念、区别以及用法 &#x1f308; 个人主页&#xff1a;高斯小哥 &#x1f525; 高质量专栏&#xff1a;Matplotlib之旅&#xff1a;零基础精通数据可视化、Python基础【高质量合集】、PyTorch零基础入门教程&#x1f448;…

基于卷积神经网络的野外可食用植物分类系统

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长 QQ 名片 :) 1. 项目简介 本文详细探讨了一基于深度学习的可食用植物图像识别系统。采用TensorFlow和Keras框架&#xff0c;利用卷积神经网络&#xff08;CNN&#xff09;进行模型训练和预测&#xff0c;并引入迁移学习模型…

联立方程模型的可识别性的通俗解释

联立方程模型的可识别性&#xff0c;主要的解法是阶条件算法和秩条件算法&#xff0c;数学公式角度的解释就不讲了&#xff0c;参考下面的前人文献。 【计量经济学】联立方程模型-CSDN博客 说一下公式算法背后的通俗原理。 在计量经济模型中&#xff0c;比如 Y23*Xu中&#x…

springboot251基于springboot-vue的毕业论文管理系统

毕业论文管理系统设计与实现 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本毕业论文管理系统就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短…