類和對象(2)
對象的賦值(=)
類型相同的對象可以相互賦值,相當於類中的數據成員相互賦值(memberwise copy)。
這種賦值與對象數據成員的訪問權限無關。
demo
class A{
int x;
public:
A(int x):x(x){}
void f1(){x=-x;}
void f2(){cout<<x<<endl;}
};
int main()
{
A a1(1);
A a2 = a1;
a1.f2();
a2.f2();
a2.f1();
a1.f2();
a2.f2();
return 0;
}
a2的改變,不會影響a1,因爲它們是兩個對象。
1
1
1
-1
再來看看java裏面
public class A {
int x;
public A(int x)
{
this.x = x;
}
public void f1()
{
this.x = -x;
}
public void f2()
{
System.out.println(x);
}
public static void main(String[] args)
{
A a1 = new A(1);
A a2 = a1;
a1.f2();
a2.f2();
a2.f1();
a1.f2();
a2.f2();
System.out.println(a1.toString());
System.out.println(a2.toString());
}
}
java裏面,對象賦值僅僅是引用的拷貝,
1
1
-1
-1
A@4b1210ee
A@4b1210ee
如果要在c++實現這樣的功能,這樣寫即可。這時,a2就是a1的別名,控制同一塊內存。
A &a2 = a1;
複製構造函數
在定義一個對象的時候,用一個已存在的同類型對象去初始化這個新對象,即構造函數的參數是另一個同類型對象的引用,這種構造函數稱爲複製構造函數。
如果沒有定義複製構造函數,編譯器自動生成一個缺省的複製構造函數,用形參對象的每個數據成員的值,初始化將要建立的對象的對應的數據成員。
隱含的複製構造函數
//這裏的函數形參是 對象的常引用
A::A(const A &a)
{
x = a.x;
y = a.y;
}
demo
class A{
int x,y;
public:
A(int x,int y):x(x),y(y){cout<<"調用構造函數"<<endl;}
void show(){cout<<"x "<<x<<" y "<<y<<endl;}
~A(){cout<<"調用析構函數"<<endl;}
A(const A &a){x=a.x;y=a.y;cout<<"調用複製構造函數"<<endl;}
};
int main()
{
A a1(1,2);
A a2 = a1; //C++還提供另一種方便的複製形式,用賦值號代替括號
A a3(a1);
a1.show();
a2.show();
a3.show();
cout<<"主函數結束"<<endl;
return 0;
}
調用構造函數
調用複製構造函數
調用複製構造函數
x 1 y 2
x 1 y 2
x 1 y 2
主函數結束
調用析構函數
調用析構函數
調用析構函數
注意:
①②是不一樣的,①是調用複製構造函數的簡便寫法
②是先創建a2對象,再用賦值語句=,這沒有調用複製構造函數
A a1;
①
A a2 = a1;
②
A a2;
a2 = a1;
+如果不希望對象被複制構造
A(const A &a) =delete ;
//指示編譯器不生成默認複製構造函數
- 淺複製與深複製
shallow copy
class A{
char* str;
int len;
public:
A(const char* s){
if(s){
len = (int)strlen(s);
str = new char[len+1];
strcpy(str, s);
}
else str = 0;
}
~A(){
if(str)
delete []str;
}
};
int main()
{
A s("hello");
A s2(s);
return 0;
}
結果報錯。爲何?
編譯器自生成的複製構造函數是:
A::A(const A &a)
{
len = a.len;
str = a.str;
//這裏新建對象的str和a.str指向同一空間,但是最後析構函數卻嘗試對同一內存空間釋放兩次!!
}
- 顯示定義複製構造函數
class A{
char* str;
int len;
public:
A(const char* s){
if(s){
len = (int)strlen(s);
str = new char[len+1];
strcpy(str, s);
}
else str = 0;
}
~A(){
if(str){
cout<<"調用析構函數"<<endl;
delete []str;
}
}
A(const A &a);
};
A::A(const A &a)
{
if(a.str)
{
len = a.len;
str = new char[len+1];
strcpy(str, a.str);
}
else str = 0;
}
int main()
{
A s("hello");
A s2(s);
return 0;
}
-
複製構造函數被調用的三種情況
- 定義一個對象時,以本類另一個對象作爲初始值,發生複製構造;
調用函數時需要將實參對象完整地傳遞給形參,就是按實參複製一個形參,系統通過調用複製構造函數,保證形參具有和實參完全相同的值。
- 定義一個對象時,以本類另一個對象作爲初始值,發生複製構造;
第二種情況
- 如果函數的形參是類的對象,虛實結合時,將使用實參對象初始化形參對象,發生複製構造;
void fun(A a)
{
}
int main()
{
A a;
fun(a);
return 0;
}
第三種情況
+ 如果函數的返回值是類的對象,函數執行完成返回主調函數時,將使用return語句中的對象初始化一個臨時無名對象,傳遞給主調函數,此時發生複製構造。
A f()
{
A a;
……
return a;
}
int main()
{
A a;
a = f();
}
不過,以上幾種調用複製構造函數都是由編譯系統自動實現的,用戶自己不用調用。
- 返回對象引用和返回對象的區別
①返回對象
A f()
{
A a;
……
return a; //按a來複制構造一個無名的臨時對象,然後析構a
}
A a1 = f(); //由臨時對象複製構造a,這條賦值語句結束後,再析構臨時對象。
②返回引用
返回引用可以節省時間和內存,返回引用與按引用傳遞對象類似,調用和被調用的函數對同一個對象進行操作。
但注意:
函數不能返回在函數中創建的臨時對象的引用,因爲當函數結束時,臨時對象將消失,這種引用是非法的。在這種情況下,應返回對象。
總結:
C++編譯器爲一個類產生的四個缺省函數
缺省的構造函數、析構函數、賦值運算符(函數) 、複製構造函數
靜態成員(Static)成員
靜態成員數據
成員數據用關鍵字static
聲明:
-
說明該數據爲該類的所有對象共享。靜態數據成員的值對所有對象都是一樣的。如果改變它的值,則各對象中這個數據成員的值都同時改變了。
-
靜態數據成員具有靜態生存期,在內存中只佔一份空間。靜態數據成員是在所有對象之外單獨開闢空間。只要在類中定義了靜態數據成員,即使不定義對象,也爲靜態數據成員分配空間,可以被引用。
-
靜態數據成員是在程序開始時分配空間,到程序結束時才釋放空間。
-
初始化方式
如果未對靜態數據成員賦初值,則編譯系統會自動賦予初值。
在類內聲明,在類外定義。
class A{
public:
static int x; //聲明但爲定義,尚未分配空間。
int y;
};
int A::x;
int main(){
// cout<<A.x<<endl;
cout<<A::x<<endl;
A a;
cout<<a.x<<endl;
cout<<a.y<<endl;
return 0;
}
0
40997
數據類型 類名∷靜態數據成員名=初值;
int A::x = 10;
- 引用方式
A a;
A::x
或者,a.x
靜態成員函數
- 成員函數也可以定義爲靜態的,在類中聲明函數的前面加static就成了靜態成員函數。如static int volume( );
- 和靜態數據成員一樣,靜態成員函數是類的一部分,而不是對象的一部分。
- 調用方式
A::f()
A a;
a.f(); //但這並不意味着此函數是屬於對象a的。
- 靜態成員函數沒有this指針。(也不需要)
- 靜態成員函數不能訪問本類中的非靜態成員,可以使用本類中的靜態數據成員。
類的友元(friend)
-
友元函數
如果在本類以外的其他地方定義了一個函數(這個函數可以是不屬於任何類的非成員函數,也可以是其他類的成員函數),在類體中用friend對其進行聲明,此函數就稱爲該類的友元函數。
友元函數在它的函數體中能夠通過對象名訪問private和protected成員,當然也可以訪問public的成員。
作用:增加靈活性 -
友元類
demo
class A{
private:
int x;
};
void display(A &a)
{
a.x;
}
正常情況下不讓用
- 不屬於任何類的成員的友元函數
class A{
private:
int x;
public:
friend void display(A &a);
};
void display(A &a)
{
a.x;
}
- 類的成員函數作爲友元函數
class B;
class A{
private:
int x;
public:
void fa(B &);
};
class B{
private:
int bx;
public:
friend void A::fa(B&);
};
void A::fa(B&b)
{
cout<<b.bx;
}
- 前向引用聲明
class B;
class A{
public:
B *b;
};
class B;
class A{
public:
B &b;
};
下面的寫法不合法。此時對象b還不能夠創建出來。
- 友元類
class A{
};
class B{
friend A;
//A是B的友元,B的所有的函數都可以使用A裏面的所有成員數據
};
(1) 友元的關係是單向的而不是雙向的。
(2)友元的關係不能傳遞。
類模板
有兩個或多個類,其功能是相同的,僅僅是數據類型不同,如下面的類:
class CompareInt{
public:
int x,y;
CompareInt(int x,int y):x(x),y(y){}
int max(){return x>y?x:y;}
};
class CompareDouble{
public:
double x,y;
CompareDouble(double x,double y):x(x),y(y){}
double max(){return x>y?x:y;}
};
C++增加了模板(template)的功能,可以聲明一個通用的類模板,有一個或多個虛擬的類型參數。
加上這行即可
template<class typenum>
demo:
template<class typenum>
class CompareNum{
public:
typenum x,y;
CompareNum(typenum x,typenum y):x(x),y(y){}
typenum max(){return x>y?x:y;}
};
類模板定義對象
A<int> a;
A<char> b;
-
在函數外定義成員函數
第一行不可少!
template<class typenum>
typenum CompareNum<typenum>::max(){
return x>y?x:y;
}
- 類模板的類型參數可以有一個或多個,每個類型前面都必須加class, 如
template<class T1,class T2,class T3>
class A{
……
};
A a(int, double, long long)
模板可以有層次,一個類模板可以作爲基類,派生出派生模板類。