【C# Programming】委托和lambda表达式、事件

news2025/1/16 1:44:34

目录

一、委托和lambda表达式

1.1 委托概述

1.2 委托类型的声明

1.3 委托的实例化

1.4 委托的内部机制 

1.5 Lambda 表达式

1.6 语句lambda

1.7 表达式lambda

1.8 Lambda表达式

1.9 通用的委托

1.10 委托没有结构相等性

1.11 Lambda表达式和匿名方法的内部机制

1.12 外部变量

1.13 外部变量的CIL实现

二、事件

2.1 多播委托

2.2 使用多播委托来编码Observer模式

2.3 委托操作符

2.4 错误处理

2.5 事件的作用

2.6 事件的声明

2.7 编码规范

2.8 事件的内部机制

2.9 自定义事件的实现


一、委托和lambda表达式

1.1 委托概述

        C/C++ 利用”函数指针”将方法的引用作为实参传给另一个方法。 C# 利用委托提供相同的功能。委托允许捕获对方法的引用,并像传递其他对象那样传递这个引用,像调用其他方法那样调用这个被捕获的方法。 例如:

public static void BubbleSort(int[] items, ComparisonHandler comparisonMethod)
{
    int i, j, temp;
    if(comparisonMethod == null)
        throw new ArgumentNullException("comparisonMethod");
    if(items == null)
        return;
    for(i = items.Length - 1; i >= 0; i--)
    {
        for(j = 1; j <= i; j++)
        {
            if(comparisonMethod(items[j - 1], items[j]))
            {
                temp = items[j - 1];
                items[j - 1] = items[j];
                items[j] = temp;
            }
        }
    }
}

1.2 委托类型的声明

        为了声明委托,要使用delegate 关键字,后面跟着方法的签名。这个方法的签名是委托所引用的方法的签名。 例如:

public delegate bool ComparisonHandler(int first, int second);

        委托可以嵌套在类中。 假如委托声明出现在另一个类的内部,则委托类型就会成为嵌套类型。

class DelegateSample
{
    public delegate bool ComparisonHandler(int first, int second);
}

1.3 委托的实例化

        为了实例化委托,需要一个和委托类型自身签名匹配的方法。 例如:

public delegate bool ComparisonHandler(int first, int second);
class DelegateSample
{
    public static void BubbleSort(int[] items, ComparisonHandler comparisonMethod)
    {
    	//…
    }
    public static bool GreaterThan(int first, int second)
    {
        return first > second;
    }
    static void Main()
    {
        int i;
        int[] items = new int[5];
        for(i = 0; i < items.Length; i++)
        {
            Console.Write("Enter an integer: ");
            items[i] = int.Parse(Console.ReadLine());
        }
        BubbleSort(items, GreaterThan);
        for(i = 0; i < items.Length; i++)
            Console.WriteLine(items[i]);
    }
}

1.4 委托的内部机制 

        委托是特殊的类,.Net 中的委托类型总是派生自System.MulticastDelegate, 后者又从 System.Delegate 派生。

        C# 编译器不允许声明直接或间接从System.Delegate 或者System.MulicastDelegate 派生的类。

1.5 Lambda 表达式

        C#2.0 引入非常精简的语法创建委托,相关的特性被称为匿名方法。 C# 3.0 相关的特性称为Lambda 表达式。这两种语法统称 匿名函数。Lambda   表达式本身分为两种类型: 语句lambda和表达式 lambda。

1.6 语句lambda

        语句lambda 由形参列表,后面跟lambda 操作符=>, 然后跟一个代码块构成。 例如: 

public class DelegateSample
{
    //…..
    public static void Main()
    {
        int i;
        int[] items = new int[5];
        for(i = 0; i < items.Length; i++)
        {
            Console.Write("Enter an integer: ");
            items[i] = int.Parse(Console.ReadLine());
        }
        BubbleSort(items, (int first, int second) => { return first < second;});
        for(i = 0; i < items.Length; i++)
        {
            Console.WriteLine(items[i]);
        }
    }
}

        当编译器能从Lambda表达式转换成的委托推断出类型,所有Lambda都不需要显式声明参数类型。 在不能推断出类型时,C#要求显式指定Lambda类型。只要显式指定了一个Lambda参数类型,所有参数类型都必须被显式指定, 例如:

