C/C++中的函数指针

一、引子

今天无聊刷了leetcode上的一道题,如下:

Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value.

Examples:

[2,3,4] , the median is 3

[2,3], the median is (2 + 3) / 2 = 2.5

Design a data structure that supports the following two operations:

  • void addNum(int num) - Add a integer number from the data stream to the data structure.
  • double findMedian() - Return the median of all elements so far.

for example:

add(1)
add(2)
findMedian() -> 1.5
add(3)
findMedian() -> 2

题意很简单,我的解法思路是:维护两个堆,一个为最大堆,放前一半较小值;

另一个为最小堆,存放后一半较大值。并且保证最小堆的元素要么跟最大堆相等,要么多一个。这样取中值只需要这两个堆的第一个元素即可,复杂度为O(1),添加元素时要更新堆,复杂度为O(logN)

#include<iostream>
#include<vector> using namespace std; class MedianFinder {
public:
// Adds a number into the data structure.
// let num_small_heap always >= num_large_heap
void addNum(int num) {
if (small_heap.size() <= large_heap.size()) { // add to small heap
if (large_heap.empty() ||
num <= large_heap[0]) { //smpile add
small_heap.push_back(num);
} else { // remove the smallest one in large heap
//and add num to large heap
small_heap.push_back(large_heap[0]);
large_heap[0] = num;
UpdateLargeHeapFromHead(large_heap);
}
UpdateSmallHeapFromTail(small_heap);
} else { // add to large heap
if (num >= small_heap[0]) { // simple add
large_heap.push_back(num);
} else {
large_heap.push_back(small_heap[0]);
small_heap[0] = num;
UpdateSmallHeapFromHead(small_heap);
}
UpdateLargeHeapFromTail(large_heap);
}
print();
} // Returns the median of current data stream
double findMedian() {
if (small_heap.size() == 0) return 0;
if (small_heap.size() > large_heap.size()) {
return (double)small_heap[0];
} else {
return small_heap[0]/2.0 + large_heap[0]/2.0;
}
} private:
void print() {
cout << "small:";
for (int i=0; i<small_heap.size(); ++i)
cout << small_heap[i] << " ";
cout << endl;
cout << "large:";
for (int i=0; i<large_heap.size(); ++i)
cout << large_heap[i] << " ";
cout << endl;
} void UpdateSmallHeapFromTail(vector<int> & heap) {
int i = heap.size()-1;
while(i > 0) {
if (heap[i] <= heap[(i-1)/2]) {
break;
}
else {
swap(heap[i], heap[(i-1)/2]);
i = (i-1)/2;
}
}
} void UpdateLargeHeapFromTail(vector<int> & heap) {
int i = heap.size()-1;
while(i > 0) {
if (heap[i] >= heap[(i-1)/2]) {
break;
}
else {
swap(heap[i], heap[(i-1)/2]);
i = (i-1)/2;
}
}
} void UpdateSmallHeapFromHead(vector<int> & heap) {
int i = 0;
int n = heap.size();
int k = 0;
while(i < n && 2*i+1 < n) {
if (2*i+2 >= n) {
k = 2*i+1;
} else {
k = heap[2*i+1] >= heap[2*i+2] ? 2*i+1 : 2*i+2;
} if (heap[i] >= heap[k]) {
break;
}
else {
swap(heap[i], heap[k]);
i = k;
}
}
} void UpdateLargeHeapFromHead(vector<int> & heap) {
int i = 0;
int n = heap.size();
int k = 0;
while(i < n && 2*i+1 < n) {
if (2*i+2 >= n) {
k = 2*i+1;
} else {
k = heap[2*i+1] <= heap[2*i+2] ? 2*i+1 : 2*i+2;
} if (heap[i] <= heap[k]) {
break;
}
else {
swap(heap[i], heap[k]);
i = k;
}
}
} private:
vector<int> small_heap;
vector<int> large_heap;
}; int main() {
MedianFinder mf;
mf.addNum(1);
mf.addNum(2);
mf.addNum(3);
mf.addNum(1);
mf.addNum(6);
mf.addNum(-1);
cout << "result:";
cout << mf.findMedian() << endl;
return 0;
}

执行结果,并且打印了每次新加入元素之后大小堆的变化。

wuman@wuman-pc:~/codes/leetcode$ ./a.out
small:1
large:
small:1
large:2
small:2 1
large:3
small:1 1
large:2 3
small:2 1 1
large:3 6
small:1 -1 1
large:2 6 3
result:1.5

