C++ STL string类使用及实现详解

1. string简介

C语言中,可以用字符数组来存储字符串,如:

char ch[] = "hello world";

C++中,可以使用string类对象来存储字符串,使用起来比C语言中的字符数组要方便得多,而且不用考虑容量的问题。

本篇博客,我会以实用为主,介绍string类常用的接口,包括常见的构造函数、容量操作、访问及遍历操作、修改操作、非成员函数等等。接着通过这些知识做几道练习题,通过题目,了解这些接口实际的用法。最后,我会讲解string的底层实现原理,并模拟实现。

由于是实用为主,某些不常用的接口我不做介绍。

2. string类对象的构造

2.1 string();

无参的构造函数,可以构造出空的string类对象,即空字符串。

string s; // 等价于string s("");

2.2 string(const char* s);

用C-string来构造string类对象。

string s("hello world");
cout << s << endl;

输出:

hello world

2.3 string(size_t n, char c);

string类对象中包含n个字符c。

string s(5, 'a');
cout << s << endl;

输出:

aaaaa

2.4 string(const string& s);

拷贝构造函数,使用一个string类对象构造另一个string类对象。

string s1("hello world");
string s2(s1);
cout << s1 << endl;
cout << s2 << endl;

输出:

hello world
hello world

2.5 使用迭代器构造

template<class InputIterator>
string(InputIterator first, InputIterator last);

利用任意类型的迭代器进行构造。

string s1("hello world");
string s2(++s1.begin(), --s1.end());
cout << s2 << endl;

 输出:

ello worl

3. string类对象的容量操作

3.1 size

size_t size() const;

返回字符串有效字符长度,同length。引入size是为了与其他容器接口保持一致,我习惯使用size而不是length。

string s("hello world");
cout << s.size() << endl;

输出:

11

3.2 length

size_t length() const;

同size,返回字符串有效字符长度。

string s("hello world");
cout << s.length() << endl;

输出:

11

3.3 capacity

size_t capacity() const;

返回空间总大小,至少是size。

string s("hello world");
cout << s.capacity() << endl;

visual studio 2022环境下输出:

15

3.4 empty

bool empty() const;

检测字符串是否为空串,是返回true,否则返回false。

string s1;
cout << s1.empty() << endl;

输出: 

1

3.5 clear

void clear();

清空有效字符,一般不改变底层空间大小。

string s("hello world");
s.clear();
cout << s.empty() << endl;

输出:

1

3.6 reserve

void reserve(size_t n = 0);

为字符串预留空间,不改变有效元素个数。当n比底层空间大时,会扩容,扩容后空间至少为n;当n比底层空间小时,一般不改变底层空间。

string s;
s.reserve(100);
cout << s.capacity() << endl;

visual studio 2022环境下输出:

111

3.7 resize

void resize(size_t n, char c = '\0');

将有效字符的个数改成n个。当n比字符串长度小时,只保留前n个字符;当n比字符串长度大时,会在后面填充字符c,直到字符串长度为n;当n比字符串底层空间大时,会扩容,扩容后空间至少为n,相当于执行了reserve(n)。

string s;
s.resize(5, 'x');
cout << s << endl;
s.resize(7, 'y');
cout << s << endl;
s.resize(3);
cout << s << endl;

输出:

xxxxx
xxxxxyy
xxx

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

4.1 operator[]

char& operator[](size_t pos);
const char& operator[](size_t pos) const;

返回pos位置的字符。非const对象调用后返回的字符可读可写,const对象调用后返回的字符只读不写。pos必须在有效的下标范围内。

string s1("hello world");
// 可读可写
s1[0] = 'H'; // "Hello world"
cout << s1[0] << endl; // 'H'

const string s2("hello world");
// 只读不写
// s2[0] = 'H'; // 错误用法
cout << s2[0] << endl; // 'h'

输出:

H
h

4.2 begin + end

iterator begin();
const_iterator begin() const;
iterator end();
const_iterator end() const;

begin获取指向第一个字符的迭代器,end获取指向最后一个字符的下一个位置的迭代器。

非const对象调用后返回的iterator指向的字符可读可写,const对象调用后返回的const_iterator指向的字符只读不写。

string s1("hello world");
string::iterator it = s1.begin();
// 可读可写
while (it != s1.end())
{
    (*it)++;
    cout << *it << " ";
    ++it;
}
cout << endl;

