c#-用c#重写python的struct包-简化版

博客介绍了如何在C#中实现类似Python `struct`模块的功能,用于处理ABI文件的数据提取。作者提供了`StructHelper`类,包含了`CalcSize`方法来计算格式字符串所占字节数,以及`Unpack`方法来解码字节数组。代码实现了大端模式下的数据转换,包括`char`, `ushort`, `uint`等类型,并计划进一步完善`pack`函数和代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

最近我在搞abi文件的解析工作,本来是用Python3写的数据提取和绘图(主要涉及struct、Biopython、matplotlib等包),但是如果想把脚本放到wpf项目中,试了几种方法,效果都不太好。所以我干脆用c#重写。

本文写的是struct包的unpack函数和calcsize函数的c#简单实现,以后会再加入pack函数并改进代码。

准备知识

python3的struct包,介绍看这里: https://docs.python.org/3/library/struct.html

举个例子:

_HEADFMT = ">H4sI2H3I"

这个是ABI文件中header的编码格式。其中:
1.> 表示大端,H是unsigned short,占2字节,s是char,占1字节,I是unsigned int,占4字节。

buffer = struct.pack(">ihb", 1, 2, 3)
# 大端模式,buffer为:(1在它所占4个字节的低端)
# b'\x00\x00\x00\x01\x00\x02\x03'
# 小端模式,buffer为:(1在它所占4个字节的高端)
buffer = struct.pack("<ihb", 1, 2, 3)
# b'\x01\x00\x00\x00\x02\x00\x03'

2.4s表示s重复四次,所以展开其实是 HssssIHHIII,所以一共占26个字节

由于abi文件中涉及的格式有限,所以我只给出一部分,如有需要可以自行补全。

简单实现

public class StructHelper {
    // 不同数据类型对应的字节数目
    public static Dictionary<char, int> charSizeDict = new Dictionary<char, int> {
        {'c', 1},{'b', 1}, {'B', 1}, {'?', 1}, {'p', 1}, {'s', 1}, 
        {'e', 2}, {'h', 2}, {'H', 2},
        {'I', 4}, {'i', 4}, {'l', 4}, {'L', 4}, {'f', 4},
        {'q', 8}, {'Q', 8}, {'d', 8}
    }; 
    
    /// <summary>
    /// 计算符合该格式的字符串的总长度
    /// </summary>
    /// <param name="s">格式</param>
    /// <returns></returns>
    public int CalcSize(string s) {
        int result = 0;
        // 不考虑little-endian或big-endian的区别
        int i = 1;
        Regex regex = new Regex("[0-9]");
        string num = "";
        while (i < s.Length) {
            if (regex.IsMatch(s.Substring(i, 1))) // 是数字
            {
                num += s[i];
            }
            else {
                if (num.Length == 0)
                {
                    result += charSizeDict[s[i]];
                }
                else {
                    int count = int.Parse(num);
                    result += charSizeDict[s[i]] * count;
                    num = "";
                }
            }
            i += 1;
        }
        return result;
    }
    
