Week2 Notes
A.三大函數(shù):拷貝構(gòu)造,拷貝賦值,析構(gòu)
string class這個不是標(biāo)準(zhǔn)庫里的string,標(biāo)準(zhǔn)庫里的太復(fù)雜了。
首先也要有防衛(wèi)式聲明。Ifndef define endif
測試代碼
int main(){
? ? strings1();
? ? string s2(“hello”);
? ? string s3(s1);//拷貝
? ? cout << s3 << endl;//操作符重載
? ? s3 = s2;//賦值動作
? ? cout << s3<< endl;
}
上面的賦值和拷貝的不同是第一個s3是第一次出現(xiàn),第二個s3是賦值。所以第一個是拷貝構(gòu)造,第二個叫拷貝賦值。如果你沒有寫,編譯器會自動給你一套,是一位一位地拷貝。那我們還要自己寫一份嗎?要考慮編譯器給的這一套夠不夠用,比如復(fù)數(shù)只要給實部虛部就夠用,但是這種帶指針的如果還使用編譯器給的拷貝構(gòu)造和拷貝賦值,就不夠用,深拷貝和淺拷貝????
Class string{
Public:
? ? String(constchar* cstr = 0);
? ? String(conststring& str);
? ? String&operator = (const string& str);
? ? ~string();
? ? char*get_C_str() const {return m_data};
private:
? ? char*m_data;
}
字符串的指針要寫在private里,我們要自己寫一套拷貝,第一個函數(shù)函數(shù)名稱和類相同,所以是構(gòu)造函數(shù),第二個函數(shù)名字也一樣,也是構(gòu)造函數(shù),但它的接收類型是自己,所以是拷貝構(gòu)造。第三個操作符=的輸入也是string,是拷貝賦值,注意,只要你的類帶著指針,你一定要寫出這兩個拷貝函數(shù)!!!!
第四個波浪線開頭的是析構(gòu)函數(shù)。當(dāng)它死亡的時候析構(gòu)函數(shù)就會被調(diào)用,新增加的234被成為big three,三個特殊函數(shù)。
第五個復(fù)習(xí)加的const是因為直接返回data不改變所以要加。
Ctor和dtor(構(gòu)造函數(shù)和析構(gòu)函數(shù))
一個字符串多長有兩種想法,一種是不知道長度但最后有結(jié)束符號,另一種是沒有結(jié)束符號,但多一個lenth,長度。C和c++用的是有結(jié)束符號的設(shè)計方法。
Inline
String::string(const char* cstr = 0){
If(cstr){
? ? M_data = new char[strlen(cstr) + 1];
? ? Strcpy(m_data, cstr);
}
else{
? ? m_data = new char[1];
? ? *m_data = ‘\0’;
}
}
inline
string::~string(){
? ? delete[]m_Data;
}
{
? ? stirngs1(),
? ? strings2(“hello”);
? ? string*p = new string(“hello”);
? ? delete p;
}
傳進(jìn)來先判斷是不是空字符串,如果是空的分配一個字符,為結(jié)束符,如果不為空,分配的空間大小是傳進(jìn)來的長度還要加上一個1,結(jié)束符號\0,分配完了之后,再用strcpy拷貝進(jìn)我們的m_data里。加入傳進(jìn)來的是hello的話,長度就是5加1,結(jié)束符號,面對字符串都要想到結(jié)束符號,新創(chuàng)建的對象就擁有內(nèi)容了。
對應(yīng)于構(gòu)造函數(shù),有下面的析構(gòu)函數(shù),做的事情是關(guān)門清理,clean up,在之前講過的復(fù)數(shù)時,不需要清理,如果你的class里有指針,多半要用動態(tài)分配,new,在使用動態(tài)分配的時候,我們要用析構(gòu)函數(shù)把動態(tài)分配的空間釋放掉,否則會發(fā)生內(nèi)存泄漏。
String *p = new string(“hello”);
Delete p;
動態(tài)分配后再調(diào)用。
在這個作用域里頭有三個字符串,離開的時候要調(diào)用三次析構(gòu)函數(shù)。
Class with pointer members必須有copy ctr和copy op=
因為如果不這么做,編譯器自己做的是一個位一個位的拷貝,對于b= a來說,a的內(nèi)容只有一個指針,如果不這么做,b = a就會把b里的指針也指向a指針指向的內(nèi)容,這對于兩個內(nèi)容都很危險,對于b的內(nèi)容來說,他指向的內(nèi)容還在可是沒有指針指向他,會發(fā)生內(nèi)存泄漏,memory leak.對于a來說,兩個指針指向他也很危險,一個改變了另一個也改變,所以這個叫淺拷貝,我們寫的函數(shù)是為了深拷貝。
Copy ctor拷貝構(gòu)造函數(shù)
Inline
String::string(const string& str){
? ? M_data= new char[strlen(str.m_data) + 1];
? ? Strcpy(m_Data,str.mdata);
}
{
? ? string s1(“hello”);
? ? string s2(s1);
}
在對m_data賦值的時候直接取另一個object的private,(兄弟之間互為friend)
在把指針拷過來的同時把內(nèi)容也拷過來,叫深拷貝,只把指針拷貝過來叫淺拷貝。
Copy assignment operator拷貝賦值函數(shù)
為了把右邊的東西賦值給左邊,本來兩邊都有東西,需要先把左邊清空,分配一塊和右邊一樣大的空間,再把右邊拷貝過來。
Inline
String& string::operator = (conststring& str){
? ? If(this== &str)
? ? ? ? Return*this;//檢測自我賦值
? ? Delete[]m_Data;
? ? M_Data= new char[strlen(str.m_Data) + 1]
? ? Strcpy(m_data,str.m_data);
? ? Return*this;
}
{
string s1(“hello”);
string s2(s1);
s2 = s1;
}
自我賦值的檢測判斷條件是兩個指針相比,自我賦值如果不寫,好像也不會出錯,只是效率高低嗎,不是,如果不寫自我賦值檢測,有可能會出錯。當(dāng)左右兩邊一樣的時候,賦值的第一步是殺掉左邊的m_data,在做第二個動作的時候string已經(jīng)不見了,會出錯。所以自我賦值不只是為了效率。
B.堆棧與內(nèi)存管理
Output函數(shù),是不是可以加在自己的rectangle類里?
#include
ostream& operator <<(ostream& os, const String str){
? ? os<< str.get_c_str();
? ? returnos;
}
stack和heap
函數(shù)本身會形成一個stack用來防止它接收到的參數(shù),以及返回地址。
在函數(shù)本體內(nèi)的聲明的任何變量,其所使用的內(nèi)存塊都取自上述stack。
System heap是指操作系統(tǒng)所提供的一塊global的內(nèi)存空間,程序可以動態(tài)分配從某種獲得若干區(qū)塊。可以在程序的任何地方動態(tài)獲得,并有責(zé)任區(qū)釋放他。
Class Complex();
{
? ? complexc1(c1,c2);
? ? complex*p= new complex(3);
}
C1叫做aotu object,stackobject;
Static complex c2(1, 2);
C2是static object,聲明在作用域結(jié)束之后依然存在,它的析構(gòu)函數(shù)不會再大括號結(jié)束的時候被調(diào)用,會在整個程序結(jié)束之后被調(diào)用。
還有一種對象叫全局對象,寫在任何大括號之外的叫做全局對象,global object,聲明在整個程序結(jié)束之后才消失,也可以視為一種static object..
{
? ? complex*p = new Complex;
}
上面的程序會發(fā)生內(nèi)存泄漏,在作用域結(jié)束后指針就死亡了,但指針指的對象還存在,就會發(fā)生memory leak;
new:先分配memory,再調(diào)用ctor;
complex *pc = new complex(1, 2);
編譯器會*pc;
void * mem = operator
new(sizeof(complex));//分配內(nèi)存,里面調(diào)用malloc
pc = static_cast(mem);
pc->complex::complex(1,2);
delete:先調(diào)用dtor,再釋放memory
在復(fù)數(shù)的時候我們沒有寫析構(gòu)函數(shù),寫了也是白寫,馬上就要死亡了。
String ps = new string(“hello”);
Delete ps;
String::~string(ps);//析構(gòu)函數(shù)
Operator delete(ps);//釋放內(nèi)存
使用malloc和free到底分配多少內(nèi)存。
Comple:兩個double是8個字節(jié),在調(diào)試模式下上面會多出32個字節(jié),下面會多出4個字節(jié),上下還有cookie,是4個字節(jié)。
一共有8+(32+4)+4*2 = 52
在vc下面分配的內(nèi)存塊一定是16字節(jié)的倍數(shù),所以分配64個字節(jié)。
在非調(diào)試模式底下,一個復(fù)數(shù)的大小要去掉灰色的,所以是8+4*2 = 16個字節(jié)。上下cookie是用來記錄整個的大小,因為在釋放的時候只給一個指針,要知道釋放的內(nèi)存大小cookie head記錄將大小的最后一位記為1.因為大小是16字節(jié)的倍數(shù),所以最后一位肯定是1.
String內(nèi)含一個指針,分配的內(nèi)存大小為:
4+(32+4)+(4*2) = 48是16的倍數(shù),在非調(diào)試模式下,大小為4+4*2 = 12,16
如果分配的是數(shù)組array會怎么樣?
Array new要搭配array delete不然會出錯。
Complex*p = new complex[3];
(8*3) + (32+4)+(4*2)+4 = 72
給80
非調(diào)試模式下,(8*3) + (4*2) + 4 = 36給48
如果array new沒有搭配array delete,會造成內(nèi)存泄漏。
String *p = new string[3];
Delete[] p;
寫不寫中括號都不影響指針這一整塊的刪除,因為分配的內(nèi)存的大小就記錄在cookie上,問題出在沒加中括號只調(diào)用了一次dtor,只有寫了中括號編譯器才知道你下面是數(shù)組,會喚起三次dtor,這三個dtor負(fù)責(zé)把各自動態(tài)分配的內(nèi)存殺掉。
C.復(fù)習(xí)String類的實現(xiàn)
動態(tài)分配的內(nèi)存塊。(memory block)
下面來寫一個字符串的class,編程實例:
class String{
public:
? ? String(constchar* cstr = 0);
? ? String(constString& str);
? ? String&operator = (const String& str);
? ? ~String();
? ? char*get_c_str() const {return m_data;}
private:
? ? char* m_data;
};
構(gòu)造函數(shù)和析構(gòu)函數(shù)
inline
String::String(const char* cstr = 0){
If(cstr){
? ? m_data= new char[strlen(cstr) + 1];
? ? strcpy(m_data,cstr);
}
else{//未設(shè)定初值
? ? m_data = new char[1];
? ? *m_data = ‘\0’;
}
}
inline
String::~String(){
? ? delete[]m_data;
}
由于上面是用array new,所以下面用array delete。
拷貝構(gòu)造函數(shù)copy cstr
inline
String::String(const String& str){
? ? m_data= new char[strlen(str.m_data) + 1];
? ? strcpy(m_data,str.m_data);
}
copy assignment operator拷貝賦值函數(shù)
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;
}
返回類型不是void,因為當(dāng)連續(xù)=時可能會出錯。
拷貝賦值要去檢驗是否是自我賦值,如果是自我賦值,就可以直接返回。如果沒有做這種檢查,不只是效率的問題,是正誤的問題。在這里const String& str中的&符號和this == &str有不同的意義,前面是傳引用,后面是取地址。前面是出現(xiàn)在typename的后面,叫引用,后面的是出現(xiàn)在object的前面,叫取地址,得到的是指針。
D.擴展補充,類模板,函數(shù)模板及其他
對象經(jīng)典有帶指針的和不帶指針的。有很多細(xì)節(jié)需要補充。
進(jìn)一步補充:static靜態(tài)
當(dāng)完成基于對象后要做面向?qū)ο螅诓煌念悆?nèi)做時要知道this pointer
不管是數(shù)據(jù)還是函數(shù)前面都可以加static
complex c1, c2, c3;
cout << c1.real();
cout << c2.real();
c1調(diào)用real函數(shù)從另一個角度看就是
complex c1, c2, c3;
cout << complex::real(&c1);
cout << complex::real(&c2);
在沒有導(dǎo)入static的時候,函數(shù)只有一份,但是它要來處理很多個對象,一定要有人告訴它你要處理誰,靠的就是this pointer,通過這個指針找到它要處理的對象在哪里。成員函數(shù)有一個this pointer但是我們不能寫進(jìn)去,這是編譯器自動會幫我們寫。
加了靜態(tài)static后它和對象就脫離了,他在內(nèi)存中有單獨一部分區(qū)域,靜態(tài)數(shù)。
靜態(tài)函數(shù)的身份和一般的成員函數(shù)一樣在內(nèi)存中只有一份。靜態(tài)的數(shù)據(jù)只有一份,什么時候會使用呢?比如在設(shè)計一個銀行的賬戶體系,有一百萬個人來開戶,我們需要設(shè)計一百萬個人的對象,但利率需要一個同一個東西,一百萬個人同一個利率,此時我們需要將利率設(shè)為靜態(tài),在內(nèi)存中只有一份,靜態(tài)函數(shù)的特征和一般成員函數(shù)不同在它沒有this pointer,這樣它不能去訪問去處理,那它有什么用,靜態(tài)函數(shù)要處理數(shù)據(jù)的話它只能處理靜態(tài)的數(shù)據(jù)。例子:
class account{
public:
? ? staticdouble m_rate;
? ? staticvoid set_rate(const double& x) {m_rate = x;}
};
double account::m_rate = 8.0;
int main(){
? ? account::set_rate(5.0);
? ? accounta;
? ? a.set_rate(7.0);
}
靜態(tài)的數(shù)據(jù)一般在類外面加定義,要不要給初值?都可以。
靜態(tài)函數(shù)只能處理靜態(tài)的數(shù)據(jù),調(diào)用static函數(shù)的方式有兩種,一種是通過object調(diào)用,第二種是通過class name來調(diào)用。
進(jìn)一步補充:把ctors放在private區(qū)
Singleton
Class A{
Public:
StaticA& getInstance{return a;}
Setup(){…}
Private:
? ? A();
? ? A(const A&rhs);
? ? Static A a;
}
A::getInstance().setup();
這個寫法還不是最完美,雖然寫好了a,但當(dāng)外界都不需要用到,a仍然存在,更好的寫法:
Meyers Singleton
class A{
public:
? ? staticA& get Instance();
? ? setup(){…}
private:
? ? A();
? ? A(constA& rhs);
…
};
A& A::getInstance(){
? ? staticA a;
? ? returna;
}
只有調(diào)用過getInstance后單例才開始存在。
進(jìn)一步補充:cout
重載了各種數(shù)據(jù)的<<操作符
進(jìn)一步補充:class template,類模板
template
把double都換成T,使用時
complex c1(2.5,1.5);
complex c2(2, 6);
模板可能會造成代碼的膨脹,以上會產(chǎn)生兩份相似的代碼,但這個是必要的。
進(jìn)一步補充:function template函數(shù)模板
stone r1(2, 3), r2(3, 3), r3;
r3 = min(r1, r2);
template
inline
const T& min(const T& a, constT& b){
? ? returnb < a? b:a;
}
class stone{
public:
stone(intw, int h, int we)
:_w(w), _h(h), _weight(we) {}
booloperator < (const stone& rhs) const
{return_weight < rhs.weight;}
private:
int_w, _h, _weight;
};
進(jìn)一步補充:namespace
namespace std{
…
}
using directive
#include
using namespace std;
int main(){
? ? cin<< …;
? ? cout<< …;
? ? return0;
}
using declaration
using std::cout;
int main(){
? ? std::cin<< …;
? ? cout<< …;
? ? return0;
}
#include
int main(){
? ? std::cin<< …;
? ? std::cout<< …;
? ? return 0;
}
在這周的作業(yè)中我遇到了一個類公有繼承了另一個類,并且在子類中含有一個指針指向一個類,在寫這個類的拷貝構(gòu)造函數(shù)時,需要將輸入對象中指針下面的值賦給this對象中的指針,這里由于需要將輸入對象leftup指針中的值取出,我在類point中寫了兩個取數(shù)據(jù)的公有方法叫g(shù)etX和getY,對于賦值,我采用的方法是借用構(gòu)造函數(shù)對新對象中的數(shù)據(jù)進(jìn)行賦值。另一方面,對于類rectangle的父類中no的處理,我的理解是no用于記錄每一個生成的shape的編號,這樣就需要一個靜態(tài)的全局?jǐn)?shù)據(jù)count用于記錄一共擁有的shape數(shù),并在每生成一個新的shape時將對應(yīng)的編號分給no,這個過程我在shape的構(gòu)造函數(shù)中實現(xiàn),對應(yīng)的,在shape的析構(gòu)函數(shù)中,我將count值減一,保證shape總數(shù)和現(xiàn)有的shape對象個數(shù)相同。在測試中,我先測試了拷貝構(gòu)造和拷貝賦值的正常使用另外在刪除其中一個對象后,再新生成一個rectangle,它對應(yīng)的no應(yīng)該是正確的。