IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 第 19 节 委托详解 -> 正文阅读

[C++知识库]第 19 节 委托详解

什么是委托

  1. 委托(delegate)是函数指针的“升级版”
    实例:C/C++中的函数指针
#include<stdio.h>

//函数指针数据类型
//Calc 是函数指针类型的名字
//两个整数类型的参数 a,b
//返回整数类型的值
typedef int(*Calc)(int a, int b);  

int Add(int x, int y)
{
	int result = x + y;
	return result;
}

int Sub(int x, int y)
{
	int result = x - y;
	return result;
}

int main()
{
	int x = 100;
	int y = 200;
	int z = 0;

	//声明函数指针类型的变量
	Calc funcPoint1 = &Add;  
	Calc funcPoint2 = &Sub;
	
	z = funcPoint1(x, y);
	printf("%d + %d = %d\n", x, y, z);

	z = funcPoint2(x, y);
	printf("%d - %d = %d\n", x, y, z);
}
  1. 一切皆地址
    1) 变量(数据)是以某个地址(变量名所对应的内存地址)为起点的一段内存中所存储的值;
    2)函数(算法)是以某个地址(函数名所对应的内存地址)为起点的一段内存中所存储的一组机器语言指令。

  2. 直接调用与间接调用
    1)直接调用:通过函数名来调用函数,CPU通过函数名直接获得函数所在地址并开始执行 —> 返回;
    2)间接调用:通过函数指针来调用函数,CPU通过读取函数指针存储的值获得函数所在地址并开始执行 —> 返回。

  3. Java 中没有与委托相对应的功能实体

  4. 委托的简单使用
    1)Action 委托
    2)Func 委托

class Program
{
    static void Main(string[] args)
    {
        Calculator cal = new Calculator();

        //Action 类型的委托,只能委托返回值为void的函数
        Action action = new Action(cal.Report);
        cal.Report();  //直接调用
        action.Invoke();  //使用委托间接调用
        action();  //简便书写

        //Func 类型的委托,可以委托各种返回值的函数
        //<>里面写函数的参数类型和返回值类型
        Func<int, int, int> func1 = new Func<int, int, int>(cal.Add);
        Func<int, int, int> func2 = new Func<int, int, int>(cal.Sub);

        int x = 100;
        int y = 200;
        int z = 0;

        z = func1.Invoke(x, y);
        Console.WriteLine(z);

        z = func2.Invoke(x, y);
        Console.WriteLine(z);

    }
}

class Calculator
{
    public void Report()
    {
        Console.WriteLine("I have 3 methods!");
    }

    public int Add(int a, int b)
    {
        int result = a + b;
        return result;
    }

    public int Sub(int a, int b)
    {
        int result = a - b;
        return result;
    }
}

委托的声明(自定义委托)

  1. 委托是一种类(class),类是数据类型,所以委托也是一种数据类型
  2. 它的声明方式与一般的类不同,主要是为了照顾可读性和C/C++传统
  3. 注意声明委托的位置
    避免写错地方结果声明成嵌套类型。
    委托在名称空间体里进行声明,和其他类处在同一级别。如果在Program类里面声明委托Calc,则Calc是Program的嵌套类,在使用时需写成Program.Calc。
  4. 委托与所封装的方法必须“类型兼容
    下图的第一行是委托类型的声明,而后面四行是委托可以封装的方法。

:返回值的数据类型一致;参数列表在个数和数据类型上一致(参数名不需要一样)

//声明自定义委托类型
//delegate 声明委托的关键字
//double 目标方法的返回值类型
//( )里面是目标方法的参数列表
public delegate double Calc(double x, double y);  

class Program
{
    static void Main(string[] args)
    {
        Calculator calculator = new Calculator();
        Calc calc1 = new Calc(calculator.Add);
        Calc calc2 = new Calc(calculator.Sub);
        Calc calc3 = new Calc(calculator.Mul);
        Calc calc4 = new Calc(calculator.Div);

        double a = 10;
        double b = 2;
        double c = 0;

        c = calc1.Invoke(a, b);  
        //或者像函数一样直接调用,c = calc1(a, b)
        Console.WriteLine("{0} + {1} = {2}", a, b, c);

        c = calc2.Invoke(a, b);
        Console.WriteLine("{0} - {1} = {2}", a, b, c);

        c = calc3.Invoke(a, b);
        Console.WriteLine("{0} * {1} = {2}", a, b, c);

        c = calc4.Invoke(a, b);
        Console.WriteLine("{0} / {1} = {2}", a, b, c);
    }
}

class Calculator
{
    public double Add(double a, double b)
    {
        return a + b;
    }

    public double Sub(double a, double b)
    {
        return a - b;
    }

    public double Mul(double a, double b)
    {
        return a * b;
    }

