日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

复制构造函数的用法及出现迷途指针问题

發布時間:2023/11/29 编程问答 38 豆豆
生活随笔 收集整理的這篇文章主要介紹了 复制构造函数的用法及出现迷途指针问题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?復制構造函數利用下面這行語句來復制一個對象

? A (A &a)

?從上面這句話可以看出,所有的復制構造函數均只有一個參數,及對同一個類的對象的引用

?

比如說我們有一個類A,定義如下:

?

?
1 2 3 4 5 6 7 8 9 10 class A { public: A(int i,int j){n=i;m=j;} //構造函數帶兩個參數,并將參數的值分別賦給兩個私有成員 A(A &t); //我們自己定義一個默認構造函數 void print(){cout<<n<<m;}//輸出兩個成員的值 private: int n;? //數據成員n int m; //數據成員m };

?

????? 在上面這個類的定義中我們定義了一個默認的構造函數(雖然默認構造函數一般是通過編譯器自動定義的,但是這里我們模擬一下它的工作過程)。默認構造函數的工作方法應該如下面所示:

?

?
1 2 3 4 A(A &t) { ????????n=t.n;m=t.m; }

?

????? ?在這里我們模擬了一個默認復制構造函數是如何運行的。它通過別名t訪問一個對象,并將該對象的成員賦給新對象的成員。這樣就完成了復制工作。這樣一來,我們如果再程序中定義了:

?

?
A a(2,4);

?

?????? 這個對象。這就表示,利用構造函數,對象a的數據成員a.n=2;a.m=4,如果我們利用下面這句話調用一個默認的復制構造函數:

?

?
A b(a)

?

?????? 那么它就會調用復制構造函數,即上面我們模擬的復制構造函數中所定義的“n=t.n;m=t.m”。這時對象b的數據成員b.n=2;b.m=4,這與對象a中的數據成員的值應該是一模一樣的。

?

迷途指針產生的原因:“淺層復制構造函數”

????? 一般地,編譯器提供的默認復制構造函數的功能只是把傳遞進來的對象的每一個成員變量的值復制給了新對象的成員變量。但是,如果這個老對象是一個指針,那么新對象也是一個指針,且它指向的內存地址和老對象是一樣的!這樣就會產生2個顯而易見的問題:

  • 我們可以隨意地對一個指針所指向的內存空間進行賦值操作。那么另外一個指針所指向的內存空間由于和前面那個指針式一模一樣的,那么它就不可避免地被修改;
  • 如果我們在程序中無意將老對象所指向的內存地址釋放掉了,那么新對象的指針自然就變成了一個迷途指針。舉一個形象的例子來說就是:如果B對象持用一個指針指向對象A, 現有一個B對象的拷貝C,那么它也持有一個指針p2指向A. 若某時, B釋放了對象A, 但C是無法知曉的, C認為它持有的指針指向的A有效, 之后若C再調用A的方法就報錯。
  • #include <iostream>using namespace std;class A {public:A(){x=new int;*x=5;} //創建一個對象的同時將成員指針指向的變量保存到 新空間中~A(){delete x;x = NULL;}//析構對象的同時刪除成員指針指向的內存空間并將指針賦為空A(A &a){cout << "復制構造函數執行...\n" <<endl;x = a.x; //將舊對象的成員指針x指向的空間處的數據賦給新對象的成員指針x }void print(){cout<<*x<<endl;}void set(int i){*x=i;}private:int *x;};int main() {A *a = new A(); //利用指針在堆中創建一個對象cout<<"a:";a->print();cout<<endl;A b=(*a); //這里初始化的對象為指針a所指向的堆中的對象//調用復制構造函數之后,將對象b變成對象a的一個拷貝,那么對象a和對象b所輸出的值應該是一樣的cout<<"b:";b.print();cout<<endl;//利用對象a中的成員函數set()將指針x指向的內存區域中的值改成32a->set(32);cout<<"a:";a->print();cout<<"b:";b.print();cout<<endl;//利用對象b中的成員函數set()將指針x指向的內存區域中的值改成99 b.set(99);cout<<"a:";a->print();cout<<"b:";b.print();cout<<endl;delete a;return 0;}

    輸出結果:

    ?

    ????? 上面紅色框就代表了用a.set(32)來改變a中指針x所指向的內存空間的值,可以看出b的指針x所指向的內存空間的值也跟著變成了32。綠色框代表了用b.set(99)來改變b中指針x所指向的內存空間的值,可以看出a的指針x所指向的內存空間的值也跟著變成了99。而在程序崩潰對話框(白底黑字那個)中指明了程序的第52行引發了一個錯誤,大意就是那個內存區域是非法的(如上圖中藍色框所示)。出現這個錯誤的原因其實就是由迷途指針造成的:當main函數結束(右大括號)并析構對象b的時候,由于指針成員x所指向的內存區域已經在第52行被釋放了,這樣一來,對象b中的指針x就變成了迷途指針了,我們再次釋放這塊內存空間的時候肯定就會導致程序的崩潰。

    上篇文章末尾談到了指針懸掛的問題,這主要是由于淺層復制構造函數的原因。為了解決這個指針懸掛的問題,這時候我們就需要引進一個新的概念:深層復制構造函數。

    ????? 下面,我們來介紹一下淺層復制構造函數深層復制構造函數之間的區別與聯系......

    • 淺層復制構造函數:淺層構造賦值函數主要是將傳遞進來的對象的成員變量的所有值賦值給新對象的成員變量。
      x=a.x;//把對象a中的指針成員變量x的值復制給了對象b中的指針成員變量x
      ????? 上面這個語句就會產生一個問題,即對象b的指針成員變量b.x和對象a的指針成員變量a.x所保存的值是同一塊內存空間的地址。如果我們析構了對象a,那么編譯器會自動釋放該內存地址,而b并不知道,這樣就產生了指針懸掛的問題。這就是由于淺層復制構造函數的運作機理產生的,它只是將舊對象的數據復制給新對象的數據,而如果是指針對象,它復制的就是指針所保存的地址。上面這句話是不是有點繞啊,我們用下圖來解釋一下: ????? 從上面這個圖就可以非常清楚地看到,當我 們析構掉對象a的時候,編譯器會自動釋放堆中所創建的內存空間。而對于對象b而言,它壓根就不知道有編譯器釋放堆中內存這么一回事,所以自然b.x就變成迷途指針了。
    • 深層復制構造函數:淺層構造賦值函數主要功能雖然也是是將傳遞進來的對象的成員變量的所有值賦值給新對象的成員變量,但是有一點不同之處在于,先看程序: ?
      1 2 x=new int;//創建一塊新空間 *x=*(a.x);//把對象a中指針成員x所指向的值賦值給了利用深層賦值構造函數所創建的對象b的指針成員x
      由上面的程序可以看出,其實深層復制構造函數中我們只添加了為成員指針指向的數據成員分配內存,同時在賦值的時候,我們是把舊對象中指針成員所指向的值復制給了新對象的指針成員,而不是地址,這樣就可以避免指針懸掛的問題了。你可能會問,上面這么長一串話是啥意思哦?不解釋,我們直接上圖!

    ????? 從上面這個圖,我們就顯而易見地看出深層復制構造函數的好處了!注意看如果我們析構了對象a,那么編譯器只是會回收內存地址為A處得內存,而不會管內存地址為D處的值。這樣就避免了利用淺層復制函數所產生的對象b的指針懸掛問題了。

    轉載于:https://www.cnblogs.com/wft1990/p/6617416.html

    總結

    以上是生活随笔為你收集整理的复制构造函数的用法及出现迷途指针问题的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。