public static void ChapterMain()
{
    int i;
    int[] items = new int[5];
    for(i = 0; i < items.Length; i++)
    {
        Console.Write("Enter an integer:");
        items[i] = int.Parse(Console.ReadLine());
    }
    DelegateSample.BubbleSort(items, (first, second) => { return first < second;});
    for(i = 0; i < items.Length; i++)
    {
        Console.WriteLine(items[i]);
    }
}

        当只有单个参数,而且类型可以推断时,这种Lambda 表达式可省略围绕参数列表的圆括号。如果Lambda没有参数, 或者有不止一个参数,或者显式指定了类型的单个参数,那么就必须将参数列表放到圆括号中。  例如:

public class Program
{
    public static void ChapterMain()
    {
        IEnumerable<Process> processes = Process.GetProcesses().Where(
           process => { return process.WorkingSet64 > 1000000000; });
    }
}

        无参数的语句Lambda

public static void ChapterMain()
{
    Func<string> getUserInput = () =>
    {
        string input;
        do
        {
            input = Console.ReadLine();
        }
        while(input.Trim().Length == 0);
        return input;
    };
}

1.7 表达式lambda

        表达式lambda只有要返回的表达式,完全没有语句块。例如:

public static void Main()
{
    int i;
    int[] items = new int[5];
    for(i = 0; i < items.Length; i++)
    {
        Console.Write("Enter an integer:");
        items[i] = int.Parse(Console.ReadLine());
    }
    DelegateSample.BubbleSort(items, (first, second) => first < second);
    for(i = 0; i < items.Length; i++)
    {
        Console.WriteLine(items[i]);
    }
}

        和null字面量相似,匿名函数不与任何类型关联。它的类型由它将要转换成的类型决定。所以不能对一个匿名方法使用typeof()操作符。另外,只有将匿名方法转换成一个特定类型后才能调用GetType

1.8 Lambda表达式

1.9 通用的委托

        .Net 3.5 包含了一组通用的委托。System.Func 系列委托代表有返回值方法,而System.Action 系列委托代表返回void 的方法。Func 委托的最后一个参数总是委托的返回类型,其他参数依次对应于委托参数的类型。

//public delegate void Action();
//public delegate void Action<in T>(T arg);
//public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
//public delegate void Action<in T1, in T2, in T3>(T1 arg1, T2 arg2, T3 arg3);
//public delegate void Action<in T1, in T2, in T3, in T4>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
// ...
//public delegate void Action<in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16>(
//        T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12, 
//         T13 arg13, T14 arg14, T15 arg15, T16 arg16);

//public delegate TResult Func<out TResult>();
//public delegate TResult Func<in T, out TResult>(T arg);
//public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
//public delegate TResult Func<in T1, in T2, in T3, out TResult>(T1 arg1, T2 arg2, T3 arg3);
//public delegate TResult Func<in T1, in T2, in T3, in T4, out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4);
// ...
//public delegate TResult Func<   in T1, in T2, in T3, in T4, in T5, in T6, in T7, in T8, in T9, in T10, in T11, in T12, in T13, in T14, in T15, in T16,
//       out TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9, T10 arg10, T11 arg11, T12 arg12,
//        T13 arg13, T14 arg14, T15 arg15, T16 arg16);             

        在许多情况下,.NET 3.5 添加的Func 委托能完全避免定义自己的委托类型。例如:

public static void BubbleSort(int[] items, Func<int, int, bool> comparisonMethod)

1.10 委托没有结构相等性

         .NET 委托类型不具备结构相等性。也就是说,不能将某个委托类型的对象转换成不相关的委托类型,即使这两个委托类型的形参和返回类型完全一致。 然而,通过C# 4.0 添加的对可变性的支持,可以在某些引用类型之间进行引用转换。

public static void ChapterMain()
{
    // Contravariance
    Action<object> broadAction =
        (object data) =>
        {
            Console.WriteLine(data);
        };
    Action<string> narrowAction = broadAction;
    // Covariance
    Func<string> narrowFunction =
        () => Console.ReadLine();
    Func<object> broadFunction = narrowFunction;
    // Contravariance and covariance combined
    Func<object, string> func1 =
        (object data) => data.ToString();
    Func<string, object> func2 = func1;
}

1.11 Lambda表达式和匿名方法的内部机制

        当编译器遇到匿名方法时, 会把它转换为特殊的隐藏的类,字段和方法。 例如:

public static void Main()
{
    int i;
    int[] items = new int[5];
    for(i = 0; i < items.Length; i++)
    {
        Console.Write("Enter an integer:");
        items[i] = int.Parse(Console.ReadLine());
    }
    BubbleSort(items, DelegateSample.__AnonymousMethod_00000000);
    for(i = 0; i < items.Length; i++)
    {
        Console.WriteLine(items[i]);
    }
}
private static bool __AnonymousMethod_00000000(int first, int second)
{
    return first < second;
}

1.12 外部变量

        在Lambda表达式外部声明的局部变量称为表达式的外部变量。当Lambda主体使用一个外部变量时, 就说该变量被这个lambda捕获

public static void ChapterMain()
{
    int i;
    int[] items = new int[5];
    int comparisonCount = 0;
    for(i = 0; i < items.Length; i++)
    {
        Console.Write("Enter an integer:");
        items[i] = int.Parse(Console.ReadLine());
    }
    DelegateSample.BubbleSort(items, (int first, int second) => {comparisonCount++; return first < second; });
    for(i = 0; i < items.Length; i++)
	    Console.WriteLine(items[i]);
    Console.WriteLine("Items were compared {0} times.", comparisonCount);
}

        如果Lambda表达式捕获了外部变量,根据该表达式创建委托可能具有比局部变量更长的生存期。在此情况下,被捕获的变量的生存期变长了。

1.13 外部变量的CIL实现

        在Lambda表达式外部声明的局部变量称为表达式的外部变量。当Lambda主体使用一个外部变量时, 就说该变量被这个lambda捕获

private sealed class __LocalsDisplayClass_00000001
{
    public int comparisonCount;
    public bool __AnonymousMethod_00000000(int first, int second)
    {
        comparisonCount++;
        return first < second;
    }
}
public static void Main()
{
    int i;
    __LocalsDisplayClass_00000001 locals = new __LocalsDisplayClass_00000001();
    locals.comparisonCount = 0;
    int[] items = new int[5];
    for(i = 0; i < items.Length; i++)
    {
         Console.Write("Enter an integer:");
         items[i] = int.Parse(Console.ReadLine());
    }
    DelegateSample.BubbleSort(items, locals.__AnonymousMethod_00000000);
    for(i = 0; i < items.Length; i++)
	    Console.WriteLine(items[i]);
    Console.WriteLine("Items were compared {0} times.", locals.comparisonCount);
}

二、事件

2.1 多播委托

        委托本身是发布-订阅模式的基本单位

        一个委托值可以引用一系列方法的, 这些方法将顺序调用。这样的委托称为多播委托。利用多播委托,单一事件的通知可以发布给多个订阅者

2.2 使用多播委托来编码Observer模式

        定义订阅者方法

class Cooler
{
    public Cooler(float temperature) {Temperature = temperature;}
    public float Temperature { get; set; }
    public void OnTemperatureChanged(float newTemperature)
    {
        if(newTemperature > Temperature)
            System.Console.WriteLine("Cooler: On");
        else
            System.Console.WriteLine("Cooler: Off");
    }
}
class Heater
{
    public Heater(float temperature) {Temperature = temperature;}
    public float Temperature { get; set; }
    public void OnTemperatureChanged(float newTemperature)
    {
        if(newTemperature < Temperature)
            System.Console.WriteLine("Heater: On");
        else
            System.Console.WriteLine("Heater: Off");
    }
}

        定义发布者

public class Thermostat
{
    public Action<float> OnTemperatureChange { get; set; }
    public float CurrentTemperature
    {
        get { return _CurrentTemperature; }
        set
        {
            if(value != CurrentTemperature)
            {
                _CurrentTemperature = value;
            }
        }
    }
    private float _CurrentTemperature;
}

        连接发布者和订阅者

public static void Main()     
{        
    Thermostat thermostat = new Thermostat();            
    Heater heater = new Heater(60);
    Cooler cooler = new Cooler(80);            
    string temperature;            // Using C# 2.0 or later syntax.          
    thermostat.OnTemperatureChange +=                heater.OnTemperatureChanged;       
    thermostat.OnTemperatureChange +=                cooler.OnTemperatureChanged;       
    Console.Write("Enter temperature: ");           
    temperature = Console.ReadLine();          
    thermostat.CurrentTemperature = int.Parse(temperature);     
}

        调用委托