    public double Div(double a, double b)
    {
        return a / b;
    }
}

委托的一般使用

  1. 实例:把方法当做参数传给另一个方法
    1)正确使用1:模板方法,“借用”指定的外部方法来产生结果。
    相当于“填空题”
    常位于代码中部
    委托返回值
    2) 正常使用2:回调(callback)方法,调用指定的外部方法。
    相当于“流水线”
    常位于代码末尾
    委托返回值

举例:
模板方法

//delegate 声明委托的关键字
//double 目标方法的返回值类型
//( )里面是目标方法的参数列表
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  //负责将产品包上盒子交给客户
{
    //模板方法
    //接收一个委托类型的参数,委托方法的返回值类型为Product
    public Box WrapProduct(Func<Product> getProduct)
    {
        Box box = new Box();
        Product product = getProduct.Invoke();
        box.Product = product;
        return 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;
    }
}

回调方法

//delegate 声明委托的关键字
//double 目标方法的返回值类型
//( )里面是目标方法的参数列表
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 Product
{
    public string Name { get; set; }  //每个产品都有自己的名字
    public double Price { get; set; }
}

class Logger  //记录程序运行的状态
{
    public void Log(Product product)
    {
        //DateTime.UtcNow 是不带时区的时间
        Console.WriteLine("Product '{0}' created at {1}. Price is {2}.", product.Name, DateTime.UtcNow, product.Price);
    }
}

class Box
{
    public Product Product { get; set; }  //每个箱子里包装一个产品
}

class WrapFactory  //负责将产品包上盒子交给客户
{
    //模板方法
    //接收一个委托类型的参数,委托方法的返回值类型为Product
    public Box WrapProduct(Func<Product> getProduct, Action<Product> logCallback)  //没有返回值的方法,使用 Action 委托
    {
        Box box = new Box();
        Product product = getProduct.Invoke();
        if (product.Price >= 50)
        {
            logCallback(product);
        }

        box.Product = product;
        return 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;
    }
}
  1. 注意:难精通 + 易使用 + 功能强大的东西,一旦被滥用则后果非常严重
    1)缺点1:这是一种方法级别的紧耦合,现实工作中要慎之又慎;
    2)缺点2:使可读性下降、debug的难度增加;
    3)缺点3:把委托回调、异步调用和多线程纠缠在一起,会让代码变得难以阅读和维护;
    4)缺点4:委托使用不当有可能造成内存泄漏和程序性能下降。

委托的高级使用

  1. 多播(multicast)委托
    一个委托内部封装不止一个方法。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading; //与多线程相关

namespace MulticastDelegateExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Yellow };
            Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
            Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Red };
            Action action1 = new Action(stu1.DoHomework);
            Action action2 = new Action(stu2.DoHomework);
            Action action3 = new Action(stu3.DoHomework);


            action1.Invoke();  //一个委托封装一个方法(单播委托)
            action2.Invoke();
            action3.Invoke();

            //一个委托封装多个方法(多播委托)
            //执行顺序与封装顺序一致,action1、action2、action3
            action1 += action2;  
            action1 += action3;

            action1.Invoke();
        }
    }

    class Student
    {
        public int ID { get; set; }
        public ConsoleColor PenColor { get; set; }  //ConsoleColor 是屏幕显示结果时字体的颜色

        public void DoHomework()
        {
            for (int i = 0; i<5; i++)
			{
                Console.ForegroundColor = this.PenColor;
                Console.WriteLine("Student {0} doing homework {1} hours(s).",this.ID,i);
                Thread.Sleep(1000);
			}
        }
    }
}

  1. 隐式异步调用

1)同步与异步的简介
中英文的语言差异
(1)同步:你做完了我(在你的基础上)接着做
(2)异步:咱们两个同时做(相当于汉语中的“同步进行”)

2)同步调用与异步调用的对比
(1)每一个运行的程序是一个进程(process)
(2)每个进程可以有一个或多个线程(thread),第一个运行的线程是主线程
(3)同步调用是在同一线程内
(4)异步调用的底层机理是多线程
(5)串行----同步----单线程,并行----异步----多线程

注意:异步调用中所说的“互不相干”指的是逻辑上,而现实工作当中经常会遇到多个线程共享(即同时访问)同一个资源(比如某个变量)的情况,这时候如果处理不当就会产生线程间争夺资源的冲突

3)隐式多线程 v.s. 显式多线程
(1)直接同步调用:使用方法名

stu1.DoHomework();
stu2.DoHomework();
stu3.DoHomework();

(2)间接同步调用:使用单播/多播委托的 Invoke 方法

Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);

//单播委托的间接同步调用,Invoke方法是同步调用
action1.Invoke();
action2.Invoke();
action3.Invoke();

