一、類與對象

1. 認識類與對象

(1)什么是類(class)?

類(class)是類型(type),是用戶自定義的類型。為什么不叫它type,因為借用Simula語言中的class關(guān)鍵字。

<1>為什么要有類?

基于便利性的考慮,現(xiàn)實世界中物(object)通常被分為幾種獨立的分類。

<2>基本概念
  • :建筑圖紙
  • 實例化:建造
  • 對象/實例:樓房
<3>面向?qū)ο蟮乃拇筇卣?/h6>
  • 1.抽象:抽出具體事物的普遍性的本質(zhì);比如分門別類:鳥類、哺乳類、魚類
    解釋:抽象出來的一般與當前目標有關(guān)的方面,叫做類或者接口,抽象包括兩個方面:數(shù)據(jù)抽象(特診:對象/類的屬性,c++也叫做成員變量)和過程抽象(行為:類的方法,c++叫做成員函數(shù)
  • 2.封裝:把數(shù)據(jù)與處理(函數(shù))包在一起;比如通信錄(增加、刪除)
    解釋:封裝就是把屬性和方法都隱藏起來,保證數(shù)據(jù)的安全性,對外我們提供接口用來給我們信賴的對象訪問。如私有變量。
  • 3.繼承:數(shù)據(jù)與處理(函數(shù))的傳承;財富與絕技、混血兒(膚色/頭發(fā)、 兩種語言
  • 4.多態(tài):同一個事物(函數(shù))的多種形態(tài);手機鍵盤數(shù)字與字母、 電腦鍵盤功能鍵
<4>面向?qū)ο蟮奈宕笤瓌t

1.單一職責原則
2.開放封閉原則
3.替換原則
4.依賴倒置原則
5.接口隔離原則

2.類的定義與對象創(chuàng)建

(1)類的定義:與struct相似(C++)

<1>格式
class 類名{
       成員變量/成員函數(shù)聲明;
};

注意:class定義最后的;一定不要忘記。

<2>構(gòu)成
  • 數(shù)據(jù)成員(data member)/成員變量/屬性:作用:對象內(nèi)部數(shù)據(jù)和狀態(tài),只能在類定義中聲明,可以在成員函數(shù)中直接調(diào)用。
  • 成員函數(shù)/方法:作用:對象相關(guān)的操作,可以在類內(nèi)實現(xiàn)或類外實現(xiàn)。成員函數(shù)可以自己調(diào)用內(nèi)部其他的成員函數(shù)。在一個類里面可以調(diào)用另一個類里面public的所有的成員函數(shù)。
//成員函數(shù)可以自己調(diào)用內(nèi)部其他的成員函數(shù)
void Print(){
        cout << name << '\t' << count << "\t¥"  << price << "\t¥"  << GetTotal() << "\t¥" << GetOff() << endl;
}
<3>作用域運算符:: -- 函數(shù)歸屬

在類中定義,在類外面進行實現(xiàn)。如果在類里面實現(xiàn)的話,不需要該格式

在類外實現(xiàn),使用該格式:
返回類型  類名::函數(shù)名
如:
void Student::Print(){}
<4>訪問限定符

private[默認]:私有
public:公有
protected:保護
實踐中,成員變量多數(shù)情況使用private或者protected,成員函數(shù)多數(shù)情況使用public。通常,通過成員函數(shù)改變對象的成員變量。

<5>類定義與類實現(xiàn)分離(.h和.cpp)

聲明和實現(xiàn)分開編寫。

  • 頭文件
    方式:#pragma once或者#ifndef #define #endif
    作用:防止頭文件二次編譯
  • 源文件 --實現(xiàn)
    引用頭文件:include <>(標準庫)/include " "(自定義第三方庫)
<6>class與struct的區(qū)別

C++的class與struct的區(qū)別

  • 1.默認的控制方式不同: struct是public,class是private
  • 2.struct可以使用花括號{}來初始化,class不可以(c++98不可以,c++11可以)

struct在C和C++中的區(qū)別
1.C++的struct可以添加成員函數(shù),而C不可以
2.C++的struct可以使用訪問控制關(guān)鍵字(public private protected),而C不可以。
3.C++的struct在定義對象時可以忽略,而在C中不可以。

SPos spos; // C++
struct SPos spos; // C/C++

(2)對象創(chuàng)建/實例化

<1>直接創(chuàng)建 --類作為類型定義變量 --棧上創(chuàng)建
//基本類型
int  a=10;
int b(10);//等價于int b=10;
//類類型
class Demo{};
//創(chuàng)建變量格式:類名 對象名;  // 創(chuàng)建對象,自動調(diào)用默認構(gòu)造函數(shù)
Demo d;
Student zhangsan("張三",true,21);//創(chuàng)建對象時可以直接賦值
//創(chuàng)建匿名對象格式:類名(); // 創(chuàng)建匿名對象
Demo();
<2>動態(tài)創(chuàng)建 --堆上創(chuàng)建
//基本類型
int* p = new int;
delete p;
p = NULL;
//對象指針new可以為對象設置初始值
int* p = new int(100);
cout << *p << endl;
//類類型
class Demo{};
//類名* 對象指針 = new 類名;// 調(diào)用默認構(gòu)造函數(shù)
Demo* d=new Demo;
Student* lisi =new Student("李四",false,22);//可以為對象設置初始值
delete 對象指針;
<3>動態(tài)創(chuàng)建數(shù)組 -- 堆上創(chuàng)建
//基本類型
int* pa = new int[10];
delete pa;// 只釋放p[0]
delete [] pa;// 釋放全部數(shù)組
//類類型
Demo* d = new Demo[10];
delete [] d;
d = NULL;
//注意:對象數(shù)組指針new不可以為對象設置初始值。
int* pa = new int[10](100);//錯誤:parenthesized initializer

注意:
1.空結(jié)構(gòu)體與空類的大小(sizeof)為1,主要在于初始化/實例化時,編譯器給變量/對象分配內(nèi)存(地址),內(nèi)存最小單位為1個字節(jié)。通常,sizeof(類型) == sizeof(變量)。


為什么空的class需要分配一個字節(jié)?
類的實例化就是在內(nèi)存中分配一塊地址,空類同樣可以被實例化,每個實例在內(nèi)存中都有一個獨一無二的地址,因此需要一塊內(nèi)存來存放該地址,而c++中可以申請的最小單位就是一個字節(jié)。


3.方法

(1)構(gòu)造函數(shù)

<1>語法
類名(參數(shù)){
  函數(shù)體
}
<2>特點

1.構(gòu)造函數(shù)的函數(shù)名與類名相同
2.在對象被創(chuàng)建時自動執(zhí)行
3.沒有返回值類型、也沒有返回值
4.可以有多個構(gòu)造函數(shù)(構(gòu)造函數(shù)的重載),但是在調(diào)用時,會根據(jù)需要調(diào)用其中一個構(gòu)造函數(shù)。

<3>構(gòu)造函數(shù)

1.默認構(gòu)造函數(shù):沒有參數(shù)的寫法。當沒有編寫構(gòu)造函數(shù)時,如果創(chuàng)建對象,系統(tǒng)會自動調(diào)用默認的構(gòu)造函數(shù),但是該構(gòu)造函數(shù)的函數(shù)體空的,不能夠達到初始化的目的,編譯可能不會出錯,執(zhí)行時會吐核。但是如果定義了構(gòu)造函數(shù),不管是否合適,編譯器都不會調(diào)用默認的構(gòu)造函數(shù)。
2.構(gòu)造函數(shù)的重載:使用類名相同的構(gòu)造函數(shù),并且是有參數(shù)的,有賦值功能。主要用在從外邊傳入已經(jīng)分配好的動態(tài)內(nèi)存,提供一個接口。

<4>調(diào)用時機

1.使用類直接創(chuàng)建對象
2.new動態(tài)創(chuàng)建

<5>構(gòu)造函數(shù)的作用

1.給創(chuàng)建的對象建立一個標識符
2.為對象數(shù)據(jù)成員開辟內(nèi)存空間
3.完成對象數(shù)據(jù)成員的初始化
例子:

class Array{
private:
      int* data;
      int length;
public:
      Array(){//構(gòu)造函數(shù)
            data=NULL;
             length=0;
      }
      Array(int* d,int l){//構(gòu)造函數(shù)的重載
            data=d;
            length=l;
      }
};

int main{
      //調(diào)用默認的構(gòu)造函數(shù)
      Array temp;
      //調(diào)用構(gòu)造函數(shù)的重載:
      int* list=new int[10];//創(chuàng)建了動態(tài)分配內(nèi)存
      for(int i=0;i<10;++i){
            list[i]=i;
       }
      Array(list,10);//調(diào)用上面的構(gòu)造函數(shù)重載,從外邊傳入已經(jīng)分配好的動態(tài)內(nèi)存。
}

(2)初始化列表

初始化類的成員有兩種方式,一是使用初始化列表,二是在構(gòu)造函數(shù)體內(nèi)進行賦值操作。
主要是性能問題,使用初始化列表少了一次調(diào)用拷貝構(gòu)造函數(shù)的過程,這對于數(shù)據(jù)密集型的類來說,是非常高效的。

<1>語法
類名(參數(shù)):成員變量1(參數(shù)1),成員變量2(參數(shù)2){
      //函數(shù)體
}
<2>作用

初始化非靜態(tài)成員變量

<3>說明

1.必須使用初始化列表的情況:

  • 常量成員:因為常量只能初始化不能賦值,所以必須放在初始化列表里面。
  • 引用類型,引用必須在定義的時候初始化,并且不能重新賦值,所以也要寫在初始化列表里面。
  • 沒有默認構(gòu)造函數(shù)的類類型,因為使用初始化列表可以不必調(diào)用默認構(gòu)造函數(shù)來初始化,而是直接調(diào)用拷貝構(gòu)造函數(shù)初始化。

2.初始化列表與構(gòu)造函數(shù)內(nèi)部成員賦值的區(qū)別: 成員變量初始化與成員變量賦值

注意:能使用初始化列表的時候盡量使用初始化列表

例子:(一般都是寫成初始化列表的形式)
把上面的構(gòu)造函數(shù)寫成初始化列表的方式:

Array():data(NULL),length(0){}
Array(int* d,int l):data(d),length(l){}
<4>成員變量的初始化順序
class Demo1{};
class Demo2{};
class Demo3{};
class Test{
public:
        Test():d1(),d3(),d2(){}
private:
      Demo1 d1;
      Demo2 d2;
      Demo3 d3;
int main(){
      Test test;
//初始化順序結(jié)果為:d1,d2,d3。
}

成員變量在使用初始化列表初始化時,與構(gòu)造函數(shù)中初始化成員列表的順序無關(guān),只與定義成員變量的順序有關(guān)。一般情況定義成員變量的順序和初始化成員列表順序一致。

<5>代碼(賬單bill)
#include <iostream>
#include <vector>
using namespace std;
class Record{
private:
    string name;
    int count;
    float price;
    float off;
public:
    Record(string name,int count,float price):name(name),count(count),price(price),off(1){}
    Record(string name,int count,float price,float off):name(name),count(count),price(price),off(off){}
    float GetTotal(){
        return count*price*off;
    }
    float GetOff(){
        return count*price*(1-off);
    }
    void Print(){
        cout << name << '\t' << count << "\t¥" << price << "\t¥" << GetTotal() << "\t¥" << GetOff() << endl;
    }
};
class Bill{
private:
    vector<Record> records;
public:
    void Add(Record r){
        records.push_back(r);
    }
    void Print(){
        cout << "物品\t數(shù)量\t單價\t總價\t節(jié)省\n";
        cout << "-------------------------------------" << endl;
        float totalPrice = 0;
        float totalOff = 0;
        for(int i=0;i<records.size();++i){
            records[i].Print();
            totalPrice+= records[i].GetTotal();
            totalOff+=records[i].GetOff();
        }
        cout << "-------------------------------------" << endl;
        cout << "總價:¥" << totalPrice << '\t' << "總節(jié)省:¥" << totalOff << endl;
    }

};

int main(){
    /*
    Record r("蘋果",3,3.5);
    Record r2("桔子",4,5.5);
    Record r3("梨",2,1.5);
    Record("香蕉",4,4.5);
    */
    Bill b;
    b.Add(Record("蘋果",3,3.5));
    b.Add(Record("桔子",4,5.5));
    b.Add(Record("梨",2,1.5,0.7));
    b.Add(Record("香蕉",4,4.5,0.8));
    b.Print();
}

(3)析構(gòu)函數(shù)

構(gòu)造函數(shù)是必須的,但是析構(gòu)函數(shù)有時不需要??!,當沒有申請新的空間時,沒有必要編寫析構(gòu)函數(shù),來釋放空間,系統(tǒng)會自動調(diào)用系統(tǒng)的析構(gòu)函數(shù)。

<1>語法
~類名(){
    函數(shù)體
}
<2>特點

1.析構(gòu)函數(shù)的函數(shù)名與類名相同
2.函數(shù)名前必須有一個~
3.沒有返回值類型、也沒有返回值
4.只能有一個析構(gòu)函數(shù)
5.沒有參數(shù)

<3>調(diào)用時機

1.對象離開作用域(作用域是以{ }開始和結(jié)束的)
2.delete調(diào)用delete時執(zhí)行,與作用域無關(guān)。

<4>默認析構(gòu)函數(shù)

類中沒有顯式定義的析構(gòu)函數(shù),編譯器就會自動為該類型生成默認析構(gòu)函數(shù)

<5>作用

釋放對象所申請占有的資源

<6>析構(gòu)順序

析構(gòu)順序和聲明順序完全相反,最先構(gòu)造的最后析構(gòu)。

C++RAII機制:
RAII(資源的取得就是初始化,Resource Acquisition Is Initialization)。步驟:1 申請資源;2 使用資源;3 釋放資源。C++語言的一種管理資源、避免泄漏的慣用法。C++標準保證任何情況下,已構(gòu)造的對象最終會銷毀,即它的析構(gòu)函數(shù)最終會被調(diào)用。簡單的說,RAII 的做法是使用一個對象,在其構(gòu)造時獲取資源,在對象生命期控制對資源的訪問使之始終保持有效,最后在對象析構(gòu)的時候釋放資源。 -- 百度百科


new/delete與malloc()與free()的區(qū)別?
1.new/delete是C++關(guān)鍵字,需要編譯器支持。malloc/free是庫函數(shù),需要頭文件支持<stdlib.h>
2.new操作符內(nèi)存分配成功時,返回的是對象類型的指針,類型嚴格與對象匹配,無須進行類型轉(zhuǎn)換,故new是符合類型安全性的操作符。而malloc內(nèi)存分配成功則是返回void * ,需要通過強制類型轉(zhuǎn)換將void指針轉(zhuǎn)換成我們需要的類型。
3.
new和delete會自動調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù);但是malloc和free不會自動調(diào)用構(gòu)造函數(shù)和析構(gòu)函數(shù)。
4.使用new操作符申請內(nèi)存分配時無須指定內(nèi)存塊的大小,編譯器會根據(jù)類型信息自行計算。而malloc則需要顯式地指出所需內(nèi)存的尺寸。
5.new內(nèi)存分配失敗時,會拋出bac_alloc異常。malloc分配內(nèi)存失敗時返回NULL。


(4)this指針

this 是 C++ 中的一個關(guān)鍵字,也是一個 const 指針,它指向當前對象,通過它可以訪問當前對象的所有成員。所謂當前對象,是指正在使用的對象。例如對于stu.show(),stu 就是當前對象,this 就指向 stu。

<1>作用域

類的內(nèi)部。

<2>特點
  • 類的一個自動生成、自動隱藏的私有成員
  • 每個對象僅有一個this指針
  • 當一個對象被創(chuàng)建時,this指針就存放指向?qū)ο髷?shù)據(jù)的首地址
  • 不是對象本身的一部分,不會影響sizeof(對象)的結(jié)果
  • 不能在 static 成員函數(shù)中使用

