详解string类+迭代器

迭代器

概念:在 C++ 中,迭代器是访问容器(如数组、列表、向量、字符串等)元素的一种方式。迭代器提供了一种统一的接口,使得你可以使用相同的代码来遍历不同类型的容器。迭代器本质上是一个指针或者指针的封装,它指向容器中的某个元素

iterator:表示迭代器类型,通常用于声明迭代器变量
迭代器是通用的,能更好与算法配合

迭代器的分类

在C++中,迭代器根据功能和能力被分为 5种类型,支持的操作逐渐增强。

在这里插入图片描述

以 std::string 为例,它的迭代器属于 随机访问迭代器。

在这里插入图片描述

获取迭代器
在这里插入图片描述
迭代器操作

在这里插入图片描述
在这里插入图片描述

范围for循环

在这里插入图片描述
一个类只要支持迭代器就支持范围for,其底层实现就是迭代器。范围for存在局限性,只能顺着遍历。若是自定义类需要自己实现普通和const迭代器。

aoto关键字

核心作用:让编译器自动推导变量类型,避免显式写出冗长或复杂的类型名

推导规则
1.忽略顶层 const 和引用,保留底层const
在这里插入图片描述
在这里插入图片描述

什么是顶层引用,为什么忽略
C++ 中并没有“顶层引用”这一标准术语,但可以通过对比 “顶层 const” 来类比理解:
1.顶层 const:表示对象本身是常量(直接修饰变量本身)。例如const int a = 10; 中的 a 是顶层 const。
底层 const:表示指针或引用指向的对象是常量。例如:const int* p = &a; 中的 p 是底层 const(指针指向的内容不可变)。
2.避免意外的引用绑定:如果 auto 自动保留引用,可能导致未预期的副作用(例如无意中修改原数据)。
简化代码逻辑:大多数情况下,开发者可能只需要操作副本而非用。
使用规则
auto 会丢弃初始化表达式中的引用属性,推导出被引用对象的类型。若需要保留引用,必须显式添加 &

2.数组退化为指针

在这里插入图片描述

注意事项:

1.必须初始化。auto 变量必须显式初始化,否则无法推导类型
2.类型可能不符合预期。推导结果可能因初始化表达式不同而变化
在这里插入图片描述
3.谨慎使用引用和 const。需显式指定 & 或 const 以保留引用或常量性

auto范围for的使用场景

在这里插入图片描述
auto加引用,ch 是字符串中字符的引用,直接绑定到原字符串的每个字符上。在循环体内对 ch 的修改(如 ch++)会直接影响原字符串。

在这里插入图片描述
不加引用auto ch,ch 是字符串中字符的副本,与原字符串完全独立。
对 ch 的修改(如 ch++)仅作用于副本,不会影响原字符串。原字符串保持不变。

在这里插入图片描述

1.为什么学习string类

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

所以C++中专门在标准模板库中封装了一个string类,方便我们进行有关字符串的操作,在实现时不用自己再实现代码细节,只需正确调用函数即可

2.标准库中的string类

2.1了解string类

定义:std::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类时,必须包含#include头文件以及using namespace std。

2.2常用接口说明

构造函数

在这里插入图片描述

1.默认构造函数:创建一个空对象,不需要任何参数。
2.复制构造函数:通过已有的string对象来创建一个新的string对象,参数是对一个string对象的常量引用。
3.从子字符串构造:第一个参数是对原始字符串的引用,pos是子字符串开始的位置,len是子字符串的长度。
4.从C字符串构造:参数是一个指向字符数组的指针。
5.从字符序列构造:从给定的字符数组中复制前n个字符来创建对象,参数是一个指向字符数组的指针和要复制的字符数量。
6.填充构造函数:参数是字符串长度和填充字符
7.模板构造函数:允许使用任何类型的输入迭代器来构造一个字符串。参数确定序列的范围。
在这里插入图片描述

容量操作函数

在这里插入图片描述

size返回字符串有效字符长度

与length底层实现原理一样,引入size是为了与其他接口保持一致,一般情况下都用size()。和strlen一样不计入’\0’
在这里插入图片描述

capacity:返回当前分配的存储空间大小,即字符串的容量

在这里插入图片描述

empty检查字符串是否为空。返回 true;否则返回 false

在这里插入图片描述

clear:将s中的字符串清空

在这里插入图片描述

注意清空时只是将size清0,不改变底层空间的大小

