c++拷贝、赋值和销毁的简单介绍
13.1拷貝、賦值與銷毀
13.1.1拷貝構造函數
當定義一個類時,我們顯式或者隱式地指定在此類型的對象的拷貝、賦值、移動、銷毀時做什么。一個類通常通過5種特殊的構造函數來控制這些操作,包括:拷貝構造函數(copy constructor)、拷貝賦值運算符(copy assignment operator),移動構造函數(move constructor)、移動賦值運算符(move assignment operator)、析構函數(destructor)。
拷貝構造函數|移動構造函數:定義了當用同類型的一個對象初始化另一個對象時的操作。
拷貝賦值運算符|移動賦值運算符:定義了當一個同類型的對象賦予另一個對象所定義的操作。
析構函數:定義了當此類型對象銷毀時的操作。
#include <string> #include <iostream> #include <vector> using namespace std; class Sales_data{public:Sales_data() = default;Sales_data(const Sales_data &);public:Sales_data(int u,double d);Sales_data(const string &);private:string bookno;int units_sold = 0.0;double revenue = 0.0; };Sales_data::Sales_data(const string &s):bookno(s){ cout << "execute Sales_data(const string &)"<< endl; }Sales_data::Sales_data(const Sales_data &s):bookno(s.bookno),units_sold(s.units_sold),revenue(s.revenue) { cout << "execute copy constructor." << endl; }Sales_data::Sales_data(int u,double d):units_sold(u),revenue(d) { cout << "execute constructor." << endl; }const Sales_data &test(const Sales_data &s) { return s; }int main() { string str = "you are good";Sales_data s; Sales_data s4 = s; Sales_data s1(1,2); //initialize execute constructor Sales_data s2(s1); //initialize execute copy constructor Sales_data s3 = s2; //initialize execute copy constructor cout << "s5 is flowing:" << endl; Sales_data s5 = str;cout << "test0:" << endl; test(s5);cout << "test1:" << endl; Sales_data s7[4]{s,s1,s2,s4};cout << "test2" << endl; vector<Sales_data> v1; //push_back cout << "push_back():" << endl; v1.push_back(s); cout << "insert :" << endl; v1.emplace(v1.begin(),s1); cout << "emplace:"<< endl; v1.emplace(v1.begin(),string("abc")); cout << "last one:" << endl' Sales_data s9 = Sales_data(); Sales_data s10(s9); return 0; }運行結果:
execute copy constructor. execute constructor. execute copy constructor. execute copy constructor. s5 is flowing: execute Sales_data(const string &) test0: test1: execute copy constructor. execute copy constructor. execute copy constructor. execute copy constructor. test2 push_back(): execute copy constructor. insert : execute copy constructor. execute copy constructor. emplace: execute Sales_data(const string &) execute copy constructor. execute copy constructor. last one: execute copy constructor.下面給一個習題:
對于一個類定義,下列敘述中錯誤的是
A 如果沒有定義拷貝構造函數,編譯器將產生一個拷貝構造函數
B 如果沒有定義缺省的構造函數,編譯器將一定生成一個缺省的構造函數
C 如果沒有定義構造函數,編譯器將會產生一個缺省的構造函數和一個拷貝構造函數
D 如果已經定義了構造函數和拷貝構造函數,編譯器不會生成任何構造函數.
D肯定是對的,A我覺得也沒錯,C我也覺得沒錯,那就是B了嗎?我試過,編譯出錯,B就是錯誤的吧
我查了答案,選B
拷貝初始化和直接初始化:
拷貝構造函數的定義:如果構造函數的第一個參數是自身類類型的引用,且所有其它參數(如果有的話)都有默認值,則此構造函數就是拷貝構造函數。拷貝構造函數在以下幾種情況下使用:
?1.拷貝初始化(用=定義變量)
2.將一個對象作為實參傳遞給非引用類型的形參。
3.一個返回類型為非引用類型的函數返回一個對象。
4.用花括號列表初始化一個數組中的元素或者一個聚合類中的成員。
5.初始化標準庫容器或調用其insert/push操作時,容器會對元素進行拷貝初始化。
注意,編譯器雖然會優化如下代碼:
string str = "hello,world!!!"; Sales_data s1 = str;優化成,
Sales_data s1(str);但是,必須拷貝構造函數必須可以調用,否則會出錯。語法上是一回事,編譯器執行是另一回事。具體參考c++ primer 5th 第442頁之----編譯器可以繞過拷貝構造函數。
13.1.2拷貝賦值運算符
1.什么是拷貝賦值運算符:
拷貝賦值運算符本身是一個重載的賦值運算符,定義為類的成員函數,左側運算對象綁定到隱含的this參數,而右側的運算對象是所屬類類型的,作為函數的參數,函數返回指向左側運算對象的引用。特點是:
(1)返回值:指向左側運算對象的引用
(2)名字:由operator關鍵字后接一個表示要定義的運算符的符號(=),通常是operator=
(3)參數:表示運算符運算的對象??截愘x值運算符接受一個與其所在類相同類型的參數。
(4)定義的操作:類似拷貝構造函數,某些拷貝賦值運算符禁止該類型對象的賦值。如果拷貝構造運算符并非出于此目的,它會將右側運算對象的每個非static成員賦予左側運算對象的對應成員。
#include <string> #include <iostream> #include <memory> using namespace std; class Sales_data{public:Sales_data() = default;Sales_data(const string &);Sales_data(const Sales_data &); //返回類型也可以是const的,那么什么時候該const,什么時候不該?Sales_data& operator=(const Sales_data &);private:string bookno;double revenue = 0.0;unsigned units_sold = 0; }; Sales_data::Sales_data(const Sales_data &s): bookno(s.bookno),revenue(s.revenue),units_sold(s.units_sold){ cout << "execute copy contructor." << endl; } Sales_data::Sales_data(const string &s):bookno(s){cout << "execute constructor." << endl;} Sales_data& Sales_data::operator=(const Sales_data &s){ //必須在函數體內用拷貝完成,不能在函數題和函數參數列表之間采用成員初始化,否則 //會報錯:only constructors take member initializers. bookno = s.bookno; units_sold = s.units_sold; revenue = s.revenue; cout << "execute copy assignment operator." << endl; return *this; } int main() { Sales_data s1; Sales_data s2("This is only a test."); s1 = s2;return 0; }運行結果:
execute constructor. execute copy assignment operator.疑問?拷貝賦值運算符返回值可以是本身類類型引用或者const本身類類型的引用。什么時候定義成const的呢?
1.什么時候使用拷貝賦值運算符?答:當對類對象進行賦值時,會使用拷貝賦值運算符。
2.合成拷貝賦值運算符完成什么工作?答:通常情況下,合成拷貝賦值運算符會把右側對象的所有非static成員逐個賦予左側對象的對應成員,這些賦值操作是由成員類型的拷貝賦值運算符來完成的。某些拷貝賦值運算符會起到禁止該類型對象賦值的效果。
3.什么時候編譯器會生成合成拷貝賦值運算符?答:當一個類未定義自己的拷貝賦值運算符,那么編譯器會自動生成一個拷貝賦值運算符。
13.1.3析構函數
1.析構函數的作用:析構函數和構造函數執行相反的操作,構造函數初始化對象的非static數據成員,還可能做一些其它工作;析構函數釋放對象所使用的資源,并銷毀對象的非static數據成員。
2.析構函數的定義:
析構函數是類的一個成員函數,由波浪號接類名構成。它沒有返回值,也不接受參數。所以,析構函數不能被重載。對于一個類,只有唯一的析構函數。
class Sales_data{ public:.... ~Sales_data(){} //析構函數 private:... };3.組成:由一個函數體和一個析構部分;構造函數由一個初始化部分和一個函數題構成。
4.析構函數執行過程:首先執行函數體,然后銷毀成員。成員按照初始化順序的逆序銷毀。
5.處理內置指針:隠式銷毀一個內置指針類型的成員不會delete它所指向的成員。
6.智能指針:與普通指針不同,智能指針是類類型,所以具有析構函數,因此,與普通指針不同,智能指針的成員在析構階段會被自動銷毀。
7.何時調用析構函數:
(1)變量在離開作用域時被銷毀。
(2)當一個對象被銷毀時,其成員被銷毀。
(3)容器(無論是標準庫容器還是數組)被銷毀時,其元素被銷毀。
(4)對于動態分配的對象,當對指向它的指針調用delete運算符時被銷毀。
(5)對于臨時對象,當創建它的完整表達式結束時被銷毀。
注意:
1.隱式銷毀一個內置指針類型的成員不會delete它所指向的對象。(只有顯式delete才調用該對象的析構函數,這和內置的其它類型或者智能指針不一樣)
2.當指向一個對象的引用或者指針離開作用域時,析構函數不會執行。
(只有顯式地調用delete時,才調用該對象的類類型的析構函數)
3.析構函數體自身并不銷毀成員。成員是在析構函數體之后隱含的析構階段中被銷毀的。在整個對象銷毀的過程中,析構函數體是作為成員銷毀步驟之外的另一部分而進行的。
c++ primer 5th? 13.1.3節習題
練習13.9.
? ? ? 答:析構函數就是波浪號加類名構成的類的成員函數,不接受參數也沒有返回值。完成的工作和構造函數相反,構造函數初始化類的非static數據成員。析構函數銷毀類的非static數據成員,釋放類使用的資源。當用戶未定義析構函數時,編譯器會生成合成析構函數。
習題13.10
? ? ?答:當StrBlob對象銷毀時,執行完析構函數的空函數體之后,會進行隱含的析構階段,銷毀非靜態數據成員data,這會調用shared_ptr的析構函數,會遞減data的引用計數,如果data的引用計數變為0,那么釋放data所指向的vector對象的內存。
? 對StrBlobPtr對象,合成析構函數在隱含的析構階段會銷毀數據成員wptr和curr,銷毀wptr會調用weak_ptr的析構函數,引用計數不變,而curr是內置類型,銷毀它不會有特殊動作。
習題13.11
class HasPtr{public:HasPtr(const string &s = string()):ps(new string(s)),i(0){}~HasPtr(){delete ps;}private:string *ps;int i; };函數體內,放delete語句。
習題13.12
這段代碼會發生三次析構函數的調用:
(1)函數結束時,局部變量item1的生命期結束,被銷毀,Sales_data的析構函數被調用。
? ?(2)函數結束時,item2在函數結束時被銷毀,Sales_data的析構函數被調用。
? ? ?(3)函數結束時,參數accum的生命期結束,被銷毀,Sales_data的析構函數被調用。
在函數結束時,trans的生命期也結束了,但它是Sales_data的指針,并不是指向它的Sales_data對象的生命期結束(只有delete指針時,指向的動態對象的生命期才結束),所以不會引起析構函數的調用。
習題13.13
#include <string> #include <iostream> #include <memory> #include <vector> using namespace std; class X{public:X(){cout << "X()" << endl;}X(const X&a):num(a.num),bookno(a.bookno){cout << "X(const X&)" << endl;}X(int i,const string &s):num(i),bookno(s){cout << "X(int i,const string &s)" << endl;}X(int i):bookno(string()){cout << "X(int i)" << endl;}~X(){cout << "~X()" << endl;}X& operator=(const X&a){num = a.num;bookno = a.bookno;cout << "X& operator=(const X&)" << endl;return *this;}private:int num = 0;string bookno;};const X & test1(const X & a) { return a; } X test2(const X a,const X b) {return a; }int main() { //默認初始化,調用默認構造函數 X a; //調用構造函數 X b(2,"hello,world"); //調用拷貝構造函數 X c(b); //調用拷貝構造函數,用c給d拷貝 X d = c; //調用拷貝構造函數和構造函數,具體過程中可能略過拷貝構造函數 X e = 1234; //調用拷貝賦值運算符 e = c; //調用函數,參數和反語i類型都是引用 cout << "call function test1:" << endl; //結果執行一次拷貝賦值運算符,函數參數是引用,不執行拷貝構造函數 c = test1(e); cout << endl << endl;cout << "call function test2:" << endl; //執行2次拷貝構造函數,因為函數2個參數都是非引用的,然后執行 c = test2(e,d);//定義一個vector vector<X> xvec; cout << "vec 's push_back:" << endl; xvec.push_back(a); cout << "xvec insert:" << endl; xvec.insert(xvec.begin(),c);cout << "execute: X *p = new X" << endl; X *p = new X(); delete p; cout << "this is the end.." << endl; return 0;}運行結果:
X() X(int i,const string &s) X(const X&) X(const X&) X(int i) X& operator=(const X&)? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?參考教程:c++ primer 第五版? ? 2021.6.24日晚上
總結
以上是生活随笔為你收集整理的c++拷贝、赋值和销毁的简单介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: error: conversion fr
- 下一篇: C++直接初始化与复制初始化的区别深入解