如果成員函數(shù)形參與成員變量同名,使用this->做為前綴區(qū)分。

class Student{
public:
    void setname(string name);
    void setage(int age);
    void show();
    
private:
    string name;
    int age;
};
void Student::setname(string name){
    this->name = name;//創(chuàng)建的類中成員變量也有name,使用name=name是無效的
//它的形參是name,和成員變量name重名,如果寫作name = name;
//這樣的語句,就是給形參name賦值,而不是給成員變量name賦值
}
void Student::setage(int age){
    this->age = age;
}
void Student::show(){
    cout<<this->name<<"的年齡是"<<this->age<<endl;
}
void Student::printThis(){
    cout<<this<<endl;
}

int main(){
    Student *pstu = new Student;//創(chuàng)建了class指針
    pstu -> setname("jack");//必須通過->來訪問
    pstu -> setage(16);
    pstu -> show();//jack的年齡是16

Student *pstu1 = new Student;
pstu1 -> printThis();
cout<<pstu1<<endl;
輸出一樣:0x7b17d8    0x7b17d8
Student *pstu2 = new Student;
pstu2 -> printThis();
cout<<pstu2<<endl;
輸出一樣:0x7b17f0     0x7b17f0
}
  • 注意,this 是一個指針,要用->來訪問成員變量或成員函數(shù);
  • this 雖然用在類的內(nèi)部,但是只有在對象被創(chuàng)建以后才會給 this 賦值,并且這個賦值的過程是編譯器自動完成的,不需要用戶干預,用戶也不能顯式地給 this 賦值。本例中,this 的值和 pstu 的值是相同的。
