C++
October 7, 2018

C++函数指针和std::function对象

C++函数指针和std::function对象

这篇博文中通过实现对String字符串大小写转换为列来说明C++中函数指针和std::function对象的使用。

我们在博文《C++实现一个简单的String类》中的自定义的String类为基础,再添加两个成员函数用于将字符串全部转为大写(toUpperCase)和全部转为小写(toLowerCase)。

分析一下这两个函数,我们可以发现,两个函数的实现有相同之处,都需要变量字符串中的每个字符,然后使用大写转换函数(std::touuper)和小写转换函数(std::tolower)进行转换即可。

既然两个函数有相同的部分,我们可以将相同的部分抽取出来,抽取出来的这部分负责对字符串进行遍历,然后将对于单个字符转换的函数作为参数传递到该用于字符串遍历的函数中。

下面我们分别使用函数指针的方式和C++ 11中的std::function对象进行实现。本文不对std::function的优点进行介绍,这是以一个简单示例进行入门介绍。

函数指针

头文件:

头文件实现中我们使用了typedef定义了一个函数指针类型,当然我们也可以使用using关键字进行定义,两者类似。

String::map函数用于对字符串进行遍历操作,然后通过传进来的函数指针对每个字符进行操作。

注意我们定义的transform函数指针的返回值是int,函数参数也是int,这是因为cctype头文件中的std::toupperstd::tolower函数的签名也是这样的。

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 String {
private:
char* _buffer;
size_t _length;
// 使用using和typedef都可以: typedef int (*transform)(int);
using transform = int (*)(int);
void init(const char* str);
String map(transform fun);

public:
String(const char* str= nullptr); // 默认构造函数
String(const String& other); // 拷贝构造函数
String(String&& other) noexcept; // 移动构造函数
~String(); // 析构函数

size_t length();
const char* data();

char& operator[](size_t index);
String& operator=(const String& other);
String& operator=(String&& other) noexcept;
String operator+(const String& other);
bool operator==(const String& other);

String toUpperCase();
String toLowerCase();

friend std::ostream& operator<<(std::ostream& output, const String& str);
friend std::istream& operator>>(std::istream& input, String& str);
};

具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
String String::map(String::transform fun) {
char* transformed = new char[_length];
for (size_t i = 0; i < _length; ++i) {
transformed[i] = static_cast<char>(fun(static_cast<int>(_buffer[i])));
}
return String(transformed);
}

String String::toUpperCase() {
return map(std::toupper);
}

String String::toLowerCase() {
return map(std::tolower);
}

测试文件:

1
2
3
4
5
6
7
8
9
10
11
#include "strings.h"
#include <iostream>

using std::cout;

int main() {
String str1("Hello World");
String str2 = str1.toUpperCase();
cout << str2 << '\n';
return 0;
}

输出结果:

1
2
3
4
5
默认构造函数(Hello World)
默认构造函数(HELLO WORLD)
HELLO WORLD
析构函数(HELLO WORLD)
析构函数(Hello World)

std::function对象

头文件

可以看到我们这里使用了std::function类型作为String::map函数的参数类型,std::function是一个模板类,尖括号中标识了返回值,圆括号中标识了参数列表(可以是多个)。

这里我们的std::function对象类型的返回值和参数列表都是char

(为什么不跟前面一样都用int呢?不感兴趣的可以忽略这一段。我做了测试:如果用int的话,会跟locale中定义的touppertolower函数定义冲突。locale头文件中的这两个函数的返回值和参数是char_type类型,编译不通过。所以我将std::function对象类型的返回值和参数列表定义为char,然后在String::toUpperCaseString::toLowerCase函数中使用匿名函数(Lambda)将cctype中的std::toupperstd::tolower函数的返回值和参数类型由int强制转换为char即可。)

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
class String {
private:
char* _buffer;
size_t _length;
void init(const char* str);
String map(std::function<char(char)> fun);

public:
String(const char* str= nullptr); // 默认构造函数
String(const String& other); // 拷贝构造函数
String(String&& other) noexcept; // 移动构造函数
~String(); // 析构函数

size_t length();
const char* data();

char& operator[](size_t index);
String& operator=(const String& other);
String& operator=(String&& other) noexcept;
String operator+(const String& other);
bool operator==(const String& other);

String toUpperCase();
String toLowerCase();

friend std::ostream& operator<<(std::ostream& output, const String& str);
friend std::istream& operator>>(std::istream& input, String& str);
};

实现代码:

在在String::toUpperCaseString::toLowerCase函数中使用可匿名函数(Lambda)对std::toupperstd::tolower函数的返回值和参数类型int进行了强制转换,这样才可以跟定义的std::function类型的函数签名相符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
String String::map(function<char(char)> fun) {
char* transformed = new char[_length];
for (size_t i = 0; i < _length; ++i) {
transformed[i] = (fun(_buffer[i]));
}
return String(transformed);
}

String String::toUpperCase() {
return map([](char c){return static_cast<char>(toupper(static_cast<int>(c)));});
}

String String::toLowerCase() {
return map([](char c){return static_cast<char>(toupper(static_cast<int>(c)));});
}

主函数跟上面函数指针的一样,输出结果也完全一样。

这个案例虽然不能体现出使用std::function类型的优势,但是对于它的简单使用可以有一个参考。