C++ —— string类(上)

目录

string的介绍

string类功能的使用介绍

constructor —— 构造

介绍使用(1)(2)(4) :构造、拷贝构造、带参构造

 介绍(3):拷贝string类对象的一部分字符,从pos位置开始,拷贝len个字符

 npos

介绍(5):取字符串的前n个字符拷贝

介绍(6) :连续n个C字符初始化

介绍(7):用迭代器区间构造

destructor —— 析构

operator= —— 赋值重载

string类对象的容量操作

size()  length()    max_size()

capacity()

reserve() 和 resize()

clear()

empty()

遍历及访问string所需的一些接口

operator[] —— []运算符重载

iterator —— 迭代器 (正向)

begin()

end()

reverse_iterator —— 反向迭代器

rbegin和rend

const修饰的迭代器

范围for

auto

 typeid().name()


string的介绍

string严格来说是属于C++标准库的,不是属于STL(标准模板库)。

STL:

我们看到里面其实是没有string的,string产生的比STL较早的,在头文件中,会单独看到一个string的头文件。C++文档

所谓的string其实就是一个字符串 ,它要解决的就是要管理字符串。(日常当中字符串是很多的,如:身份证号码,家庭地址,电话号码等等)

打开string头文件可以看到:

 有4种,这个与所谓的编码有关系,这里我们先看string:

由于编码的原因,string是typedef出来的,它的原生类型是:

//原生类型
basic_string<char>

 除了char,还有其他字符的类型(编码的原因)

//原生类型
basic_string<char16_t>

//原生类型
basic_string<char32_t>

basic_string<wchar_t>

注意:basic_string其实是一个模板,所以string底层还是一个模板。

 所以,string是一个管理字符串的类。

string的介绍:

总的来说,string是一个对象,是用一个字符的顺序表实现的(也就是字符数组实现的),所以它就是一个字符顺序表(或字符数组),它可以动态的增长。(可以主要兼容UTF-8)

 string底层大致成员变量如下:

class string
{
public:
   /*成员函数(功能接口)*/
private:
	char* str;   //指针指向堆上的空间,这些空间存储对应字符串
	size_t size;   //有效字符个数
	size_t capacity;  //容量
};

下面将介绍string类的常用接口说明。 

string类功能的使用介绍

string主要包含接口如下:

我们要使用string类要记得包含一个头文件:

#include <string>   //C++

//C语言的是
#include<string.h>    //注意两者区分

constructor —— 构造

string类对象的常见构造
(constructor)函数名称功能说明
string()构造空的string类对象,即空字符串
string(const char* s) 用C-string来构造string类对象
string(size_t n, char c)string类对象中包含n个字符c
string(const string&s) 拷贝构造函数

但string构造总的来说是有七种构造方式的:

我们来看C++98版本的,string它有七种构造方式 ,下面来介绍一下:

default (1)	
string();        //无参构造(也是默认构造,构造一个空字符串,长度为0个字符)

copy (2)	
string (const string& str);    //拷贝构造

substring (3)	
string (const string& str, size_t pos, size_t len = npos);   //子串构造函数(复制str中,从字符位置pos开始,到len位置字符的部分)

from c-string (4)	
string (const char* s);    //带参构造(用一个常量字符串构造)

from sequence (5)	
string (const char* s, size_t n);    //拷贝前n个字符初始化

fill (6)	
string (size_t n, char c);    //连续n个C的字符去初始化

range (7)	
template <class InputIterator>
string  (InputIterator first, InputIterator last);    //迭代器区间初始化
介绍使用(1)(2)(4) :构造、拷贝构造、带参构造

我们先来介绍使用(1)(2)(4):

(string重载了流插入、流提取)

#include<iostream>
#include<string>

using namespace std;


int main()
{
	string s1;           //默认构造
	string s2("12345");  //带参构造
	string s3(s2);		 //拷贝构造


	//string也重载了流插入流提取(>>,<<)
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;

	cin >> s1;
	cout << s1 << endl;

	return 0;
}

 介绍(3):拷贝string类对象的一部分字符,从pos位置开始,拷贝len个字符
substring (3)	
string (const string& str, size_t pos, size_t len = npos);   //拷贝构造的一个变形