<3>實質(zhì):

this實際上是成員函數(shù)的一個形參,在調(diào)用成員函數(shù)時將對象的地址作為實參傳遞給this。不過 this 這個形參是隱式的,它并不出現(xiàn)在代碼中,而是在編譯階段由編譯器默默地將它添加到參數(shù)列表中。


C++函數(shù)傳參的三種方式:
訪問內(nèi)存的方式:變量,指針,引用
1.傳值
//void Swap(int a,int b);
//Swap(n,m);
//等價于:int a=n;int b=m;(賦值操作,不能修改值)
2.傳地址/指針
////void Swap(int* a,int* b);
//Swap(&n,&m);
//等價于:int* a=&n;int* b=&m;(把地址傳進去,可以修改)
3.傳引用
//void Swap(int& a,int& b);
//Swap(n,m);
//等價于:int& a=n;int& b=m;(引用,可以修改)


(5)引用(別名)

<1>語法
  • 聲明:const 類型名& 對象名/類型名& 對象名
  • 使用:與對象變量、基本類型變量一樣
int a=10;
int& b=a;
cout << "&a:" <<&a <<'\t'<<"a:"<<a<<endl;
cout << "&b:" <<&b <<'\t'<<"b:"<<b<<endl;
a=100;
cout << "&a:" <<&a <<'\t<<'"a:"<<a<<endl;
cout << "&b:" <<&b <<'\t'<<"b:"<<b<<endl;
//執(zhí)行結(jié)果:a和b的地址一樣,值也一直一樣,不管修改a的值或者b的值,都一樣
&a:0x61ff08     a:10
&b:0x61ff08     b:10
&a:0x61ff08     a:100
&b:0x61ff08     b:100

引用其實就是一個別名,ab代表的是相同的對象。一個變量可以有多個別名。

    int a=10;
    int& c=a;
    int& d=a;
    cout << "&a:" << &a <<" "<< a <<endl;
    cout << "&c:" << &c <<" "<< c <<endl;
    cout << "&d:" << &d <<" "<< d <<endl;
/執(zhí)行結(jié)果:
&a:0x61ff00 10
&c:0x61ff00 10
&d:0x61ff00 10
<2>何處使用引用?
  • (1)函數(shù)的參數(shù)列表
//使用指針可以同樣實現(xiàn)
void Swap(int& n,int& m){//作為參數(shù)
    //cout << "&n:" << &n << "\t&m:" << &m << endl;//同一塊內(nèi)存
    int t=m;
    m=n;
    n=t;
}
int main(){
    int n=10;
    int m=100;
    //cout << "&n:" << &n << "\t&m:" << &m << endl;//和上面是同樣的地址
    Swap(n,m);
}
  • (2)函數(shù)的返回值
//當函數(shù)有多個返回值時,return可以返回一個,其他的可以使用引用來返回和指針類似
int divide(int n,int m,int& mod){//return返回div,mod以引用的方式返回
    int div=n/m;
    mod=n%m;
    return div;
}
    int mod;
    int div=divide(n,m,mod);
  • (3)作為成員變量 -- 對象初始化時,必須顯示初始化
    對象初始化時,必須顯示初始化
class Simple{
        int& n;
public:
  //引用成員變量必須在構(gòu)造函數(shù)初始化列表中初始化
        Simple(int& n):n(n){}
        void Print(){
                cout << "&n:" << &n << '\t' << n << endl;  
        }
};
int main(){
      int m=10;
      Simple s(m);
      s.Print();
      m=100;
      s.Print();
}
//執(zhí)行結(jié)果:
&n:0x61ff04     10
&n:0x61ff04     100
注意:如果成員變量是指針,也可以達到這種效果,但是如果是變量就不行了。
<3>引用的特點
  • 1.引用必須初始化
    int& b=a;
  • 2.引用必須使用變量來初始化,初始化常量是沒有意義的。
    int& b=10;(錯誤)
  • 3.引用初始化后不能修改,只能是一個變量的別名,不能修改為其他變量的別名,可以修改其值。
int a=10,c=20;
int& b=a;
//下面的例子:修改b的值就是修改a的值,并沒有改變b是a別名的事實,只是改變了a或者b的值。
b=12;
//b=c;//錯誤。已經(jīng)是a的別名了,不能再修改稱為c的別名了
<4>為何使用引用?
  • (1)避免對象復制
  • (2)避免傳遞空指針
  • (3)使用方便
<5>引用的作用

取代指針:都可以通過函數(shù)修改函數(shù)外部的值。

有時使用引用而不是指針:指針可能是空的,會吐核。

