C++ STL-string類底層實現

?摘要:

本文實現了一個簡易的string類,主要包含以下功能:

1. 默認成員函數:構造函數(默認/參數化)、拷貝構造、賦值重載和析構函數,采用深拷貝避免內存問題;

2. 迭代器支持:通過char*實現begin()/end()迭代器;

3. 容量操作:size()/capacity()獲取大小容量,reserve()/resize()調整空間;

4. 字符串修改:push_back()、append()、insert()、erase()等操作;

5. 訪問操作:重載operator[]和c_str()方法;

6. 實用功能:find()查找、substr()子串;

7. 運算符重載:關系運算符、流操作符<<和>>。類內部使用動態分配的char數組存儲字符串,通過_size和_capacity管理空間。實現時特別注意了深拷貝、邊界檢查和內存管理,基本模擬了標準string類的核心功能。

目錄

?摘要:

實現框架

一、默認成員函數

1.默認構造函數

2.構造函數

3.拷貝構造函數(重點)

4.賦值運算符重載函數

5.析構函數

二、迭代器相關函數

begin和end

三、容量和大小相關函數

size和capacity

reserve和resize

四、與修改字符串相關的函數

push_back

append

operator+=

insert?

erase

clear

五、訪問字符串相關函數

operator[ ]

c_str

小知識點- npos

find函數

substr函數

六、關系運算符重載函數

>、>=、<、<=、==、!=

>>和<<運算符的重載


實現框架