这个算法的效率还是可以的,在leetcode上能进前3%,但是有个问题就是这里的代码很很多重复的地方,看了《clean code》之后,忍不住要对其进行优化。

我们看到更新大小堆的函数除了一个是<=,一个是>=之外,其他的都一样,所以可以写成一个函数,传入一个回调函数专门用于比较。如从后向前更新可以写为:

void UpdateHeapFromTail(vector<int> & heap,
bool (MedianFinder::*compare)(int, int)) {
int i = heap.size()-1;
while(i > 0) {
if ((this->*compare)(heap[i],heap[(i-1)/2])) {
break;
}
else {
swap(heap[i], heap[(i-1)/2]);
i = (i-1)/2;
}
}
}

整个代码可以优化为:

#include<iostream>
#include<vector> using namespace std; class MedianFinder {
public:
// Adds a number into the data structure.
// let num_small_heap always >= num_large_heap
void addNum(int num) {
if (small_heap.size() <= large_heap.size()) { // add to small heap
if (large_heap.empty() ||
num <= large_heap[0]) { //smpile add
small_heap.push_back(num);
} else { // remove the smallest one in large heap
//and add num to large heap
small_heap.push_back(large_heap[0]);
large_heap[0] = num;
UpdateHeapFromHead(large_heap, &MedianFinder::LessOrEqual);
}
UpdateHeapFromTail(small_heap, &MedianFinder::LessOrEqual);
} else { // add to large heap
if (num >= small_heap[0]) { // simple add
large_heap.push_back(num);
} else {
large_heap.push_back(small_heap[0]);
small_heap[0] = num;
UpdateHeapFromHead(small_heap, &MedianFinder::GreatOrEqual);
}
UpdateHeapFromTail(large_heap, &MedianFinder::GreatOrEqual);
}
} // Returns the median of current data stream
double findMedian() {
if (small_heap.size() == 0) return 0;
if (small_heap.size() > large_heap.size()) {
return (double)small_heap[0];
} else {
return small_heap[0]/2.0 + large_heap[0]/2.0;
}
} private:
bool LessOrEqual(int left, int right) {
return left <= right;
}
bool GreatOrEqual(int left, int right) {
return left >= right;
} void UpdateHeapFromTail(vector<int> & heap,
bool (MedianFinder::*compare)(int, int)) {
int i = heap.size()-1;
while(i > 0) {
if ((this->*compare)(heap[i],heap[(i-1)/2])) {
break;
}
else {
swap(heap[i], heap[(i-1)/2]);
i = (i-1)/2;
}
}
} void UpdateHeapFromHead(vector<int> & heap,
bool (MedianFinder::*compare)(int, int)) {
int i = 0;
int n = heap.size();
int k = 0;
while(i < n && 2*i+1 < n) {
if (2*i+2 >= n) {
k = 2*i+1;
} else {
k = (this->*compare)(heap[2*i+1],heap[2*i+2])
? 2*i+1 : 2*i+2;
} if ((this->*compare)(heap[i], heap[k])) {
break;
}
else {
swap(heap[i], heap[k]);
i = k;
}
}
} private:
vector<int> small_heap;
vector<int> large_heap;
}; int main() {
MedianFinder mf;
mf.addNum(1);
mf.addNum(2);
mf.addNum(3);
mf.addNum(1);
mf.addNum(6);
mf.addNum(-1);
cout << mf.findMedian() << endl;
return 0;
}

二、C/C++中的函数指针

在这之前没怎么用过C++函数指针这个特性,对其也不是很了解,所以趁这个机会在学习下C/C++的函数指针。

1. C中的函数指针

函数指针主要有两个用途:

  • 转换表
  • 作为参数传递给另一个函数(回调函数)

下面我们先介绍C中函数指针的语法。

函数指针的声明,如下面的例子中,可以声明为

void (*funcptr)(int) = TestFunc,也可以使用typedef简化声明;

可以直接使用赋值操作符进行初始化;在函数指针的初始化之前具有TestFunc的原型是很重要的,否则编译器无法检查TestFunc的类型是否与funcptr所指向的类型一致。

初始化表达式中的&操作符是可选的,因为函数名被使用时总是由编译器把它转换为函数指针,&操作符只是显示地说明了编译器将隐式完成的任务。

如下面的例子所示,我们有三种方式调用函数:

a. 第一种简单地使用名字调用函数TestFunc,但它的执行过程可能和你想象的不太一样。函数名TestFunc首先被转换为一个函数指针,该指针指定函数在内存中的位置。然后,函数调用操作符调用该函数,执行开始于这个地址的代码。

b. 第二种对func执行间接访问操作,它把函数指针转换为函数名。这个转换并不是真正需要的,因为编译器在执行函数调用操作符之前又会把它转换回去。不过,这条语句的效果和第一种是完全相同的。

c. 第三种和前面两种的效果一样。间接访问并非必需,因为编译器需要的是一个函数指针。

#include<stdio.h>

typedef void Func(int);

void TestFunc(int a)
{
printf("function pointer test: %d\n", a);
} int main()
{
Func* func = &TestFunc; //&可以省略
TestFunc(0); // 这三种方式调用函数都可以
(*func)(1);
func(2);
return 0;
}

2. C++中的函数指针

C++向下兼容C,所以前面C中函数指针的声明、定义、初始化和调用都可以用于C++,但是C++增加了类,有了类成员函数,类成员函数又分静态的和非静态的。这样函数指针的使用就变得更加复杂了,下面一一叙述。

非静态成员函数指针

简单的讲,指向类成员函数的指针与普通函数指针的区别在于,前者不仅要匹配函数的参数类型和个数以及返回值类型,还要匹配该函数指针所属的类类型。总结一下,比较以下几点:

a)参数类型和个数

b)返回值类型

c)所属的类类型(特别之处)

究其原因,是因为非静态的成员函数必须被绑定到一个类的对象或者指针上,才能得到被调用对象的this指针,然后才能调用指针所指的成员函数(我们知道,所有类的对象都有自己数据成员的拷贝,但是成员函数都是共用的,为了区分是谁调用了成员函数,就必须有this指针,this指针是隐式的添加到函数参数列表里去的)。

声明:与普通函数作为区分,指向类的成员函数的指针只需要在指针前加上类类型即可,格式为:

typedef 返回值 (类名::*指针类型名)(参数列表);

赋值:只需要用类的成员函数地址赋值即可,格式为:

指针类型名 指针名 = &类名::成员函数名;

注意:这里的这个&符号是比较重要的:不加&,编译器会认为是在这里调用成员函数,所以需要给出参数列表,否则会报错;加了&,才认为是要获取函数指针。这是C++专门做了区别对待。

调用:调用方法也很简单,针对调用的对象是对象还是指针,分别用.和->进行调用,格式为:

(类对象.*指针名)(参数列表);

(类指针->*指针名)(参数列表);

注意:这里的前面一对括号是很重要的,因为()的优先级高于成员操作符指针的优先级。

#include<iostream>

using namespace std;

class TestClass {
public:
typedef void (TestClass::*FuncPtr)(int);
void Test(int a){
TestFunc(a, &TestClass::Print); //传入参数(赋值)
}
void TestFunc(int a, FuncPtr pf) { // 这两种原型都可以
//void TestFunc(int a, void (TestClass::*pf)(int)) {
(this->*pf)(a); //调用,一定要用this指针
}
private:
void Print(int a)
{
cout << "test non-static function pointer:" << a << endl;
}
}; int main()
{
TestClass t;
t.Test(1);
return 0;
}
静态成员函数指针

类的静态成员和普通成员的区别在于,他们是不依赖于具体对象的,所有实例化的对象都共享同一个静态成员,所以静态成员也没有this指针的概念。

所以,指向类的静态成员的指针就是普通的指针。

#include<iostream>

using namespace std;

class TestClass {
public:
//typedef void (TestClass::*FuncPtr)(int) // 错误
typedef void (*FuncPtr)(int); //不要用类型限定
void Test(int a){
//TestFunc(a, Print);可以这么用,说明静态成员函数的类型跟非静态是不一样的。
TestFunc(a, &TestClass::Print); //传入参数(赋值),&可以省略
}
void TestFunc(int a, FuncPtr pf) { // 这两种原型都可以
//void TestFunc(int a, void (TestClass::*pf)(int)) {
pf(a); //调用,不能用this指针
}
private:
static void Print(int a)
{
cout << "test non-static function pointer:" << a << endl;
}
}; int main()
{
TestClass t;
t.Test(1);
return 0;
}

引用

--C和指针/ (美)里科(Reek, K. A.)著;徐波译. —北京:人民邮电出版社,2008. 4(2016. 5重印)

--恼人的函数指针(二):指向类成员的指针