//也就是拷贝一部分字符,从pos位置开始,拷贝len个字符
//接着上面演示的构造代码
int main()
{
	string s1;           //默认构造
	string s2("1234567");  //带参构造
	string s3(s2);		 //拷贝构造


	//string也重载了流插入流提取(cout,cin)
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;

	cin >> s1;
	cout << s1 << endl;

	string s4(s2, 2, 4);  //这里就是让s4去拷贝s2的下标为2的位置开始,拷贝4个字符
	cout << s4 << endl;

	return 0;
}

我们观察输出结果,看到从3开始拷贝四个字符到6,3456四个字符就到s4里去了。若此时这里给的字符超过字符串长度,会不会报错呢?

string s2("1234567"); 
string s4(s2, 2, 15);   //让它从pos=2开始拷贝15个字符

我们观察结果,是没有报错的:

文档中: 

 

也就是说string太短了,不够len个字符,它就直到它结束为止(有多少拷贝多少 );这里还说或者为npos也是拷到它的结束。

第三个构造它的len是有缺省值npos的,如果不传第三个参数len,那么它就用这个缺省值,直到拷贝它的结束为止。

string s2("1234567"); 
string s4(s2, 2);
cout<<s2<<endl;
cout<<s4<<endl;

运行结果:

 下面对npos介绍一下

 npos

npos是一个string的const静态的成员变量,它可以直接在类里面用,若在外面用的时候,需要指定类域。

npos的值真实的值其实不是-1,在这里-1存的就是补码(-1补码是全1) ,这里又赋值给size_t类型,size_t是unsigned int(无符号整型),-1就会变成整型最大值。

所以这里npos要表达的是:从pos位置取len个字符,如果len给npos,就是要取整型的最大值,

字符串不可能有这么长(字符串中遇到\0会停止),且又给了整型的最大值了,就是要让它拷贝到字符串的结束。

介绍(5):取字符串的前n个字符拷贝
string (const char* s, size_t n);    //第(4)个(带参构造)的变形

//取字符串的前n个字符拷贝

 

也就是取一个字符串的前n个字符初始化。

测试一下: 

int main()
{
	string s6("hello world", 5);
	cout << s6 << endl;
	return 0;
}

 运行结果:

介绍(6) :连续n个C字符初始化
string (size_t n, char c);    //n个C字符初始化

也就是连续n个C的字符初始化,例如:'X'就是一个C字符。

int main()
{
	string s7(10, 'X');    //用10个X字符初始化
	cout << s7 << endl;
	return 0;
}

运行结果: 

介绍(7):用迭代器区间构造
int main()
{
	string s0("Initial string");
	string s8(s0.begin(), s0.begin() + 7);  //迭代器区间构造
	cout <<"s0: "<< s0 << endl;
	cout <<"s8: " << s8 << endl;
}

运行结果:

可以看到s8中用了s0起始位置开始的7个字符初始化了 。

destructor —— 析构

string的析构函数会自动调用,string的底层是一个动态开辟的数组,构造和析构函数是自动调用的(类和对象这里有介绍)。

operator= —— 赋值重载

赋值运算符重载也是默认成员函数(类和对象有介绍)。

string (1)	
string& operator= (const string& str);  //支持string的赋值

c-string (2)	
string& operator= (const char* s);      //支持char*的赋值

character (3)	
string& operator= (char c);             //支持一个字符的赋值

 测试:

int main()
{
	string s1;           
	string s6("hello world", 5);

	s1 = s6;
    //本质就是调用了operator=
	cout << s1 << endl;

	s1 = "abcde";
	cout << s1 << endl;

	s1 = 'x';
	cout << s1 << endl;

	return 0;
}

运行结果:

string类对象的容量操作

函数名称功能说明
size返回字符串有效字符长度
length返回字符串有效字符长度
max_size获取最大长度(能最大开多长)
capacity返回空间容量的大小
empty检查字符串释放为空串,若是空串返回true,否则返回false
clear清空有效字符
reserve为字符串预留空间
resize将有效字符的个数改成n个,多出的空间用字符c填充

size()  length()    max_size()

size()和 length()它们两个功能上完全一致,只是一般size用的比较多,是为了与其他容器接口保持一致。

这里拿size()来说,string提供了一个size的接口,它是用来获取string的长度(有多少个字符)。

 max_size()是用来获取它最大的长度,就是它最大能开多长

int main()
{
	string s("FFDUST");
	cout << s.length() << endl;
	cout << s.size() << endl;

	cout << s.max_size() << endl;
}

capacity()

 capacity()接口也就是返回该string当前空间容量的大小是多少