const string s2("hello world");
// 只读不写
string::const_iterator cit = s2.begin();
while (cit != s2.end())
{
    // (*cit)++; // 错误用法
    cout << *cit << " ";
    ++cit;
}
cout << endl;

输出:

i f m m p ! x p s m e 
h e l l o   w o r l d 

4.3 rbegin + rend

reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
reverse_iterator rend();
const_reverse_iterator rend() const;

同begin和end,只不过是反向的。非const对象调用后返回的reverse_iterator指向的字符可读可写,const对象调用后返回的const_reverse_iterator指向的字符只读不写。

string s1("hello world");
string::reverse_iterator rit = s1.rbegin();
// 可读可写
while (rit != s1.rend())
{
    (*rit)++;
    cout << *rit << " ";
    ++rit;
}
cout << endl;

const string s2("hello world");
// 只读不写
string::const_reverse_iterator crit = s2.rbegin();
while (crit != s2.rend())
{
    // (*crit)++; // 错误用法
    cout << *crit << " ";
    ++crit;
}
cout << endl;

输出:

e m s p x ! p m m f i 
d l r o w   o l l e h 

4.4 范围for

遍历string类对象更简洁的形式,底层会转换为迭代器。

string s("hello world");
for (auto ch : s)
{
    cout << ch << " ";
}
cout << endl;

输出:

h e l l o   w o r l d 

5. string类对象的修改操作

5.1 push_back

void push_back(char c);

在字符串后尾插字符c。

string s("hello world");
s.push_back('!');
cout << s << endl;

输出:

hello world!

5.2 append

string& append(const string& str);
string& append(const char* s);
string& append(size_t n, char c);

在字符串后追加一个字符串。

string s1("abc");
string s2("defg");
s1.append(s2);
cout << s1 << endl;
s1.append("hijkl");
cout << s1 << endl;
s1.append(2, 'm');
cout << s1 << endl;

输出:

abcdefg
abcdefghijkl
abcdefghijklmm

5.3 operator+=

string& operator+=(const string& str);
string& operator+=(const char* s);
string& operator+=(char c);

在字符串后追加一个字符串或者字符。

string s1("abc");
string s2("defg");
s1 += s2;
cout << s1 << endl;
s1 += "hijkl";
cout << s1 << endl;
s1 += 'm';
cout << s1 << endl;

输出: 

abcdefg
abcdefghijkl
abcdefghijklm

5.4 c_str

const char* c_str() const;

返回一个C格式字符串,以字符'\0'结尾。

string s("abcde");
cout << s.c_str() << endl;

输出:

abcde

5.5 find

size_t find(const string& str, size_t pos = 0) const;
size_t find(const char* s, size_t pos = 0) const;
size_t find(char c, size_t pos = 0) const;
static const size_t npos = -1;

从pos位置开始,往后找字符串或字符,返回该字符串或字符第一次出现的位置。若找不到,返回npos。

string s1("hello world");
size_t ret = s1.find('w');
if (ret != string::npos)
    cout << s1[ret] << endl;

ret = s1.find("wor");
if (ret != string::npos)
    cout << s1[ret] << endl;

string s2("or");
ret = s1.find(s2);
if (ret != string::npos)
    cout << s1[ret] << endl;

输出:

w
w
o

5.6 rfind

size_t rfind(const string& str, size_t pos = npos) const;
size_t rfind(const char* s, size_t pos = npos) const;
size_t rfind(char c, size_t pos = npos) const;
static const size_t npos = -1;

类似find,从pos位置开始,往前找字符串或字符。相当于返回该字符串或字符最后一次出现的位置。若找不到,返回npos。

5.7 substr

string substr(size_t pos = 0, size_t len = npos) const;

从pos位置开始,向后截取len个字符并返回。若len太大,截取时超出字符串范围,则截取到字符串的尾部。

string s("hello world");
string ret = s.substr(6, 3);
cout << ret << endl;

输出:

wor

6. string类非成员函数

6.1 operator+

string operator+(const string& str1, const string& str2);
string operator+(const char* s, const string& str);
string operator+(const string& str, const char* s);
string operator+(char& c, const string& str);
string operator+(const string& str, char& c);

拼接字符串和字符串或者字符。尽可能少用,因为传值返回导致深拷贝,效率低。

