前言
類的6個默認成員函數(shù):如果一個類中什么成員都沒有,簡稱為空類。
空類中真的什么都沒有嗎?并不是,任何類在什么都不寫時,編譯器會自動生成以下6個默認成員函數(shù)。
默認成員函數(shù):用戶沒有顯式實現(xiàn),編譯器會生成的成員函數(shù)稱為默認成員函數(shù)。
class Date {};
一、運算符重載
定義
C++為了增強代碼的可讀性引入了運算符重載,運算符重載是具有特殊函數(shù)名的函數(shù),也具有其返回值類型,函數(shù)名字以及參數(shù)列表,其返回值類型與參數(shù)列表與普通的函數(shù)類似。
函數(shù)名字為:關鍵字operator
后面接需要重載的運算符符號。
函數(shù)原型:返回值類型 operator
操作符(參數(shù)列表)
注意:
-
不能通過連接其他符號來創(chuàng)建新的操作符:比如
operator@
-
重載操作符必須有一個類類型參數(shù) 用于內(nèi)置類型的運算符,其含義不能改變,例如:內(nèi)置的整型
+
,不 能改變其含義 -
作為類成員函數(shù)重載時,其形參看起來比操作數(shù)數(shù)目少1,因為成員函數(shù)的第一個參數(shù)為隱藏的
this
-
.* :: sizeof ?: .
注意以上5個運算符不能重載。
實例
// 全局的operator==
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//private:
int _year;
int _month;
int _day;
};
// 這里會發(fā)現(xiàn)運算符重載成全局的就需要成員變量是公有的,那么問題來了,封裝性如何保證?
// 這里其實可以用友元解決,或者干脆重載成成員函數(shù)。
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, 26);
Date d2(2018, 9, 27);
cout << (d1 == d2) << endl;
}
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// bool operator==(Date* this, const Date& d2)
// 這里需要注意的是,左操作數(shù)是this,指向調(diào)用函數(shù)的對象
bool operator==(const Date& d2)
{
return _year == d2._year;
&& _month == d2._month
&& _day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
注意要點
同時存在全局運算符重載和類里的運算符重載,編譯器會優(yōu)先調(diào)用類里的運算符重載
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// bool operator==(Date* this, const Date& d2)
// 這里需要注意的是,左操作數(shù)是this,指向調(diào)用函數(shù)的對象
bool operator==(const Date& d2)
{
return _year == d2._year;
&& _month == d2._month
&& _day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
函數(shù)重載與運算符重載的區(qū)別
不同點
函數(shù)重載和運算符重載是C++中兩個相關但不同的概念。
函數(shù)重載是指在同一個作用域中定義多個具有相同名稱但參數(shù)列表不同的函數(shù)。這樣做的目的是為了提供更靈活的函數(shù)調(diào)用方式,使得同一個函數(shù)名可以根據(jù)不同的參數(shù)類型或參數(shù)個數(shù)執(zhí)行不同的操作。
運算符重載是指在C++中允許自定義類的成員函數(shù)或非成員函數(shù)來重新定義運算符的行為。通過運算符重載,可以為自定義的類創(chuàng)建與內(nèi)置類型相似的運算符行為,使得自定義類的對象可以像內(nèi)置類型一樣進行運算。
總結:函數(shù)重載是針對函數(shù)進行的,通過改變參數(shù)列表來定義多個同名函數(shù);而運算符重載是針對運算符進行的,通過重新定義運算符的行為來實現(xiàn)與內(nèi)置類型相似的運算。
相似點
函數(shù)重載和運算符重載在某些方面是相似的,它們都是通過改變函數(shù)或運算符的行為來提供更靈活的功能。
-
名稱相同:函數(shù)重載和運算符重載都是使用相同的名稱來定義多個不同的行為。
-
參數(shù)列表變化:函數(shù)重載通過改變參數(shù)列表來定義多個同名函數(shù),而運算符重載通過改變函數(shù)參數(shù)或者在類中定義成員函數(shù)重載運算符。
-
增加靈活性:無論是函數(shù)重載還是運算符重載,都可以根據(jù)需要定義不同的行為,使得代碼更加靈活易用。
-
增加可讀性:函數(shù)重載和運算符重載可以使代碼更具可讀性,因為可以根據(jù)函數(shù)名或運算符符號來推測其功能。
盡管函數(shù)重載和運算符重載在某些方面相似,但它們的目的和應用場景有所不同。函數(shù)重載用于定義同一功能的不同實現(xiàn),而運算符重載用于為自定義類創(chuàng)建與內(nèi)置類型相似的運算符行為。
總結
-
函數(shù)重載:可以讓函數(shù)名相同,參數(shù)不同的函數(shù)同時存在
-
運算符重載:讓自定義類型可以使用運算符,并且控制運算符的行為,增強可讀性
-
他們之間各論各的,沒有關系
-
多個同一運算符重載可以構成函數(shù)重載
二、賦值運算符重載
賦值運算符重載格式
- 參數(shù)類型:
const T&
,傳遞引用可以提高傳參效率 - 返回值類型:
T&
,返回引用可以提高返回的效率,有返回值目的是為了支持連續(xù)賦值 - 檢測是否自己給自己賦值
- 返回
*this
:要復合連續(xù)賦值的含義
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;
}
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;
};
賦值運算符重載要點
重載要點
- 賦值運算符只能重載成類的成員函數(shù)不能重載成全局函數(shù)
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
int _year;
int _month;
int _day;
};
// 賦值運算符重載成全局函數(shù),注意重載成全局函數(shù)時沒有this指針了,需要給兩個參數(shù)
Date& operator=(Date& left, const Date& right)
{
if (&left != &right)
{
left._year = right._year;
left._month = right._month;
left._day = right._day;
}
return left;
}
// 編譯失?。?/span>
// error C2801: “operator =”必須是非靜態(tài)成員
原因:賦值運算符如果不顯式實現(xiàn),編譯器會生成一個默認的。此時用戶再在類外自己實現(xiàn)一個全局的賦值運算符重載,就和編譯器在類中生成的默認賦值運算符重載沖突了,故賦值運算符重載只能是類的成員函數(shù)。
- 用戶沒有顯式實現(xiàn)時,編譯器會生成一個默認賦值運算符重載,以值的方式逐字節(jié)拷貝。
注意:- 內(nèi)置類型成員變量是直接賦值的,而自定義類型成員變量需要調(diào)用對應類的賦值運算符重載完成賦值。
- 跟拷貝構造類似。
class Time
{
public:
Time()
{
_hour = 1;
_minute = 1;
_second = 1;
}
Time& operator=(const Time& t)
{
if (this != &t)
{
_hour = t._hour;
_minute = t._minute;
_second = t._second;
}
return *this;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本類型(內(nèi)置類型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定義類型
Time _t;
};
int main()
{
Date d1;
Date d2;
d1 = d2;
return 0;
}
既然編譯器生成的默認賦值運算符重載函數(shù)已經(jīng)可以完成字節(jié)序的值拷貝了,還需要自己實現(xiàn)嗎?當然像日期類這樣的類是沒必要的。那么下面的類呢?驗證一下試試?
// 這里會發(fā)現(xiàn)下面的程序會崩潰掉?這里就需要我們以后講的深拷貝去解決。
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申請空間失敗");
return;
}
_size = 0;
_capacity = capacity;
}
void Push(const DataType& data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
~Stack()
{
if (_array)
{
free(_array);
_array = nullptr;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
size_t _size;
size_t _capacity;
};
int main()
{
Stack s1;
s1.Push(1);
s1.Push(2);
s1.Push(3);
s1.Push(4);
Stack s2;
s2 = s1;
return 0;
}
注意:如果類中未涉及到資源管理,賦值運算符是否實現(xiàn)都可以;一旦涉及到資源管理則必須要實現(xiàn)。
傳值返回和傳址返回要點
可以看到傳值和傳址在遇到不同問題時有不同的表現(xiàn),如下,在運算符重載的問題下,傳址調(diào)用比傳值調(diào)用的效率更高,關于為什么要返回*this
,見下面
正常的賦值表達式都是支持連續(xù)賦值的,如果我們使用Date
作為返回值,可以見到效率太低了,相比指向返回Date
的引用效率更高
d1 = d2 = d3
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
根據(jù)上式,我們可以見到,我們是把d3
的值賦給d2
,而d2
的地址存放在this
指針里,我們需要對this
指針進行解引用才能得到d2
的地址,得到d2
的地址后,我們便可以進行下一步的賦值
三、前置++和后置++重載
示例
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 前置++:返回+1之后的結果
// 注意:this指向的對象函數(shù)結束后不會銷毀,故以引用方式返回提高效率
Date& operator++()
{
_day += 1;
return *this;
}
// 后置++:
// 前置++和后置++都是一元運算符,為了讓前置++與后置++形成能正確重載
// C++規(guī)定:后置++重載時多增加一個int類型的參數(shù),但調(diào)用函數(shù)時該參數(shù)不用傳遞,編譯器自動傳遞
// 注意:后置++是先使用后+1,因此需要返回+1之前的舊值,故需在實現(xiàn)時需要先將this保存一份,然后給this + 1
//而temp是臨時對象,因此只能以值的方式返回,不能返回引用
Date operator++(int)
{
Date temp(*this);
_day += 1;
return temp;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
Date d1(2022, 1, 13);
d = d1++; // d: 2022,1,13 d1:2022,1,14
d = ++d1; // d: 2022,1,15 d1:2022,1,15
return 0;
}
概念
在C++中,++
操作符可以被重載為前置++
和后置++
兩種形式。
前置++
表示在操作數(shù)之前增加1,并返回增加后的值。
后置++
表示在操作數(shù)之后增加1,并返回增加前的值。
下面是前置++
和后置++
的重載函數(shù):
class MyClass {
public:
MyClass& operator++() { // 前置++
// 在此處增加1的邏輯
return *this;
}
MyClass operator++(int) { // 后置++
MyClass temp(*this); // 復制當前對象
// 在此處增加1的邏輯
return temp; // 返回增加前的對象
}
};
四、深挖operator
在C++中,輸出流操作符 <<
可以被重載用于自定義類型的對象,以便在流中輸出該對象的內(nèi)容。
友元函數(shù)
具體請看這篇文章,C++從入門到精通——友元
下面是重載流輸出操作符的示例:
#include <iostream>
class MyClass {
public:
int value;
MyClass(int val) : value(val) {}
friend std::ostream& operator<<(std::ostream& os, const MyClass& obj) {
os << obj.value;
return os;
}
};
int main() {
MyClass obj(42);
std::cout << obj << std::endl; // 輸出對象的內(nèi)容
return 0;
}
在上述示例中,我們定義了一個名為MyClass
的類,該類具有一個整型成員變量value
。我們將流輸出操作符 <<
聲明為友元函數(shù),并在函數(shù)中實現(xiàn)輸出對象的內(nèi)容。在主函數(shù)中,我們創(chuàng)建了一個名為obj
的MyClass
對象,并使用流輸出操作符將其內(nèi)容輸出到標準輸出流中。
輸出結果將是 “42
”。
注意,我們可以通過重載流輸出操作符來控制輸出對象的格式和內(nèi)容。
模擬實現(xiàn)
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void operator<<(ostream& out)
{
out<<_year<<"年"<<_month<<"月"<<_day<<"日"<<endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.operator<<(cout);
d1 << cout;
return 0;
}
注意以下情況,因為在類里定義的函數(shù),第一個對象永遠是this
指針,寫成cout<<d1
是錯誤的寫法,即函數(shù)重載中,參數(shù)順序和操作數(shù)順序是一致的。
d1 << cout;
我們可以通過使用在類外面定義一個新函數(shù),或者在類里使用上面的示例。下面定義的函數(shù)直接運行的話是會出錯的,因為_year
,_month
,_day
是私有的。
void operator<<(ostream& out,const Date& d)
{
out<<d._year<<"年"<<d._month<<"月"<<d._day<<"日"<<endl;
}
以此類推,流輸入也是同理
友元函數(shù)
友元函數(shù)是指在類的定義中,聲明為友元的函數(shù)可以直接訪問該類的私有成員和保護成員。友元函數(shù)可以是全局函數(shù),也可以是其他類的成員函數(shù)。在C++中,使用關鍵字friend
來聲明友元函數(shù)。友元函數(shù)的定義通常在類的外部。
友元函數(shù)的特點是可以繞過類的訪問權限,直接訪問類的私有成員和保護成員。這在一些特殊情況下很有用,例如需要在其他類中對該類的私有成員進行操作或者需要在全局函數(shù)中操作該類的私有成員。
需要注意的是,友元函數(shù)并不是類的成員函數(shù),因此在調(diào)用時不需要通過對象來訪問,可以直接使用函數(shù)名進行調(diào)用。另外,由于友元函數(shù)不屬于類的成員函數(shù),因此不能使用this
指針。
友元函數(shù)的具體用法可以分為兩種情況:
- 全局函數(shù)作為友元函數(shù):全局函數(shù)可以在類的外部定義,并通過
friend
關鍵字聲明為友元函數(shù)。在全局函數(shù)的定義中,可以直接訪問該類的私有成員和保護成員。 - 對象的成員函數(shù)作為友元函數(shù):在另一個類的成員函數(shù)中通過友元關鍵字將該類的成員函數(shù)聲明為友元函數(shù)。在友元函數(shù)的定義中,可以直接訪問該類的私有成員和保護成員。
需要注意的是,友元函數(shù)并不是類的成員函數(shù),因此不能直接訪問類的成員變量和成員函數(shù)。如果需要訪問類的成員變量和成員函數(shù),可以通過對象來訪問。文章來源:http://www.zghlxwxcb.cn/news/detail-858039.html
友元函數(shù)的使用應該謹慎,因為它破壞了封裝性原則,導致代碼可讀性和可維護性降低。在設計類的時候,應該盡量避免使用友元函數(shù),而是通過成員函數(shù)來操作類的私有成員和保護成員。只有在確實有必要的情況下,才考慮使用友元函數(shù)。文章來源地址http://www.zghlxwxcb.cn/news/detail-858039.html
到了這里,關于C++從入門到精通——類的6個默認成員函數(shù)之賦值運算符重載的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!