int main()
{
	string s("FFDUST");

	cout << "s的空间大小为:"<<s.capacity() << endl;
}

 

(注意它和size,length接口的区别)

这里还要注意容量扩容问题(每个编译器处理扩容几倍不一致),观察下面程序:

int main()    
{
	string s;
	size_t sz = s.capacity();
	cout << "capacity : " << sz << '\n';
	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;
}

 VS下结果:

 所以VS下,扩容是先扩2倍,再后面扩1.5倍。

reserve() 和 resize()

 reserve()接口就是先给该对象预留多少空间,不改变有效元素个数,若reserve的参数小于string的底层空间总大小时,reserve不会改变容量大小(这里VS下不会缩容,其他编译器可能会缩容,C++规定没有约束)。

int main()
{
	string s("FFDUST");
	cout << "s的空间大小为:" << s.capacity() << endl;
	s.reserve(100);
	cout << "s的空间大小为:" << s.capacity() << endl;
	s.reserve(1);
	cout << "s的空间大小为:" << s.capacity() << endl;

	return 0;
}

VS这里会做整数倍对齐,所以这里capacity可能会比传的参数较大一些。

注意:谈string容量的时候默认不包含\0的,所以开空间实际上会多1位用来放\0,但capacity返回的是不带\0的。

reserve的用法:提前开空间,避免扩容,提高效率。

int main()
{
	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;
}

这里就是给s预留了足够大的空间,再插入时候不会发生扩容。 

resize()接口(将有效字符的个数改成n个,多出的空间用字符c填充),相当于扩容插入。

这里它是有两个版本的:resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0(也就是\0)来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间。注意的是:resize若比size小的话会删除数据(缩容不确定),比capacity大就会扩容。

int main()
{
	string s("ffdust");
	cout << s << endl;
	cout << "s的空间大小为:" << s.capacity() << endl;
	s.resize(100);      //默认插入\0
	cout << s << endl;
	cout << "s的空间大小为:" << s.capacity() << endl;
}

int main()
{
	string s("ffdust");
	cout << s << endl;
	cout << "s的空间大小为:" << s.capacity() << endl;

	s.resize(100,'1');        //用字符1填充多出的元素空间,注意这里会扩容
	cout << s << endl;
	cout << "s的空间大小为:" << s.capacity() << endl;

}

 

 clear()

clear()也就是清空有效字符,一般严格来说不会清理掉容量,只是把数据清理掉了

int main()
{
	string s("ffdust");
	cout << s << endl;
	cout << &s << endl;
	cout << "s的空间大小为:" << s.capacity() << endl;
	s.clear();
	cout << s << endl;
	cout << &s << endl;
	cout << "s的空间大小为:" << s.capacity() << endl;

	return 0;
}

 运行结果:

 empty()

empty()接口时用来判空的(有没有字符),也就是检测字符串是否为空串,若是返回true,否则返回false。

int main()
{
	string s("ffdust");
	cout << s << endl;
	if (s.empty())
	{
		cout << s << ",是空串" << endl;
	}
	else
	{
		cout << s << ",不是空串" << endl;
	}
	cout << "s的空间大小为:" << s.capacity() << endl;
	s.clear();
	cout << s << endl;
	if (s.empty())
	{
		cout << s << ",是空串" << endl;
	}
	else
	{
		cout << s << ",不是空串" << endl;
	}
	cout << "s的空间大小为:" << s.capacity() << endl;

	return 0;
}

运行结果:

遍历及访问string所需的一些接口

函数功能功能说明
operator[]返回pos位置的字符,const string类对象也以调用
begin()+end()begin获取第一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
rbegin()+rend()rbegin获取最后一个字符迭代器 + rend获取第一个字符前一个位置的迭代器
范围forC++11支持更简洁的范围for的新遍历方式

遍历string有三种方式:

  1. 下标+[]
  2. 迭代器
  3. 范围for

operator[] —— []运算符重载

string重载operator[],访问pos位置的字符,返回这个字符的引用。 (可以读和修改)

string重载了[],这样就可以让string当成数组一样使用下标访问。string底层意思大致理解是有一个指针,指针指向一个数组,是动态开辟的,它还要有size和capacity,重载operator[],就可以让它去访问第i个位置的字符:

class string
{
public:
    char& operator[](size_t i)
    {
	return _str[i];           //返回第i字符的引用(这里引用返回,不只是可以减少拷贝,还能让它被修改)
    }
private:
	char* str;
	size_t size;
	size_t capacity;
};

