一、 運算符重載
1. 什么是運算符重載
重載這個概念在早前的函數(shù)重載,大家已經(jīng)見識過了。函數(shù)可以重載, 運算符也是可以重載。 運算符重載就是對已有的運算符重新進行定義,賦予其另一種功能,以達到適應(yīng)不同的數(shù)據(jù)類型。運算符重載不能改變它本來的寓意(也就是 加法不能變更為 減法)
運算符重載只是一種 “語法上的方便” , 它只是一種函數(shù)調(diào)用的方式。
int a = 3 ;
int b = 4 ;
int c = a + b ; //編譯器通過
Student s1 ;
Student s2 ;
Student s3 = s1 + s2 ; // 編譯器不通過
2. 定義運算符重載
重載的運算符是帶有特殊名稱的函數(shù),函數(shù)名是由關(guān)鍵字
operator和其后要重載的運算符符號構(gòu)成的。與其他函數(shù)一樣,重載運算符有一個返回類型和一個參數(shù)列表。假如我們需要把兩個學(xué)生的年紀 和 體重 各自相加,然后得出第三個學(xué)生的數(shù)值。那么使用運算符重載可以如此編寫:
- 以前寫法
class Student{
public:
int age ;
int weight;
public :
Student(age_val , weight_val):age{age_val},weight{weight_val}{
}
};
Student s1(18 , 120);
Student s2(20 , 90);
int age = s1.age + s2.age ;
int weight = s1.weight + s2.weight ;
Student s3 (age , weight);
- 運算符重載
運算符可以寫成全局函數(shù)也可以寫成成員函數(shù)。
- 全局函數(shù)
Student operator+ (Student &s1 , Student &s2){
Student temp(s1.age + s2.age , s1.weight + s2.weight);
return temp;
}
- 成員函數(shù)
Student operator+ ( Student &s2){
Student temp(this.age + s2.age , this.weight + s2.weight);
return temp;
}
3. 運算符重載練習(xí)
1. 輸出運算符重載
其實這里說就是我們平常見過的
<<。<<實際上是位移運算符,但是在c++里面,可以使用它來配合cout進行做控制臺打印輸出。cout其實是ostream的一個實例,而ostrem是 類basic_ostream的一個別名,所以之所以能使用cout <<來輸出內(nèi)容,全是因為basic_ostream里面對<<運算符進行了重載.
#include <iostream> //由于這里引入了iostream 所以下面可以直接對 << 進行重載
class Student{
public:
string name {"zhangsan"};
public:
Student(string name){
this->name = name;
}
};
//對 << 運算符進行重載。
ostream& operator<< (ostream& o, Student &s1){
o << s1.name ;
return o;
}
int main() {
Student s1("張三");
cout << s1 <<"i" ;
return 0;
}
2. 輸入運算符重載
#include <iostream> //由于這里引入了iostream 所以下面可以直接對 << 進行重載
//對 << 運算符進行重載。
ostream& operator<< (ostream& o, Student &s1){
o << s1.name ;
return o;
}
//對 >> 運算符進行重載
istream& operator>> (istream& in, Student &s1){
in >> s1.name;
return in;
}
int main() {
Student s1("張三");
cout << s1 <<"i" ;
return 0;
}
4.賦值運算符重載
- 默認的賦值運算符
賦值運算符在此前編碼就見過了,其實就是
=操作符 。
stu s1;
stu s2;
s2= s1; //拷貝賦值
s2 = move(s1); // 一旦有這行代碼,表示s1不能用了。
class Student{
int no;
int age ;
public :
Student(no , age){
this->no = no ;
this->age = age ;
}
}
Student s1(10001 , 15);
Student s2 ;
s2 = s1; //此處使用了默認的賦值運算符。
Student s2 = s1;
1. 拷貝賦值運算
其實這里說的就是
=賦值運算符
//拷貝賦值
Stu& operator=(const Stu &h){
cout <<"執(zhí)行拷貝賦值函數(shù)" << endl;
d = new int();
*d = *h.d;
}
- main
int main(){
Stu stu1("張三",18);
Stu stu2 ;
stu2 = stu1; //拷貝賦值
}
2. 移動賦值運算
移動賦值運算,接收的是一個右值,并且移動之后,原有對象將不再擁有對數(shù)據(jù)的控制權(quán)。
//移動賦值
Stu& operator=(const Stu &&h){
cout <<"執(zhí)行移動賦值函數(shù)" << endl;
d = h.d;
h.d = nullptr;
return *this;
}
- main
int main(){
Stu stu1("張三",18);
Stu stu2 ;
stu2 = move(stu1); //移動后,stu1 將不再擁有對數(shù)據(jù)的控制權(quán)
}
5. 調(diào)用運算符重載
一般來說,可以使用對象來訪問類中的成員函數(shù),而對象本身是不能像函數(shù)一樣被調(diào)用的,除非在類中重載了
調(diào)用運算符。 如果類重載了函數(shù)調(diào)用運算符,則我們可以像使用函數(shù)一樣使用該類的對象。在外面使用對象(),實際上背后訪問的是類中重載的調(diào)用運算符函數(shù)。如果某個類重載了調(diào)用運算符,那么該類的對象即可稱之為:函數(shù)對象 ,因為可以調(diào)用這種對象,所以才說這些對象行為
像函數(shù)一樣。
class Calc{
public:
int operator()(int val){
return val <0 ? -val :val;
}
};
int main(){
Calc c ;
int value = c(-10);
return 0;
}
- 標準庫中定義的函數(shù)對象
在標準庫中定義了一組算術(shù)運算符 、關(guān)系運算符、邏輯運算符的類,每個類都有自己重載的調(diào)用運算符。要想使用這些類,需要導(dǎo)入
#include<functional>, 后面要說的lamdda表達式正是一個函數(shù)對象
plus<int > p; //加法操作
int a = p(3 , 5);
negate<int> n; //可以區(qū)絕對值
cout <<n(-10) << endl;
6. lambda 表達式
也叫做
lambda 函數(shù), lambda 表達式的出現(xiàn)目的是為了提高編碼效率,但是它的語法卻顯得有點復(fù)雜。lambda表達式表示一個可以執(zhí)行的代碼單元,可以理解為一個未命名的內(nèi)聯(lián)函數(shù)。
1. lambda表達式的語法
在編寫lambda表達式的時候,可以忽略參數(shù)列表和返回值類型,但是前后的捕獲列表和函數(shù)體必須包含 , 捕獲列表的中括號不能省略,編譯根據(jù)它來識別后面是否是
lambda表達式,并且它還有一個作用是能夠讓lambda的函數(shù)體訪問它所處作用域的成員。
//語法
[捕獲列表](參數(shù)列表)->返回值類型{函數(shù)體}
[]()->int{};
//示例1:
[](int a ,int b)->int{return a + b ;} ; //一個簡單的加法
[](int a ,int b){return a + b ;} ; //如果能明確返回值類型,那么 ->int 也可以省略掉
[]{return 3 + 5 ;} ; //如果不需要參數(shù),那么參數(shù)列表頁可以忽略。至此不能再精簡了。
[]{} ; //這是最精簡的lambda表達式了,不過沒有任何用處,等于一個空函數(shù),沒有函數(shù)體代碼
2. 傳遞參數(shù)和獲取返回值
lambda表達式定義出來并不會自己調(diào)用,需要手動調(diào)用。
//1. 接收lambda表達式,然后調(diào)用
auto f = [](int a ,int b)->int{return a + b ;};
int result = f(3,4); //調(diào)用lambda函數(shù),傳遞參數(shù)
//2. 不接收,立即調(diào)用。
int result= [](int a ,int b){return a + b }(3,4); //后面的小括號等同于調(diào)用這個函數(shù)。
3. 捕獲列表的使用
labmda表達式需要在函數(shù)體中定義,這時如果想訪問所處函數(shù)中的某個成員,那么就需要使用捕獲列表了。捕獲列表的寫法通常有以下幾種形式:
| 形式 | 作用 |
|---|---|
| [a] | 表示值傳遞方式捕獲變量 a |
| [=] | 表示值傳遞方式捕獲所有父作用域的變量(包括this) |
| [&a] | 表示引用方式傳遞捕獲變量a |
| [&] | 表示引用傳遞方式捕獲所有父作用域的變量(包括this) |
| [this] | 表示值傳遞方式捕獲當(dāng)前的this指針 |
| [=,&a,&b] | 引用方式捕獲 a 和 b , 值傳遞方式捕獲其他所有變量 (這是組合寫法) |
int main(){
int a = 3 ;
int b = 5;
auto f1 = [a,b]{return a + b;}; //值傳遞方式捕獲 a 和 b
cout << f1() << endl; //打印 8
auto f2 = [&a,&b]{ //引用方式捕獲 a 和 b
a = 30; //這里修改會導(dǎo)致外部的a 也跟著修改。
return a + b;
};
cout << f2() << endl; //這里打印35
cout << "a= "<< a << endl; //再打印一次,a 變成30了
}
4. lambda 的應(yīng)用場景
編寫lamdda表達式很簡單,但是用得上lambda表達式的地方比較特殊。一般會使用它來封裝一些邏輯代碼,使其不僅具有函數(shù)的包裝性,也具有可見的自說明性。在C++ 中,函數(shù)的內(nèi)部不允許在定義函數(shù),如果函數(shù)中需要使用到某一個函數(shù)幫助計算并返回結(jié)果,代碼又不是很多,那么lambda表達式不失為一種上佳選擇。如果沒有l(wèi)ambda表達式,那么必須在外部定義一個內(nèi)聯(lián)函數(shù)。 來回查看代碼稍顯拖沓,定義lambda函數(shù),距離近些,編碼效率高些。 lambda表達式就是內(nèi)聯(lián)的。
inline
- 沒有使用lambda函數(shù)
計算6科考試總成績。
int getCout(vector<int> scores){
int result = 0 ;
for(int s : scores){
result += s;
}
return result;
}
int main(){
vector<int> scores{80,90,75,99,73,23};
//獲取總成績
int result = getCout(scores);
cout <<"總成績是: "<< result << endl;
}
- 使用labmda表達式
lambda函數(shù)屬于內(nèi)聯(lián),并且靠的更近,也便于閱讀。
int main(){
vector<int> scores{80,90,75,99,73,23};
int result2 = [&]{
int result = 0 ;
for(int s : scores){
result += s;
}
return result;
}();
cout <<"總成績是2: "<< result2 << endl;
}
二、 繼承
1. 什么是繼承
繼承是類與類之間的關(guān)系,是一個很簡單很直觀的概念,與現(xiàn)實世界中的繼承類似,例如兒子繼承父親的財產(chǎn)。
繼承(Inheritance)可以理解為一個類從另一個類獲取成員變量和成員函數(shù)的過程。例如B類 繼承于A類,那么 B 就擁有 A 的成員變量和成員函數(shù)。被繼承的類稱為父類或基類,繼承的類稱為子類或派生類。 子類除了擁有父類的功能之外,還可以定義自己的新成員,以達到擴展的目的。
2. is A 和 has A
大千世界,不是什么東西都能產(chǎn)生繼承關(guān)系。只有存在某種聯(lián)系,才能表示有繼承關(guān)系。如:哺乳動物是動物,狗是哺乳動物,因此,狗是動物,等等。所以在學(xué)習(xí)繼承后面的內(nèi)容之前,先說說兩個術(shù)語
is A和has A。
- is A
是一種繼承關(guān)系,指的是類的父子繼承關(guān)系。表達的是一種方式:這個東西是那個東西的一種。例如:長方體與正方體之間--正方體是長方體的一種。正方體繼承了長方體的屬性,長方體是父類,正方體是子類。
- has A
has-a 是一種組合關(guān)系,是關(guān)聯(lián)關(guān)系的一種(一個類中有另一個類型的實例),是整體和部分之間的關(guān)系(比如汽車和輪胎之間),并且代表的整體對象負責(zé)構(gòu)建和銷毀部分對象,代表部分的對象不能共享。
3. 繼承入門
通常在繼承體系下,會把共性的成員,放到父類來定義,子類只需要定義自己特有的東西即可?;蛘吒割愄峁┑男袨槿绮荒軡M足,那么子類可以選擇自定重新定義。
class Person{
string name;
int age ;
};
class Student:public Person{
//不用再定義了
//string name;
//int age ;
}
4. 繼承下的訪問權(quán)限
當(dāng)一個類派生自基類,該基類可以被繼承為 public、protected 或 private 幾種類型。繼承類型是在繼承父類時指定的。 如:
class Student : public Person
我們幾乎不使用 protected 或 private 繼承,通常使用 **public ** 繼承。當(dāng)使用不同類型的繼承時,遵循以下幾個規(guī)則:
- 公有繼承(public):當(dāng)一個類派生自公有基類時,基類的公有成員也是派生類的公有成員,基類的保護成員也是派生類的保護成員,基類的私有成員不能直接被派生類訪問
- 保護繼承(protected): 當(dāng)一個類派生自保護基類時,基類的公有和保護成員將成為派生類的保護成員。
- 私有繼承(private):當(dāng)一個類派生自私有基類時,基類的公有和保護成員將成為派生類的私有成員。
5. 構(gòu)造和析構(gòu)函數(shù)
構(gòu)造函數(shù)是對象在創(chuàng)建是調(diào)用,析構(gòu)函數(shù)是對象在銷毀時調(diào)用。但是在繼承關(guān)系下,無論在對象的創(chuàng)建還是銷毀,都會執(zhí)行父類和子類的構(gòu)造和析構(gòu)函數(shù)。
1. 繼承中的構(gòu)造析構(gòu)調(diào)用原則
a. 子類對象在創(chuàng)建時會首先調(diào)用父類的構(gòu)造函數(shù);
b. 父類構(gòu)造函數(shù)執(zhí)行完畢后,執(zhí)行子類的構(gòu)造函數(shù);
c. 當(dāng)父類的構(gòu)造函數(shù)中有參數(shù)時,必須在子類的初始化列表中顯示調(diào)用;
d. 析構(gòu)函數(shù)執(zhí)行的順序是先調(diào)用子類的析構(gòu)函數(shù),再調(diào)用父類的析構(gòu)函數(shù)
class Student: public Person{
public :
Student(){
cout << "調(diào)用了子類類構(gòu)造函數(shù)" << endl;
}
~Student(){
cout << "調(diào)用了子類析構(gòu)函數(shù)" << endl;
}
};
int main() {
Student s1
return 0;
}
2. 繼承和組合的構(gòu)造和析構(gòu)
a. 先調(diào)用父類的構(gòu)造函數(shù),再調(diào)用組合對象的構(gòu)造函數(shù),最后調(diào)用自己的構(gòu)造函數(shù);
b. 先調(diào)用自己的析構(gòu)函數(shù),再調(diào)用組合對象的析構(gòu)函數(shù),最后調(diào)用父類的析構(gòu)函數(shù)。
//父類
class Person{
public :
Person(){
cout << "調(diào)用了父類構(gòu)造函數(shù)" << endl;
}
~Person(){
cout << "調(diào)用了父類析構(gòu)函數(shù)" << endl;
}
};
class A{
public :
A(){
cout << "調(diào)用A的構(gòu)造函數(shù)" << endl;
}
~A(){
cout << "調(diào)用A的析構(gòu)函數(shù)" << endl;
}
};
//子類
class Student: public Person{
public :
Student(){
cout << "調(diào)用了子類類構(gòu)造函數(shù)" << endl;
}
~Student(){
cout << "調(diào)用了子類析構(gòu)函數(shù)" << endl;
}
public:
A a;
};
int main() {
Student s1(18 , "zhangsan");
return 0;
}
6. 調(diào)用父類有參構(gòu)造
繼承關(guān)系下,子類的默認構(gòu)造函數(shù)會隱式調(diào)用父類的默認構(gòu)造函數(shù),假設(shè)父類沒有默認的無參構(gòu)造函數(shù),那么子類需要使用參數(shù)初始化列表方式手動調(diào)用父類有參構(gòu)造函數(shù)。 一般來說在創(chuàng)建子類對象前,就必須完成父類對象的創(chuàng)建工作,也就是在執(zhí)行子類構(gòu)造函數(shù)之前,必須先執(zhí)行父類的構(gòu)造函數(shù)。c++使用初始化列表來完成這個工作
- 父類
class Person{
private :
int age ;
string name ;
public :
Person(int age , string name){
cout << "調(diào)用了父類構(gòu)造函數(shù)" << endl;
this->age = age ;
this->name = name;
}
}
- 子類
class Student: public Person{
public :
Student(int age , string name):Person(age ,name){
cout << "調(diào)用了子類類構(gòu)造函數(shù)" << endl;
}
}
Student s1;
7. 再說初始化列表
初始化列表在三種情況下必須使用:
- 情況一、需要初始化的數(shù)據(jù)成員是對象,并且對應(yīng)的類沒有無參構(gòu)造函數(shù)
- 情況二、需要初始化const修飾的類成員或初始化引用成員數(shù)據(jù);
- 情況三、繼承關(guān)系下,父類沒有無參構(gòu)造函數(shù)情況
- 初始化列表的賦值順序是按照類中定義成員的順序來決定
- 常量和引用的情況
//有const 和 引用的情況
class stu{
const int a1; //常量不允許修改值,所以不允許在構(gòu)造里面使用 = 賦值
int &age; //
T(int age):a1(10),age(age){
}
}
- 初始化對象成員
類中含有其他類的對象成員,如果要初始化,只能使用初始化類列表方式。
class A{
public:
int a;
A(int a):a(a){}
};
class stu{
A a;
stu():a(9){
}
};
8. 重新定義父類同名函數(shù)
在繼承中,有時候父類的函數(shù)功能并不夠強大,子類在繼承之后,可以對其進行增強擴展。 如果還想調(diào)用你父類的函數(shù),可以使用
父類::函數(shù)名()訪問
class WashMachine{
public:
void wash(){
cout << "洗衣機在洗衣服" << endl;
}
};
class SmartWashMachine : public WashMachine{
public:
void wash(){
cout << "智能洗衣機在洗衣服" << endl;
cout << "開始添加洗衣液~~" << endl;
//調(diào)用父類的函數(shù)
WashMachine::wash();
}
};
9 . 多重繼承
C++ 允許存在多繼承,也就是一個子類可以同時擁有多個父類。只需要在繼承時,使用逗號進行分割即可。
class Father{
};
class Mother{
}
class Son:public Father , public Mother{
}
- 多重繼承的構(gòu)造函數(shù)
多繼承形式下的構(gòu)造函數(shù)和單繼承形式基本相同,只是要在子類的構(gòu)造函數(shù)中調(diào)用多個父類的構(gòu)造函數(shù) 。 他們調(diào)用的順序由定義子類時,繼承的順序決定。
class Mother{
string c;
int d;
public:
Mother(string c ,int d):c(c),d(d){}
};
class Son:public Father , public Mother{
string e;
int f;
public:
Son(string e ,int f):Father(e,f),Mother(e,f){
}
};
10. 類的前置聲明
一般來說,類和 變量是一樣的,必須先聲明然后再使用,如果在某個類里面定義類另一個類的對象變量,那么必須在前面做前置聲明,才能編譯通過。
class B; //所有前置聲明的類,在某個類中定義的時候,只能定義成引用或者指針。
class A{
public:
//B b0; //因為這行代碼,單獨拿出來說,會執(zhí)行B類的無參構(gòu)造,但是編譯器到此處的時候,還不知道B這個類的構(gòu)造長什么樣。
B &b1;
B *b2;
A(B &b1 , B *b2):b1(b1),b2(b2){
}
};
class B{
};
int main(){
// B b; //---> 執(zhí)行B的構(gòu)造函數(shù)。
B b1;
B b2;
A a(b1 ,&b2);
return 0 ;
}