從C/C++遷移到Go:內存管理思維轉變

一、引言

在當今高速發展的軟件開發世界中,語言遷移已成為技術進化的常態。作為一名曾經的C/C++開發者,我經歷了向Go語言轉變的全過程,其中最大的認知挑戰來自內存管理模式的根本性差異。

我記得第一次接觸Go項目時的困惑:沒有析構函數?不用手動釋放內存?這些看似簡單的變化,實則需要一套全新的思維模式。本文旨在幫助有C/C++背景、正在或即將使用Go的開發者,特別是那些已有1-2年Go經驗但仍在掙扎于內存管理思維轉變的朋友們。

Go語言設計的核心理念之一是簡化內存管理,讓開發者將更多精力集中在業務邏輯上。這種設計不僅提高了開發效率,也減少了常見的內存錯誤,如懸垂指針、重復釋放等。然而,這并不意味著我們可以完全忽視內存管理 - 相反,我們需要以不同的方式思考它。

二、C/C++與Go內存管理模型對比

C/C++內存管理回顧:手動分配與釋放

在C/C++中,內存管理就像是精心照料一座花園 - 每一株植物(內存)都需要親手種植和清理:

// C語言中的內存管理
void process_data() {char* buffer = (char*)malloc(1024);  // 手動分配內存if (buffer == NULL) {return;  // 內存分配失敗處理}// 使用buffer進行數據處理// ...free(buffer);  // 必須手動釋放內存// 如果這里忘記釋放,或提前返回,就會造成內存泄漏
}// C++中使用new/delete
void process_data_cpp() {int* numbers = new int[100];  // 動態分配數組// 使用numbers數組// ...delete[] numbers;  // 必須記得釋放,且使用正確的形式
}

C++后來引入了RAII(資源獲取即初始化)模式和智能指針,部分改善了手動內存管理的問題:

// 使用智能指針
void modern_cpp_approach() {std::unique_ptr<int[]> numbers(new int[100]);// 使用numbers// ...// 不需要手動釋放,智能指針會在作用域結束時自動處理
}// 或使用標準容器
void using_containers() {std::vector<int> numbers(100);  // 內存管理由vector處理// 使用numbers// ...// vector離開作用域時自動釋放內存
}

C/C++內存管理的核心特點

  • 開發者對內存有完全控制權
  • 必須手動跟蹤每個對象的生命周期
  • 資源管理責任完全在開發者
  • 內存錯誤(泄漏、越界、使用已釋放內存)是常見問題

Go的自動內存管理

Go語言則像是一個有智能園丁的花園——你只需決定種什么花,園丁會處理灌溉和清理工作:

// Go語言中的內存管理
func processData() {buffer := make([]byte, 1024)  // 分配內存// 使用buffer進行處理// ...// 無需手動釋放內存// 當buffer不再被引用時,垃圾回收器會自動回收它
}

Go的垃圾回收器(GC)是一個并發的三色標記清除收集器,它會周期性地識別和回收不再使用的內存。這種設計極大地簡化了內存管理,但也引入了新的考量點。

Go內存分配策略

特性棧分配堆分配
分配速度非常快相對較慢
生命周期函數返回自動釋放由GC回收
適用情況局部變量且不會逃逸返回值、大對象、全局引用
性能影響幾乎沒有開銷有GC開銷

逃逸分析是Go編譯器的一項重要技術,它決定一個變量應該分配在棧上還是堆上。當編譯器無法確定變量在函數返回后是否還會被使用時,通常會采取保守策略,將其分配在堆上。

// 這個變量會留在棧上 - 因為它的生命周期僅限于函數內
func calculateSum() int {x := 10  // 在棧上分配y := 20  // 在棧上分配return x + y  // 返回值,不是引用
}// 這個變量會逃逸到堆上 - 因為它在函數結束后仍然可訪問
func createData() *int {x := 10  // 初始在棧上,但會逃逸到堆上return &x  // 返回局部變量的指針,導致變量必須在堆上分配
}

三、思維模式轉變的關鍵點

從C/C++遷移到Go,需要進行以下幾個關鍵的思維轉變:

從"我必須釋放內存"到"讓GC處理它"

在C/C++中,未釋放的內存是程序員的失誤,而在Go中,這是預期行為。這種轉變需要建立對垃圾回收器的信任,同時了解其工作原理以避免性能問題。

