1. 程式人生 > >10-C++遠征之模板篇-學習筆記

10-C++遠征之模板篇-學習筆記

字符串鍵 什麽 如果 containe 代碼示例 默認 顯示 函數重載 由於

C++遠征之模板篇

將會學到的內容:

  • 模板函數 & 模板類 -> 標準模板類
  • 友元函數 & 友元類
  • 靜態數據成員 & 靜態成員函數
  • 運算符重載: 一切皆有可能

友元函數

函數定義分類:

1. 全局函數
2. 成員函數

友元全局函數

例子:

class Coordinate
{
    friend void printXY(Coordinate &c);
public:
    Coordinate(int x,int y);
private:
    int m_iX;
    int m_iY;
}

關鍵字friend + 聲明友元函數(對象的引用或指針)

  • 傳入引用或指針訪問速度更快,不提倡直接傳入對象。

技術分享圖片

因為我們在main函數中想使用printxy來調用坐標類的私有數據成員。
前提是我們需要在被調用的坐標類中聲明該函數為友元函數。

友元成員函數

友元成員函數定義在類中,並把該函數聲明為另外一個類的友元函數。

class Coordinate
{
    friend void Circle::printXY(Coordinate &c);
public:
    Coordinate(int x,int y);
private:
    int m_iX;
    int m_iY;
}

此時的printXY並不是一個全局函數。而是一個Circle類中的成員函數。
也就是現在Circle的成員函數想使用坐標類的私有數據,那麽我們就要去Coordinate中去聲明。

class Circle
{
public:
    void printXY(Coordinate &c)
    {
        cout << c.m_iX << c.m_iY;
    }
}

int main()
{
    Coordinate coor(3,5);
    Circle circle;
    circle.printXY(coor);
    return 0;
}

友元函數的風險: 破壞了Coordinate的封裝性。

友元函數編碼實現

技術分享圖片

友元全局函數

2-2-FriendFunctionGlobal

Time.h

#ifndef TIME_H
#define TIME_H

#include <iostream>
using namespace std;

class Time
{
    friend void printTime(Time &t);//重點
public:
    Time(int hour,int min,int sec);

private:
    int m_iHour;
    int m_iMinute;
    int m_iSecond;

};
#endif

Time.cpp

#include "Time.h"

Time::Time(int hour, int min, int sec)
{
    m_iHour = hour;
    m_iMinute = min;
    m_iSecond = sec;
}

main.cpp

#include <iostream>
#include <stdlib.h>
#include "Time.h"
using namespace std;

void printTime(Time &t);
int main()
{
    Time t(6, 34, 35);
    printTime(t);
    system("pause");
    return 0;
}

void printTime(Time &t)
{
    cout << t.m_iHour << endl;
    // Time::m_iHour”: 無法訪問 private 成員(在“Time”類中聲明)
    cout << t.m_iMinute << endl;
    cout << t.m_iSecond << endl;
}

技術分享圖片

如上圖,看到是可以訪問到t內部的私有數據成員。

友元成員函數

2-2-FriendMemberFunction

Time.h

#ifndef TIME_H
#define TIME_H
#include "Match.h"
#include <iostream>
using namespace std;

class Time
{
    friend void Match::printTime(Time &t);
    //重點,建議寫在最外面。但是放在public,private都不影響。
public:
    Time(int hour,int min,int sec);

private:
    int m_iHour;
    int m_iMinute;
    int m_iSecond;
};
#endif

Time.cpp

#include "Time.h"

Time::Time(int hour, int min, int sec)
{
    m_iHour = hour;
    m_iMinute = min;
    m_iSecond = sec;
}

Match.h

#ifndef MATCH_H
#define MATCH_H

class Time;//聲明有這樣一個類
class Match
{
public:
    void printTime(Time &t);//
};

#endif

Match.cpp

#include "Match.h"
#include "Time.h"
#include <iostream>

using namespace std;

void Match::printTime(Time &t)
{
    cout << t.m_iHour << ":" << t.m_iMinute << ":" << t.m_iSecond << endl;
}

main.cpp

#include <iostream>
#include <stdlib.h>
#include "Time.h"
#include "Match.h"
using namespace std;

int main()
{
    Time t(6, 34, 35);
    Match m;
    m.printTime(t);
    system("pause");
    return 0;
}

技術分享圖片

Match的printTime函數想要用t裏面的私有數據,所以必須去向Time申請:也就是Time類需要聲明friend void Match::printTime(Time &t);,Match的這個方法才可以訪問它。

而Match因為要訪問到Time內部的數據,所以Match要聲明Time類:class Time;

友元函數的聲明可以寫在public,private,類內全局都可以。但建議寫在類最前面。

單元鞏固

定義Coordinate類,並將全局display函數聲明為Coordinate類的友元函數
Coordinate類數據成員m_iXm_iY

display函數用於顯示m_iXm_iY

#include <iostream>
using namespace std;

/**
 * 定義Coordinate類
 * 友元函數:display
 * 數據成員:m_iX、m_iY
 */
class Coordinate
{
    // 友元函數
    friend void display(Coordinate &coor);
public:
    Coordinate(int x, int y)
    {
        m_iX = x;
        m_iY = y;
    }
public:
    int m_iX;
    int m_iY;
};

/**
 * display函數用於顯示m_iX、m_iY的值
 */
void display(Coordinate &coor)
{
    cout << "m_iX:" << coor.m_iX << endl;
    cout << "m_iY:" << coor.m_iY << endl;
}

int main(void)
{
    // 實例化Coordinate對象
    Coordinate coor(0,0);
    // 調用display函數
    display(coor);
    return 0;
}

技術分享圖片

友元類

class Circle;//聲明類的存在

class Coordinate
{
    friend Circle;//聲明友元類。
public:
    Coordinate(int x,int y)
private:
    int m_iX;
    int m_iY;
}
class Circle
{
public:
    void printXY()
    {
        cout << m_coor.m_iX << m_coor.m_iY;
    }
private:
    Coordinate m_coor; // 聲明他要用到的對象
}

任何Circle的成員函數都可以使用這個對象。

