57、C 固定大小缓冲区与运算符重载详解

C# 固定大小缓冲区与运算符重载详解

1. 固定大小缓冲区

在 C# 中,固定大小缓冲区可像数组一样聚合在结构体中,但结构体支持使用固定大小缓冲区,类则不支持。使用固定大小缓冲区时,需遵循以下规则:
- 仅在非安全上下文中可用。
- 只能表示一维数组(向量)。
- 数组必须有特定长度。
- 仅允许在 struct 类型中使用。
- 仅涉及 bool byte char short int long sbyte ushort uint ulong float double 类型。

固定大小缓冲区的语法如下:

attributs accessibilité modificateur fixed type identificateur [expression]

以下是使用 fixed 关键字的 OSVERSIONINFO 结构体示例代码:

public class API {
    [StructLayout(LayoutKind.Sequential)] 
    unsafe public struct OSVERSIONINFO { 
        public System.Int32 dwOSVersionInfoSize; 
        public System.Int32 dwMajorVersion; 
        public System.Int32 dwMinorVersion; 
        public System.Int32 dwBuildNumber; 
        public System.Int32 dwPlatformId; 
        public fixed char szCSDVersion[128]; 
    } 
}

1.1 指针与非安全代码

虽然 C# 语言通常不提供指针,但开发者仍可在非安全代码上下文中使用。使用指针时,需注意以下几点:
- 指针使用限于非安全代码,代码中必须出现 unsafe 关键字。
- 可创建指向非托管类型(如 int float char 类型)的指针。
- 非安全代码必须使用 unsafe 编译器选项进行编译。

1.2 内存管理与 fixed 语句

不能创建指向可移动内存的指针,因为托管内存可移动且由垃圾回收器(GC)管理。 fixed 语句可在一个代码块期间固定托管内存,使固定后的内存不再受 GC 影响。

1.3 函数导入与互操作性

DllImportAttribute 用于描述从库中导出的非托管函数,包含多种配置函数导入的选项。在函数调用期间,互操作性封送器会在托管内存和非托管内存之间封送参数和返回值。其中,可直接封送的类型无需转换,不可直接封送的类型则需转换为非托管内存。开发者可使用 MarshalAs 属性显式封送不可直接封送的类型。

2. 运算符重载概述

开发者可对用户定义类型进行运算符重载,以描述该类型与常用运算符的交互方式。内置类型(如 int float 和长值类型)能自动支持大多数运算符,使用条件、逻辑和数学运算符时表现直观。但 C# 编译器无法直接在标准运算符上下文中使用用户定义的类,此时需要通过运算符重载来协助编译器理解。

运算符重载适用于表示数字系统或执行数学运算的用户定义类型,如分数、复数、总和、十六进制数字系统等。此外,还可通过运算符重载描述用户定义类型之间的转换。

运算符重载应直观且避免混淆,例如 System.String operator+ 进行重载以实现字符串连接,这与加法概念类似。若重载 operator+ 导致截断字符,则会造成混淆。运算符重载不宜过度使用,并非适用于所有情况。若不想使用运算符重载,可实现命名方法(如 Add 代替 operator+ Subtract 代替 operator- ),命名方法调用更明确,而运算符函数调用较隐式。

3. 逻辑和数学运算符重载

3.1 不可重载的运算符