<6>引用和指針的區(qū)別
  • 1.指針指向一塊內(nèi)存,它的內(nèi)容是所指內(nèi)存的地址;引用是某塊內(nèi)存的別名
  • 2.引用只能在定義時被初始化一次,之后不可變;指針可變;(特點3)
  • 3.引用不能為空,指針可以為空;
  • 4.引用使用時無需解引用*,指針需要解引用;
  • 5.sizeof 引用 得到的是所指向的變量/對象的大小,而sizeof 指針得到的是指針本身的大??;
  • 6.對于引用類型的成員變量所屬類的大小是按照指針大小計算,自身大小按照自身類型計算。(計算類的sizeof時并不計算成員函數(shù),只計算成員變量)
    //各種類型指針的大小(32位的編譯器是4,64位的是8)
    int* pi =&i;
    char* pc=&c;
    bool* pb=&b;
    float* pf=&f;
    double*pd=&d;
    cout << "sizeof(pi):" << sizeof(pi) <<endl;
    cout << "sizeof(pc):" << sizeof(pc) <<endl;
    cout << "sizeof(pb):" << sizeof(pb) <<endl;
    cout << "sizeof(pf):" << sizeof(pf) <<endl;
    cout << "sizeof(pd):" << sizeof(pd) <<endl;
    //各種引用的大小
    int& ri=i;
    char& rc=c;
    bool& rb=b;
    float& rf=f;
    double& rd=d;
    cout << "sizeof(ri):" << sizeof(ri) <<endl;//4
    cout << "sizeof(rc):" << sizeof(rc) <<endl;//1
    cout << "sizeof(rb):" << sizeof(rb) <<endl;//1
    cout << "sizeof(rf):" << sizeof(rf) <<endl;//4
    cout << "sizeof(rd):" << sizeof(rd) <<endl;//8

引用類型的成員變量在計算類/對象的sizeof和單獨計算時是不同的。

class Reference{
    int& ri;
    char& rc;
    bool& rb;
    float& rf;
    double& rd;
public: 
    Reference(int& i,char& c,bool& b,float& f,double& d):ri(i),rc(c),rb(b),rf(f),rd(d){}
    void PrintSize(){
        cout << "sizeof(ri):" << sizeof(ri) <<endl;//4
        cout << "sizeof(rc):" << sizeof(rc) <<endl;//1
    }
};
sizeof(Reference)=20;

(6)拷貝/復制構(gòu)造函數(shù)

<1>語法
類名(類名& 形參){ 
    函數(shù)體
}
或者:
類名(const 類名& 形參){ 
    函數(shù)體
}

只有拷貝構(gòu)造函數(shù)必須使用引用,其他所有函數(shù)使用引用只是避免對象傳參時多余的拷貝構(gòu)造。不使用引用會調(diào)用拷貝構(gòu)造函數(shù),但是該函數(shù)不使用引用,自己沒法調(diào)用自己啊。

<2>調(diào)用時機

1.手動調(diào)用

類名 對象名;  // 調(diào)用默認構(gòu)造函數(shù)
類名 對象2 = 對象1;    // 調(diào)用復制構(gòu)造函數(shù)
類名 對象3(對象1);     // 調(diào)用復制構(gòu)造函數(shù)

2.自動調(diào)用

  • 一個對象作為函數(shù)參數(shù),以值傳遞的方式傳入函數(shù)體
    參數(shù)前沒有加引用,且沒有拷貝構(gòu)造函數(shù),會造成多次釋放,解決:拷貝構(gòu)造函數(shù)
  • 一個對象作為函數(shù)返回值,以值從函數(shù)返回
  • 一個對象需要通過另外一個對象進行初始化
  • 一個對象拷貝構(gòu)造,它的成員對象自動調(diào)用拷貝構(gòu)造
  • 子對象拷貝構(gòu)造父對象自動調(diào)用拷貝構(gòu)造。
<3>實例
#include <iostream>
#include <cstring>
using namespace std;
class Array{
public:
    int* data;
    int size;
public:
    Array():data(NULL),size(0){
        cout << "Array Default Construction" << " this:" << this << " &data:"<< data <<endl;
        
    }
    Array(const Array& arr){//拷貝/賦值構(gòu)造函數(shù)(也可以寫成初始化列表的形式)
        cout << arr.data << endl;
        data=new int[arr.size];
        memcpy(data,arr.data,sizeof(int)*arr.size);//int類型可以這樣拷貝
        size=arr.size;
        cout << "Array Copy Construction:" <<" this:" << this << " &data:"<< data <<endl;
    }
    ~Array(){
        cout << "Array Destruction Free address:" <<" this:" << this << " &data:"<< data << endl;
        delete [] data; 
        data=NULL;
        size=0;
    }
    void push_back(int val){
        ++size;
        int* tmp=new int[size];
        memcpy(tmp,data,sizeof(int)*(size-1));
        tmp[size-1]=val;
        delete [] data;
        data=tmp;
    }
    int get_size(){
        return size;
    }
    int& at(int index){
        return data[index];
    }
};
//在arr前面加上&可以解決,否則就會調(diào)用拷貝構(gòu)造函數(shù),
//來隱式建立一個新的對象,并在對象作用域結(jié)束的時候調(diào)用析構(gòu)函數(shù)來銷毀新建的對象。
void PrintArray(Array arr){//4.copy(對象d),free(d)
    for(int i=0;i<arr.get_size();++i){
        cout << arr.at(i) << endl;
    }
}
Array CreateArr(){
//不加構(gòu)造函數(shù)編譯可以通過,由于沒有初始化分配內(nèi)存會導致吐核。
    Array arr;//1.default(對象a)
    arr.push_back(100);
    arr.push_back(200);
    arr.push_back(300);
    return arr;//2.copy(對象b),free(a)
}
int main(){
    Array arr=CreateArr();//3.copy(對象c),free(b)
    for(int i=0;i<arr.get_size();++i){//修改返回值(+10)
        int& t=arr.at(i);//必須使用引用來接返回值
        t +=10;
    }
    PrintArray(arr);
}//5.free(c)

  • 實例分析:name指針被分配一次內(nèi)存,但是程序結(jié)束時該內(nèi)存卻被釋放了兩次,會導致崩潰!
  • 原因:這是由于編譯系統(tǒng)在我們沒有自己定義拷貝構(gòu)造函數(shù)時,會在拷貝對象時調(diào)用默認拷貝構(gòu)造函數(shù),進行的是淺拷貝!即對指針name拷貝后會出現(xiàn)兩個指針指向同一個內(nèi)存空間
  • 解決:在對含有指針成員的對象進行拷貝時,必須要自己定義拷貝構(gòu)造函數(shù),使拷貝后的對象指針成員有自己的內(nèi)存空間,即進行深拷貝,這樣就避免了內(nèi)存泄漏發(fā)生。
  • RVO和NRVO機制:
    返回值優(yōu)化(Return Value Optimization,簡稱RVO),是這么一種優(yōu)化機制:當函數(shù)需要返回一個對象的時候,如果自己創(chuàng)建一個臨時對象用戶返回,那么這個臨時對象會消耗一個構(gòu)造函數(shù)(Constructor)的調(diào)用、一個復制構(gòu)造函數(shù)的調(diào)用(Copy Constructor)以及一個析構(gòu)函數(shù)(Destructor)的調(diào)用的代價。而如果稍微做一點優(yōu)化,就可以將成本降低到一個構(gòu)造函數(shù)的代價,也就是將內(nèi)容直接構(gòu)造到左值中,中間不生成臨時變量。(匿名對象)。
    NRVO,即Named Return Value Optimization,有名字的返回值優(yōu)化。