public class Thermostat
{
    // Define the event publisher
    public Action<float> OnTemperatureChange { get; set; }
    public float CurrentTemperature
    {
        get { return _CurrentTemperature; }
        set
        {
            if(value != CurrentTemperature)
            {
                _CurrentTemperature = value;
                // INCOMPLETE:  Check for null needed Call subscribers
                OnTemperatureChange(value);
            }
        }
    }
    private float _CurrentTemperature;
}

        检查null值

public static void ChapterMain()
{
    Thermostat thermostat = new Thermostat();
    Heater heater = new Heater(60);
    Cooler cooler = new Cooler(80);
    Action<float> delegate1;
    Action<float> delegate2;
    Action<float> delegate3;
    // use Constructor syntax for C# 1.0.
    delegate1 = heater.OnTemperatureChanged;
    delegate2 = cooler.OnTemperatureChanged;
    Console.WriteLine("Invoke both delegates:");
    delegate3 = delegate1;
    delegate3 += delegate2;
    delegate3(90);
    Console.WriteLine("Invoke only delegate2");
    delegate3 -= delegate1;
    delegate3(30);
}

2.3 委托操作符

        为了合并多个订阅者,要使用+= 操作符。这个操作符会获取第一个委托,并将第二个委托添加到委托链中。 要从委托链中删除委托,则要使用-=操作符

public static void ChapterMain()
{
    Thermostat thermostat = new Thermostat();
    Heater heater = new Heater(60);
    Cooler cooler = new Cooler(80);
    Action<float> delegate1;
    Action<float> delegate2;
    Action<float> delegate3;
    // use Constructor syntax for C# 1.0.
    delegate1 = heater.OnTemperatureChanged;
    delegate2 = cooler.OnTemperatureChanged;
    Console.WriteLine("Invoke both delegates:");
    delegate3 = delegate1;
    delegate3 += delegate2;
    delegate3(90);
    Console.WriteLine("Invoke only delegate2");
    delegate3 -= delegate1;
    delegate3(30);
}

        我们还可以使用+ 和- 合并委托

public static void ChapterMain()
{
    Thermostat thermostat = new Thermostat();
    Heater heater = new Heater(60);
    Cooler cooler = new Cooler(80);
    Action<float> delegate1, delegate2, delegate3;
    delegate1 = heater.OnTemperatureChanged;
    delegate2 = cooler.OnTemperatureChanged;
    Console.WriteLine("Combine delegates using + operator:");
    delegate3 = delegate1 + delegate2;
    delegate3(60);
    Console.WriteLine("Uncombine delegates using - operator:");
    delegate3 = delegate3 - delegate2;
    delegate3(60);
}

        无论+ - 还是+= 、-=,在内部都是使用静态方法system.Delegate.combine() 和System.Delegate.Remove()来实现的。Combine 会连接两个参数,将两个委托的调用列表按顺序连接起来,Remove 则将第二个参数指定的委托删除

        顺序调用

2.4 错误处理

        假如一个订阅者发生了异常,链中后续的订阅者就收不到通知

public static void Main()
{
    Thermostat thermostat = new Thermostat();
    Heater heater = new Heater(60);
    Cooler cooler = new Cooler(80);
    string temperature;
    thermostat.OnTemperatureChange +=heater.OnTemperatureChanged;
    thermostat.OnTemperatureChange +=
        (newTemperature) =>
    {
        throw new InvalidOperationException();
    };
    thermostat.OnTemperatureChange +=
        cooler.OnTemperatureChanged;

    Console.Write("Enter temperature: ");
    temperature = Console.ReadLine();
    thermostat.CurrentTemperature = int.Parse(temperature);
}

        为了避免该问题,必须手动遍历委托链并单独调用委托

public class Thermostat {
    public Action<float> OnTemperatureChange;
    public float CurrentTemperature
    {
        get { return _CurrentTemperature; }
        set
        {
            if(value != CurrentTemperature) {
                _CurrentTemperature = value;
                Action<float> onTemperatureChange = OnTemperatureChange;
                if (onTemperatureChange != null) {
                    List<Exception> exceptionCollection =   new List<Exception>();
                    foreach(Action<float> handler in onTemperatureChange.GetInvocationList())
                    {
                        try  {
                            handler(value);
                        }
                        catch(Exception exception)   {
                            exceptionCollection.Add(exception);
                        }
                    }
                    if(exceptionCollection.Count > 0) 
                        throw new AggregateException(  "There were exceptions thrown by " +  "OnTemperatureChange Event subscribers.", exceptionCollection);
                }
            }
        }
    }
    private float _CurrentTemperature;
}

