開源C++版AI畫圖大模型框架stable-diffusion.cpp開發使用初體驗

stable-diffusion.cpp是一個C++編寫的輕量級開源類AIGC大模型框架,可以支持在消費級普通設備上本地部署運行大模型進行AI畫圖,以及作為依賴庫集成的到應用程序中提供類似于網頁版stable-diffusion的功能。

以下基于stable-diffusion.cpp的源碼利用C++ api來開發實例demo演示加載本地模型文件輸入提示詞生成畫圖,這里采用顯卡CUDA加速計算,如果沒有顯卡也可以直接使用CPU。

項目結構

stable_diffusion_cpp_starter- stable-diffusion.cpp- src|- main.cpp- CMakeLists.txt

有兩個前置操作:

  • 在系統安裝好CUDA Toolkit
  • 將stable-diffusion.cpp源碼根目錄的CMakeLists.txt里面SD_CUBLAS選項打開設為ON

不過,如果沒有支持CUDA的顯卡,默認采用CPU計算,則可以忽略以上兩項

CMakeLists.txt

cmake_minimum_required(VERSION 3.15)project(stable_diffusion_cpp_starter)set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED ON)add_subdirectory(stable-diffusion.cpp)include_directories(${CMAKE_CURRENT_SOURCE_DIR}/stable-diffusion.cpp${CMAKE_CURRENT_SOURCE_DIR}/stable-diffusion.cpp/thirdparty
)file(GLOB SRCsrc/*.hsrc/*.cpp
)add_executable(${PROJECT_NAME} ${SRC})target_link_libraries(${PROJECT_NAME} stable-diffusion ${CMAKE_THREAD_LIBS_INIT} # means pthread on unix
)

main.cpp

#include <stdio.h>
#include <string.h>
#include <time.h>
#include <iostream>
#include <random>
#include <string>
#include <vector>#include "stable-diffusion.h"#define STB_IMAGE_IMPLEMENTATION
#define STB_IMAGE_STATIC
#include "stb_image.h"#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_WRITE_STATIC
#include "stb_image_write.h"#define STB_IMAGE_RESIZE_IMPLEMENTATION
#define STB_IMAGE_RESIZE_STATIC
#include "stb_image_resize.h"const char* rng_type_to_str[] = {"std_default","cuda",
};// Names of the sampler method, same order as enum sample_method in stable-diffusion.h
const char* sample_method_str[] = {"euler_a","euler","heun","dpm2","dpm++2s_a","dpm++2m","dpm++2mv2","lcm",
};// Names of the sigma schedule overrides, same order as sample_schedule in stable-diffusion.h
const char* schedule_str[] = {"default","discrete","karras","ays",
};const char* modes_str[] = {"txt2img","img2img","img2vid","convert",
};enum SDMode 
{TXT2IMG,IMG2IMG,IMG2VID,CONVERT,MODE_COUNT
};struct SDParams 
{int n_threads = -1;SDMode mode   = TXT2IMG;std::string model_path;std::string vae_path;std::string taesd_path;std::string esrgan_path;std::string controlnet_path;std::string embeddings_path;std::string stacked_id_embeddings_path;std::string input_id_images_path;sd_type_t wtype = SD_TYPE_COUNT;std::string lora_model_dir;std::string output_path = "output.png";std::string input_path;std::string control_image_path;std::string prompt;std::string negative_prompt;float min_cfg     = 1.0f;float cfg_scale   = 7.0f;float style_ratio = 20.f;int clip_skip     = -1;  // <= 0 represents unspecifiedint width         = 512;int height        = 512;int batch_count   = 1;int video_frames         = 6;int motion_bucket_id     = 127;int fps                  = 6;float augmentation_level = 0.f;sample_method_t sample_method = EULER_A;schedule_t schedule           = DEFAULT;int sample_steps              = 20;float strength                = 0.75f;float control_strength        = 0.9f;rng_type_t rng_type           = CUDA_RNG;int64_t seed                  = 42;bool verbose                  = false;bool vae_tiling               = false;bool control_net_cpu          = false;bool normalize_input          = false;bool clip_on_cpu              = false;bool vae_on_cpu               = false;bool canny_preprocess         = false;bool color                    = false;int upscale_repeats           = 1;
};static std::string sd_basename(const std::string& path) 
{size_t pos = path.find_last_of('/');if (pos != std::string::npos) {return path.substr(pos + 1);}pos = path.find_last_of('\\');if (pos != std::string::npos) {return path.substr(pos + 1);}return path;
}std::string get_image_params(SDParams params, int64_t seed) 
{std::string parameter_string = params.prompt + "\n";if (params.negative_prompt.size() != 0) {parameter_string += "Negative prompt: " + params.negative_prompt + "\n";}parameter_string += "Steps: " + std::to_string(params.sample_steps) + ", ";parameter_string += "CFG scale: " + std::to_string(params.cfg_scale) + ", ";parameter_string += "Seed: " + std::to_string(seed) + ", ";parameter_string += "Size: " + std::to_string(params.width) + "x" + std::to_string(params.height) + ", ";parameter_string += "Model: " + sd_basename(params.model_path) + ", ";parameter_string += "RNG: " + std::string(rng_type_to_str[params.rng_type]) + ", ";parameter_string += "Sampler: " + std::string(sample_method_str[params.sample_method]);if (params.schedule == KARRAS) {parameter_string += " karras";}parameter_string += ", ";parameter_string += "Version: stable-diffusion.cpp";return parameter_string;
}/* Enables Printing the log level tag in color using ANSI escape codes */
void sd_log_cb(enum sd_log_level_t level, const char* log, void* data) 
{SDParams* params = (SDParams*)data;int tag_color;const char* level_str;FILE* out_stream = (level == SD_LOG_ERROR) ? stderr : stdout;if (!log || (!params->verbose && level <= SD_LOG_DEBUG)) return;switch (level) {case SD_LOG_DEBUG:tag_color = 37;level_str = "DEBUG";break;case SD_LOG_INFO:tag_color = 34;level_str = "INFO";break;case SD_LOG_WARN:tag_color = 35;level_str = "WARN";break;case SD_LOG_ERROR:tag_color = 31;level_str = "ERROR";break;default: /* Potential future-proofing */tag_color = 33;level_str = "?????";break;}if (params->color == true) fprintf(out_stream, "\033[%d;1m[%-5s]\033[0m ", tag_color, level_str);else fprintf(out_stream, "[%-5s] ", level_str);fputs(log, out_stream);fflush(out_stream);
}int main(int argc, const char* argv[]) 
{// set sd paramsconst std::string model_path = "./v1-5-pruned-emaonly.ckpt";const std::string img_output_path = "./gen_img.png";const std::string prompt = "a cute little dog with flowers";SDParams params;params.model_path = model_path;params.output_path = img_output_path;params.prompt = prompt;sd_set_log_callback(sd_log_cb, (void*)&params);if (params.mode == CONVERT) {bool success = convert(params.model_path.c_str(), params.vae_path.c_str(), params.output_path.c_str(), params.wtype);if (!success) {fprintf(stderr,"convert '%s'/'%s' to '%s' failed\n",params.model_path.c_str(),params.vae_path.c_str(),params.output_path.c_str());return 1;} else {printf("convert '%s'/'%s' to '%s' success\n",params.model_path.c_str(),params.vae_path.c_str(),params.output_path.c_str());return 0;}}if (params.mode == IMG2VID) {fprintf(stderr, "SVD support is broken, do not use it!!!\n");return 1;}// prepare image bufferbool vae_decode_only          = true;uint8_t* input_image_buffer   = NULL;uint8_t* control_image_buffer = NULL;if (params.mode == IMG2IMG || params.mode == IMG2VID) {vae_decode_only = false;int c              = 0;int width          = 0;int height         = 0;input_image_buffer = stbi_load(params.input_path.c_str(), &width, &height, &c, 3);if (input_image_buffer == NULL) {fprintf(stderr, "load image from '%s' failed\n", params.input_path.c_str());return 1;}if (c < 3) {fprintf(stderr, "the number of channels for the input image must be >= 3, but got %d channels\n", c);free(input_image_buffer);return 1;}if (width <= 0) {fprintf(stderr, "error: the width of image must be greater than 0\n");free(input_image_buffer);return 1;}if (height <= 0) {fprintf(stderr, "error: the height of image must be greater than 0\n");free(input_image_buffer);return 1;}// Resize input image ...if (params.height != height || params.width != width) {printf("resize input image from %dx%d to %dx%d\n", width, height, params.width, params.height);int resized_height = params.height;int resized_width  = params.width;uint8_t* resized_image_buffer = (uint8_t*)malloc(resized_height * resized_width * 3);if (resized_image_buffer == NULL) {fprintf(stderr, "error: allocate memory for resize input image\n");free(input_image_buffer);return 1;}stbir_resize(input_image_buffer, width, height, 0,resized_image_buffer, resized_width, resized_height, 0, STBIR_TYPE_UINT8,3 /*RGB channel*/, STBIR_ALPHA_CHANNEL_NONE, 0,STBIR_EDGE_CLAMP, STBIR_EDGE_CLAMP,STBIR_FILTER_BOX, STBIR_FILTER_BOX,STBIR_COLORSPACE_SRGB, nullptr);// Save resized resultfree(input_image_buffer);input_image_buffer = resized_image_buffer;}}// init sd contextsd_ctx_t* sd_ctx = new_sd_ctx(params.model_path.c_str(),params.vae_path.c_str(),params.taesd_path.c_str(),params.controlnet_path.c_str(),params.lora_model_dir.c_str(),params.embeddings_path.c_str(),params.stacked_id_embeddings_path.c_str(),vae_decode_only,params.vae_tiling,true,params.n_threads,params.wtype,params.rng_type,params.schedule,params.clip_on_cpu,params.control_net_cpu,params.vae_on_cpu);if (sd_ctx == NULL) {printf("new_sd_ctx_t failed\n");return 1;}sd_image_t* control_image = NULL;if (params.controlnet_path.size() > 0 && params.control_image_path.size() > 0) {int c                = 0;control_image_buffer = stbi_load(params.control_image_path.c_str(), &params.width, &params.height, &c, 3);if (control_image_buffer == NULL) {fprintf(stderr, "load image from '%s' failed\n", params.control_image_path.c_str());return 1;}control_image = new sd_image_t{(uint32_t)params.width,(uint32_t)params.height,3,control_image_buffer};if (params.canny_preprocess) {  // apply preprocessorcontrol_image->data = preprocess_canny(control_image->data,control_image->width,control_image->height,0.08f,0.08f,0.8f,1.0f,false);}}// generate imagesd_image_t* results;if (params.mode == TXT2IMG) {results = txt2img(sd_ctx,params.prompt.c_str(),params.negative_prompt.c_str(),params.clip_skip,params.cfg_scale,params.width,params.height,params.sample_method,params.sample_steps,params.seed,params.batch_count,control_image,params.control_strength,params.style_ratio,params.normalize_input,params.input_id_images_path.c_str());} else {sd_image_t input_image = {(uint32_t)params.width,(uint32_t)params.height,3,input_image_buffer};if (params.mode == IMG2VID) {results = img2vid(sd_ctx,input_image,params.width,params.height,params.video_frames,params.motion_bucket_id,params.fps,params.augmentation_level,params.min_cfg,params.cfg_scale,params.sample_method,params.sample_steps,params.strength,params.seed);if (results == NULL) {printf("generate failed\n");free_sd_ctx(sd_ctx);return 1;}size_t last            = params.output_path.find_last_of(".");std::string dummy_name = last != std::string::npos ? params.output_path.substr(0, last) : params.output_path;for (int i = 0; i < params.video_frames; i++) {if (results[i].data == NULL) continue;std::string final_image_path = i > 0 ? dummy_name + "_" + std::to_string(i + 1) + ".png" : dummy_name + ".png";stbi_write_png(final_image_path.c_str(), results[i].width, results[i].height, results[i].channel,results[i].data, 0, get_image_params(params, params.seed + i).c_str());printf("save result image to '%s'\n", final_image_path.c_str());free(results[i].data);results[i].data = NULL;}free(results);free_sd_ctx(sd_ctx);return 0;} else {results = img2img(sd_ctx,input_image,params.prompt.c_str(),params.negative_prompt.c_str(),params.clip_skip,params.cfg_scale,params.width,params.height,params.sample_method,params.sample_steps,params.strength,params.seed,params.batch_count,control_image,params.control_strength,params.style_ratio,params.normalize_input,params.input_id_images_path.c_str());}}if (results == NULL) {printf("generate failed\n");free_sd_ctx(sd_ctx);return 1;}int upscale_factor = 4;  // unused for RealESRGAN_x4plus_anime_6B.pthif (params.esrgan_path.size() > 0 && params.upscale_repeats > 0) {upscaler_ctx_t* upscaler_ctx = new_upscaler_ctx(params.esrgan_path.c_str(),params.n_threads,params.wtype);if (upscaler_ctx == NULL) printf("new_upscaler_ctx failed\n");else {for (int i = 0; i < params.batch_count; i++) {if (results[i].data == NULL) {continue;}sd_image_t current_image = results[i];for (int u = 0; u < params.upscale_repeats; ++u) {sd_image_t upscaled_image = upscale(upscaler_ctx, current_image, upscale_factor);if (upscaled_image.data == NULL) {printf("upscale failed\n");break;}free(current_image.data);current_image = upscaled_image;}results[i] = current_image;  // Set the final upscaled image as the result}}}size_t last            = params.output_path.find_last_of(".");std::string dummy_name = last != std::string::npos ? params.output_path.substr(0, last) : params.output_path;for (int i = 0; i < params.batch_count; i++) {if (results[i].data == NULL) continue;std::string final_image_path = i > 0 ? dummy_name + "_" + std::to_string(i + 1) + ".png" : dummy_name + ".png";stbi_write_png(final_image_path.c_str(), results[i].width, results[i].height, results[i].channel,results[i].data, 0, get_image_params(params, params.seed + i).c_str());printf("save result image to '%s'\n", final_image_path.c_str());free(results[i].data);results[i].data = NULL;}free(results);free_sd_ctx(sd_ctx);free(control_image_buffer);free(input_image_buffer);return 0;
}

運行結果

ggml_cuda_init: GGML_CUDA_FORCE_MMQ:   no
ggml_cuda_init: CUDA_USE_TENSOR_CORES: yes
ggml_cuda_init: found 1 CUDA devices:Device 0: NVIDIA GeForce GTX 1060 with Max-Q Design, compute capability 6.1, VMM: yes
[INFO ] stable-diffusion.cpp:169  - loading model from './v1-5-pruned-emaonly.ckpt'
[INFO ] model.cpp:736  - load ./v1-5-pruned-emaonly.ckpt using checkpoint format
[INFO ] stable-diffusion.cpp:192  - Stable Diffusion 1.x
[INFO ] stable-diffusion.cpp:198  - Stable Diffusion weight type: f32
[INFO ] stable-diffusion.cpp:419  - total params memory size = 2719.24MB (VRAM 2719.24MB, RAM 0.00MB): clip 469.44MB(VRAM), unet 2155.33MB(VRAM), vae 94.47MB(VRAM), controlnet 0.00MB(VRAM), pmid 0.00MB(VRAM)
[INFO ] stable-diffusion.cpp:423  - loading model from './v1-5-pruned-emaonly.ckpt' completed, taking 18.72s
[INFO ] stable-diffusion.cpp:440  - running in eps-prediction mode
[INFO ] stable-diffusion.cpp:556  - Attempting to apply 0 LoRAs
[INFO ] stable-diffusion.cpp:1203 - apply_loras completed, taking 0.00s
ggml_gallocr_reserve_n: reallocating CUDA0 buffer from size 0.00 MiB to 1.40 MiB
ggml_gallocr_reserve_n: reallocating CUDA0 buffer from size 0.00 MiB to 1.40 MiB
[INFO ] stable-diffusion.cpp:1316 - get_learned_condition completed, taking 514 ms
[INFO ] stable-diffusion.cpp:1334 - sampling using Euler A method
[INFO ] stable-diffusion.cpp:1338 - generating image: 1/1 - seed 42
ggml_gallocr_reserve_n: reallocating CUDA0 buffer from size 0.00 MiB to 559.90 MiB|==================================================| 20/20 - 1.40s/it
[INFO ] stable-diffusion.cpp:1381 - sampling completed, taking 35.05s
[INFO ] stable-diffusion.cpp:1389 - generating 1 latent images completed, taking 35.07s
[INFO ] stable-diffusion.cpp:1392 - decoding 1 latents
ggml_gallocr_reserve_n: reallocating CUDA0 buffer from size 0.00 MiB to 1664.00 MiB
[INFO ] stable-diffusion.cpp:1402 - latent 1 decoded, taking 3.03s
[INFO ] stable-diffusion.cpp:1406 - decode_first_stage completed, taking 3.03s
[INFO ] stable-diffusion.cpp:1490 - txt2img completed in 38.64s
save result image to './gen_img.png'

注:

  • stable_diffusion支持的模型文件需要自己去下載,推薦到huggingface官網下載ckpt格式文件
  • 提示詞要使用英文
  • 支持文字生成圖和以圖輔助生成圖,參數很多,可以多嘗試

源碼

stable_diffusion_cpp_starter

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

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

相關文章

人工智能的未來:暢想智能新時代

人工智能正在改變我們的世界&#xff0c;它將帶我們走向何方&#xff1f; 著名神經科學家、Numenta 公司創始人杰夫?霍金斯 Jeff Hawkins 在其著作《人工智能的未來》中&#xff0c;描繪了一幅人工智能發展的光明圖景。他認為&#xff0c;人工智能將超越人類智能&#xff0c;…

理解Gobrs-Async相對于CompletableFuture的優勢

Gobrs-Async框架針對復雜應用場景下的異步任務編排&#xff0c;提供了一些傳統Future或CompletableFuture所不具備的特性和能力&#xff0c;以下是它能夠解決的問題和相對于CompletableFuture的優勢&#xff1a; 1. **全鏈路異常回調**&#xff1a; - Gobrs-Async允許為任務…

關于地圖點擊的操作

_this.map.dragging.disable(); //地圖拖拽 _this.map.doubleClickZoom.disable(); //禁止雙擊放大地圖 _this.map.scrollWheelZoom.disable(); //禁止鼠標滑輪滾動放大縮小地圖 _this.map.dragging.enable(); //e…

備份和鏡像TrinityCore

相比重新安裝&#xff0c;省去了編譯的過程&#xff0c;同時還能保留以前的人物、裝備等。 注意&#xff0c;若不想重新編譯安裝&#xff0c;則需要創建一樣的目錄、賬戶等&#xff0c;以減少不必要的麻煩。 首先備份數據: mysql備份和導入方法見&#xff1a;使用dump備份my…

視覺與味蕾的交響:紅酒與藝術的無界狂歡,震撼你的感官世界

在浩瀚的藝術海洋中&#xff0c;紅酒以其不同的魅力&#xff0c;成為了一種跨界整合的媒介。當雷盛紅酒與藝術相遇&#xff0c;它們共同呈現出一場特別的視覺盛宴&#xff0c;讓人沉醉在色彩與光影的交織中&#xff0c;感受紅酒與藝術的無界碰撞。 雷盛紅酒&#xff0c;宛如一件…

AI作畫Prompt不會寫?Amazon Bedrock Claude3.5來幫忙

最新上線的Claude3.5 Sonnet按照官方介紹的數據來看&#xff0c;在多方面超越了CPT-4o&#xff0c;是迄今為止最智能的模型。 而跟上一個版本相比&#xff0c;速度是Claude 3 Opus的兩倍&#xff0c;成本只有其五分之一。 Claude3.5 Sonnet不僅擅長解釋圖表、圖形或者從不完…

vue3中子組件調用父組件事件

在 Vue 3 中&#xff0c;子組件調用父組件的事件&#xff08;或方法&#xff09;的方式與 Vue 2 類似&#xff0c;但 Vue 3 引入了 Composition API&#xff0c;這可能會改變你組織代碼的方式。不過&#xff0c;基本的通信機制——通過自定義事件 ($emit) 通知父組件——仍然保…

總結:DataX

一、介紹 本文主要介紹DataX的安裝與使用。 二、安裝 安裝&#xff1a;DataX/userGuid.md at master alibaba/DataX GitHub 六、案例 實現從MySQL同步數據到HDFS&#xff0c;然后使用Hive進行聚合計算并將結果存儲回MySQL。 步驟2&#xff1a;使用DataX同步MySQL數據到H…

Day28:回溯法 491.遞增子序列 46.全排列 47.全排列 II 332.重新安排行程 51. N皇后 37. 解數獨

491. 非遞減子序列 給你一個整數數組 nums &#xff0c;找出并返回所有該數組中不同的遞增子序列&#xff0c;遞增子序列中 至少有兩個元素 。你可以按 任意順序 返回答案。 數組中可能含有重復元素&#xff0c;如出現兩個整數相等&#xff0c;也可以視作遞增序列的一種特殊情…

Atcoder ABC359E Water Tank 題解

題目傳送門 題解 分析 分類討論。 記第 i i i 個答案為 a n s i 1 ans_i1 ansi?1。 第 i i i 個數就是目前的最大值。 顯然&#xff0c; a n s i h i i ans_ih_i \times i ansi?hi?i。第 i i i 個數就是目前的最大值。 記 l a s t i last_i lasti? 為 i i i …

網絡安全學習路線圖(2024版詳解)

近期&#xff0c;大家在網上對于網絡安全討論比較多&#xff0c;想要學習的人也不少&#xff0c;但是需要學習哪些內容&#xff0c;按照什么順序去學習呢&#xff1f;其實我們已經出國多版本的網絡安全學習路線圖&#xff0c;一直以來效果也比較不錯&#xff0c;本次我們針對市…

Java中多態的實現原理解析

Java中多態的實現原理解析 大家好&#xff0c;我是免費搭建查券返利機器人省錢賺傭金就用微賺淘客系統3.0的小編&#xff0c;也是冬天不穿秋褲&#xff0c;天冷也要風度的程序猿&#xff01;在本文中&#xff0c;我們將深入探討Java中多態的實現原理及其應用。多態是面向對象編…

centos中查看服務的日志

在CentOS中查看服務的日志通常涉及查看系統日志文件&#xff0c;這些文件通常位于/var/log/目錄下。不同的服務可能會有不同的日志文件。以下是一些常見的日志文件和查看它們的方法&#xff1a; 1. **系統日志**&#xff1a;系統日志通常存儲在/var/log/messages或/var/log/sy…

學會python——生成日志信息(python實例十二)

目錄 1、認識Python 2、環境與工具 2.1 python環境 2.2 Visual Studio Code編譯 3、生成日志信息 3.1 代碼構思 3.2 代碼示例 3.3 運行結果 4、總結 1、認識Python Python 是一個高層次的結合了解釋性、編譯性、互動性和面向對象的腳本語言。 Python 的設計具有很強的…

MySQL serverTimezone=UTC

在數據庫連接字符串中使用 serverTimezoneUTC 是一個常見的配置選項&#xff0c;特別是當數據庫服務器和應用程序服務器位于不同的時區時。這個選項指定了數據庫服務器應當使用的時區&#xff0c;以確保日期和時間數據在客戶端和服務器之間正確傳輸和處理。 UTC&#xff08;協…

Vue-雙向數據綁定指令

v-model指令 雙向數據綁定就是當數據設置給表單元素時&#xff0c;修改這個數據會修改表單元素的值&#xff0c; 修改表單元素的值同樣也會修改這個數據 <body><div id"app"><input type"text" v-model"name"><p>{{name…

利用 Swifter 加速 Pandas 操作的詳細教程

利用 Swifter 加速 Pandas 操作的詳細教程 引言 Pandas 是數據分析中常用的庫&#xff0c;但在處理大型數據集時效率可能會較低。Swifter 提供了一種簡便的方法&#xff0c;通過并行處理來顯著加速 Pandas 操作。 Swifter 簡介 Swifter 是一個開源庫&#xff0c;旨在自動優…

一個項目學習Vue3---創建一個 Vue 應用

步驟1&#xff1a;安裝符合要求的node版本 目前官網要求使用的node.js版本為18.3及其以上 所以我們要安裝node.js 18.3及其以上版本 NVM安裝教程&#xff1a;一個項目學習Vue3---NVM和NPM安裝-CSDN博客 若不想安裝NVM&#xff0c;可以直接下載適合自己的node版本Node.js — …

Go 延遲調用 defer

&#x1f49d;&#x1f49d;&#x1f49d;歡迎蒞臨我的博客&#xff0c;很高興能夠在這里和您見面&#xff01;希望您在這里可以感受到一份輕松愉快的氛圍&#xff0c;不僅可以獲得有趣的內容和知識&#xff0c;也可以暢所欲言、分享您的想法和見解。 推薦:「stormsha的主頁」…

硬件實用技巧:電阻精度和常用阻值表

若該文為原創文章&#xff0c;轉載請注明原文出處 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/139986658 長沙紅胖子Qt&#xff08;長沙創微智科&#xff09;博文大全&#xff1a;開發技術集合&#xff08;包含Qt實用技術、樹莓派、三維、OpenCV…