C++智能指針之shared_ptr的具體使用
std::shared_ptr概念
unique_ptr因?yàn)槠渚窒扌?獨(dú)享所有權(quán)),一般很少用于多線程操作。在多線程操作的時(shí)候,既可以共享資源,又可以自動(dòng)釋放資源,這就引入了shared_ptr。
shared_ptr為了支持跨線程訪問(wèn),其內(nèi)部有一個(gè)引用計(jì)數(shù)(線程安全),用來(lái)記錄當(dāng)前使用該資源的shared_ptr個(gè)數(shù),在結(jié)束使用的時(shí)候,引用計(jì)數(shù)為-1,當(dāng)引用計(jì)數(shù)為0時(shí),會(huì)自動(dòng)釋放其關(guān)聯(lián)的資源。
特點(diǎn) 相對(duì)于unique_ptr的獨(dú)享所有權(quán),shared_ptr可以共享所有權(quán)。其內(nèi)部有一個(gè)引用計(jì)數(shù),用來(lái)記錄共享該資源的shared_ptr個(gè)數(shù),當(dāng)共享數(shù)為0的時(shí)候,會(huì)自動(dòng)釋放其關(guān)聯(lián)的資源。
對(duì)比unique_ptr,shared_ptr不支持?jǐn)?shù)組,所以,如果用shared_ptr指向一個(gè)數(shù)組的話,需要自己手動(dòng)實(shí)現(xiàn)deleter,如下所示:
std::shared_ptr<int> p(new int[8], [](int *ptr){delete []ptr;});
shared_ptr模板類
template<class T> class shared_ptr {
public:
using element_type = remove_extent_t<T>;
using weak_type = weak_ptr<T>;
// 構(gòu)造函數(shù)
constexpr shared_ptr() noexcept;
constexpr shared_ptr(nullptr_t) noexcept : shared_ptr() { }
template<class Y> explicit shared_ptr(Y* p);
template<class Y, class D> shared_ptr(Y* p, D d);
template<class Y, class D, class A> shared_ptr(Y* p, D d, A a);
template<class D> shared_ptr(nullptr_t p, D d);
template<class D, class A> shared_ptr(nullptr_t p, D d, A a);
template<class Y>
shared_ptr(const shared_ptr<Y>& r, element_type* p) noexcept;
template<class Y>
shared_ptr(shared_ptr<Y>&& r, element_type* p) noexcept;
shared_ptr(const shared_ptr& r) noexcept;
template<class Y> shared_ptr(const shared_ptr<Y>& r) noexcept;
shared_ptr(shared_ptr&& r) noexcept;
template<class Y> shared_ptr(shared_ptr<Y>&& r) noexcept;
template<class Y> explicit shared_ptr(const weak_ptr<Y>& r);
template<class Y, class D> shared_ptr(unique_ptr<Y, D>&& r);
// 析構(gòu)函數(shù)
~shared_ptr();
// 賦值
shared_ptr& operator=(const shared_ptr& r) noexcept;
template<class Y>
shared_ptr& operator=(const shared_ptr<Y>& r) noexcept;
shared_ptr& operator=(shared_ptr&& r) noexcept;
template<class Y>
shared_ptr& operator=(shared_ptr<Y>&& r) noexcept;
template<class Y, class D>
shared_ptr& operator=(unique_ptr<Y, D>&& r);
// 修改函數(shù)
void swap(shared_ptr& r) noexcept;
void reset() noexcept;
template<class Y> void reset(Y* p);
template<class Y, class D> void reset(Y* p, D d);
template<class Y, class D, class A> void reset(Y* p, D d, A a);
// 探察函數(shù)
element_type* get() const noexcept;
T& operator*() const noexcept;
T* operator->() const noexcept;
element_type& operator[](ptrdiff_t i) const;
long use_count() const noexcept;
explicit operator bool() const noexcept;
template<class U>
bool owner_before(const shared_ptr<U>& b) const noexcept;
template<class U>
bool owner_before(const weak_ptr<U>& b) const noexcept;
};
shared_ptr多個(gè)指針指向相同的對(duì)象。shared_ptr使用引用計(jì)數(shù),每一個(gè)shared_ptr的拷貝都指向相同的內(nèi)存。每使用他一次,內(nèi)部的引用計(jì)數(shù)加1,每析構(gòu)一次,內(nèi)部的引用計(jì)數(shù)減1,減為0時(shí),自動(dòng)刪除所指向的堆內(nèi)存。shared_ptr內(nèi)部的引用計(jì)數(shù)是線程安全的,但是對(duì)象的讀取需要加鎖。
- 初始化。智能指針是個(gè)模板類,可以指定類型,傳入指針通過(guò)構(gòu)造函數(shù)初始化。也可以使用make_shared函數(shù)初始化。不能將指針直接賦值給一個(gè)智能指針,一個(gè)是類,一個(gè)是指針。例如
std::shared_ptr<int> p4 = new int(1);的寫法是錯(cuò)誤的,是不能隱式轉(zhuǎn)換。 - 拷貝和賦值??截愂沟脤?duì)象的引用計(jì)數(shù)增加1,賦值使得原對(duì)象引用計(jì)數(shù)減1,當(dāng)計(jì)數(shù)為0時(shí),自動(dòng)釋放內(nèi)存。后來(lái)指向的對(duì)象引用計(jì)數(shù)加1,指向后來(lái)的對(duì)象。
- get函數(shù)獲取原始指針。
- 注意不要用一個(gè)原始指針初始化多個(gè)shared_ptr,否則會(huì)造成二次釋放同一內(nèi)存
- 注意避免循環(huán)引用,shared_ptr的一個(gè)最大的陷阱是循環(huán)引用,循環(huán),循環(huán)引用會(huì)導(dǎo)致堆內(nèi)存無(wú)法正確釋放,導(dǎo)致內(nèi)存泄漏。循環(huán)引用我們?cè)诤竺娴膚eak_ptr中介紹。
所有智能指針類都有一個(gè)explicit構(gòu)造函數(shù),該構(gòu)造函數(shù)將指針作為參數(shù)。因此不需要自動(dòng)將指針轉(zhuǎn)換為智能指針對(duì)象:
std::shared_ptr<int> pi; int* p_reg = new int; //pi = p_reg; // not allowed(implicit conversion) pi = std::shared_ptr<int>(p_reg); // allowed(explicit conversion) //std::shared_ptr<int> pshared = p_reg; // not allowed(implicit conversion) //std::shared_ptr<int> pshared(g_reg); // allowed(explicit conversion)
下面我們看一個(gè)簡(jiǎn)單的例子:
#include <iostream>
#include <memory>
using namespace std;
int main()
{
std::shared_ptr<int> sp = std::make_shared<int>(10);
cout << sp.use_count() << endl;//1
std::shared_ptr<int> sp1(sp);//再次被引用則計(jì)數(shù)+1
cout << sp1.use_count() << endl;//2
}
從上面可以看到,多次被引用則會(huì)增加計(jì)數(shù),我們可以通過(guò)使用use_count方法打印具體的計(jì)數(shù)。
shared_ptr的構(gòu)造和析構(gòu)
#include <iostream>
#include <memory>
struct C {int* data;};
int main () {
auto deleter = [](int* ptr){
std::cout << "custom deleter called\n";
delete ptr;
};//Labmbda表達(dá)式
//默認(rèn)構(gòu)造,沒(méi)有獲取任何指針的所有權(quán),引用計(jì)數(shù)為0
std::shared_ptr<int> sp1;
std::shared_ptr<int> sp2 (nullptr);//同1
//擁有指向int的指針?biāo)袡?quán),引用計(jì)數(shù)為1
std::shared_ptr<int> sp3 (new int);
//同3,但是擁有自己的析構(gòu)方法,如果指針?biāo)赶驅(qū)ο鬄閺?fù)雜結(jié)構(gòu)C
//結(jié)構(gòu)C里有指針,默認(rèn)析構(gòu)函數(shù)不會(huì)將結(jié)構(gòu)C里的指針data所指向的內(nèi)存釋放,
//這時(shí)需要自己使用自己的析構(gòu)函數(shù)(刪除器)
std::shared_ptr<int> sp4 (new int, deleter);
//同4,但擁有自己的分配器(構(gòu)造函數(shù)),
//如成員中有指針,可以為指針?lè)峙鋬?nèi)存,原理跟淺拷貝和深拷貝類似
std::shared_ptr<int> sp5 (new int, [](int* p){delete p;}, std::allocator<int>());
//如果p5引用計(jì)數(shù)不為0,則引用計(jì)數(shù)加1,否則同樣為0, p6為0
std::shared_ptr<int> sp6 (sp5);
//p6的所有權(quán)全部移交給p7,p6引用計(jì)數(shù)變?yōu)闉?
std::shared_ptr<int> sp7 (std::move(sp6));
//p8獲取所有權(quán),引用計(jì)數(shù)設(shè)置為1
std::shared_ptr<int> sp8 (std::unique_ptr<int>(new int));
std::shared_ptr<C> obj (new C);
//同6一樣,只不過(guò)擁有自己的刪除器與4一樣
std::shared_ptr<int> sp9 (obj, obj->data);
std::cout << "use_count:\n";
std::cout << "p1: " << sp1.use_count() << '\n'; //0
std::cout << "p2: " << sp2.use_count() << '\n'; //0
std::cout << "p3: " << sp3.use_count() << '\n'; //1
std::cout << "p4: " << sp4.use_count() << '\n'; //1
std::cout << "p5: " << sp5.use_count() << '\n'; //2
std::cout << "p6: " << sp6.use_count() << '\n'; //0
std::cout << "p7: " << sp7.use_count() << '\n'; //2
std::cout << "p8: " << sp8.use_count() << '\n'; //1
std::cout << "p9: " << sp9.use_count() << '\n'; //2
return 0;
}
shared_ptr賦值
給shared_ptr賦值有三種方式,如下
#include <iostream>
#include <memory>
int main () {
std::shared_ptr<int> foo;
std::shared_ptr<int> bar (new int(10));
//右邊是左值,拷貝賦值,引用計(jì)數(shù)加1
foo = bar;
//右邊是右值,所以是移動(dòng)賦值
bar = std::make_shared<int> (20);
//unique_ptr 不共享它的指針。它無(wú)法復(fù)制到其他 unique_ptr,
//無(wú)法通過(guò)值傳遞到函數(shù),也無(wú)法用于需要副本的任何標(biāo)準(zhǔn)模板庫(kù) (STL) 算法。只能移動(dòng)unique_ptr
std::unique_ptr<int> unique (new int(30));
// move from unique_ptr,引用計(jì)數(shù)轉(zhuǎn)移
foo = std::move(unique);
std::cout << "*foo: " << *foo << '\n';
std::cout << "*bar: " << *bar << '\n';
return 0;
}
make_shared
看下面make_shared的用法:
#include <iostream>
#include <memory>
int main () {
std::shared_ptr<int> foo = std::make_shared<int> (10);
// same as:
std::shared_ptr<int> foo2 (new int(10));
//創(chuàng)建內(nèi)存,并返回共享指針,只創(chuàng)建一次內(nèi)存
auto bar = std::make_shared<int> (20);
auto baz = std::make_shared<std::pair<int,int>> (30,40);
std::cout << "*foo: " << *foo << '\n';
std::cout << "*bar: " << *bar << '\n';
std::cout << "*baz: " << baz->first << ' ' << baz->second << '\n';
return 0;
}
效率提升 std::make_shared(比起直接使用new)的一個(gè)特性是能提升效率。使用std::make_shared允許編譯器產(chǎn)生更小,更快的代碼,產(chǎn)生的代碼使用更簡(jiǎn)潔的數(shù)據(jù)結(jié)構(gòu)??紤]下面直接使用new的代碼:
std::shared_ptr<Test> sp(new Test);
很明顯這段代碼需要分配內(nèi)存,但是它實(shí)際上要分配兩次。每個(gè)std::shared_ptr都指向一個(gè)控制塊,控制塊包含被指向?qū)ο蟮囊糜?jì)數(shù)以及其他東西。這個(gè)控制塊的內(nèi)存是在std::shared_ptr的構(gòu)造函數(shù)中分配的。因此直接使用new,需要一塊內(nèi)存分配給Widget,還要一塊內(nèi)存分配給控制塊。如果使用std::make_shared來(lái)替換:
auto sp = std::make_shared<Test>();
一次分配就足夠了。這是因?yàn)閟td::make_shared申請(qǐng)一個(gè)單獨(dú)的內(nèi)存塊來(lái)同時(shí)存放Widget對(duì)象和控制塊。這個(gè)優(yōu)化減少了程序的靜態(tài)大小,因?yàn)榇a只包含一次內(nèi)存分配的調(diào)用,并且這會(huì)加快代碼的執(zhí)行速度,因?yàn)閮?nèi)存只分配了一次。另外,使用std::make_shared消除了一些控制塊需要記錄的信息,這樣潛在地減少了程序的總內(nèi)存占用。
對(duì)std::make_shared的效率分析可以同樣地應(yīng)用在std::allocate_shared上,所以std::make_shared的性能優(yōu)點(diǎn)也可以擴(kuò)展到這個(gè)函數(shù)上。
異常安全
另外一個(gè)std::make_shared的好處是異常安全,我們看下面一句簡(jiǎn)單的代碼:
callTest(std::shared_ptr<Test>(new Test), secondFun());
簡(jiǎn)單說(shuō),上面這個(gè)代碼可能會(huì)發(fā)生內(nèi)存泄漏,我們先來(lái)看下上面這個(gè)調(diào)用中幾個(gè)語(yǔ)句的執(zhí)行順序,可能是順序如下:
new Test() secondFun() std::shared_ptr<Test>()
如果真是按照上面這樣的代碼順序執(zhí)行,那么在運(yùn)行期,如果secondFun()中產(chǎn)生了一個(gè)異常,程序就會(huì)直接返回了,則第一步new Test分配的內(nèi)存就泄露了,因?yàn)樗肋h(yuǎn)不會(huì)被存放到在第三步才開(kāi)始管理它的std::shared_ptr中。但是如果使用std::make_shared則可以避免這樣的問(wèn)題。調(diào)用代碼將看起來(lái)像這樣:
callTest(std::make_shared<Test>(), secondFun());
在運(yùn)行期,不管std::make_shared或secondFun哪一個(gè)先被調(diào)用。如果std::make_shared先被調(diào)用,則在secondFun調(diào)用前,指向動(dòng)態(tài)分配出來(lái)的Test的原始指針能安全地被存放到std::shared_ptr中。如果secondFun之后產(chǎn)生一個(gè)異常,std::shared_ptr的析構(gòu)函數(shù)將發(fā)現(xiàn)它持有的Test需要被銷毀。并且如果secondFun先被調(diào)用并產(chǎn)生一個(gè)異常,std::make_shared就不會(huì)被調(diào)用,因此這里就不需要考慮動(dòng)態(tài)分配的Test了。
計(jì)數(shù)線程安全?
我們上面一直說(shuō)shared_ptr中的計(jì)數(shù)是線程安全的,其實(shí)shared_ptr中的計(jì)數(shù)是使用了我們前面文章介紹的std::atomic特性,引用計(jì)數(shù)加一減一操作是原子性的,所以線程安全的。引用計(jì)數(shù)器的使用等價(jià)于用 std::memory_order_relaxed 的 std::atomic::fetch_add 自增(自減要求更強(qiáng)的順序,以安全銷毀控制塊)。
#include <iostream>
#include <memory>
#include <thread>
#include <chrono>
#include <mutex>
struct Test
{
Test() { std::cout << " Test::Test()\n"; }
~Test() { std::cout << " Test::~Test()\n"; }
};
//線程函數(shù)
void thr(std::shared_ptr<Test> p)
{
//線程暫停1s
std::this_thread::sleep_for(std::chrono::seconds(1));
//賦值操作, shared_ptr引用計(jì)數(shù)use_cont加1(c++11中是原子操作)
std::shared_ptr<Test> lp = p;
{
//static變量(單例模式),多線程同步用
static std::mutex io_mutex;
//std::lock_guard加鎖
std::lock_guard<std::mutex> lk(io_mutex);
std::cout << "local pointer in a thread:\n"
<< " lp.get() = " << lp.get()
<< ", lp.use_count() = " << lp.use_count() << '\n';
}
}
int main()
{
//使用make_shared一次分配好需要內(nèi)存
std::shared_ptr<Test> p = std::make_shared<Test>();
//std::shared_ptr<Test> p(new Test);
std::cout << "Created a shared Test\n"
<< " p.get() = " << p.get()
<< ", p.use_count() = " << p.use_count() << '\n';
//創(chuàng)建三個(gè)線程,t1,t2,t3
//形參作為拷貝, 引用計(jì)數(shù)也會(huì)加1
std::thread t1(thr, p), t2(thr, p), t3(thr, p);
std::cout << "Shared ownership between 3 threads and released\n"
<< "ownership from main:\n"
<< " p.get() = " << p.get()
<< ", p.use_count() = " << p.use_count() << '\n';
//等待結(jié)束
t1.join(); t2.join(); t3.join();
std::cout << "All threads completed, the last one deleted\n";
return 0;
}
輸出:
Test::Test()
Created a shared Test
p.get() = 0xa7cec0, p.use_count() = 1
Shared ownership between 3 threads and released
ownership from main:
p.get() = 0xa7cec0, p.use_count() = 4
local pointer in a thread:
lp.get() = 0xa7cec0, lp.use_count() = 5
local pointer in a thread:
lp.get() = 0xa7cec0, lp.use_count() = 4
local pointer in a thread:
lp.get() = 0xa7cec0, lp.use_count() = 3
All threads completed, the last one deleted
Test::~Test()
enable_shared_from_this
在某些場(chǎng)合下,會(huì)遇到一種情況,如何安全的獲取對(duì)象的this指針,一般來(lái)說(shuō)我們不建議直接返回this指針,可以想象下有這么一種情況,返回的this指針保存在外部一個(gè)局部或全局變量,當(dāng)對(duì)象已經(jīng)被析構(gòu)了,但是外部變量并不知道指針指向的對(duì)象已經(jīng)被析構(gòu)了,如果此時(shí)外部繼續(xù)使用了這個(gè)指針就會(huì)發(fā)生程序奔潰。既要像指針操作對(duì)象一樣,又能安全的析構(gòu)對(duì)象,很自然就想到,智能指針就很合適!我們來(lái)看下面這段程序:
#include <iostream>
#include <memory>
class Test{
public:
Test(){
std::cout << "Test::Test()" << std::endl;
}
~Test(){
std::cout << "Test::~Test()" << std::endl;
}
std::shared_ptr<Test> GetThis(){
return std::shared_ptr<Test>(this);
}
};
int main()
{
std::shared_ptr<Test> p(new Test());
std::shared_ptr<Test> p_this = p->GetThis();
std::cout << p.use_count() << std::endl;
std::cout << p_this.use_count() << std::endl;
return 0;
}
編譯運(yùn)行后程序輸出如下:
free(): double free detected in tcache 2
Test::Test()
1
1
Test::~Test()
Test::~Test()
從上面的輸出可以看到,構(gòu)造函數(shù)調(diào)用了一次,析構(gòu)函數(shù)卻調(diào)用了兩次,很明顯這是不正確的。而std::enable_shared_from_this正是為了解決這個(gè)問(wèn)題而存在。
std::enable_shared_from_this 能讓一個(gè)對(duì)象(假設(shè)其名為 t ,且已被一個(gè) std::shared_ptr 對(duì)象 pt 管理)安全地生成其他額外的 std::shared_ptr 實(shí)例(假設(shè)名為 pt1, pt2, ... ) ,它們與 pt 共享對(duì)象 t 的所有權(quán)(這個(gè)是關(guān)鍵,直接使用this無(wú)法達(dá)到該效果)。
std::enable_shared_from_this是模板類,內(nèi)部有個(gè)_Tp類型weak_ptr指針,std::enable_shared_from_this的構(gòu)造函數(shù)都是protected,因此不能直接創(chuàng)建std::enable_from_shared_from_this類的實(shí)例變量,只能作為基類使用,通過(guò)調(diào)用shared_from_this成員函數(shù),將會(huì)返回一個(gè)新的 std::shared_ptr<T> 對(duì)象,它與 pt 共享 t 的所有權(quán)。因此使用方法如下代碼所示:
#include <iostream>
#include <memory>
// 這里必須要 public繼承,除非用struct
class Test : public std::enable_shared_from_this<Test> {
public:
Test(){
std::cout << "Test::Test()" << std::endl;
}
~Test(){
std::cout << "Test::~Test()" << std::endl;
}
std::shared_ptr<Test> GetThis(){
std::cout << "shared_from_this()" << std::endl;
return shared_from_this();
}
};
int main()
{
std::shared_ptr<Test> p(new Test());
std::shared_ptr<Test> p_this = p->GetThis();
std::cout << p.use_count() << std::endl;
std::cout << p_this.use_count() << std::endl;
return 0;
}
在類內(nèi)部通過(guò) enable_shared_from_this 定義的 shared_from_this() 函數(shù)構(gòu)造一個(gè) shared_ptr<Test>對(duì)象, 能和其他 shared_ptr 共享 Test 對(duì)象。一般我們使用在異步線程中,在異步調(diào)用中,存在一個(gè)保活機(jī)制,異步函數(shù)執(zhí)行的時(shí)間點(diǎn)我們是無(wú)法確定的,然而異步函數(shù)可能會(huì)使用到異步調(diào)用之前就存在的變量。為了保證該變量在異步函數(shù)執(zhí)期間一直有效,我們可以傳遞一個(gè)指向自身的share_ptr給異步函數(shù),這樣在異步函數(shù)執(zhí)行期間share_ptr所管理的對(duì)象就不會(huì)析構(gòu),所使用的變量也會(huì)一直有效了(?;睿?/p>
shared_ptr使用注意事項(xiàng):
不要把一個(gè)原生指針給多個(gè)shared_ptr管理;不要主動(dòng)刪除
shared_ptr所管理的裸指針;BigObj *p = new BigObj(); std::shared_ptr<BigObj> sp(p); std::shared_ptr<BigObj> sp1(p); delete p;
不要把this指針給shared_ptr,像上面一樣使用enable_shared_from_this;
不要不加思考地把指針替換為shared_ptr來(lái)防止內(nèi)存泄漏,shared_ptr并不是萬(wàn)能的,而且使用它們的話也是需要一定的開(kāi)銷的;
共享?yè)碛袡?quán)的對(duì)象一般比限定作用域的對(duì)象生存更久,從而將導(dǎo)致更高的平均資源使用時(shí)間;
在多線程環(huán)境中使用共享指針的代價(jià)非常大,這是因?yàn)槟阈枰苊怅P(guān)于引用計(jì)數(shù)的數(shù)據(jù)競(jìng)爭(zhēng);
如果你使用智能指針管理的資源不是new分配的內(nèi)存,記住傳遞給它一個(gè)刪除器。
總結(jié)
智能指針是模板類而不是指針。創(chuàng)建一個(gè)智能指針時(shí),必須指針可以指向的類型,<int>,<string> ……等。 智能指針實(shí)質(zhì)就是重載了->和*操作符的類,由類來(lái)實(shí)現(xiàn)對(duì)內(nèi)存的管理,確保即使有異常產(chǎn)生,也可以通過(guò)智能指針類的析構(gòu)函數(shù)完成內(nèi)存的釋放。具體來(lái)說(shuō)它利用了引用計(jì)數(shù)技術(shù)和 C++ 的 RAII(資源獲取就是初始化)特性。
到此這篇關(guān)于C++智能指針之shared_ptr的具體使用的文章就介紹到這了,更多相關(guān)C++ shared_ptr內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于VC中使用ForceInclude來(lái)強(qiáng)制包含stdafx.h的解決方法
本篇文章是對(duì)VC中使用ForceInclude來(lái)強(qiáng)制包含stdafx.h的解決方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
C語(yǔ)言中實(shí)現(xiàn)KMP算法的實(shí)例講解
KMP算法即字符串匹配算法,C語(yǔ)言中KMP可以避免指針回溯從而達(dá)到高效,接下來(lái)就來(lái)總結(jié)一下C語(yǔ)言中實(shí)現(xiàn)KMP算法的實(shí)例講解2016-06-06
Visual Studio Code 2020安裝教程及CPP環(huán)境配置(教程圖解)
這篇文章主要介紹了Visual Studio Code 2020安裝教程、CPP環(huán)境配置,本文通過(guò)圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-03-03
用C語(yǔ)言實(shí)現(xiàn)自動(dòng)售貨機(jī)
這篇文章主要為大家詳細(xì)介紹了用C語(yǔ)言實(shí)現(xiàn)自動(dòng)售貨機(jī),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-01-01