#include<iostream>
#include<assert.h>
using namespace std;namespace lzg
{class string{public://typedef char* iterator;using iterator = char*;using const_iterator = const char*;//一、默認成員函數string(const char* str = "");       //默認構造string(const string& s);            //拷貝構造string& operator=(const string& s); //賦值重載~string();                          //析構函數//二、迭代器相關函數iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}//三、容量和大小相關函數void reserve(size_t n);size_t size() const{return _size;}size_t capacity() const{return _capacity;}void resize(size_t n, char ch = '\0');//四、與修改字符串相關的函數void push_back(char ch);void append(const char* str);string& operator+=(char ch);string& operator+=(const char* str);void insert(size_t pos, char ch);void insert(size_t pos, const char* str);void erase(size_t pos, size_t len = npos);void clear(){_str[0] = '\0';_size = 0;}//五、訪問字符串相關函數char& operator[](size_t i){assert(i < _size);return _str[i];}const char& operator[](size_t i) const{assert(i < _size);return _str[i];}const char* c_str() const{return _str;}size_t find(char ch, size_t pos = 0);size_t find(const char* str, size_t pos = 0);string substr(size_t pos, size_t len = npos);private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;public:static const size_t npos;};//六、關系運算符重載函數bool operator== (const string& lhs, const string& rhs);bool operator!= (const string& lhs, const string& rhs);bool operator> (const string& lhs, const string& rhs);bool operator< (const string& lhs, const string& rhs);bool operator>= (const string& lhs, const string& rhs);bool operator<= (const string& lhs, const string& rhs);ostream& operator<<(ostream& os, const string& str);istream& operator>>(istream& is, string& str);
}

一、默認成員函數

1.默認構造函數

string():_str(new char[1] {'\0'}),_size(0),_capacity(0)
{}

為_str開辟1字節空間來存放'\0'_capacity不記錄\0的大小,這樣調用c_str()就不會報錯

2.構造函數

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

注意在private中的聲明順序,這里初始化列表只走_size(大小為str的長度),讓_str和_capacity在函數體內初始化(這樣保證了不會出錯,比如三者都在初始化列表的話,會先初始化_str這樣有風險),同樣的,_str開辟空間時,為'\0'多開辟1字節

構造函數有一個默認構造就行了,寫1個就行,把1注釋掉,并把2改為全缺省

string(const char* str="")//不要寫成" "這是非空:_size(strlen(str))
{_capacity = _size;_str = new char[_size + 1];strcpy(_str, str);
}

3.拷貝構造函數(重點)

在模擬實現拷貝構造函數前,我們應該首先了解深淺拷貝:

我們不寫拷貝構造,編譯器默認生成的拷貝構造是值拷貝也叫淺拷貝

淺拷貝:拷貝出來的目標對象的指針和源對象的指針指向的內存空間是同一塊空間。其中一個對象的改動會對另一個對象造成影響。
深拷貝:深拷貝是指源對象與拷貝對象互相獨立。其中任何一個對象的改動不會對另外一個對象造成影響。

很明顯,我們并不希望拷貝出來的兩個對象之間存在相互影響,因此,我們這里需要用到深拷貝。

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

這里還有一個現代(偷懶)寫法

string(const string& s){string tmp(s._str);swap(tmp);}

現代寫法與傳統寫法的思想不同:先構造一個tmp對象,然后再將tmp對象與拷貝(s)對象的數據交換即可。

4.賦值運算符重載函數

與拷貝構造函數類似,賦值運算符重載函數的模擬實現也涉及深淺拷貝問題,我們同樣需要采用深拷貝

再強調一下賦值和拷貝的區別,賦值是兩個已創建的對象之間完成賦值操作,拷貝是指一個已創建的對象調用拷貝構造生成一個新的對象

// s1 = s3                      s3
string& operator=(const string& s)
{if (this != &s)//避免自己給自己賦值{delete[] _str;  //釋放掉s1的舊空間,開辟一個和s3容量的新空間_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}return *this;
}

同樣的賦值也有現代寫法

// 現代寫法// s1 = s3string& operator=(string s){swap(s);return *this;}

賦值運算符重載函數的現代寫法是通過采用“值傳遞”接收右值的方法,讓編譯器自動調用拷貝構造函數,然后我們再將拷貝出來的對象與左值進行交換即可

5.析構函數

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

二、迭代器相關函數

string類中的迭代器實際上就是字符指針,只是給字符指針起了一個別名叫iterator而已。

typedef char* iterator;

typedef const char* const_iterator;

begin和end

iterator begin()
{return _str; //返回字符串中第一個字符的地址
}const_iterator begin()const
{return _str; //返回字符串中第一個字符的const地址
}iterator end()
{return _str + _size; //返回字符串中最后一個字符的后一個字符的地址
}const_iterator end()const
{return _str + _size; //返回字符串中最后一個字符的后一個字符的const地址
}

其實范圍for的底層就是調用了begin()和end()迭代器,當你模擬實現了迭代器,范圍for自然就能使用

string s("hello world!!!");
//編譯器將其替換為迭代器形式
for (auto e : s)
{cout << e << " ";
}
cout << endl;

注:自己寫的begin()和end()函數名必須是這樣的,不能有任何大小寫否則范圍for就報錯

三、容量和大小相關函數

size和capacity

因為string類的成員變量是私有的,我們并不能直接對其進行訪問,所以string類設置了size和capacity這兩個成員函數,用于獲取string對象的大小和容量。
size函數用于獲取字符串當前的有效長度(不包括’\0’)。

size_t size() const
{return _size;
}

capacity函數用于獲取字符串當前的容量。(不包括’\0’)。

size_t capacity() const
{return _capacity;
}

reserve和resize

reserve和resize這兩個函數的執行規則一定要區分清楚。
reserve規則:
?1、當n大于對象當前的capacity時,將capacity擴大到n或大于n。
?2、當n小于對象當前的capacity時,什么也不做。

void reserve(size_t n)
{if (n > _capacity){char* tmp = new char[n + 1];//多開一個空間用于存放'\0'strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}
}

resize規則:

1、當n大于當前的size時,將size擴大到n,擴大的字符為ch,若ch未給出,則默認為’\0’。
2、當n小于當前的size時,將size縮小到n。

//改變大小
void resize(size_t n, char ch = '\0')
{if (n <= _size) {_size = n; _str[_size] = '\0'; }else {if (n > _capacity) {reserve(n); }for (size_t i = _size; i < n; i++) {_str[i] = ch;}_size = n; _str[_size] = '\0'; }
}

四、與修改字符串相關的函數

push_back

void push_back(char c)
{if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}_str[_size] = c;    // 在結尾位置寫入新字符(覆蓋原'\0')_size++;            // 增加長度_str[_size] = '\0'; // 在新結尾補終止符
}

實現push_back還可以直接復用下面即將實現的insert函數。

//尾插字符
void push_back(char ch)
{insert(_size, ch); //在字符串末尾插入字符ch
}

append

//尾插字符串
void append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){size_t newCapacity = 2 * _capacity;// 擴2倍不夠,則需要多少擴多少if (newCapacity < _size + len)newCapacity = _size + len;reserve(newCapacity);}//避免尾插字符串時如果字符串大于二倍_capacity時的多次擴容strcpy(_str + _size, str);_size += len;
}

