7.Big Three:拷貝構造,拷貝賦值,析構
(1)什么時候需要自己寫拷貝構造和拷貝賦值函數
當編譯器提供的默認拷貝構造和拷貝賦值函數不再滿足要求的時候,比方說類里面帶指針,必須自己寫拷貝構造和拷貝賦值函數;
String(constString&?str);
String&?operator=(constString&?str);
如果不這么做,會怎么樣?如圖1所示,使用默認的拷貝構造和拷貝賦值函數,是一種淺拷貝,只會把指針拷貝過來,會造成內存泄漏,同時兩個指針指向同一個地方,以后改動任何一個變量,會造成另外一個的改變。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 1
(2)怎么寫拷貝構造和拷貝賦值函數
拷貝構造:①創造自己;②拷貝
拷貝構造函數,顧名思義就是拷貝和構造。拷貝構造函數,是一種特殊的構造函數,它由編譯器調用來完成一些基于同一類的其他對象的構建及初始化。其唯一的參數(對象的引用)是不可變的(const類型)。
如果一個類中沒有定義拷貝構造函數,那么編譯器會自動產生一個默認的拷貝構造函數,這個默認的參數可能為X::X(const?X&)或X::X(X&),
由編譯器根據上下文決定選擇哪一個,默認拷貝構造函數的行為如下:默認的拷貝構造函數執行的順序與其他用戶定義的構造函數相同
,執行先父類后子類的構造.拷貝構造函數對類中每一個數據成員執行成員拷貝(memberwise?Copy)的動作.
a)如果數據成員為某一個類的實例,那么調用此類的拷貝構造函數.
b)如果數據成員是一個數組,對數組的每一個執行按位拷貝.
c)如果數據成員是一個數量,如int,double,那么調用系統內建的賦值運算符對其進行賦值
舉例:
inline
String::String(constString&?str)
{
m_data?=newchar[strlen(str.m_data)?+?1];
strcpy(m_data,?str.m_data);
}
拷貝賦值:①delete自己;②重新創造自己;③拷貝
拷貝賦值函數參數跟拷貝構造函數相同,兩者的區別在于:構造函數是對象創建并用另一個已經存在的對象來初始化它。
賦值函數只能把一個對象賦值給另一個已經存在的對象。
注意:考慮自我拷貝的情況
舉例:
inline
String&?String::operator?=(constString&?str)
{
if(this==?&str)
return*this;
delete[]?m_data;
m_data?=newchar[strlen(str.m_data)?+?1];
strcpy(m_data,?str.m_data);
return*this;
}
(3)如果class里面有指針,多半要做動態分配
做了動態分配,則在創建的對象死亡之前析構函數會被調用起來;
Stack(堆),是存在于某作用域(scope)的一塊內存空間(memory space)。例如當你調用函數,函數本身會形成一個Stack用來放置它所接受的參數,以及返回地址。
在函數本體(function body)內聲明的任何變量(local object),其所使用的內存塊都取自上述Stack。
Heap(堆),或者說system heap,是指由操作系統提供的一塊global內存空間,程序可動態分配從某種獲得若干區塊。
(1)stack objects的生命期
classComplex{...}
...
{
Complex?c1(1,2);
}
c1便是所謂的Stack object,其生命在作用于(scope)結束之際結束。這種作用域內的object,又稱為auto object,因為它會被自動清理;
(2)static local objects的生命期
classComplex?{...}
...
{
staticComplex?c2(1,2);
}
c2便是static object,其生命在作用域(scope)結束之后仍然存在,直到整個程序結束。
(3)global objects的生命期
classComplex?{...}
...
Complex?c3(1,2);
intmain()
{
...
}
c3便是所謂global object,其生命在整個程序結束之后才結束。也可以把它視為一種static object,其作用域是整個程序。
(4)heap objects的生命期
classComplex?{...}
...
{
Complex*?p=newComplex;
...
deletep;
}
p指的便是heap object,其生命在它被delete掉之后結束。如果沒有delete p;會出現內存泄漏(memory leak),因為當作業域結束,p所致的heap object仍然存在,但指針p的生命卻結束了,作用域之外再也看不到p(也就沒有機會delete p)。
(5)new:先分配memory,再調用ctor
絕大部分編譯器對調用new,轉化為三步,詳見圖2
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 2
(6)delete:先調用dtor,再釋放memory
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖 3
(7)動態分配所得的array
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 4
(8)array new一定要搭配array delete
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?圖 5
(1)static
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖 6
類complex成員函數只有一份(不可能創建了幾個對象就有幾個函數),但是要處理很多對象,那就得靠this pointer來處理不同的對象。
而static的部分就和對象脫離了,它存在于內存的一部分,只有一份。
什么時候會使用static對象呢?就是和對象無關的部分。
staic成員函數和一般成員函數的特征就是static成員函數沒有this pointer,既然沒有this pointer,那static 成員函數不能和一般的函數一樣去訪問處理non-static data members,那只能處理static members
例如:
classAccount
{
public:
staticdoublem_rate;
staticvoidset_rate(constdouble&?x)?{m_data?=?x?};
};
doubleAccount::m_rate?=?8.0;//靜態數據必須要這樣定義,因為脫離對象,
intmain()
{
Account::set_rate(5.0);
Account?a;
a.set_rate(7.0);
}
調用static函數有兩種方法:
①通過object調用;
②通過class name 調用;
(2)Singleton模式(把ctors放在private區)
classA
{
public:
staticA&?getInstance();
setup()?{...}
private:
A();
A(constA&?rhs);
staticA?a;
...
};
A&?A::getInstance()
{
returna;
}
a本來就存在,和對象無關,然后不想其他人創建,那就把構造函數放在private里,那怎么取得a呢,就用個static A& getInstance()取得a,這是與外界的接口。但這不是最好的寫法,因為不管你用不用,a都存在。所以更好的寫法如下:
classA
{
public:
staticA&?getInstance();
setup()?{...}
private:
A();
A(constA&?rhs);
...
};
A&?A::getInstance()
{
staticA?a;//只有當有人掉用這個函數,a才會存在
returna;
}
(3)cout
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 圖 7
可以看出cout就是一種ostream,實際上是重載了<<運算符的函數,用于打印不同類型的數。
(4)class template,類模板
template
classcomplex
{
public:
complex?(T?r?=?0,?T?i?=?0)
:?re?(r),?im(i)
{}
complex&?operator?+=?(constcomplex&);
T?real()const{returnre;?}
T?imag()const{returnim;?}
private:
T?re,?im;
friendcomplex&?_doapl?(complex*,constcomplex&);
};
//調用如下
{
complex?c1(2.5,1.5);
complex?c2(2,6);
}
(5)function template函數模板
classstone
{
public:
stone(intw,inth,intwe)
:?_w(w),?_h(h),?_weight(we)
{}
booloperator?<?(conststone&?rhs)const
{return_weight?<?rhs._weight;?}
private:
int_w,?_h,?_weight;
};
template
inlineconstT&?min(constT&?a,constT&?b)
{
returnb?<?a???b?:?a;
}
//使用
stone?r1(2,3),?r2(3,3),?r3;
r3?=?min(r1,r2);//則會調用min函數,函數里面會接著調用stone::operator<函數
(6)namespace
以防和別人寫的東西重名。
使用方法有兩種:
①using directive
usingnamespacestd;//把namspace空間的東西全打開
cin?>>?i;
cout?<<"hello"<<?endl;
②using declaration
usingstd::cout;
std::cin?>>?i;
cout?<<"hello"<<?endl;
不要在頭文件中使用using namespace std;,容易造成命名空間污染;