【C++】 string底層封裝的模擬實現

目錄

  • 前情提要
  • Member functions —— 成員函數
    • 構造函數
    • 拷貝構造函數
    • 賦值運算符重載
    • 析構函數
  • Element access —— 元素訪問
  • Iterator —— 迭代器
  • Capacity —— 容量
    • size
    • capacity
    • clear
    • empty
    • reserve
    • resize
  • Modifiers —— 修改器
    • push_back
    • append
    • operator+=(char ch)
    • operator+=(const char* s)
    • 在pos位置插入n個字符
    • 刪除pos位置的n個元素
    • swap
  • String Operations —— 字符串操作
    • 從pos位置開始找指定的字符
    • 從pos位置開始找指定的字符串(找子串)
    • 從pos位置開始取len個有效字符(取子串)
  • 流插入和流提取
    • 流插入
    • 流提取
  • swap函數解析
  • 源碼

前情提要

因為我們接下來實現的類和和庫中std命名空間的string類的類名相同,所以我們為了防止沖突,用一個bit命名的命名空間解決這個問題

namespace bit
{class string {public://...private:size_t _size;size_t _capacity;char* _str;};
}
  • 接下去呢,就在測試的test3.cpp中包含一下這個頭文件,此時我們才可以在自己實現的類中去調用一些庫函數
#include <iostream>
#include <assert.h>
using namespace std;#include "string.h"

Member functions —— 成員函數

構造函數

  • 這里我們把構造函數的聲明放在string.h的頭文件中,然后用分文件編寫的設計在stirng.cpp中實現構造函數的源代碼
string::string():_str(new char[1] {'\0'}) , _size(0), _capacity(0)
{}
  • 我們利用構造函數的初始化列表來初始化類的3個成員變量,_str表示存儲的string的內存空間,_size表示string對象的長度,_capacity表示這個string 對象的占用空間是多少個字節

或許有的兄弟會有疑問,為什么初始化_str的時候,開辟_str的空間要用 new char[1]而不是new char,這是因為我們string的數據存儲都是連續存儲在一起的,用\0標識結束位置,如果用new char那么單獨在各個不連續的空間并不能讓每個數據后面都有\0,所以我們new char[]連續的空間,存儲在一起,在這塊連續的空間后放上\0。

  • 然后我們立即來測試一下,因為我們自己實現的 string類 是包含在了命名空間bit中的,那么我們在使用這個類的時候就要使用到 域作用限定符::
bit::string s1;

接下來有了無參的構造函數,我們再重載一個有參的字符串構造函數

	string::string(const char* str):_size(strlen(str)){_capacity = _size;_str = new char[_size + 1];strcpy(_str, str);}
  • 先說一下為什么初始化列表我值初始化了_size
    在這里插入圖片描述

在這里插入圖片描述

  • 看到了嗎?不是,哥們,怎么_size都還沒初始化就去把_str初始化了,這個時候問題就不來了嗎,開的空間大小就是隨機的一個值。
  • 那為什么會出現這個情況呢?
    在這里插入圖片描述
  • 這個我在類和對象的文章里面講過,初始化列表要按照類中成員變量的聲明順序來初始化,很明顯_str這個變量比_size這個變量先聲明,所以這里就會出現先初始化_str變量需要用_size,但是_size還沒有初始化的問題

綜上,這就是為什么在初始化列表初始只初始化_size的原因,這樣保證一個變量的順序一定是正確的。其余的變量在函數體里面初始化, 當然第一個無參構造也可以這么寫,我這里提出來這個寫法就是想說明注意這個問題。

拷貝構造函數