operator+=

+=運算符的重載是為了實現字符串與字符、字符串與字符串之間能夠直接使用+=運算符進行尾插。
+=運算符實現字符串與字符之間的尾插直接調用push_back函數即可。

//+=運算符重載
string& operator+=(char ch)
{push_back(ch); //尾插字符串return *this; //返回左值(支持連續+=)
}

+=運算符實現字符串與字符串之間的尾插直接調用append函數即可。

//+=運算符重載
string& operator+=(const char* str)
{append(str); //尾插字符串return *this; //返回左值(支持連續+=)
}

insert?

insert函數的作用是在字符串的任意位置插入字符或是字符串。

void insert(size_t pos, char ch);

void insert(size_t pos,const char* str);

void insert(size_t pos, char ch)
{if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}size_t end = _size;while (end >=(int)pos){_str[end + 1] = _str[end];end--;}_str[pos] = ch;_size++;
}

insert函數插入字符串時也是類似思路

void insert(size_t pos, const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){size_t newCapacity = 2 * _capacity;// 擴2倍不夠,則需要多少擴多少if (newCapacity < _size + len)newCapacity = _size + len;reserve(newCapacity);}size_t end = _size + len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;
}

erase

erase函數的作用是刪除字符串任意位置開始的n個字符。

void erase(size_t pos, size_t len)
{assert(pos < _size);if (len >= _size - pos){_str[pos] = '\0';_size = pos;}else{   //從后往前挪size_t end = pos + len;while (end <= _size){_str[end-len] = _str[end];//覆蓋pos及后len長度的值,完成刪除end++;}_size -= len;}
}

clear

clear函數用于將對象中存儲的字符串置空,實現時直接將對象的_size置空,然后在字符串后面放上’\0’即可

//清空字符串
void clear()
{_size = 0; //size置空_str[_size] = '\0'; //字符串后面放上'\0'
}

swap

有三個swap,有兩個是string里的swap函數,另外一個是算法庫的swap函數

std::swap
template <class T> void swap (T& a, T& b);

std::string::swap

void swap (string& str);

void swap (string& x,sring& y);

算法庫中的swap也能交換自定義類型,但代價非常大

我們自己實現swap完全不用這么復雜,直接把s1、s2指針及數據交換一下就行了

//交換兩個對象的數據
void swap(string& s)
{//調用庫里的swap::swap(_str, s._str); //交換兩個對象的C字符串::swap(_size, s._size); //交換兩個對象的大小::swap(_capacity, s._capacity); //交換兩個對象的容量
}

還有一個swap存在的意義是什么呢?

void swap(string& s1,string& s2)
{s1.swap(s2);
}

這樣在類外面調用swap函數時就不會調到算法庫的swap函數了(模板實例化會走上面的swap函數)

五、訪問字符串相關函數

operator[ ]

[ ]運算符的重載是為了讓string對象能像C字符串一樣,通過[ ] +下標的方式獲取字符串對應位置的字符。

//[]運算符重載(可讀可寫)
char& operator[](size_t i)
{assert(i < _size); //檢測下標的合法性return _str[i]; //返回對應字符
}

在某些場景下,我們可能只能用[ ] +下標的方式讀取字符而不能對其進行修改。例如,對一個const的string類對象進行[ ] +下標的操作,我們只能讀取所得到的字符,而不能對其進行修改。所以我們需要再重載一個[ ] 運算符,用于只讀操作。

//[]運算符重載(可讀可寫)
const  char& operator[](size_t i)const
{assert(i < _size); //檢測下標的合法性return _str[i]; //返回對應字符
}

c_str

c_str函數用于獲取對象C類型的字符串

//返回C類型的字符串
const char* c_str()const
{return _str;
}

小知識點- npos

在 C++ 標準庫中,?npos? 是一個特殊的靜態常量成員,主要用于表示“無效位置”或“未找到”的狀態

注:只有整形才能在聲明中定義

static const size_t npos=-1;

??核心用途

(1) ?查找函數失敗時的返回值

std::string str = "Hello";
size_t pos = str.find('x');  // 查找不存在字符
if (pos == std::string::npos) {  // 必須用 npos 檢查std::cout << "Not found!";
}

(2) ?表示“直到字符串末尾”?

std::string sub = str.substr(2, std::string::npos);  // 從索引2到末尾

特性

  • ?無符號值?:由于是?size_t類型,避免直接與?-1比較,而應使用?npos。?

  • ?足夠大?:其值(如 18446744073709551615)保證不會與任何有效索引沖突。

find函數

find函數是用于在字符串中查找一個字符或是字符串

find函數:正向查找即從字符串開頭開始向后查找

1、正向查找第一個匹配的字符。

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;}}//找不到返回nposreturn npos;
}

