友元与运算符重载
运算符重载等同于函数重载,原生运算符仅支持少数几个数据类型,重载运算符可支持自定义类型。现定义一个二维向量类:
class Vec
{
double x;
double y;
public:
Vec(double x, double y);
}
Vec::Vec(double x = 0, double y = 0) {
this->x = x;
this->y = y;
}
要求为该类实现乘法,有三种:Vec*Vec
、Vec*x
和x*Vec
。前两种乘法可直接使用成员函数来实现,如下所示:
Vec Vec::operator*(const Vec& v) {
Vec res;
res.x = this->x * v.x;
res.y = this->y * v.y;
return res;
}
Vec Vec::operator*(double r) {
Vec res;
res.x = this->x * r;
res.y = this->y * r;
return res;
}
问题是第三种。第三种乘法x*Vec
因为向量类在右边,所以不能使用成员函数来实现,但是使用非成员函数的话会存在访问权限问题。C++中友元关键字friend
修饰的函数和对象可访问类的私有属性。因此Vec.h
内容为:
#include<iostream>
using namespace std;
class Vec
{
double x;
double y;
public:
Vec(double x, double y);
Vec operator*(const Vec& v); // Vec*Vec
Vec operator*(double r); // Vec*r
friend Vec operator*(double r, const Vec& v); // r*Vec, 友元函数可访问私有属性
friend ostream& operator<<(ostream& cout, const Vec& v); // 重载输出
};
而Vec.cpp
内容为:
#include<iostream>
#include "Vec.h"
using namespace std;
Vec::Vec(double x = 0, double y = 0) {
this->x = x;
this->y = y;
}
Vec Vec::operator*(const Vec& v) {
Vec res;
res.x = this->x * v.x;
res.y = this->y * v.y;
return res;
}
Vec Vec::operator*(double r) {
Vec res;
res.x = this->x * r;
res.y = this->y * r;
return res;
}
Vec operator*(double r, const Vec& v) {
Vec res;
res.x = r * v.x;
res.y = r * v.y;
return res;
}
ostream& operator<<(ostream& cout, const Vec& v) {
cout << "<" << v.x << ", " << v.y << ">";
return cout; // 为了支持连续输出
}
模版
函数模版
当函数需要能够支持多种数据类型时,重载是一种策略,重载的问题在于需要为每一种数据类型都写一套函数代码。如果对于不同的数据类型,函数的逻辑是一样,函数重载就等同于写重复代码,对于这种情况,C++提供了模版机制,可将不同数据类型下的函数逻辑抽象成模版,而具体的类型待指定。
函数模版与函数重载的区别:
- 函数模版对不同数据类型的处理逻辑是一模一样的,而重载函数的逻辑可以不一样;
- 在参数数量不同或者逻辑不同的情况下只能使用函数重载,否则可以使用函数模版;
- 模版也是可重载的,主要解决参数数量不同和逻辑不同的问题。
比如说交换函数Swap(a,b)
,不管参数属于整形、浮点、字符、还是其他自定义的数据类型,交换两个物体的函数逻辑是一摸一样的,因此可以将该函数抽象成模版。
#include<iostream>
using namespace std;
template<typename T>
void Swap(T* a, T* b) {
T tmp = *a;
*a = *b;
*b = tmp;
}
int main(void) {
int a = 1;
int b = 2;
Swap<int>(&a, &b);
cout << a << ' ' << b << endl;
float c = 1.0f;
float d = 2.0f;
Swap<float>(&c, &d);
cout << c << ' ' << d << endl;
return 0;
}
类模板
模版这里有一个坑:模版函数的声明与实现必须都在头文件中,这是微软给出的建议,为什么要这么做的原因见此。下面实现一个弱智的栈类,Stk.h
:
#include<iostream>
using namespace std;
template<typename T>
class Stk {
T* base;
int cap;
int size;
public:
Stk(int cap);
Stk(const Stk& arr); // 拷贝构造
~Stk();
Stk& operator= (const Stk& arr); // 赋值重载
void append(const T& x);
T top();
};
template<typename T>
Stk<typename T>::Stk(int cap) {
this->cap = cap;
this->size = 0;
this->base = new T[this->cap];
}
template<typename T>
Stk<typename T>::Stk(const Stk& arr) {
this->cap = arr.cap;
this->size = arr.size;
this->base = new T[this->cap];
for (int i = 0; i < this->size; i++) {
this->base[i] = arr.base[i];
}
}
template<typename T>
Stk<typename T>::~Stk() {
if (this->base != NULL) {
delete[] this->base;
this->base = NULL;
this->size = 0;
}
}
template<typename T>
Stk<T>& Stk<typename T>::operator=(const Stk& arr) {
this->~Arr();
this->cap = arr.cap;
this->size = arr.size;
this->base = new T[this->cap];
for (int i = 0; i < this->size; i++) {
this->base[i] = arr.base[i];
}
}
template<typename T>
void Stk<typename T>::append(const T& x) {
if (this->size == this->cap) {
return;
}
this->base[this->size] = x;
this->size += 1;
}
template<typename T>
T Stk<typename T>::top() {
return this->base[size-1]; // 不考虑异常
}
main.h
:
#include<iostream>
#include<typeinfo>
#include "Stk.h"
using namespace std;
int main()
{
Stk<int> s1 = Stk<int>(5);
s1.append(0);
Stk<int> s2 = Stk<int>(s1);
s2.append(1);
Stk<int> s3 = s2;
cout<<s3.top()<<endl;
Stk<float> s4 = Stk<float>(5);
s4.append(1.2f);
cout << s4.top() << endl;
return 0;
}