Day16(上).函数指针//socket动态库回调函数开发//泛型编程//类模板

老习惯,回忆一下:

多态

重写 PK 重载理解

函数重载

        必须在同一个类中进行

        子类无法重载父类的函数,父类同名函数将被名称覆盖

        重载是在编译期间根据参数类型和个数决定函数调用

函数重写

        必须发生于父类与子类之间

        并且父类与子类中的函数必须有完全相同的原型

        使用virtual声明之后能够产生多态(如果不使用virtual,那叫重定义)

        多态是在运行期间根据具体对象的类型决定函数调用


请谈谈你对多态的理解

多态的实现效果

        多态:同样的调用语句有多种不同的表现形态;

多态实现的三个条件

        有继承、有virtual重写、有父类指针(引用)指向子类对象。

多态的C++实现

         virtual关键字,告诉编译器这个函数要支持多态;不要根据指针类型判断如何调用;而是要根据指针所指向的实际对象类型来判断如何调用
多态的理论基础

        动态联编PK静态联编。根据实际的对象类型来判断重写函数的调用。

多态的重要意义

       设计模式的基础。

实现多态的理论基础

       函数指针做函数参数

 


多态原理探究 

//分析一下要想实现多态,c++编译器应该动什么手脚

         //第一个需要动手脚的地方  起码这个函数print 我应该特殊处理

         virtual void print()

         {

                   cout<<"a"<<a<<endl;

         }

//父类虚函数有virtual关键字,子类无论有没有关键字,都默认添加virtual关键字

//pBase 我怎么知道是父类对象还是子类对象

         //动手脚2::区分是父类对象还是子类对象,提前布局

         pBase->print();  //


虚函数表指针(VPTR)被编译器初始化的过程

基类和子类对象指针++混搭风

/指针也是一种数据类型,指针数据的数据类型是指,它所指的内存空间的数据类型
//最后一点引申 指针的步长 。。。c++

class Parent01
{
protected:
	int i;
	int		j;
public:
	virtual void f()
	{
		cout<<"Parent01::f"<<endl;
	}
};


class Child01 : public Parent01
{	
public:
	int k;
public:
	Child01(int i, int j)
	{
		printf("Child01:...do\n");
	}

	virtual void f()
	{
		printf("Child01::f()...do\n");
	}
};

void howToF(Parent01 *pBase)
{
	pBase->f();
}

//指针的步长 在c++领域仍然有效,父类指针的步长和子类指针的步长不一样
//多态是靠迟绑定实现的(vptr+函数指针实现)
int main06()
{
	int i = 0;
	Parent01* p = NULL;
	Child01* c = NULL;

	//不要把父类对象还有子类对象同事放在一个数组里面
	Child01 ca[3] = {Child01(1, 2), Child01(3, 4), Child01(5, 6)};

	//不要用父类指针做赋值指针变量,去遍历一个子类的数组。

	//把数组的首地址,赋给基类指针
	p = ca;
	//把数组的首地址,赋给子类指针
	c = ca;

	p->f();
	c->f(); //有多态发生

// 	p++;
// 	c++;
// 
// 	p->f();//有多态发生
// 	c->f();

	for (i=0; i<3; i++)
	{
		howToF(&(ca[i]));
	}

	
	system("pause");
	return 0;
}

为什么要定义虚析构函数(非常重要)

#include "iostream"
using namespace std;
class AA
{
public:
	AA(int a= 0)
	{
		this->a = a;
		print(); //在构造函数里面能实现多态吗?
	}
	virtual ~AA()
	{
		cout<<"父类析构函数do"<<endl;
	}

	//分析一下要想实现多态,c++编译器应该动什么手脚
	//第一个需要动手脚的地方  起码这个函数print 我应该特殊处理
	virtual void print()
	{
		cout<<"父类的"<<"a"<<a<<endl;
	}

protected:
	int a ;
};

class BB : public AA
{
public:
	BB(int a= 0, int b = 0)
	{
		this->a = a;
		this->b = b;

	}

	~BB()
	{
		cout<<"子类析构函数do"<<endl;
	}
	virtual void print()
	{
		cout<<"子类的"<<"a"<<a<<"b"<<b<<endl;
	}
private:
	int b ;
};

//如果想通过父类指针 执行 所有的子类对象的析构函数,那么需要在父类析构函数前加上virtual关键字
//把父类的析构函数变成虚析构函数 
void howToDelete(AA *pBase)
{
	delete pBase;
}
void main()
{
	BB *b1 = new BB(1, 2);
	b1->print();

	howToDelete(b1);
	//子类对象的时候,
	//delete b1;
	system("pause");
}
纯虚函数和接口类的基础
具体代码参看这节最后:http://blog.csdn.net/jorg_zhao/article/details/46741515

整个工程的压缩包下载地址:http://pan.baidu.com/s/1eQCSmQ2
C++中类封装了成员函数,C中函数有handle

********************************************************************************************************************

回调函数

 先回忆一下C语言中的知识点:

1. 函数类型基础

函数的三要素:名称、参数、返回值

C语言中通过typedef为函数类型重命名

