动态内存的基本功能和使用
動態內存的基本功能和使用
- 基本知識
- 動態內存
- new和delete
- shared_ptr
- StrBlob類與StrBlobPtr類
- 示例代碼
- 程序1
- 程序2
- 程序3
本文是本人大一期間在校學習C++課程時所撰寫的實驗報告的摘錄,由于剛上大學,剛接觸計算機編程方面的相關知識,故可能會有很多不足甚至錯誤的地方,還請各位看官指出及糾正。
本文所涉及到的“教材”指:
電子工業出版社《C++ Primary中文版(第5版)》
如需轉載或引用請標明出處。
本文涉及到的三個程序通過自定義了一個結構體Foo、類型StrBlob、指向其的類型StrBlobPtr以及定義其他一些操作這些類或結構體的函數,來演示了如何使用動態內存及其特點,并展示了shared_ptr的用法和特點。下面是一些相關的知識點。
基本知識
動態內存
除了自動和static對象外,C++還支持動態分配對象。動態分配的對象的生存期與它們在哪里建創是無關的,只有當顯示地被釋放時,這些對象才會銷毀。也就是說,動態對象的生存期由程序來控制,當動態對象不再使用時,我們的代碼必須顯示地銷毀它們。
new和delete
在C++中,動態內存的管理是通過一對運算符來完成的:new,在動態內存中為對象分配空間并返回一個指向該對象的指針,我們可以選擇對對象進行初始化;delete,接受一個動態對象的指針,銷毀該對象,并釋放與之關聯的內存。
然而想通過使用這兩個關鍵字管理動態內存很容易出問題,因為我們很容易就忘了釋放內存,這種情況下就會產生內存泄漏;有時在尚有指針應用內存的情況下我們就釋放了它,這時就會產生引用非法內存的指針。
為了更安全地使用動態內存,新的標準庫提供了幾種智能指針類型來管理動態對象。下面主要介紹shared_ptr。
shared_ptr
shared_ptr類似于vector,智能指針也是模板,因此建創智能指針時須告知編譯器該指針可以指向的類型。例如,我們可以像這樣去建創智能指針:
shared_ptr<string> p1; //p1可以指向string shared_ptr<vector<string>> p2; //p2可以指向由string組成的vector智能指針的用法和普通的指針類似,都可以通過解引用智能指針返回它指向的目標,都可以通過->運算符返回它指向目標的成員等。
shared_ptr最大的特點是,可以認為每個shared_ptr都有一個關聯的計數器,通常也稱其為應用計數。無論何時我們拷貝一個shared_ptr時,計數器都會遞增。
例如,當引用一個shared_ptr初始化另一個shared_ptr,或將它作為參數傳遞給一個函數以及作為函數的返回值時,它關聯的計數器就會遞增。當我們給shared_ptr賦予一個新值或是shared_ptr被銷毀(例如一個局部的shared_ptr離開其作用域)時,計數器會遞減。一旦一個shared_ptr的計數器變為0,它就會自動銷毀自己所管理的對象,并釋放它們占用的內存。而這一特性使得正確、安全地使用動態內存變得非常容易。
具體例子詳見代碼注釋部分。
StrBlob類與StrBlobPtr類
這兩個類是自定義的類,用于演示如何使用動態內存。其中StrBlob類是對象本身,StrBlobPtr類是用于管理對象的類似于指針的類型。其中的一些操作函數的實現方法詳見代碼注釋部分。
示例代碼
程序1
① Foo.h
#ifndef FOO_H //如果沒有定義FOO_H,則進行以下定義 #define FOO_H#include <iostream>typedef int T; //定義新的int類型T struct Foo { //默認情況下Foo的成員是公共的Foo(T t): val(t) { } //重載,使Foo(t)相當于令Foo成員val的值為tT val; //Foo只有一個成員val };//定義輸出函數,接受一個輸出流和一個Foo類型數據的引用為實參 std::ostream& print(std::ostream &os, const Foo &f) {os << f.val; //通過輸出流os輸出f的成員valreturn os; //返回os }#endif //定義結束② allocPtr.cpp
#include <vector> using std::vector;#include <string> using std::string;#include <iostream> using std::istream; using std::ostream; using std::cin; using std::cout; using std::endl; //包含有Foo類型相關信息的頭文件 #include "Foo.h"//factory函數返回一個指向動態分配內存對象的指針,所指類型為Foo //作用為生成一個動態分配內存的對象 Foo* factory(T arg) {//恰當地處理參數arg//通過new關鍵字,動態分配一塊內存大小為Foo的大小的對象,用arg為參數初始化該對象//同時調用方負責釋放此內存return new Foo(arg); }//use_factory函數返回一個指向動態分配內存對象的指針,所指類型為Foo //作用為對其中的數據進行操作 Foo* use_factory(T arg) {Foo *p = factory(arg); //通過調用factory函數,動態分配Foo所需的空間,用指針p指向print(cout, *p); //用自定義的輸出函數將p指向的對象的內容(val)輸出到cout cout << endl; //結束一行//返回p以便被調用方使用return p; //調用方必須記得釋放動態分配的內存 } int main() {T arg;while (cin >> arg) { //有正確輸入使執行循環Foo *p = use_factory(arg); //用指針p指向use_factory函數的返回值delete p; //使用之后釋放p所指向的動態內存}system("pause");return 0; }運行結果:
依次輸入11 22 33 55 88 -99 00 z[enter],窗口顯示:
程序2
① Foo.h
同上。
② allocSP.cpp
運行結果同程序1
程序3
① StrBlob.h
//如果沒有定義STRBLOB_H,則進行以下定義 #ifndef STRBLOB_H #define STRBLOB_H #include <vector> #include <string> #include <memory> //使用shared_ptr等 #include <stdexcept> //使用一些異常類class StrBlobPtr; //該聲明需要在StrBlob中進行友元聲明class StrBlob {friend class StrBlobPtr; //聲明StrBlobPtr為友元類 public: //公開成員typedef std::vector<std::string>::size_type size_type; //定義簡寫的size_typeStrBlob() : data(new std::vector<std::string>()) { } //重載構造,使StrBlob()相當于用使用new動態分配內存大小為vector<std::string>構造的data StrBlob(const std::string*, const std::string*); //之前的C++11沒有initializer_list,我們將定義一個構造函數//它的指針指向一個數組//定義關于size的操作size_type size() const { return data->size(); } //size函數返回成員data的大小bool empty() const { return data->empty(); } //empty函數判斷data是否為空//定義增加、刪除元素的操作void push_back(const std::string &t) { data->push_back(t); } //在成員data中添加類型為string的元素tvoid pop_back();//定義元素訪問的操作std::string& front(); //返回data的頭元素std::string& back(); //返回data的尾元素//下面是關于StrBlobPtr的接口StrBlobPtr begin();StrBlobPtr end(); private: //私有成員 std::shared_ptr<std::vector<std::string> > data; //data為指向存放string的vector的shared_ptr指針 void check(size_type i, const std::string &msg) const; //檢查函數,如果data[i]不存在則拋出異常msg };//使用內聯關鍵字重載構造函數 inline StrBlob::StrBlob(const std::string *beg, const std::string *end): data(new std::vector<std::string>(beg, end)) { }//StrBlobPtr在嘗試訪問不存在的元素時拋出異常 class StrBlobPtr {friend bool eq(const StrBlobPtr&, const StrBlobPtr&); //聲明友元函數eq public: //公開成員StrBlobPtr(): curr(0) { } //重載構造,使StrBlobPtr()相當于curr(0)StrBlobPtr(StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) { } //同為重載構造std::string& deref() const; //成員函數derefStrBlobPtr& incr(); //成員函數incr()StrBlobPtr& decr(); //成員函數decr() private://如果檢查成功的話,check函數返回一個指向該vector的shared_ptr指針std::shared_ptr<std::vector<std::string>> check(std::size_t, const std::string&) const;//成員wptr的類型為指向vector<string>的weak_ptr指針,意味著其指向的對象可能被釋放 std::weak_ptr<std::vector<std::string> > wptr;std::size_t curr; //成員curr用于指示當前位置 };inline std::string& StrBlobPtr::deref() const {//檢查該位置是否超出vector的范圍std::shared_ptr<std::vector<std::string>> p = check(curr, "dereference past end"); return (*p)[curr]; //(*p)是該指針指向的vector }inline std::shared_ptr<std::vector<std::string>> StrBlobPtr::check(std::size_t i, const std::string &msg) const {//檢查該向量vector是否存在std::shared_ptr<std::vector<std::string> > ret = wptr.lock(); //如果不存在,則拋出異常if (!ret)throw std::runtime_error("unbound StrBlobPtr");//如果超出范圍,則拋出另一個異常 if (i >= ret->size()) throw std::out_of_range(msg);return ret; //否則返回一個指向vector的shared_ptr指針 }//返回對遞增對象的引用 inline StrBlobPtr& StrBlobPtr::incr() {//如果curr早就超過了容器的范圍,則不能遞增check(curr, "increment past end of StrBlobPtr"); //檢查curr的值是否合法++curr; //增加currreturn *this; //返回當前對象 }inline StrBlobPtr& StrBlobPtr::decr() {//如果curr的值為0,則遞減它會產生一個無效的目標--curr; //向前移動一個元素check(-1, "decrement past begin of StrBlobPtr"); //如果curr的值為-1, 則拋出異常 return *this; //返回當前對象 }//返回StrBlob中的頭元素和尾元素 inline StrBlobPtr StrBlob::begin() {return StrBlobPtr(*this); //返回頭元素 }inline StrBlobPtr StrBlob::end() {StrBlobPtr ret = StrBlobPtr(*this, data->size()); //返回尾元素return ret; }//關于StrBlobPtr中相同元素的操作 inline bool eq(const StrBlobPtr &lhs, const StrBlobPtr &rhs) {std::shared_ptr<std::vector<std::string>> l = lhs.wptr.lock(), r = rhs.wptr.lock();//如果這兩個vector是同一個的話if (l == r) //如果它們同時指向NULL或同時指向同一個元素的話,說明它們是相等的 return (!r || lhs.curr == rhs.curr);elsereturn false; //如果它們指向不同的vector,說明它們不相等 }//上面函數的相反 inline bool neq(const StrBlobPtr &lhs, const StrBlobPtr &rhs) {return !eq(lhs, rhs); } #endif //結束定義② useBlob.cpp
#include <iostream> using std::cout; using std::endl;#include <string> using std::string;//包含有StrBolb類型相關信息的頭文件 #include "StrBlob.h"int main() {StrBlob b1; //定義一個StrBlob類型b1{//劃分出一個新的塊string temp[] = { "a", "an", "the" }; //定義臨時的string數組temp,用于存儲數據StrBlob b2(temp, temp + sizeof(temp) / sizeof(*temp)); //在塊中定義另一個StrBlob類型b2,用temp中的元素進行初始化,b2的計數器加一b1 = b2; //將b2賦值給b1,遞增b2內置的計數器,此時b2計數為2b2.push_back("about"); //向b2中添加元素"about"cout << b2.size() << endl; //輸出b2的大小} //離開b2的作用域,b2內置的計數器減一,此時b2計數為1,不為0,因此對象沒有被銷毀cout << b1.size() << endl; //輸出b1的大小,若一樣,則說明b1和b2是同一個vector//當StrBlobPtr類型的指針it不指向b1的尾元素時執行循環for (StrBlobPtr it = b1.begin(); neq(it, b1.end()); it.incr())cout << it.deref() << endl; //輸出it指向的元素,然后遞增itsystem("pause");return 0; }運行結果:
說明b1和b2指向的是同一個vector。
總結
以上是生活随笔為你收集整理的动态内存的基本功能和使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Kali Linux ver2020.4
- 下一篇: 共享智能指针编程实验