class BigObject{};
BigObject foo(){return BigObject(); // RVO(匿名對象)}
BigObject bar(){
    BigObject localObj;
    return localObj; // NRVO(有名字的對象)
}

gcc/clang自動RVO/NRVO優(yōu)化,不執(zhí)行拷貝構(gòu)造函數(shù),所以在執(zhí)行時看不到執(zhí)行了拷貝構(gòu)造函數(shù)。當不忽略拷貝構(gòu)造函數(shù),可以在編譯命令添加選項禁止-fno-elide-constructors;
VC在Debug環(huán)境下返回值執(zhí)行拷貝構(gòu)造函數(shù),在Release環(huán)境下實施RVO/NRVO優(yōu)化。

<4>默認拷貝構(gòu)造函數(shù)

如果沒有自己定義拷貝構(gòu)造函數(shù),編譯器會調(diào)用默認的拷貝構(gòu)造函數(shù),和之前不一樣的是(默認構(gòu)造函數(shù)和默認析構(gòu)函數(shù)不會做任何事情),但是默認的拷貝構(gòu)造函數(shù)會執(zhí)行淺拷貝,即只拷貝內(nèi)存地址.

  • 作用:復制一個已經(jīng)存在的對象
  • 本質(zhì):內(nèi)存拷貝(淺拷貝)
  • 解決:必須要自己定義拷貝構(gòu)造函數(shù),使拷貝后的對象指針成員有自己的內(nèi)存空間,即進行深拷貝,這樣就避免了內(nèi)存泄漏發(fā)生。
  • 問題:
    1.一個類可以有多個拷貝構(gòu)造函數(shù),加上const。
    2.拷貝構(gòu)造函數(shù)的參數(shù)必須是引用;
    3.禁止使用拷貝構(gòu)造函數(shù):
    目的:編寫的類不允許其他人拷貝。
    兩種方法:
    1)添加私有的拷貝構(gòu)造函數(shù)c++98:
    private:
    Simple(const Simple&);//私有的拷貝構(gòu)造函數(shù)聲明C++98
    報錯:Simple::Simple(const Simple&)' is private within this context
    2)拷貝構(gòu)造函數(shù)=delete;
    Simple(const Simple&)=delete;//C++11
    報錯:use of deleted function 'Simple::Simple(const Simple&)
<5>什么時候需要自己定義拷貝構(gòu)造函數(shù)
  • 1.三大定律(The BigThree)。
  • 2.一般來說你在類中進行了new操作,你就需要析構(gòu)函數(shù),在你需要析構(gòu)函數(shù)的類中,一般需要加上挎貝構(gòu)造函數(shù)和賦值函數(shù)。
  • 3.下面三種對象需要調(diào)用拷貝構(gòu)造函數(shù)(有時也稱“復制構(gòu)造函數(shù)”)
    1)。 一個對象作為函數(shù)參數(shù),以值傳遞的方式傳入函數(shù)體;
    2)。一個對象作為函數(shù)返回值,以值傳遞的方式從函數(shù)返回;
    3)。一個對象用于給另外一個對象進行初始化(常稱為復制初始化)
    通常的原則是:①對于凡是包含動態(tài)分配成員或包含指針成員的類都應該提供拷貝構(gòu)造函數(shù);②在提供拷貝構(gòu)造函數(shù)的同時,還應該考慮重載"="賦值操作符號。

深拷貝和淺拷貝最根本的區(qū)別在于是否真正獲取一個對象的復制實體,而不是引用。
假設B復制了A,修改A的時候,看B是否發(fā)生變化:

  • 如果B跟著也變了,說明是淺拷貝,拿人手短?。ㄐ薷亩褍?nèi)存中的同一個值)
  • 如果B沒有改變,說明是深拷貝,自食其力?。ㄐ薷亩褍?nèi)存中的不同的值)

深拷貝(Memberwise Copy)與淺拷貝(Bitwise Copy):

  • 淺拷貝:淺拷貝只是對指針的拷貝,拷貝后兩個指針指向同一個內(nèi)存空間,對帶有指針的類淺拷貝會引發(fā) memory leak,動態(tài)分配內(nèi)存的淺拷貝就是只拷貝了內(nèi)存地址,會造成二次/多少次釋放,解決方法:深拷貝
  • 深拷貝:深拷貝不但對指針進行拷貝,而且對指針指向的內(nèi)容進行拷貝,經(jīng)深拷貝后使這個增加的指針指向這個新的內(nèi)存。

(7)賦值運算符重載函數(shù)

  • 類和結(jié)構(gòu)體可以直接賦值,但是數(shù)組不行。
<1>語法
類名& operater=(const 類名& 形參){
  // 賦值操作
  return *this;
}

只有拷貝構(gòu)造函數(shù)必須使用引用,其他所有函數(shù)使用引用只是避免對象傳參時多余的拷貝構(gòu)造

<2>調(diào)用時機
  • 賦值
<3>默認賦值運算符重載函數(shù)
  • 內(nèi)存拷貝(淺拷貝)
<4>理解
  • 拷貝構(gòu)造函數(shù)用于解決初始化等三種情況而導致的內(nèi)存二次釋放問題;
  • 賦值運算符重載函數(shù)用于解決類的賦值問題而導致的內(nèi)存二次釋放問題。
  • 如果沒有定義賦值運算符重載,編譯器會調(diào)用默認的賦值操作(淺拷貝),如果自己定義了該函數(shù),則編譯器就會按照該函數(shù)來進行。
  • 禁用賦值運算符重載函數(shù)與禁用拷貝構(gòu)造函數(shù)一樣。

初始化和賦值的區(qū)別:
1.普通情況下,初始化和賦值好像沒有什么特別去區(qū)分它的意義。int a=100;和int a;a=100之間仿佛沒有任何區(qū)別,但是對于c++的類就不同了:初始化不是賦值,初始化是創(chuàng)建變量的時候賦予其一個初始值,而賦值的含義是把對象的當前值擦除,用一個新值替代。
2.本質(zhì):賦值和初始化的區(qū)別:Simple s;Simple t;t=s;(賦值操作)。賦值是兩個對象都已經(jīng)構(gòu)造好了,初始化是其中一個對象還不存在。
3.出現(xiàn)的問題:
如果類中有指針的話,賦值操作還是會造成二次釋放(淺拷貝,調(diào)用了二次析構(gòu)函數(shù)),記住賦值操作不會調(diào)用拷貝構(gòu)造函數(shù),因為對象都已經(jīng)存在不需要再重新構(gòu)造。class k=t才會調(diào)用拷貝構(gòu)造函數(shù)。

<5>實例
class Test{
private:
    int* p;
public:
    Test():p(NULL){//默認構(gòu)造函數(shù)
        cout <<this<< " Default Construction: "<< p<<endl;
    }
    Test(int n){//構(gòu)造函數(shù)重載
        p=new int(n);
        cout<<this <<" Construction: "<<p<<endl;
    }
    Test(const Test& s){//拷貝構(gòu)造函數(shù)
        p=new int(*(s.p));
        cout << this<<" Copy Construction: "<<p<<endl;
    }
    Test& operator=(const Test& s){//賦值運算符重載函數(shù)
        if(this==&s) return *this;//1.判斷是否是自身賦值
        if(NULL!=this->p){//2.刪除之前申請的內(nèi)存(防止之前的對象賦值過),防止內(nèi)存泄漏
            delete p;
        }
        p=new int(*(s.p));//深拷貝
        cout << this<<" Operator: "<<p<<endl;
        return *this;//3.返回當前對象
    }
    ~Test(){//析構(gòu)函數(shù)
        cout << this<<" Destruction: "<<p<<endl;
        delete p;
    }
};
int main(){
    Test s(10);//重載
    //初始化操作
    //Test s1=s;//拷貝
    Test s2;//默認
    //賦值操作不是初始化
    s2=s;//賦值運算符重載函數(shù)
//(對于類來說,寫不寫賦值構(gòu)造函數(shù)都一樣),上面的等式可以寫成:s2.operate=(s);
//基本類型是沒有這種函數(shù)的。
}