  • 在類和對象的文章中,我講過,一個對象拷貝給另一個對象,拷貝構造函數對于內置類型不做處理,對于自定義類型那么就要調用那個被拷貝對象的默認構造函數來拷貝給另一個對象。那么這個時候就會發生淺拷貝的問題。
    在這里插入圖片描述
  • 所謂的淺拷貝,就是如上面一樣, s2是s1的拷貝,但是這種拷貝他不會讓s1指向原來的空間,s2指向新開辟的空間,淺拷貝只是把s1中3個成員變量的值拷貝過去,并不會執行開空間操作,這時候兩個對象的_str的成員變量都指向一個空間,就會導致其中一個對象已經析構了這塊空間,另一個對象銷毀的時候又對這塊已經被析構了的空間造成二次析構,這相當于就是內存泄露,一塊空間已經還給操作系統了,我們還通過一個指針來訪問這塊空間,這時候編譯器就會報錯了。
  • 在這里插入圖片描述

在這里插入圖片描述
在這里插入圖片描述

  • 所以我們就要自定義來實現一個拷貝構造函數解決,兩個指針指向同一塊空間造成兩次析構的問題。
	string::string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}
  • 這段代碼,我們就單獨給_str開了和被拷貝對象一樣大的空間,然后把被拷貝對象的數據拷貝到_str中,完成了深拷貝

賦值運算符重載

對于賦值運算符重載這一塊我們知道它也是屬于類的默認成員函數,如果我們自己不去寫的話類中也會默認地生成一個

  • 但是呢默認生成的這個也會造成一個 淺拷貝 的問題。看到下面圖示,我們要執行s1 = s3,此時若不去開出一塊新空間的話,那么s1和s3就會指向一塊同一塊空間,此時便造成了下面這些問題

1.又會造成上面拷貝構造的問題,兩次析構
2.因為他們指向同一塊空間,修改一個對象就會影響另一個對象
3.原先s1所指向的那塊空間沒人維護了,就造成了內存泄漏的問題

在這里插入圖片描述

	string& string::operator = (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;}}
  • 和拷貝構造一樣,但是多了的是我們要把被賦值的對象的_str內存釋放了,不然那塊空間就沒人管了,就會導致內存泄露。
  • 再補充一點這里為啥要判斷this != &s ,就是自己給自己賦值,也許大部分人不會這么做,但是不一定有人會用錯賦值,就像汽車的車窗防夾功能一樣,沒人會主動讓車窗夾,但是總有些特殊情況。

下面我們來寫一個現代的懶人寫法

string& string::operator = (string s)
{if (this != &s){swap(s);return *this;}
}
  • 是不是非常的簡潔,這個就是用了一個swap函數來解決,來現在我們就來詳細說一下這個swap函數是怎么完成賦值拷貝的。

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

  • 這就完美的完成了我們s1得到了s2的數據,又把原來s1的空間釋放了
    在這里插入圖片描述
  • 附上swap的源代碼,就是和我們模擬的一樣,改變了_str的指向
string::string(const string& s)
{string tmp(s.c_str());swap(tmp);
}
  • 那么拷貝構造也可以利用這個swap函數來完成拷貝給this對象的問題

析構函數

最后的話就是析構函數這一塊,前面在調試的過程中我們已經看到很多遍了,此處不再細述

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

Element access —— 元素訪問

  • 嘿嘿,這個接口可是非常的好用,讓我們string可以像數組一樣使用,爽得很。
char& string::operator[](size_t pos)
{assert(pos >= 0 && pos < _size);return _str[pos];
}
  • 因為我們string底層封裝的用來存儲數據的變量就是一個char*的_str指針,我們在C語言階段說過,指針是可以像數組那樣使用的,所以直接返回_str的pos位置元素就行了,另外注意pos一定是在有效的范圍內,所以斷言一下
// 可讀不可寫
const char& operator[](size_t pos) const
{assert(pos < _size);return _str[pos];
}
  • 這個版本就是提供給我們const的string對象使用的

那現在有人問,所以這玩意實現來有什么用?不急,請看VCR
在這里插入圖片描述

在這里插入圖片描述

  • 是不是很輕松就拿到了string對象中的每個數據

在這里插入圖片描述

