String(C++)

文章目录

  • 前言
  • 文档介绍
  • 经典题目讲解
    • HJ1 字符串最后一个单词的长度
  • 模拟实现
    • 框架
    • 构造函数
    • 析构函数
    • 迭代器
    • c_str()
    • 赋值
    • size()
    • capacity()
    • reserve
    • empty()
    • [ ]访问
    • front/back
    • push_back
    • append
    • operator+=
    • insert一个字符
    • insert一个字符串
    • erase
    • swap
    • find一个字符
    • find一个字符串
    • substr()
    • clear()
    • 拷贝
    • 赋值
    • operator<<
    • operator>>
    • 完整代码
  • string总体介绍
    • stoi/stod
    • to_string
    • Class instantiations
  • vs和g++下string结构的
  • 总结


前言

在本篇文章中,我们将会学习到string相关的内容,并且对部分容器进行模拟实现,了解底层原理。
vs下ctrl+f可进行搜索

文档介绍

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)的序列,这个
    类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作。

string本质就是char的字符数组
在这里插入图片描述

三个成员函数:构造,析构,赋值

在这里插入图片描述

我们主要看一下这一个

在这里插入图片描述
npos是const静态成员变量,为-1
在这里插入图片描述

但是这个-1是size_t的,也就是无符号整形,这样算下来就是整形的最大值。

元素访问

在这里插入图片描述
我们string遍历可以通过下标+【】进行访问,但是其他容器不一定适合。
这个仅仅适合部分容器,要求底层有一定的连续物理空间。对于树,链表,迭代器才是主流。
迭代器的区间一般都是左闭右开的
at如果出错会抛异常,【】会直接报错。

迭代器

在这里插入图片描述
我们这里主要看一下这个一点
在这里插入图片描述,
一个是const的一个是非const的,这里会走参数匹配。
这里是const_iterator。保证迭代器指向的内容不会被修改。
不是const iterator,对于这样,保证这个迭代器不会被修改,那就不能进行加加减减操作了。
一般不会直接定义const对象,多用于传参。

我们某些情况下也可以用auto代替,但是会降低代码可读性。

容量相关接口

在这里插入图片描述
capacity:容量
对于vs编译器是1.5倍扩容。对于g++,是2倍扩容。这里为什么不一样呢??
C++只说明了不同编译器,不同平台要实现相应的接口,供用户使用。但是没有具体说明如何实现。

reserve:开空间,影响容量
一般用于知道空间大小,一下子开好,避免大量扩容。
在vs下也不是n传级就开多少空间,他是按照扩容机制实现的。
g++是n是多少,加开多少。

对于reserve缩容情况!!!
vs,不会进行缩容。
g++,会进行缩容,但是这个缩容也是有限度的,不会影响数据。(如果n比size小,最小缩到size)

resize:开空间+初始化,影响数据和容量
在这里插入图片描述
如果resize>capacity,扩容+尾插,不传参数c,默认为/0
如果resize在size和capacity之间,尾插
如果resize<size,capacity不变,size变为resize大小。

clear:清空数据
clear只会清空数据,不改变底层大小

修改

在这里插入图片描述
注意:push_back只会尾插一个字符
我们最常用的就是+=

assign是赋值,我们一般不使用这个。
replace是替换,效率很低,能不用就不用。

字符串操作

在这里插入图片描述
c_str:返回c格式字符串,与data相同
下面这段代码不会编译通过。

int main()
{string filename("main.cpp");FILE* fout = fopen(filename, "r");char ch = fgetc(fout);while (ch != EOF){cout << ch;ch = fgetc(fout);}return  0;
}

fopen的参数是这样的
在这里插入图片描述
我们需要将string转换成c格式的字符串进行传参/

int main()
{string filename("main.cpp");FILE* fout = fopen(filename.c_str(), "r");char ch = fgetc(fout);while (ch != EOF){cout << ch;ch = fgetc(fout);}return  0;
}

substr:取子串,注意参数

在这里插入图片描述
find_first_of:查找这里面的任意一个

非成员函数

在这里插入图片描述
operator+
尽量少用,因为传值返回,导致深拷贝效率低

relational operators
大小比较

getline
获取一行字符串
我们在用scanf和cin输入多个值的时候,用空格或者换行进行分割
有些时候我们需要接收到空格,这时候就可以用getline,这个是遇到换行结束。

经典题目讲解

