C++远征之模板篇

本文及代码收录于个人编程笔记(整理中,欢迎Star):
https://github.com/mtianyan/Programming-Notebook

将会学到的内容:

  • 模板函数 & 模板类 -> 标准模板类
  • 友元函数 & 友元类
  • 静态数据成员 & 静态成员函数
  • 运算符重载: 一切皆有可能

友元函数

函数定义分类:

1. 全局函数
2. 成员函数

友元全局函数

例子:

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

关键字friend + 声明友元函数(对象的引用或指针)

  • 传入引用或指针访问速度更快,不提倡直接传入对象。

mark

因为我们在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;
}

mark

如上图,看到是可以访问到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_Hclass 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;
}

mark

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;
}

mark

友元类

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_Hclass 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;
}

mark

因为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;
}

mark

这里要在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;
}

内存中静态数据成员和普通数据成员的区别。

mark

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_Hclass 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变成11cout << 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;
}

运行结果:

mark

提醒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

一元运算符重载

  • -(负号)的重载
  • ++符号的重载

一元运算符: 只与一个操作数运算。

-(负号)的重载:

mark

  • 友元函数重载(类中定义一个友元函数,全局函数)
  • 成员函数重载

成员函数重载

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;
}

运行结果:

mark

  • 友元函数运算符重载

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;
}

mark

二元运算符重载

  • +号运算符重载方式: 友元函数重载 & 成员函数重载

+成员函数重载

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;
}

mark

  • 运算符重载可以使运算符具有新的功能。
  • 运算符重载使用关键字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;
}

运行结果:

mark

函数模板

为什么要使用模板?

// 比较两个整数
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;
}

mark

  • 函数模板的参数可以是一个也可以是多个。
  • 如果函数模板的参数个数为零个就没必要使用函数模板了。
  • 使用函数模板时,需要指定模板参数,此时的函数称为模板函数。
  • 当需要定义多个功能相同,数据类型不同的函数时,可以使用函数模板来定义。

单元巩固

定义一个函数模板,功能是交换两个数的位置

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;
}

mark

类模板

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;
}

运行结果:

mark

  • 定义一个类模板就相当于定义了一系列功能相同类型不同的类
  • 定义类模板需要使用关键字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; // 传入参数intvec.push_back(10);//最尾部插入元素10vec.push_pop();//删除10cout << vec.size()<<endl;//此时仍然为0return 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映射

键值映射一一对应,通过键找值.

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;//错误,每个都包含keyvaluecout << 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;//错误,每个都包含keyvaluecout << itor->first << endl; //输出keycout << 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、4vector<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++远征之模板篇-学习笔记相关推荐

  1. HarmonyOS(鸿蒙)App项目实战(1)运动手表篇学习笔记

    HarmonyOS(鸿蒙)APP项目实战(1)运动手表篇学习笔记 前言 概括 创建项目 创建主页面 创建index中的组件 设定index中组件的样式 为主页面添加应有的功能 创建倒计时页面 创建da ...

  2. Kafka入门篇学习笔记整理

    Kafka入门篇学习笔记整理 Kafka是什么 Kafka的特性 应用场景 Kafka的安装 单机版部署 集群部署环境准备 Kafka 2.x集群部署 Kafka 3.x集群部署 监听器和内外网络 K ...

  3. HTML5 权威指南第 10 章 文档分节 学习笔记

    HTML5 权威指南第 10 章 文档分节 学习笔记 第 8 章 标记文字 内容从从文字出发,专注如何将单体内容正确的呈现出来:第 9 章 组织内容 内容从段落出发,专注如何将单体内容合理的放在段落中 ...

  4. Java基础篇 学习笔记

    List item Java基础篇 学习笔记 java基础篇 第1章 计算机.程序和java概述 学习笔记 1.1什么是计算机 简单来说:计算机就是 ' 存储 ' 和 ' 处理 ' 数据的电子设备. ...

  5. C++编程语言关于模板的学习笔记

    C++编程语言关于模板的学习笔记 1.模板函数 1.1基本概念 1.2模板函数推演示例: 1.3区分完全泛化.部分特化以及完全特化 1.4注意点: 2.模板类 2.1模板类编译时的推演规则 2.2可以 ...

  6. 鸟叔linux私房菜基础篇简体,鸟叔的Linux私房菜基础篇-学习笔记(一)

    鸟叔的Linux私房菜基础篇-学习笔记(一) 开机进入命令行模式: ctrl+alt+[F1-F6]的任意键进入命令行编辑界面 ctrl+alt+F7进入图形界面模式 开始下达指令 [dmtsai@s ...

  7. 10月15日计算机视觉基础学习笔记——分割网络的设计

    文章目录 前言 一.分割器的设计 1.优化:减小 feature map 2.上采样 up sampling 二.经典分割模型的涨点方法 前言 本文为10月15日计算机视觉基础学习笔记--分割网络的设 ...

  8. 狂神说java spring篇 学习笔记

    前言 笔记整合自 未名湖畔种千玺:跟着狂神学Spring(官方资料+自己整合+细节补充+整合MyBatis) 黑心白莲:[狂神说]Spring学习笔记(全) 酷狗蛋儿的:Spring c命名和p命名空 ...

  9. 熊逸《唐诗50讲》登高篇 - 学习笔记与感想

    此篇已加入熊逸<唐诗50讲>学习笔记索引目录. 一.登高篇具体内容 在登高篇中,熊逸老师选择了6首唐诗,其中杜甫的<登高>是他最为欣赏也最为推荐的一首,认为其代表了唐诗诗歌水平 ...

最新文章

  1. RBAC基于角色的用户权限管理
  2. 基于投影仪的定位技术
  3. 每日一皮:高级开发员 VS 菜鸟开发员
  4. Java float浮点数精度丢失问题
  5. Linux poll
  6. 外设驱动库开发笔记6:AD719x系列ADC驱动
  7. GET http://127.0.0.1:8888/senchaApp3/src/NavigstionView.js?_dc=1397541249248 404 (Not Found)
  8. 登录窗体与主窗体的关闭
  9. c++ pair类型的基本问题
  10. linux下常用的dns软件,Linux常见应用--DNS服务器常见应用实现
  11. 实验02 使用网络模拟器Packet Tracer
  12. IOS 发展史各个ipone的发布时间(二)
  13. python利用四个坐标点对图片目标区域最小外接矩形进行裁剪
  14. QT框架下的OpenGL使用---实战篇---鼠标选取点对象
  15. 北漂小斌和你分享宋代建筑模数斗拱材分制的学习方法以及CAD案例图纸
  16. ubuntu硬盘序列号怎么查询_linux查看设备和硬盘序列号 ip mac地址
  17. pytorch的cuda环境搭建(GPU版本安装)
  18. 吴彩强:从表征到行动---意向性的自然主义进路
  19. UGeek大咖说 | 精彩回顾:京东商城可观测性体系的落地与实践
  20. 【复变函数】常用公式大全

热门文章

  1. Kubelet 源码剖析
  2. spring boot web 开发示例
  3. RocketMQ源码解析-PullConsumer取消息(1)
  4. mysql innodb_data_file_path_MySQL修改innodb_data_file_path参数的一些注意事项
  5. 线上频繁发生Full GC 如何调优?如何快速定位OOM、cpu飙升、线程死锁等问题
  6. Go 面试专题 | slice 扩容后的内存容量如何计算?
  7. Docker容器的单进程模型
  8. redis的内存会不会耗尽
  9. MySQL中EXPLAIN详解
  10. InnoDB存储引擎详解