C++ note (7)
Eiaton

运算符重载 Ⅱ

注意事项

函数对象

当定义了 operator() 的类的对象调用此操作符时,其表现形式如同普通函数调用一般,故取名函数对象,举例:

1
2
3
4
5
6
class cmp {
public:
bool operator () (const int& a, const int& b) {
return a < b;
}
};
1
2
3
4
5
int main() {
cmp f;
cout << f(1,2) << endl;
return (0);
}
1
2
1

函数对象实际上也是一个对象,所以可以拥有自己的成员变量,从而执行带谓词的函数执行:

1
2
3
4
5
6
7
8
9
10
11
12
// greater.h
class greaterThan {

private:
int base;

public:
greaterThan(int x) : base(x) {}
bool operator () (const int& x) {
return x > base;
}
};
1
2
3
4
5
6
#include "greater.h"
int main() {
greaterThan g(10);
cout << g(15) << endl;
return (0);
}
1
2
1

所以,函数对象相比普通的函数有一个非常重要的用途,即 作为谓词函数(Predicate)

谓词函数通常用来对传进来的参数进行判断,并返回布尔值。标准库中有大量的函数都定义了多个重载版本,其中包含由用户提供谓词函数的,比如:find_if,remove_if,等等。

现在假设我们有一串数字,要从中找出第一个大于10的数字:

1
2
3
4
5
6
7
8
9
10
11
12
#include "greater.h"
bool greaterThan10(const int& x) {
return x > 10;
}
int main() {
vector<int> a = {5, 10, 15, 20, 25};
// find_if return a iterator
cout << *find_if(a.begin(), a.end(), greaterThan(10)) << endl;
cout << *find_if(a.begin(), a.end(), greaterThan10) << endl;
return (0);
// 用函数对象,使得代码更具有可读性
}

友元函数

问题引入

Q:如何解决外部函数无法访问类的 private 成员?

1
2
3
4
5
6
7
8
9
10
11
12
13
class integer {
int x;
};

istream& operator >> (istream& is, integer& Int) {
is >> Int.x; // error: private, unavailable
return is;
}

ostream& operator << (ostream& os, const integer& Int) {
os << Int.x; // error: private, unavailable
return os;
}

A:使用友元函数(friend function)

概念

  • friend return_type function_name(parameter_type_list);

将正常声明的函数放进类内部,并在前面加上 friend 关键字,那么这个函数虽然不属于类,但却可以访问类的私有变量以及私有函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class integer {
int x;
friend istream& operator >> (istream& is, integer& Int);
friend ostream& operator << (ostream& os, const integer& Int);
};

istream& operator >> (istream& is, integer& Int) {
is >> Int.x; // ok
return is;
}

ostream& operator << (ostream& os, const integer& Int) {
os << Int.x; // ok
return os;
}

友元函数的声明可以放在类的私有部分,也可以放在公有部分,它们是没有区别的,都说明是该类的一个友元函数。

类的成员函数也是一种函数,所以,其他类的成员函数也可以作为友元函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class integer;

struct cmp {
bool operator () (const integer& a, const integer b);
};

class integer {
private:
int x;

public:
integer(int x = 0) : x(x) {}
friend bool cmp::operator () (const integer& a, const integer& b); // declaration
};

bool cmp::operator () (const integer& a, const integer& b) {
return a.x < b.x;
}

友元类

有时候其他类的成员函数可能会很多,一个一个的声明为友元函数会比较麻烦。

所以我们就可以直接声明友元类

  • 一个类 A 可以将另一个类 B 声明为自己的友元,那么类 B 的所有成员函数就都可以访问类 A 对象的私有成员
  • friend class B; (在类 A 的内部)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    class integer;

    struct cmp {
    bool operator () (const integer& a, const integer b);
    };

    class integer {
    private:
    int x;

    public:
    integer(int x = 0) : x(x) {}
    friend cmp; // new declaration
    };

    bool cmp::operator () (const integer& a, const integer& b) {
    return a.x < b.x;
    }

操作符重载限定

必须是函数成员

隐式转换

问题引入