2.5 事件的作用

        封装订阅: 事件仅对包容类内部对象提供对赋值操作符的支持。

public static void Main()
{
    Thermostat thermostat = new Thermostat();
    Heater heater = new Heater(60);
    Cooler cooler = new Cooler(80);
    string temperature;
    thermostat.OnTemperatureChange = heater.OnTemperatureChanged;
    // Bug: Assignment operator overrides // previous assignment.
    thermostat.OnTemperatureChange =    cooler.OnTemperatureChanged;
    Console.Write("Enter temperature: ");
    temperature = Console.ReadLine();
    thermostat.CurrentTemperature = int.Parse(temperature);
}

        封装发布: 事件确保只有包容类才能触发异常

public static void ChapterMain()
{
    //……..
    thermostat.OnTemperatureChange +=  heater.OnTemperatureChanged;
    thermostat.OnTemperatureChange +=cooler.OnTemperatureChanged;
    // Bug: Should not be allowed
    thermostat.OnTemperatureChange(42);
}

2.6 事件的声明

        C# 使用事件解决了委托的两大问题。Event 定义了一个新的成员类型,例如:

public class Thermostat 
{
    public class TemperatureArgs : System.EventArgs 
    {
        public TemperatureArgs(float newTemperature)
        {
            NewTemperature = newTemperature;
        }
        public float NewTemperature { get; set; }
    }
    // Define the event publisher
    public event EventHandler<TemperatureArgs> OnTemperatureChange = delegate { };
    public float CurrentTemperature
    {
        get { return _CurrentTemperature; }
        set { _CurrentTemperature = value; }
    }
    private float _CurrentTemperature;
}

         添加关键字event后,会禁止为一个public 委托字段使用赋值操作符,只有包容类才能调用向所有委托发出的通知委托; delegate{}表示一个空委托,代表由零个侦听者构成的集合。通过赋值空委托,可以引发事件而不必检查是否有侦听者

2.7 编码规范

        为了获得所需功能,需要将原始委托变量声明为字段,然后添加event关键字。为了遵循C#编码规范,需要将原始委托替换成新的委托类型 EventHandle,例如:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e)
    where TEventArgs : EventArgs;
  • 第一参数sender是object 类型, 它包含对调用委托的那个对象的一个引用(静态事件为null)  
  • 第二参数是System.EventArgs类型的,或者从System.EventArgs派生,但包含了事件的附加数据。

        触发事件通知

public float CurrentTemperature
{
    get { return _CurrentTemperature; }
    set {
        if(value != CurrentTemperature)
        {
            _CurrentTemperature = value;
            // If there are any subscribers, notify them of changes in temperature by invoking said subcribers
            OnTemperatureChange?.Invoke( this, new TemperatureArgs(value));
         }
    }
}

规范 :

  • 要在调用委托前检查它的值不为null  
  • 不要为非静态事件的sender传递null 值  
  • 要为静态事件的sender传递null值
  • 不要为eventArgs传递null值 要为事件使用EventHandler<TEventArgs> 委托类型
  • 要为TEventArgs 使用System.EventArgs类型或者它的派生类型  
  • 考虑使用System.EventArgs的子类作为事件的实参类型,除非完全确定事件永远不需要携带任何数据

2.8 事件的内部机制

        事件限制外部类只能通过+=向发布者添加订阅方法,并用-=取消订阅,除此之外任何事情不允许做。此外,它还禁止除包容类之外的任何类调用事件。为此,编译器会获取带有event修饰符的public 委托变了,并将委托声明为private,此外还添加两个方法和特殊的事件块。

public class Thermostat {
    // ...
    // Declaring the delegate field to save the list of subscribers.
    private EventHandler<TemperatureArgs> _OnTemperatureChange;
    public void add_OnTemperatureChange(EventHandler<TemperatureArgs> handler) {
        System.Delegate.Combine(_OnTemperatureChange, handler);
    }
    public void remove_OnTemperatureChange(  EventHandler<TemperatureArgs> handler) {
        System.Delegate.Remove(_OnTemperatureChange, handler);
    }
    //public event EventHandler<TemperatureArgs> OnTemperatureChange
    //{
    //    add
    //    {
    //        add_OnTemperatureChange(value);
    //    }
    //    remove
    //    {
    //        remove_OnTemperatureChange(value);
    //    }
    //}
    public class TemperatureArgs : System.EventArgs {
        public TemperatureArgs(float newTemperature) {}
    }
}