在這里插入圖片描述

  • 看到沒,可以像數組一樣更改sting對象的數據

所以非常的方便,const對象一樣用法,就不展示了。

Iterator —— 迭代器

  • 當然,除了[]可以訪問我們的string對象的數據,迭代器也可以
    -而對于迭代器而言我們也是要去實現兩種,一個是非const的,一個則是const的
typedef char* iterator;
typedef const char* const_iterator;
  • 這里的話我就實現一下最常用的【begin】和【end】,首位的話就是_str所指向的這個位置,而末位的話則是_str + _size所指向的這個位置
    iterator begin()
iterator begin()
{return _str;
}iterator end()
{return _str + _size;
}
  • const迭代器就是照葫蘆畫瓢了,只需要重載就行了
const_iterator begin() const
{return _str;
}const_iterator end() const
{return _str + _size;
}

在這里插入圖片描述
在這里插入圖片描述

Capacity —— 容量

size

  • 首先是size, 這個接口非常的簡單,就是統計一下元素個數就行了. 因為不會改變this變量,所以加上一個 const
size_t size() const
{return _size;
}

capacity

  • 對于 capacity() 也是同樣的道理
size_t capacity() const
{return _capacity;
}

clear

  • 這個注意只清理元素個數,不會清理內存空間。我們直接在_str[0]這個位置放上一個\0即可,并且再去修改一下它的_size = 0即可
void clear()
{_str[0] = '\0';_size = 0;
}

empty

  • 對于 empty() 來說呢就是對象中沒有數據,那么使用0 == _size即可
bool empty() const
{return 0 == _size;
}

reserve

  • 當我們構造函數初始化開辟的空間內存不夠的時候,我們就要進行擴容。
// 擴容(修改_capacity)
void reserve(size_t newCapacity = 0)
{// 當新容量大于舊容量的時候,就開空間if (newCapacity > _capacity){// 1.以給定的容量開出一塊新空間char* tmp = new char[newCapacity + 1];// 2.將原本的數據先拷貝過來strcpy(tmp, _str);// 3.釋放舊空間的數據delete[] _str;// 4.讓_str指向新空間_str = tmp;// 5.更新容量大小_capacity = newCapacity;}
}

resize

1.如果n小于_size,那么就把元素個數調整到n就行
2.如果n大于_size小于_capacity,那么把元素個數調整到n,但是不擴容,把多的內存初始為\0
3.如果這個 n > _size 的話,我們便要選擇去進行擴容了

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

Modifiers —— 修改器