    /// <summary>
    /// 按类型将byte[]转为string
    /// </summary>
    /// <param name="type">类型:s/H/I/h/b/B/f</param>
    /// <param name="raw">byte[]</param>
    /// <returns></returns>
    private string UnpackHelper(char type, byte[] raw) {
        // 小端模式要翻转
        if (BitConverter.IsLittleEndian)
            Array.Reverse(raw);
        string result = "";
        switch (type) {
            case 's':
                byte[] raw0 = new byte[2];
                if (BitConverter.IsLittleEndian)
                {
                    raw0[0] = raw[0];
                    raw0[1] = 0;
                }
                else {
                    raw0[0] = 0;
                    raw0[1] = raw[0];
                }
                result = BitConverter.ToChar(raw0,0).ToString();
                // 如果需要转ascii也可以下面这样
                //result = System.Text.Encoding.ASCII.GetString(raw);
                break;
            case 'H':
                byte[] raw2 = new byte[8];
                // BitConverter.ToInt32是转为32位有符号整数,对于可能存在的4294967295这种数值会溢出
                // 所以使用ToInt64。但是这需要补足8字节
                if (BitConverter.IsLittleEndian)
                {
                    raw2[0] = raw[0];
                    raw2[1] = raw[1];
                    raw2[2] = 0;
                    raw2[3] = 0;
                    raw2[4] = 0;
                    raw2[5] = 0;
                    raw2[6] = 0;
                    raw2[7] = 0;
                }
                else {
                    raw2[0] = 0;
                    raw2[1] = 0;
                    raw2[2] = 0;
                    raw2[3] = 0;
                    raw2[4] = 0;
                    raw2[5] = 0;
                    raw2[6] = raw[0];
                    raw2[7] = raw[1];
                }
                result = BitConverter.ToInt64(raw2, 0).ToString();
                break;
            ...
            case 'h': // short 2 bytes
                result = BitConverter.ToUInt16(raw, 0).ToString();
                break;
            case 'f': // float, 4 bytes
            	// 这个注意不要用ToDouble
                result = BitConverter.ToSingle(raw, 0).ToString(); 
                break;
            default:
                break;
        }
        return result;
    }
    