//多播委托的间接同步调用
action1 += action2;
action1 += action3;
action1.Invoke();

(3)隐式异步调用:使用委托的 BeginInvoke

Action action1 = new Action(stu1.DoHomework);
Action action2 = new Action(stu2.DoHomework);
Action action3 = new Action(stu3.DoHomework);
            
//隐式的间接异步调用(最最重要)
//BeginInvoke方法会自动地生成一个分支线程,在分支线程里调用封装的方法
//参数1:异步调用的回调,在分支线程里调用完方法之后的后续操作,若无操作,则为null
//参数2:一般为null
action1.BeginInvoke(null, null);
action2.BeginInvoke(null, null);
action3.BeginInvoke(null, null);

(4)显式异步调用:使用 Thread 或 Task

//Thread 显式的异步调用
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();

//Task 显式的异步调用(using System.Threading.Tasks;)
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();

完整代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;  //Tasks
using System.Threading;  //与多线程相关

namespace MulticastDelegateExample
{
    class Program
    {
        static void Main(string[] args)
        {
            Student stu1 = new Student() { ID = 1, PenColor = ConsoleColor.Yellow };
            Student stu2 = new Student() { ID = 2, PenColor = ConsoleColor.Green };
            Student stu3 = new Student() { ID = 3, PenColor = ConsoleColor.Red };

            //直接同步调用(直接调用是用方法的名字调用该方法)
            stu1.DoHomework();
            stu2.DoHomework();
            stu3.DoHomework();

            Action action1 = new Action(stu1.DoHomework);
            Action action2 = new Action(stu2.DoHomework);
            Action action3 = new Action(stu3.DoHomework);

            //单播委托的间接同步调用,Invoke方法是同步调用
            action1.Invoke();
            action2.Invoke();
            action3.Invoke();

            //多播委托的间接同步调用
            action1 += action2;
            action1 += action3;
            action1.Invoke();

            //隐式的间接异步调用(最最重要)
            //BeginInvoke方法会自动地生成一个分支线程,在分支线程里调用封装的方法
            //参数1:异步调用的回调,在分支线程里调用完方法之后的后续操作,若无操作,则为null
            //参数2:一般为null
            action1.BeginInvoke(null, null);
            action2.BeginInvoke(null, null);
            action3.BeginInvoke(null, null);

            //Thread 显式的异步调用
            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();

            //Task 显式的异步调用(using System.Threading.Tasks;)
            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.ForegroundColor = ConsoleColor.Cyan;
                Console.WriteLine("Main thread {0}.",i);
                Thread.Sleep(1000);
            }
        }
    }

    class Student
    {
        public int ID { get; set; }
        public ConsoleColor PenColor { get; set; }  //ConsoleColor 是屏幕显示结果时字体的颜色

        public void DoHomework()
        {
            for (int i = 0; i<5; i++)
			{
                Console.ForegroundColor = this.PenColor;
                Console.WriteLine("Student {0} doing homework {1} hours(s).",this.ID,i);
                Thread.Sleep(1000);
			}
        }
    }
}

异步调用会出现资源冲突

  1. 应该适时地使用接口(interface)取代一些对委托的使用
    Java 完全地使用接口取代了委托的功能,即 Java 没有 C# 中委托相对应的功能实体
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DelegateExample
{
    class Program
    {
        static void Main(string[] args)
        {
            IProductFactory pizzaFactory = new PizzaFactory();
            IProductFactory toycarFactory = new ToyCarFactory();
            WrapFactory wrapFactory = new WrapFactory();

            Box box1 = wrapFactory.WrapProduct(pizzaFactory);
            Box box2 = wrapFactory.WrapProduct(toycarFactory);

            Console.WriteLine(box1.Product.Name);
            Console.WriteLine(box2.Product.Name);
        }
    }

    interface IProductFactory
    {
        Product Make();
    }

    class PizzaFactory : IProductFactory
    {
        public Product Make()
        {
            Product product = new Product();
            product.Name = "Pizza";
            return product;
        }
    }

    class ToyCarFactory : IProductFactory
    {
        public Product Make()
        {
            Product product = new Product();
            product.Name = "Toy Car";
            return product;
        }
    }

    class Product
    {
        public string Name { get; set; }  //每个产品都有自己的名字
    }

    class Box
    {
        public Product Product { get; set; }  //每个箱子里包装一个产品
    }

    class WrapFactory  //负责将产品包上盒子交给客户
    {
        public Box WrapProduct(IProductFactory productFactory)  
        {
            Box box = new Box();
            Product product = productFactory.Make();
            box.Product = product;
            return box;
        }
    }
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-09-10 23:54:34  更:2021-09-10 23:54:52 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 19:44:15-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码