CPP類和對象的使用(2)

類和對象(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)
模板可以有層次,一個類模板可以作爲基類,派生出派生模板類。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章