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. How many integers can you find HDU - 1796_容斥计数

    Code: #include<cstdio> using namespace std; typedef long long ll; const int R=13; ll a[R]; ll ...

  2. IOS:兼容ios6和低版本

    viewDidUnload在ios6开始被弃用了,所以我们在这里处理内存警告的这类问题,这个时候我们就要把相应的处理放在 didReceiveMemoryWarning中. - (void)didRe ...

  3. struct 模块简介

    用处 按照指定格式将Python数据转换为字符串,该字符串为字节流,如网络传输时,不能传输int,此时先将int转化为字节流,然后再发送; 按照指定格式将字节流转换为Python指定的数据类型; 处理 ...

  4. mysql开发之---每日一得01

    2015年7月7日------------------------- 1.truncate表会清空建表语句auto_increment的值:某个表的id即是主键也是自增,你能够选择插入随意id值,假设 ...

  5. HorizontalDragLayout-模仿QQclient的Item滑动删除

    首先感谢http://blog.csdn.net/lmj623565791/article/details/46858663hongyang的文章.之前看过ViewDragHelper类也读过一些de ...

  6. unix关于打包命令zip的使用

    unix zip命令的基本使用方法是: zip [參数] [打包后的文件名称] [打包的文件夹路径] linux zip命令參数列表: -a 将文件转成ASCII模式 -F 尝试修复损坏的压缩文件 - ...

  7. tcMalloc 配置和优化 nginx 高性能

    tcMalloc优化nginx  记住:nginx一定要先启动 1>下载安装libunwind: #wget  http://download.savannah.gnu.org/releases ...

  8. Universal-Image-Loader(UIL)图片载入框架使用简介

    这个也是近期项目中使用到的第三方图片载入框架.在这里也自己总结一下,简单的介绍一些使用的方式. UIL图片载入框架特点 简单介绍: 项目地址:https://github.com/nostra13/A ...

  9. swift-初探webView与JS交互

    公司接下来的项目需要用swift内嵌h5来实现, 以前没有做过swift项目, 现在慢慢将所学的一点一滴记录一下 一个是怕自己忘了- =, 再就是希望大家看到能帮助我哈哈哈 前几天想要直接用swift ...

  10. LightOJ--1094-- Farthest Nodes in a Tree(树的直径裸题)

    Farthest Nodes in a Tree Time Limit: 2000MS Memory Limit: 32768KB 64bit IO Format: %lld & %llu S ...