C++基础(十二):string类

       这一篇博客,我们正式进入STL中的容器的字符串类的学习,C++标准模板库(STL)中的std::string类是一个用于表示和操作字符串的类。它封装了动态分配的字符数组,提供了丰富的成员函数来进行字符串的操作,例如拼接、查找、替换、比较等。std::string类还支持运算符重载,如+用于字符串拼接,==用于字符串比较,[]用于访问字符等。它简化了C风格字符串的处理,使得字符串操作更加安全和便捷。我们学习主要是从两个方面学习:1、如何使用它。2、底层的原理。3、模拟实现。

目录

一、为什么学习string类?

1.1 C语言中的字符串

二、标准库中的string类

2.1 string类(了解)

2.2 总结

2.3 string类的常用接口说明(最常用的接口)

2.3.1 string类对象的常见构造函数

2.3.2string类对象的访问及遍历操作

2.3.3 string类对象的容量操作

2.3.4. string类对象的修改操作

2.3.5  string类非成员函数

2.3.6 vs和g++下string结构的说明 

三、练习题

3.1 仅仅反转字母

3.2 找字符串中第一个只出现一次的字符

3.3 字符串里面最后一个单词的长度

3.4 验证一个字符串是否是回文

四、string类的模拟实现

4.1 实现一个简单的string =>深浅拷贝问题

4.2 浅拷贝

4.3 深拷贝


一、为什么学习string类?

1.1 C语言中的字符串

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

        在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。

二、标准库中的string类

2.1 string类(了解)

  1. 字符串是表示字符序列的类
  2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
  3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信 息,请参阅basic_string)。
  4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits 和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
  5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

2.2 总结

1. string是表示字符串的字符串类,它就是一个自定义类型,可以用它来实例化字符串类对象。

2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作成员函数以及描述这个字符串类属性的一些成员变量。

3. string在底层实际是:basic_string模板类的别名,typedef basic_string string;

4. 不能操作多字节或者变长字符的序列。

在使用string类时,必须包含#include头文件(#include<string>)以及using namespace std;

2.3 string类的常用接口说明(最常用的接口)

2.3.1 string类对象的常见构造函数

#include <iostream>
#include <string>       //必须要加这个头文件,因为标准库std它是分文件写的
using namespace std;    //string类的实现都在std标准命名空间下

int main()
{
   string  s1;            //1、实例化s1对象,无参构造函数
   string  s2("hello");   //2、实例化s1对象,带参构造函数
   string  s3(s2);        //3、拷贝构造函数
   string  s4(10,'a');    //4、带参的拷贝构造函数,字符串初始化为10个a

  
   string  s5="hello"; //注意:这里是先用字符串"world"构造出一个临时的字符串对象,然后用这个临时的对象再去拷贝构造s5这个对象!然后被编译器直接优化成一步:直接去构造s5对象!它和第2个是等价的!

   string  s6=s2;   //注意:这里看起来是赋值,其实是调用拷贝构造函数!
   
   cout<< s1 << endl;   //这里打印是空串,是因为C++STL中的string设计中默认会给字符串加上'\0'
   cout<< s2 << endl;
   cout<< s3 << endl;
   cout<< s4 << endl;
   cout<< s5 << endl;
   cout<< s6 << endl;


   s1 = s2;            //这里是赋值运算符重载(string类已经实现好的)
   cout<< s1 << endl;

  
   return 0;

}


/*************************字符串的插入***************************************/

int main()
{
   string s("12345");
   s.push_back('6');    //字符串尾部插入单个字符
   s.append("78");     //字符串尾部插入字符串

 下面是使用运算符重载的方式:
   s+='1';             //字符串尾部插入单个字符
   s+="2222";          //字符串尾部插入字符串
   cout<< s <<endl;


由此可见:字符串的插入使用+=运算符更加的方便!!推荐使用


   string s;
   s+= "zhang";
   s+='-';
   s+="子杰";
   cout<<s<<endl;


    return 0;

}

/****************************实现字符串转整型******************************/