reserve请求改变字符串的容量,只开空间。

如果 n 大于当前容量,字符串的容量会被增加到至少 n;如果 n 小于或等于当前容量,此调用可能不会改变容量,这是一个不具有约束力的请求。底层实现看是否还存有数据(是否使用clear+reserve(0)),若有则不缩,反之缩。
一般情况下不会缩容,因为系统不支持分段释放内存,缩容是以时间换空间的方式 ,将原空间需保留部分拷贝到新空间,再释放原空间。
在这里插入图片描述
n 是一个 size_t 类型的参数,表示要预留的最小字符数。如果省略此参数或其值为 0,则不会预留额外的内存。通过预留足够的内存,可以减少在字符串增长过程中因内存不足而进行的多次内存分配和数据复制,从而提高程序的性能

resize:预留足够的内存,开空间+填值初始化。

如果 n 小于当前长度,字符串会被截断;如果 n 大于当前长度,字符串会被扩展,并用空字符填充。
在这里插入图片描述
存在第二种带填充字符的重载。将字符串的大小调整为 n 个字符,如果需要扩展字符串,则使用字符 c 进行填充。如果当前字符串长度大于 n,则截断字符串,只保留前 n 个字符。
注意:resize 函数不会抛出异常,即使在内存分配失败的情况下,它也会静默地减少字符串的大小。

max_size返回可容纳的最大字符数

由 std::string 所基于的 std::allocator 决定的,并且受到系统内存限制的影响。
在这里插入图片描述

在这里插入图片描述

类对象访问及遍历操作

在这里插入图片描述
是一个重载的运算符函数,它允许你使用方括号[]来访问字符串中的字符。这个运算符有两个版本:

char& operator[] (size_t pos):
这是一个非常量版本,返回一个对字符串中指定位置pos的字符的引用。这意味着你可以通过这个引用来修改字符串中的字符。

const char& operator[] (size_t pos) const:
这是一个常量版本,返回一个对字符串中指定位置pos的字符的常量引用。这意味着你可以读取字符串中的字符,但不能通过这个引用来修改它。

在这里插入图片描述
对比at,at是在还没[]运算符重载时出现的,同样具有查找和修改的功能。
在这里插入图片描述
区别于[],at会进行边界检查,通常慢一点,当出现边界问题时会抛异常。[]不会检查边界,出问题直接报错。at提供了更好的安全性和错误处理能力,可以帮助避免潜在的运行时错误。但在日常中索引值一般都确定,更加习惯于使用[]。

在这里插入图片描述

都返回一个迭代器
begin:指向容器的第一个元素
end:指向容器“结束”的位置,即最后一个元素之后的位置
rbegin:指向容器的最后一个元素,即反向遍历的开始位置
rend:指向容器第一个元素之前的位置,即反向遍历结束的位置
cbegin:返回一个常量迭代器,指向容器第一个元素,常量迭代器不允许修改容器中的元素
cend:返回一个常量迭代器,指向容器的“结束”位置。
crbegin:返回一个常量反向迭代器(const_reverse_iterator),其余和rbegin相同
crend:返回一个常量反向迭代器,其余和rend相同

string的三种遍历方式

void Teststring3()
{string s("hello Bit");// 3种遍历方式:// 需要注意的以下三种方式除了遍历string对象,还可以遍历是修改string中的字符,// 另外以下三种方式对于string而言,第一种使用最多// 1. for+operator[]for (size_t i = 0; i < s.size(); ++i)cout << s[i] << " ";cout << endl;// 2.迭代器string::iterator it = s.begin();while (it != s.end()){cout << *it <<" ";++it;}cout << endl;//string::reverse_iterator rit = s.rbegin();//C++11之后,直接使用auto定义迭代器,让编译器推到迭代器的类型auto rit = s.rbegin();while (rit != s.rend()){cout << *rit << " ";rit++;}cout << endl;// 3.范围forfor (auto& ch : s){cout << ch << " ";ch++;}cout << endl;
}
int main()
{Teststring3();return 0;
}

string类对象的修改操作