语法:

typedef type name(parameter list);
typedef int func(int, int);
typedef void func(int);

代码实例:

#include "stdio.h"
#include "stdlib.h"
#include "string.h"

int arr1[10];
//定义一个数组类型
typedef int array[10];
//定义一个指向数组的数组指针类型
typedef int(*parray)[10];

void main()
{
    //由数组类型定义变量
    array arr2;   //相当于int arr2[10];
    arr2[0] = 1;

    //用数组指针类型去定义一个数组
    parray parr3;
    parr3 = &arr2;
    (*parr3)[0] =2;

    //直接定义一个指向数组类型的指针
    int(*myparray)[10] = &arr2;
    (*myparray)[0] = 3;

    system("pause");
}
函数指针

函数指针用于指向一个函数,函数名是执行函数体的入口地址;

1) 可用过函数类型定义函数指针 FuncType* pointer;

2) 也可直接定义:typedef (*pointer) (parameter list);

                               pointer为函数指针变量名

                               type为指向函数的返回值类型

                               parameter list为指向函数的参数类型列表

(1)

#include "stdio.h"
#include "stdlib.h"
#include "string.h"

//1. 定义一个函数类型(然后用类型去指针)
typedef int FUNC(int);
//函数名称就代表函数的入口地址 函数名称本身就是一个地址
int test(int i)
{
	return i*i;
}

int main()
{
	//用函数类型定义一个函数指针
	FUNC *myfunc = NULL;

	//通过函数指针(函数的入口地址)可以指向函数体(言外之意就是可以进行函数调用)
	myfunc = test;
	printf("%d \n", myfunc(2));

	//对函数名取多少地址 都是一样的
	myfunc = &test;
	printf("%d \n", myfunc(2));

	system("pause");
}
上面的代码总结:通过函数指针可以执行一个函数调用

(2)

#include "stdio.h"
#include "stdlib.h"
#include "string.h"

//2. 定义一个指向函数类型的指针类型
typedef int(*myfunc) (int);
myfunc aa;

(3)

#include "stdio.h"
#include "stdlib.h"
#include "string.h"

void func()
{
    printf("执行了f\n");
}
void main()
{
    //3. 直接定义函数指针,并且赋值
    void(*myfunc1)() = func;
    void(*myfunc2)() = &func;

    myfunc1();
    myfunc2();

    system("pause");
}

2. 函数指针做函数参数

指针做函数参数 PK 函数指针做函数参数

1. 一级指针做函数参数,二级指针做函数参数,  三级指针。。。。。。

2. 函数指针做函数参数

        当函数指针做函数的参数,传递给一个被调用函数,被调用函数丸可以通过这个指针调用外部的函数,这就形成了回调。

#include "stdio.h"
#include "stdlib.h"
#include "string.h"

int add(int a, int b);
//第二个函数是函数指针做函数参数
//在libfun被调用函数里面,就可以通过这个函数指针调用外部的函数,形成一个回调函数
int libfun(int(*pDis)(int a, int b));

int add(int a, int b)
{
	return a + b;
}
//pDis是add的入口地址
int libfun(int(*pDis)(int a, int b))
{
	int a, b;
	a = 1;
	b = 2;
	//执行了add函数调用(被调用函数调用了外部函数)
	printf("%d\n", pDis(a, b));
	return 0;
}

int main(void)
{
	//直接定义了一个函数指针
	int (*pfunc)(int a, int b);
	//函数名赋给函数指针,就是函数的入口地址赋给了pfunc
	pfunc = add;
	libfun(pfunc);

	system("pause");
}
上面的几个函数都是在同一个文件当中

假如

int libfun(int(*pDis)(int a, int b))
是一个库中的文件,就只有使用回调了,通过函数指针参数将外部函数地址传入来实现调用

函数add的代码做了修改,也不必改动库的代码,就可以正常实现调用,便于程序的维护和升级。


回调函数的基础知识

回调函数是利用函数指针实现的一种调用机制

回调机制原理

        当具体事件发生时,调用者通过函数指针调用具体函数

        回调机制的将调用者和被调函数分开,两者互不依赖


1. 需要把待回调函数的入口地址注入到动态库中

2. 动态库业务逻辑函数去调用

********************************************************************************************************************

模版

类属--------类型参数化,又称参数模版

        使得程序(算法)可以从逻辑功能上抽象,把被处理的对象(数据)类型作为参数传递

C++提供两种模板机制:函数模版 和 类模板

语法形式: template <类型形式参数表>

代码:

#include "iostream"
using namespace std;