对象隐式转换

如果对象 T 存在构造函数 T(T1), 则 T1 类型对象(实参)可隐式转为 T 类型对象(形参)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class integer {
int x;

public:
integer(int x = 0) : x(x) {}
friend integer operator + (const integer& lhs, const integer& rhs) {
return lhs.x + rhs.x; // 1. int -> integer
}
friend ostream& operator << (ostream& o, const integer& hs) {
o << hs.x;
return o;
}
};

int main() {
string s;
s = "Hello"; // 2. const char* -> string
cout << s << endl;

integer i1(3), i2;
i2 = 1.1 + i1; // 3. double -> int int -> integer
cout << i2 << endl;
return (0);
}

重载协议-const

如果重载的函数参数一样,可以通过转换到某个重载函数,编译会如何哪个版本的选择?

  1. 类型直接匹配的优先选择;
  2. const 类型实参匹配 const 版本
  3. 非 const 实参优先匹配非 const 版本。没有则隐式转换为 const 版本匹配
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class Integer {
    int x;

    public:
    integer(int x = 0) : x(x) {}
    friend ostream& operator << (ostream& o, const Integer& hs) {
    o << "const " << hs.x;
    return o;
    }
    friend ostream& operator << (ostream& o, Integer& hs) {
    o << "no_const " << hs.x ;
    return o;
    }
    };

    int main() {
    Integer i1(1);
    const Integer i2(2);
    cout << i1 << "," << 3 << "," << i2 << endl;
    }
    1
    2
    no_const 1,3,const 2

    练习:
  4. 注释去除非 const 版本,编译运行
  5. 注释去除 const 版本,编译

nullptr

C 语言零值常数有很多表示,如 0, NULL, ‘\0’, C++ 右引入了 nullptr 表示空值指针字面量

下边的例子解释了 nullptr 的必要性

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
27
28
29
30
31
32
33
34
35
36
37
38
39
class Integer {
int x;
`public:
Integer(int x = 0) : x(x) {}
friend Integer operator + (const Integer& lhs, Integer rhs){
return lhs.x + rhs.x;
}
friend Integer operator+(const Integer& lhs, Integer* rhs){
if (rhs) {
return lhs.x + rhs->x + 2000;
} else {
return lhs.x + 1000;
}
}
friend ostream& operator << (ostream& o, const Integer& hs) {
o << hs.x;
return o;
}
};

class Girlfriend {/* ... */};

void kissGirlfriend(Girlfriend* gf) {
cout << "pointer"<<endl;
}

void kissGirlfriend(int gfID) {
cout << "int"<<endl;
}

int main() {
kissGirlfriend(nullptr); // 指针类型字面量(viod *)0
kissGirlfriend(0); // 尝试 NULL 取代 0

Integer i1(1), i2;
i2 = i1 + 0; // 尝试 1,nullptr,NULL,&i1 取代 0
cout << i2 << endl;
return (0);
}

先进行类型匹配

再进行类型转换

0优先转指针了,不会执行integer类型转换

运算符重载,0必须特殊处理。如采用显式转换(integer)0转指针了,不会执行integer类型转换

explicit

C++ 关键字 explicit,用于关闭这种自动类型转化的特性

即被 explicit 关键字修饰的类构造函数,不能进行自动地隐式类型转换,只能显式地进行类型转换

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
27
28
29
30
class Integer;
struct cmp {
bool operator () (const Integer& a, const Integer& b);
};

class Integer {
int x;

public:
explicit Integer(cosnt char* s) : x(atoi(s)) {}
Integer(int x = 0) : x(x) {}
friend cmp;
};

bool cmp::operator () (const Integer& a, const Integer& b) {
return a.x < b.x;
}

int main() {
Integer a("3"), b("4");
cmp compare;
cout << compare(a,b) << endl;

cout << compare(Integer("1"), Integer("2")) << endl; // explicit construction, ok
cout << compare((Integer)"1", (Integer)"2") << endl; // explicit conversion, ok

cout << compare("1", "2") << endl; // error
cout << compare("a", "b") << endl; // error
return (0);
}