對於友元的註意事項

  • 友元關系不可傳遞 (b是a的朋友,c是b的朋友,c不一定是a的朋友)
  • 友元關系的單向性。 (單向好友關系)
  • 友元聲明的形式與數量不受限制。 (好友人數不設上限,好友可以是類,函數的混搭)

友元只是封裝的補充(不得已的做法, 破壞了封裝性): 定向的暴露。

友元類編碼實現

技術分享圖片

2-5-FriendClass

Time.h

#ifndef TIME_H
#define TIME_H

class Match;//
class Time
{
    friend Match;// 聲明自己友元
public:
    Time(int hour,int min,int sec);

private:
    void printTime();
    int m_iHour;
    int m_iMinute;
    int m_iSecond;

};
#endif

Time.cpp

#include "Time.h"
#include <iostream>
using namespace std;

Time::Time(int hour, int min, int sec)
{
    m_iHour = hour;
    m_iMinute = min;
    m_iSecond = sec;
}
void Time::printTime()
{
    cout << m_iHour << "時" << m_iMinute << "分" << "秒" << endl;
}

Match.h

#ifndef MATCH_H
#define MATCH_H

#include "Time.h"

class Match
{
public:
    Match(int hour,int min,int sec);
    void testTime();
private:
    Time m_tTimer; // 聲明朋友存在
};

#endif

Match.cpp

#include "Match.h"
#include <iostream>

using namespace std;

Match::Match(int hour, int min, int sec):m_tTimer(hour, min, sec)
{

}
void Match::testTime()
{
    m_tTimer.printTime();
    cout << m_tTimer.m_iHour << ":" << m_tTimer.m_iMinute << ":" << m_tTimer.m_iSecond << endl;
}

main.cpp

#include <iostream>
#include <stdlib.h>
#include "Time.h"
#include "Match.h"
using namespace std;

int main()
{
    Match m(6, 30, 50);
    m.testTime();

    system("pause");
    return 0;
}

技術分享圖片

因為Time.h中聲明了友元類

class Match;
friend Match;// 聲明自己友元

單元鞏固

定義Time類,數據成員:m_iHour, m_iMinute,m_iSecond 成員函數:構造函數
定義Watch類,數據成員:m_tTime, 成員函數:構造函數,display用於顯示時間
Time類是Watch類的友元(Watch是Time友元類)

註:由於編譯器不同,友元類有兩種寫法.

1. friend class 類名;
2. friend 類名;

