友元函数与友元类

友元函数可以很方便地处理类的私有数据成员,但是也存在更改私有数据成员值的风险。

举个栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Coordinate {
friend void printXY(Coordinate &c);
//friend void Circle::printXY(Coordinate &c);
public:
Coordinate(int x, int y);
private:
int m_iX;
int m_iY;
};
class Circle {
public:
void printXY(Coordinate &c) {
cout << c.m_iX << c.m_iY << endl;
}
};
void printXY(Coordinate &c) {//直接访问私有成员
cout << c.m_iX << c.m_iY << endl;
}
int main(void) {
Coordinate coor(3, 5);
printXY(coor);
return 0;
}

友元类:使用方法与友元函数类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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 << endl;
}
private:
Coordinate coor;
}

友元注意事项:

  • 友元关系不可传递

  • 友元关系的单向性

  • 友元声明的形式及数量不受限制

友元是作为封装的补充,破坏了封装性。在程序架构阶段设计合理是可以避免使用友元的。

类模板中的友元声明:

类模板中可以出现三种友元声明,每一种都声明了与一个或多个实体的友元关系:

1、普通非模板类或函数的友元声明。也即一般的友元声明

2、类模板或函数模块的友元声明。形式如下:

1
2
3
4
5
template<typename T> class Foo1;
template<typename Type> class Bar {
template<typename T> friend class Foo1;
template<typename T> friend void func();
}

3、只授予对类模板或函数模板的特定实例的访问权的友元声明。形式如下:

1
2
3
4
5
6
template<typename T> class Foo1;
template<typename Type> class Bar {
//只有当Foo1和func()使用与Bar实例同一模板实参时才是友元,当然也可以为Foo1和func()指定特定的形参类型,比如char*
template<typename Type> friend class Foo1;
template<typename Type> friend void func();
}

静态成员static

静态数据成员依赖于类,因此即使没有实例化一个类,也可以使用静态数据成员,使用Class::mem形式使用。

不能在静态成员函数中使用非静态数据成员(因为静态成员函数的参数中不默认包含this指针

注意事项:

  • 静态数据成员必须单独初始化

  • 静态成员函数不能调用非静态成员函数和非静态数据成员

  • 静态数据成员只有一份,且不依赖对象而存在(例如sizeof(对象)的值不包含静态数据成员)

运算符重载

可以通过友元函数重载、成员函数重载两种方法完成。二者的区别在于成员函数比友元函数少了操作符操作对象本身这个参数。

但对于<<、>>操作符只能用友元函数重载,原因是此时的操作符操作对象不可省略。

[]操作符只能用成员函数重载,原因是参数中必须有this指针。

形式为Class &operator运算符(){};

对于类Coordinate:

1
2
3
4
5
6
7
8
9
class Coordinate {
public:
Coordinate(int x, int y) {
m_iX = x;
m_iY = y;
}
private:
int m_iX, m_iY;
}

例如对于+运算符(两种形式):

1
2
3
4
5
6
7
8
9
10
11
12
friend Coordinate &operator+(const Coordinate &coor1, const Coordinate &coor2) {
Coordinate tmp(0, 0);
tmp.m_iX = coor1.m_iX + coor2.m_iX;
tmp.m_iY = coor1.m_iY + coor2.m_iY;
return tmp;
}
Coordinate &operator+(const Coordinate &coor) {
Coordinate tmp(0, 0);
tmp.m_iX = this->m_iX + coor.m_iX;
tmp.m_iY = this->m_iY + coor.m_iY;
return tmp;
}

对于<<操作符:

1
2
3
4
friend ostream &operator(ostream out, const Coordinate &coor) {
out << coor.m_iX << ", " << coor.m_iY << endl;
return out;
}

对于[]操作符:

1
2
3
4
5
6
7
8
int &operator[](int index) {
if(index == 0) {
return m_iX;
}
if(index == 1) {
return m_iY;
}
}

这种形式会引发一个问题,自操作运算符的重载,比如自增运算符++,这里C++规定返回值 &operator++(){}为前置、
返回值 &operator++(int){}为后置两种形式来区分

前置++:

1
2
3
4
5
Coordinate &operator++() {
++m_iX;
++m_iY;
return *this;
}

后置++:

1
2
3
4
5
6
Coordinate &operator++(int) {
Coordinate tmp(*this);
++m_iX;
++m_iY;
return tmp;
}

函数模板

函数模板用来简化下面的操作,提取函数的共同点。

1
2
3
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>

classtypename是等效的

使用class

1
2
3
4
5
6
7
8
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');//也可指定参数类型

使用typename

1
2
3
4
5
6
7
8
9
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);

