運算符重載

一、 運算符重載

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ù)。

  1. 全局函數(shù)
Student operator+ (Student &s1 , Student &s2){
    Student temp(s1.age + s2.age , s1.weight + s2.weight);
    return temp;
}
  1. 成員函數(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 Ahas 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、protectedprivate 幾種類型。繼承類型是在繼承父類時指定的。 如: class Student : public Person

我們幾乎不使用 protectedprivate 繼承,通常使用 **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 ;
}




?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 寫在開頭:本人打算開始寫一個Kotlin系列的教程,一是使自己記憶和理解的更加深刻,二是可以分享給同樣想學(xué)習(xí)Kot...
    胡奚冰閱讀 959評論 1 1
  • C++運算符重載-上篇 本章內(nèi)容:1. 運算符重載的概述2. 重載算術(shù)運算符3. 重載按位運算符和二元邏輯運算符4...
    Haley_2013閱讀 2,386評論 0 51
  • 基本上我們進行運算符重載時有兩種形式,類內(nèi)的運算符重載和頂層函數(shù)位置的運算符重載。 操作符重載指的是將C++提供的...
    飛揚code閱讀 1,784評論 0 4
  • C++語言的一個很有意思的特性就是除了支持函數(shù)重載外還支持運算符重載,原因就是在C++看來運算符也算是一種函數(shù)。比...
    歐陽大哥2013閱讀 2,791評論 0 8
  • C++運算符重載-下篇 本章內(nèi)容:1. 運算符重載的概述2. 重載算術(shù)運算符3. 重載按位運算符和二元邏輯運算符4...
    Haley_2013閱讀 1,534評論 0 49

友情鏈接更多精彩內(nèi)容