string s1("hello ");
string s2("world");
cout << s1 + s2 << endl;
cout << s1 + "world" << endl;
cout << "hello " + s2 << endl;
cout << s1 + 'a' << endl;
cout << 'b' + s2 << endl;

输出:

hello world
hello world
hello world
hello a
bworld

6.2 operator>>

istream& operator>>(istream& in, string& str);

流插入运算符重载,以空格、换行作为分隔符。

string s("hello world");
cin >> s;
cout << s << endl;

输出示例(第一行为输入):

abc def
abc

6.3 operator<<

ostream& operator<<(ostream& out, const string& str);

流提取运算符重载。

string s("hello world");
cout << s << endl;

输出:

hello world

6.4 getline

istream& getline(istream& in, string& str, char delim = '\n');

获取字符并存储到字符串中,直到遇到字符delim,返回该字符串。该字符串不包括delim。一般用来获取一行字符串。

string s;
getline(cin, s);
cout << s << endl;

输出示例(第一行为输入):

abc def
abc def

6.5 关系运算符

bool operator>(const string& str1, const string& str2);
bool operator>(const string& str, const char* s);
bool operator>(const char* s, const string& str);
bool operator<(const string& str1, const string& str2);
bool operator<(const string& str, const char* s);
bool operator<(const char* s, const string& str);
bool operator>=(const string& str1, const string& str2);
bool operator>=(const string& str, const char* s);
bool operator>=(const char* s, const string& str);
bool operator<=(const string& str1, const string& str2);
bool operator<=(const string& str, const char* s);
bool operator<=(const char* s, const string& str);
bool operator==(const string& str1, const string& str2);
bool operator==(const string& str, const char* s);
bool operator==(const char* s, const string& str);
bool operator!=(const string& str1, const string& str2);
bool operator!=(const string& str, const char* s);
bool operator!=(const char* s, const string& str);

比较字符串的大小,比较的是字符串对应位置的字符的ASCII码。

7. 练习

7.1 仅仅反转字母

仅仅反转字母icon-default.png?t=N7T8https://leetcode.cn/problems/reverse-only-letters/description/

定义左指针left和右指针right,两个指针从两边向中间移动,直到相遇为止。若遇到字母,就交换左右指针指向的字符。

class Solution {
public:
    string reverseOnlyLetters(string s) {
        int left = 0, right = s.size() - 1;
        while (left < right)
        {
            // 左边找字母
            while (left < right && !isalpha(s[left]))
                ++left;
            // 右边找字母
            while (left < right && !isalpha(s[right]))
                --right;

            swap(s[left++], s[right--]);
        }

        return s;
    }
};

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

找字符串中第一个只出现一次的字符原题链接icon-default.png?t=N7T8https://leetcode.cn/problems/first-unique-character-in-a-string/description/

字符串中只会出现小写字母,所以可以用计数排序的思路,定义数组countA记录每个字符出现的次数,把每个字符ch映射到下标为ch-'a'的位置。第一次遍历字符串,统计出每个字符出现的次数。第二次遍历字符串,查找只出现一次的字符。

class Solution {
public:
    int firstUniqChar(string s) {
        int countA[26] = {0};
        for (auto ch : s)
            countA[ch - 'a']++;

        for (int i = 0; i < s.size(); ++i)
            if (countA[s[i] - 'a'] == 1)
                return i;

        return -1;
    }
};

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

字符串里面最后一个单词的长度原题链接icon-default.png?t=N7T8https://www.nowcoder.com/practice/8c949ea5f36f422594b306a2300315da?tpId=37&&tqId=21224&rp=5&ru=/activity/oj&qru=/ta/huawei/question-ranking

由于输入包含空格,所以要使用getline获取字符串。接着使用rfind查找最后一个空格,从而确定最后一个单词的长度。

#include <iostream>
using namespace std;

int main() {
    string s;
    getline(cin, s);
    // 查找最后一个空格
    size_t ret = s.rfind(' ');
    if (ret != string::npos)
        cout << s.size() - ret - 1 << endl;
    else // 字符串没有空格
        cout << s.size() << endl;

    return 0;
}

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

验证一个字符串是否是回文原题链接icon-default.png?t=N7T8https://leetcode.cn/problems/valid-palindrome/description/