在这里插入图片描述
1.operator+=:重载的加法赋值运算符,用于将另一个字符串或字符串字面量追加到当前字符串的末尾。
2.append:将指定的字符串或字符数组追加到当前字符串的末尾
3.push_back:将单个字符追加到字符串的末尾
4.assign:将字符串的内容替换为指定的字符串或字符数组,会覆盖
5.insert:在字符串的指定位置插入另一个字符串或字符数组。头部插入存在数据挪动导致效率问题
6.erase:从字符串中删除指定范围的字符
7.replace:替换字符串中指定范围的字符为另一个字符串或字符数组
8.swap:交换当前字符串与另一个字符串的内容
9.pop_back删除字符串最后一个字符
在这里插入图片描述

各接口的具体实现细节可以在cplusplus.com官网上去查看。这里不一一列举,学习了解重点即可。

字符串操作

在这里插入图片描述

c_str() 函数用于获取一个 C++ 风格的字符串(C string),也就是以空字符(‘\0’)结尾的字符数组。返回一个指向内部数据的指针,该数据是一个以空字符终止的字符数组,即 C 风格的字符串。这个指针可以用来与需要 C 风格字符串的函数或库进行交互,使得可以在需要 C 风格字符串的上下文中使用 C++ 字符串。

重点分析:

在这里插入图片描述
1.查找字符串,pos参数指定开始查找位置,默认为0
2.查找C风格字符串
3.查找字符数组,pos为开始位置,n为要查找的字符数
4.查找单个字符,pos为开始位置默认为0,可以指定很方便
未找到都返回std::string::npos

在这里插入图片描述
用于从一个字符串对象中提取子字符串,并返回一个新的字符串对象,原始字符串不会被修改。
pos为开始查找位置,默认为0,len表示要提取的字串长度,默认为npos,是一个很大的值,若不自己给范围将提取到字符串末尾。
如果指定的 pos 超出了主字符串的有效范围,或者 len 为 0,则 substr 函数将返回一个空字符串。

这两个接口函数可以很好的实现查找和分割字符串,以获取一个网址的协议 域名 资源名为例。
在这里插入图片描述

string结构的说明

下述结构是在32位平台下进行验证,32位平台下指针占4个字节.
vs下string的结构

string总共占28个字节,内部结构稍微复杂一点,先是有一个联合体,联合体用来定义string中字符串的存储空间:
当字符串长度小于16时,使用内部固定的字符数组来存放
当字符串长度大于等于16时,从堆上开辟空间
这种设计也是有一定道理的,大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量最后:还有一个指针做一些其他事情。故总共占16+4+4+4=28个字节。
在这里插入图片描述

g++下string的结构

G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:
空间总大小
字符串有效长度
引用计数
指向堆空间的指针,用来存储字符串
在这里插入图片描述

3.string类的模拟实现

实现细节已在代码中注释,主要实现常用接口以及相关操作

  • string.h