2、正向查找第一個匹配的字符串。

這里用到了一個strstr函數

char * strstr (char * str1, const char * str2 );

返回指向 str1 中第一次出現的 str2 的指針,如果 str2 不是 str1 的一部分,則返回空指針。

size_t find(const char* str, size_t pos = 0)
{assert(pos < _size);const char* ptr = strstr(_str, str);if (ptr)//如果找到了{return ptr - _str;  //下標}else //為空{return npos;}
}

substr函數

substr函數用來取字符串中的子字符串,位置和長度由自己決定

默認從首字符取,默認取的長度是取到尾

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

調用拷貝構造,返回pos位后len長度的字符串

string substr(size_t pos, size_t len)
{assert(pos < _size);//len長度足夠大就直接取到尾if (len > _size - pos){len = _size - pos;}string sub;sub.reserve(len);for (size_t i = pos;i < len; i++){sub += _str[pos + i];}return sub;
}

六、關系運算符重載函數

>、>=、<、<=、==、!=

關系運算符有 >、>=、<、<=、==、!= 這六個,但是對于C++中任意一個類的關系運算符重載,我們均只需重載其中的兩個,剩下的四個關系運算符可以通過復用已經重載好了的兩個關系運算符來實現。

例如,對于string類,我們可以選擇只重載 <?和 == 這兩個關系運算符。

注:重載在類外面,命名域例:lzg里。這樣操作數就不用限定死是string類的

?lhs?:代表 ?Left-Hand ?Side (左側操作數/左側值)

?rhs?:代表 ?Right-Hand ?Side (右側操作數/右側值)

//==運算符重載
bool operator== (const string& lhs, const string& rhs)
{return strcmp(lhs.c_str(), rhs.c_str()) == 0;
}//<運算符重載
bool operator< (const string& lhs, const string& rhs)
{return strcmp(lhs.c_str(), rhs.c_str()) < 0;
}

剩下的四個關系運算符的重載,就可以通過復用這兩個已經重載好了的關系運算符來實現了。

//<=運算符重載
bool operator<= (const string& lhs, const string& rhs)
{return lhs < rhs || lhs == rhs;
}//!=運算符重載
bool operator!= (const string& lhs, const string& rhs)
{return !(lhs == rhs);
}//>運算符重載
bool operator> (const string& lhs, const string& rhs)
{return !(lhs <= rhs);
}//>=運算符重載
bool operator>= (const string& lhs, const string& rhs)
{return !(lhs < rhs);
}

>>和<<運算符的重載

>>運算符的重載

ostream& operator<<(ostream& os, const string& str)
{for (size_t i = 0; i < str.size(); i++){os << str[i];}return os;
}

<<運算符的重載

istream& operator>>(istream& is, string& str)
{str.clear();char ch;//is >> ch;ch = is.get();while (ch != ' ' && ch != '\n'){str += ch;ch = is.get();}return is;
}