push_back

  • 首先明確一點,我們只要是要一直插入數據,就需要足夠大的空間,需要空間我們要動態的擴容,這里我們設置默認空間4, 每次擴容兩倍
  • 最后擴容完,我們就可以安心插入數據了,但是要特別注意在尾插完加上一個\0
	void string::push_back(char ch){//插入之前,檢查是否需要擴容if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}_str[_size++] = ch;_str[_size] = '\0';}

append

  • 接下來我們要追加的是一個字符串,首先我們要先計算出這個字符串的長度,然后在我們原有的元素個數上加上這個字符串的長度,如果總長度大于我們的內存再擴容到滿足裝下這個字符串的長度,否則兩倍擴容即可,有效利用空間。
	void string::append(const char* str){size_t len = strlen(str);if (_size + len >= _capacity){int NewCapacity = _capacity == 0 ? 4 : 2 * _capacity;if (NewCapacity < _size + len){NewCapacity = _size + len;}reserve(NewCapacity);}int j = 0;for (size_t i = _size; i <= _size + len; i++){_str[i] = str[j++];}_size += len;}

operator+=(char ch)

  • 首先的話是去【+=】一個字符,這里我們直接復用前面的push_back()接口即可,最后因為【+=】改變的是自身,所以我們return *this,那么返回一個出了作用域不會銷毀的對象,可以采取 引用返回 減少拷貝
string& operator+=(char ch)
{push_back(ch);return *this;
}

operator+=(const char* s)

  • 而對于【+=】一個字符串,我們則是去復用前面的append()即可
string& operator+=(const char* s)
{append(s);return *this;
}

在pos位置插入n個字符

	void string::insert(size_t pos, size_t n, char ch){assert(pos <= _size);if (_size + n >= _capacity){int NewCapacity = _capacity == 0 ? 4 : 2 * _capacity;if (NewCapacity < _size + n){NewCapacity = _size + n;}reserve(NewCapacity);}int j = _size;for (size_t i = _size + n; i >= pos + n ; i--){_str[i] = _str[j--];}j = 0;for (size_t i = pos; i <pos + n; i++){_str[i] = ch;}_size += n;}- 首先計算我們插入n個字符后的長度,如果大于內存,我們就需要擴容。然后

在這里插入圖片描述
在這里插入圖片描述

刪除pos位置的n個元素

	void string::erase(size_t pos, size_t len){assert(pos <= _size);int j = pos;if (len != npos){for (size_t i = pos + len; i <= _size; i++){_str[j++] = _str[i];}_size -= len;}else{_str[pos + 1] = '\0';_size = pos + 1;}}

其實就是從pos + len 個位置的元素開始往前覆蓋,就行,然后減少長度,在遍歷的時候因為我們是遍歷到長度的位置就不會遍歷到長度之外的字符,然后后面插入的時候也能覆蓋

在這里插入圖片描述

swap

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

String Operations —— 字符串操作

從pos位置開始找指定的字符

  • 從0開始遍歷到長度的位置查找有沒有指定的字符,如果有返回下標,沒有返回npos
size_t find(char ch, size_t pos) const
{assert(pos <= _size);for (size_t i = 0; i < _size; i++){if (_str[i] == ch){return i;}}return npos;
}
  • npos是一個在const靜態成員變量的無符號整數,用于標識沒查找到指定的內容,原則來說靜態成員變量應該在類外初始化,因為他要給類的所以對象使用,可是這里在缺省參數的位置初始化,這個位置原本是留給初始化列表中沒初始化成功的,但是這是一個例外.條件必須是const常量 并且整形的數據才能這樣寫
    在這里插入圖片描述

從pos位置開始找指定的字符串(找子串)

  • 這可以使用strstr子串查找函數,如果返回指針為空就沒有,否則會返回子串的起始地址,如果我們想要得到子串在_str中的指針距離起始位置指針-指針即可
size_t find(const char* s, size_t pos)	const
{assert(pos < _size);char* tmp = strstr(_str, s);if (tmp){// 指針相減即為距離return tmp - _str;}return npos;
}

從pos位置開始取len個有效字符(取子串)

string string::substr(size_t pos, size_t len)
{assert(pos < _size);if (len > (_size -= pos)){len = _size - pos;	}string sub;sub.reserve(len);for (size_t i = 0; i <= len; i++){sub += _str[pos + i];}return sub;
}
  • 如果截取的長度大于_size - = pos,從pos開始的總長度,最多只能截取到 _size - pos,后面就復用sub += 尾插了

流插入和流提取

流插入

  • 如果不想寫s1 << cout這種就需要吧this這個在類中固定第一個位置的參數放在后面去,這樣我們就不能重載在類中,重載在全局中
ostream& operator<<(ostream& os, const string& s)
{for (size_t i = 0; i < s.size(); i++){os << s[i];}return os;
}

流提取

istream& operator>>(istream& is,  string& s)
{s.clear(); // 對原來的字符串清理,重新輸入char ch;ch = is.get();char buffer[256];int i = 0;while (ch != ' ' && ch != '\n'){buffer[i++] = ch;if (i == 255){buffer[i] = '\0';s += buffer;i = 0;}ch = is.get();}if (i > 0){buffer[i] = '\0';s += buffer;}return is;
}
  • 1.這里輸入ch一定要用流提取的get函數,不然cin 和scanf會直接忽略空格和\n, 不會寫入到ch里面導致不能結束,get就不會忽略
    2.這里用buffer數組的原因是:盡量減少擴容的次數和空間浪費的情況
    3.所以我們使用一個數組buffer暫存里面,如果buffer存儲滿了再擴容,這時候只需要擴容一次,而且buffer是在棧上開辟的,是可以這個函數結束就銷毀了

在這里插入圖片描述

swap函數解析

  • 為啥要自己寫一個成員函數swap?可以看一下算法庫swap的過程
    在這里插入圖片描述在這里插入圖片描述

在這里插入圖片描述

源碼

string.h
#define  _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
using namespace std;
namespace bit
{class string{public:typedef char* iterator;typedef const char* const_iterator;string();string(const char* str);string(const string& s);~string();void reserve(size_t n);iterator begin();iterator end();char& operator[](size_t pos);const char& operator[](size_t pos) const;void insert(size_t pos, char ch);void insert(size_t pos, const char* str);void insert(size_t pos, size_t n, char ch);void erase(size_t pos, size_t len = npos);void clear();void swap(string& s);size_t size() const;const_iterator begin() const;const_iterator end() const;void push_back(char ch);void append(const char* str);void resize(size_t n, char ch = '\0');size_t find(char c, size_t pos = 0);size_t find(const char* str, size_t pos = 0);string& operator+=(char ch);string& operator+=(const char* str);string& operator = (string s);string substr(size_t pos, size_t len = npos);const char* c_str() const{return _str;}private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0;public:static const size_t npos = -1;};ostream& operator<<(ostream& os, const string& s);istream& operator>>(istream& is,  string& s);istream& getline(istream& is, string& s, char delim = '#');void swap(string& s1, string& s2);
}
string.cpp
#include"String.h"
#include<iostream>
using namespace std;
namespace bit
{typedef char* iterator;typedef const char* const_iterator;string::string():_str(new char[1] {'\0'}) //不能初始化為null實現c_str的時候防止cout對空指針解引用, _size(0), _capacity(0){}string::string(const char* str):_size(strlen(str)){_capacity = _size;_str = new char[_size + 1];strcpy(_str, str);}//傳統寫法//string::string (const string& s)//{//	_str = new char[s._capacity + 1];//	strcpy(_str, s._str);//	_size = s._size;//	_capacity = s._capacity;//}string::string(const string& s){string tmp(s.c_str());swap(tmp);}string::~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}void string:: reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}iterator string::begin(){return _str;}char& string::operator[](size_t pos){assert(pos >= 0 && pos < _size);return _str[pos];}void string::resize(size_t n, char ch){if (n > _size){if (n > _capacity){reserve(n);}for (size_t i = _size; i < n; i++){_str[i] = ch;}}else{_size = n;_str[n] = '\0';}}const char& string::operator[](size_t pos) const{assert(pos >= 0 && pos <= _size);return _str[pos];}size_t string::size() const{return _size;}iterator string::end(){return _str + _size;}const_iterator string::begin() const{return _str;}const_iterator string::end() const{return _str + _size;}void string::push_back(char ch){//插入之前,檢查是否需要擴容if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}_str[_size++] = ch;_str[_size] = '\0';}void string::append(const char* str){size_t len = strlen(str);if (_size + len >= _capacity){int NewCapacity = _capacity == 0 ? 4 : 2 * _capacity;if (NewCapacity < _size + len){NewCapacity = _size + len;}reserve(NewCapacity);}int j = 0;for (size_t i = _size; i <= _size + len; i++){_str[i] = str[j++];}_size += len;}void string::insert(size_t pos, char ch) {assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : 2 * _capacity);}for (int i = _size + 1; i > pos; i--){_str[i] = _str[i - 1];}_str[pos] = ch;_size++;_str[_size] = '\0';}void string::insert(size_t pos, size_t n, char ch){assert(pos <= _size);if (_size + n >= _capacity){int NewCapacity = _capacity == 0 ? 4 : 2 * _capacity;if (NewCapacity < _size + n){NewCapacity = _size + n;}reserve(NewCapacity);}int j = _size;for (size_t i = _size + n; i >= pos + n ; i--){_str[i] = _str[j--];}j = 0;for (size_t i = pos; i <pos + n; i++){_str[i] = ch;}_size += n;}void string::insert(size_t pos, const char* str){assert(pos <= _size);int len = strlen(str);if (_size + len >= _capacity){int NewCapacity = _capacity == 0 ? 4 : 2 * _capacity;if (NewCapacity < _size + len){NewCapacity = _size + len;}reserve(NewCapacity);}int j = _size;for (int i = _size + len ; i >= pos + len; i--){_str[i] = _str[j--];}j = 0;for (int i = pos; i < pos + len; i++){_str[i] = str[j++];}_size += len;}void string::erase(size_t pos, size_t len){assert(pos <= _size);int j = pos;if (len != npos){for (size_t i = pos + len; i <= _size; i++){_str[j++] = _str[i];}_size -= len;}else{_str[pos + 1] = '\0';_size = pos + 1;}}void string::clear(){_size = 0;_str[0] = '\0';}size_t string::find(char c, size_t pos){assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == c){return i;}}return npos;}size_t string::find(const char* str, size_t pos){assert(pos < _size);const char* ptr = strstr(_str + pos, str);if (ptr == nullptr){return npos;}else{return ptr - _str;}}string& string::operator+=(char ch){push_back(ch);return *this;}string& string::operator+=(const char* str){append(str);return *this;}/*string& string::operator = (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){if (this != &s){swap(s);return *this;}}string string::substr(size_t pos, size_t len){assert(pos < _size);if (len > (_size -= pos)){len = _size - pos;	}string sub;sub.reserve(len);for (size_t i = 0; i <= len; i++){sub += _str[pos + i];}return sub;}ostream& operator<<(ostream& os, const string& s){for (size_t i = 0; i < s.size(); i++){os << s[i];}return os;}istream& operator>>(istream& is,  string& s){s.clear(); // 對原來的字符串清理,重新輸入char ch;ch = is.get();char buffer[256];int i = 0;while (ch != ' ' && ch != '\n'){buffer[i++] = ch;if (i == 255){buffer[i] = '\0';s += buffer;i = 0;}ch = is.get();}if (i > 0){buffer[i] = '\0';s += buffer;}return is;}istream& getline(istream& is, string& s, char delim){s.clear(); // 對原來的字符串清理,重新輸入char ch;ch = is.get();char buffer[256];int i = 0;while ( ch != delim){buffer[i++] = ch;if (i == 255){buffer[i] = '\0';s += buffer;i = 0;}ch = is.get();}if (i > 0){buffer[i] = '\0';s += buffer;}return is;}void string::swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}void swap(string& s1, string& s2){s1.swap(s2);}
}

這就是stirng的底層模擬實現,看完去實現一下吧

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

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

相關文章

計算機網絡相關知識小結

計算機網絡 1.計算機網絡&#xff1a;獨立計算機&#xff0c;通信線路連接&#xff0c;實現資源共享 2.組成&#xff1a;資源子網和通信子網 3.拓撲分類 4.范圍&#xff1a;LAN, MAN. WAN 5、有線和無線 6.按照方向&#xff1a;單工、雙工&#xff0c;全雙工 7.傳輸對象方式&a…

16-CSS3新增選擇器

知識目標 掌握屬性選擇器的使用掌握關系選擇器的使用掌握結構化偽類選擇器的使用掌握偽元素選擇器的使用 如何減少文檔內class屬性和id屬性的定義&#xff0c;使文檔變得更加簡潔&#xff1f; 可以通過屬性選擇器、關系選擇器、結構化偽類選擇器、偽元素選擇器。 1. 屬性選擇…

【彈性計算】異構計算云服務和 AI 加速器(四):FPGA 虛擬化技術

《異構計算云服務和 AI 加速器》系列&#xff0c;共包含以下文章&#xff1a; 異構計算云服務和 AI 加速器&#xff08;一&#xff09;&#xff1a;功能特點異構計算云服務和 AI 加速器&#xff08;二&#xff09;&#xff1a;適用場景異構計算云服務和 AI 加速器&#xff08;…

Java進階——位運算

位運算直接操作二進制位&#xff0c;在處理底層數據、加密算法、圖像處理等領域具有高效性能和效率。本文將深入探討Java中的位運算。 本文目錄 一、位運算簡介1. 與運算2. 或運算異或運算取反運算左移運算右移運算無符號右移運算 二、位運算的實際應用1. 權限管理2. 交換兩個變…

OpenAI深夜直播「偷襲」谷歌!GPT-4o原生圖像生成:奧特曼帶梗圖,AGI戰場再燃戰火

引言&#xff1a;AI戰場的「閃電戰」 當谷歌剛剛發布「地表最強」Gemini 2.5 Pro時&#xff0c;OpenAI立即以一場深夜直播「閃電反擊」——GPT-4o的原生圖像生成功能正式上線&#xff01;從自拍變梗圖到相對論漫畫&#xff0c;奧特曼&#xff08;OpenAI團隊&#xff09;用一連…

鴻蒙harmonyOS:筆記 正則表達式

從給出的文本中&#xff0c;按照既定的相關規則&#xff0c;匹配出符合的數據&#xff0c;其中的規則就是正則表達式&#xff0c;使用正則表達式&#xff0c;可以使得我們用簡潔的代碼就能實現一定復雜的邏輯&#xff0c;比如判斷一個郵箱賬號是否符合正常的郵箱賬號&#xff0…

[首發]烽火HG680-KD-海思MV320芯片-2+8G-安卓9.0-強刷卡刷固件包

烽火HG680-KD-海思MV320芯片-28G-安卓9.0-強刷卡刷固件包 U盤強刷刷機步驟&#xff1a; 1、強刷刷機&#xff0c;用一個usb2.0的8G以下U盤&#xff0c;fat32&#xff0c;2048塊單分區格式化&#xff08;強刷對&#xff35;盤非常非常挑剔&#xff0c;usb2.0的4G U盤兼容的多&a…

Python-數據處理

第十五章 生成數據 安裝Matplotlib&#xff1a;通過pip install matplotlib命令安裝庫。繪制折線圖的核心語法為&#xff1a; import matplotlib.pyplot as plt x_values [1, 2, 3] y_values [1, 4, 9] plt.plot(x_values, y_values, linewidth2) plt.title(&quo…

Java基礎-23-靜態變量與靜態方法的使用場景

在Java中&#xff0c;static關鍵字用于定義靜態變量和靜態方法。它們屬于類本身&#xff0c;而不是類的某個實例。因此&#xff0c;靜態成員可以通過類名直接訪問&#xff0c;而無需創建對象。以下是靜態變量與靜態方法的常見使用場景&#xff1a; 一、靜態變量的使用場景 靜態…

大模型架構記錄12【Agent實例-tool】

運行根目錄下幾個ipynb文件- Learn-Agent.ipynb- 學習《Custom agent 自定義代理》部分- v1-Create-Custom-Agent.ipynb- v2-Create-Custom-Agent.ipynb- 基于v1&#xff0c;新增一些職位描述&#xff08;JD&#xff09;信息- v3-Create-Custom-Agent.ipynb- 基于v2&#xff0c…

在MCU工程中優化CPU工作效率的幾種方法

在嵌入式系統開發中&#xff0c;優化 CPU 工作效率對于提升系統性能、降低功耗、提高實時性至關重要。Keil 作為主流的嵌入式開發工具&#xff0c;提供了多種優化策略&#xff0c;包括 關鍵字使用、內存管理、字節對齊、算法優化 等。本文將從多個方面介紹如何在 Keil 工程中優…

Linux系統下C語言fork函數使用案例

一、fork函數的作用 生成一個子進程&#xff0c;異步執行某個任務&#xff1b; 二、子進程的作用 1、子進程能復制一份父進程的變量、函數&#xff1b; 2、子進程可以和父進程同時并發執行&#xff1b; 函數語法&#xff1a; pid_t fork() 說明&#xff1a;調用后返回一個進程…

MySQL中的CREATE TABLE LIKE和CREATE TABLE SELECT

MySQL中的CREATE TABLE LIKE和CREATE TABLE SELECT CREATE TABLE LIKECREATE TABLE SELECT CREATE TABLE LIKE CREATE TABLE ... LIKE可以用來復制表結構&#xff0c;源表上的索引和約束也會復制。CREATE TABLE ... LIKE不能復制表數據。CREATE TABLE ... LIKE只能復制基表&…

Java開發者指南:深入理解HotStuff新型共識算法

&#x1f9d1; 博主簡介&#xff1a;CSDN博客專家、全棧領域優質創作者、高級開發工程師、高級信息系統項目管理師、系統架構師&#xff0c;數學與應用數學專業&#xff0c;10年以上多種混合語言開發經驗&#xff0c;從事DICOM醫學影像開發領域多年&#xff0c;熟悉DICOM協議及…

opencv圖像處理之指紋驗證

一、簡介 在當今數字化時代&#xff0c;生物識別技術作為一種安全、便捷的身份驗證方式&#xff0c;正廣泛應用于各個領域。指紋識別作為生物識別技術中的佼佼者&#xff0c;因其獨特性和穩定性&#xff0c;成為了眾多應用場景的首選。今天&#xff0c;我們就來深入探討如何利…

wfs.js之h264轉碼mp4分析

準備源文件 下載源文件 git clone https://github.com/ChihChengYang/wfs.js.git編譯后得到wfs.js這個文件 調用 在demo/index.html中&#xff0c;前端對wfs.js進行了調用 var video1 document.getElementById("video1"), wfs new Wfs(); wfs.attachMedia…

協程 Coroutine

協程是 C20 引入的新特性。 文章目錄 基本概念std::coroutine_handlepromise 類型co_yield 基本用法 優勢異步 TCPco_await 基本概念 協程&#xff08;Coroutine&#xff09;是一種比線程更加輕量級的并發編程模型。協程的調度由程序員手動控制。 異步不是并行&#xff0c;但…

uniapp中的流式輸出

一、完整代碼展示 目前大多數的ai對話都是流式輸出&#xff0c;也就是對話是一個字或者多個字逐一進行顯示的下面是一個完整的流式顯示程序&#xff0c;包含的用戶的消息發出和ai的消息回復 <template><view class"chat-container"><view class&quo…

洛谷題單1-P5703 【深基2.例5】蘋果采購-python-流程圖重構

題目描述 現在需要采購一些蘋果&#xff0c;每名同學都可以分到固定數量的蘋果&#xff0c;并且已經知道了同學的數量&#xff0c;請問需要采購多少個蘋果&#xff1f; 輸入格式 輸入兩個不超過 1 0 9 10^9 109 正整數&#xff0c;分別表示每人分到的數量和同學的人數。 輸…

JS 手撕題高頻考點

前端面試中&#xff0c;JS 手撕題是高頻考點&#xff0c;主要考察 編程能力、算法思維、JS 核心知識。以下是最常見的手撕題分類 代碼示例&#xff1a; 目錄 &#x1f4cc; 1. 手寫函數柯里化&#x1f4cc; 2. 手寫 debounce&#xff08;防抖&#xff09;&#x1f4cc; 3. 手寫…