#pragma once
#include<assert.h>
using namespace std;namespace ee
{class string{friend ostream& operator<<(ostream& out, const ee::string& s);friend istream& operator>>(istream& in, ee::string& s);public:typedef char* iterator;typedef const char* const_iterator;iterator begin(){//_str为指向字符串首元素地址的指针return _str;}iterator end(){//_size隐式转换成指针类型return  _size+_str;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}//构造函数,从C风格字符串创建string(const char* str=""){_size = strlen(str);_capacity = _size;_str = new char[_capacity + 1];//strcpy(_str, str);//strcpy遇到空白符会终止拷贝,memcpy拷贝整个字符串包括'\0'memcpy(_str, str, _size + 1);}//拷贝构造,接收一个对象的引用,复制现有来创建string(const string&s){_size = s._size;_capacity = _size;_str = new char[_size + 1];//strcpy(_str, s._str);memcpy(_str, s._str, _size + 1);}void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//赋值运算符重载,传统写法//string& operator=(const string& s)//{//	if (this != &s)//	{//		char*tmp= new char[s._capacity + 1];//		//加1是为了把'\0'带上//		memcpy(tmp, s._str, s._size+1);//		delete[]_str;//		_str = tmp;//		_size = s._size;//		_capacity = s._capacity;//	}//	return *this;//}//过渡写法,在函数体内调用拷贝构造进行交换//string& operator=(const string& s)//{//	if (this != &s)//	{//		string tmp(s);//		//this->swap(tmp);//		swap(tmp);//	}//	return *this;//}//现代写法,运用std库中swap函数进行交换string& operator=(string s)//传值传参{//传参过程中s已经进行一次深拷贝swap( s);return*this;}//析构函数~string(){_size = _capacity = 0;delete[] _str;_str = nullptr;}//将自定义字符串类对象转化为C风格字符串const char* c_str()const{return _str;}//有效数据个数size_t size()const{return _size;}//可读写char& operator[](size_t pos){assert(pos < _size);return _str[pos];}//只读const char& operator[](size_t pos)const{assert(pos < _size);return _str[pos];}//预存空间void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];//strcpy(tmp, _str);memcpy(tmp, _str, _size + 1);delete[] _str;_str = tmp;_capacity = n;}}//调整容量void resize(size_t n,char ch='\0'){//分三种情况,n小于容量,等于和大于,统一开辟一块新空间扩容if (n < _size){_size = n;_str[n] = '\0';}else{reserve(n+_size);//拷贝原数据for (size_t i = _size; i < _size+n; i++){_str[i] = ch;}_size += n;//最后一个位置手动赋值_str[_size] = '\0';}}void push_back(char ch){//先检查容量if (_size == _capacity){//采取二倍扩容reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;_size++;_str[_size] = '\0';}//追加函数void append(const char*str){int len = strlen(str);if (_size + len > _capacity){//_size+len可能比二倍容量还大,至少扩这么多reserve(_size + len);}//拷贝时从_size下一个位置开始,避免前面数据被覆盖//strcpy(_str+_size, str);memcpy(_str + _size, str, len + 1);_size+= len;}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}void insert(size_t pos, size_t n, char ch){assert(pos <= _size);if (_size + n > _capacity){reserve(_size + n);}//挪动数据size_t end = _size;while (end >= pos && end != npos){_str[end + n] = _str[end];end--;}for (size_t i = 0; i < n; i++){_str[pos] = ch;pos++;}_size += n;}void insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}//挪动数据size_t end = _size;while (end >= pos && end != npos){_str[end + len] = _str[end];end--;}//for (size_t i = 0; i < len; i++)//{//_str[pos + i] = str[i];//}//strcpy(_str + pos, str);memcpy(_str + pos, str, len + 1);_size += len;}void erase(size_t pos, size_t len = npos){//len给一个缺省值,代表默认完全删除assert(pos <= _size);//完全删除情况if (npos == len || len + pos >= _size){_str[pos] = '\0';_size = pos;}//部分删除情况(取中间一段删除)else{size_t end = len + pos;while (end <= _size){//挪动的数据_str[pos++] = _str[end++];}_size -= len;}}//从pos位置开始查找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* str, size_t pos=0){assert(pos < _size);//运用字符串中查找子字符串的函数const char* ptr = strstr(_str + pos, str);if (ptr){//返回子字符串在原始字符串中的位置//若直接返回ptr是指向子字符串的一个指针,而不是索引return ptr - _str;}else{return npos;}}string substr(size_t pos = 0, size_t len = npos){assert(pos < _size);size_t n = len;//要查找的子字符串就是原字符串的情况if (pos + len > _size|| len == npos){//第一个条件判断输入len是否超出长度,第二个条件判断是否输入len//为避免栈溢出应将len==npos放到第一个判断条件,若没有传入len的话就不用判断后条件n = _size - pos;}//返回一个新字符串string tmp;tmp.reserve(n);for (size_t i = pos; i < pos+n; i++){tmp += _str[i];}return tmp;}void clear(){_str[0] = '\0';_size = 0;}//手撕实现//bool operator<(const string& s)//{//	size_t i1 = 0;//	size_t i2 = 0;//	while (i1 < _size && i2 < _size)//	{//		if (_str[i1] <s. _str[i2])//		{//			return true;//		}//		else//		{//			return false;//		}//	}//	// "hello" "hello"   false//	// "helloxx" "hello" false//	// "hello" "helloxx" true//	//存在三种情况需要判断//	if (i1 == _size && i2 != _size)//	{//		return true;//	}//	else//	{//		return false;//	}//}//运用库函数实现bool operator<(const string& s)const{//memcpy不检查空字符,strcmp遇到'\0'就停止比较//指定比较小字符串的长度,指定大的会越界int ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);return ret == 0 ? _size < s._size : ret < 0;//当ret==0时,比较部分相等,长的大//当memcpy返回负值ret<0,返回true,memcpy返回正值,// 与条件判断语句第三部分ret<0相反,返回false}bool operator ==(const string&s)const{return _size == s._size&& memcmp(_str, s._str, _size) == 0;}//已知两个关系运算符,剩余直接复用即可bool operator>(const string&s)const{return !(*this< s)&&!( *this== s);}bool operator>=(const string& s)const{return !(*this < s);}bool operator<=(const string& s)const{return !(*this > s);}bool operator!=(const string& s) const{return !(*this == s);}private:size_t _size;size_t _capacity;char* _str;public:const static size_t npos;};const size_t string::npos = -1;//istream和ostream都是std库中的成员,使用需要加上命名空间(若没展开)ostream& operator << (ostream& out, const string& s){//ostream在库中定义是防拷贝的,得用引用/*for (size_t i = 0; i < s.size(); i++){//这里单个字符的打印就不需要友元去访问类的私有成员out << s[i];}*/for ( auto ch :s)//auto出来没有const属性,需手动添加{out << ch;}return out;}istream& operator >> (istream& in, string& s){s.clear();//>>流提取运算符和scanf一样,会跳过前导空白符,读取过程中// 遇到空格和换行符会终止char ch = in.get();//get每次读取一个字符while (ch == ' ' || ch == '\n')//循环条件为了跳过前导空白字符{ch = in.get();}//创建临时数组来接收字符,避免频繁开辟空间char buff[128];int i = 0;while (ch != ' ' && ch != '\n'){//改变条件可以跳过作为结束标志的字符buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}//索引i没到127不需要扩容直接跳出循环//没有将buff中临时变量提取到s中去if (i != 0){buff[i] = '\0';s += buff;}return in;}};

在这里插入图片描述

  • 测试test.cpp
#define  _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<string>
#include"string.h"
using namespace std;//测试:构造+迭代器
void teststring1()
{ee::string s1("keep going");cout << s1.c_str() << endl;ee::string s2;//为空即\0,遇到\0直接不打印cout << s2.c_str() << endl;//遍历方式for (size_t i = 0; i < s1.size(); i++){cout << s1[i] << " ";}cout << endl;//修改值/*for (size_t i = 0; i < s1.size(); i++){s1[i]++;}cout<< endl;*///还没重载<<运算符ee::string s3("keep going");auto it = s3.begin();while (it != s3.end()){(*it)++;cout << *it << " ";it++;}cout << endl;//范围forfor(auto ch:s3){cout << ch << " ";}cout << endl;
}
//测试:push_back/append
void teststring2()
{ee::string s1("keep going");cout << s1.c_str() << endl;s1.push_back('!');s1.append(" move on");cout << s1.c_str() << endl;}//测试:insert/erase
void teststring3()
{ee::string s1("keepgoing");s1.insert(9, "****");//字符串末尾默认为'\0',*后面的字符无法打印cout << s1.c_str() << endl;s1.insert(4, 6, '!');cout << s1.c_str() << endl;//s1.erase(s1.begin(), s1.end());s1.erase(4,6);cout << s1.c_str() << endl;s1.erase(1, 30);cout << s1.c_str() << endl;}//测试:substr
void teststring4()
{ee::string url = "ftp://www.baidu.com/?tn=65081411_1_oem_dg";size_t pos1 = url.find("://");if (pos1 != ee::string::npos){ee::string protocol = url.substr(0, pos1);cout << protocol.c_str() << endl;}size_t pos2 = url.find('/', pos1 + 3);if (pos2 != ee::string::npos){ee::string domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));ee::string uri = url.substr(pos2 + 1);cout << domain.c_str() << endl;cout << uri.c_str() << endl;}
}//测试:resize
void teststring5()
{ee::string s("hellow world");s.resize(8);cout << s.c_str() << endl;s.resize(15, 'i');cout << s.c_str() << endl;s.resize(11);cout << s.c_str() << endl;
}//测试:+=运算符
void teststring6()
{// c的字符数组,以\0为终止算长度// string不看\0,以size为终止算长度ee::string s("hello world");s += '\0';s += "6666666";cout << s.c_str() << endl;cout << s << endl;
}//测试:流插入,流提取
void teststring7()
{ee::string s;cin >> s;cout << s << endl;
}//测试:关系运算符
void teststring8()
{ee::string s1("hello");ee::string s2("hello");cout << (s1 < s2) << endl;cout << (s1 > s2) << endl;cout << (s1 == s2) << endl<<endl;ee::string s3("hello");ee::string s4("helloxxx");cout << (s3 < s4) << endl;cout << (s3 > s4) << endl;cout << (s3 == s4) << endl<<endl;ee::string s5("helloxxx");ee::string s6("hello");cout << (s5 < s6) << endl;cout << (s5 > s6) << endl;cout << (s5 == s6) << endl << endl;
}//测试:赋值运算符重载
void teststring9()
{ee::string s1("hello");ee::string s2(s1);cout << s1 << endl;cout << s2 << endl;ee::string s3("xxxxxxxxxxxxx");s1 = s3;cout << s1 << endl;cout << s3 << endl;
}
int main()
{teststring9();return 0;
}

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

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

相关文章

小红书不绑定手机号会显示ip吗

小红书作为一个生活方式分享平台&#xff0c;拥有庞大的用户群体。在小红书上&#xff0c;用户可以分享自己的生活点滴、购物心得、美食体验等&#xff0c;与其他用户进行互动交流。最近&#xff0c;不少用户对于小红书是否会在不绑定手机号的情况下显示IP属地产生了疑问&#…

Web-Machine-N7靶机实战攻略

1.安装并开启靶机 下载VirtualBox&#xff1a;https://www.virtualbox.org 导入虚拟机 设置为桥接模式 2.获取靶机IP Kali设为桥接模式 3.访问靶机 4.获取敏感目录文件和端口 gobuster dir -u http://172.16.2.68 -w /usr/share/wordlists/dirbuster/directory-list-2.3-me…

wsl配置指南

wsl配置步骤 1.安装2.列出当前的发行版3.导出要迁移的发行版&#xff0c;并指定导出的路径及文件名4.注销掉已经导出的发行版5.重新导入到新的路径&#xff0c;可以指定新的名称6.修改默认用户7.更换source8.配置gpu环境 1.安装 在microsoft store中搜索ubuntu&#xff0c;选择…

Linux|fork命令及其使用的写时拷贝技术

fork复制进程 fork通过以下步骤来复制进程&#xff1a; 分配新的进程控制块&#xff1a;内核为新进程分配一个新的进程控制块&#xff08;PCB&#xff09;&#xff0c;用于存储进程的相关信息&#xff0c;如进程 ID、状态、寄存器值、内存指针等。复制进程地址空间&#xff1…

Android Compose 框架基础按钮模块深度剖析(四)

Android Compose 框架基础按钮模块深度剖析 一、引言 在现代 Android 应用开发中&#xff0c;Android Compose 框架以其声明式编程范式和简洁高效的开发体验&#xff0c;逐渐成为开发者构建用户界面的首选。而注解在 Android Compose 框架中扮演着至关重要的角色&#xff0c;…

HarmonyOS开发,A持有B,B引用A的场景会不会导致内存泄漏,看这里!

问题 :A持有B,B引用A的场景会不会导致内存泄漏? 答案 :方舟虚拟机的内存管理和GC采用的是根可达算法,根可达算法可以解决循环引用问题,不会导致A引用B,B引用A的内存泄漏。 根可达算法原理 根可达算法以一系列被称为 “根对象”(如栈中的局部变量、静态变量等)作为起…

【数据库备份】docker中数据库备份脚本——MySql备份脚本

docker中数据库备份脚本——MySql备份脚本 #!/bin/bash# MySQL数据库信息 DB_USER"root" DB_PASSWORD"你的密码"# 备份保存主目录 BACKUP_ROOT"/data/data_backup/mysql"# 最多保留的备份日期文件夹数 MAX_DATE_FOLDERS15# 数组包含要备份的数据…

TCP、UDP协议的应用、ServerSocket和Socket、DatagramSocket和DatagramPacket

DAY13.1 Java核心基础 TCP协议 TCP 协议是面向连接的运算层协议&#xff0c;比较复杂&#xff0c;应用程序在使用TCP协议之前必须建立连接&#xff0c;才能传输数据&#xff0c;数据传输完毕之后需要释放连接 就好比现实生活中的打电话&#xff0c;首先确保电话打通了才能进…

Web爬虫利器FireCrawl:全方位助力AI训练与高效数据抓取

Web爬虫利器FireCrawl&#xff1a;全方位助力AI训练与高效数据抓取 一、FireCrawl 项目简介二、主要功能三、FireCrawl应用场景1. 大语言模型训练2. 检索增强生成&#xff08;RAG&#xff09;&#xff1a;3. 数据驱动的开发项目4. SEO 与内容优化5. 在线服务与工具集成 四、安装…

excel文件有两列,循环读取文件两列赋值到字典列表。字典的有两个key,分别为question和answer。将最终结果输出到json文件

import pandas as pd import json# 1. 读取 Excel 文件&#xff08;假设列名为 question 和 answer&#xff09; try:df pd.read_excel("input.xlsx", usecols["question", "answer"]) # 明确指定列 except Exception as e:print(f"读取文…

【C#】CS学习之Modbus通讯

摘要 本文详细描述了如何在在C#的Winform应用程序中使用NModbus库实现Modbus通讯&#xff0c;包括读取保持寄存器、以及相应的UI界面设计和事件处理。 前言 ​应用场景 Modbus 从站广泛应用于工业自动化领域&#xff1a; 1、传感器数据采集&#xff08;如温度、压力等&#xf…

windows环境下NER Python项目环境配置(内含真的从头安的perl配置)

注意 本文是基于完整项目的环境配置&#xff0c;即本身可运行项目你拿来用 其中有一些其他问题&#xff0c;知道的忽略即可 导入pycharm基本包怎么下就不说了&#xff08;这个都问&#xff1f;给你一拳o(&#xff40;ω*)o&#xff09; 看perl跳转第5条 1.predict报错多个设备…

使用DDR4控制器实现多通道数据读写(四)

在创建完DDR4的仿真模型后&#xff0c;我们为了实现异步时钟的读写&#xff0c;板卡中在PL端提供了一组差分时钟&#xff0c;可以用它通过vivado中的Clock Wizard IP核生成多个时钟&#xff0c;在这里生成两个输出时钟&#xff0c;分别作为用户的读写时钟&#xff0c;这样就可以…

企业数字化20项目规划建设方案微服务场景与数据应用(50页PPT)(文末有下载方式)

资料解读&#xff1a;企业数字化 2.0 项目规划建设方案微服务场景与数据应用 详细资料请看本解读文章的最后内容。 在数字化浪潮的席卷下&#xff0c;企业数字化转型已成为提升竞争力、实现可持续发展的关键路径。这份《企业数字化 2.0 项目规划建设方案微服务场景与数据应用》…

Oracle OCP认证是否值得考?

Oracle OCP&#xff08;Oracle Certified Professional&#xff09;认证是数据库领域的传统权威认证&#xff0c;但随着云数据库和开源技术的崛起&#xff0c;其价值正面临分化。是否值得考取&#xff0c;需结合你的职业定位、行业需求及长期规划综合判断。以下是关键分析&…

蓝桥杯之AT24C02的页写页读

一、原理&#xff1a; 1、页写&#xff1a;一次性向AT24C02里的多个数据存储单元地址写入多个数据 &#xff08;1&#xff09;在AT24C02的页写模式下&#xff0c;每次写入数据后&#xff0c;存储单元地址会自动加1。 &#xff08;2&#xff09;一页有8个数据存储单元&#xff…

大白话详细解读函数之柯里化

1. 函数柯里化是什么&#xff1f; 函数柯里化是一种将多参数函数转换成一系列单参数函数的技术。简单来说&#xff0c;就是把一个接收多个参数的函数&#xff0c;变成每次只接收一个参数&#xff0c;并返回一个新函数&#xff0c;直到所有参数都接收完毕&#xff0c;最后返回结…

【C++网络编程】第2篇:简单的TCP服务器与客户端

一、TCP通信流程回顾 1. 服务器端流程 1. 创建Socket → socket() 2. 绑定地址和端口 → bind() 3. 开始监听 → listen() 4. 接受客户端连接 → accept() 5. 接收/发送数据 → recv()/send() 6. 关闭连接 → closesocket()2. 客户端流程 1. 创建Socket → socket() 2. 连接…

Spring IoC DI入门

一、Spring&#xff0c;Spring Boot和Spring MVC的联系及区别 Spring是另外两个框架的基础&#xff0c;是Java生态系统的核心框架&#xff0c;而SpringMVC是Spring 的子模块&#xff0c;专注于 Web 层开发&#xff0c;基于 MVC 设计模式&#xff08;模型-视图-控制器&#xff…

【uniapp】记录tabBar不显示踩坑记录

由于很久没有使用uniapp了&#xff0c;官方文档看着又杂乱&#xff0c;底部tab导航栏一直没显示&#xff0c;苦思许久&#xff0c;没有发现原因&#xff0c;最后网上搜到帖子&#xff0c;list里的第一个数据&#xff0c;pages 的第一个 path 必须与 tabBar 的第一个 pagePath 相…