對于類中的成員變量是非指針時,寫不寫賦值運算符都一樣。

<6>賦值運算符重載的禁用

和拷貝構(gòu)造函數(shù)的禁用一樣!??!
c++98和c++11兩種。

//(1)c++98
private:
      類名& operator=(const 類名& 對象形參);//并且不在函數(shù)中實現(xiàn)
//(2)c++11
public:
     類名& operator=(const 類名& 對象形參)=delete;
<7>三大定律(Rule of three/The Big Three)

如果類中明確定義下列其中一個成員函數(shù),那么必須連同其他二個成員函數(shù)編寫至類內(nèi),即下列三個成員函數(shù)缺一不可:

  • 析構(gòu)函數(shù)(destructor)
  • 拷貝構(gòu)造函數(shù)(copy constructor)
  • 復制賦值運算符(copy assignment operator)

(8)友元細說

友元函數(shù)沒有 this 指針,因為友元不是類的成員。只有成員函數(shù)才有 this 指針。

<1>作用

非成員函數(shù)訪問類中的私有成員

<2>分類
  • 1.全局友元函數(shù):
    將全局函數(shù)聲明成友元函數(shù)。盡管友元函數(shù)的原型有在類的定義中出現(xiàn)過,但是友元函數(shù)并不是成員函數(shù),是全局函數(shù)。有兩種寫法:
//(1)在類中只寫【friend 函數(shù)聲明】,不寫其實現(xiàn),實現(xiàn)在類外面。
class 類名{
public:
    friend void Func(參數(shù));
};
void Func(參數(shù)){
    函數(shù)體;
}
//(2)在類中直接完成全局函數(shù)的實現(xiàn),在函數(shù)名前面加上friend。
class 類名{
public:
      firend void Func(參數(shù)){
            函數(shù)體;
      }
};
  • 2.友元類:
    將整個類聲明為友元。類2中的所有成員都可以訪問類1中的私有成員。
class 類名1{
public:
        friend 類名2;
};
  • 3.友元成員函數(shù)
    一個類中的某個成員函數(shù)需要訪問另一個類的私有成員,可以將這個成員函數(shù)寫成另一個類的友元成員函數(shù)。

需要分為.h和.cpp(聲明和實現(xiàn))兩個文件來編寫,有5個文件(兩個類的聲明,兩個類的實現(xiàn),及main函數(shù))。
注意:(1)如果一個文件需要知道類的成員信息,要include頭文件
(2)如果一個文件只需要只知道類名,可以使用類的前置聲明.
如果用反或者多用的話都會報錯。

<3>特點
  • 單向性:
    類A是類B的友元,但B不一定是類A的友元。
  • 不可傳遞性
    若類B是類A的友元,類C是B的友元,類C不一定是類A的友元
  • 不能被繼承
    父類是類A的友元,但是其父類的子類不是類A的友元。

(9)const限定符

<1>本質(zhì)
  • 只讀(read only)
<2>const與變量和對象
  • 格式
(1)const 類型 變量 = 初始值;
或者:類型 const 變量 = 初始值;//一般采用這種寫法,const在類型的后面
int const a=2;
(2)類型 const 對象;
  • 理解
    1.定義時必須初始化
    2.全局作用域聲明的const變量默認作用域是定義所在文件
    3.const對象只能調(diào)用const成員函數(shù)
    4.const與宏定義#define的區(qū)別
<3>const與指針
<4>const與引用
類型 const& 變量=初始值
const 類型& 變量=初始值
int a=10;
const int& b=a;
b=20;//錯誤
a=20;//可以

引用對象的值不可以改變。

<5>const與函數(shù)的參數(shù)和返回值
  • 1.const修飾函數(shù)參數(shù)
    如果函數(shù)內(nèi)部不會改變參數(shù)的值,參數(shù)定義成const& 類型,否則定義成&類型。
void Func(const Simple& s){
    s.Print();
}
(1)不加引用會導致調(diào)用拷貝構(gòu)造函數(shù)(默認淺拷貝),造成多次釋放,所以加&
(2)不加const只能接受非const成員,不能接受const成員和匿名成員,
(這就是為什么拷貝構(gòu)造函數(shù)的參數(shù)要加const的原因,因為不知道傳入的參數(shù)是什么)
int main(){
    Func(s);//非const對象
    Func(k);//const對象
    Func(Simple(100));//匿名對象
}
  • 2.const修飾函數(shù)的返回值
    函數(shù)的返回值不能改變,常用于字符串/指針
//在上面的Simple類中編寫成員函數(shù)
const  int Get(){//這種情況是沒有意義的,因為copy的一份返回值賦值給a。
    return n;
}
int a=s.Get();
*********************************
const在字符串和指針中的使用
//在String類中編寫成員函數(shù)
const char* c_str(){
    return str;
}
char* s=s.c_str();//不能接收,因為返回值是const char*。
const char* s=s.c_str();//必須使用const char* 來接收,返回值是一個常量指針。
<6>const修飾的成員變量和成員函數(shù)
  • 1.const修飾成員變量
    (1)不能在類聲明中初始化const數(shù)據(jù)成員變量或者基本類型(C++98不行),但是C++11可以。
    (2)const成員變量只能在類構(gòu)造函數(shù)的初始化列表中初始化
class Simple{
    int m=0;//C++98不允許成員變量在類聲明中直接初始化
    int const n=0;
private:
    Simple():n(0){}//const成員變量必須在初始化列表中初始化
    Simple(int n):n(n){}  
};
int main(){
Simple s;
Simple t=s;//拷貝構(gòu)造,可以使用拷貝構(gòu)造函數(shù)
t=s;//如果存在const成員變量,不能使用賦值運算符
}

(3)注意:在c++11中成員變量在類定義中有初始化,并且在構(gòu)造函數(shù)中也有初始化,編譯器會自動忽略類定義中的初始化。
(4)應用:const成員變量一般用于類定義后不可修改的信息,例如:學生學號

使用const成員變量不能省略構(gòu)造函數(shù)(引用類型的成員變量相同)
使用const成員變量不能使用賦值運算符重載函數(shù)

  • 2.const修飾成員函數(shù):
    (1)非const的成員函數(shù),可以修改成員變量的值,只能被非const的對象調(diào)用
    (2)const成員函數(shù),不允許修改成員變量的值,可以被const對象和非const對象調(diào)用
class Simple{
    int n;
public:
    Simple(int n):n(n){}
    void Print()const{//const成員函數(shù),const成員函數(shù)
        cout << n << endl;
    }
    void Increase(){//非const的成員函數(shù)
        ++n;
    }
};
int main(){
    Simple s(10);//定義非const對象
    s.Increase();//非const對象可以調(diào)用非const成員函數(shù)
    s.Print();//非const對象也可以調(diào)用const成員函數(shù)
    const Simple k(20);//定義const對象
    k.Print();//const對象可以調(diào)用const成員函數(shù)
    //k.Increase();//const對象不能調(diào)用非const成員函數(shù)
}

