C/C++中的函数指针

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重印)

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

原文链接: https://www.cnblogs.com/keviwu/p/5929748.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    C/C++中的函数指针

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/241534

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月13日 下午9:39
下一篇 2023年2月13日 下午9:39

相关推荐