使用如下:

int main()
{
	string s6("hello world");
	cout << s6 << endl;

	s6[5] = 'a';     //将下标为5的字符修改成a
	cout << s6 << endl;
	return 0;
}

运行结果:

就相当于用数组一样,可以获取这个位置的字符,也可以修改这个位置的字符,但这里的底层就是函数调用,调用的operator[],注意:这里越界了可以检查出来的。 

char& operator[](size_t i)
{
    assert(i < _size);   //这里断言,只要越界了就直接报错
	return _str[i];
}

我们看到这里15位置越界访问了,就报错了。

所以,我们可以搭配着size接口防止越界访问来一个一个字符的遍历:

int main()
{
	string s6("hello world1111");

    //下标+[]
	for (int i = 0; i < s6.size(); i++)
	{
		cout << s6[i] << " ";
	}
	cout << endl;

	return 0;
}

 运行结果:

iterator —— 迭代器 (正向)

 迭代器遍历:

int main()
{
	string s6("hello world");
    //迭代器遍历——正向
	string::iterator it = s6.begin();
	while (it != s6.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

	return 0;
}

正向遍历(begin+end) :

注:迭代器是STL六大组件之一,迭代器是用来遍历和访问这些容器的 。(迭代器有可能是原生指针,也有可能不是)。 

迭代器是类似于一个指针一样的东西,通过解引用可以访问对应位置的数据,迭代器还规定,不管它底层是怎么定义的,它是属于对应容器的类域。(上面string的迭代器属于string的类域,所以要写成 string::iterator),所以所有的容器都可以通过迭代器来访问(通用的访问容器的方式)。

 begin()

begin()就是返回开始位置的迭代器。

string这里就是返回的是第一个有效字符的位置的迭代器。

注:这里有两个版本,第一个就是普通对象用来调用的,第二个是const修饰的对象的const迭代器所调用(这里普通对象也能调用,但是只能读,不能写)。

end()

end()返回的是最后一个有效数据的下一个位置的迭代器。

string这里就是返回最后一个有效字符的下一个位置的迭代器。

注:这里有两个版本,第一个就是普通对象用来调用的,第二个是const修饰的对象的const迭代器所调用(这里普通对象也能调用,但是只能读,不能写)。

reverse_iterator —— 反向迭代器

 这里就与iterator完全相反着了

 反向遍历(rbegin+rend): 

int main()
{
      //反向迭代器遍历
    // string::reverse_iterator rit = s6.rbegin();
    // C++11之后,直接使用auto定义迭代器,让编译器推到迭代器的类型
    auto rit = s6.rbegin();
    while (rit != s6.rend())
    {
	    cout << *rit <<" ";
	    ++rit;
    }
    cout << endl;
}

 运行结果:

rbegin和rend

这两个接口就相当于begin与end接口相反的。  

也就是rbegin获取最后一个字符迭代器 , rend获取第一个字符前一个位置的迭代器

const修饰的迭代器

 const修饰的对象,要用迭代器遍历时,是需要const修饰的迭代器的,它不能使用普通迭代器,

const迭代器修饰的是迭代器的指向,而不是迭代器的本身(类似于const修饰指针)

如下所示:

int main()
{
    //const对象
    const string cs("const hello world");
    //const修饰的(正向)迭代器
    string::const_iterator cit = cs.begin();  //这里也可以用cbegin接口,不过begin接口有提供的const修饰的版本,这就已经够用了
    while (cit != cs.end())     //cend同上也可以用
    {
	    //错误	C3892	“cit” : 不能给常量赋值
	    //*cit += 'x';
	    cout << *cit << " ";
	    ++cit;
    }
    cout << endl;

    //const修饰的反向迭代器
    string::const_reverse_iterator crit = cs.rbegin();   //这里也可以用crbegin接口,不过rbegin接口有提供的const修饰的版本,这就已经够用了
    while (crit != cs.rend())    //crend同上也可以用
    {
	    //错误	C3892	“crit” : 不能给常量赋值
	    //*crit += 'x';
	    cout << *crit << " ";
	    ++crit;
    }
    cout << endl;

    return 0;
}

 运行结果:

const修饰的迭代器,普通对象也是可以使用的(也就是权限可以缩小,但不能放大),不过只能读,不能写。

int main()
{
    //普通对象
	string s("hello world");
    //const修饰的迭代器
	string::const_iterator it = s.begin();
	while (it != s.end())
	{
		//错误	C3892	“it” : 不能给常量赋值
		//*it += 'x';
		cout << *it << " ";
		++it;
	}
	cout << endl;

	//const修饰的反向迭代器
	string::const_reverse_iterator rit = s.rbegin();
    while (rit != s.rend())
    {
	    //错误	C3892	“rit” : 不能给常量赋值
	    //*rit += 'x';
	    cout << *rit << " ";
	    ++rit;
    }
    cout << endl;
    return 0;
}

运行结果:

 (这里也可以用auto自动推导它的类型,方便代码的编写)

范围for

  • 对于一个有范围的集合而言,C++11中引入了基于范围的for循环。for循环后的括号由冒号分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。
  • 范围for可以作用到数组和容器对象上进行遍历。
auto
在早期 C/C++ auto 的含义是:使用 auto修饰的变量,是具有自动存储器的局部变量。 C++11 中,赋予了 auto 全新的含义即: auto 不再是一个存储类型 指示符,而是作为一个新的类型指示符来指示编译器, auto 声明的变量必须由编译器在编译时期 推导而得

范围for和auto就可以配合使用来遍历string:
int main()
{
	string s6("hello world");
    
    //字符赋值,自动迭代,自动判断结束
	for (auto e : s6)  //auto这里自动推导类型为char
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

 运行结果:

所以这里范围for想表达的是:

  • 注意:范围for的底层角度是跟迭代器一样的,也就是说范围for这段代码被编译了之后会被替换成迭代器。(所以要支持范围for前提是要支持迭代器遍历)

从汇编层可以看到,这里就是被替换成了迭代器: 

 范围for跟迭代器就相当于没有区别,范围for只是方便编写代码。这里对比迭代器,也展示出auto的价值就是缩短代码(简化代码),不过这样会使代码可读性降低。

//这两个it的类型是一致的
string::iterator it = s6.begin();

//auto会自动推导成上面的string::iterator
auto it = s6.begin();

  • 迭代器是可以修改的。
int main()
{
	string::iterator it = s6.begin();
	cout << s6 << endl;
	while (it != s6.end())
	{
		*it += 2;           //让里面的每一个值都加等2
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto e : s6)
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

 运行结果对比一下:

可以看到每一个值都被改变了,范围for打印数据也受到影响。范围for里也是可以修改数据的,下面在范围for里面打印原来的值:

int main()
{
	string::iterator it = s6.begin();
	cout << s6 << endl;
	while (it != s6.end())
	{
		*it += 2;           //让里面的每一个值都加等2
		cout << *it << " ";
		++it;
	}
	cout << endl;

	for (auto e : s6)
	{
        e -= 2;           //只需要让e减等2即可
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

运行结果:

  •  注意:这里有坑的是,范围for打印出来的结果看似把s6恢复原状了,这时cout一下s6可以看到s6还是没有变回去的。这个原因是:

这里若想在范围for里修改,只需要加& 符号,也就是引用

for (auto& e : s6)    //这里auto自动推导为char类型,
                      //若想是char的引用,就需要主动添加一个&
//这时e就是s6里面(*it)的别名,也就是取它每个字符的别名
{
	e -= 2;           //这时修改e,就修改了string里的值
	cout << e << " ";
}
cout << endl;

注意:用auto声明指针类型时,用auto和auto*没有任何区别(也就是当是一个指针类型时,可以直接写auto即可),但用auto声明引用类型时则必须加上&(auto &)。 

 typeid().name()

 typeid()可以帮助我们观察类型

typeid(变量名).name()   //可以看到该变量的类型
int func1()
{
	return 10;
}

int main()
{
	int a = 10;
	auto b = a;
	auto c = 'a';
	auto d = func1();
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	int x = 10;
	auto y = &x;
	auto* z = &x;
	auto& m = x;
	cout << typeid(x).name() << endl;
	cout << typeid(y).name() << endl;
	cout << typeid(z).name() << endl;
	return 0;
}

运行结果: 

 

这里auto使用时还需要注意一些事项: 

// 编译报错:rror C3531: “e”: 类型包含“auto”的符号必须具有初始值设定项
auto e;

不能这样定义,它不知道e到底是多大,auto定义的变量类型一定是由右边的变量或者函数调用表达式的返回值推导的。

// 不能做参数
void func2(auto a) //这里就算给了缺省值也不支持的
{}

 auto不能做参数类型,但auto可以做返回值。

// 可以做返回值,但是建议谨慎使用
auto func3()
{
	return 3;
}

// 编译报错:error C3538: 在声明符列表中,“auto”必须始终推导为同一类型
auto cc = 3, dd = 4.0;

注意在一行里面定义,定义时要用同一类型

// 编译报错:error C3318: “auto []”: 数组不能具有其中包含“auto”的元素类型
auto array[] = { 4, 5, 6 };

auto不能定义一个数组(C++规定)

在这篇先总结一部分string的接口使用,剩下的部分接口总结使用在后续文章介绍。

制作不易,若有不足之处或出问题的地方,请各位大佬多多指教 ,感谢大家的阅读支持!!!   

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

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

相关文章

DveOps-Git-版本控制

1. 概述 分布式版本控制系统 版本控制 2. Git极速上手指南 官方传送门:Git - Branching and Merging 2.1 安装 ## windows https: git-scm.com/download/## Linux(CentOS/Fedora/Rocky Linux/RHEL) yum install -y git ## MacOS brew install git## Ubuntu/Debian apt in…

【C++游戏程序】easyX图形库还原游戏《贪吃蛇大作战》(二)

终于忙完这段时间的项目、考证了&#xff0c;接下来将继续填之前的坑了。 书接上回【C游戏程序】easyX图形库还原游戏《贪吃蛇大作战》&#xff08;一&#xff09; 本次将使得角色和AI动起来&#xff0c;实现键盘控制&#xff0c;同时使得AI可以动起来以及一些细节补充 一.角…

【UE5】使用基元数据对材质传参,从而避免新建材质实例

在项目中&#xff0c;经常会遇到这样的需求&#xff1a;多个模型&#xff08;例如 100 个&#xff09;使用相同的材质&#xff0c;但每个模型需要不同的参数设置&#xff0c;比如不同的颜色或随机种子等。 在这种情况下&#xff0c;创建 100 个实例材质不是最佳选择。正确的做…

Python-简单病毒程序合集(一)

前言&#xff1a;简单又有趣的Python恶搞代码&#xff0c;往往能给我们枯燥无味的生活带来一点乐趣&#xff0c;激发我们对编程的最原始的热爱。那么话不多说&#xff0c;我们直接开始今天的编程之路。 编程思路&#xff1a;本次我们将会用到os,paltform,threading,ctypes,sys,…

初级数据结构——串

目录 前言一、串的定义二、串的存储结构三、串的基本操作四、串的模式匹配五、串的应用六、c代码模版七、经典例题1.汉字统计代码题解 2.查找最大元素代码题解 3.首字母变大写代码题解 八、总结结语 前言 这期我们一起深入学习初级数据结构——串&#xff0c;数据结构中的串&a…

【K8S系列】Kubernetes Pod节点ImagePullBackOff 状态及解决方案详解【已解决】

在 Kubernetes 中&#xff0c;当某个 Pod 的容器无法从指定的镜像仓库拉取镜像时&#xff0c;Pod 的状态会变为 ImagePullBackOff。这通常是因为指定的镜像不存在、镜像标签错误、认证失败或网络问题等原因。 以下是关于 ImagePullBackOff 的详细分析及解决方案。 1. ImagePull…

CSS3新特性——字体图标、2D、3D变换、过渡、动画、多列布局

目录 一、Web字体 二、字体图标 三、2D变换 1.位移 &#xff08;1&#xff09;浮动 &#xff08;2&#xff09;相对定位 &#xff08;3)绝对定位和固定定位 &#xff08;4&#xff09;位移 用位移实现盒子的水平垂直居中 2.缩放 利用缩放调整字体到12px以下&#xff…

前端项目规范~

前言 项目一般都是几个开发一起迭代升级&#xff0c;那肯定存在各种代码风格、格式化以及命名等等&#xff0c;懂得都懂&#x1f4a9;&#xff0c;所以项目规范就凸显出来了呀&#xff0c;以下主要是介绍工具自动化使用~ husky 安装husky pnpm add --save-dev husky .husk…

【编译器】Dev C++建立C语言工程

【编译器】Dev C建立C语言工程 文章目录 [TOC](文章目录) 前言一、创建工程二、添加.c.h三、主函数处理四、在桌面中打开exe文件五、参考资料总结 前言 在使用了很多编译器之后&#xff0c; 要么是太大了&#xff0c; 要么是太新了&#xff0c; 要么是在线编译器&#xff0c;用…

CHIMA网络安全攻防大赛经验分享

比赛模式 第一轮&#xff1a;20分钟基础知识赛&#xff08;50道题&#xff09; 安全运维&#xff0c;法律法规&#xff0c;linux操作系统等 第二轮&#xff1a;50分钟CTF夺旗&#xff08;5道题&#xff09; 题目涵盖 密码学 运用多种工具&#xff0c;如ASCII对照&#xff0c…

基于yolov8、yolov5的植物类别识别系统(含UI界面、训练好的模型、Python代码、数据集)

项目介绍 项目中所用到的算法模型和数据集等信息如下&#xff1a; 算法模型&#xff1a;     yolov8、yolov8 SE注意力机制 或 yolov5、yolov5 SE注意力机制 &#xff0c; 直接提供最少两个训练好的模型。模型十分重要&#xff0c;因为有些同学的电脑没有 GPU&#xff0…

JavaWeb开发10

多表设计 一对多 关系实现&#xff1a;在数据库表中多的一方添加字段来关联一的一方的主键 外键约束 一对一 关系&#xff1a;一对一关系&#xff0c;多用于单表拆分&#xff0c;将一张表的基础字段放在一张表中&#xff0c;其他字段放在另一张表中&#xff0c;以提高操作…

leetcode-12-整数转罗马数字

题解&#xff1a; 1、初始化字典&#xff1a; 2、 代码&#xff1a;

Seatunnel解决Excel中无法将数字类型转换成字符串类型以及源码打包

需求 需要实现将Excel中的数字类型的单元格像数据库中字符串类型的字段中推送 问题原因 Seatunnel在读取字段类型的时候都是使用强转的形式去获取数据的 假如说数据类型不一样的话直接强转就会报错 修改位置 org/apache/seatunnel/api/table/type/SeaTunnelRow.java org…

Keil基于ARM Compiler 5的工程迁移为ARM Compiler 6的工程

环境&#xff1a; keil版本为5.38&#xff0c;版本务必高于5.30 STM32F4的pack包版本要高于2.9 软件包下载地址&#xff1a;https://zhuanlan.zhihu.com/p/262507061 一、更改Keil中编译器 更改后编译&#xff0c;会报很多错&#xff0c;先不管。 二、更改头文件依赖 观察…

JeecgBoot 与分布式事务 Seata v1.7.0 集成实战

准备环境 一、创建四个数据库&#xff0c;如下 jeecg_order&#xff08;订单数据库&#xff09; jeecg_account&#xff08;账户数据库&#xff09; jeecg_product&#xff08;商品数据库&#xff09; seata&#xff08;seata数据库&#xff09;以上数据库脚本已存放至 jeecg…

鸿蒙动画开发07——粒子动画

1、概 述 粒子动画是在一定范围内随机生成的大量粒子产生运动而组成的动画。 动画元素是一个个粒子&#xff0c;这些粒子可以是圆点、图片。我们可以通过对粒子在颜色、透明度、大小、速度、加速度、自旋角度等维度变化做动画&#xff0c;来营造一种氛围感&#xff0c;比如下…

MAC创建一个自动操作,启动系统【睡眠】功能,并将绑定快捷键

目的 通过 Automator 创建一个服务来启动系统【睡眠】这个功能&#xff0c;并绑定快捷键。 步骤一&#xff1a;创建 Automator 服务 打开 Automator&#xff1a; ○ 在 Spotlight 中搜索 Automator&#xff0c;然后打开。选择服务类型&#xff1a; ○ 在 Automator 的启动界…

基于AIRTEST和Jmeter、Postman的自动化测试框架

基于目前项目和团队技术升级&#xff0c;采用了UI自动化和接口自动化联动数据&#xff0c;进行相关测试活动&#xff0c;获得更好的测试质量和测试结果。

HarmonyOS4+NEXT星河版入门与项目实战------Button组件

文章目录 1、控件图解2、案例实现1、代码实现2、代码解释3、运行效果4、总结1、控件图解 这里我们用一张完整的图来汇整 Button 的用法格式、属性和事件,如下所示: 按钮默认类型就是胶囊类型。 2、案例实现 这里我们实现一个根据放大和缩小按钮来改变图片大小的功能。 功…