void swap(int &a, int &b)
{
	int c;
	c = a;
	a = b;
	b = c;
}
void swap(float &a, float &b)
{
	float c;
	c = a;
	a = b;
	b = c;
}
//template关键字告诉C++编译器,现在开始泛型编程
//typename告诉C++编译器,T为类型,不要乱报错
template <typename T>
void T_swap(T &a, T &b)
{
	T c;
	c = a;
	a = b;
	b = c;
}
void main()
{
	int a = 1, b = 2;
	swap(a, b);

	float a1 = 1, b1 = 2;
	swap(a1, b1);

	//泛型编程的调用方式有两种
	//1.  自动类型推导
	int aa = 3, bb = 4;
	cout << "aa:" << aa << " " << "bb:" << bb << endl;
	T_swap(aa, bb);
	cout << "aa:" << aa << " " << "bb:" << bb << endl;
	//2. 具体类型调用
	float xx = 0.3, yy = 0.5;
	cout << "xx:" << xx << "  " << "yy:" << yy << endl;
	T_swap<float>(xx, yy);
	cout << "xx:" << xx << "  " << "yy:" << yy << endl;
	system("pause");
}
继续强化:

我们要实现排序算法:

1.普通的做法是这样的:

#include "iostream"
using namespace std;

int sortArray(int *a, int num)
{
	int i,j;
	for (i = 0; i < num; i++)
	{
		for (j = 0; j < num; j++)
		{
			if (a[i] < a[j])
			{
				int tmp = a[i];
				a[i] = a[j];
				a[j] = tmp;
			}
		}
	}
	return 0;
}
void print(int *a, int num)
{
	for (int i = 0; i < num; i++)
	{
		cout << a[i] << endl;
	}
}
void main()
{
	int a[10] = { 1, 12, 32, 24, 15, 26, 7, 85, 3, 2 };
	int num = sizeof(a) / sizeof(*a);
	sortArray(a, num);
	print(a, num);
	system("pause");
}

用模版做是这样的:

#include "iostream"
using namespace std;

template<typename T>
int T_sortArray(T *a, int num)
{
	int i, j;
	for (i = 0; i < num; i++)
	{
		for (j = 0; j < num; j++)
		{
			if (a[i] < a[j])
			{
				T tmp = a[i];
				a[i] = a[j];
				a[j] = tmp;
			}
		}
	}
	return 0;
}
template<typename T>
void T_print(T *a, int num)
{
	for (int i = 0; i < num; i++)
	{
		cout << a[i]<<",";
	}
	cout<< endl;
}
void main()
{
	int a[10] = { 1, 12, 32, 24, 15, 26, 7, 85, 3, 2 };
	int num = sizeof(a) / sizeof(*a);
	T_sortArray<int>(a, num);
	T_print<int>(a, num);
	
	char buf[] = "jdl2njvk239vwfl2";
	int len = strlen(buf);
	T_sortArray<char>(buf, len);
	T_print<char>(buf, len);

	system("pause");
}

继续加强:--------函数模版遇上重载


但是太复杂,仅供参考,下面总结一下:

1.  函数模版可以像普通函数一样被重载

2.  C++编译器优先考虑普通函数

3.  如果函数模版可以产生一个更好的匹配,那么选择模版

4.  可以通过空模版实参列表的语法限定编译器只通过模版匹配

#include "iostream"
using namespace std;

int max(int a, int b)
{
    cout << "int max(a,b)" << endl;
    return a > b ? a : b;
}
template <typename T>
T max(T a, T b)
{
    cout << "T max(a,b)" << endl;
    return a > b ? a : b;
}
template <typename T>
T max(T a, T b, T c)
{
    cout << "T max(a, b, c)" << endl;
    return max(max(a, b), c);
}
void main()
{
    int a = 1, b = 3;
    //C++编译器会默认调用具体类型
    max(a, b);
    //强制使用模版
    max<>(a, b);

    int aa = 0.2, bb = 0.5, cc = 0.7;
    max(aa, bb);
    max(aa, bb, cc);
    system("pause");
}
编译器的深入理解:
1. 编译器并不是把函数模版处理成能够任意类型的函数

2. 编译器从函数模版通过具体类型产生不同的函数

3. 编译器会对函数模版进行两次编译

4. 在声明的地方对模版代码本身进行编译

5. 在调用的地方对参数替换后的代码进行编译

类模板

#include "iostream"
using namespace std;

template <typename T>
class A
{
public:
	void setA(T a)
	{
		this->a = a;
	}
	void getA()
	{
		return this->a;
	}
private:
	T a;
};
void main()
{
	//要把类模板具体成类型后,才能定义变量
	//A a;   //ERROR
	A<int> a;

	system("pause");
}

#include "iostream"
using namespace std;

template <typename T>
class A
{
public:
	A(T a)
	{
		this->a = a;
	}
	void setA(T a)
	{
		this->a = a;
	}
	void getA()
	{
		return this->a;
	}
private:
	T a;
};
class B : public A<int>
{
public:
	B(int a, int b) :A<int>(a)
	{
		this -> b = b;
	}
private:
	int b;
};
void main()
{
	//要把类模板具体成类型后,才能定义变量
	//A a;   //ERROR
	A<int> a;

	B b(1,2);
	system("pause");
}

类模版遇上友元函数

................................................

类模板与static成员

从类模型实例化的每个模版类有自己的类模板数据成员,该模版的所有对象共享一个static数据成员

和非模版类的static数据成员一样,模版类的static数据成员也应该在文件范围定义和初始化

每个模版类有自己的类模板的static数据成员副本








  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值