2.9 自定义事件的实现

        编译器为+=和-=生成的代码是可以自定义的,例如

public class Thermostat {
    public class TemperatureArgs : System.EventArgs
    {
        //….
    }
    // Define the delegate data type
    public delegate void TemperatureChangeHandler(object sender, TemperatureArgs newTemperature);
    // Define the event publisher
    public event TemperatureChangeHandler OnTemperatureChange
    {
        add
        {
            _OnTemperatureChange = (TemperatureChangeHandler)System.Delegate.Combine(value, _OnTemperatureChange);
        }
        remove
        {
            _OnTemperatureChange = (TemperatureChangeHandler)System.Delegate.Remove(_OnTemperatureChange, value);
        }
    }
    protected TemperatureChangeHandler _OnTemperatureChange;
    public float CurrentTemperature
    {
        //......
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1154548.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vite+vue3路由切换滚动条位置重置el-scrollbar

vitevue3路由切换滚动条位置重置 本文目录 vitevue3路由切换滚动条位置重置使用原生滚动条使用el-scrollbaruseRoute和useRouter 当切换到新路由时&#xff0c;想要页面滚到顶部&#xff0c;或者是保持原先的滚动位置&#xff0c;就像重新加载页面那样&#xff0c;vue-router 可…

升级版运算放大器应用电路(二)

网友&#xff1a;你上次分享的经典运算放大电路太棒了&#xff0c;再分享几个吧&#xff01; 工程师&#xff1a;好吧&#xff0c;那你瞪大耳朵&#xff0c;看好了~ 1、仪器放大电路&#xff0c;此电路使用于小信号的放大&#xff0c;一般用于传感器信号的放大。传感器的输出信…

紫光同创PGL50H图像Sobel边缘检测

本原创文章由深圳市小眼睛科技有限公司创作&#xff0c;版权归本公司所有&#xff0c;如需转载&#xff0c;需授权并注明出处 适用于板卡型号&#xff1a; 紫光同创PGL50H开发平台&#xff08;盘古50K开发板&#xff09; 一&#xff1a;软硬件平台 软件平台&#xff1a;PDS_…

ROS中如何实现将一个基于A坐标系下的三维向量变换到基于B坐标系下?

摘要 ROS中通过tf.TransformListener.lookupTransform方法获取从A坐标系到B坐标系的旋转四元数rot&#xff0c;通过quaternion_multiply函数实现 p ′ q p q − 1 p qpq^{-1} p′qpq−1中的矩阵乘法&#xff0c;从而可以方便获取在新坐标系下的四元数坐标表示 p ′ p p′. 基…

app广告变现,开发者如何提高用户的参与度?

越来越多的开发者已经认识到用户参与移动应用内广告的价值。 1、人性化的消息传递 人性化的消息传递在情感层面上与用户产生共鸣的方式进行沟通&#xff0c;让他们感到被理解、被重视和参与。它在用户之间建立了一种信任感和可信度。对话式和相关的语气建立了超越交易关系的联…

OBS直播软件使用NDI协议输入输出

OBS&#xff08;Open Broadcaster Software&#xff09;是一个免费的开源的视频录制和视频推流软件。其功能强大并广泛使用在视频导播、录制及直播等领域。 OBS可以导入多种素材&#xff0c;除了本地音频、视频、图像外&#xff0c;还支持硬件采集设备&#xff0c;更能支持各种…

致远OA wpsAssistServlet任意文件读取漏洞复现 [附POC]

文章目录 致远OA wpsAssistServlet任意文件读取漏洞复现 [附POC]0x01 前言0x02 漏洞描述0x03 影响版本0x04 漏洞环境0x05 漏洞复现1.访问漏洞环境2.构造POC3.复现 0x06 修复建议 致远OA wpsAssistServlet任意文件读取漏洞复现 [附POC] 0x01 前言 免责声明&#xff1a;请勿利用…

多测师肖sir_高级金牌讲师_性能测试之badboy录制脚本02

性能测试之badboy录制脚本 一、下载安装包&#xff0c;点击安装 二、点击我同意 三、选择路径&#xff0c;点击install 打开以下界面&#xff0c;表示安装成功 第二步&#xff1a;录制流程 界面视图&#xff0c;模拟浏览器&#xff0c;能够进行操作 需要录制脚本的URL 点…

英语小作文模板(06求助+描述;07描述+建议)

06 求助描述&#xff1a; 题目背景及要求 第一段 第二段 第三段 翻译成中文 07 描述&#xff0b;建议&#xff1a; 题目背景及要求 第一段 第二段

记一次 AWD 比赛中曲折的 Linux 提权

前提背景&#xff1a; 今天一场 AWD 比赛中&#xff0c;遇到一个场景&#xff1a;PHP网站存在SQL注入和文件上传漏洞, MYSQL当前用户为ROOT&#xff0c;文件上传蚁剑连接SHELL是权限很低的用户。我需要想办法进行提权&#xff0c;才能读取到 /root 目录下的 flag。 一、sqlmap …

通过外网客户端远程到内部区域网环境

拓扑 项目需要远程内部区域网德服务器&#xff0c;可以提供一台双网卡的电脑来实现

面向制造企业的持续发展,2023数字化工单管理系统创新篇章-亿发

面向制造企业的持续发展&#xff0c;2023数字化工单管理系统开创新篇章-亿发 随着制造业的持续发展&#xff0c;运维工单管理日益成为关键环节&#xff0c;它设计客户管理、设备维护、服务商合作等多个业务领域&#xff0c;对运营效率和服务质量有着重要影响。然而&#xff0c…

MySQL用户管理和授权

用户管理和授权是属于MySQL当中的DCL语句 创建用户以及一些相关操作 明文创建用户 create user zzrlocalhost IDENTIFIED by 123456;create user 这是创建用户的开头zzr表示用户名 localhost&#xff1a;新建的用户可以在哪些主机上登录。即可以使用IP地址&#xff0c;网段&a…

竞赛选题 深度学习图像风格迁移 - opencv python

文章目录 0 前言1 VGG网络2 风格迁移3 内容损失4 风格损失5 主代码实现6 迁移模型实现7 效果展示8 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习图像风格迁移 - opencv python 该项目较为新颖&#xff0c;适合作为竞赛课题…

SAP MM学习笔记39 - MRP(资材所要量计划)

这一章开始&#xff0c;离开请求书&#xff0c;学点儿新知识啦。 MRP ( Material Requirement Planning ) - 资材所要量计划。 它的位置在下面的调达周期图上来看&#xff0c;就是右上角的 所要量决定那块儿。 1&#xff0c;MRP(资材所要量计划) 的概要 MRP 的主要目的就是 确…

2024云渲染渲染100超简便的使用方法!渲染100云渲染邀请码5858

云渲染解决了本地电脑只能同时渲染一张图&#xff0c;并且占用本地电脑情况&#xff0c;让云渲染使用者也越来越多&#xff01; 最近好多朋友在问我渲染100 - 官方注册邀请码【5858】如何提交渲染&#xff1f;今天我来总结一下 1.先在官网下载客户端&#xff0c;网页认证为渲染…

QT通过url下载http地址下的文件(文件夹)

前言 之前只写过通过http协议通信&#xff0c;没有写过下载http地址中的文件或者文件夹&#xff0c;了解一下在QT下如何下载。 其实很简单&#xff0c;同使用协议通信相同的是&#xff0c;创建QNetworkAccessManager和QNetworkRequest&#xff0c;设置QNetworkRequest的url&a…

游戏中的随机——“动态平衡概率”算法(二)

前言 本文是对上一篇文章的补充和总结。 在上一篇文章中&#xff0c;笔者提出了一套基本可用的“动态平衡概率”算法&#xff0c;本文将继续对该算法进行更加深入的探讨&#xff0c;解决上篇文章中的部分遗留问题&#xff0c;以及记录一下对“游戏中的概率”的一些思考&#…

【优选算法系列】第一节.二分查找简介加习题(704. 二分查找和34. 在排序数组中查找元素的第一个和最后一个位置)

文章目录 前言二分查找简介一、二分查找 1.1 题目描述 1.2 题目解析 1.2.1 算法原理 1.2.2 代码编写二、在排序数组中查找元素的第一个和最后一个位置 2.1 题目描述 2.2 题目解析 2.2.1 算法原理 2.2.2 代码编…

CSS选择器、CSS属性相关

CSS选择器 CSS属性选择器 通过标签的属性来查找标签&#xff0c;标签都有属性 <div class"c1" id"d1"></div>id值和class值是每个标签都自带的属性&#xff0c;还有另外一种&#xff1a;自定义属性 <div class"c1" id"d1&…