HJ1 字符串最后一个单词的长度

HJ1 字符串最后一个单词的长度
我们如果直接用cin输入,是错误的。
cin是以空格和换行为结束标志的,我们读取不到换行。
可以考虑用getline,这个是遇到换行才结束。

#include <iostream>
#include <string>
using namespace std;int main() 
{string s;getline(cin,s);// cin>>s;auto rit=s.rfind(' ');if(rit!=-1){cout<< s.size()-rit-1<<endl;}else {cout<< s.size()<<endl;}return 0;
}

模拟实现

框架

class string
{public:private:char* _ptr;size_t _size;size_t _capacity;//特殊处理,只有整形才可以static const size_t npos = -1;
};

这个特殊处理可以这样用
const static int N=10;
int a[N];

构造函数

我们看一下这样的构造函数是否正确

string(const char* ptr):_ptr(ptr), _size(strlen(ptr)), _capacity(strlen(ptr))
{}

这样子是不正确的,我们并不是直接把地址赋值给他,而是重新开一块同样大的空间,把数据拷贝过去。

string(const char* ptr)//多开一个,放‘\0’:_ptr(new char [strlen(ptr)]+1 ), _size(strlen(ptr))//容量不关心‘\0’,只关心有效空间, _capacity(strlen(ptr))
{//数据拷贝strcpy(_ptr,ptr);
}

但是这样还是有一点小问题,strlen时间复杂度为O(N),
在这里我们需要计算三次。

我们可以直接给_size,通过_size进行传参,那麽我们再进行初始化列表的时候就必须按照声明顺序执行。

那我们如何解决呢??
初始化列表在这种场景下又不是必须的,我们可以不用初始化列表,就用正常的构造函数。

我么还需要处理传递参数为空的情况。

string()
{_size = strlen(ptr);_ptr =nullptr;_capacity = _size;strcpy(_ptr, ptr);
}

我们将_ptr初始化为空是否可行呢??
是不可以,如果我们直接这个string进行打印,就会出现空指针的解引用操作。

我们可以给一个空串。所以完整的构造应该是这样的

string(const char* ptr = "")
{_size = strlen(ptr);_ptr = new char[_size + 1];_capacity = _size;strcpy(_ptr, ptr);
}

析构函数

这个很简单,将开辟的资源释放就可以。

~string()
{delete[] _ptr;_ptr = nullptr;_capacity = _size = 0;
}

delete对空机型特殊处理,delete nullptr不会报错。

迭代器

我们的迭代器区间是左闭右开的
我们需要实现普通迭代器和const迭代器

typedef char* iterator;
typedef const char* const_iterator;iterator begin()
{return _ptr;
}
const_iterator begin()const 
{return _ptr;
}iterator end()
{return _ptr + _size;
}
const_iterator end()const
{return _ptr + _size;
}

范围for就是傻瓜式的替换,底层还是迭代器

c_str()

将一个string转换成一个char*使用

const char* c_str()const
{return _ptr;
}

赋值

string operator=(string& str)
{if (this != &str){_size = str._size;_capacity = str._capacity;delete[]_ptr;_ptr = new char[_size + 1];strcpy(_ptr, str._ptr);}return *this;
}

size()

计算数据个数

size_t size()const
{return _size;
}

capacity()

计算容量大小

size_t capacity()const
{return _capacity;
}

reserve

开空间,n是多少我们就开多少。
我们这里不会进行缩容

void reserve(size_t n=0)
{if (n > capacity()){char* tmp = new char[n + 1];strcpy(tmp, _ptr);delete _ptr;_capacity = n;_ptr = tmp;}
}

empty()

判断是否为空

bool empty()const
{return _size == 0;
}

[ ]访问

我们的【】范围需要实现两份
const与非const 版本

char& operator[](size_t pos)
{assert(pos >= 0 && pos < _size);return  _ptr[pos];
}
const char& operator[](size_t pos)const
{assert(pos >= 0 && pos < _size);return  _ptr[pos];
}

front/back

访问头和尾

char& back()
{return _ptr[_size - 1];
}
char& front()
{return _ptr[0];
}
const char& back()const
{return _ptr[_size - 1];
}
const char& front()const 
{return _ptr[0];
}

push_back

尾插一个字符

void push_back(char c)
{//是否扩容if (_size == _capacity){int newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}_ptr[_size] = c;_size++;_ptr[_size] = '\0';
}

append