int main()
{
   string s("12345");
   int val=0;
   for(int i=0; i<s.size(); i++)
   {
      //cout<<s[i]<<" ";  [ ]遍历字符串
      val*=10;
      val+=s[i]-'0'; //这里是字符相减,其实就是ASCII码相减,1的ASCII码为49,0的ASCII码为48
   }

   cout<<val<<endl;
   return 0;

}

/************************************************************************/


2.3.2string类对象的访问及遍历操作

 

 

 

字符串遍历方式1:

       for循环结合[ ]的运算符重载,就可以像C语言中遍历字符数粗的方式一样遍历这个字符串对象。这种方式也是使用最多的。

/*****************字符串的遍历方式1:for+operator[]下标***********************************/
#include <iostream>
#include <string>       
using namespace std;    

int main()
{
	string  s1("hello");
	s1 += ' ';
	s1 += "world";
	cout <<s1<< endl;

	//对字符串进行写操作
	for (int i = 0; i < s1.size(); i++)
	{
		s1[i] += 1;  
//这里可不是重载+=运算符,这里先结合左边的[]运算符重载,返回指定i位置的字符,然后将其加1.
	}
	 
	//对字符串进行读操作 
	for (int i = 0; i < s1.size(); i++)
	{
		cout << s1[i] << " " ;
	}
	cout << endl;

	return 0;
}

/************************************************************************/

 字符串遍历方式2:使用迭代器,迭代器是一个比较通用的对于容器类的遍历方法,在这里可以暂时将迭代器理解为一个字符指针。

/*****************字符串的遍历方式2:迭代器:像指针一样的东西(后面学的其他容器都有这种方式)************/
#include <iostream>
#include <string>       
using namespace std;

int main()
{
	string  s1("hello");
	s1 += ' ';
	s1 += "world";
	cout << s1 << endl;

	//对字符串进行写操作
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		*it += 1;
		++it;
	}

	//对字符串进行读操作
	 it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

 	//C++11之后,直接使用auto定义迭代器,让编译器推到迭代器的类型
   /*
   auto rit = s1.rbegin();
   while (rit != s1.rend())
   {
      cout << *rit << endl;
   }
   */

	return 0;
}

 字符串遍历方式3:C++11支持的范围for,其底层也是被编译器替换成迭代器。

/*****************字符串的遍历方式3:C++11支持的范围for****************/
#include <iostream>
#include <string>       
using namespace std;

int main()
{
	string  s1("hello");
	s1 += ' ';
	s1 += "world";
	cout << s1 << endl;


	//底层被编译器替换成迭代器
	for (auto ch : s1)
	{
		cout << ch << " ";
   }
	
	return 0;
}

/*****************************************************************/

总结:

        string的遍历方式一共有3种, for+[]、 begin()+end()、 范围for。
string遍历时使用最多的还是for+下标 或者 范围for(C++11后才支持),begin()+end()大多数使用在需要使用STL提供的算法操作string时,比如:采用reverse逆置string。

/*************************反向迭代器************************************/
#include <iostream>
#include <string>       
using namespace std;