逻辑和数学运算符大多为一元或二元运算符,用户定义类型可对其进行重载,但条件运算符(唯一的三元运算符)不可重载。以下是用户定义类型中不可重载的运算符列表:
- 点运算符( identifier.member
- 调用运算符( methodname()
- 赋值运算符( = += /= %= 等)
- 条件运算符( && || ?:
- checked unchecked 运算符
- new 运算符
- typeof 运算符
- as is 运算符
- 数组运算符( []
- 指针运算符

赋值运算符(如 += /= )可通过重载单个运算符隐式重载。此外,开发者不能使用运算符重载定义新运算符。

3.2 C++ 与 C# 运算符重载差异

C++ 中可重载赋值运算符、 new 运算符和数组运算符,但 C# 不允许。从 C++ 迁移代码到 C# 时需注意,可通过实现 ICloneable 接口替代赋值运算符重载。由于垃圾回收器负责管理动态内存, new 运算符在托管代码中不可重载。在 C++ 中,数组运算符重载常用于创建安全数组以控制边界错误,但在 C# 中,CLR 会自动检测这些错误,减少了重载数组运算符的必要性。

3.3 运算符重载的特性

运算符重载会重新定义运算符在表达式中的行为,但不会改变运算符的语法、优先级或结合性。例如,重载除法运算符后,其语法仍为 obj1/obj2 ,优先级和结合性也保持不变。

3.4 运算符重载的实现

重载运算符需实现为静态公共函数。一元运算符有一个操作数,二元运算符有两个操作数,且重载运算符的参数不能是 ref out

一元或二元运算符重载方法的语法如下:

public static type operator unaire(typeclasse opérande)
public static type operator binaire(type opérandelhs, type opéranderhs) 

对于一元运算符方法,操作数类型与包含类相同;对于二元运算符方法,至少一个参数必须是包含类型,这样类型才能在表达式中作为左操作数(lhs)、右操作数(rhs)或同时作为两者出现。返回类型可以是除 void 外的任何值类型。

以下是一个 ZClass 类重载 operator+ 的示例:

using System; 
namespace Donis.CSharpBook{ 
    public class Starter{ 
        public static void Main(){ 
            ZClass obj1=new ZClass(5,10); 
            ZClass obj2=new ZClass(15,20); 
            ZClass obj3=obj1+obj2; 
            Console.WriteLine(obj3.Total); 
        } 
    } 
    class ZClass {
        public ZClass(int _fielda, int _fieldb) {
            fielda=_fielda; 
            fieldb=_fieldb; 
        } 
        public static ZClass operator+(ZClass lhs, ZClass rhs) {
            return new ZClass(lhs.fielda+rhs.fielda,
                lhs.fieldb+rhs.fieldb);
        }
        public int Total { 
            get {
                return fielda+fieldb; 
            } 
        } 
        protected int fielda, fieldb; 
    } 
}

3.5 派生类中的运算符重载

运算符方法在派生类中可用,运算符函数至少有一个操作数为基类型。可将派生类实例替换为基类型操作数并访问基类型成员,但返回基类型实例给派生类型变量时可能会在运行时出错。若要在派生类中改变运算符行为,可重新实现运算符函数。

以下是 YClass 派生自 ZClass 并进行运算符重载的示例:

namespace Donis.CSharpBook{ 
    public class Starter{ 
        public static void Main(){ 
            YClass obj1=new YClass(5,10); 
            YClass obj2=new YClass(15,20); 
            ZClass obj3=obj1+obj2; 
            Console.WriteLine(obj3.Total); 
            YClass obj4=obj1-obj2; 
            Console.WriteLine(obj4.Total); 
        } 
    } 
    // Partial listing 
    public class YClass:ZClass { 
        public YClass(int _fielda, int _fieldb)
            :base(_fielda, _fieldb) { 
        } 
        public static YClass operator-(YClass lhs, YClass rhs) {
            return new YClass(lhs.fielda-rhs.fielda,
                lhs.fieldb-rhs.fieldb);
        }
    }
} 

3.6 多次重载运算符

可对成员运算符函数进行多次重载,每次重载的函数签名必须唯一。操作数的隐式转换在一定程度上可减少运算符重载的需求。

以下是 operator+ 被重载三次的示例:

public static ZClass operator+(ZClass lhs, ZClass rhs) {
    return new ZClass(lhs.fielda+rhs.fielda,
        lhs.fieldb+rhs.fieldb);
} 
public static ZClass operator+(ZClass lhs, int rhs) {
    return new ZClass(lhs.fielda+rhs,
        lhs.fieldb+rhs);
} 
public static ZClass operator+(int lhs, ZClass rhs) {
    return new ZClass(lhs+rhs.fielda,
        lhs+rhs.fieldb);
} 

使用示例:

obj3=obj1+obj2;
obj1=obj1+10;
obj2=20+obj2; 

3.7 并行命名方法

由于运算符重载未在公共语言规范(CLS)中规定,并非所有托管语言都支持。因此,建议为每个成员运算符函数实现并行命名方法,命名方法可调用成员运算符函数。

以下是 ZClass 类实现并行命名方法的示例:

public class ZClass { 
    public ZClass(int _fielda, int _fieldb) { 
        fielda=_fielda; 
        fieldb=_fieldb; 
    } 
    public static ZClass operator+(ZClass lhs, ZClass rhs) {
        return new ZClass(lhs.fielda+rhs.fielda, 
            lhs.fieldb+rhs.fieldb); 
    } 
    public ZClass Add(ZClass rhs) {
        return this+rhs; 
    } 
    public int Total { 
        get { 
            return fielda+fieldb; 
        } 
    } 
    protected int fielda, fieldb; 
}

3.8 运算符重载的语义规则

运算符重载的语义确保逻辑和数学运算符的唯一性,重载这些运算符时需遵循特殊规则。

3.9 增量和减量运算符重载

增量( ++ )和减量( -- )运算符是一元运算符,操作数和返回类型必须是包含类或其派生类。为保持运算符的底层行为,增量和减量运算符应返回当前对象的修改实例,也可返回全新实例,但重载不应改变运算符的基本含义。重载增量或减量运算符会同时影响其前缀和后缀使用方式。

以下是 ZClass 类重载增量和减量运算符的示例:

public class ZClass { 
    public ZClass(int _fielda, int _fieldb) { 
        fielda=_fielda; 
        fieldb=_fieldb; 
    }
    public static ZClass operator++(ZClass curr) {
        ++curr.fielda; 
        ++curr.fieldb; 
        return curr; 
    }
    public static ZClass operator--(ZClass curr) {
        --curr.fielda; 
        --curr.fieldb; 
        return curr; 
    } 
    public int Total {
        get { 
            return fielda+fieldb; 
        } 
    } 
    public int fielda, fieldb; 
}

3.10 左移和右移运算符重载

左移和右移运算符通常执行二进制移位操作。重载这些运算符时,第一个操作数必须与包含类类型相同,第二个操作数必须是 int 类型,表示移位的幅度。返回类型可以是除 void 外的任何元素。左移和右移运算符可独立实现,但建议同时实现,因为它们之间存在逻辑联系。

综上所述,运算符重载为用户定义类型提供了更灵活的使用方式,但在使用时需遵循相关规则和原则,确保代码的可读性和可维护性。通过合理运用运算符重载和配套的命名方法,可使代码更加直观和易于理解。

4. 转换运算符重载

4.1 转换运算符的作用

转换运算符可用于描述用户定义类型之间的转换,包括将用户定义类型转换为内置类型或其他用户定义类型。在大多数情况下,内置类型之间可以进行转换,例如将短值隐式转换为长值,反之亦然。但对于用户定义类型,编译器需要开发者通过重载转换运算符来明确转换规则。

4.2 转换运算符的实现

转换运算符也需实现为静态公共函数,其语法如下:
- 隐式转换:

public static implicit operator targettype(sourcetype operand)
  • 显式转换:
public static explicit operator targettype(sourcetype operand)

以下是一个简单的示例,展示如何将 ZClass 类型转换为 int 类型:

public class ZClass {
    public ZClass(int _fielda, int _fieldb) {
        fielda = _fielda;
        fieldb = _fieldb;
    }
    public static implicit operator int(ZClass obj) {
        return obj.fielda + obj.fieldb;
    }
    public int fielda, fieldb;
}

class Program {
    static void Main() {
        ZClass obj = new ZClass(5, 10);
        int result = obj; // 隐式转换
        Console.WriteLine(result);
    }
}

4.3 转换运算符的注意事项

  • 转换运算符应确保转换的合理性和直观性,避免出现意外的结果。
  • 隐式转换应确保不会丢失数据或引发异常,否则建议使用显式转换。
  • 转换运算符的重载也应遵循运算符重载的一般原则,避免过度使用。

5. 运算符重载的实际应用示例

5.1 复数类的运算符重载

假设我们要实现一个复数类 Complex ,并对其进行运算符重载,以支持复数的加法、减法和乘法运算。

using System;

public class Complex {
    public double Real { get; set; }
    public double Imaginary { get; set; }

    public Complex(double real, double imaginary) {
        Real = real;
        Imaginary = imaginary;
    }

    // 加法运算符重载
    public static Complex operator +(Complex c1, Complex c2) {
        return new Complex(c1.Real + c2.Real, c1.Imaginary + c2.Imaginary);
    }

    // 减法运算符重载
    public static Complex operator -(Complex c1, Complex c2) {
        return new Complex(c1.Real - c2.Real, c1.Imaginary - c2.Imaginary);
    }

    // 乘法运算符重载
    public static Complex operator *(Complex c1, Complex c2) {
        double real = c1.Real * c2.Real - c1.Imaginary * c2.Imaginary;
        double imaginary = c1.Real * c2.Imaginary + c1.Imaginary * c2.Real;
        return new Complex(real, imaginary);
    }

    public override string ToString() {
        return $"{Real} + {Imaginary}i";
    }
}

class Program {
    static void Main() {
        Complex c1 = new Complex(3, 4);
        Complex c2 = new Complex(1, 2);

        Complex sum = c1 + c2;
        Complex diff = c1 - c2;
        Complex prod = c1 * c2;

        Console.WriteLine($"Sum: {sum}");
        Console.WriteLine($"Difference: {diff}");
        Console.WriteLine($"Product: {prod}");
    }
}

5.2 分数类的运算符重载

再来看一个分数类 Fraction 的运算符重载示例,支持分数的加法和减法运算。

public class Fraction {
    public int Numerator { get; set; }
    public int Denominator { get; set; }

    public Fraction(int numerator, int denominator) {
        Numerator = numerator;
        Denominator = denominator;
    }

    // 加法运算符重载
    public static Fraction operator +(Fraction f1, Fraction f2) {
        int numerator = f1.Numerator * f2.Denominator + f2.Numerator * f1.Denominator;
        int denominator = f1.Denominator * f2.Denominator;
        return new Fraction(numerator, denominator);
    }

    // 减法运算符重载
    public static Fraction operator -(Fraction f1, Fraction f2) {
        int numerator = f1.Numerator * f2.Denominator - f2.Numerator * f1.Denominator;
        int denominator = f1.Denominator * f2.Denominator;
        return new Fraction(numerator, denominator);
    }

    public override string ToString() {
        return $"{Numerator}/{Denominator}";
    }
}

class Program {
    static void Main() {
        Fraction f1 = new Fraction(1, 2);
        Fraction f2 = new Fraction(1, 3);

        Fraction sum = f1 + f2;
        Fraction diff = f1 - f2;

        Console.WriteLine($"Sum: {sum}");
        Console.WriteLine($"Difference: {diff}");
    }
}

6. 运算符重载的总结

6.1 运算符重载的优点

  • 增强代码的可读性和直观性:让用户定义类型的使用方式与内置类型相似,使代码更易于理解。
  • 提高代码的可维护性:通过合理的运算符重载,可将复杂的操作封装在运算符中,减少代码的冗余。
  • 增加代码的灵活性:允许用户根据需要自定义类型的运算规则。

6.2 运算符重载的缺点

  • 可能导致代码混淆:如果运算符重载不合理,可能会使代码的含义变得模糊,增加理解难度。
  • 增加代码复杂度:过多的运算符重载会使代码变得复杂,不易维护。
  • 可移植性问题:由于运算符重载未在所有托管语言中得到支持,可能会影响代码的可移植性。

6.3 使用运算符重载的建议

  • 遵循直观性原则:确保运算符重载的行为符合用户的预期,避免产生混淆。
  • 适度使用:避免过度使用运算符重载,只在必要时进行重载。
  • 实现并行命名方法:为每个运算符重载实现对应的命名方法,提高代码的可移植性。

7. 总结

本文详细介绍了 C# 中的固定大小缓冲区和运算符重载的相关知识。固定大小缓冲区在特定场景下可提供更高效的内存管理,但使用时需遵循严格的规则。运算符重载为用户定义类型提供了更灵活的使用方式,可使代码更加直观和易于理解。但在使用运算符重载时,需遵循相关规则和原则,确保代码的可读性和可维护性。

表格:运算符重载总结

运算符类型 可重载性 注意事项
逻辑和数学运算符(部分) 可重载 遵循语义规则,注意操作数和返回类型
赋值运算符 不可重载 可通过单个运算符重载隐式实现
条件运算符 不可重载 -
增量和减量运算符 可重载 操作数和返回类型需为包含类或派生类
左移和右移运算符 可重载 第一个操作数为包含类类型,第二个为 int 类型
转换运算符 可重载 分为隐式和显式转换,确保转换合理性

mermaid 流程图:运算符重载使用流程

graph TD;
    A[确定是否需要运算符重载] -->|是| B[选择要重载的运算符];
    B --> C[遵循运算符重载规则实现函数];
    C --> D[测试运算符重载的正确性];
    D -->|通过| E[考虑实现并行命名方法];
    E --> F[完成运算符重载];
    A -->|否| G[使用其他方法实现功能];

通过合理运用固定大小缓冲区和运算符重载,开发者可以更好地利用 C# 语言的特性,提高代码的质量和性能。在实际开发中,应根据具体需求和场景,谨慎选择是否使用这些特性,并遵循相关的规则和原则。

内容概要:本文详细介绍了一个基于CNN-GRUAdaBoost集成的深度学习模型在时间序列预测中的完整项目实现。该模型通过卷积神经网络(CNN)提取局部时空特征,利用门控循环单元(GRU)捕捉长期时序依赖,并结合AdaBoost自适应提升算法增强模型泛化能力鲁棒性,有效应对非线性、噪声干扰和复杂动态变化的挑战。项目涵盖从数据生成、预处理、模型构建、训练优化到结果可视化和GUI交互界面开发的全流程,提供了完整的代码示例模块化系统架构设计,支持金融、能源、交通、医疗等多个领域的高精度预测应用。; 适合人群:具备一定Python编程基础和机器学习知识,熟悉深度学习框架(如TensorFlow/Keras)的数据科学家、算法工程师及高校研究人员,尤其适合从事时间序列分析、智能预测系统开发的相关从业者。; 使用场景及目标:①实现高精度时间序列预测,如股票价格、电力负荷、交通流量等;②构建具备强鲁棒性和抗噪能力的工业级预测系统;③开发集成深度学习集成学习的复合模型以提升预测稳定性;④通过GUI界面实现模型的便捷部署交互式分析。; 阅读建议:建议读者结合文档中的代码逐步实践,重点关注数据预处理、模型集成机制可视化模块的设计逻辑,同时可在不同数据集上进行迁移实验,深入理解CNN-GRUAdaBoost协同工作的原理优势。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值