// C++思維方式(在Go中不必要)
func processDataCppStyle(data []byte) []byte {result := make([]byte, len(data)*2)// 處理數據...// 不需要也不應該嘗試"釋放"result// defer free(result) // 這在Go中是錯誤的思維return result // 返回result,讓調用者使用,GC會在適當時機回收
}

從"對象所有權"到"對象生命周期"

C++開發者習慣于思考"誰擁有這個對象",而Go開發者應該思考"這個對象的引用存在多久"。

// 在Go中,我們關注引用而非所有權
type Resource struct {data []byte
}func NewResource() *Resource {return &Resource{data: make([]byte, 1024),}
}func processResource() {r := NewResource()// 使用r// ...// 不需要顯式釋放r// 當沒有任何引用指向r時,它會被自動回收
}

從"最小化內存分配"到"合理設計對象"

在C/C++中,每次內存分配都是需要權衡的,而在Go中,我們應該更關注對象和數據結構的合理設計,而非糾結于每次分配。

內存管理思維對比圖

在這里插入圖片描述

注意:上圖是概念性的,表達了C/C++與Go在內存管理思維上的差異。

從"手動內存池管理"到"理解GC工作方式"

不再需要創建復雜的對象池來避免內存分配(除非在特定的高性能場景),而是需要了解GC的工作方式,避免給它增加不必要的負擔。

四、實際項目中的內存優化經驗

雖然Go有自動內存管理,但在大型項目中,依然需要關注內存使用效率。以下是一些實戰經驗:

大型對象處理策略

當處理大型對象時,頻繁的分配和回收會給GC帶來顯著壓力。對于這類場景,可以考慮:

// 避免在熱路徑中頻繁創建大對象
// 不推薦的方式
func ProcessRequests(requests []Request) {for _, req := range requests {// 每個請求都創建一個大型緩沖區buffer := make([]byte, 10*1024*1024)processWithBuffer(req, buffer)}
}// 推薦的方式
func ProcessRequestsOptimized(requests []Request) {// 創建一次,重復使用buffer := make([]byte, 10*1024*1024)for _, req := range requests {processWithBuffer(req, buffer)// 可以在這里清空buffer,而不是重新分配}
}

內存池復用與sync.Pool應用場景

對于需要頻繁創建和銷毀的臨時對象,sync.Pool提供了一種重用對象的機制,可有效減少GC壓力:

var bufferPool = &sync.Pool{New: func() interface{} {// 創建一個默認大小的緩沖區return make([]byte, 8192)},
}func ProcessRequest(data []byte) []byte {// 從池中獲取一個緩沖區buffer := bufferPool.Get().([]byte)// 確保無論如何都將緩沖區放回池中defer bufferPool.Put(buffer)// 重置buffer或調整大小buffer = buffer[:0] // 清空但保留容量if cap(buffer) < len(data)*2 {// 如果容量不夠,創建新的buffer = make([]byte, 0, len(data)*2)}// 使用buffer處理數據// ...return result // 注意返回的是結果,不是buffer本身
}

重要提示sync.Pool不提供內存所有權保證,對象可能隨時被回收,因此不適合用來管理需要長期持有的資源。它最適合處理生命周期短暫的臨時對象。

切片和映射的預分配與重用技巧

預分配足夠的容量可以減少內存重新分配和數據復制:

// 不推薦:會導致多次擴容和內存復制
func buildSliceInefficient(n int) []int {result := []int{}  // 容量為0for i := 0; i < n; i++ {result = append(result, i)  // 可能多次觸發擴容}return result
}// 推薦:預分配容量
func buildSliceEfficient(n int) []int {result := make([]int, 0, n)  // 預分配足夠容量for i := 0; i < n; i++ {result = append(result, i)  // 不會觸發擴容}return result
}// 同樣適用于map
func buildMapEfficient(n int) map[string]int {result := make(map[string]int, n)  // 預估容量for i := 0; i < n; i++ {result[fmt.Sprintf("key-%d", i)] = i}return result
}

避免不必要的堆分配

了解Go的逃逸分析規則,可以幫助我們減少不必要的堆分配:

// 會導致堆分配的函數
func createBufferEscape() *bytes.Buffer {buf := new(bytes.Buffer)  // 會分配在堆上,因為返回了指針buf.WriteString("hello")return buf
}// 避免堆分配的版本
func createBufferNoEscape() bytes.Buffer {var buf bytes.Buffer  // 在調用者的棧上分配buf.WriteString("hello")return buf  // 返回值,而非指針,可能在棧上處理
}// 當返回值較大時,編譯器可能還是會選擇堆分配
// 這時我們可以考慮傳入預分配的緩沖區
func writeToBuffer(buf *bytes.Buffer) {buf.WriteString("hello")// 不返回任何東西,調用者已持有buf
}

五、常見內存問題診斷與處理

盡管Go有GC,但我們仍然需要診斷和處理內存問題。

內存泄漏排查工具與方法

Go中的內存泄漏通常由于某些對象被長期引用但不再使用導致的:

// 潛在的內存泄漏示例
var globalCache = make(map[string]*largeObject)func processAndCache(key string, data []byte) {obj := processData(data)  // 創建大對象globalCache[key] = obj    // 存入全局緩存// 問題:從不清理cache,導致內存持續增長
}// 改進版本
var (globalCache = make(map[string]*largeObject)cacheMutex  = &sync.Mutex{}
)func processAndCacheImproved(key string, data []byte) {cacheMutex.Lock()defer cacheMutex.Unlock()// 檢查緩存大小,必要時清理if len(globalCache) > maxCacheSize {// 清理部分緩存,例如按LRU策略evictOldEntries()}obj := processData(data)globalCache[key] = obj
}

使用pprof進行內存分析

import ("net/http"_ "net/http/pprof"  // 注冊pprof handlers"runtime/pprof""os"
)func main() {// 在后臺啟動pprof服務go func() {http.ListenAndServe("localhost:6060", nil)}()// 在關鍵點記錄堆內存分析f, _ := os.Create("heap.prof")defer f.Close()pprof.WriteHeapProfile(f)// 應用主邏輯...
}

通過訪問http://localhost:6060/debug/pprof/可以查看各種性能指標,或使用命令行工具分析:

go tool pprof http://localhost:6060/debug/pprof/heap

GC調優參數與實戰經驗

Go的GC相對黑盒,但我們可以通過環境變量和運行時參數進行有限調整:

import "runtime"func configureGC() {// 設置GC目標百分比:默認是100,意味著使用內存是上次GC后的2倍時觸發// 調低這個值會導致GC更頻繁,但每次停頓更短// 調高這個值會減少GC次數,但可能增加單次停頓時間和內存使用量runtime.SetGCPercent(100)// 手動觸發GC(通常不建議,但在某些場景有用)runtime.GC()// 查看當前內存統計var stats runtime.MemStatsruntime.ReadMemStats(&stats)log.Printf("Alloc = %v MiB", stats.Alloc / 1024 / 1024)
}

GC實戰經驗

  • 在CPU密集型應用中,可以考慮調高GOGC值減少GC頻率
  • 在內存受限環境中,適當調低GOGC值減少峰值內存使用
  • 對延遲敏感的服務,可以在請求低谷期手動觸發GC

六、案例分析:從C++到Go的重構實踐

我曾參與將一個高性能網絡服務器從C++重構為Go的項目,分享一些經驗和代碼對比:

網絡服務框架重構案例

C++版本的網絡服務器

// C++版本 (簡化)
class Connection {
private:std::vector<char> receiveBuffer_;std::vector<char> sendBuffer_;public:Connection() : receiveBuffer_(8192), sendBuffer_(8192) {}~Connection() {// 關閉連接,清理資源close();}void processRequests() {while (isConnected()) {// 分配內存用于新請求std::unique_ptr<Request> req(new Request());// 接收和解析請求if (!receiveRequest(req.get())) {continue;}// 處理請求std::unique_ptr<Response> resp(processRequest(req.get()));// 發送響應sendResponse(resp.get());// 智能指針自動釋放內存}}
};

Go版本的網絡服務器

// Go版本 (簡化)
type Connection struct {conn net.Conn
}func NewConnection(conn net.Conn) *Connection {return &Connection{conn: conn}
}func (c *Connection) ProcessRequests(ctx context.Context) error {// 預分配一次,重復使用recvBuf := make([]byte, 8192)for {select {case <-ctx.Done():return ctx.Err()default:// 接收請求n, err := c.conn.Read(recvBuf)if err != nil {return err}// 解析請求req, err := ParseRequest(recvBuf[:n])if err != nil {continue}// 處理請求并獲取響應resp := ProcessRequest(req)// 發送響應if err := SendResponse(c.conn, resp); err != nil {return err}// 無需手動釋放req和resp,GC會處理}}
}

重構后的變化

  1. 代碼更加簡潔,不需要顯式內存管理
  2. 錯誤處理更加自然,通過返回值而非異常
  3. 通過context支持更優雅的超時和取消
  4. 性能接近C++版本,但開發效率顯著提高
  5. 內存使用更可預測,避免了C++版本中的一些細微內存泄漏

七、最佳實踐與經驗總結

基于我們的實踐經驗,總結出以下Go內存管理最佳實踐:

數據結構設計原則

  1. 優先考慮值類型:對于小型對象,使用值類型而非指針可以減少GC壓力。
// 不推薦 - 小結構體使用指針傳遞
type Point struct {X, Y int
}
func (p *Point) Move(dx, dy int) {p.X += dxp.Y += dy
}// 推薦 - 小結構體使用值傳遞
func (p Point) MoveBy(dx, dy int) Point {return Point{p.X + dx, p.Y + dy}
}
  1. 考慮內存布局:緊湊的內存布局有利于緩存局部性。
// 結構體字段順序會影響內存對齊
// 不優化的結構體
type UserInfo struct {Name    string  // 16字節Age     int     // 8字節Active  bool    // 1字節 + 7字節填充Address string  // 16字節
}// 優化后的結構體 - 減少填充
type UserInfoOptimized struct {Name    string  // 16字節Address string  // 16字節Age     int     // 8字節Active  bool    // 1字節 + 7字節填充
}

大對象處理策略

  1. 分塊處理:將大數據集分割成小塊處理,避免一次性分配大量內存。
// 處理大文件時,使用緩沖區讀取
func ProcessLargeFile(filename string) error {file, err := os.Open(filename)if err != nil {return err}defer file.Close()buffer := make([]byte, 32*1024) // 32KB緩沖區for {n, err := file.Read(buffer)if err == io.EOF {break}if err != nil {return err}// 處理緩沖區中的數據ProcessChunk(buffer[:n])}return nil
}
  1. 考慮使用mmap:對于超大文件,考慮使用內存映射。
import "golang.org/x/exp/mmap"func ProcessWithMMap(filename string) error {reader, err := mmap.Open(filename)if err != nil {return err}defer reader.Close()// 直接訪問映射內存,無需加載整個文件data := make([]byte, 100)_, err = reader.ReadAt(data, 0)return err
}

并發場景下的內存考量

  1. 避免全局對象過度共享:減少鎖競爭,考慮分片或本地緩存。
// 不推薦:所有goroutine共享一個map,高并發下鎖競爭嚴重
var (globalCache = make(map[string]interface{})cacheMutex  = &sync.RWMutex{}
)// 推薦:使用分片減少鎖競爭
type ShardedCache struct {shards    [256]shardhashFunc  func(string) uint8
}type shard struct {items map[string]interface{}mu    sync.RWMutex
}func (c *ShardedCache) Get(key string) interface{} {shardIndex := c.hashFunc(key)shard := &c.shards[shardIndex]shard.mu.RLock()defer shard.mu.RUnlock()return shard.items[key]
}
  1. 控制并發度:過高的并發會導致過多的內存分配。
// 使用有界工作池控制并發度
func ProcessItems(items []Item) {const maxWorkers = 100semaphore := make(chan struct{}, maxWorkers)var wg sync.WaitGroupfor _, item := range items {wg.Add(1)semaphore <- struct{}{} // 獲取令牌go func(item Item) {defer func() {<-semaphore // 釋放令牌wg.Done()}()ProcessItem(item)}(item)}wg.Wait()
}

八、常見誤區與注意事項

從C/C++轉到Go的開發者經常會陷入以下誤區:

Go并非沒有內存泄漏

即使有GC,Go程序仍然可能出現內存泄漏,尤其是以下情況:

// 泄漏1:goroutine泄漏
func leakyFunction() {ch := make(chan int) // 無緩沖通道go func() {val := <-ch // 永遠阻塞,因為沒有人發送fmt.Println(val)}()// goroutine會泄漏,因為通道永遠不會關閉
}// 泄漏2:忘記關閉文件/網絡連接
func leakyResourceHandling() {file, _ := os.Open("data.txt")// 忘記 defer file.Close()data, _ := ioutil.ReadAll(file)process(data)// 文件句柄泄漏
}// 泄漏3:不斷增長的緩存
var cache = map[string][]byte{}
var mutex = &sync.Mutex{}func addToCache(key string, value []byte) {mutex.Lock()defer mutex.Unlock()cache[key] = value// 永不清理的緩存最終會耗盡內存
}

過度優化的陷阱

有時候過度關注內存優化反而會適得其反:

// 過度優化:復雜的對象池
type complexObjectPool struct {pool     []*ComplexObjectpoolLock sync.Mutex
}func (p *complexObjectPool) Get() *ComplexObject {p.poolLock.Lock()defer p.poolLock.Unlock()if len(p.pool) == 0 {return &ComplexObject{}}obj := p.pool[len(p.pool)-1]p.pool = p.pool[:len(p.pool)-1]return obj
}// 更好的選擇:使用標準庫
var stdPool = sync.Pool{New: func() interface{} {return &ComplexObject{}},
}

忽視GC開銷的問題

在某些高性能場景下,GC暫停可能成為性能瓶頸:

// 問題代碼:頻繁分配大量臨時對象
func ProcessLargeDataset(data []byte) []Result {var results []Result// 處理每個數據塊for i := 0; i < len(data); i += chunkSize {chunk := data[i:min(i+chunkSize, len(data))]// 每次迭代產生大量臨時對象intermediateResults := process(chunk)// 合并結果results = append(results, intermediateResults...)}return results
}// 改進:減少臨時對象,預分配內存
func ProcessLargeDatasetImproved(data []byte) []Result {// 預估結果大小results := make([]Result, 0, len(data)/averageResultSize)// 重用臨時對象tmp := make([]byte, maxTempSize)for i := 0; i < len(data); i += chunkSize {chunk := data[i:min(i+chunkSize, len(data))]// 使用預分配的臨時緩沖區count := processInto(chunk, tmp)// 只分配實際需要的結果newResults := processResults(tmp[:count])results = append(results, newResults...)}return results
}

遷移過程中的思維慣性問題

C/C++的一些最佳實踐在Go中可能反而是反模式:

// C++思維:手動管理連接池
type ConnectionPool struct {connections []*Connectionmutex       sync.Mutex
}func (p *ConnectionPool) GetConnection() *Connection {p.mutex.Lock()defer p.mutex.Unlock()if len(p.connections) == 0 {return newConnection()}conn := p.connections[len(p.connections)-1]p.connections = p.connections[:len(p.connections)-1]return conn
}func (p *ConnectionPool) ReturnConnection(conn *Connection) {p.mutex.Lock()defer p.mutex.Unlock()p.connections = append(p.connections, conn)
}// Go思維:使用標準庫和上下文控制
import "database/sql"// 使用標準庫的連接池
db, err := sql.Open("postgres", connStr)
db.SetMaxOpenConns(100)
db.SetMaxIdleConns(25)
db.SetConnMaxLifetime(5 * time.Minute)// 使用上下文控制生命周期
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()rows, err := db.QueryContext(ctx, "SELECT * FROM users")

九、未來展望與結論

Go內存管理的發展趨勢

Go語言的內存管理正在不斷演進:

  1. GC改進:每個版本都在提高GC性能,減少停頓時間
  2. 編譯器優化:更智能的逃逸分析和內聯決策
  3. 泛型支持:Go 1.18+的泛型功能可能影響內存使用模式
  4. 更多運行時控制:未來可能提供更細粒度的GC控制

個人成長與技術選擇建議

  1. 接受不同的思維模式:不要試圖將C++的模式強加于Go
  2. 理解而非規避GC:了解GC工作原理,與之合作而非對抗
  3. 優先考慮可讀性:Go的哲學是簡潔明了,不要過度優化
  4. 衡量再優化:使用基準測試驗證優化的必要性和效果

總結關鍵思維轉變要點

  1. 自動內存管理不等于無需關注內存:理解GC的工作方式和限制
  2. 從手動控制到合理設計:設計合理的數據結構和算法更重要
  3. 從所有權模型到引用跟蹤:理解對象生命周期
  4. 從精細控制到適度放手:信任語言運行時,專注業務邏輯

從C/C++遷移到Go的過程中,內存管理思維的轉變可能是最大的挑戰,但也帶來了巨大的回報。通過擁抱Go的設計理念,我們可以編寫出更簡潔、更可靠、更易維護的代碼,同時保持接近C/C++的性能水平。

希望本文能幫助你平穩完成這一思維轉變,充分發揮Go語言的潛力!

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

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

相關文章

正確設置 FreeRTOS 與 STM32 的中斷優先級

在裸機開發&#xff08;非 RTOS&#xff09;時&#xff0c;大多數 STM32 外設的中斷優先級通常不需要手動配置&#xff0c;原因如下&#xff1a; ? 裸機開發中默認中斷優先級行為 特點說明默認中斷優先級為 0如果你不設置&#xff0c;STM32 HAL 默認設置所有外設中斷為 0&…

EasyExcel之SheetWriteHandler:解鎖Excel寫入的高階玩法

引言在 EasyExcel 強大的功能體系中&#xff0c;SheetWriteHandler 接口是一個關鍵的組成部分。它允許開發者在寫入 Excel 的 Sheet 時進行自定義處理&#xff0c;為實現各種復雜的業務需求提供了強大的支持。通過深入了解和運用 SheetWriteHandler 接口&#xff0c;我們能夠更…

Python單例模式魔法方法or屬性

1.單例模式概念定義:單例模式(Singleton Pattern)是一種創建型設計模式&#xff0c;它確保一個類只能有一個實例&#xff0c;并提供一個全局訪問點來獲取該實例。這種模式在需要控制資源訪問、配置管理或協調系統操作時特別有用。核心特點:私有構造函數&#xff1a;防止外部通過…

【Kubernetes系列】Kubernetes 資源請求(Requests)

博客目錄 引言一、資源請求的基本概念1.1 什么是資源請求1.2 請求與限制的區別 二、CPU 請求的深入解析2.1 CPU 請求的單位與含義2.2 CPU 請求的調度影響2.3 CPU 請求與限制的關系 三、內存請求的深入解析3.1 內存請求的單位與含義3.2 內存請求的調度影響3.3 內存請求的特殊性 …

大型語言模型中的自動化思維鏈提示

摘要 大型語言模型&#xff08;LLMs&#xff09;能夠通過生成中間推理步驟來執行復雜的推理任務。為提示演示提供這些步驟的過程被稱為思維鏈&#xff08;CoT&#xff09;提示。CoT提示有兩種主要范式。一種使用簡單的提示語&#xff0c;如“讓我們一步一步思考”&#xff0c;…

Private Set Generation with Discriminative Information(2211.04446v1)

1. 遇到什么問題&#xff0c;解決了什么遇到的問題現有差分隱私生成模型受限于高維數據分布建模的復雜性&#xff0c;合成樣本實用性不足。深度生成模型訓練依賴大量數據&#xff0c;加入隱私約束后更難優化&#xff0c;且不保證下游任務&#xff08;如分類&#xff09;的最優解…

C++編程語言入門指南

一、C語言概述 C是由丹麥計算機科學家Bjarne Stroustrup于1979年在貝爾實驗室開發的一種靜態類型、編譯式、通用型編程語言。最初被稱為"C with Classes"(帶類的C)&#xff0c;1983年更名為C。它既具有高級語言的抽象特性&#xff0c;又保留了底層硬件操作能力&…

ZED相機與Foxglove集成:加速機器人視覺調試效率的實用方案

隨著機器人技術的發展&#xff0c;實時視覺數據流的高效傳輸和可視化成為提升系統性能的重要因素。通過ZED相機&#xff08;包括ZED 2i和ZED X&#xff09;與Foxglove Studio平臺的結合&#xff0c;開發者能夠輕松訪問高質量的2D圖像、深度圖和點云數據&#xff0c;從而顯著提高…

目標檢測新紀元:DETR到Mamba實戰解析

&#x1f680;【實戰分享】目標檢測的“后 DE?”時代&#xff1a;DETR/DINO/RT-DETR及新型骨干網絡探索&#xff08;含示例代碼&#xff09; 目標檢測從 YOLO、Faster R-CNN 到 Transformer 結構的 DETR&#xff0c;再到 DINO、RT-DETR&#xff0c;近兩年出現了許多新趨勢&am…

【IOS】XCode創建firstapp并運行(成為IOS開發者)

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 這篇文章主要介紹XCode創建firstapp并運行 學其所用&#xff0c;用其所學。——梁啟超 歡迎來到我的博客&#xff0c;一起學習&#xff0c;共同進步。 喜歡的朋友可以關注一下&#xff0c;下次更新不迷路…

class類和style內聯樣式的綁定 + 事件處理 + uniapp創建自定義頁面模板

目錄 一.class類的綁定 1.靜態編寫 2.動態編寫 二.style內聯樣式的綁定 三.事件處理 1.案例1 2.案例2 四.uniapp創建自定義頁面模板 1.為什么要這么做&#xff1f; 2.步驟 ①打開新建頁面的界面 ②在彈出的目錄下&#xff0c;新建模板文件 ③用HBuilderX打開該模板…

android 卡頓和丟幀區別

Android 卡頓&#xff08;Jank&#xff09;與丟幀&#xff08;Frame Drop&#xff09;的核心區別在于問題本質與用戶感知&#xff0c;以下是分層解析&#xff1a; ? 一、本質差異 維度卡頓&#xff08;Jank&#xff09;丟幀&#xff08;Frame Drop&#xff09;定義用戶可感知…

【python實用小腳本-125】基于 Python 的 Gmail 郵件發送工具:實現高效郵件自動化

引言 在現代辦公和開發環境中&#xff0c;郵件通信是一種重要的溝通方式。自動化發送郵件可以大大提高工作效率&#xff0c;例如發送通知、報告或文件。本文將介紹一個基于 Python 的 Gmail 郵件發送工具&#xff0c;它能夠通過 Gmail 的 SMTP 服務器發送郵件&#xff0c;并支持…

gateway斷言配置詳解

一、Predicate - 斷? 1、簡單用法 spring:cloud:gateway:routes:- id: after_routeuri: https://example.orgpredicates:- After2017-01-20T17:42:47.789-07:00[America/Denver] 2、自定義斷言 新建類VipRoutePredicateFactory&#xff0c;注意VipRoutePredicateFactory名字…

基于大模型的尿毒癥全流程預測與診療方案研究報告

目錄 一、引言 1.1 研究背景與意義 1.2 研究目的與方法 1.3 國內外研究現狀 二、尿毒癥相關理論基礎 2.1 尿毒癥的定義、病因與發病機制 2.2 尿毒癥的癥狀與診斷標準 2.3 尿毒癥的治療方法概述 三、大模型技術原理與應用 3.1 大模型的基本概念與發展歷程 3.2 大模型…

裸金屬服務器租用平臺-青蛙云

企業對服務器性能與靈活性的要求與日俱增。青蛙云M-啟強裸金屬服務器租用平臺應運而生&#xff0c;為企業提供了一種兼具物理機性能和云計算彈性的解決方案。裸金屬服務器租用平臺的優勢?(一)高配性能&#xff0c;無虛擬化開銷?裸金屬服務器直接運行在物理硬件之上&#xff0…

[Terence Tao訪談] AlphaProof系統 | AI嗅覺 | 研究生學習 | 龐加萊猜想(高維) | 復雜問題簡單化

玩這些有趣的東西。通常情況下什么也得不到&#xff0c;你必須學會說&#xff1a;“好吧&#xff0c;再試一次&#xff0c;什么都沒發生&#xff0c;我會繼續前進。” DeepMind的AlphaProof系統 Q&#xff1a;DeepMind的AlphaProof系統是通過強化學習訓練的&#xff0c;使用的…

Aseprite工具入門教程4之動畫導入Unity

1、時間軸功能 &#xff08;1&#xff09;眼睛圖標 顯示/隱藏圖層圖層隱藏時無法繪制 &#xff08;2&#xff09;鎖定圖標 鎖定后無法移動或編輯圖層防止意外在錯誤圖層上繪制 &#xff08;3&#xff09;單元格圖標 兩個點代表幀分開&#xff0c;一個橢圓代表幀統一。分開就…

移動硬盤頻繁提示格式化?解決異常故障的正確方法

移動硬盤作為數據存儲的重要工具&#xff0c;不少人都習慣將照片、文檔、項目資料甚至整臺電腦的備份都放在里面。但有時&#xff0c;一件令人頭疼的事悄然發生&#xff1a; 插上硬盤&#xff0c;系統卻突然提示&#xff1a;“使用驅動器中的光盤之前需要將其格式化。是否要將…

Java泛型筆記

1 為什么需要泛型 Java5之前&#xff0c;是沒有泛型的。通過兩段代碼我們就可以知道為何我們需要泛型 public int addInt(int a, int b) {return a b; }public double addDouble(double a, double b) {return a b; } 實際開發中&#xff0c;經常有數值類型求和的需求&…