類中6個預設的函式
在C++中,系統給了6個預設的函式:建構函式、解構函式、拷貝建構函式u、賦值運算子的過載函式、取地址操作符的過載函式和const修飾的取地址操作符的過載函式。
1、建構函式
建構函式的函式名與類名相同,比如class Goods{};它的預設建構函式就是Goods(),沒有返回值。
建構函式時系統呼叫的,如果自己在類中寫了,系統就不會提供,系統提供的建構函式都是共有的(public)、內聯的。
建構函式的作用就是,生成新物件的時候對“物件”進行初始化用的。
物件的生成有兩個步驟:1、開闢記憶體。2、記憶體空間進行初始化。建構函式就記憶體空間是在初始化的時候系統呼叫的。所以建構函式不能手動呼叫。
#include<iostream> #pragma warning(disable:4996); class Goods { public: Goods() { std::cout<<"Goods::Goods()"<<std::endl; } Goods(int amount) { std::cout<<"Goods::Goods(int )"<<std::endl; mname = new char[1](); mamount = amount; } Goods(char *name,float price) { std::cout<<"Goods::Goods(char ,float )"<<std::endl; mname = new char[strlen(name)+1](); strcpy(mname,name); mprice = price; } Goods(char *name,float price,int amount) { std::cout<<"Goods::Goods(char ,float ,int )"<<std::endl; mname = new char[strlen(name)+1](); strcpy(mname,name); mprice = price; mamount = amount; } private: char *mname; float mprice; int mamount; }; int main() { Goods goods1; Goods goods2("goods2",10.5); Goods goods3(20); Goods goods4("goods4",10.5,20); return 0; }
從上面的程式碼可以看出,一個類中可以有多個建構函式,這也就是說,建構函式是可以過載的。一個物件的生成可以有不同的方式,比如上面的goods1到goods4每一個物件都是呼叫不同的建構函式生成的。
2、解構函式
解構函式的函式名是在類名前加~,比如class Goods{};它的預設解構函式就是~Goods(),沒有返回值。
我們通過觀察上面的程式碼就會發現,上面每一個建構函式都會申請了堆上的資源,但是最後並沒有釋放,是不是造成了記憶體洩露?沒存,在類中每一個物件都有它自己的資源,在這個物件生存週期結束要銷燬的時候系統就會將物件的資源釋放掉,但是它是如何釋放的呢,這就是我們要說的析構函數了。
解構函式就是專門用來釋放資源的,需要注意的是,它不可以過載,但是可以手動呼叫。手動呼叫時一定要注意自己寫的程式碼有沒有使用堆記憶體,如果使用了堆記憶體,手動呼叫之後,物件的生存週期並不會縮短,在物件生存週期結束的時候,系統還會呼叫一次。就會造成重複釋放資源。
#include<iostream>
#pragma warning(disable:4996)
class Goods
{
public:
Goods()
{
std::cout<<"Goods::Goods()"<<std::endl;
mname = new char[1]();
}
Goods(int amount)
{
std::cout<<"Goods::Goods(int )"<<std::endl;
mname = new char[1]();
mamount = amount;
}
Goods(char *name,float price)
{
std::cout<<"Goods::Goods(char ,float )"<<std::endl;
mname = new char[strlen(name)+1]();
strcpy(mname,name);
mprice = price;
}
Goods(char *name,float price,int amount)
{
std::cout<<"Goods::Goods(char ,float ,int )"<<std::endl;
mname = new char[strlen(name)+1]();
strcpy(mname,name);
mprice = price;
mamount = amount;
}
~Goods()
{
std::cout<<"Goods::~Goods()"<<std::endl;
delete[] mname;
}
private:
char *mname;
float mprice;
int mamount;
};
int main()
{
Goods goods1;
// goods1.~Goods();
// std::cout<<"------------------"<<std::endl;
// goods1.shell();
Goods goods2("goods2",10.5);
Goods goods3(20);
Goods goods4("goods4",10.5,20);
return 0;
}
我們可以看出,建立了幾個物件,最後就呼叫了幾個解構函式。最後要說的一點就是:先構造的物件後析構,後構造的物件先析構。因為物件的資訊是在棧上儲存的,先構造的先壓棧後構造的後壓棧。
3、拷貝建構函式。
拷貝建構函式的函式名與建構函式一樣,它的作用是用一個已存在的物件來生成一個新的物件。比如class Goods{};它的預設建構函式就是Goods(const Goods& rhs),沒有返回值。
如果程式設計師在程式碼中自己沒有寫拷貝建構函式,系統也會提供預設的拷貝建構函式。但是系統提供的拷貝建構函式,它只能進行淺拷貝,不會對堆區資源進行復制。所以在類中,如果沒有用到堆區資源的時候可以使用預設的拷貝建構函式,如果使用了堆區資源,建議自己寫拷貝建構函式,防止淺拷貝的發生。
#include<iostream>
#pragma warning(disable:4996)
class Goods
{
public:
Goods()
{
std::cout<<"Goods::Goods()"<<std::endl;
mname = new char[1]();
}
Goods(int amount)
{
std::cout<<"Goods::Goods(int )"<<std::endl;
mname = new char[1]();
mamount = amount;
}
Goods(char *name,float price)
{
std::cout<<"Goods::Goods(char ,float )"<<std::endl;
mname = new char[strlen(name)+1]();
strcpy(mname,name);
mprice = price;
}
Goods(char *name,float price,int amount)
{
std::cout<<"Goods::Goods(char ,float ,int )"<<std::endl;
mname = new char[strlen(name)+1]();
strcpy(mname,name);
mprice = price;
mamount = amount;
}
Goods(const Goods& rhs)
{
std::cout<<"Goods::Goods(const Goods& )"<<std::endl;
mname = new char[strlen(rhs.mname)+1];
strcpy(mname,rhs.mname);
mprice = rhs.mprice;
mamount = rhs.mamount;
}
~Goods()
{
std::cout<<"Goods::~Goods()"<<std::endl;
delete[] mname;
}
private:
char *mname;
float mprice;
int mamount;
};
int main()
{
Goods goods4("goods4",10.5,20);
Goods goods5(goods4);
return 0;
}
我們可以看到,這段程式碼先是呼叫了三個引數的建構函式生成了goods4然後再呼叫拷貝建構函式用goods4生成goods5。拷貝建構函式的形參使用了,const Goods& rhs,而不使用Goods rhs或者Goods *rhs是有原因的。
形參如果用Goods rhs 來傳遞,在實參往形參傳遞的過程中又是一個用已存在物件來生成新物件的過程,又得呼叫拷貝建構函式,如此遞迴下去,從而導致棧溢位。
形參如果用Goods *rhs來傳遞實參與形參的值,那不就成了建構函式,而不是拷貝建構函式。
4、賦值運算子的過載函式
我們就以Goods類為例。賦值運算子的過載函式的函式原型為Goods& operator=(const Goods&rhs)
賦值運算子的過載函式的作用是:把一個已生成的物件賦值給相同型別的已存在物件。
實現賦值運算子的過載函式需要實現以下4個需求:
(1)、自賦值問題。賦值時如果發現是自己給自己賦值,就沒有必要進行復制,直接返回原物件。
(2)、釋放舊資源。一定要把已存在目的物件所佔用資源釋放掉,如果不釋放,就會造成記憶體洩露問題。
(3)、申請新資源。如果原物件使用了堆資源,一定也要給目的物件申請同樣的對資源,將原物件的堆資源複製給目的物件,否則就會出現淺拷貝問題。
(4)、賦值。最後一步才是將原物件的棧資源賦值給目的物件。
#include<iostream>
#pragma warning(disable:4996)
class Goods
{
public:
Goods()
{
std::cout<<"Goods::Goods()"<<std::endl;
mname = new char[1]();
}
Goods(int amount)
{
std::cout<<"Goods::Goods(int )"<<std::endl;
mname = new char[1]();
mamount = amount;
}
Goods(char *name,float price)
{
std::cout<<"Goods::Goods(char ,float )"<<std::endl;
mname = new char[strlen(name)+1]();
strcpy(mname,name);
mprice = price;
}
Goods(char *name,float price,int amount)
{
std::cout<<"Goods::Goods(char ,float ,int )"<<std::endl;
mname = new char[strlen(name)+1]();
strcpy(mname,name);
mprice = price;
mamount = amount;
}
Goods(const Goods& rhs)
{
std::cout<<"Goods::Goods(const Goods& )"<<std::endl;
mname = new char[strlen(rhs.mname)+1];
strcpy(mname,rhs.mname);
mprice = rhs.mprice;
mamount = rhs.mamount;
}
Goods& operator=(const Goods& rhs)
{
std::cout<<"Goods::Goods& operator=(const Goods& )"<<std::endl;
if(this == &rhs)
{
return *this;
}
delete[] mname;
mname = new char[strlen(rhs.mname)+1];
strcpy(mname,rhs.mname);
mprice = rhs.mprice;
mamount = rhs.mamount;
return *this;
}
~Goods()
{
std::cout<<"Goods::~Goods()"<<std::endl;
delete[] mname;
}
void shell()
{
std::cout<<"shell"<<std::endl;
}
private:
char *mname;
float mprice;
int mamount;
};
int main()
{
Goods goods1;
Goods goods4("goods4",10.5,20);
goods1 = goods4;
return 0;
}
我們可以看到,賦值運算子的過載函式在傳遞引數的時候也用了const.原因有兩個:1、防止修改了實參的值。在賦值的時候不能把原物件的資料做了更改。2、接收隱式生成的臨時變數。如果賦值運算子有變是一個隱式生成的臨時物件,隱式生成的臨時物件是一個常量,常量的引用必須用const來修飾。
最後要提的一點就是臨時量問題。
(1)、內建型別的臨時量都是常量。
(2)、自定義型別的臨時量是變數。
(3)、隱式生成物件的臨時量是常量。
(4)、顯示生成物件的臨時量是變數。