C语言中的函数指针、函数的直接/间接调用、C# 委托(自定义委托、内置泛型委托、委托的实例化、委托的一般使用(模板方法、回调方法)、泛型委托、多播委托、同步/异步使用委托)
文章目录
- C语言中的函数指针
- 函数的直接调用与间接调用
- Java中没有与委托对应的功能实体
- C# 委托
- C# 自定义委托类型
- C# 内置泛型委托类型
- 委托的实例化
- 委托也支持泛型的使用
- 委托的一般使用
- 模板方法
- 回调方法
- 多播委托
- 委托的高级用法
- 同步调用 举例:直接同步调用、间接同步调用
- 异步调用 举例:隐式多线程BeginInvoke、显示多线程Thread类和Task类
- 适当使用接口取代一些对委托的功能
- 委托具有以下特点
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(); //准备一个BoxProduct 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(); //准备一个BoxProduct 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用来实现回调机制的技术,而事件又是回调机制的一种应用
- 委托可以链接在一起(多播委托);例如,可以对一个事件调用多个方法。
- 声明的委托和方法的签名和返回值基本一致(因为有协变性和逆变性,所以不需要完全一致)
C语言中的函数指针、函数的直接/间接调用、C# 委托(自定义委托、内置泛型委托、委托的实例化、委托的一般使用(模板方法、回调方法)、泛型委托、多播委托、同步/异步使用委托)相关推荐
- 【示例】C语言中利用数组存放函数指针
C语言中利用数组存放函数指针,增加函数使用的灵活性.使用时只需提供数组索引,即可调用不同函数. 预备知识: 1.指向函数的指针 一个函数在编译时被分配一个入口地址,这个地址就被称为函数的指针. 例如: ...
- C语言:一个涉及指针函数返回值与printf乱码、内存堆栈的经典案例
C语言:一个涉及指针函数返回值与printf乱码.内存堆栈的经典案例 一个奇怪的C语言问题,涉及到指针.数组.堆栈.以及printf.以下实现: 整数向字符串的转换,返回字符串指针,并在main函数中 ...
- c语言 格式转换函数,C语言中的格式转换函数.doc
C语言中的格式转换函数 C语言中的格式转换函数 表头文件 #include 定义函数 double atof(const char *nptr); 函数说明 atof()会扫描参数nptr字符串,跳过 ...
- 怎样设置一个函数C语言,C语言中怎样编写一个函数 如何在C语言中定义一个函数?...
如何在C语言中定义一个函数?小编很想在你面前流泪最后却还是选择装作打个哈欠 为什么小编怎么定义函数都不正确呢? 总是说小编 表达语法错误在main函数中 小编们可以在头文件与main函数之间定义,并编 ...
- c语言中有裁剪字符串的函数吗,C语言中的字符串截取函数
/*======================================================== 子数整数 源程序名 num.??? (pas,c,cpp) 可执行文件名 num. ...
- c语言随机字符rand,C语言中生产随机数 rand()函数
一:如果你只要产生随机数而不需要设定范围的话,你只要用rand()就可以了:rand()会返回一随机数值, 范围在0至RAND_MAX 间.RAND_MAX定义在stdlib.h, 其值为214748 ...
- c语言中fputc函数的作用是,C语言中文件的读写函数之 fputc、fgetc
C语言中文件的读写函数之 fputc.fgetc 首先,我们要知道fputs和fgetc这两个函数是包含在标准库函数中的函数,换句话说,就是封装在标准函数中的两个函数.其中这两个函数都是每次只能输入或 ...
- c语言中常用的字符函数以及字符串函数
文章目录 前言 一.常用字符串函数 1.strlen() 2.strcpy() 3.strcat() 4.strcmp() 5.strstr() 6.memcpy() 6.memmove() 二.qs ...
- bind merge r 和join_R语言中的数据合并函数(merge,cbind和rbind)的使用
R语言中的数据合并函数(merge,cbind和rbind)的使用-R语言中用cbind() 和rbind() 构建分块矩阵 1.merge函数 两个数据框拥有相同的时间或观测值,但这些列却不尽相同. ...
最新文章
- BZOJ 1801 [Ahoi2009]中国象棋(线性动规)(洛谷P2051)
- 机器学习模型身后的数学和统计背景:统计与信息论Probability and Information Theory
- 从Openvswitch代码看网络包的旅程
- headless 怎么处理_公司清算注销债务怎么处理
- java备忘录_Java 8备忘单中的可选
- 于.net开发平台项目案例集锦
- JDBC 出现references non-existing project XXX, launch configuration问题的解决办法
- 利用HUtool读取Excel内容
- 为什么黑客都用python-黑客编程为什么首选Python语言?这里告诉你答案!
- python小绵羊怎么画_使用Python的turtle画小绵羊
- Java程序员3个月从月薪6k涨到15k,你知道我是怎么过来的吗?
- android黑色半透明dialog背景,Dialog背景半透明
- Linux卸载Anaconda
- win10禁用计算机维护,想要电脑不卡,你必须知道win10必须禁用的服务有哪些
- 你的快递“动”了吗,快递受阻,缺的不止快递小哥
- @PostConstruct与afterPropertiesSet
- 《零基础入门学习Python》第031讲:永久存储:腌制一缸美味的泡菜
- [笔记]Windows核心编程《十六》线程栈
- 中基鸿业低收入家庭如何理财
- 前后端不分离到分离演变,优势,前后端接口联调,排错