目錄
8、拷貝構造函數
拷貝構造函數:只有單個形參,該形參是對本類類型對象的引用(一般常用const修飾),在用已存在的類類型對象
創建新對象時由編譯器自動調用。
8.1、特徵
拷貝構造函數也是特殊的成員函數,其特徵如下:
1. 拷貝構造函數是構造函數的一個重載形式。
2. 拷貝構造函數的參數只有一個且必須使用引用傳參,使用傳值方式會引發無窮遞歸調用。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);
return 0;
}
3. 若未顯示定義,系統生成默認的拷貝構造函數。 默認的拷貝構造函數對象以內存存儲按字節序完成拷 貝,這種拷貝我們叫做 淺拷貝,或者值拷貝。
8.2、淺拷貝構造無法解決類當中有堆空間的拷貝
#include <iostream>
#include <stdlib.h>
using namespace std;
class String
{
public:
String(const char* str = "jack")
{
_str = (char*)malloc(strlen(str) + 1);
strcpy(_str, str);
}
~String()
{
cout << "~String()" << endl;
free(_str);
}
private:
char* _str;
};
int main()
{
String s1("hello");
String s2(s1);
system("pause");
return 0;
}
前面說過淺拷貝是按字節序拷貝的,以上程序在析構的時候會發生錯誤,調用s2的析構函數時free函數報錯,因爲s2對象根本沒有開闢過空間只是對s1的值拷貝,所以無法執行free。以上程序就需要深拷貝。
9、賦值運算符重載
9.1、運算符重載
C++爲了增強代碼的可讀性引入了運算符重載,運算符重載是具有特殊函數名的函數,也具有其返回值類 型,函數名字以及參數列表,其返回值類型與參數列表與普通的函數類似。
函數名字爲:關鍵字operator後面接需要重載的運算符符號。
函數原型:返回值類型 operator操作符(參數列表)
注意:
- 不能通過連接其他符號來創建新的操作符:比如operator@
- 重載操作符必須有一個類類型或者枚舉類型的操作數
- 用於內置類型的操作符,其含義不能改變,例如:內置的整型+,不 能改變其含義
- 作爲類成員的重載函數時,其形參看起來比操作數數目少1的成員函數
- 操作符有一個默認的形參this,限定爲第一個形參
- .* 、:: 、sizeof 、?: 、. 注意以上5個運算符不能重載。這個經常在筆試選擇題中出現。
#include <iostream>
using namespace std;
// 全局的operator==
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
/* 左操作數省略的情況下,運算符重載應該在類的內部 */
bool operator==(const Date& d2)/*左操作數是this,指向的調用函數的對象 */
{
return _year == d2._year&&_month == d2._month&&_day == d2._day;
}
//private:
int _year;
int _month;
int _day;
};
// 這裏會發現運算符重載成全局的就需要成員變量是共有的,那麼問題來了,封裝性如何保證?
// 這裏其實可以用我們後面學習的友元解決,或者乾脆重載成成員函數。
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
void Test()
{
Date d1(2018, 9, 27);
Date d2(2018, 9, 27);
cout << (d1 == d2) << endl;
cout << d1.operator==(d2);
}
int maind()
{
Test();
system("pause");
return 0;
}
9.2、賦值運算符重載
賦值運算符重載主要有以下五個注意點
1. 參數類型:本類對象的引用
2. 返回值:本類對象的引用
3. 檢測是否自己給自己賦值
4. 返回*this
5. 一個類如果沒有顯式定義賦值運算符重載,編譯器也會生成一個,完成對象按字節序的值拷貝。
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date& operator=(const Date& d)/* 運算符重載參數也是引用 */
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(2018, 10,1);
// 這裏d1調用的編譯器生成operator=完成拷貝,d2和d1的值也是一樣的。
d1 = d2;
system("pause");
return 0;
}
10、const成員
https://blog.csdn.net/weixin_43447989/article/details/100548283
至此類的六大默認函數全部總結完畢,接下來我們來看類的一些細節玩法。
1、構造函數體賦值和初始化列表
1.1、構造函數體賦值
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
顧名思義構造函數體賦值就是用參數和=的方式給類的變量賦初值的行爲,和它類似的行爲有初始化列表
1.2初始化列表
初始化列表:以一個冒號開始,接着是一個以逗號分隔的數據成員列表,每個"成員變量"後面跟一個放在括號中的初始值或表達式。
注意:
1. 每個成員變量在初始化列表中只能出現一次(初始化只能初始化一次)
2. 類中包含以下成員,必須放在初始化列表位置進行初始化:
引用成員變量
const成員變量
類類型成員(該類沒有默認構造函數)
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
class B
{
public:
B(int a, int ref)
:_aobj(a)
,_ref(ref)
,_n(10)
{}
private:
A _aobj; // 沒有默認構造函數
int& _ref; // 引用
const int _n; // const
};
2、static成員
https://blog.csdn.net/weixin_43447989/article/details/100548859
3、友元
友元分爲友元函數和友元類,友元提供了一種突破封裝的方式,有時提供了便利。但是友元會增加耦合度,破壞了封裝,所以友元不宜多用。除了約定俗成的方式下,一般不用,是普通的外部函數,和類沒有關係。
3.1友元函數。
問題:現在我們嘗試去重載operator<<,然後發現我們沒辦法將operator<<重載成成員函數。因爲cout的輸出流對象和隱含的this指針在搶佔第一個參數的位置。this指針默認是第一個參數也就是左操作數了。但是實際使用中cout需要是第一個形參對象,才能正常使用。所以我們要將operator<<重載成全局函數。但是這樣的話,又會導致類外沒辦法訪問成員,那麼這裏就需要友元來解決。
class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, const Date& d);
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
prvate:
int _year;
int _month;
int _day
};
ostream& operator<<(ostream& _cout, const Date& d)
{
_cout<<d._year<<"-"<<d._month<<"-"<<d._day;
return _cout;
}
istream& operator>>(istream& _cin, const Date& d)
{
_cin>>d._year;
_cin>>d._month;
_cin>>d._day;
return _cin;
}
int main()
{
Date d;
cin>>d;
cout<<d<<endl;
return 0;
}
在這裏需要解釋一下輸出運算符重載,可以見到聲明瞭一個ostream類類型的變量,這個類是專門用作輸出操作的,裏面內置了printf,重載<<,只是讓其輸出內容改變並沒有改變它的實現方式。輸入算符重載也是一樣。
注意:
- 友元函數可訪問類的私有成員,但不是類的成員函數
- 友元函數不能用const修飾
- 友元函數可以在類定義的任何地方聲明,不受類訪問限定符限制
- 一個函數可以是多個類的友元函數
- 友元函數的調用與普通函數的調用和原理相同
3.2友元類
- 一個外部類可以通過友元的方式來訪問另一個類當中的成員函數和私有成員變量。
- 友元關係是單向的,不具有交換性。
比如上述Time類和Date類,在Time類中聲明Date類爲其友元類,那麼可以在Date類中直接訪問Time 類的私有成員變量,但 想在Time類中訪問Date類中私有的成員變量則不行。
- 友元關係不能傳遞
如果B是A的友元,C是B的友元,則不能說明C時A的友元。
class Date; // 前置聲明
class Time
{
friend class Date; // 聲明日期類爲時間類的友元類,則在日期類中就直接訪問Time類中的私有成員變
量
public:
Time(int hour, int minute, int second)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second)
{
// 直接訪問時間類私有的成員變量
_t._hour = hour;
_t._minute = minute;
_t.second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
4、內部類
如果一個類定義在另一個類的內部,那麼這個類就叫做這個類的內部類。外部類對內部類沒有優越的訪問權限,但是內部類卻可以自由的訪問外部類的成員函數和變量,也就是說外部類是內部類的友元。
注意:
內部類就是外部類的友元類。注意友元類的定義,內部類可以通過外部類的對象參數來訪問外部類中
的所有成員。但是外部類不是內部類的友元。
sizeof(外部類)=外部類,和內部類沒有任何關係。
class A
{
private:
static int k;
int h;
public:
class B
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
};
};
int A::k = 1;
int main()
{
A::B b;
b.foo(A());
return 0;
}