定义左指针left和右指针right,两个指针从两边向中间移动,直到相遇为止。若遇到数字或字母,判断它们指向的字符转小写字母后是否相同,从而判断是否是回文串。

class Solution {
public:
    bool isPalindrome(string s) {
        int left = 0, right = s.size() - 1;
        while (left < right)
        {
            // 左边找数字字母
            while (left < right && !isalnum(s[left]))
                ++left;
            // 右边找数字字母
            while (left < right && !isalnum(s[right]))
                --right;

            if (tolower(s[left++]) != tolower(s[right--]))
                return false;
        }

        return true;
    }
};

7.5 字符串相加 

字符串相加原题链接icon-default.png?t=N7T8https://leetcode.cn/problems/add-strings/description/

从最低位开始,每次取对应位的数字相加,并计算进位。考虑把相加的结果插入到字符串头部,但是反复的头插效率太低,所以应把相加的结果尾插到字符串中,再整体反转。注意到插入的过程中可能会多次扩容,所以先用reserve保留足够的空间,避免扩容的消耗。

class Solution {
public:
    string addStrings(string num1, string num2) {
        string ret;
        ret.reserve(max(num1.size(), num2.size()) + 1);
        int carry = 0; // 进位
        int end1 = num1.size() - 1, end2 = num2.size() - 1;
        while (end1 >= 0 || end2 >= 0)
        {
            // 取出对应位置的数字
            int n1 = end1 >= 0 ? num1[end1] - '0' : 0;
            int n2 = end2 >= 0 ? num2[end2] - '0' : 0;
            
            int sum = n1 + n2 + carry;
            carry = sum / 10;
            ret += ((sum % 10) + '0');

            --end1;
            --end2;
        }

        if (carry == 1)
            ret += '1';

        reverse(ret.begin(), ret.end());
        return ret;
    }
};

8. 模拟实现

8.1 最简易实现思路

如果不考虑容量问题,理论上只需要一个char*类型的指针就能管理字符串了。要实现一个最简易的string,只需要实现利用C-string的构造函数、拷贝构造、赋值运算符重载以及析构函数。除此之外,可以顺手实现C++11中的移动构造和移动赋值,以及size、c_str、关系运算符、operator[]等接口。

8.1.1 C-string构造

利用C-string构造,只需要new出足够大的空间,多开一个空间存储'\0'。

string(const char* s = "")
	:_str(new char[strlen(s)+1])
{
	strcpy(_str, s);
}

8.1.2 拷贝构造

拷贝构造,可以利用C++标准规定的现代写法,转为利用C-string构造。

string(const string& str)
	:string(str._str)
{}

8.1.3 赋值运算符重载

赋值运算符重载,仍然使用现代写法,采取传值传参。

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

其中swap交换对应的_str指针即可。

void swap(string& str)
{
	std::swap(str._str, _str);
}

8.1.4 析构函数

析构函数直接delete即可。

~string()
{
	delete[] _str;
	_str = nullptr;
}

8.1.5 移动构造和移动赋值

C++11中的移动构造和移动赋值,只需要交换_str指针。

string(string&& str) noexcept
	:_str(str._str)
{
	str._str = nullptr;
}

string& operator=(string&& tmp) noexcept
{
	swap(tmp);
	return *this;
}

8.1.6 其它接口

接下来c_str、size、operator[]都可以顺手实现。

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

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

char& operator[](size_t pos)
{
	assert(pos <= size());
	return _str[pos];
}

const char& operator[](size_t pos) const
{
	assert(pos <= size());
	return _str[pos];
}

关系运算符重载作为非成员函数,只需实现>和==,其余复用即可。

bool operator>(const string& str1, const string& str2)
{
	return strcmp(str1.c_str(), str2.c_str()) > 0;
}

bool operator==(const string& str1, const string& str2)
{
	return strcmp(str1.c_str(), str2.c_str()) == 0;
}

bool operator>=(const string& str1, const string& str2)
{
	return str1 > str2 || str1 == str2;
}

bool operator<=(const string& str1, const string& str2)
{
	return !(str1 > str2);
}

bool operator!=(const string& str1, const string& str2)
{
	return !(str1 == str2);
}

bool operator<(const string& str1, const string& str2)
{
	return !(str1 >= str2);
}

8.1.7 完整实现

完整的实现及测试代码如下:

#include<iostream>
#include<string.h>
#include<assert.h>
#include<utility>
using namespace std;

namespace xbl
{
	class string
	{
	public:
		string(const char* s = "")
			:_str(new char[strlen(s)+1])
		{
			strcpy(_str, s);
		}

		string(const string& str)
			:string(str._str)
		{}

		string(string&& str) noexcept
			:_str(str._str)
		{
			str._str = nullptr;
		}

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

		string& operator=(string&& tmp) noexcept
		{
			swap(tmp);
			return *this;
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
		}

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

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

		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 swap(string& str)
		{
			std::swap(str._str, _str);
		}
	private:
		char* _str;
	};

	bool operator>(const string& str1, const string& str2)
	{
		return strcmp(str1.c_str(), str2.c_str()) > 0;
	}

	bool operator==(const string& str1, const string& str2)
	{
		return strcmp(str1.c_str(), str2.c_str()) == 0;
	}

	bool operator>=(const string& str1, const string& str2)
	{
		return str1 > str2 || str1 == str2;
	}

	bool operator<=(const string& str1, const string& str2)
	{
		return !(str1 > str2);
	}

	bool operator!=(const string& str1, const string& str2)
	{
		return !(str1 == str2);
	}

	bool operator<(const string& str1, const string& str2)
	{
		return !(str1 >= str2);
	}

	void test_string()
	{
		string s1;
		cout << s1.c_str() << endl;
		
		string s2("hello world");
		cout << s2.size() << endl;
		cout << s2[1] << endl;
		string s3(s2);
		string s4 = s3;
		s1 = s2;
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
		cout << s3.c_str() << endl;
		cout << s4.c_str() << endl;

		cout << (s1 == s2) << endl;
	}
}

输出:


11
e
hello world
hello world
hello world
hello world
1

8.2 带size和capacity的版本

8.1的实现思路适用于面试时,被要求模拟实现string。特点是实现简单,但效率不高,因为strlen遍历字符串的时间复杂度是O(N)。一个功能完善的string可以考虑带上size_t类型的_size和_capacity成员变量,分别用来记录有效元素的个数和容量。

8.2.1 C-string构造

这样C-string的构造函数就可以这样写。

string(const char* s = "")
	:_size(strlen(s))
{
	_capacity = _size == 0 ? 3 : _size;
	_str = new char[_capacity + 1];
	strcpy(_str, s);
}

观察监视窗口:

8.2.2 拷贝构造

拷贝构造不能简单地利用C-string构造实现了,这是因为下面的情况中,C-string以'\0'结尾,那么'\0'后面的字符就不会被识别为有效字符。

所以,正确的做法是,使用memcpy,把空间上的有效数据拷贝过去。

string(const string& str)
{
	char* tmp = new char[str._size + 1];
	memcpy(tmp, str._str, (str._size + 1) * sizeof(char));
	_str = tmp;
	_size = _capacity = str._size;
}

观察监视窗口:

8.2.3 赋值运算符重载

采用现代写法,利用传值传参的深拷贝。

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

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

8.2.4 析构函数

释放_str指针指向的空间。

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

8.2.5 移动构造和移动赋值

交换对应的数据即可。

string(string&& str) noexcept
	:_str(nullptr)
{
	swap(str);
}

string& operator=(string&& str) noexcept
{
	swap(str);
	return *this;
}

8.2.6 c_str + size + capacity

返回对应的成员变量。

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

size_t size() const
{
	return _size;
}

size_t capacity() const
{
	return _capacity;
}

8.2.7 operator[]

实现const和非const两个版本。

char& operator[](size_t pos)
{
	assert(pos < _size);
	return _str[pos];
}

const char& operator[](size_t pos) const
{
	assert(pos < _size);
	return _str[pos];
}

8.2.8 关系运算符

不能直接使用strcmp,因为下面的情况中,如果使用C-string比较,s1和s2应该相等,但事实上,s1<s2,原因是s2更长。

std::string s1("hello world");
std::string s2("hello world");
s2 += '\0';

cout << (s1 == s2) << endl;
cout << (s1 < s2) << endl;

输出:

0
1

所以,要手动比较。先考虑operator<,同时遍历两个字符串,若遇到不相等的字符,则比较结束,结果是“小于”或者“大于”,判断是否是“小于”即可。若其中一个字符串遍历完了,那么长的字符串更大。再考虑operator==,当两个字符串的size相同,且所有字符均相同,则字符串相等。其余的关系运算符复用operator<和operator==即可。