(1)全局函數(shù)是不能被const修飾的,const只能修飾成員函數(shù)
(2) 必須在成員函數(shù)的聲明和定義后都加上const

<7>const修飾總結(jié)

(10)static限定符

<1>本質(zhì)
  • 生存周期:整個程序的生存周期。
  • 作用域:屬于類,不屬于對象
<2>語法
  • 聲明
class 類名{
     //靜態(tài)成員變量
    static 類型  變量;
    //靜態(tài)成員函數(shù)
    static 返回類型 函數(shù)(形參列表);//在返回類型前加static
};
  • 定義
 //靜態(tài)成員變量
類型  類名::變量=賦值;///靜態(tài)成員變量必須在類外面定義初始化,且沒有static
//靜態(tài)成員函數(shù)
返回類型 類名::函數(shù)(形參列表){//注意:定義函數(shù)時沒有static
    函數(shù)體;
}

靜態(tài)成員變量必須在類外面初始化,且沒有static限定符。

  • 調(diào)用
(1)通過類名(Class Name)調(diào)用:
類名::函數(shù)(實參列表);
(2)通過對象(Object)調(diào)用:
對象.函數(shù)(實參列表);
<3>規(guī)則
  • static只能用于類的聲明中,定義不能標示為static
  • 非靜態(tài)是可以訪問靜態(tài)的方法和函數(shù)
  • 靜態(tài)成員函數(shù)可以設置private,public,protected訪問權(quán)限
  • 靜態(tài)成員變量必須在類外面定義初始化
  • 靜態(tài)成員變量在對象之間可以共享數(shù)據(jù)(比如實現(xiàn)一個數(shù)據(jù)的遞增)
  • 只有靜態(tài)成員函數(shù)才可以使用【類名::函數(shù)名/變量名】來訪問。
  • 靜態(tài)成員變量的生存周期和函數(shù)一樣長
  • 對象的大小不包含靜態(tài)成員變量
<4>禁忌
  • 靜態(tài)成員函數(shù)不能訪問非靜態(tài)函數(shù)或者變量(用來修改靜態(tài)成員變量的值)
  • 靜態(tài)成員函數(shù)不能使用this關(guān)鍵字(因為屬于類,不屬于對象)
  • 靜態(tài)成員函數(shù)不能使用cv限定符(const與volatile)
<5>實例
class StaticSimple{
    string name;
    static int num;//行號(1)
    int count;//記錄對象說了多少句話
public:
    StaticSimple(const string& name):name(name),count(0){}//構(gòu)造函數(shù)
    void Print(const string& s){//const不能修飾成員函數(shù),因為count是遞增的
      cout << ++num<< " "<<name<<":\""<< s << "\""<< ++count <<endl;//(2)(5)
    }
    static void PrintComment(const string& s){
        cout << ++num<<" "<<s<<endl;//不能使用count(3)
    }
};
// 靜態(tài)成員必須在類外定義初始化
// static 關(guān)鍵字只能用在靜態(tài)成員變量的聲明前,不能用在定義初始化
/*static*/ int StaticSimple::num = 0;
int main(){
    StaticSimple s("LerBon");
    s.Print("Hello");
    //StaticSimple::Print("Hello");//(4)
    s.PrintComment("Hello World");
    StaticSimple::PrintComment("Hello World");//(4)
    StaticSimple t("Jobs");
    t.Print("Apple");//靜態(tài)變量在對象之間可以共享數(shù)據(jù),num(行號)的自增
    s.Print("What?");
    t.Print("Big Apple");
    s.Print("Ok!");
}
<6>static總結(jié)
<7>static const/static const限定符

static const/const static修飾的成員變量在類初始化必須是數(shù)字類型(int和char),把char歸到數(shù)值類型,因為里面存的是ascii碼。

class Test{
  static const int a=10;
  static const char e='a';
  //static const float b=3.14;//不行,必須在類外初始化
};
/*static*/ const float Test::b=3.14;//要加const,不能加static,為了規(guī)范一般加上省略。
  • 總結(jié)
變量類型 聲明位置
一般成員變量 在構(gòu)造函數(shù)初始化列表中初始化
const成員常量 必須在構(gòu)造函數(shù)初始化列表中初始化
static成員變量 必須在類外初始化
static const/const static成員變量 變量聲明處或者類外初始化
<8> 類和結(jié)構(gòu)體的sizeof
  • 見疑惑?。?!

(11) 內(nèi)聯(lián)函數(shù)

inline -- 宏定義的接班人
編譯時將函數(shù)體代碼和實參代替函數(shù)調(diào)用語句。

<1>本質(zhì)
  • 內(nèi)聯(lián)函數(shù)的代碼直接替換函數(shù)調(diào)用,省去函數(shù)調(diào)用的開銷
<2>條件
  • 一般用在代碼比較簡單的函數(shù)
<3>語法
  • 關(guān)鍵字inline必須與函數(shù)實現(xiàn)/定義體放在一起才能使函數(shù)成為內(nèi)聯(lián),將inline放在函數(shù)聲明前面不起任何作用,通常內(nèi)聯(lián)函數(shù)定義在頭文件中(.h),定義在實現(xiàn)中會報錯。
inline void Tickets::Get(){//inline必須寫在頭文件里面
        --count;
        ++getcount;
        cout << name << " leave " << count << " have " << getcount << endl; 
}
inline /*static*/ void Tickets::UpdateCount(int n){
    count =n;
}
  • 定義在類聲明之中的成員函數(shù)將自動地成為內(nèi)聯(lián)函數(shù);
  • 內(nèi)聯(lián)是自己定義的,編譯器在執(zhí)行時是否當作內(nèi)聯(lián),取決于編譯器自己的優(yōu)化。
<4>慎用內(nèi)聯(lián)
  • 如果函數(shù)體內(nèi)的代碼比較長,使用內(nèi)聯(lián)將導致內(nèi)存消耗代價較高
  • 如果函數(shù)體內(nèi)出現(xiàn)循環(huán),那么執(zhí)行函數(shù)體內(nèi)代碼的時間要比函數(shù)調(diào)用的開銷大
  • 不要隨便地將構(gòu)造函數(shù)和析構(gòu)函數(shù)的定義體放在類聲明中
<5>理解
  • C++編譯器直接將函數(shù)體插入在函數(shù)調(diào)用的地方;
  • 內(nèi)聯(lián)函數(shù)沒有普通函數(shù)調(diào)用時的額外開銷(壓棧,跳轉(zhuǎn),返回);
  • 內(nèi)聯(lián)函數(shù)是由編譯器的一種請求,因此編譯器可能拒絕這種請求。
  • 宏代碼片段由預處理器處理,進行簡單的文本替換,沒有任何編譯過程;
  • C++編譯器能夠進行優(yōu)化,一些函數(shù)即使沒有inline聲明,也可能被編譯器內(nèi)聯(lián)編譯

(12)運算符重載

<1>為什么要進行運算符重載

運算符重載是為了解決類對象之間的運算的,通常的運算符只用于算術(shù)運算,如常量int之間,因為編譯器已經(jīng)定義了;而一個類的兩個對象之間成員進行運算必須重新定義,讓編譯器在遇到對象運算時能按我們要求的進行運算,這就是運算符重載的意義,即重定義運算符.運算符重載的聲明operator 關(guān)鍵字告訴編譯器,它是一個運算符重載

<2>語法
  • 成員函數(shù)運算符重載
返回值類型 operator 運算符(參數(shù)){
      函數(shù)體
}
  • 友元函數(shù)運算符重載