參考鏈接

C++string接口

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

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

相關文章

【LeetCode每日一題】

每日一題3. 無重復字符的最長子串題目總體思路代碼1.兩數之和題目總體思路代碼15. 三數之和題目總體思路代碼2025.8.153. 無重復字符的最長子串 題目 給定一個字符串 s &#xff0c;請你找出其中不含有重復字符的 最長 子串 的長度。 示例 1: 輸入: s “abcabcbb” 輸出: 3…

sharding-jdbc讀寫分離配置

一主兩從&#xff0c;爆紅是正常的&#xff0c;不知為啥 spring:shardingsphere:datasource:names: ds_master,ds_s1,ds_s2ds_master:type: com.zaxxer.hikari.HikariDataSourcedriverClassName: com.mysql.jdbc.DriverjdbcUrl: jdbc:mysql://192.168.135.100:3306/gmall_produ…

【大模型核心技術】Dify 入門教程

文章目錄一、Dify 是什么二、安裝與部署2.1 云端 SaaS 版&#xff08;快速入門&#xff09;2.2 私有化部署&#xff08;企業級方案&#xff09;三、界面導航與核心模塊3.1 控制臺概覽3.2 核心功能模塊詳解3.2.1 知識庫&#xff08;RAG 引擎&#xff09;3.2.2 工作流編排3.2.3 模…

homebrew 1

文章目錄brew(1) – macOS&#xff08;或 Linux&#xff09;上缺失的包管理器概要描述術語表基本命令install *formula*uninstall *formula*listsearch \[*text*|/*text*/]命令alias \[--edit] \[*alias*|*alias**command*]analytics \[*subcommand*]autoremove \[--dry-run]bu…

設計索引的原則有哪些?

MySQL 索引設計的核心原則是 在查詢性能與存儲成本之間取得平衡。以下是經過實踐驗證的 10 大設計原則及具體實現策略&#xff1a;一、基礎原則原則說明示例/反例1. 高頻查詢優先為 WHERE、JOIN、ORDER BY、GROUP BY 頻繁出現的列建索引? SELECT * FROM orders WHERE user_id1…

使用影刀RPA實現快遞信息抓取

最近公司項目有個需求&#xff0c;要求抓取快遞單號快遞信息&#xff0c;比如簽收地點、簽收日期等。該項目對應的快遞查詢網站是一個國外的網站&#xff0c;他們有專門的快遞平臺可以用于查詢。該平臺提供了快遞接口進行查詢&#xff0c;但需要付費。同時也提供了免費的查詢窗…

蟻劍--安裝、使用

用途限制聲明&#xff0c;本文僅用于網絡安全技術研究、教育與知識分享。文中涉及的滲透測試方法與工具&#xff0c;嚴禁用于未經授權的網絡攻擊、數據竊取或任何違法活動。任何因不當使用本文內容導致的法律后果&#xff0c;作者及發布平臺不承擔任何責任。滲透測試涉及復雜技…

Varjo XR虛擬現實軍用車輛駕駛與操作培訓

Patria基于混合現實的模擬器提供了根據現代車輛乘員需求定制的培訓&#xff0c;與傳統顯示設置相比&#xff0c;全新的模擬解決方案具有更好的沉浸感和更小的物理空間需求。Patria是芬蘭領先的國防、安全和航空解決方案提供商。提供尖端技術和全面的培訓系統&#xff0c;以支持…

Java 10 新特性及具體應用

目錄 1. 局部變量類型推斷&#xff08;JEP 286&#xff09; 2. 不可修改集合&#xff08;JEP 269&#xff09; 3. 并行全垃圾回收&#xff08;JEP 307&#xff09; 4. 應用類數據共享&#xff08;JEP 310&#xff09; 5. 線程局部管控&#xff08;JEP 312&#xff09; 總結…

【力扣 Hot100】刷題日記

D8 全排列(非回溯法) 全排列原題鏈接 在刷leetcode的時候&#xff0c;看到這道題目并沒法使用像STL的next_permutation方法&#xff0c;感嘆C便利的同時&#xff0c;又惋惜Java并沒有類似的API&#xff0c;那我們只能從原理入手了&#xff0c;仿寫此算法。 其實回溯法更應該…