bool operator<(const string& str1, const string& str2)
{
	size_t i = 0;
	while (i < str1.size() && i < str2.size())
	{
		if (str1[i] != str2[i])
		{
			return str1[i] < str2[i];
		}
		++i;
	}

	// 若有效字符均相等,则长的字符串更大
	return str1.size() < str2.size();
}

bool operator==(const string& str1, const string& str2)
{
	if (str1.size() != str2.size())
	{
		return false;
	}

	// size相同
	for (size_t i = 0; i < str1.size(); ++i)
	{
		if (str1[i] != str2[i])
		{
			return false;
		}
	}

	return true;
}

bool operator<=(const string& str1, const string& str2)
{
	return str1 < str2 || str1 == str2;
}

bool operator>(const string& str1, const string& str2)
{
	return !(str1 <= str2);
}

bool operator>=(const string& str1, const string& str2)
{
	return !(str1 < str2);
}

bool operator!=(const string& str1, const string& str2)
{
	return !(str1 == str2);
}

8.2.9 持续更新中

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

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

相关文章

C#,普洛尼克数(Pronic Number)的算法与源代码

1 普洛尼克数(pronic number) 普洛尼克数(pronic number)&#xff0c;也叫矩形数、欧波朗数(oblong number)&#xff0c;是两个连续非负整数的积&#xff0c;即mn*(n1)。第n个普洛尼克数侪是n个三角形数个两倍。 2 计算结果 3 源程序 using System; namespace Legalsoft.Tru…

c++之说_14|左值引用与右值引用

提起左右值引用我就头疼 左值&#xff1a; 1、在内存中开辟了空间的便叫左值 2、左值不一定可以赋值 如字符串常量 3、左值可以取地址 右值&#xff1a; 1、在内存中没有开辟空间的 2、右值无法取地址 如&#xff1a; 立即数&#xff08;1&#xff0c;2&#xff0c;3…

Unity类银河恶魔城学习记录7-1 P67 Sword Throw Skill State源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili Sword_Skill.cs using System.Collections; using System.Collections.Gen…

nba2k24 韩旭面补

nba2k23-24 韩旭面补 nba2k23-nba2k24通用 韩旭面补 下载地址&#xff1a; https://www.changyouzuhao.cn/9605.html

【原创 附源码】Flutter安卓及iOS海外登录--Tiktok登录最详细流程

最近接触了几个海外登录的平台&#xff0c;踩了很多坑&#xff0c;也总结了很多东西&#xff0c;决定记录下来给路过的兄弟坐个参考&#xff0c;也留着以后留着回顾。更新时间为2024年2月7日&#xff0c;后续集成方式可能会有变动&#xff0c;所以目前的集成流程仅供参考&#…

Flex布局 (上万字)超详细讲解 这篇就够了

一、Flex概述 Flex布局&#xff0c;全称为“Flexible Box Layout”&#xff0c;意为“弹性盒布局”。它是一种现代的CSS布局模式&#xff0c;旨在提供一种更有效的方式来布局、对齐和分配容器中项目之间的空间&#xff0c;即使它们的大小未知或动态变化。 Flex布局的主要特点…

Unity类银河恶魔城学习记录6-2 P66 Clone‘s Attack源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili Clone_Skill.cs using System.Collections; using System.Collections.Gen…

二叉搜索树删除操作的递归与非递归写法

如何进行删除操作 对于二叉搜索树的删除操作&#xff0c;主要分为以下3种情况讨论&#xff1a; 1、删除的结点没有左右孩子 2、删除的结点只有一个孩子 3、删除的结点有左右孩子 所以&#xff0c;我们将会用if…else…分为最多3种情况讨论&#xff08;实际上只分了两种&#x…

关于java的多线程初识

关于java的多线程初识 我们从今天开始&#xff0c;正式学习java的多线程&#xff0c;我们在前面的文章中学习到了java的基础&#xff0c; 但是距离我们工作实战还差的很远&#xff0c;我们学习好了基础&#xff0c;以后的文章会逐步的深入&#xff0c;去讲解各种前端框架&…

同余数论性质