int main()
{
	string  s1("hello");
	s1 += ' ';
	s1 += "world";
	cout << s1 << endl;

	//倒着遍历字符串:
	
	//对字符串进行写操作
	string::reverse_iterator rit = s1.rbegin();
	while (rit != s1.rend())
	{
		*rit += 1;
		++rit;
	}


	//对字符串进行读操作
	 rit = s1.rbegin();
	while (rit != s1.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
	return 0;
}
/*********************************************************************/

迭代器总结:

  1.  迭代器从方向上分为:正向迭代器和反向( rbegin()和rend() )迭代器;
  2.  迭代器从参数属性上分为:普通迭代器和常性(const)迭代器(不能通过迭代器修改数据);

2.3.3 string类对象的容量操作

 

 

/***********************字符串容量操作****************************/
#include <iostream>
#include <string>       
using namespace std;

int main()
{
	string  s1("hello");
	string  s2("**world**");

	cout << s1.size() << endl;
	cout << s2.size() << endl;

	cout << s1.length() << endl;
	cout << s2.length() << endl;

	cout << s1.capacity() << endl;
	cout << s2.capacity() << endl;

	s2 += "11111111";
	cout << s2.capacity() << endl;


	s1.clear();
	cout << s1 <<"   " << endl;
	cout << s1.capacity() << endl;


	return 0;
}

/*****************************************************************/

字符串在进行插入的时候,如果容量满了,他是会自动进行扩容的,按照1.5倍进行扩容。 但是扩容会带来一定的开销(它需要重新拷贝字符串)

/**********************************************************************/
int main()
{
		string s;
		size_t sz = s.capacity();
		cout << "making s grow:\n";
		for (int i = 0; i < 100; ++i)
		{
			s.push_back('c');
			if (sz != s.capacity())
			{
				sz = s.capacity();
				cout << "capacity changed: " << sz << '\n';
			}
		}
	
	return 0;
}

/*****************************************************************/

如何解决呢?这就是reverse() ,一开始便将需要开的内存空间设置好。(一般设置为整数)

/**********************************************************************/
int main()
{
	// 构建string时,如果提前已经知道string中大概要放多少个元素,可以提前将string中空间设置好
		string s;
		s.reserve(100);
		size_t sz = s.capacity();

		cout << "making s grow:\n";
		for (int i = 0; i < 100; ++i)
		{
			s.push_back('c');
			if (sz != s.capacity())
			{
				sz = s.capacity();
				cout << "capacity changed: " << sz << '\n';
			}
		}
	
	return 0;
}

/*****************************************************************/

利用reserve提高插入数据的效率,避免增容带来的开销。

#include <iostream>
#include <string>       
using namespace std;    


/**********************************************************************/
int main()
{
	string s("hello world");
	cout << s.size() << endl;       //11
	cout << s.capacity() << endl;  //15
	cout << endl;

	s.resize(5);
	cout << s.size() << endl;        //5   改变了size
	cout << s.capacity() << endl;    //15
	cout << endl;

	s.resize(20);
	cout << s.size() << endl;      //20   
	cout << s.capacity() << endl;  //31    空间扩容了,改变了capacity

	return 0;
}



/*****************************************************************/

注意:

  1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一 致,一般情况下基本都是用size()。
  2. clear()只是将string中有效字符清空,不改变底层空间大小。
  3. resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(size_t n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变有效元素个数时,如果是将元素个数增多,可能会改变底层容量的大小(进行扩容),如果是将元素个数减少,底层空间总大小不变。
  4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于 string的底层空间总大小时,reserve不会改变容量大小。

2.3.4. string类对象的修改操作

 

/*************************字符串的插入***************************************/
int main()
{
	string str;
	str.push_back('1');                 // 在str后插入字符'1'
	str.append("hello");               // 在str后追加一个字符"hello"
	str += 'w';                       // 在str后追加一个字符'w'   
	str += "orld";                   // 在str后追加一个字符串"orld"
	cout << str << endl;            //调用的是string重载的operator<<(这里看成的是自定义类型string对象)
	cout << str.c_str() << endl;   // 直接输出const char *字符串    (这里看成的是基本数据类型)




	//获取字符数组的首地址,用C字符串的形式遍历
	const char* s = str.c_str();
	while (*s)
	{
		cout << *s ;
		++s;
	}
	cout << endl;



	return 0;
}

/*****************************************************************/

 

 

/***************************=获取文件的后缀****************************/
int main()
{
	// 获取file的后缀
	string file("string.cpp");

	size_t pos = file.rfind('.');
//rfind 方法从字符串的右端开始查找 '.' 字符的位置。如果找到,返回该字符的位置索引。这里 pos 会被赋值为 6

	string suffix(file.substr(pos, file.size() - pos));
//substr 方法从 pos 位置开始截取字符串,一直到字符串末尾。file.size() - pos 计算的是从 pos 位置开始到字符串末尾的字符数为4。在这个例子中,suffix 将被赋值为 ".cpp"。
	cout << suffix << endl;
	return 0;
}

/*****************************************************************/
/*****************取出url中的域名************************************/
	// npos是string里面的一个静态成员变量
	// static const size_t npos = -1;
int main()
{
	string url("http://www.cplusplus.com/reference/string/string/find/");
	cout << url << endl;
	size_t start = url.find("://");
	if (start == string::npos)     //证明没找到
	{
		cout << "invalid url" << endl;
		return;
	}

	start += 3;                  //此时start为7

	size_t finish = url.find('/', start);
从 start 位置开始查找字符 '/' 在 url 中的位置,并将结果赋值给变量 finish。在这个例子中,finish 将被赋值为 22

	string address = url.substr(start, finish - start);
使用 substr 方法从 start 位置开始截取子字符串,长度为 finish - start。在这个例子中,address 将被赋值为 "www.cplusplus.com"。

	cout << address << endl;

    return 0;
}
/*****************删除url的协议前缀**********************************/
int main()
{
    string url("http://www.cplusplus.com/reference/string/string/find/");
 	pos = url.find("://");  // 查找"://"在字符串url中的位置,并将结果赋值给变量pos
	url.erase(0, pos + 3); // 从字符串url中删除从位置0到位置pos + 3的所有字符
	cout << url << endl;

   return 0;
}

注意:

1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般 情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。

2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好。

2.3.5  string类非成员函数

2.3.6 vs和g++下string结构的说明 

 注意:下述结构是在32位平台下进行验证,32位平台下指针占4个字节。

vs下string的结构:

      string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:

  1. 当字符串长度小于16时,使用内部固定的字符数组来存放;
  2. 当字符串长度大于等于16时,从堆上开辟空间;
union _Bxty
{ 
// storage for small buffer or pointer to larger one
   value_type _Buf[_BUF_SIZE];
   pointer _Ptr;
   char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx;

这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内 部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。 其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量 最后:还有一个指针做一些其他事情。故总共占16+4+4+4=28个字节。

g++下string的结构

       G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:

  1. 空间总大小
  2. 字符串有效长度
  3. 引用计数
struct _Rep_base
{
    size_type      _M_length;
    size_type      _M_capacity;
    _Atomic_word   _M_refcount;
};

指向堆空间的指针,用来存储字符串。

三、练习题

3.1 仅仅反转字母

class Solution {
public:
    bool isLetter(char ch)
   {
      if(ch >= 'a' && ch <= 'z')
          return true;
      if(ch >= 'A' && ch <= 'Z')
          return true;
      return false;
 }
 string reverseOnlyLetters(string S)
 {
     if(S.empty())
     return S;
 
     size_t begin = 0, end = S.size()-1;
     while(begin < end)
     {
          while(begin < end && !isLetter(S[begin]))
          ++begin;
 
          while(begin < end && !isLetter(S[end]))
           --end;
          swap(S[begin], S[end]);   //c++模板实现好的交换函数
          ++begin;
          --end;
     }


      return S;
 }

3.2 找字符串中第一个只出现一次的字符

class Solution 
{
public:

      int firstUniqChar(string s) 
      {
 
         // 使用哈希思想统计每个字符出现的次数
         int count[26] = {0};
         int size = s.size();
         for(int i = 0; i < size; ++i)
         count[s[i]-'a'] ++;
 
         // 按照字符次序从前往后找只出现一次的字符
         for(int i = 0; i < size; ++i)
         if(1 == count[s[i]-'a'])
         {
              return i;
         }
 
          return -1;
      }
};

3.3 字符串里面最后一个单词的长度

#include<iostream>
#include<string>
using namespace std;
int main()
{
   string s;
    // 不要使用cin>>s,因为会它遇到空格就结束了
   // while(cin>>s)
   while(getline(cin, s))
   {
      size_t pos = s.rfind(' ');
      cout<<s.size()-(pos+1)<<endl;
  }

    return 0;
}

3.4 验证一个字符串是否是回文

class Solution {
public:
 bool isLetterOrNumber(char ch)
{
     if((ch >= '0' && ch <= '9')|| 
        (ch >= 'a' && ch <= 'z')||
        (ch >= 'A' && ch <= 'Z'))
     {
         return true;
     }

    return false;
 
 bool isPalindrome(string s)
 {
     // 先小写字母转换成大写,再进行判断
    for(auto& ch : s)
    {
        if(ch >= 'a' && ch <= 'z')
        ch -= 32;
    }
 
 int begin = 0, end = s.size()-1;
 while(begin < end)
 {
        while(begin < end && !isLetterOrNumber(s[begin]))
        ++begin;
 
        while(begin < end && !isLetterOrNumber(s[end]))
        --end;
 
       if(s[begin] != s[end])
       {
          return false;
       }
      else
       {
 
         ++begin;
         --end;
       }
 }
 
     return true;
 }
};

四、string类的模拟实现

      上面已经对string类进行了简单的介绍,只要能够正常使用即可。在面试中,面试官总喜欢面试模拟实现string类,最主要是实现string类的构造、拷贝构造、赋值运算符重载以及析构函数。

4.1 实现一个简单的string =>深浅拷贝问题

/********************************string类的模拟实现********************************/
/*string对象中存储指针,指针指向的数组中存储字符(字符数组),字符数组的最后必须保留'\0'**************************/

//面试题:实现一个简单的string =>深浅拷贝问题


#include <iostream>     
using namespace std;

class String
{
public:

	/*
	String()                                 //无参构造函数
		:_str(new char[1])   
	{
		_str[0] = '\0';                     //即使是空字符串,C++默认自动会存储一个'\0'
	}

	String( char* str)                      //带参构造函数
		:_str(new char[strlen(str)+1])    //在堆上开辟存储字符串的空间,C++默认自动会存储一个'\0'
	{
		strcpy(_str, str);               //将代码区(常量区)的字符串拷贝到堆区,这样就可以对字符串进行修改!
	}
	*/



	/*全缺省的默认构造函数*/
	String(char* str="")                     //有一个'\0'                      
		:_str(new char[strlen(str) + 1])    //在堆上开辟存储字符串的空间,C++默认自动会存储一个'\0'
	{
		strcpy(_str, str);               //将代码区(常量区)的字符串拷贝到堆区,这样就可以对字符串进行修改!
	}

	//自己实现的深拷贝的拷贝构造函数 :String s2(s1)
	String(const String& s) 
		:_str(new char[strlen(s._str) + 1] )          //1、开辟和s1同样大小的空间
	{
		strcpy(_str, s._str);                        //2、将s1的数据拷贝到s2
	}


	//自己实现的深拷贝的赋值运算符重载operator=()    s1=s3;
	String& operator=(const String& s)
	{
		if (this != &s)   //防止出现自己给自己赋值 s1=s1;
		{
			char* tmp = new char[strlen(s._str) + 1];    //1、开辟和s3同样大小的空间
			strcpy(tmp, s._str);                        //2、将s3的数据拷贝到tmp所指空间
			delete[] _str;                             //3、释放原来的s1的空间,防止内存泄漏
			_str = tmp;                               //4、修改原来的s1指针的指向
		}

		return *this;   //支持连续赋值
	}

	char& operator[](size_t i)
	{
		return _str[i];
	}


	~String()                         //析构函数
	{
		delete[] _str;
		_str = nullptr;
	}


	size_t size()
	{
		return strlen(_str);
	}

	

private:
	 char* _str;


};

浅拷贝问题:

说明:如果String类没有显式定义其拷贝构造函数与赋值运算符重载,此时编译器会合成默认的,当用s1构造s2时,编译器会调用默认的拷贝构造。最终导致的问题是,s1、s2共用同一块内存空间,在释放时同一块空间被释放多次而引起程序崩溃,这种拷贝方式,称为浅拷贝。

4.2 浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共 享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一不想分享就你争我夺,玩具损坏。

可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父母给每个孩 子都买一份玩具,各自玩各自的就不会有问题了。

4.3 深拷贝

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

至此,这一讲内容介绍完毕,内容简单,星光不问赶路人,加油吧,感谢阅读,如果对此专栏感兴趣,点赞加关注! 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

身边的故事(十五):阿文的故事:再消失

物镜人非&#xff0c;沧海桑田。像我们这些普通的凡人&#xff0c;哪有什么试错的机会&#xff0c;每走一步都是如履薄冰&#xff0c;小心谨慎&#xff0c;错一步可能就会万劫不复。唉&#xff0c;如果...唉...哪有什么如果... 阿文的房子很快装修完成&#xff0c;入新房那天就…

世界商用飞机机型大全-使用Java抓取FlightAware后的答案

目录 前言 一、数据说明 1、实时航班飞机机型数据 2、网页结构分析 二、使用Java进行信息抓取 1、定义页面PageVO对象 2、爬取属性定义 3、启动信息抓取组件 三、成果分析 1、商业飞行的飞机机型的种类 2、飞机种类排名前十名 3、航班数排名后十名 4、看中国国产大飞…

Typora篇-忍痛开启

语雀专业会员即将到期, 我看着99元的学费款, 我决定重新用回Typora。 虽然里面有一些文件但是我还是舍不得ಥ_ಥ 99元巨款。 下面开启我的Typora整活历程&#xff0c; 大家有什么好用的插件快捷方式一起来分享啊。

Profibus转Modbus模块连SmartPLC接汇川630伺服案例

一、环境&#xff1a;Modbus转Profibus模块&#xff08;XD-MDPB100)是一种通讯协议转换器&#xff0c;能够实现Modbus 协议与Profibus-DP协议的信息共享。汇川630伺服作为一种先进的运动控制设备&#xff0c;其平稳性和准确性获得了充分肯定。本文将详细分析怎么使用Profibus转…

U盘管理软件有哪些?3款好用的软件亲测有效!

在数字化办公与数据交换日益频繁的今天&#xff0c;U盘作为便携的存储设备&#xff0c;其重要性不言而喻。 然而&#xff0c;U盘的使用也带来了数据泄露、病毒感染等安全隐患。为了有效管理U盘&#xff0c;确保数据安全与合规性&#xff0c;市场上涌现出了众多U盘管理软件。 小…

代码随想录(day1)二分法

if语句的基本语法 if 要判断的条件: 条件成立的时候&#xff0c;要做的事举例&#xff1a; if nums[middle]<target:leftmiddle1 while语句的基本语法&#xff1a; while 判断条件(condition)&#xff1a;执行语句(statements)举例&#xff1a; while left<right:midd…

ctfshow web入门 nodejs web334--web337

web334 有个文件下载之后改后缀为zip加压就可以得到两个文件 一个文件类似于index.php 还有一个就是登录密码登录成功就有flag username:ctfshow password:123456因为 return name!CTFSHOW && item.username name.toUpperCase() && item.password passwor…

tkinter给按钮设置背景图片

tkinter给按钮设置背景图片 效果代码 效果 代码 import tkinter as tk from PIL import Image, ImageTk# 创建主窗口 root tk.Tk() root.title("按钮背景图片示例")# 加载图片 image Image.open("new.png") photo ImageTk.PhotoImage(image)# 创建按钮…

谷歌云 | Gemini 大模型赋能 BigQuery 情感分析:解码客户评论,洞悉市场风向

情感分析是企业洞察客户需求和改进产品服务的重要工具。近年来&#xff0c;随着自然语言处理 (NLP) 技术的飞速发展&#xff0c;情感分析变得更加精准高效。Google 推出的 Gemini 模型&#xff0c;作为大型语言模型 (LLM) 的代表&#xff0c;拥有强大的文本处理能力&#xff0c…

day02_员工管理

文章目录 新增员工需求分析和设计代码开发功能测试代码完善录入的用户名已存在&#xff0c;抛出异常后没有处理新增员工的时候&#xff0c;创建人id和修改人id设置为了固定值ThreadLocal&#xff08;面试题&#xff09; 分页查询问题解决 启用禁用员工账号需求和分析代码设计 编…

腾讯发布2024大模型十大最新趋势!

近日&#xff0c;在2024世界人工智能大会上&#xff0c;腾讯正式发布了《2024大模型十大趋势——走进“机器外脑”时代》报告。目前&#xff0c;这一报告正在AI产业界各大社群快速传播。 报告中&#xff0c;腾讯研究院试图通过10个关键性的趋势&#xff0c;去理解全世界范围内正…

【React Hooks原理 - useCallback、useMemo】

介绍 在实际项目中&#xff0c;useCallback、useMemo这两个Hooks想必会很常见&#xff0c;可能我们会处于性能考虑避免组件重复刷新而使用类似useCallback、useMemo来进行缓存。接下来我们会从源码和使用的角度来聊聊这两个hooks。【源码地址】 为什么要有这两个Hooks 在开始…

HBuilder X 小白日记03-用css制作简单的交互动画

:hover选择器&#xff0c;用于选择鼠标指针浮动在上面的元素。 :hover选择器可用于所有元素&#xff0c;不只是链接 :link选择器 设置指向未被访问页面的链接的样式 :visited选择器 用于设置指向已被访问的页面的链接 :active选择器 用于活动链接

观察者模式(Observer Pattern)

观察者模式&#xff08;Observer Pattern&#xff09; 定义 观察者模式定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时&#xff0c;会通知所有观察者对象&#xff0c;使它们能够自动更新自己。别名&#xff1…

AI语言处理的双刃剑:Tokens令牌化技术解析

生成式人工智能模型&#xff0c;如GPT-4o&#xff0c;采用基于Transformer架构的复杂处理方式&#xff0c;这与人类处理文本的方式存在明显差异。这些模型依赖于一种称为“令牌化”的过程&#xff0c;将文本分解为更小的片段&#xff0c;称为“令牌”&#xff0c;以便更有效地处…

BP神经网络的实践经验

目录 一、BP神经网络基础知识 1.BP神经网络 2.隐含层选取 3.激活函数 4.正向传递 5.反向传播 6.不拟合与过拟合 二、BP神经网络设计流程 1.数据处理 2.网络搭建 3.网络运行过程 三、BP神经网络优缺点与改进方案 1.BP神经网络的优缺点 2.改进方案 一、BP神经网络基…

XDMA原理学习(1)——DMA技术详解

目录 一、什么是DMA&#xff1f;为什么需要DMA&#xff1f; 二、DMA分类 2.1 Block DMA 2.2 Scatter-Gather DMA 2.3 Ring buffer DMA 三、实际案例 3.1 STM32微处理器 3.1.1 Block DMA 3.1.2 Scatter-Gather DMA 3.1.3 使用场景举例&#xff1a; 3.1.4 配置与实现 …

香橙派OrangePi AIpro测评:我的高性能AI开发板实操

香橙派OrangePi AIpro测评&#xff1a;高性能AI开发板的实际应用与操作指南 前言 在物联网和人工智能领域飞速发展的背景下&#xff0c;开发板作为硬件开发的重要工具&#xff0c;越来越受到开发者的青睐。香橙派OrangePi AIpro因其强大的性能和丰富的接口&#xff0c;成为了…

嵌入式Linux系统编程 — 7.4 fork、vfork函数创建子进程

目录 1 父进程与子进程概念 2 fork创建子进程 3 系统调用 vfork()函数 4 vfork与 fork函数如何选择 1 父进程与子进程概念 进程与子进程是操作系统中的一个基本概念&#xff0c;用于描述进程之间的层级关系。下面是对这一概念的简要说明&#xff1a; 父进程&#xff1a;在…

jmeter-beanshell学习6-beanshell生成测试报告

前面写了各种准备工作&#xff0c;内容组合用起来&#xff0c;应该能做自动化了&#xff0c;最后一步&#xff0c;生成一个报告&#xff0c;报告格式还是csv 报告生成的路径和文件&#xff0c;在用户参数写好&#xff0c;防止以后改路径或者名字&#xff0c;要去代码里面改。以…