    /// <summary>
    /// 解码byte[]
    /// </summary>
    /// <param name="format">格式</param>
    /// <param name="buffer">byte[]</param>
    /// <returns></returns>
    public List<string> Unpack(string format, byte[] buffer) {
        List<string> result = new List<string>();
        char first = format[0];
        bool isBigEndian = false;
        if (first.Equals('>') || first.Equals('!')) {
            isBigEndian = true;
        }
        Regex regex = new Regex("[0-9]");
        string num = "";
        int pos = 0;
        // 暂时不考虑小端
        if (isBigEndian) {
            int i = 1;
            while (i < format.Length) {
                char curChar = format[i];
                // 如果是数字
                if (regex.IsMatch(format.Substring(i, 1)))
                {
                    num += curChar;
                }
                else
                {
                    if (charSizeDict.ContainsKey(curChar) == false) {
                        Console.WriteLine("curChar: " + curChar);
                    }
                    int singleSize = charSizeDict[curChar];
                    // 单一字符
                    if (num.Length == 0)
                    {
                        // 构建byte[]
                        byte[] raw = new byte[singleSize];
                        int k = 0;
                        for (int j = pos; j < pos + singleSize; j++) {
                            raw[k] = buffer[j];
                            k += 1;
                        }
                        // 计算结果
                        result.Add(UnpackHelper(curChar, raw));
                        // 移动buffer中的位置指针
                        pos += charSizeDict[curChar];
                    }
                    else 
                    {
                        int count = int.Parse(num);
                        string tmp = "";
                        for (int m = 0; m < count; m++)
                        {
                            byte[] raw = new byte[singleSize];
                            int k = 0;
                            for (int j = pos; j < pos + singleSize; j++)
                            {
                                raw[k] = buffer[j];
                                k += 1;
                            }
                            // I和H是各自分开的
                            if (curChar != 's')
                            {
                                result.Add(UnpackHelper(curChar, raw));
                            }
                            else { // s的话是要count个合为一个的
                                tmp += UnpackHelper(curChar, raw);
                            }
                            pos += charSizeDict[curChar];
                        }
                        if (tmp.Length > 0) {
                            result.Add(tmp);
                            //tmp = "";
                        }
                        // 清空num
                        num = "";
                    }
                }
                // 移动format中的指针
                i += 1;
            }
        }
        return result;
    }
}
<think>好的,我现在需要回答用户关于C语言结构体与C++类区别的问题。首先,我需要回忆自己之前学过的知识,确保准确无误。用户可能希望得到详细的对比,所以我要从多个方面展开分析。 首先,C的结构体和C++的类在语法上有相似之处,但功能上差异很大。C的结构体主要用于数据封装,而C++的类则支持面向对象的特性。我应该从访问控制、成员函数、继承、多态、内存管理等方面进行对比。 访问控制方面,C的结构体默认是public的,所有成员都可以直接访问。而C++的类默认是private的,成员需要显式声明为public才能被外部访问。C++的结构体其实和类类似,但默认访问权限是public,这点需要注意。可能用户容易混淆C++的结构体和类,需要特别说明。 成员函数方面,C的结构体不能含函数,只能有数据成员。而C++的类可以有成员函数,支持方法定义,这是面向对象的核心。这里要强调C的结构体只能有数据,而C++的类可以有数据和方法的结合。 继承和多态方面,C语言不支持继承,结构体之间无法直接继承。而C++的类支持单继承、多继承,以及虚函数实现多态。这部分是C++相对于C的重要扩展,需要详细解释,特别是虚函数和动态绑定的概念。 内存布局方面,C的结构体成员按照声明顺序排列,可能有填充字节。C++的类同样如此,但如果有虚函数,会含虚表指针,这会增加内存开销。这个区别对于性能敏感的应用很重要,需要指出。 类型转换方面,C的结构体需要显式转换,而C++的类可以通过继承体系进行向上、向下转型,使用dynamic_cast等操作符。这可能涉及到类型安全的问题,C++提供了更多的机制来保证转换的安全性。 实例化方式上,C的结构体变量直接声明,而C++可以使用构造函数进行初始化。构造和析构函数的存在是C++类管理资源的重要特性,比如RAII机制,这在C语言中是没有的。 模板支持方面,C的结构体无法与模板结合,而C++的类可以模板化,支持泛型编程。这也是C++在代码复用和泛型设计上的优势。 工程应用方面,C的结构体常用于数据聚合,而C++的类更适合构建复杂对象模型。可能需要举一些例子,比如C中结构体用于定义数据结构,C++类用于实现设计模式中的对象。 总结时,要强调两者虽然语法相似,但设计理念不同:C的结构体是数据集合,而C++的类是数据和行为的封装,支持OOP特性。用户可能需要理解这些区别来选择合适的工具,或者在从C转向C++时注意这些变化。 需要检查是否有遗漏的方面,比如友元函数、运算符重载、静态成员等,这些也是C++类的特性。同时,确认术语使用正确,比如“默认访问权限”、“虚函数表”等,确保解释清晰准确。 最后,组织内容时按照逻辑顺序,从基本定义到高级特性,逐步展开,帮助用户建立全面的理解。避免使用过于专业的术语,必要时举例说明,使内容更易理解。</think>以下是关于C语言结构体与C++类区别的详细对比分析,结合核心特性、应用场景及代码示例: --- ### 一、本质定位差异 | 特性 | C结构体 | C++类 | |---------------------|----------------------------------|-----------------------------------| | **设计目标** | 数据容器 | 对象建模(数据+行为) | | **编程范式** | 面向过程 | 面向对象 | --- ### 二、核心特性对比 #### 1. 访问控制 ```c // C结构体(默认全公开) struct Point { int x; // 直接访问 int y; }; ``` ```cpp // C++类(默认私有) class Circle { private: // 显式声明私有 double radius; public: void draw() { /* 方法实现 */ } }; ``` **关键区别**: - C结构体成员默认`public`,无访问控制符 - C++类默认`private`,支持`public/protected/private`三级控制 - C++结构体默认`public`(与类语法差异) --- #### 2. 成员函数 ```c // C结构体无法含函数 struct Student { char name[20]; // void print() { ... } // 编译错误 }; // 需定义外部函数操作结构体 void print_student(struct Student s) { ... } ``` ```cpp // C++类可含方法 class Student { public: string name; void print() { // 成员函数 cout << name; } }; ``` --- #### 3. 继承与多态 ```c // C无原生继承支持 struct Animal { int age; }; struct Cat { struct Animal base; // 组合模拟继承 char breed[20]; }; ``` ```cpp // C++支持继承体系 class Animal { public: virtual void speak() { // 多态基础 cout << "Animal sound"; } }; class Cat : public Animal { public: void speak() override { // 重写虚函数 cout << "Meow"; } }; ``` **多态实现**: - C++通过虚函数表(vtable)实现动态绑定 - C需手动维护函数指针模拟多态 --- #### 4. 内存布局差异 **C结构体内存模型**: ```text +-------------+ | 成员1数据 | 按声明顺序连续存储 | 成员2数据 | 可能有内存对齐填充 +-------------+ ``` **C++类内存模型(含虚函数)**: ```text +-------------+ | vptr | 虚表指针(指向vtable) | 成员数据 | +-------------+ ``` --- ### 三、进阶特性对比 #### 1. 构造/析构函数 ```cpp class Matrix { public: Matrix(int w, int h) { // 构造函数 data = new int[w*h]; } ~Matrix() { // 析构函数 delete[] data; // RAII资源管理 } private: int* data; }; ``` C语言需手动初始化和清理资源: ```c struct Matrix { int* data; int w, h; }; void matrix_init(struct Matrix* m, int w, int h) { m->data = malloc(w*h*sizeof(int)); } void matrix_clear(struct Matrix* m) { free(m->data); } ``` --- #### 2. 运算符重载 ```cpp class Vector3D { public: float x, y, z; Vector3D operator+(const Vector3D& other) { return {x+other.x, y+other.y, z+other.z}; } }; ``` C语言无法实现运算符重载,需定义函数: ```c struct Vector3D { float x, y, z; }; struct Vector3D add_vectors(struct Vector3D a, struct Vector3D b) { struct Vector3D result; result.x = a.x + b.x; // ...类似处理y,z return result; } ``` --- ### 四、工程实践差异 #### 1. 类型安全 ```cpp // C++类型检查 class A {}; class B {}; A a; B* p = &a; // 编译错误 ``` ```c // C允许强制类型转换 struct A { int x; }; struct B { double y; }; struct A a; struct B* p = (struct B*)&a; // 允许但危险 ``` --- #### 2. 模板支持 ```cpp template <typename T> class Box { T content; public: void store(T item) { content = item; } T retrieve() { return content; } }; ``` C语言无法实现泛型结构体,需使用`void*`或宏: ```c #define DEFINE_BOX(type) \ struct Box_##type { \ type content; \ } DEFINE_BOX(int); // 生成Box_int类型 ``` --- ### 五、典型应用场景 | 场景 | C结构体典型应用 | C++类典型应用 | |---------------------|--------------------------|----------------------------| | 数据记录 | 网络协议头定义 | GUI控件封装 | | 硬件交互 | 寄存器映射结构 | 设备驱动抽象层 | | 跨语言接口 | 与Python/C#交互的数据载体 | COM组件开发 | | 内存敏感型系统 | 嵌入式系统状态存储 | 游戏引擎核心对象 | --- ### 六、相互转换技巧 #### C++类装C结构体(兼容性设计) ```cpp extern "C" { // 保证C兼容布局 struct CLegacyStruct { int count; float values[10]; }; } class CppWrapper { private: CLegacyStruct raw; // 含C结构体 public: void modern_interface() { // 通过方法操作原始结构体 } }; ``` --- ### 总结记忆表 | 对比维度 | C结构体 | C++类 | |---------------|----------------------------------|----------------------------------| | **数据封装** | 纯数据聚合 | 数据+方法 | | **访问控制** | 无(默认public) | 支持三级访问控制 | | **继承体系** | 无原生支持 | 支持单/多重继承 | | **多态实现** | 需手动模拟 | 虚函数+动态绑定 | | **资源管理** | 手动初始化/清理 | 构造函数/析构函数 | | **类型系统** | 弱类型检查 | 强类型检查+类型转换操作符 | | **泛型支持** | 宏模拟 | 模板 | **核心结论**: C结构体是轻量级数据载体,C++类是面向对象的核心单元,二者在语法相似性下隐藏着设计哲学的根本差异。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值