C/C++中的函数指针的更多相关文章

  1. QT中使用函数指针

    想仿命令行,所以定义了一个类,让一个String 对应一个 function,将两者输入list容器. 类中定义了 QString commandStr; void (MainWindow::*com ...

  2. Delphi中的函数指针判断是否为空

    delphi函数指针 只有@@p才代表了函数指针本身的地址   assigned(p) 判断是否为空 或者用 @p=nil 来判断函数指针是不是为空 Delphi中的函数指针实际上就是指针,只是在使用 ...

  3. C++中使用函数指针 【瓦特芯笔记】

         在C++类中使用函数指针. 类型定义:      typedef 返回类型(类名::*新类型)(参数表) //类定义 class CA { public: char lcFun(int a) ...

  4. 1、C语言中的函数指针

    一 通常的函数调用 void MyFun(int x); //此处的申明也可写成:void MyFun( int ); int main(int argc, char* argv[]) { MyFun ...

  5. C语言结构体中的函数指针

      这篇文章简单的叙述一下函数指针在结构体中的应用,为后面的一系列文章打下基础 本文地址:http://www.cnblogs.com/archimedes/p/function-pointer-in ...

  6. Keil C51 中的函数指针和再入函数

    函数指针是C语言中几个难点之一.由于8051的C编译器的独特要求,函数指针和再入函数有更多的挑战需要克服.主要由于函数变量的传递.典型的(绝大部分8051芯片)函数变量通过堆栈的入栈和出栈命令来传递. ...

  7. C++中的函数指针和指针函数

    C++中的函数指针和指针函数       数组名一般可以被当成指向数组第一个元素的常量指针,同样的情况,在函数中,函数名可以本当成指向函数的常量指针,假如一个函数已经定义,那么它在计算机中一定有特定的 ...

  8. C语言中的函数指针

    C语言中的函数指针 函数指针的概念:   函数指针是一个指向位于代码段的函数代码的指针. 函数指针的使用: #include<stdio.h> typedef struct (*fun_t ...

  9. 利用C语言中的函数指针实现c++中的虚函数

    C语言中的函数指针 #include<stdio.h> int fun1(int a) { return a*a; } int fun2(int a) { return a*a*a; } ...

随机推荐

  1. better-scroll的使用方法,动态创建dom使用better-scroll

    移动端经常会用页面高度超过了手机屏幕的高度,但是有没有滚动条的出现这时候就用 better-scroll 这个插件, iscroll 是常用的但是这个组件没有人在维护了,导致很多的问题没有办法解决. ...

  2. forEach 列出数组的每个元素:

    数组.forEach便利所有的元素 array.forEach(function(currentValue, index, arr), thisValue) function(currentValue ...

  3. Everything Be True FreeCodeCamp

    function every(collection, pre) { // Is everyone being true? for(var i in collection){ if(!collectio ...

  4. ZJOI2015 幻想乡战略游戏 动态点分治_树链剖分_未调完

    Description 傲娇少女幽香正在玩一个非常有趣的战略类游戏,本来这个游戏的地图其实还不算太大,幽香还能管得过来,但是不知道为什么现在的网游厂商把游戏的地图越做越大,以至于幽香一眼根本看不过来, ...

  5. 什么时候用created,什么时候用mounted

    created 在实例创建完成后被立即调用.在这一步,实例已完成以下的配置:数据观测 (data observer), 属性和方法的运算,watch/event 事件回调.然而,挂载阶段还没开始,$e ...

  6. centos7.XXX配置python3环境

    众做周知,centos 是自带python2.7的.可是随着社会的进步,科技的发展,技术一步步更新换代,python2.7已经不足以满足项目的需求.这时候python3横空出世. 下面跟着我来一起实现 ...

  7. 【hiho一下第二周 】Trie树

    [题目链接]:http://hihocoder.com/problemset/problem/1014 [题意] [题解] 在字典树的域里面加一个信息cnt; 表示这个节点下面,记录有多少个单词; 在 ...

  8. ❝ Windows系统的FTP上传下载脚本 ❞

    运行环境:windows 脚本功能:从目标系统下载数据库备份文件*.dmp 执行方法:windows任务计划定时调用文件ftp.bat 文件1:ftp.bat echo 开始备份日期: >> ...

  9. GIT文件的4种状态

  10. rails的数据库查询方法

    rails的数据库查询方法 学习了:http://blog.csdn.net/menxu_work/article/details/8664962 学习了:http://www.cnblogs.com ...