如果對象A中有對象成員B,對象B沒有默認構造函數(也就是有參數傳遞,那麽對象A必須在初始化列表中初始化對象B。

#include <iostream>
using namespace std;
class Watch;

/**
 * 定義Time類
 * 數據成員:m_iHour, m_iMinute,m_iSecond 
 * 成員函數:構造函數
 * 友元類:Watch
 */
class Time
{
    // 友元類
    friend class Watch;
public:
    Time(int hour, int min, int sec)
    {
        m_iHour = hour;
        m_iMinute = min;
        m_iSecond = sec;
    }
public:
    int m_iHour;
    int m_iMinute;
    int m_iSecond;
};

/**
 * 定義Watch類
 * 數據成員:m_tTime
 * 成員函數:構造函數
 * display用於顯示時間
 */
class Watch
{
public:
    Watch(Time &t):m_tTime(t)
    {
        
    }
    void display()
    {
        cout << m_tTime.m_iHour << endl;
        cout << m_tTime.m_iMinute << endl;
        cout << m_tTime.m_iSecond << endl;
    }
public:
    Time m_tTime;
};

int main()
{
    Time t(6, 30, 20);
    Watch w(t);
    w.display();

    return 0;
}

技術分享圖片

這裏要在Watch的構造函數中,將數據成員m_tTime填入Time的引用。

  • 友元的聲明不受訪問限定符影響,可以聲明在類中的任何位置。
  • 友元具有單向性,A是B的友元,B不一定是A的友元。
  • 友元函數和友元類必須使用關鍵字friend定義。
  • 友元不具有傳遞性

c++靜態

前面介紹過了:

  • 普通的數據成員
  • 普通的成員函數
  • const關鍵字
  • 常數據成員 & 常成員函數

關鍵字:

static: 靜態數據成員 & 靜態的成員函數

舉個例子:

class Tank
{
public:
    Tank(){s_iCount++;}
    ~Tank(){s_iCount--;}

    static int getCount(){ return s_iCount;}
    static int s_iCount;

private:
    string m_strCode;
    int Tank::s_iCount = 0; //靜態數據成員的單獨初始化
}

在原本的普通數據成員前面加上關鍵字static,就可以成為靜態數據成員。
同理,在普通的成員函數前面加上關鍵字static,就可以成為靜態成員函數。

坦克大戰中, 自己方坦克數量多就會很英勇,自己方坦克少就很懦弱。

這時我們需要讓所有的坦克對象能知道自己方有多少坦克。

  • 靜態數據成員不依賴於對象而存在,依賴於類,僅此一份。
  • 靜態成員,不必實例化就是存在的。
  • 不能在構造函數中實例化,靜態數據成員必須單獨初始化。

構造方法中坦克數量++,析構函數中坦克數量--;對於每一個坦克都可以通過訪問這個變量知道自己方還有多少坦克。

兩種訪問方法:

  • 直接通過類來訪問靜態的成員函數。
  • 對象點號訪問方式。
int main()
{
    cout << Tank::getCount() <<endl;
    cout << Tank::s_iCount <<endl;

    Tank tank;
    cout << tank.getCount() <<endl;
    cout << tank.s_iCount << endl;

    return 0;
}

內存中靜態數據成員和普通數據成員的區別。

技術分享圖片

Tank實例化出多個對象, 那麽普通數據成員code就會一個一個的誕生。
在這四個對象誕生之前, s_iCount就已經誕生了, 有且僅有這一個。

  • 靜態成員函數不能調用非靜態成員函數和非靜態數據成員(它出生那會,對象還不存在呢)
  • 非靜態成員函數可以調用靜態成員函數和靜態數據成員

對象(孫子)都沒有出生,你還是爺爺輩的人,就不能用孫子的錢。

從this指針談靜態成員函數。

class Tank
{
public:
    void fire();
    static int getCount();
private:
    string m_strCode;
    static int s_iCount;
}

當我們通過fire去調用普通和靜態成員。

// 隱形的this指針
void fire(Tank *this)
{
    this -> m_strCode = "01";
    s_iCount = 0;
}

// 靜態成員函數,不會傳入this指針
static int getCount()
{
    m_strCode = "01"; 
    // 並不會傳入this指針。
    // 並不能確定是哪個對象的成員了,
    return s_iCount;
}

註意事項

  • 靜態成員必須單獨初始化。(與類一起,不與對象一起產生)
  • 靜態成員函數不能調用非靜態成員函數和非靜態數據成員
  • 非靜態成員函數可以調用靜態成員函數和靜態數據成員
  • 靜態數據成員只有一份,且不依賴對象而存在

sizeof求對象的大小,不會包含靜態數據成員大小。

靜態成員函數編碼

技術分享圖片

3-2-StaticMemberFunction

Tank.h

#ifndef TANK_H
#define TANK_H

class Tank
{
public:
    Tank(char code);
    ~Tank();
    void fire();
    static int getCount();
private:
    static int s_iCount;
    char m_cCode;
};

#endif

Tank.cpp

#include <iostream>
#include "Tank.h"
using namespace std;

int Tank::s_iCount = 10; //構造函數之外,單獨初始化

Tank::Tank(char code)
{
    m_cCode = code;
    s_iCount++;
    cout << "tank" << endl;

}
Tank::~Tank()
{
    s_iCount--;
    cout << "~Tank()" << endl;
}

void Tank::fire()
{
    cout << "Tank--fire" << endl;
}
int Tank::getCount()
//聲明時添加static,定義時與普通一致
{
    return s_iCount;
}

main.cpp

#include "Tank.h"
#include <stdlib.h>
#include <iostream>
using namespace std;

int main()
{
    cout << Tank::getCount() << endl;
    //在類實例化之前就能使用
    Tank t1(‘A‘);
    cout << Tank::getCount() << endl;
    //初值10變成11
    cout << t1.getCount() << endl;

    // 堆上實例化自己管理內存
    Tank *p = new Tank(‘B‘);
    cout << Tank::getCount() << endl;
    Tank *q = new Tank(‘C‘);
    cout << q->getCount() << endl;

    delete p;
    delete q;

    cout << Tank::getCount() << endl;
    system("pause");
    return 0;
}

運行結果:

技術分享圖片

提醒1: 靜態的成員函數能否加上Const關鍵字。

static int getCount() const;

//錯誤,本來是給this指針加const,現在沒有指針了。
// 報錯: 靜態成員函數上不允許修飾符

在普通成員函數中調用靜態成員函數

void Tank::fire()
{
    getCount();
    cout << "Tank--fire" << endl;
}

普通成員函數中調用靜態成員函數,是可以正常調用的。

在靜態成員函數中調用普通成員函數。

int Tank::getCount()
//聲明時添加static。定義時普通
{
    fire(); //錯誤
    m_cCode = ‘C‘; // 錯誤
    // 報錯: 對非靜態成員“Tank::m_cCode”的非法引用
    return s_iCount;
}

非靜態成員函數的非法調用

  • 定義靜態成員函數和靜態數據成員都需要static關鍵字。
  • 公有靜態成員函數可以被類直接調用。
  • 靜態成員函數只能訪問靜態數據成員和調用靜態成員函數。
  • 靜態數據成員不能在構造函數初始化,必須單獨初始化。

一元運算符重載(重點難點)

給原有的運算符賦予新功能

原本+是做數字相加操作,重載為字符串拼接。

舉個栗子:

int main()
{
    string str1("mtian");
    string str2("yan");
    string str3 = str1 + "" + str2;
    cout << str3 <<endl;
    return 0; 
}

上述代碼中= , +, <<都做了重載

int main(void)
{
    Coordinate coor1(1,3);
    Coordinate coor2(2,5);
    Coordinate coor3(0,0);
    coor3 = coor1 + coor2;
    cout << coor3 << endl;
    return 0;
}

上述代碼中= , +, <<都做了重載.可以直接輸出坐標。

運算符重載的本質:函數重載

定義運算符重載的關鍵字operator

一元運算符重載

  • -(負號)的重載
  • ++符號的重載

一元運算符: 只與一個操作數運算。

-(負號)的重載:

技術分享圖片

  • 友元函數重載(類中定義一個友元函數,全局函數)
  • 成員函數重載

成員函數重載

class Coordinate
{
public:
    Coordinate(int x,int y);
    Coordinate& operator-();// 負號成員函數重載

private:
    int m_iX;
    int m_iY;
}

//實現: 隱形this指針
Coordinate& Coordinate::operator-()
//隱藏的參數
{
    m_iX = -m_iX;
    m_iY = -m_iY;
    return *this;
}

使用時

int main()
{
    Coordinate coor1(3,5);

    -coor1; //coor1.operator-();

    return 0;
}

友元函數重載

class Coordinate
{
friend Coordinate& operator-(Coordinate &coor); // 使用友元聲明全局函數
public:
    Coordinate(int x,int y);
    Coordinate& operator-();//

private:
    int m_iX;
    int m_iY;
}

//實現

Coordinate& operator-(Coordinate &coor)
{
    coor.m_iX = -coor.m_iX;
    coor.m_iY = -coor.m_iY;

    return *this;
}

//調用

int main()
{
    Coordinate coor1(3,5);

    -coor1; //operator-(cool);

    return 0;
}

成員函數重載與友元函數重載的區別。

  • operator-(cool):友元函數
  • coor1.operator-();:成員函數

++符號的重載:

  • 前置++符號重載
  • 後置++符號重載

++運算符前置重載

class Coordinate
{
public:
    Coordinate(int x,int y);
    Coordinate& operator++(); //前置++ & 成員函數

private:
    int m_iX;
    int m_iY;
}

//定義實現

Coordinate& Coordinate::operator++()
{
    m_iX++;
    m_iY++;

    return *this;
}

//使用

int main()
{
    Coordinate coor1(3,5);
    ++coor1; //coor1.operator++()

    return 0;
}

重載後置++

class Coordinate
{
public:
    Coordinate(int x,int y);
    Coordinate operator++(int);//後置++
    //1. 返回的對象為對象。
    //2. 傳入參數int。int沒有任何用。只是為了標識。

private:
    int m_iX;
    int m_iY;
}


Coordinate operator++(int)
{
    Coordinate old(*this); //保存原來的值
    m_iX++;
    m_iY++;

    return old;
}

int main()
{
    Coordinate coor1(3,5);
    coor1++; //coor1.operator(0);

    return 0;
}

一元運算符編碼實現

技術分享圖片

4-2-UnaryOperatorOverload

  • 成員函數重載

Coordinate.h

#ifndef COORDINATE_H
#define COORDINATE_H
#include <iostream>
using namespace std;

class Coordinate
{
public:
    Coordinate(int x,int y);
    Coordinate & operator-(); // 聲明運算符重載
    int getX();
    int getY();
private:
    int m_iX;
    int m_iY;

};
#endif

Coordinate.cpp

#include "Coordinate.h"

Coordinate::Coordinate(int x, int y)
{
    m_iX = x;
    m_iY = y;
}

int Coordinate::getX()
{
    return m_iX;
}

int Coordinate::getY()
{
    return m_iY;
}

// 運算符重載實現
Coordinate &Coordinate::operator-()
{
    m_iX = -m_iX;
    this->m_iY = -this->m_iY;

    return *this;
}

main.cpp

#include "Coordinate.h"
#include <iostream>
#include <stdlib.h>
using namespace std;

int main()
{
    Coordinate coor1(1, 3);
    cout << coor1.getX() << "," << coor1.getY() << endl;
    -coor1; //coor1.operator-()
    cout << coor1.getX() << "," << coor1.getY() << endl;
    system("pause");
    return 0;
}

運行結果:

技術分享圖片

  • 友元函數運算符重載

4-2-UnaryOperatorOverloadByFriendFunction

Coordinate.h:

class Coordinate
{
    friend Coordinate &operator-(Coordinate &c); //聲明友元函數運算符重載

public:
    Coordinate(int x,int y);
    int getX();
    int getY();
private:
    int m_iX;
    int m_iY;
};

Coordinate.cpp:

#include "Coordinate.h"

Coordinate::Coordinate(int x, int y)
{
    m_iX = x;
    m_iY = y;
}

int Coordinate::getX()
{
    return m_iX;
}

int Coordinate::getY()
{
    return m_iY;
}
// 運算符重載實現
Coordinate &operator-(Coordinate &c)
{
    c.m_iX = -c.m_iX;
    c.m_iY = -c.m_iY;

    return c;
}

main.cpp,沒有變化。

運行結果也沒有變化。

一元運算符編碼實現二(++運算符重載)

4-3-PlusPlusOperatorOverload

Coordinate.h

#ifndef COORDINATE_H
#define COORDINATE_H
#include <iostream>
using namespace std;

class Coordinate
{
    friend Coordinate &operator-(Coordinate &c);

public:
    Coordinate(int x,int y);
    Coordinate &operator++();//前置++
    Coordinate operator++(int);//後置++,int 標誌這是後置++。不返回引用,返回對象。

    int getX();
    int getY();
private:
    int m_iX;
    int m_iY;

};

#endif

Coordinate.cpp

#include "Coordinate.h"

Coordinate::Coordinate(int x, int y)
{
    m_iX = x;
    m_iY = y;
}

int Coordinate::getX()
{
    return m_iX;
}

int Coordinate::getY()
{
    return m_iY;
}
Coordinate &operator-(Coordinate &c)
{
    c.m_iX = -c.m_iX;
    c.m_iY = -c.m_iY;

    return c;
}
Coordinate &Coordinate::operator++()
{
    m_iX++;
    m_iY++;
    return *this;
}
Coordinate Coordinate::operator++(int)
{
    Coordinate old(*this);
    this->m_iX++;
    this->m_iY++;
    return old;
}

main.cpp

#include "Coordinate.h"
#include <iostream>
#include <stdlib.h>
using namespace std;

int main()
{
    Coordinate coor1(1, 3);
    cout << coor1.getX() << "," << coor1.getY() << endl;
    ++coor1;
    cout << coor1.getX() << "," << coor1.getY() << endl;
    -coor1; //coor1.operator-()
    cout << coor1.getX() << "," << coor1.getY() << endl;
    cout << (coor1++).getX() << ",";
    cout << (coor1++).getY() << endl;
    cout << coor1.getX() << "," << coor1.getY() << endl;
    //上面兩個分號所以coor1++執行了兩次。
    //到上一行打印的時候已經是x,y都加了2了。
    system("pause");
    return 0;
}

技術分享圖片

二元運算符重載

  • +號運算符重載方式: 友元函數重載 & 成員函數重載

+成員函數重載

class Coordinate
{
public:
    Coordinate(int x,int y);
    Coordinate operator+(const Coordinate &coor); //成員函數

private:
    int m_iX;
    int m_iY;
}


Coordinate operator+(const Coordinate &coor)
{
    Coordinate temp;
    temp.m_iX = this ->m_iX + coor.m_iX;
    temp.m_iY = this ->m_iY + coor.m_iY;

    return temp;
}

int main()
{
    Coordinate coor1(3,5);
    Coordinate coor1(4,7);
    Coordinate coor1(0,0);

    coor3 = coor1 + coor2; // coor1.operator+(coor2);operator+(coor1,coor2)

    return 0; 
}

加號運算符的友元函數重載

class Coordinate
{
friend Coordinate operator+(const Coordinate &c1, const Coordinate &c2);
public:
    Coordinate(int x,int y);

private:
    int m_iX;
    int m_iY;
}

// const限制加法不要修改加數本身的值

Coordinate operator+(const Coordinate &c1, const Coordinate &c2)
{
    Coordinate temp;
    temp.m_iX = c1.m_iX + c2.m_iX;
    temp.m_iY = c1.m_iY + c2.m_iY;

    return temp;
}

int main()
{
    Coordinate coor1(3,5);
    Coordinate coor1(4,7);
    Coordinate coor1(0,0);

    coor3 = coor1 + coor2; // 相當於調用operator+(coor1,coor2)

    return 0; 
}

重載<<輸出運算符

class Coordinate
{
friend ostream& operator <<(ostream &out,const Coordinate &coor);
// 返回值必須是ostream&
public:
    Coordinate(int x,int y);
private:
    int m_iX;
    int m_iY;
}

//實現

ostream& operator<<(ostream &out,const Coordinate &coor)
{
    out << coor.m_iX <<","<<coor.m_iY;

    return out;
}

//使用

int main()
{
    Coordinate coor(3,5);
    cout <<coor; //operator<<(cout,coor);

    return 0;
}

理解到cout是一個ostream的對象。

輸出運算符可以采用成員函數重載?

  • 使用加號運算符重載: 傳入一個參數(第二個加數),第一個加數默認當前的對象。
  • 第一個對象必須是ostream,不能是當前指針對象。

[]索引運算符重載

class Coordinate
{
public:
    Coordinate(int x,int y);
    int operator[](int index); // 成員函數
private:
    int m_iX;
    int m_iY;
}

int Coordinate::operator [](int index)
{
    if(0==index)
        {return m_iX;}
    if(1==index)
        {return m_iY;}
}

//使用
int main()
{
    Coordinate coor[3,5];
    cout << coor[0]; // coor.operator[](0);
    cout << coor[1]; // coor.operator[](1);

    return 0;
}

索引運算符可以采用友元函數重載?

不能采用友元函數重載。友元函數第一個參數可以是this指針,也可以是其他東西: 可是索引運算符,第一個必須是this指針才能指向對象。

二元運算符代碼示例

技術分享圖片

4-5-BinaryOperatorOverload

運算符的重載:對於加號運算符,輸出運算符,索引運算符。

Coordinate.h

#ifndef COORDINATE_H
#define COORDINATE_H
#include <iostream>//包含了ostream
using namespace std;

class Coordinate
{
    friend Coordinate &operator-(Coordinate &c);
    friend Coordinate operator+(Coordinate c1,Coordinate c2);
    friend ostream &operator<<(ostream &output, Coordinate &coor);
public:
    Coordinate(int x,int y);
    Coordinate &operator++();//前置++
    Coordinate operator++(int);//後置++
    //Coordinate operator+(Coordinate &c); // 成員函數重載加號
    //其實裏面有兩個參數。
    int operator [](int index);
    int getX();
    int getY();
private:
    int m_iX;
    int m_iY;

};
#endif

Coordinate.cpp

#include "Coordinate.h"

Coordinate::Coordinate(int x, int y)
{
    m_iX = x;
    m_iY = y;
}

int Coordinate::getX()
{
    return m_iX;
}

int Coordinate::getY()
{
    return m_iY;
}
Coordinate &operator-(Coordinate &c)
{
    c.m_iX = -c.m_iX;
    c.m_iY = -c.m_iY;

    return c;
}
Coordinate &Coordinate::operator++()
{
    m_iX++;
    m_iY++;
    return *this;
}
Coordinate Coordinate::operator++(int)
{
    Coordinate old(*this);
    this->m_iX++;
    this->m_iY++;
    return old;
}

//Coordinate Coordinate::operator+(Coordinate &c)
//{
//  Coordinate temp(0, 0);
//  temp.m_iX = this->m_iX +c.m_iX;
//  temp.m_iY = this->m_iY +c.m_iY;
//
//  return temp;
//}
Coordinate operator+(Coordinate c1, Coordinate c2)
{
    Coordinate temp(0, 0);
    temp.m_iX = c1.m_iX + c2.m_iX;
    temp.m_iY = c1.m_iY + c2.m_iY;

    return temp;
}

ostream &operator<<(ostream &output, Coordinate &coor)
{
    output << coor.m_iX << "," << coor.m_iY;
    return output;
}
int Coordinate::operator [](int index)
{
    if (0 == index)
    {
        return m_iX;
    }if (1 == index)
    {
        return m_iY;
    }
}

main.cpp

#include "Coordinate.h"
#include <iostream>
#include <stdlib.h>
using namespace std;

int main()
{
    Coordinate coor1(1, 3);
    Coordinate coor2(2, 4);
    Coordinate coor3(0, 0);

    coor3 = coor1 + coor2;

    //cout << coor3.getX() << "," << coor3.getY() << endl;

    cout << coor3[0] <<endl;
    cout << coor3[1] << endl;
    

    system("pause");
    return 0;
}

技術分享圖片

  • 運算符重載可以使運算符具有新的功能。
  • 運算符重載使用關鍵字operator
  • 有些運算符必須使用成員函數重載,有些則必須使用友元函數重載。

如索引運算符重載就不可以使用友元函數重載,因為索引運算符第一個參數必須傳入this。 輸出運算符<<只能用友元函數重載(因為輸出第一個參數為ostream)。

  • ++運算符重載需要區分前置++重載和後置++重載。

單元鞏固

定義Coordinate類
數據成員:m_iX, m_iY
成員函數:構造函數
重載“--”運算符,重載“+”運算符

#include <iostream>
using namespace std;

/**
 * 定義Coordinate類
 * 數據成員:m_iX,m_iY
 * 成員函數:構造函數
 * 重載--運算符,重載+運算符
 */
class Coordinate
{
public:
    Coordinate(int x, int y)
    {
        m_iX = x;
        m_iY = y;
    }
    // 前置--運算符重載
    Coordinate & operator--()
    {
        this ->m_iX--;
        this ->m_iY--;
        return *this;
    }
    
    
    // 後置--運算符重載
    Coordinate operator--(int)
    {
        Coordinate old = *this;
        this ->m_iX--;
        this ->m_iY--;
        return old;
    }
    
    
    
    // +號運算符重載
    Coordinate operator+(Coordinate c)
    {
        Coordinate temp(0,0);
        temp.m_iX = this ->m_iX + c.m_iX;
        temp.m_iY = this ->m_iY + c.m_iY;
        return temp;
    }
    
    

public:
    int m_iX;
    int m_iY;
};

int main(void)
{
    Coordinate coor1(1, 3);
    Coordinate coor2(2, 4);
    Coordinate coor3(0, 0);

    coor1--;
    --coor2;
    coor3 = coor1 + coor2;

    cout << coor3.m_iX << endl;
    cout << coor3.m_iY << endl;

    return 0;
}

運行結果:

技術分享圖片

函數模板

為什麽要使用模板?

// 比較兩個整數
int max(int a,int b)
{
    return (a>b)?a:b;
}
// 比較兩個浮點數
float max(float a,float b)
{
    return (a>b)?a:b;
}
// 比較兩個字符串
char max(char a,char b)
{
    return (a>b)?a:b;
}

三種不同類型,但是運算邏輯完全一致。

解決方案:

類型作為參數傳入。計算機來做出這三個函數。

關鍵字:template typename class

這裏的class不是類,是來表明數據類型的。

template <class T>
// 聲明一種參數類型
T max(T a,T b)     //函數模板
{
    return (a>b)?a:b;
}

// 不寫明,計算機會自動進行識別
int ival = max(100,99); //模板函數

char cval = max<char>(‘A‘,‘B‘);
//指定之後,傳入參數一定要是這種數據類型才可以

函數模板是模子,生產出來的是模板函數。
只有函數模板,計算機不會產生代碼數據; 只有使用時才會產生真正的代碼

template <typename T>
void swap(T& a,T& b)
{
    T tmp = a;
    a = b;
    b = tmp;
}

int x =20,y=30;
swap<int>(x,y);

變量作為模板參數

template <int size>
void display()
{
    cout << size <<endl;
}

display<10>();

多參數函數模板

template <typename T,typename C>
void display(T a,C b)
{
    cout << a <<" " <<b <<endl;
}

//使用
int a = 1024; string str = "helloworld";
display<int,string>(a,str);

typename 和 class可以混用

template <typename T,class U>
T minus(T* a, U b)

template <typename T,int size>
void display(T a)
{
    for(int i=0;i < size;i++)
    cout << a << endl;
}

display<int,5>(15);

函數模板與重載

函數模板可以做出無數個模板函數來。

模板函數之間形成重載。

不同的函數模板做出來的模板函數也能形成重載,比如下面:

template <typename T>
void display(T a);

template <typename T>
void display(T a,T b);//參數個數不同

template <typename T,int size>
void display(T a);

display<int>(10);
display<int>(10,20);
display<int,5>(30);//三個函數形成重載
  • 函數模板本身不會在內存中產生代碼, 因為沒有模板參數就無從知道要合成怎樣的函數

  • 模板參數可以是類型, 變量(編譯時實際上是常量), 或多個類型和變量的組合

  • 同一個函數模板的不同的模板函數之間可以看作互為重載

  • 函數名稱相同但模板參數或函數參數不同的來自不同函數模板的模板函數之間也可以互為重載

函數模板代碼實踐

技術分享圖片

5-2-FunctionTemplate

main.cpp

#include <iostream>
#include <stdlib.h>
using namespace std;

template <typename T>
void display(T a)
{
    cout << a << endl;
}

template <typename T, class S>
void display(T t,S s)
{
    cout << t << endl;
    cout << s << endl;
}

template <typename T,int Ksize>
//該變量實例化時變為常量

void display(T a)
{
    for (int i = 0; i < Ksize; i++)
    {
        cout << a << endl;
    }
}

int main()
{
    display<int>(10);
    display<double>(10.98); // 模板函數
    display<int,double>(5, 8.3);
    display<int,3>(10);
    system("pause");
    return 0;
}

技術分享圖片

  • 函數模板的參數可以是一個也可以是多個。
  • 如果函數模板的參數個數為零個就沒必要使用函數模板了。
  • 使用函數模板時,需要指定模板參數,此時的函數稱為模板函數。
  • 當需要定義多個功能相同,數據類型不同的函數時,可以使用函數模板來定義。

單元鞏固

定義一個函數模板,功能是交換兩個數的位置

5-4-swapNum

#include <iostream>
using namespace std;
#include <stdlib.h>
/**
* 定義模板函數swapNum
* 實現功能:交換兩個數的位置
*/
template <typename T>
void swapNum(T &a, T &b)
{
    T temp = a;
    a = b;
    b = temp;
}

int main(void)
{
    float x = 10.1;
    float y = 20.5;
    // 調用模板函數
    swapNum<float>(x, y);
    cout << "x = " << x << endl;
    cout << "y = " << y << endl;
    system("pause");
    return 0;
}

技術分享圖片

類模板

template <class T>
class MyArray
{
public:
    void display()
    {...}
private:
    T *m_pArr;
}

只有數據類型不同,其他基本都相同。

類外定義成員函數,需要像下面這樣:

template <class T> //每定義一個類外成員函數,都要在前面寫這行。
// 類名後面寫<T>
void MyArray<T>::display()
{

}

//使用
int main()
{
    MyArray<int> arr;
    arr.display();

    return 0;
}

類模板

類模板多個參數的使用情況

template <template T,int KSize>
class Container
{
public:
    void display();
private:
    T m_obj;
}

// 類外定義
template<typename T,int KSize>
void Container<T,KSize>::display()
{
    for (int i = 0; i < KSize; ++i)
    {
        cout << m_obj << endl;
    }
}

int main()
{
    Container<int,10> ct1;
    ct1.display();

    return 0;
}

特別提醒:

模板代碼不能分離編譯(類的聲明與定義都要寫在.h文件中)。

  • 類模板類外定義,每一個成員函數前都要寫上template<typename T, int KSize>

類模板編碼實現

技術分享圖片

5-6-ClassTemplate

MyArray.h:

#ifndef MYARRAY_H
#define MYARRAY_H

#include <iostream>
using namespace std;

template <typename T,int Ksize,int KVal>//模板參數列表
class MyArray
{
public:
    MyArray();
    ~MyArray()
    {
        delete[]m_pArr;
        m_pArr = NULL;
    }//寫在類內部的函數不需要註意什麽。
    void display();

private:
    T *m_pArr;
};
template <typename T, int KSize, int KVal>
MyArray<T, KSize, KVal>::MyArray()
{
    m_pArr = new T[KSize];
    for (int i = 0; i < KSize; i++)
    {
        m_pArr[i] = KVal;
    }
}

template <typename T,int KSize,int KVal>
void MyArray<T, KSize, KVal>::display()
{
    for (int i = 0; i < KSize; i++)
    {
        cout << m_pArr[i] << endl;
    }
}

#endif

MyArray.cpp

//什麽都不寫

main.cpp:

#include <stdlib.h>
#include <string>
#include "MyArray.h"
using namespace std;

int main()
{
    MyArray<int, 5, 6> arr;//已經形成模板類了
    arr.display();
    system("pause");
    return 0;
}

運行結果:

技術分享圖片

  • 定義一個類模板就相當於定義了一系列功能相同類型不同的類
  • 定義類模板需要使用關鍵字template
  • 模板參數既可以是類型,也可以是變量
  • 定義類模板的參數可以使用typename和class,可以混用

單元鞏固

定義一個矩形類模板
該模板中含有計算矩形面積和周長的成員函數
數據成員為矩形的長和寬。

#include <iostream>
using namespace std;

/**
 * 定義一個矩形類模板Rect
 * 成員函數:calcArea()、calePerimeter()
 * 數據成員:m_length、m_height
 */
template <typename T>
class Rect
{
public:
    Rect(T length,T height);
    T calcArea();
    T calePerimeter();
public:
    T m_length;
    T m_height;
};

/**
 * 類屬性賦值
 */
template <typename T>
Rect<T>::Rect(T length,T height)
{
    m_length = length;
    m_height = height;
}

/**
 * 面積方法實現
 */
template <typename T>
T Rect<T>::calcArea()
{
    return m_length * m_height;
}

/**
 * 周長方法實現
 */
template <typename T>
T Rect<T>::calePerimeter()
{
    return ( m_length + m_height) * 2;
}

int main(void)
{
    Rect<int> rect(3, 6);
    cout << rect.calcArea() << endl;
    cout << rect.calePerimeter() << endl;
    return 0;
}

註意事項:

template <typename T>
Rect<T>::Rect(T length,T height)//<T>加在類上而不是方法。

標準模板庫

STL: Standard Template Lib

標準模板庫就是系統已經預先寫好了的一些模板的集合,你可以直接使用(將之實例化)

常用部分:

向量(Vecotr)

向量的本質就是對數組的封裝;根據存儲的元素個數自動變長或縮短。

優秀的特點: 隨機讀取能在常數時間內完成

初始化vector對象的方式:

vector<T> v1; // vector保存類型為T的對象。默認構造函數v1為空
vector<T> v2(v1); //v2是v1的一個副本
vector<T> v3(n, i); //v3包含n個值為主的元素
vector<T> v4(n); //v4包含有值初始化元素的n個副本

// 具體的使用
vector<int> ivec1;
vector<int> ivec2(ivec1);

vector<string> svec1;
vector<string> svec2(svec1);

vector<int> ivec4(10,-1); //數組中包含10個-1
vector<string> ivec4(10,"hi"); //數組中包含10個hi

上面代碼分別是:

初始化一個空向量
用一個向量初始化另一個向量

vector常用函數:

方法 作用
empty () 判斷向量是否為空
begin() 返回向量叠代器首元素
end () 返回向量叠代器末元素的下一個元素
clear () 清空向量
front () 第一個數據
back () 最後一個數據
size () 獲得向量中數據大小
push_back (elem) 將數據插入向量尾
pop_ back() 刪除向量尾部數據

向量初始化之後必須要有配套的函數才能讓我們體會到方便,上面這些都是向量的配套函數。

實際使用中的例子如下:

int main()
{
    vector<int> vec; // 傳入參數int
    vec.push_back(10);//最尾部插入元素10
    vec.push_pop();//刪除10
    cout << vec.size()<<endl;//此時仍然為0
    return 0;
}

// 數組的遍歷
for(int k=0;k < vec.size();k++)
{
    cout << vec[k] << endl;
}

叠代器(iterator)遍歷。

int main()
{
    vector vec;
    vec.push_back("hello"); // 尾部插入很多元素
    vector<string>::iterator citer = vec.begin(); // 定義一個向量的叠代器
    // <>標明向量使用的數據類型。::標識出當前叠代器是屬於向量的叠代器
    // 叠代器的變量名citer 數據類型:`vector<string>::iterator citer`
    // 通過該叠代器指向當前向量的第一個元素。

    for (;citer != vec.end();citer++)
    //中止條件:end指當前向量vec最後一個向量的下一個位置。
    //指向下一個元素。
    {
        cout << *citer << endl; // 加*表示指向的元素值。
    }
}

list:鏈表

示意圖如下:

技術分享圖片

  • 鏈表會有一個頭結點。每一個鏈表都由若幹結點組成。
  • 一個結點都沒有稱之為空鏈表
  • 存在多個結點。第一個結點稱之為頭結點。
  • 對於每一個結點由兩部分組成。一部分是數據部分(ABCDE都是數據域),還有一部分是指針部分。
  • 指針部分用來將結點串聯起來。

雙鏈表:可以從頭找到尾,也可以從尾找到頭。

d 和 e中間插入數據,讓d指向新數據,新數據指向e就可以了。

特點:數據插入速度快(不像向量,插入一個其他的全部都得後移)

使用方法上與數組基本相同。

map:映射

技術分享圖片

鍵值映射一一對應,通過鍵找值.

map<int,string> m;//通過映射定義一個映射對象
pair<int,string> p1(10,"shanghai");//向映射中放入key&value
pair<int,string> p2(20,"beijing");

m.insert(p1); //將pair放入m中
m.insert(p2);

cout << m[10] << endl;//通過key訪問值
cout << m[20] << endl;
map<string,string> m;//通過映射定義一個映射對象
pair<string,string> p1("S","shanghai");//向映射中放入key&value
pair<string,string> p2("B","beijing");

m.insert(p1);//將pair放入m中
m.insert(p2);

cout << m["S"] << endl;//通過key訪問值
cout << m["B"] << endl;

標準模板庫代碼實現

技術分享圖片

vector使用方法:

6-2-vectorPushBackPopFor

#include <iostream>
#include <stdlib.h>
#include <vector>
#include <list>
#include <map>
using namespace std;

int main()
{
    vector<int> vec;
    
    vec.push_back(3);//從尾部去插入
    vec.push_back(4);
    vec.push_back(6);
    
    vec.pop_back();//從尾部去除一個
    
    for (int i=0;i<vec.size();i++)
    {
        cout << vec[i] << endl;
    }
    cout << "end for" << endl;
    cout << vec.size() << endl;
    return 0;
}

運行結果:

技術分享圖片

叠代器版本: 6-2-2-IteratorVector

#include <iostream>
#include <stdlib.h>
#include <vector>
#include <list>
#include <map>
using namespace std;

int main()
{
    vector<int> vec;
    
    vec.push_back(3);//從尾部去插入
    vec.push_back(4);
    vec.push_back(6);
    
    //叠代器
    vector<int>::iterator itor = vec.begin();
    
    //cout << *itor << endl; // 打印出來的是第一個元素值
    
    for (;itor != vec.end();itor++)
    {
        cout << *itor << endl;
    }
    cout << "end iterator" << endl;
    cout << vec.front() << endl;//第一個元素
    cout << vec.back() << endl;//最後一個元素
    
    return 0;
}

運行結果:

技術分享圖片

這裏請註意end獲取到的是最後一個元素的下一個位置

標準模版庫編碼實現2

list的遍歷代碼:

6-3-ListOnlyCanUseIterator

#include <iostream>
#include <stdlib.h>
#include <vector>
#include <list>
#include <map>
using namespace std;

int main()
{
    list<int> list1;
    list1.push_back(4);
    list1.push_back(7);
    list1.push_back(10);

//  for (int i=0;i<list1.size();i++)
//  {
//      cout << list1[i] << endl;//錯誤

//  }
    return 0;
}

報錯:

Type ‘list<int>‘ does not provide a subscript operator

#include <iostream>
#include <stdlib.h>
#include <vector>
#include <list>
#include <map>
using namespace std;

int main()
{
    list<int> list1;
    list1.push_back(4);
    list1.push_back(7);
    list1.push_back(10);

//  for (int i=0;i<list1.size();i++)
//  {
//      cout << list1[i] << endl;//錯誤

//  }
  list<int>::iterator itor = list1.begin();
    for (;itor != list1.end();itor++)
    {
        cout << *itor << endl;
    }
    return 0;
}

技術分享圖片

map遍歷:

6-3-MapIterator

#include <iostream>
#include <stdlib.h>
#include <vector>
#include <list>
#include <map>
#include <string>
using namespace std;

int main()
{
    map<int,string> m;
    pair<int,string> p1(3,"hello");
    pair<int,string> p2(6,"world");
    
    //m.push_back(p1); //錯誤,pushback不是map的成員
    
    m.insert(p1);
    m.insert(p2);

    cout << m[3] << endl;
    cout << m[6] << endl;

    map<int,string>::iterator itor = m.begin();
    for (;itor != m.end();itor++)
    {
        //cout << *itor << endl;//錯誤,每個都包含keyvalue
        cout << itor->first << endl;
        cout << itor->second << endl;
    }
    
    return 0;
}

運行結果:

技術分享圖片

string類型map

6-3-2-StringMap

#include <iostream>
#include <stdlib.h>
#include <vector>
#include <list>
#include <map>
#include <string>
using namespace std;

int main()
{
    map<string,string> m;
    pair<string,string> p1("H","hello");
    pair<string,string> p2("W","world");
    //m.push_back(p1);//錯誤
    m.insert(p1);
    m.insert(p2);

    cout << m["H"] << endl;
    cout << m["W"] << endl;

    map<string,string>::iterator itor = m.begin();
    for (;itor != m.end();itor++)
    {
        //cout << *itor << endl;//錯誤,每個都包含keyvalue
        cout << itor->first << endl; //輸出key
        cout << itor->second << endl;//輸出value
    }
    
    return 0;
}

運行結果:

技術分享圖片

  • vector是對數組的封裝,所以一旦對象被實例化,其大小就可以改變.大小可以根據元素數量改變
  • list的特點是數據插入速度快。
  • map需要與pair一起使用,用來存儲多個key-value對。
  • 不同廠商的標準模板庫的實現細節可以不同,基本用法及原理相同。

單元鞏固

使用vector存儲數字3,6,8,4,並遍歷。
使用map存儲S-Shang Hai B-Bei Jing G-Guang Zhou,並遍歷

6-5-VectorMapIterator

#include <vector>
#include <map>
#include <string>
#include <iostream>
using namespace std;

int main(void)
{
    // 使用vector存儲數字:3、4、8、4
    vector<int> vec;
    vec.push_back(3);
    vec.push_back(4);
    vec.push_back(8);
    vec.push_back(4);
    //循環打印數字
    
    vector<int>::iterator itor1 = vec.begin();
    for(;itor1 != vec.end();itor1++)
    {
        cout << *itor1 <<endl;
    }
    
    // 使用map來存儲字符串鍵值對
    map<string, string> m;
    pair<string, string> p1("S","Shang Hai");
    pair<string, string> p2("B","Bei Jing");
    pair<string, string> p3("G","Guang Zhou");
    m.insert(p1);
    m.insert(p2);
    m.insert(p3);
    // 打印map中數據
    map<string, string>::iterator itor2 = m.begin();
    for(;itor2 != m.end();itor2++)
    {
        cout << itor2->first <<endl;
        cout << itor2->second <<endl;
    }
    return 0;
}

運行結果:

技術分享圖片

註意事項:

 cout << itor2->first <<endl;
 cout << itor2->second <<endl;

itor1和itor2不能重名。

10-C++遠征之模板篇-學習筆記