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重印)
C/C++中的函数指针的更多相关文章
- QT中使用函数指针
想仿命令行,所以定义了一个类,让一个String 对应一个 function,将两者输入list容器. 类中定义了 QString commandStr; void (MainWindow::*com ...
- Delphi中的函数指针判断是否为空
delphi函数指针 只有@@p才代表了函数指针本身的地址 assigned(p) 判断是否为空 或者用 @p=nil 来判断函数指针是不是为空 Delphi中的函数指针实际上就是指针,只是在使用 ...
- C++中使用函数指针 【瓦特芯笔记】
在C++类中使用函数指针. 类型定义: typedef 返回类型(类名::*新类型)(参数表) //类定义 class CA { public: char lcFun(int a) ...
- 1、C语言中的函数指针
一 通常的函数调用 void MyFun(int x); //此处的申明也可写成:void MyFun( int ); int main(int argc, char* argv[]) { MyFun ...
- C语言结构体中的函数指针
这篇文章简单的叙述一下函数指针在结构体中的应用,为后面的一系列文章打下基础 本文地址:http://www.cnblogs.com/archimedes/p/function-pointer-in ...
- Keil C51 中的函数指针和再入函数
函数指针是C语言中几个难点之一.由于8051的C编译器的独特要求,函数指针和再入函数有更多的挑战需要克服.主要由于函数变量的传递.典型的(绝大部分8051芯片)函数变量通过堆栈的入栈和出栈命令来传递. ...
- C++中的函数指针和指针函数
C++中的函数指针和指针函数 数组名一般可以被当成指向数组第一个元素的常量指针,同样的情况,在函数中,函数名可以本当成指向函数的常量指针,假如一个函数已经定义,那么它在计算机中一定有特定的 ...
- C语言中的函数指针
C语言中的函数指针 函数指针的概念: 函数指针是一个指向位于代码段的函数代码的指针. 函数指针的使用: #include<stdio.h> typedef struct (*fun_t ...
- 利用C语言中的函数指针实现c++中的虚函数
C语言中的函数指针 #include<stdio.h> int fun1(int a) { return a*a; } int fun2(int a) { return a*a*a; } ...
随机推荐
- 判断list数组里的json对象有无重复,有则去重留1个
查找有无重复的 var personLength = [{ certType: '2015-10-12', certCode:'Apple'}, { certType: '2015-10-12', c ...
- 强大的JQuery链式操作风格
实例代码 <style type="text/css"> #menu {width: 300px;} .has_children {background:#555;co ...
- vue 动态添加路由 require.context()
之前的写法 'use strict' import Vue from 'vue' import MessageBroadcast from 'page/MessageBroadcast' import ...
- nyoj329-循环小数
329-循环小数 内存限制:64MB时间限制:3000msSpecial Judge: No accepted:1submit:1 题目描述: 我们可爱的 c小加 近段儿正在潜心研究数学,当他学习到循 ...
- Git clone时出现Please make sure you have the correct access rights and the repository exists.问题已解决。
看了好多资料终于搞定了git 中clone命令报错这个问题,废话不多说直接上步骤希望对大家有帮助. 1 删除.ssh文件夹(直接搜索该文件夹)下的known_hosts(手动删除即可,不需要git ...
- php中的form表单
表单处理 表单的概念在生活中很常见,就像是问卷调查表一样,别人先把问卷发给你,你照着问卷的要求填写,完事过后再将填完的问卷发给别人,从而达到一个将别人需要的信息传递给别人的一种方式. 传统的网页大多数 ...
- 初步体验libsvm用法1(官方自带工具)
在机器学习和模式识别领域,svm理论使用得很广泛,其理论基础是统计学习,但是如果我们的研究方向不是svm理论,我们只是利用已有的svm工具来对我们的任务进行分类和回归,那么libsvm是一个不错的选择 ...
- RobotFrameWork+APPIUM实现对安卓APK的自动化测试----第二篇【原理】
http://blog.csdn.net/deadgrape/article/details/50574459 接着上一篇,我们开始聊聊APPIUM的框架和运行模式.废话不多说直接上图. 1.首先自动 ...
- 全球级的分布式数据库 Google Spanner原理
开发四年只会写业务代码,分布式高并发都不会还做程序员?->>> Google Spanner简介 Spanner 是Google的全球级的分布式数据库 (Globally-Di ...
- [using_microsoft_infopath_2010]Chapter12 管理监视InfoPath表单服务
本章概要: 1.在SharePoint中心控制台管理InfoPath设置 2.分析监视浏览器表单开考虑潜在性能问题 3.最小化回发数据