同余概念 当 a%m b%m&#xff0c;说明a和b同余&#xff0c;写作若 a≡b(mod m) 性质 衍生出几条性质 1.m | abs(a-b)&#xff0c;即|a-b|是m的倍数。&#xff08;注意&#xff0c;0是任何数的倍数&#xff09; 2.当a≡b(mod m)&#xff0c;c≡d(mod m)&#xff0c; 有ac…

IDEA Ultimate下载(采用JetBrain学生认证)

IDEA Ultimate版本下载 Ulitmate是无限制版&#xff08;解锁所有插件&#xff0c;正版需要付费。学生可以免费申请许可&#xff09;Community是开源社区版本&#xff08;部分插件不提供使用&#xff0c;比如Tomcat插件。免费&#xff09; 我们将通过学生认证获取免费版。 Je…

【vue3学习笔记】shallowReactive与shallowRef;readOnly与shallowReadOnly;toRaw与markRaw

尚硅谷Vue2.0Vue3.0全套教程丨vuejs从入门到精通 课程 P158节 《shallowReactive与shallowRef》笔记&#xff1a; reactive()与shallowReactive()&#xff1a;reactive()处理后的数据是响应式的&#xff0c;对象内嵌套的深层结构全部是响应式的。shallowReactive()处理后的数据…

闭环控制系统手自动策略(车辆定速巡航应用)

闭环控制系统的手自动策略并不会完全一样&#xff0c;不同的行业&#xff0c;基于不同的规范和安全考虑给出的手自动策略是不一样的&#xff0c;这里我们介绍汽车行业定速巡航应用。 PID闭环控制系统手自动切换的相关文章&#xff0c;还可以查看下面链接&#xff1a; 无扰切换…

2013-2022年上市公司迪博内部控制指数、内部控制分项指数数据

2013-2022年上市公司迪博内部控制指数、分项指数数据 1、时间&#xff1a;2013-2022年 2、范围&#xff1a;上市公司 3、指标&#xff1a;证券代码、证券简称、辖区、证监会行业、申万行业、内部控制指数、战略层级指数、经营层级指数、报告可靠指数、合法合规指数、资产安全…

基于Locust实现MQTT协议服务的压测脚本

一、背景简介 业务背景大概介绍一下&#xff0c;就是按照国标规定&#xff0c;车辆需要上传一些指定的数据到ZF的指定平台&#xff0c;同时车辆也会把数据传到企业云端服务上&#xff0c;于是乎就产生了一些性能需求。 目前我们只是先简单的进行了一个性能场景的测试&#xf…

C++进阶(十五)C++的类型转换

&#x1f4d8;北尘_&#xff1a;个人主页 &#x1f30e;个人专栏:《Linux操作系统》《经典算法试题 》《C》 《数据结构与算法》 ☀️走在路上&#xff0c;不忘来时的初心 文章目录 一、C语言中的类型转换二、为什么C需要四种类型转换三、C强制类型转换1、static_cast2、reint…

中国电子学会2019年12月份青少年软件编程Scratch图形化等级考试试卷三级真题(选择题、判断题)

一、单选题(共 25 题&#xff0c;每题 2 分&#xff0c;共 50 分) 1.怎样修改图章的颜色&#xff1f;&#xff08; &#xff09; A. 只需要一个数字来设置颜色 B. 设置 RGB 的值 C. 在画笔中设置颜色、饱和度、亮度 D. 在外观中设置或修改角色颜色特效 2.以下程序的执…

2024年Midjourney 付费订阅流程 | Midjourney 各版本介绍,使用虚拟信用卡支付买Midjourney流程指南

1.Midjourney介绍 Midjourney 是一款备受欢迎的人工智能生成图像工具&#xff0c;它可以通过输入文字描述&#xff0c;自动生成精美的图像。与许多其他图像生成工具不同&#xff0c;Midjourney 不需要安装任何软件&#xff0c;也不受个人电脑性能的限制&#xff0c;因为它运行…

计算机毕业设计SSM基于的冷链食品物流信息管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; vue mybatis Maven mysql5.7或8.0等等组成&#xff0c;B…

微信小程序(四十二)wechat-http拦截器

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.wechat-http请求的封装 2.wechat-http请求的拦截器的用法演示 源码&#xff1a; utils/http.js import http from "wechat-http"//设置全局默认请求地址 http.baseURL "https://live-api.ith…