JetPack系列教程(七):Palette——讓你的APP色彩“飛”起來!

JetPack系列教程&#xff08;七&#xff09;&#xff1a;Palette——讓你的APP色彩“飛”起來&#xff01; 各位開發小伙伴們&#xff0c;還在為APP的配色發愁嗎&#xff1f;別擔心&#xff0c;今天咱們就來聊聊JetPack家族里的“色彩魔法師”——Palette&#xff01;這個神奇的…

力扣hot100 | 矩陣 | 73. 矩陣置零、54. 螺旋矩陣、48. 旋轉圖像、240. 搜索二維矩陣 II

73. 矩陣置零 力扣題目鏈接 給定一個 m x n 的矩陣&#xff0c;如果一個元素為 0 &#xff0c;則將其所在行和列的所有元素都設為 0 。請使用 原地 算法。 示例 1&#xff1a; 輸入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 輸出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]]…

ARC與eARC是什么?主要用在哪?

在家庭影音設備不斷升級的今天&#xff0c;人們對音視頻體驗的要求越來越高。無論是追劇、玩游戲還是觀看電影大片&#xff0c;很多用戶不再滿足于電視自帶的揚聲器&#xff0c;而是希望借助回音壁、功放或家庭影院系統&#xff0c;獲得更加震撼的沉浸式聲音體驗。一、ARC是什么…

解鎖JavaScript性能優化:從理論到實戰

文章目錄 前言 一、常見性能瓶頸剖析 二、實戰案例與優化方案 (一)DOM 操作優化案例? (二)事件綁定優化案例? (三)循環與遞歸優化案例? (四)內存管理優化案例? 三、性能優化工具介紹 總結 前言 性能優化的重要性 在當今數字化時代,Web 應用已成為人們生活和工作…

結構化記憶、知識圖譜與動態遺忘機制在醫療AI中的應用探析(上)

往期相關內容推薦: 基于Python的多元醫療知識圖譜構建與應用研究(上)

XSS攻擊:從原理入門到實戰精通詳解

一、XSS攻擊基礎概念1.1 什么是XSS攻擊 XSS&#xff08;Cross-Site Scripting&#xff0c;跨站腳本攻擊&#xff09;是一種將惡意腳本注入到可信網站中的攻擊手段。當用戶訪問被注入惡意代碼的頁面時&#xff0c;瀏覽器會執行這些代碼&#xff0c;導致&#xff1a;用戶會話被劫…

Leetcode 14 java

今天復習一下以前做過的題目&#xff0c;感覺是忘光了。 160. 相交鏈表 給你兩個單鏈表的頭節點 headA 和 headB &#xff0c;請你找出并返回兩個單鏈表相交的起始節點。如果兩個鏈表不存在相交節點&#xff0c;返回 null 。 圖示兩個鏈表在節點 c1 開始相交&#xff1a; 題目數…

用 FreeMarker 動態構造 SQL 實現數據透視分析

在 ERP、BI 等系統中&#xff0c;數據透視分析&#xff08;Pivot Analysis&#xff09;是非常常見的需求&#xff1a;用戶希望按任意維度&#xff08;如門店、時間、商品分類等&#xff09;進行分組統計&#xff0c;同時選擇不同的指標&#xff08;如 GMV、訂單數、客單價等&am…

13.深度學習——Minst手寫數字識別

第一部分——起手式 import torch from torchvision import datasets, transforms import torch.nn as nn import torch.nn.functional as F import torch.optim as optimuse_cuda torch.cuda.is_available()if use_cuda:device torch.device("cuda") else: device…

【JAVA高級】實現word轉pdf 實現,源碼概述。深坑總結

之前的需求做好后,需求,客戶突發奇想。要將生成的word轉為pdf! 因為不想讓下載文檔的人改動文檔。 【JAVA】實現word添加標簽實現系統自動填入字段-CSDN博客 事實上這個需求難度較高,并不是直接轉換就行的 word文檔當中的很多東西都需要處理 public static byte[] gener…