【C++指南】告別C字符串陷阱:如何實現封裝string?

?

🌟?各位看官好,我是egoist2023

🌍?種一棵樹最好是十年前,其次是現在!

💬?注意:本章節只詳講string中常用接口及實現,有其他需求查閱文檔介紹。

🚀?今天通過了解string接口,從而實現封裝自己的string類達到類似功能。

👍?如果覺得這篇文章有幫助,歡迎您一鍵三連,分享給更多人哦!

引入

C 語言中,字符串是以 '\0' 結尾的一些字符的集合,為了操作方便, C 標準庫中提供了一些 str 系列
的庫函數,但是這些庫函數與字符串是分離開的,不太符合 OOP 的思想,而且底層空間需要用戶
自己管理,稍不留神可能還會越界訪問。因此在C++中string用封裝的方式解決了這一問題。

string類的文檔介紹?--> 如有需要自行查閱文檔中接口實現。

auto和范圍for

auto關鍵字(自動推導類型

  • 在早期C/C++auto的含義是:使用auto修飾的變量,是具有自動存儲器的局部變量,后來這個不重要了。C++11中,標準委員會變廢為寶賦予了auto全新的含義即:auto不再是一個存儲類型指示符,而是作為一個新的類型指示符來指示編譯器,auto聲明的變量必須由編譯器在編譯時期推導而得
  • auto聲明指針類型時,用autoauto*沒有任何區別,但用auto聲明引用類型時則必須加&。
  • 當在同一行聲明多個變量時,這些變量必須是相同的類型,否則編譯器將會報錯,因為編譯器實際只對第一個類型進行推導,然后用推導出來的類型定義其他變量
  • auto不能作為函數的參數,可以做返回值,但謹慎使用。
  • auto不能直接用來聲明數組。

范圍for(底層就是迭代器

  • 對于一個有范圍的集合而言,由程序員來說明循環的范圍是多余的,有時候還會容易犯錯誤。因此C++11中引入了基于范圍的for循環。for循環后的括號由冒號分為兩部分:第一部分是范圍內用于迭代的變量,第二部分則表示被迭代的范圍,自動迭代,自動取數據,自動判斷結束。
  • 范圍for可以作用到數組和容器對象上進行遍歷
  • 范圍for的底層很簡單,容器遍歷實際就是替換為迭代器,這個從匯編層也可以看到。

了解string常用接口

?1.常見構造

(constructor) 函數名稱
功能說明
string() (重點)
構造空的 string 類對象,即空字符串
string(const char* s) (重點)
C-string 來構造 string 類對象
string(size_t n, char c)
string 類對象中包含 n 個字符c
string(const string&s) (重點)
拷貝構造函數

2.容量操作

函數名稱
功能說明

size(重點)

返回字符串有效字符長度
length
返回字符串有效字符長度
capacity
返回空間總大小
empty
檢測字符串釋放為空串,是返回 true ,否則返回 false
clear
清空有效字符(不改變底層空間大小)
reserve
為字符串預留空間
resize
將有效字符的個數該成 n 個,多出的空間用字符 c 填充
注意:
1. size() length() 方法底層實現原理完全相同,引入 size() 的原因是保持與其他接口容器一致,而length函數是由于歷史原因遺留的。
2. resize(size_t n) resize(size_t n, char c) 都是將字符串中有效字符個數改變到 n 個,不
同的是當字符個數增多時: resize(n) 0 來填充多出的元素空間, resize(size_t n, char
c) 用字符 c 來填充多出的元素空間。注意: resize 在改變元素個數時,如果是將元素個數
增多,可能會改變底層容量的大小,如果是將元素個數減少,底層空間總大小不變。
3. reserve(size_t res_arg=0) :為 string 預留空間,不改變有效元素個數,當 reserve 的參
數小于 string 的底層空間總大小時, reserver 不會改變容量大小。

?3.迭代器訪問

函數名稱
功能說明
operator[] (重點)
返回 pos 位置的字符, const string 類對象調用
begin + end
begin 獲取一個字符的迭代器 + end 獲取最后一個字符下一個位
置的迭代器
rbegin + rend
begin 獲取一個字符的迭代器 + end 獲取最后一個字符下一個位
置的迭代器
范圍 for
\

4.修改操作

函數名稱
功能說明
push_back
在字符串后尾插字符 c
append
在字符串后追加一個字符串
operator+= ( 重點 )
在字符串后追加字符串 str
c_str ( 重點 )
返回 C 格式字符串
find + npos (
)
從字符串 pos 位置開始往后找字符 c ,返回該字符在字符串中的 位置
rfind
從字符串 pos 位置開始往后找字符c,返回該字符在字符串中的位置
substr
str 中從 pos 位置開始,截取 n 個字符,然后將其返回

5.非成員函數

函數
功能說明
operator+
盡量少用,因為傳值返回,導致深拷貝效率低
operator>> (重點)
輸入運算符重載
operator<< (重點)
輸出運算符重載
getline (重點)
獲取一行字符串
relational operators (重點)
大小比較

string類模擬實現

底層結構

class string
{
public://...private:char* _str = nullptr;int _size = 0;int _capacity = 0;const static size_t npos;
};

?在上面定義的結構當中,其常量npos表示字符串末尾之前的所有字符,在substr接口中有使用。

const size_t string::npos = -1; //-1的無符號整數即表示最大值

1.常見構造?

我們知道無論如何字符串當中末尾總會存' \0 ' ,作為標記。因此在構造字符串string時,一定要多開一個空間存 ' \0 ' 。那如果new空間失敗呢?采用拋異常的方式,在外進行捕獲異常(之后會講)。

在如下一段程序中,將字符串str拷貝到string當中,但是這樣會導致多次析構一塊空間導致程序崩潰的問題。

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

淺/深拷貝

淺拷貝:也稱位拷貝,編譯器只是將對象中的值拷貝過來 。如果 對象中管理資源 ,最后就會 導致
多個對象共享同一份資源,當一個對象銷毀時就會將該資源釋放掉,而此時另一些對象不知道該
資源已經被釋放,以為還有效,所以當繼續對資源進項操作時,就會發生發生了訪問違規
如下圖當中, s1 s2 共用同一塊內 存空間,在釋放時同一塊空間被釋放多次而引起程序崩潰 ,這種拷貝方式,稱為淺拷貝。

?深拷貝:不單單是把數據拷貝過去,還需要開一塊內存空間,防止指向同一塊空間。


	string::string(const char* str):_size(strlen(str)){_str = new char[_size + 1];//如果失敗需要捕獲異常_capacity = _size;strcpy(_str, str);}string::string(size_t n, char ch):_str(new char[n + 1]), _size(n), _capacity(n){for (size_t i = 0;i < n;i++){_str[i] = ch;}_str[_size] = '\0';}//析構?string::~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}?

拷貝構造、賦值運算法重載(重點)

拷貝構造:

?目標是將s中的數據拷貝到_str中,那我們直接調用strcpy函數將s數據拷過來即可??

	string::string(const string& s){strcpy(_str, s._str);}

?但是這樣會導致析構時多次析構一塊空間,從而報錯(依然是淺拷貝的問題)。

	string::string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}

賦值運算符重載:

特殊情況下可能自己給自己賦值,為了不再拷貝一次做判斷。

	string& string::operator=(const string& s){if (this != &s){delete _str;_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}return *this;}
現代寫法

實際上,上面的兩段代碼顯得過于笨拙且冗雜,都是老老實實自己手寫申請空間。而在如下一段程序當中,借用構造函數來完成拷貝及其賦值。而這種方法,也是實踐當中最常用到的現代寫法。

    void string::swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//拷貝構造簡潔化  --> 現代寫法string::string(const string& s){string tmp(s._str);swap(tmp);}

在如上一段程序當中,通過構造函數構造tmp。s這里是引用傳參,即出了作用域不會銷毀?;而tmp是屬于這個棧內的空間,出了作用域就會銷毀。此時我們借助swap的特性,將_str指向的指針進行交換,此時就是*this指向了新申請的空間,再將個數和空間交換即可。

這樣看,和平日寫的拷貝構造是差不多的。別著急,我們再來看看賦值運算符重載的簡化實現。

  1. 方法一:仍然采用上面思想寫賦值重載;
  2. 方法二:實際上,當我們寫完了拷貝構造后,我們甚至還能再借助拷貝構造的特性來完成賦值重載。此時,我們不再使用引用傳參,而是借助拷貝構造出s,而s出了作用域就會銷毀,此時我們再借助swap來進行交換。這樣來看,這種現代寫法是不是既簡潔又充滿著妙處。
string& string::operator=(string s){//s即是拷貝構造過來的swap(s); //出了作用域就會析構return *this;}

2.容量操作

//增容
void string::reserve(size_t n)
{char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;
}

3. 迭代器訪問

什么是迭代器?

迭代器的作用是用來訪問容器(用來保存元素的數據結構)中的元素,所以使用迭代器,我們就可以訪問容器中里面的元素。那迭代器不就相當于指針一個一個訪問容器中的元素嗎?并不是,迭代器是像指針一樣的行為,但是并不等同于指針,且功能更加豐富,這點需在之后慢慢體會。(本章節體現并不是很明顯)

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

4. 修改操作

push_back插入邏輯:

  1. 當插入元素大于容器容量時,需進行擴容操作;
  2. _size的位置是' \0 ',但直接將插入元素覆蓋即可,_size++,重新加上' \0 '?。
void string::push_back(char x)
{if (_size + 1 > _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}_str[_size++] = x;_str[_size] = '\0';
}

?append插入邏輯:

  1. 計算需要插入字符串的長度len,若string的個數+len大于容量則需擴容;
  2. 若個數+len長度大于2倍擴容時,則應擴容到個數+len容量;
  3. 往string末尾插入字符串。
void string::append(const char* str)
{size_t len = strlen(str);if (len + _size > _capacity){int NewCapacity = 2 * _capacity;if (len + _size > 2 * _capacity){NewCapacity = len + _size;}reserve(NewCapacity);}strcpy(_str + _size, str);_size += len;
}

?+=運算符重載邏輯:

  1. 如果插入的是字符串,則采用append函數的邏輯;
  2. 如果插入的是字符,則采用push_back函數的邏輯;
  3. 無論哪種情況,實現方式都和以上兩種代碼實現方式是相同的,因此我們可以以復用的方式,更容易維護我們的代碼。
string& string::operator+=(const char* str)
{append(str);return *this;
}
string& string::operator+=(char x)
{push_back(x);return *this;
}

?insert函數實現邏輯:

  1. 擴容邏輯與其上是類似的,區別在于插入元素后的數據是從后往前還是從前往后挪動;
  2. 如果是從前往后挪動,那么會發生覆蓋數據的現象,而從后往前就不會,這點在之前也有強調過;
	void string::insert(size_t pos, size_t n, char ch){assert(pos <= _size);//擴容if (_size + n > _capacity){// size_t newCapacity = 2 * _capacity;if (_size + n > 2 * _capacity){newCapacity = _size + n;}reserve(newCapacity);}//int end = _size;//while (end >= (int)pos)//這里不強轉會有err//{//	_str[end + n] = _str[end];//	--end;//}size_t end = _size + n;while (end > pos + n - 1){_str[end] = _str[end - n];--end;}for (size_t i = 0;i < n;i++){_str[pos + i] = ch;}_size += n;}
  1. ?擴容邏輯與其上對應重載函數是一樣的;
  2. 一樣是需要將pos后的位置進行挪動后,思路是類似的,那能否復用上面的實現函數呢?

如果復用上面的函數,那么該往這位置插入的字符串都是相同的一個字符,這樣想似乎不能復用。

但是沒關系,這些位置剛好是為要插入字符串預留的,那么我們只要將這些位置覆蓋一遍即可。

	void string::insert(size_t pos, const char* str){size_t n = strlen(str);insert(pos, n, 'x');for (size_t i = 0;i < n;i++){_str[i + pos] = str[i];}}

復用 :通過犧牲空間方法。

string tmp(n, ch);insert(pos, tmp.c_str());

5. 非成員函數

vs string 的結構
string 總共占 28 個字節 ,內部結構稍微復雜一點,先是 有一個聯合體,聯合體用來定義
string 中字符串的存儲空間
  • 當字符串長度小于16時,使用內部固定的字符數組來存放
  • 當字符串長度大于等于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 個字節。

流提取

vs下額外定義了個buff數組以減少擴容,提高效率。我們同樣采用這種思想造類似的輪子。

//cin>>s
istream& operator>>(istream& in, string& s)
{s.clear();//char ch = in.get();//while (ch != ' ' && ch != '\n')//{//	s += ch;//	ch = in.get();//}//為了減少頻繁的擴容,定義一個數組char buff[1024];char ch = in.get();size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 1023){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;
}

?流插入

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

getline函數(難點)

實現邏輯:

  1. 每次輸入都往buff數組中填入數據;
  2. 當數據超過buff數組容量時,將數組里的數據加到string當中,buff數組從0開始繼續填入數據;
  3. 如果ch==delim時,不再填入數據,將buff數組里剩下的數據加到string當中。
istream& getline(istream& is, string& s, char delim)
{char buff[1024];char ch = is.get();size_t i = 0;while (ch != delim){buff[i++] = ch;if (i == 1023){buff[i] = '\0';s += buff;i = 0;}ch = is.get();}if (i > 0){buff[i] = '\0';s += buff;}return is;
}

代碼實現

string.h

#pragma once
#include<iostream>
#include<string.h>
#include<assert.h>
using namespace std;namespace egoist
{class string{public://迭代器typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}//計算串的size和capacitysize_t size() const{return _size;}size_t capacity() const{return _capacity;}//構造函數string(const char* str = "");string(size_t n, char ch);//交換void swap(string& s);//拷貝構造string(const string& s);const char* c_str() const{return _str;}void reserve(size_t n);void push_back(char x);void append(const char* str);=重載運算符//string& operator=(const string& s);//現代簡潔化string& operator=(string s);string& operator+=(const char* str);string& operator+=(char x);//比較大小bool operator==(const string& s) const;bool operator!=(const string& s) const;bool operator<(const string& s) const;bool operator<=(const string& s) const;bool operator>(const string& s) const;bool operator>=(const string& s) const;//[]運算符重載char& operator[](size_t pos){assert(pos < _size);assert(pos >= 0);return _str[pos];}const char& operator[](size_t pos) const{assert(pos < _size);assert(pos >= 0);return _str[pos];}void insert(size_t pos, size_t n, char ch);void insert(size_t pos, const char* str);void erase(size_t pos = 0, size_t len = npos);size_t find(char ch, size_t pos = 0);size_t find(const char* str, size_t pos = 0);void clear(){_str[0] = '\0';_size = 0;}string substr(size_t pos, size_t len = npos);//析構~string();private:char* _str = nullptr;int _size = 0;int _capacity = 0;const static size_t npos;};//cout<<sostream& operator<<(ostream& out, const string& s);//cin>>sistream& operator>>(istream& in, string& s);istream& getline(istream& is, string& s, char delim = '\n');}

string.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"string.h"namespace egoist
{const size_t string::npos = -1;string::string(const char* str):_size(strlen(str)){_str = new char[_size + 1];//如果失敗需要捕獲異常_capacity = _size;strcpy(_str, str);}string::string(size_t n, char ch):_str(new char[n + 1]), _size(n), _capacity(n){for (size_t i = 0;i < n;i++){_str[i] = ch;}_str[_size] = '\0';}拷貝構造//string::string(const string& s)//{//	_str = new char[s._capacity + 1];//	strcpy(_str, s._str);//	_size = s._size;//	_capacity = s._capacity;//}void string::swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//拷貝構造簡潔化  --> 現代寫法string::string(const string& s){string tmp(s._str);swap(tmp);}void string::reserve(size_t n){//需要增容 --> 為了和new配套使用,不用reallocchar* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}void string::push_back(char x){if (_size + 1 > _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}_str[_size++] = x;_str[_size] = '\0';}void string::append(const char* str){size_t len = strlen(str);if (len + _size > _capacity){int NewCapacity = 2 * _capacity;if (len + _size > 2 * _capacity){NewCapacity = len + _size;}reserve(NewCapacity);}strcpy(_str + _size, str);_size += len;}//=運算符重載//string& string::operator=(const string& s)//{//	if (this != &s)//	{//		delete _str;//		_str = new char[s._capacity + 1];//		strcpy(_str, s._str);//		_size = s._size;//		_capacity = s._capacity;//	}//	return *this;//}//現代簡潔化 --> 通過調用拷貝構造string& string::operator=(string s){//s即是拷貝構造過來的swap(s); //出了作用域就會析構return *this;}string& string::operator+=(const char* str){append(str);return *this;}string& string::operator+=(char x){push_back(x);return *this;}//比較大小bool string::operator==(const string& s) const{return strcmp(_str, s._str) == 0;}bool string::operator!=(const string& s) const{return !(*this == s);}bool string::operator<(const string& s) const{return strcmp(_str, s._str) < 0;}bool string::operator<=(const string& s) const{return (*this < s) || (*this == s);}bool string::operator>(const string& s) const{return !(*this <= s);}bool string::operator>=(const string& s) const{return !(*this < s);}void string::insert(size_t pos, size_t n, char ch){assert(pos <= _size);//擴容if (_size + n > _capacity){// size_t newCapacity = 2 * _capacity;if (_size + n > 2 * _capacity){newCapacity = _size + n;}reserve(newCapacity);}//int end = _size;//while (end >= (int)pos)//這里不強轉會有err//{//	_str[end + n] = _str[end];//	--end;//}size_t end = _size + n;while (end > pos + n - 1){_str[end] = _str[end - n];--end;}for (size_t i = 0;i < n;i++){_str[pos + i] = ch;}_size += n;}void string::insert(size_t pos, const char* str){由于高度相似,可采用復用//assert(pos <= _size);//size_t n = strlen(str);擴容//if (_size + n > _capacity)//{//	// //	size_t newCapacity = 2 * _capacity;//	if (_size + n > 2 * _capacity)//	{//		newCapacity = _size + n;//	}//	reserve(newCapacity);//}//size_t end = _size + n;//while (end > pos + n - 1)//{//	_str[end] = _str[end - n];//	--end;//}size_t n = strlen(str);insert(pos, n, 'x');for (size_t i = 0;i < n;i++){_str[i + pos] = str[i];}//通過犧牲空間方法復用/*string tmp(n, ch);insert(pos, tmp.c_str());*/}void string::erase(size_t pos, size_t len){assert(pos >= 0);if (len > _size - pos){_str[pos] = '\0';_size = pos;}else {for (size_t i = pos;i <= _size;i++){_str[i] = _str[i + len];}_size -= len;}}size_t string::find(char ch, size_t pos){for (size_t i = pos;i < _size;i++){if (_str[i] == ch)return i;}return npos;}size_t string::find(const char* str, size_t pos){const char* p = strstr(_str + pos, str);if (p == nullptr){return npos;}else{return p - _str;}}string string::substr(size_t pos, size_t len){size_t leftlen = _size - pos;if (len > leftlen)len = leftlen;string tmp;tmp.reserve(len);for (size_t i = 0; i < len; i++){tmp += _str[pos + i];}return tmp;}string::~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}//cout<<sostream& operator<<(ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}//cin>>sistream& operator>>(istream& in, string& s){s.clear();//char ch = in.get();//while (ch != ' ' && ch != '\n')//{//	s += ch;//	ch = in.get();//}//為了減少頻繁的擴容,定義一個數組char buff[1024];char ch = in.get();size_t i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 1023){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}istream& getline(istream& is, string& s, char delim){char buff[1024];char ch = is.get();size_t i = 0;while (ch != delim){buff[i++] = ch;if (i == 1023){buff[i] = '\0';s += buff;i = 0;}ch = is.get();}if (i > 0){buff[i] = '\0';s += buff;}return is;}
}

擴展 --> 引用計數的寫時拷貝

寫時拷貝就是一種拖延癥,是在淺拷貝的基礎之上增加了引用計數的方式來實現的。
引用計數:用來記錄資源使用者的個數。在構造時,將資源的計數給成 1 ,每增加一個對象使用該
資源,就給計數增加 1 ,當某個對象被銷毀時,先給該計數減 1 ,然后再檢查是否需要釋放資源,
如果計數為 1 ,說明該對象時資源的最后一個使用者,將該資源釋放;否則就不能釋放,因為還有
其他對象在使用該資源。


?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/78298.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/78298.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/78298.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

系統架構師2025年論文《論軟件架構評估2》

論軟件系統架構評估 v2.0 摘要: 某市醫院預約掛號系統建設推廣應用項目是我市衛生健康委員會 2019 年發起的一項醫療衛生行業便民惠民信息化項目,目的是實現轄區內患者在轄區各公立醫療機構就診時,可以通過多種線上渠道進行預約掛號,提升就醫體驗。我作為系統架構師參與此…

BEVDet4D: Exploit Temporal Cues in Multi-camera 3D Object Detection

背景 對于現有的BEVDet方法,它對于速度的預測誤差要高于基于點云的方法,對于像速度這種與時間有關的屬性,僅靠單幀數據很難預測好。因此本文提出了BEVDet4D,旨在獲取時間維度上的豐富信息。它是在BEVDet的基礎上進行拓展,保留了之前幀的BEV特征,并將其進行空間對齊后與當…

el-upload 上傳邏輯和ui解耦,上傳七牛

解耦的作用在于如果后面要我改成從阿里云oss上傳文件&#xff0c;我只需要實現上傳邏輯從七牛改成阿里云即可&#xff0c;其他不用動。實現方式有2部分組成&#xff0c;一部分是上傳邏輯&#xff0c;一部分是ui。 上傳邏輯 大概邏輯就是先去服務端拿上傳token和地址&#xff0…

酒水類目電商代運營公司-品融電商:全域策略驅動品牌長效增長

酒水類目電商代運營公司-品融電商&#xff1a;全域策略驅動品牌長效增長 在競爭日益激烈的酒水市場中&#xff0c;品牌如何快速突圍并實現長效增長&#xff1f;品融電商憑借「效品合一 全域增長」方法論與全鏈路運營能力&#xff0c;成為酒水類目代運營的領跑者。從品牌定位、視…

機器學習特征工程中的數值分箱技術:原理、方法與實例解析

標題&#xff1a;機器學習特征工程中的數值分箱技術&#xff1a;原理、方法與實例解析 摘要&#xff1a; 分箱技術作為機器學習特征工程中的關鍵環節&#xff0c;通過將數值數據劃分為離散區間&#xff0c;能夠有效提升模型對非線性關系的捕捉能力&#xff0c;同時增強模型對異…

【MySQL專欄】MySQL數據庫的復合查詢語句

文章目錄 1、首先練習MySQL基本語句的練習①查詢工資高于500或崗位為MANAGER的雇員&#xff0c;同時還要滿足他們的姓名首字母為大寫的J②按照部門號升序而雇員的工資降序排序③使用年薪進行降序排序④顯示工資最高的員工的名字和工作崗位⑤顯示工資高于平均工資的員工信息⑥顯…

Python爬蟲(5)靜態頁面抓取實戰:requests庫請求頭配置與反反爬策略詳解

目錄 一、背景與需求?二、靜態頁面抓取的核心流程?三、requests庫基礎與請求頭配置?3.1 安裝與基本請求3.2 請求頭核心參數解析?3.3 自定義請求頭實戰 四、實戰案例&#xff1a;抓取豆瓣讀書Top250?1. 目標?2. 代碼實現3. 技術要點? 五、高階技巧與反反爬策略?5.1 動態…

HTML給圖片居中

在不同的布局場景下&#xff0c;讓 <img> 元素居中的方法有所不同。下面為你介紹幾種常見的居中方式 1. 塊級元素下的水平居中 如果 <img> 元素是塊級元素&#xff08;可以通過 display: block 設置&#xff09;&#xff0c;可以使用 margin: 0 auto 來實現水平居…

【高頻考點精講】前端構建工具對比:Webpack、Vite、Rollup和Parcel

前端構建工具大亂斗:Webpack、Vite、Rollup和Parcel誰是你的菜? 【初級】前端開發工程師面試100題(一) 【初級】前端開發工程師面試100題(二) 【初級】前端開發工程師的面試100題(速記版) 最近在后臺收到不少同學提問:“老李啊,現在前端構建工具這么多,我該選哪個?…

趕緊收藏!教您如何用 GitHub 賬號,獲取永久免費的 Docker 容器!!快速搭建我們的網站/應用!

文章目錄 ?? 介紹 ???? 演示環境 ???? 永久免費的 Docker 容器 ???? 注冊與登錄? 創建 Docker 容器?? 部署你的網站?? 注意事項?? 使用場景?? 相關鏈接 ???? 介紹 ?? 還在為搭建個人網站尋找免費方案而煩惱? 今天發現一個寶藏平臺!只需一個 Git…

Java大師成長計劃之第3天:Java中的異常處理機制

&#x1f4e2; 友情提示&#xff1a; 本文由銀河易創AI&#xff08;https://ai.eaigx.com&#xff09;平臺gpt-4o-mini模型輔助創作完成&#xff0c;旨在提供靈感參考與技術分享&#xff0c;文中關鍵數據、代碼與結論建議通過官方渠道驗證。 在 Java 編程中&#xff0c;異常處理…

大數據去重

實驗4 大數據去重 1.實驗目的 通過Hadoop數據去重實驗&#xff0c;學生可以掌握準備數據、偽分布式文件系統配置方法&#xff0c;以及在集成開發環境Eclipse中實現Hadoop數據去重方法。 2.實驗要求 了解基于Hadoop處理平臺的大數據去重過程&#xff0c;理解其主要功能&…

http協議、全站https

一、http協議 1、為何要學http協議? 用戶用瀏覽器訪問網頁,默認走的都是http協議,所以要深入研究web層,必須掌握http協議 2、什么是http協議 1、全稱Hyper Text Transfer Protocol(超文本傳輸協議) ### 一個請求得到一個響應包 普通…

使用 Logstash 遷移 MongoDB 數據到 Easysearch

大家好&#xff01;在前面的文章中&#xff0c;我們已經詳細介紹了如何通過 Logstash 和 Canal 工具實現 MySQL 數據向 Easysearch 的遷移。如果您正在使用 MongoDB 作為數據存儲&#xff0c;并希望將其數據遷移到 Easysearch 中&#xff0c;這篇指南或許能為您提供一些幫助。 …

亞馬遜英國站FBA費用重構:輕小商品迎紅利期,跨境賣家如何搶占先機?

一、政策背景&#xff1a;成本優化成平臺與賣家共同訴求 2024年4月&#xff0c;亞馬遜英國站&#xff08;Amazon.co.uk&#xff09;發布近三年來力度最大的FBA費用調整方案&#xff0c;標志著英國電商市場正式進入精細化成本管理時代。這一決策背后&#xff0c;是多重因素的疊…

使用Qt Quick Controls創建自定義日歷組件

目錄 引言相關閱讀1. DayOfWeekRow2. MonthGrid3. WeekNumberColumn 項目結構及實現工程結構圖代碼實現及解析1. 組件封裝2. 主界面實現 運行效果 總結下載鏈接 引言 Qt6 Quick框架提供了一套豐富的日歷相關組件&#xff0c;包括 MonthGrid、DayOfWeekRow 和 WeekNumberColumn…

【AI微信小程序開發】大轉盤小程序項目代碼:自設轉盤選項和概率(含完整前端+后端代碼)

系列文章目錄 【AI微信小程序開發】AI減脂菜譜小程序項目代碼:根據用戶身高/體重等信息定制菜譜(含完整前端+后端代碼)【AI微信小程序開發】AI菜譜推薦小程序項目代碼:根據剩余食材智能生成菜譜(含完整前端+后端代碼)【AI微信小程序開發】圖片工具小程序項目代碼:圖片壓…

redis相關問題整理

Redis 支持多種數據類型&#xff1a; 字符串 示例&#xff1a;存儲用戶信息 // 假設我們使用 redis-plus-plus 客戶端庫 auto redis Redis("tcp://127.0.0.1:6379"); redis.set("user:1000", "{name: John Doe, email: john.doeexample.com}"…

Vue-組件的懶加載,按需加載

在Vue項目中實現組件的懶加載&#xff08;也稱為按需加載或代碼分割&#xff09;&#xff0c;可以大大提升應用的加載速度和性能。懶加載主要通過Webpack的代碼分割功能實現&#xff0c;特別是使用動態導入&#xff08;import()語法&#xff09;。 為什么要使用懶加載&#xf…

C# new Bitmap(32043, 32043, PixelFormat.Format32bppArgb)報錯:參數無效,如何將圖像分塊化處理?

C#處理非常大的圖像&#xff08;如 32043x32043 像素&#xff09;時&#xff0c;確實需要采取分塊化處理的方法來避免內存不足的問題。分塊化處理可以將大圖像分割成多個較小的塊&#xff0c;分別進行處理和保存&#xff0c;最后再合并這些塊以形成完整的圖像。以下是一個詳細的…