尾插一个字符串

void append(const string& str)
{if (_capacity < str._size+_size){reserve(str._size + _size);}strcpy(_ptr+_size, str._ptr);_size += str._size;_ptr[_size] = '\0';
}

operator+=

我们在实际使用中,最常用的就是+=

string& operator+=(const string& str)
{append(str);return *this;
}
string& operator+=(const char& ch)
{push_back(ch);return *this;}

insert一个字符


string& insert(size_t pos, const char& ch)
{assert(pos >= 0 && pos <= _size);//扩容if (_size == _capacity){int newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}int end = _size;while (end >= pos){if (end == 1){int i = 0;}_ptr[end+1] = _ptr[end];end--;}_ptr[pos] = ch;_size++;return *this;
}

我们看一下这段代码
正常情况下是没有问题的,但是如果在0位置插入就会出问题

我们调试看一下

在这里插入图片描述
继续下一步

在这里插入图片描述

我们发现end=-1,pos=0,end>=pos居然是真的。
那这是为什么呢??
在这里插入图片描述
我们可以看到二者的类型是不一样的,这里会把end提升为unsigned_int进行比较。
-1如果是unsigned_int,就是整形的最大数,肯定大于0.

解决1
将pos强制转化成int类型,按照int进行比较

在这里插入图片描述

解决2
专门处理等于情况,不让等于发生。
我们上面是把end位置的值挪动到end+1位置,最后才可能end走到与pos相同的位置,二者可能相等。
但是我们如果把end-1的位置挪动到end位置就不会发生上面的情况了。

string& insert(size_t pos, const char& ch)
{assert(pos >= 0 && pos <= _size);//扩容if (_size == _capacity){int newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}size_t end = _size + 1;while (end > pos){if (end == 1){int i = 0;}_ptr[end] = _ptr[end - 1];end--;}_ptr[pos] = ch;_size++;return *this;
}

insert一个字符串

string& insert(size_t pos, const char* str)
{assert(pos >= 0 && pos <= _size);//判断扩容int n = strlen(str);if (n + _size > _capacity){reserve(n + _size);}//挪动数据int end = _size;while (end >= (int)pos){_ptr[end+n] = _ptr[end ];end--;}strncpy(_ptr + pos, str, n);_size += n;return *this;
}

erase

从pos位置开始删除len个字符。