模板使用参数

1
2
3
4
5
6
template <int size>
void display() {
cout << size << endl;
}
display<10>();

多参数函数模板

1
2
3
4
5
6
7
8
template <typename T, typename C>
void display(T a, C b) {
cout << a << " " << b << endl;
}
int a = 1024;
string str = "hello world";
display<int, string>(a, str);

typename 和 class作用相同,可以混用

即:template <typename T, class C>

函数模板与重载:

1
2
3
4
5
6
7
8
9
10
11
template <typename T>
void display(T a);
display<int>(10);
template <typename T>
void display(T a, T b);
display<int>(10, 20);
template <typename T, int size>
void display(T a);
display<int, 5>(10);

类模板

类模板同函数模板使用方法类似

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <class T>
class MyArray {
public:
void display() {...}
private:
T *m_pArr;
}
int main(void) {
MyArray<int> arr;
arr.display();
return 0;
}

一般来说类模板的声明与定义需要写在一起,即一个头文件中,之所以不能分离是因为链接的时候,需要实例化类模板,而在main函数中只能找到头文件中的声明,没有定义(定义在.cpp文件中,但是因为类模板是在被使用的时候才会实例化,因此.cpp文件中是未实例化的定义),这样的话就会导致连接错误。

成员模板

任意类(模板类或非模板类)都可以拥有本身为类模板或函数模板的成员,这种成员称为成员模板

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template<typename Type> class Queue {
public:
//成员模板
template<typename It> Queue(It beg, It end) : head(0), end(0) {
copy_elems(beg, end);
}
template<typename Iter> void assign(Iter, Iter);
private:
template<typename Iter> void copy_elems(Iter, Iter);
};
//类外定义形式
template<typename T> template<typename Iter>
void Queue<T>::assign(Iter beg, Iter end) {
destory();
copy_elems(beg, end);
}

函数模板的特化

对于下面这个compare模板函数,如果我们尝试给它传递两个char*的实参,函数将会比较两个指针的大小,而不是我们希望的字符串的大小

1
2
3
4
5
template<typename T> int compare(const T &v1, const T &v2) {
if(v1 < v2) return -1;
if(v2 < v1) return 1;
return 0;
}

为了方便地解决这种问题,C++引入了模板特化

模板特化:该定义中有一个或多个模板形参的实际类型或实际值是指定的。定义形式如下:

  • 关键字template后面接一对空的尖括号<>

  • 再接模板名和一对尖括号,尖括号中指定这个特化定义的模板形参

例如我们可以将compare函数特化为:

1
2
3
4
template<>
int compare(const char*)(const char* const &v1, const char* const &v2) {
return strcmp(v1, v2);
}

这样当我们调用compare(“hello”, “world”)时就不会再出错了!当然如果使用其他类型的实参仍会调用compare的通用版本。

注意template<>不可省略,否则声明定义的是该函数的重载非模板版本。

模板类的特化以及模板类的模板成员函数特化与模板函数特化形式上类似。

上面讲述的是模板的完全特化,对于有多个模板参数的模板类或模板函数我们还可以做到模板的部分特化,比如有类template<typename T, typename U> class EXP,则我们可以使用template<typename T, string> class EXP的形式完成模板的部分特化。

重载与函数模板

函数模板可以重载,可以定义有相同名字但形参数目不同或类型不同的多个函数模板,也可以定义与函数模板有相同名字的普通非模板函数。

C++ OOP特性还可参考本站其他相关博文:

C++ 【oop】封装篇

C++ 【oop】继承篇

C++ 【oop】多态篇