friend 返回值類型 operator 運算符(形參列表) { 
      函數(shù)體 
} 
<3> 數(shù)學類的運算符重載(復數(shù)類)
 class Complex{
private:
    int real;
    int imag;
public:
    Complex():real(0),imag(0){}
    Complex(int real):real(real),imag(0){}//單個參數(shù)的構(gòu)造函數(shù)可以實現(xiàn)int自動轉(zhuǎn)化成Complex對象
    Complex(int real,int imag):real(real),imag(imag){}
}
void Print(){
        cout << real << "+" << imag << "i" <<endl;
    }
  • 算數(shù)運算符(+、-、*、/、%
        //算數(shù)運算符,成員函數(shù)加法
    Complex operator+(const Complex& a)const{//不用返回引用,對局部變量的引用是沒有意義的
        Complex res;//不要返回局部變量的引用,可以返回成員變量的引用
        res.real=real+a.real;
        res.imag=imag+a.imag;
        return res;
    }
    //簡寫
    Complex operator+(const Complex& a)const{
        return Complex(real+a.real,imag+a.imag);//匿名對象
    }
    //友元加法
    friend Complex operator(const Complex& a,const Complex& b){
        return Complex(a.real+b.real,a.imag+b.imag);
    }
測試:
     Complex c1(1,2);
        Complex c2(2,3);
        (c1+c2).Print();//匿名對象
      (c1+1).Print();//1會默認轉(zhuǎn)換成復數(shù)
        //c1.operator+(Complex(1)).Print();//成員函數(shù)運算符重載
    //operator+(c1,Complex(1)).Print();//友元函數(shù)運算符重載
      (1+c1).Print();
        //1.operator+(c1);錯誤
    //operator+(Complex(1),c1).Print();////友元函數(shù)運算符重載
  • 關(guān)系運算符(==、!=
//關(guān)系運算符
    bool operator==(const Complex& a)const{//不加const會在后面的友元!=中報錯
        return real==a.real && imag==a.imag;
    }
       friend bool operator!=(const Complex& a,const Complex& b){//!=
        return !(a==b);
    }
        cout << (c1!=c2) << endl;
        //c1.operator!=(c2);
    //opearator!=(c1,c2);
    //c1.operator!=(c2,X);//不寫friend表示是類的成員函數(shù),會用.調(diào)用,
  • 單目運算符(+、-
//單目算數(shù)運算
    Complex operator+(){//取正
        return *this;
    }
    Complex operator-(){//取反
        return Complex(-real,-imag);
    }
  • 前綴自增自減運算符(++、--
//前綴自增運算
    Complex operator++(){//前綴++
        ++real;
        return *this;
    }
        friend Complex operator++(Complex& a){
                ++a.real;
                 return a; 
}
  • 后綴自增自減運算符(++、--
//后綴自增運算
    Complex operator++(int){//后綴++
        Complex res=*this;
        ++real;
        return res;
    }
friend Complex operator++(Complex& a,int){//(這個是局部變量使用引用不能返回)
        Complex res=a;
        ++a.real;
        return res;
    }
<4>流運算符重載

流運算符只能使用友元函數(shù)實現(xiàn),因為函數(shù)的第一個參數(shù)必須是流對象,不是類創(chuàng)建的對象。一般在流運算符中不加endl,在外面控制回車。

    //流運算符
    friend ostream& operator<<(ostream& os,const Complex& c){
        os << c.real << '+' << c.imag<<'i';
        return os;
    }
    friend istream& operator>>(istream& is,Complex& c);//沒有const,因為c是改變的
};
istream& operator>>(istream& is,Complex& c){
    is >> c.real >> c.imag;
    return is;
}
測試:
    cin >> c;//等同于 operator>>(cin,c);
    cout << c << endl;

cin和cout中的c是字符character的意思。

<5>中括號[ ]運算符重載(下標運算符重載)

標準庫中的可以直接使用【】來打印和修改,但是自己定義的類是不能打印和修改的,因此需要重載。只有成員函數(shù)

class String{
    char* str;
public:
    //operator[]的返回類型通常為引用,否則返回值是不能修改的,因為他是一個常量,不是變量。
      //返回值不加引用可以打印,但是不能修改其值。
    char& operator[](int index){//返回值類型由成員變量的類型決定。
        return str[index];
    }
};
int main(){
    String s2("hello");//自己定義的類必須重載[ ]
    s2[0]=toupper(s2[0]);
    cout << s2[0] << endl;
}
<6>小括號()的重載(函數(shù)調(diào)用運算符重載)
class Simple{
    int n;
public:
    Simple(int n):n(n){}
    void operator()(int a){
        cout << (n+a) << endl;
    }
};
int main(){
    Simple s(10);
    s(12);//仿函數(shù),像函數(shù)
        //s.operator()(12);
輸出:22
}
<7>運算符重載的規(guī)則
  • 不能重載的運算符:成員運算符.、作用域運算符::、sizeof、條件運算符?:
  • 不允許用戶自定義新的運算符,只能對已有的運算符進行重載
  • 重載運算符不允許改變運算符原操作數(shù)的個數(shù);
  • 重載運算符不能改變運算符的優(yōu)先級
  • 重載運算符函數(shù)不能有默認的參數(shù),會導致參數(shù)個數(shù)不匹配

(13)構(gòu)造函數(shù)形參

<1>構(gòu)造函數(shù)形參的默認值
class Simple{
    int n;
public:
    //Simple():n(0){}
    Simple(int n=0):n(n){}//加默認參數(shù),可以使Simple t有意義,在不寫其他構(gòu)造函數(shù)時不會報錯。
    void operator()(int a){
        n+=a;
    }
    friend ostream& operator<<(ostream& os,const Simple& s){
        os<<s.n;
        return os;
    }
};
int main(){
    Simple t;
    cout << t <<endl;//0
    Simple s(10);
    cout << s << endl;//10
    s(12);//s.operator()(12);
    cout << s <<endl;//12
}
<2>禁止單參默認轉(zhuǎn)換

關(guān)鍵字:explicit
如果構(gòu)造函數(shù)只有一個參數(shù),使用時存在默認轉(zhuǎn)換的情況。如果在單參構(gòu)造函數(shù)前加上關(guān)鍵字explicit,可以禁止默認轉(zhuǎn)換的情況。

class A{
    int n;
public:
    explicit A(int i):n(i){
        cout << "A(" << i << ")" << endl;
    }
    A(const A& m){//拷貝構(gòu)造函數(shù)
        n=m.n;
        cout << "Copy Construct" << endl;
    }
    A operator+(A b){
        return A(n + b.n);//A(30) A(30)
    }
};
int main(){
    A a(10);  //A(10)
    A b(20);//A(20)
    
        //錯誤
    //1+a;//1.operator+(a);
    //A operator+(const A&,const A&);//如果寫的是加法的友元函數(shù)重載就可以

        //下面兩個會調(diào)用拷貝構(gòu)造函數(shù)
        a.operator+(b);
    a+b;

      //在不加關(guān)鍵字時,下面是對的,1會默認轉(zhuǎn)換為A(1)為匿名對象,不會調(diào)用拷貝構(gòu)造函數(shù)。
    //a+1;
    //a.operator+(A(1));//默認轉(zhuǎn)換
      
    //在不加explicit時,100會自動轉(zhuǎn)換為A(100),下面等式可以理解為A c=A(100),不會調(diào)用拷貝構(gòu)造函數(shù)。
        A c=100;    
}
因此:匿名對象初始化一個對象時,是不會調(diào)用拷貝構(gòu)造函數(shù)的。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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