文章目录
C语言中的函数指针
#include<stdio.h>
// 声明函数指针,定义为一种数据类型。
typedef int (*Calc)(int a, int b);
int Add(int a, int b)
{
int result = a + b;
return result;
}
int Sub(int a, int b)
{
int result = a - b;
return result;
}
int main()
{
int x = 100, y = 200, z = 0;
// 直接调用
z = Add(x, y);
printf("%d+%d=%d\n", x, y, z);
z = Sub(x, y);
printf("%d-%d=%d\n", x, y, z);
// 声明函数指针类型的变量,并将函数的首地址赋给指针
Calc funcPointer1 = &Add;
Calc funcPointer2 = ⋐
//通过函数指针来间接调用函数
z = funcPointer1(x, y);
printf("%d+%d=%d\n", x, y, z);
z = funcPointer2(x, y);
printf("%d-%d=%d\n", x, y, z);
system("pause");
return 0;
}
看完代码觉得好多余呀,绕来绕去,直接用不好吗?我也有此感触:Python中都是直接把函数作为变量传递的。
def Add(a, b): return a+b
a = Add
print(a(2,3))
函数的直接调用与间接调用
- 直接调用:通过函数名来调用函数(CPU通过函数名直接获得函数所在地址并开始执行——>返回调用者)
- 间接调用:通过函数指针来调用函数(CPU通过读取指向某个函数的函数指针存储的值获得函数所在地址并开始执行——>返回调用者)
- 直接调用和间接调用,效果是完全一样的。
Java中没有与委托对应的功能实体
- 为了提高应用程序的安全性,java语言禁止程序员直接去访问地址,也就是说java语言把C++语言中所有与指针相关的内容都舍弃掉了
- C#通过委托这种数据类型,保留了函数指针的功能
C# 委托
- C#委托是C++函数指针的升级版,具有更高的安全性。
- 按照字面意思:一件事情,我不亲自去做,而是让别人去代办,替我去做。
- 从数据结构来讲,委托是一种类,类是引用类型的数据类型,所以委托也是一种数据类型。
- ,它存储的就是 “一系列” 具有相同签名和返回回类型的方法的地址。调用委托的时候,委托包含的所有方法“都”将被执行。
- 注意声明委托的位置,避免写错地方声明称嵌套类型(编译和运行都可以通过,但是不合理)
C# 自定义委托类型
最初使用委托时,均需要先自定义委托类型,然后定义一个符合委托类型签名的函数,在调用前,需声明并创建委托对象,将指定函数与委托进行关联。
委托的声明:delegate <函数返回类型> <委托名> (<函数参数>)
public delegate int Math(int param1, int param2); //声明委托类型
Public int Add(int param1,int param2) //定义同签名函数
{
Return param1+param2;
}
Math math; //声明委托变量
math=new Math(Add); //创建委托对象,与指定进行关联
math(3,4); //调用委托函数
C# 内置泛型委托类型
在写程序时,如果需要三个、四个参数的委托类型,则需要再次自定义委托类型。简单的委托调用,却需要根据签名不断改变多次定义委托类型,而微软推出了对此进行简化的内置委托类型:Action和Func,简化了这些不必要的操作。
Action 与 Func是.NET类库中增加的内置委托类型,以便更加简洁方便的使用委托。
两种委托类型的区别在于:Action委托签名不提供返回类型,而Func提供返回类型。
Action委托具有Action、Action<T1,T2>、Action<T1,T2,T3>……Action<T1,……T16>多达16个的重载,其中传入参数均采用泛型中的类型参数T,涵盖了几乎所有可能存在的无返回值的委托类型。Func则具有Func、Func<T,Tresult>、Func<T1,T2,T3……,Tresult>17种类型重载,T1……T16为出入参数,Tresult为返回类型。
Func的使用方式:无需使用new实例化,直接和函数进行关联。
Func<int,int,int> math=Add; //指定委托对象并关联函数
math(3,4); //调用委托函数
Action的使用如同上面Func的使用一样,只是缺少了返回类型,直接调用委托函数。
Public void Add(int param1,int param2)
{
MessageBox.show((param1+param2).ToString());
}
//遇到此类的委托函数调用,那我们就可以直接用Action了:
Action<int,int> math=Add;
math(3,4);
代码示例:
namespace DelegateDemo
{
class Program
{
//声明委托
delegate int MyDelegate(int x, int y);
static void Main(string[] args)
{
//1、Action<T>只能委托必须是无返回值的方法
Action<string> _action = new Action<string>(SayHello);
_action("Hello World");
//2、Fun<TResult>只是委托必须有返回值的方法
Func<int, bool> _func = new Func<int, bool>(Check);
_func(5);
//3、Predicate:此委托返回一个bool值,该委托通常引用一个"判断条件函数"。
//需要指出的是,判断条件一般为“外部的硬性条件”,比如“大于50”,而不是由数据自身指定,如“查找数组中最大的元素就不适合”。
Predicate<int> _predicate = new Predicate<int>(Check);
//使用Lambda表达式
Predicate<int> predicate = p => p % 2 == 0;
_predicate(26);
}
static void SayHello(string strMsg)
{
Console.WriteLine(strMsg);
}
//返回值为bool值
static bool Check(int i)
{
if (i % 2 == 0)
{
return true;
}
return false;
}
}
}
委托的实例化
使用new关键字
//<委托类型> <实例化名>=new <委托类型>(<注册函数>)
MyDelegate _MyDelegate=new MyDelegate(CheckMod); //用函数CheckMod实例化上面的MyDelegate 委托为_MyDelegate,并进行绑定
直接实例化
//在.net 2.0开始可以直接将匹配的函数注册到实例化委托:<委托类型> <实例化名>=<注册函数>
MyDelegate _myDelegate = CheckMod; //将函数CheckMod注册到委托实例_myDelegate上
匿名函数
//<委托类型> <实例化名> = delegate(<函数参数>){函数体}
Func<int,int,int> math=delegate(int param1,int param2){ return param1+param2; }
Lambda
Func<int,int,int> math=(param1,param2)=>{ return param1+param2; }
代码示例:
class Program
{
//声明委托
delegate int MyDelegate(int x, int y);
static void Main(string[] args)
{
//实例化委托
//1、使用new关键字
MyDelegate _myDelegate = new MyDelegate(GetSum);
//2、使用匿名方法
MyDelegate myDelegate = delegate(int x, int y)
{
return x + y;
};
//3、使用Lambda表达式
MyDelegate myDelegateLambda = (int x, int y) => { return x + y; };
}
static int GetSum(int x, int y)
{
return x + y;
}
}
委托也支持泛型的使用
泛型委托定义:delegate <函数返回类型> <委托名><T1,T2,T3…> (T1 t1,T2 t2,T3 t3…)
可以使用in、out来修饰泛型,表明作为参数还是返回值
//定义有两个泛型(T1,T2)的委托,T2作为委托函数返回类型,T1作为委托函数参数类型
delegate T2 DelegateDemo<in T1,out T2>(T1 t);
static bool Check(int i)
{
if(i%2==0)
{
return true;
}
return false;
}
static void Main(string[] args)
{
DelegateDemo<int, bool> _delegate =Check; //将泛型委托委托<T1,T2>实例化为<int,bool>,即表示有一个int类型参数且返回类型是bool的函数.
Console.WriteLine(_delegate(9)); //false
}
委托的一般使用
- 实例:把方法当作参数传给另一个方法
- 正确使用1:模板方法,“借用“指定的外部方法来产生结果
- 相当于“填空题"
- 常位于代码中部
- 委托有返回值
- 正确使用1:模板方法,“借用“指定的外部方法来产生结果
- 正确使用2:回调( callback )方法,调用指定的外部方法
- 相当于“流水线”
- 常位于代码末尾
- 委托无返回值
- 注意:难精通+易使用+功能强大东西,一旦被滥用则后果非常严重
- 缺点1:这是一种方法级别的紧耦合,现实工作中要慎之又慎
- 缺点2:使可读性下降、debug的难度增加
- 缺点3:把委托回调、异步调用和多线程纠缠在一起,会让代码变得难以阅读和维护
- 缺点4:委托使用不当有可能造成内存泄漏和程序性能下降
- 例如:对象没有引用变量,但是它的方法有委托的话就会造成内存泄漏
模板方法
参照如下代码,体会使用模板方法的好处是:Product类、Box类、WrapFactory类都不用修改,只需要扩展ProductFactory类中的产品就可以生产不同的产品。不管是生产哪种产品的方法,只要将该方法封装到委托类型的对象里传给模板方法,调用模板方法时就可以将产品包装成箱子再交还回来,这样可以最大限度的实现代码的重复使用。代码的复用不但可以提高工作效率,还可以减少程序Bug的引入,良好的复用结构是所有优秀软件所追求的共同目标之一。
namespace 委托_刘铁猛
{
public delegate double Calc(double x, double y); //定义委托
class Program
{
static void Main(string[] args)
{
ProductFactory productFactory = new ProductFactory();
WrapFactory wrapFactory = new WrapFactory();
Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);
Box box1 = wrapFactory.WrapProduct(func1);
Box box2 = wrapFactory.WrapProduct(func2);
Console.WriteLine(box1.Product.Name);
Console.WriteLine(box2.Product.Name);
}
}
class Product
{
public string Name { get; set; }
}
class Box
{
public Product Product { get; set; }
}
class WrapFactory
{
/// <summary>
/// 模板方法,委托的调用getProduct是可以修改的地方,传入不同的getProduct可以实现
/// 不同的产出产品,不同的产品不用再修改WrapProduct中的方法
/// </summary>
/// <param name="getProduct"></param>
/// <returns></returns>
public Box WrapProduct(Func<Product> getProduct)
{
Box box = new Box(); //准备一个Box
Product product = getProduct.Invoke(); //获取一个产品
box.Product = product; //把产品装到Box里面
return box; //返回Box
}
}
class ProductFactory
{
public Product MakePizza()
{
Product product = new Product();
product.Name = "Pizza";
return product;
}
public Product MakeToyCar()
{
Product product = new Product();
product.Name = "Toy Car";
return product;
}
}
}
回调方法
回调关系是对于某个方法可以调用或者不调用,用的着的时候调用它,用不着得时候不调用它。回调方法给了我们一个机会,可以动态的选择后续将被调用的方法(有多个备选方法)。当以回调方法的形式使用委托时,需将委托类型的变量传进主调方法里面,被传入抓主调方法中的委托的变量它内部会封装一个被回调的方法。主调函数会根据自己的逻辑来决定是否要调用回调方法。一般情况下,主调方法会在主要逻辑执行完之后,决定“是否”需要调用回调方法。
namespace 委托_刘铁猛
{
public delegate double Calc(double x, double y); //定义委托
class Program
{
static void Main(string[] args)
{
ProductFactory productFactory = new ProductFactory();
WrapFactory wrapFactory = new WrapFactory();
Func<Product> func1 = new Func<Product>(productFactory.MakePizza);
Func<Product> func2 = new Func<Product>(productFactory.MakeToyCar);
Logger logger = new Logger();
Action<Product> log = new Action<Product>(logger.Log);
Box box1 = wrapFactory.WrapProduct(func1, log);
Box box2 = wrapFactory.WrapProduct(func2, log);
Console.WriteLine(box1.Product.Name);
Console.WriteLine(box2.Product.Name);
}
}
class Logger
{
public void Log(Product product)
{
Console.WriteLine("Product {0} created at {1}. Price is {2}",
product.Name, DateTime.UtcNow, product.Price);
}
}
class Product
{
public string Name { get; set; }
public double Price { get; set; }
}
class Box
{
public Product Product { get; set; }
}
class WrapFactory
{
//将logCallback作为回调方法使用,也就是说在主方法中根据某些条件决定是否使用到这个方法
public Box WrapProduct(Func<Product> getProduct, Action<Product> logCallback)
{
Box box = new Box(); //准备一个Box
Product product = getProduct.Invoke(); //获取一个产品
//产品价格大于等于50则打印log信息;根据这个条件来决定是否使用logCallback方法
if (product.Price >= 50)
{
logCallback(product);
}
box.Product = product; //把产品装到Box里面
return box; //返回Box
}
}
class ProductFactory
{
public Product MakePizza()
{
Product product = new Product();
product.Name = "Pizza";
product.Price = 12;
return product;
}
public Product MakeToyCar()
{
Product product = new Product();
product.Name = "Toy Car";
product.Price = 100;
return product;
}
}
}
无论是模板方法还是回调方法,其本质都是用委托类型的对象绑定一个外部的方法,然后将这个委托传入方法的内部来进行间接调用。委托的功能非常强大,但使用不当会造成严重的后果。
多播委托
实例化委托时必须将一个匹配函数注册到委托上来实例化一个委托对象,但是一个实例化委托不仅可以注册一个函数还可以注册多个函数,注册多个函数后,在执行委托的时候会根据注册函数的注册先后顺序依次执行每一个注册函数。
注意:委托必须先实例化以后,才能使用+=注册其他方法。如果对注册了函数的委托实例从新使用=号赋值,相当于是重新实例化了委托,之前在上面注册的函数和委托实例之间也不再产生任何关系。
有+=注册函数到委托,也有-=解除注册
例如:MyDelegate _myDelegate-=CheckMod;
如果在委托注册了多个函数后,如果委托有返回值,那么调用委托时,返回的将是最后一个注册函数的返回值。
namespace DelegateDemo
{
class Program
{
//声明委托
delegate int MyDelegate(int x, int y);
static void Main(string[] args)
{
MyDelegate _myDelegate = new MyDelegate(fun1);
_myDelegate += fun2;
Console.WriteLine(_myDelegate(10,23));
Console.ReadKey();//输出10,返回最后一个注册函数的返回值
}
static int fun1(int x, int y)
{
return x + y;
}
static int fun2(int x, int y)
{
return x;
}
}
}
委托的高级用法
- 同步与异步简介
- 同步:你先做完,我在你基础上接着做
- 异步:你我同时做
- 同步调用与异步调用对比
- 每个运行的程序是一个进程
- 每个进程中可以有一个或多个线程
- 同步调用是在同一个线程内
- 异步调用底层机理是多线程
- 同步 串行 单线程,异步 并行 多线程
同步调用 举例:直接同步调用、间接同步调用
//直接同步调用
//用方法的名字调用该方法
class Program
{
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1,PenColor=ConsoleColor.Red };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Blue };
//直接调用三个方法,然后主线程还有一些事情要做
stu1.DoHomework();
stu2.DoHomework();
stu3.DoHomework();
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Main thread {0}.", i);
Thread.Sleep(1000);
}
}
}
class Student
{
public int ID { get; set; }
public ConsoleColor PenColor { get; set; }
public void DoHomework()
{
for (int i = 0; i < 5; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hour(s).", this.ID, i);
Thread.Sleep(1000);
}
}
}
//间接同步调用(单播委托)
class Program
{
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1,PenColor=ConsoleColor.Red };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Blue };
Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);
action1.Invoke();
action2.Invoke();
action3.Invoke();
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Main thread {0}.", i);
Thread.Sleep(1000);
}
}
}
class Student
{
public int ID { get; set; }
public ConsoleColor PenColor { get; set; }
public void DoHomework()
{
for (int i = 0; i < 5; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hour(s).", this.ID, i);
Thread.Sleep(1000);
}
}
}
异步调用 举例:隐式多线程BeginInvoke、显示多线程Thread类和Task类
隐式多线程:委托对象的BeginInvoke方法
//隐式多线程
class Program
{
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1, PenColor=ConsoleColor.Red };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Blue };
Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);
action1.BeginInvoke(null, null);
action2.BeginInvoke(null, null);
action3.BeginInvoke(null, null);
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Main thread {0}.", i);
Thread.Sleep(1000);
}
}
}
class Student
{
public int ID { get; set; }
public ConsoleColor PenColor { get; set; }
public void DoHomework()
{
for (int i = 0; i < 5; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hour(s).", this.ID, i);
Thread.Sleep(1000);
}
}
}
显示多线程(自己声明多线程):使用Thread类和Task类,它们有一个委托类型为形参的构造函数,用来为某一个方法创建线程。
//方法一 使用Thread
class Program
{
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1,PenColor=ConsoleColor.Red };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Blue };
//Thread类有public Thread(ThreadStart start)构造函数,ThreadStart是一个委托类型
Thread thread1 = new Thread(new ThreadStart(stu1.DoHomework));
Thread thread2 = new Thread(new ThreadStart(stu2.DoHomework));
Thread thread3 = new Thread(new ThreadStart(stu3.DoHomework));
thread1.Start();
thread2.Start();
thread3.Start();
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Main thread {0}.", i);
Thread.Sleep(1000);
}
}
}
class Student
{
public int ID { get; set; }
public ConsoleColor PenColor { get; set; }
public void DoHomework()
{
for (int i = 0; i < 5; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hour(s).", this.ID, i);
Thread.Sleep(1000);
}
}
}
//方法二 Task
class Program
{
static void Main(string[] args)
{
Student stu1 = new Student() { ID = 1,PenColor=ConsoleColor.Red };
Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Blue };
//Task类有public Task(Action action)构造函数
Task task1 = new Task(new Action(stu1.DoHomework));
Task task2 = new Task(new Action(stu2.DoHomework));
Task task3 = new Task(new Action(stu3.DoHomework));
task1.Start();
task2.Start();
task3.Start();
for (int i = 0; i < 10; i++)
{
Console.WriteLine("Main thread {0}.", i);
Thread.Sleep(1000);
}
}
}
class Student
{
public int ID { get; set; }
public ConsoleColor PenColor { get; set; }
public void DoHomework()
{
for (int i = 0; i < 5; i++)
{
Console.ForegroundColor = this.PenColor;
Console.WriteLine("Student {0} doing homework {1} hour(s).", this.ID, i);
Thread.Sleep(1000);
}
}
}
多线程的运行结果中其他三个线程颜色会不相同
但是由于多个线程访问同一个资源时,在争抢资源时发生冲突,所以跟理想状态不相同
为解决这个问题,可以“加锁”–高级内容,未来再说吧
适当使用接口取代一些对委托的功能
Java中没有委托照样活的好好的,是因为java完全按照设计模式的思想来开发,设计模式由一个核心思想就是面向接口编程,使用委托写的功能完全可以使用面向接口的思想去实现。
委托具有以下特点
- 委托是一个引用类型,它保存对方法的引用(存放的是方法的首地址)
- 因为委托是引用类型,委托允许将方法作为参数进行传递。
- 委托就是.net用来实现回调机制的技术,而事件又是回调机制的一种应用
- 委托可以链接在一起(多播委托);例如,可以对一个事件调用多个方法。
- 声明的委托和方法的签名和返回值基本一致(因为有协变性和逆变性,所以不需要完全一致)