string& erase(size_t pos=0, size_t len=npos)
{if (len == npos || len + pos >= _size){//删除直接给'\0'_ptr[pos] = '\0';_size = pos;}else{//数据挪动strcpy(_ptr + pos, _ptr + pos + len);_size -= len;}return *this;
}

if (len == npos || len + pos >= _size)
如果我们变成len+pos>=_size,不写前面的条件,很可能会越界。

swap

我们会发现库里面有三个swap
在这里插入图片描述
这个调用是为了减少string的拷贝。

在这里插入图片描述
这个与下面相比,如果我们是swap(s1,s2);
我们会优点调用这个,这个就是根据第一个实现的。
模板的匹配。
在这里插入图片描述
这个是所有STL库的交换函数

我们string的交换只需要交换三个变量的内容就饿可以,但是需要调用库里的,否则就会产生死递归。

void swap(string& str)
{std::swap(_ptr, str. _ptr);std::swap(_size, str._size);std::swap(_capacity, str._capacity);
}

find一个字符

我们直接进行遍历就可以。

size_t find(const char ch, size_t pos=0)const
{size_t i = 0;for (i = pos; i < _size; i++){if (_ptr[i] == ch){return i;}}return npos;
}

find一个字符串

借助strstr实现


size_t find(const char* ptr, size_t pos = 0)const
{//strstr 查找字串char* tmp = strstr(_ptr+pos, ptr);if (tmp == nullptr){return npos;}else{//返回位置return tmp-_ptr;}
}

substr()

取string一部分

string substr(size_t pos = 0, size_t len = npos)
{size_t end = pos + len;if (len == npos || pos + len > _size){end = _size;}string tmp;reserve(_capacity + 1);for (size_t i = pos; i< end; i++){tmp += _ptr[i];}return tmp;
}

clear()

清空数据

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

拷贝

我们的string有资源,所以我们需要深拷贝。

默认的拷贝完成浅拷贝,完不成我们的任务。

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

开一块同样大的空间,把值拷贝过去。

string(const string  ptr)
{_ptr = new char[ptr._size + 1];_size = ptr._size;_capacity = ptr._capacity;strcpy(_ptr, ptr._ptr);
}

上面写的是传统写法。

我们看一种现代写法

void swap(string& str)
{std::swap(_ptr, str. _ptr);std::swap(_size, str._size);std::swap(_capacity, str._capacity);
}
string(const string& ptr)
{string tmp(ptr._ptr);swap(tmp);
}

为什么这样就可以呢??
我们画图看一下:

s1,s2各自的空间
tmp是根据s1拷贝出来的.
在这里插入图片描述

我们看一下swap之后的效果

s2指向tmp的空间,这不就是我们想要的吗!!
tmp指向s2的空间。
在这里插入图片描述

赋值

赋值按照传统写法

string operator=(string& str)
{if (this != &str){_size = str._size;_capacity = str._capacity;delete[]_ptr;_ptr = new char[_size + 1];strcpy(_ptr, str._ptr);}return *this;
}

现代写法


string operator=(string s)
{swap(s);return *this;
}

这里千万不能用引用

我们这里不需要进行判断,因为s已经拷贝出来了。
在这里插入图片描述

swap之后,s出了作用域要进行销毁,就自动把原来s3的空间进行释放了。

operator<<

<<不是必须是友元,我们用友元是为了可以拿到私有成员。
我们string直接对每个字符进行遍历及就可以了

ostream& operator<<(ostream& out, const string& s)
{for (auto ch : s){out << ch;}return out;
}

operator>>

这个运算符>>输入多个值,默认空格和换行进行分割。
.>>是一个个字符的拿。
但是我们用这个>>,默认是拿不到换行个空格的。

我们可以用get获取。
为了避免多次扩容,我们可以引入一个buff字符数组来缓解这个问题。

流提取会覆盖这个变量已有的内容,我们再进行输入之前需要先清空。

istream& operator>>(istream& in, string& s)
{s.clear();char buff[128];char ch = in.get();int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;
}

完整代码

namespace peng
{class string{public:string(const char* ptr = ""){_size = strlen(ptr);_ptr = new char[_size + 1];_capacity = _size;//strcpy(_ptr, ptr);}string(const string& str, size_t pos, size_t len = npos){size_t n = strlen(str._ptr);if (len == npos || pos + len > n){_size = n - pos;_capacity = n - pos;_ptr = new char[_size + 1];strcpy(_ptr, str._ptr + pos);}_size = len;_capacity = len;_ptr = new char[len + 1];strncpy(_ptr, str._ptr + pos + 1, len);_ptr[_size] = '\0';}string(size_t n, char c = '\0'){_size = n;_capacity = n;_ptr = new char[n + 1];for (size_t i = 0; i < n; i++){_ptr[i] = 'c';}_ptr[_size] = '\0';}~string(){delete[] _ptr;_ptr = nullptr;_capacity = _size = 0;}const char* c_str()const{return _ptr;}//迭代器,左闭右开typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _ptr;}const_iterator begin()const{return _ptr;}iterator end(){return _ptr + _size;}const_iterator end()const{return _ptr + _size;}size_t size()const{return _size;}size_t capacity()const{return _capacity;}//开空间,n是多少开多少//不进行缩容void reserve(size_t n = 0){if (n > capacity()){char* tmp = new char[n + 1];strcpy(tmp, _ptr);delete _ptr;_capacity = n;_ptr = tmp;}}bool empty()const{return _size == 0;}char& operator[](size_t pos){assert(pos >= 0 && pos < _size);return  _ptr[pos];}const char& operator[](size_t pos)const{assert(pos >= 0 && pos < _size);return  _ptr[pos];}char& back(){return _ptr[_size - 1];}char& front(){return _ptr[0];}const char& back()const{return _ptr[_size - 1];}const char& front()const{return _ptr[0];}void push_back(char c){if (_size == _capacity){int newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}_ptr[_size] = c;_size++;_ptr[_size] = '\0';}void append(const string& str){if (_capacity < str._size + _size){reserve(str._size + _size);}strcpy(_ptr + _size, str._ptr);_size += str._size;_ptr[_size] = '\0';}string& operator+=(const string& str){append(str);return *this;}string& operator+=(const char& ch){push_back(ch);return *this;}string& insert(size_t pos, const char* str){assert(pos >= 0 && pos <= _size);//判断扩容int n = strlen(str);if (n + _size > _capacity){reserve(n + _size);}//挪动数据int end = _size;while (end >= (int)pos){_ptr[end + n] = _ptr[end];end--;}strncpy(_ptr + pos, str, n);_size += n;return *this;}string& insert(size_t pos, const char& ch){assert(pos >= 0 && pos <= _size);//扩容if (_size == _capacity){int newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}size_t end = _size + 1;while (end > pos){_ptr[end] = _ptr[end - 1];end--;}_ptr[pos] = ch;_size++;return *this;}string& erase(size_t pos = 0, size_t len = npos){if (len == npos || len + pos >= _size){//删除直接给'\0'_ptr[pos] = '\0';_size = pos;}else{//数据挪动strcpy(_ptr + pos, _ptr + pos + len);_size -= len;}return *this;}size_t find(const char ch, size_t pos = 0)const{size_t i = 0;for (i = pos; i < _size; i++){if (_ptr[i] == ch){return i;}}return npos;}size_t find(const char* ptr, size_t pos = 0)const{//strstr 查找字串char* tmp = strstr(_ptr + pos, ptr);if (tmp == nullptr){return npos;}else{//返回位置return tmp - _ptr;}}//取string一部分string substr(size_t pos = 0, size_t len = npos){size_t end = pos + len;if (len == npos || pos + len > _size){end = _size;}string tmp;reserve(_capacity + 1);for (size_t i = pos; i < end; i++){tmp += _ptr[i];}return tmp;}void swap(string& str){std::swap(_ptr, str._ptr);std::swap(_size, str._size);std::swap(_capacity, str._capacity);}//拷贝赋值//s1(s2)//string(const string  ptr)//{//	_ptr = new char[ptr._size + 1];//	_size = ptr._size;//	_capacity = ptr._capacity;//	strcpy(_ptr, ptr._ptr);//}//s1=s2;//s1.operator(s2);//string operator=(string& str)//{//	if (this != &str)//	{//		_size = str._size;//		_capacity = str._capacity;//		delete[]_ptr;//		_ptr = new char[_size + 1];//		strcpy(_ptr, str._ptr);//	}//	return *this;//}string(const string& ptr){string tmp(ptr._ptr);swap(tmp);}string operator=(string s){swap(s);return *this;}void clear(){_size = 0;_ptr[0] = '\0';}private:char* _ptr = nullptr;size_t _size = 0;size_t _capacity = 0;//特殊处理static const size_t npos = -1;};ostream& operator<<(ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}istream& operator>>(istream& in, string& s){s.clear();char buff[128];char ch = in.get();int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}}

string总体介绍

stoi/stod

在这里插入图片描述
这几个函数可以把字符串转成整形或者浮点型

to_string

将整形或者浮点数转化成string

在这里插入图片描述

int main()
{string s1("1111");int m = stoi(s1);cout << m << endl;cout << typeid(m).name() << endl;int n = 22222;string s2 = to_string(n);cout << typeid(s2).name() << endl;cout << s2 << endl;return 0;
}

在这里插入图片描述

Class instantiations

在这里插入图片描述

这些是编码表。
我们最熟悉的就是ASCII码,每个值都一一对应。

但是对于我们的汉字,需要用多个字节表示一个中文汉字,一般常见的汉字都是用两个字节表示。

随着历史的发展,产生了万国码

在这里插入图片描述

在这里插入图片描述
每一种又对应各自的编码方式。

在我们中国,自己设立一一套规范,称为GBK
在这里插入图片描述

string就是用到UTF-8的格式
在这里插入图片描述

vs和g++下string结构的

VS

下面代码的结果是什么???

int main()
{string s1;cout << sizeof(s1) <<endl;string s2("12345");cout << sizeof(s2) << endl;string s3("1234sssssssssssssssssssssssssssssssssss5");cout << sizeof(s3) << endl;return 0;
}

在这里插入图片描述
结果是28,为什呢??

在这里插入图片描述
我们看到库里还开了一个16字节大小的字符数组。

我们可以认为底层是这样的
在这里插入图片描述
 当字符串长度小于16,使用内部固定的字符数组来存放
 当字符串长度大于等于16,从堆上开辟空间

我们可以认为是这样

在这里插入图片描述

G++

但是如果我们在g++下测试,打印8。
一个指针的大小,g++下默认指针是8个字节。

g++下是如何通过一个char*指针解决的呢??
浅拷贝问题:
1.析构两次
2.一个修改会影响另一个

g++解决浅拷贝:引用计数,写时拷贝

引用计数:用来记录资源使用者的个数。
在构造时,将资源的计数给成1,每增加一个对象使用该资源,就给计数增加1;
当某个对象被销毁时,先给该计数减1,然后再检查是否需要释放资源。
如果计数为1,说明该对象时资源的最后一个使用者,将该资源释放;否则就不能释放,因为还有其他对象在使用该资源。

具体是这样实现的

在这里插入图片描述

总结

以上就是今天要讲的内容,本文仅仅详细介绍了 。希望对大家的学习有所帮助,仅供参考 如有错误请大佬指点我会尽快去改正 欢迎大家来评论~~ 😘 😘 😘

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

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

相关文章

手慢无!限量奶茶免费领,千元大奖组队赢!

&#x1f680; AI 卡片大作战全新启动&#xff01;&#xff01;&#x1f552; 限时两周&#xff0c;组队狂欢&#xff01;&#x1f46b; 邀请好友&#xff0c;解锁免费奶茶福利&#xff01;&#x1f4b0; 学习卡片&#xff0c;赢取 1888 超级现金大奖心动不如行动&#xff0c;快…

经典游戏案例:仿植物大战僵尸

学习目标&#xff1a;仿植物大战僵尸核心玩法实现 游戏画面 项目结构目录 部分核心代码 using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; using Random UnityEngine.Random;public enum…

Web APIs-DOM-事件相关整理(完成网页交互)

目录 1.事件监听 2.事件监听绑定 3.事件类型 4.实例注意 5.事件对象 6.环境对象 7.回调函数 1.事件监听 &#xff08;绑定事件/注册事件&#xff09;: 程序检测有没有事件产生&#xff08;事件&#xff1a;比如单机一个按钮&#xff08;编程时系统发生的动作或者事情&a…

网络爬虫Xpath开发工具的使用

开发人员在编写网络爬虫程序时若遇到解析网页数据的问题&#xff0c;则需要花费大量的时间编 写与测试路径表达式&#xff0c;以确认是否可以解析出所需要的数据。为帮助开发人员在网页上直接 测试路径表达式是否正确&#xff0c;我们在这里推荐一款比较好用的 XPath 开发工…

轻松学AI绘画:PS AI插件,小白的入门秘籍

各位AIGC创意爱好者们&#xff0c;你们是否对AI绘画充满好奇&#xff0c;却又对那些复杂的国外软件感到望而却步&#xff1f;别急&#xff0c;今天我要为大家介绍一款适合新手的国产PS AI插件——StartAI&#xff0c;它将为你的创作之路带来无限可能&#xff01; StartAI&…

大学网页制作作品1

作品须知&#xff1a;1.该网页作品预计分为5个页面&#xff08;其中1个登录页面&#xff0c;1个首页主页面&#xff0c;3个分页面&#xff09;&#xff0c;如需要可自行删改增加页面。&#xff08;总共约800行html,1200行css,100行js&#xff09; 2.此网页源代码只用于学习和模…

短视频最火的10个拍摄技巧,新手也能这样拍出大片效果

短视频越来越占据了人们的生活&#xff0c;不管是记录生活还是发个朋友圈是不是总感觉咱们自己拍出来的效果总是不如别人呢&#xff1f;更别说发短视频平台呢&#xff01;下面就分享10个拍摄技巧大家学着试试慢慢也能拍出大片效果。 不管你以后是否发展短视频平台&#xff0c;…

免费的音频剪辑软件有哪些?分享9个实用的软件,自媒体人必备!

音频剪辑软件能够帮助我们对音视频文件实现个性化剪辑&#xff0c;包括分割、合并、添加音效、转换格式等。那么都有哪些免费好用的音频剪辑软件和方法&#xff0c;本文整理了电脑、手机、在线的音频剪辑方法&#xff0c;能够有效解决音频剪辑的需求&#xff0c;一起来看看吧&a…

本地电脑配置不足,对工业仿真计算有哪些影响?

工业仿真计算对电脑的要求相对较高&#xff0c;这主要是因为仿真过程涉及到大量的数据处理和复杂的计算任务。一个高效的工业仿真系统需要强大的计算能力和稳定的运行环境&#xff0c;以确保仿真的准确性和实时性。 工业仿真对电脑配置有哪些要求 首先&#xff0c;工业仿真计算…

基于STM32设计的智能家居远程调温系统(通过红外线控制空调)_75

文章目录 一、前言1.1 项目介绍【1】项目功能介绍【2】项目硬件模块组成1.2 设计思路【1】整体设计思路【2】ESP8266工作模式配置1.3 设计的意义1.4 开发工具的选择1.5 系统框架图1.6 系统功能总结1.7 原理图二、硬件选型2.1 ESP8266-串口WIFI2.2 STM32F103C8T6开发板2.3 红外学…

题目 2721: 蓝桥杯2022年第十三届决赛真题-背包与魔法

题目 2721: 蓝桥杯2022年第十三届决赛真题-背包与魔法 原题链接&#xff1a;完成情况&#xff1a;解题思路&#xff1a;Problem ExplanationCode ExplanationSummary 参考代码&#xff1a;_题目2721_蓝桥杯2022年第十三届决赛真题_背包与魔法 错误经验吸取 原题链接&#xff1…

Hexo结合多个主题扩展为Gallery画廊并实现文章加密

文章目录 1. 初始化2. 安装加密3. 配置文件4. 创建Token5. 新建公开仓库6. 工作流7. 实现效果1. 加密2. 画廊B主题 可能参考的文章&#xff1a; 如何优雅的使用Github Action服务来将Hexo部署到Github Pages - Hexo 当前PC环境中有Node和Git。版本可以参考Hexo文档。 文章中…

ubuntu的不同python版本的pip安装及管理

ubuntu的不同python版本的pip安装及管理_ubuntu 安装两个pip-CSDN博客https://blog.csdn.net/qq_32277533/article/details/106770850

第10章 启动过程组 (识别干系人)

第10章 启动过程组 10.2识别干系人&#xff0c;在第三版教材第361~362页&#xff1b; 文字图片音频方式 视频13 第一个知识点&#xff1a;主要工具与技术 1、数据收集 问卷调查 包括一对一调查、焦点小组讨论&#xff0c;或其他大规模信息收集技术 头脑风暴 头脑风暴&#xff…

本地服务怎么发布成rpc服务

目录 1.引入 2.user.proto 3.userservice.cc 1.引入 example文件夹作为我们框架项目的使用实例&#xff0c;在example文件夹下创建callee和caller两个文件夹 callee是RPC服务的提供者。在callee创建一个文件&#xff1a;userservice.cc 我们有没有这样一个框架&#xff0c;把…

基于FreeRTOS+STM32CubeMX+LCD1602+MCP4162(SPI接口)的数字电位器Proteus仿真

一、仿真原理图: 二、仿真效果: 三、STM32CubeMX配置: 1)、SPI配置: 2)、时钟配置: 四、软件部分: 1)、主函数: /* USER CODE BEGIN Header */ /** ****************************************************************************** * @file : mai…

MinIO下载和安装(Windows)

1、MinIO下载和安装 | 用于创建高性能对象存储的代码和下载内容 2、在本地硬盘中并新建一个minio文件夹 里面再创建bin文件夹和data文件夹 bin 用于存放下载的minio.exe data 用于存放数据 logs 用于存放日志 3、 编写启动脚本start.bat echo off echo [信息] 运行MinIO文服务…

群智优化:探索BP神经网络的最优配置

群智优化&#xff1a;探索BP神经网络的最优配置 一、数据集介绍 鸢尾花数据集最初由Edgar Anderson测量得到&#xff0c;而后在著名的统计学家和生物学家R.A Fisher于1936年发表的文章中被引入到统计和机器学习领域数据集特征&#xff1a; 鸢尾花数据集包含了150个样本&#…

工业软件的分类与选择策略:针对中小企业的实际应用考量

工业软件是现代工业体系的“大脑”&#xff0c;已经渗透到几乎所有工业领域的核心环节&#xff0c;是现代产业之“魂”&#xff0c;是制造强国之重器。工业软件通过优化生产流程、实时监控设备状态、实现自动化控制等功能&#xff0c;可以帮助企业显著提升生产效率和质量&#…

鸿蒙开发系统基础能力:【@ohos.hiTraceMeter (性能打点)】

性能打点 本模块提供了追踪进程轨迹&#xff0c;度量程序执行性能的打点能力。本模块打点的数据供hiTraceMeter工具分析使用。 说明&#xff1a; 本模块首批接口从API version 8开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 导入模块 impor…