泛型笔记问答

1. 什么是泛型?它解决了传统编程的哪些问题?

核心解答 :

泛型的核心价值是"类型安全+代码复用+高性能 "

泛型就是"把类型当成参数传"的编程方式,定义类、方法时不固定具体类型,使用时再指定。比如List<T>,T就是类型参数,用的时候可以传string、int,变成List<string>、List<int>。

传统编程的3个痛点,泛型全解决了:

  1. 代码复用差:比如要写"存int的容器""存string的容器",得写两个几乎一样的类;
  2. 类型不安全:用ArrayList存数据,本质是存object,int转object是装箱,取的时候还要拆箱,容易出现类型转换错误(比如存int取的时候转string);
  3. 性能差:装箱拆箱会在堆和栈之间拷贝数据,高频操作(比如工业设备数据采集)会明显拖慢速度。

工业场景示例:

csharp 复制代码
// 传统非泛型:存PLC设备数据,类型不安全+装箱拆箱
ArrayList plcDataList = new ArrayList();
plcDataList.Add(100); // int装箱成object
plcDataList.Add("运行中"); // string直接存
// 取数据时必须手动转换,容易错
int data1 = (int)plcDataList[0];
string data2 = (string)plcDataList[1];

// 泛型:类型安全+无装箱拆箱
List<int> plcIntData = new List<int>();
plcIntData.Add(100); // 直接存int,无装箱
// plcIntData.Add("运行中"); // 编译直接报错,类型安全

List 泛型 List

2. 泛型类、泛型接口、泛型方法的定义方式有什么区别?分别用在什么场景?

核心解答 :

区别主要在"类型参数的作用范围"和"定义位置",口语化记:

  • 泛型类:类型参数写在类名后面(class 类名<T>),作用范围是整个类,适合做"通用数据容器/工具类"(比如工业场景的设备数据缓存容器);
  • 泛型接口:类型参数写在接口名后面(interface 接口名<T>),作用范围是整个接口,适合做"通用契约"(比如不同设备的数据分析接口);
  • 泛型方法:类型参数写在方法名前面(返回值 方法名<T>(参数)),作用范围只限于当前方法,适合做"通用工具方法"(比如不同类型的PLC数据转换)。

场景总结:类/接口级泛型管"整个组件通用",方法级泛型管"单个功能通用"。

工业场景示例:

csharp 复制代码
// 1. 泛型类:PLC设备数据缓存容器(整个类都用T类型)
public class PlcDataCache<T>
{
    private List<T> _cache = new List<T>();
    public void AddData(T data) => _cache.Add(data);
    public T GetData(int index) => _cache[index];
}

// 2. 泛型接口:设备数据分析契约(所有实现类都要实现T类型的分析逻辑)
public interface IDataAnalyzer<T>
{
    T Analyze(T rawData);
}

// 3. 泛型方法:PLC数据转换(只此方法通用,不影响整个类)
public class PlcTool
{
    // 把任意类型的原始数据转换成目标类型T
    public T ConvertData<U, T>(U rawData)
    {
        return (T)Convert.ChangeType(rawData, typeof(T));
    }
}

// 使用示例
var intCache = new PlcDataCache<int>();
intCache.AddData(200); // 存int类型设备数据

var analyzer = new PlcIntDataAnalyzer(); // 实现IDataAnalyzer<int>
int result = analyzer.Analyze(150);

var tool = new PlcTool();
int data = tool.ConvertData<string, int>("100"); // 把string转int

二、进阶核心类(高频难点)

3. 泛型约束有哪些?为什么需要约束?举个工业场景的实际应用。

核心解答 :

泛型约束就是"给类型参数T加规则",限制T必须是某种类型/满足某种条件,否则编译器报错。没有约束的话,T可以是任意类型,没法在泛型里调用T的方法(比如想调用T的Run()方法,没约束就不知道T有没有这个方法)。

常见约束(口语化记):

  1. where T : class:T必须是引用类型(比如string、自定义类);
  2. where T : struct:T必须是值类型(比如int、double、DateTime);
  3. where T : 基类名:T必须继承这个基类;
  4. where T : 接口名:T必须实现这个接口;
  5. where T : new():T必须有无参构造函数(能new出来)。

工业场景示例:

csharp 复制代码
// 需求:写一个通用的PLC设备控制类,要求T必须是"工业设备类"(有Run()、Stop()方法)
// 1. 定义工业设备基类
public abstract class IndustrialDevice
{
    public abstract void Run();
    public abstract void Stop();
}

// 2. 泛型控制类,加约束:T必须继承IndustrialDevice
public class DeviceController<T> where T : IndustrialDevice, new()
{
    private T _device;
    public DeviceController()
    {
        _device = new T(); // 因为有new()约束,才能new T()
    }
    public void StartDevice() => _device.Run(); // 因为有基类约束,才能调用Run()
    public void StopDevice() => _device.Stop();
}

// 3. 具体设备类(继承IndustrialDevice)
public class PlcDevice : IndustrialDevice
{
    public override void Run() => Console.WriteLine("PLC设备启动");
    public override void Stop() => Console.WriteLine("PLC设备停止");
}

// 使用:只能传继承IndustrialDevice的类型
var plcCtrl = new DeviceController<PlcDevice>();
plcCtrl.StartDevice(); // 正常调用
// var errCtrl = new DeviceController<string>(); // 编译报错:string不继承IndustrialDevice

4. 什么是泛型的协变和逆变?in和out关键字分别起什么作用?工业场景怎么用?

核心解答 :

协变和逆变是"泛型类型参数的隐式转换规则",解决"泛型接口/委托的类型兼容"问题。口语化理解:

  • 协变(out关键字):子类类型能隐式转换成父类类型(比如Dog→Animal),泛型接口里T只能当返回值(不能当参数);
  • 逆变(in关键字):父类类型能隐式转换成子类类型(比如Animal→Dog),泛型接口里T只能当参数(不能当返回值);

记住核心:out=输出(返回值)=协变;in=输入(参数)=逆变。没有in/out的泛型类型,不能做这种隐式转换。

5. 泛型的协变和逆变,在工业场景怎么用?

工业场景示例:

csharp 复制代码
// ===================== 第一步:定义有业务语义的设备层级(父类+子类) =====================
// 父类:通用设备(有基础属性)
public class Device 
{
    public string DeviceId { get; set; } // 所有设备都有的唯一ID
}

// 子类1:PLC设备(继承通用设备,新增PLC专属属性)
public class PlcDevice : Device 
{
    public string PlcIp { get; set; } = "192.168.1.100"; // PLC专属IP
}

// 子类2:机器人设备(继承通用设备,新增机器人专属属性)
public class RobotDevice : Device 
{
    public string RobotModel { get; set; } = "ABB-123"; // 机器人专属型号
}

// ===================== 第二步:协变接口(out T,只生产/返回T) =====================
// 协变接口:数据提供器(只"生产"设备数据,T仅做返回值,所以加out)
// 核心:只要是"生产子类设备"的提供器,都能当成"生产父类设备"的提供器用
public interface IDataProvider<out T> 
{
    // T只做返回值(生产/输出),符合out的要求
    T GetDeviceData(); 
}

// 实现1:PLC数据提供器(专门生产PLC设备数据)
public class PlcDataProvider : IDataProvider<PlcDevice>
{
    // 生产PLC设备数据(返回子类PlcDevice)
    public PlcDevice GetDeviceData() 
    {
        Console.WriteLine("生产PLC设备数据");
        return new PlcDevice { DeviceId = "PLC001" };
    }
}

// 实现2:机器人数据提供器(专门生产机器人设备数据)
public class RobotDataProvider : IDataProvider<RobotDevice>
{
    // 生产机器人设备数据(返回子类RobotDevice)
    public RobotDevice GetDeviceData()
    {
        Console.WriteLine("生产机器人设备数据");
        return new RobotDevice { DeviceId = "ROBOT001" };
    }
}

// ===================== 第三步:逆变接口(in T,只消费/接收T) =====================
// 逆变接口:数据处理器(只"消费"设备数据,T仅做参数,所以加in)
// 核心:只要是"处理父类设备"的处理器,都能当成"处理子类设备"的处理器用
public interface IDataProcessor<in T> 
{
    // T只做参数(消费/输入),符合in的要求
    void ProcessDeviceData(T device); 
}

// 实现1:通用设备处理器(能处理所有Device类型的设备,父类处理器)
public class DeviceDataProcessor : IDataProcessor<Device>
{
    // 消费通用设备数据(接收父类Device)
    public void ProcessDeviceData(Device device) 
    {
        Console.WriteLine($"处理通用设备数据:设备ID={device.DeviceId}");
        // 工业场景:这里可以加通用逻辑(比如记录设备运行日志、上报MES系统)
    }
}

// 实现2:PLC专属处理器(仅处理PLC设备,子类处理器,对比用)
public class PlcDataProcessor : IDataProcessor<PlcDevice>
{
    public void ProcessDeviceData(PlcDevice device)
    {
        Console.WriteLine($"处理PLC专属数据:设备ID={device.DeviceId},PLC IP={device.PlcIp}");
    }
}

// ===================== 第四步:使用示例(协变+逆变的实际价值) =====================
public static void Main()
{
    // -------------------- 协变示例:子类提供器 → 父类提供器 --------------------
    // 需求:我需要一个"生产通用设备"的提供器(IDataProvider<Device>)
    // 实际只有"生产PLC设备"的提供器(PlcDataProvider),协变允许直接赋值
    IDataProvider<Device> plcDeviceProvider = new PlcDataProvider();
    // 调用GetDeviceData,返回的是PlcDevice,但能赋值给Device(子类→父类)
    Device plcData = plcDeviceProvider.GetDeviceData();
    Console.WriteLine($"协变获取的设备ID:{plcData.DeviceId}"); // 输出:PLC001

    // 同理:机器人提供器也能当成通用设备提供器用
    IDataProvider<Device> robotDeviceProvider = new RobotDataProvider();
    Device robotData = robotDeviceProvider.GetDeviceData();
    Console.WriteLine($"协变获取的设备ID:{robotData.DeviceId}"); // 输出:ROBOT001

    // -------------------- 逆变示例:父类处理器 → 子类处理器 --------------------
    // 需求:我需要一个"处理PLC设备"的处理器(IDataProcessor<PlcDevice>)
    // 实际只有"处理通用设备"的处理器(DeviceDataProcessor),逆变允许直接赋值
    IDataProcessor<PlcDevice> plcProcessor = new DeviceDataProcessor();
    // 调用ProcessDeviceData,传入PLC设备(父类处理器能处理子类设备)
    plcProcessor.ProcessDeviceData(new PlcDevice { DeviceId = "PLC002" }); 
    // 输出:处理通用设备数据:设备ID=PLC002

    // 对比:如果用PLC专属处理器,只能处理PLC,不能处理通用设备(无逆变)
    IDataProcessor<PlcDevice> plcSpecialProcessor = new PlcDataProcessor();
    plcSpecialProcessor.ProcessDeviceData(new PlcDevice { DeviceId = "PLC003" });
    // 输出:处理PLC专属数据:设备ID=PLC003
}

6. 泛型缓存是什么?原理是什么?工业场景怎么用(举个高频例子)?

核心解答 :

泛型缓存是"利用泛型类型的唯一性,实现不同类型的独立缓存"------不同的泛型类型(比如Cache<int>、Cache<string>)会被编译器当成两个完全不同的类,各自有独立的静态成员,静态成员的生命周期和程序一致,所以能当缓存用。

原理:泛型类的静态成员是"按泛型类型实例化的",不同T对应不同的静态成员副本,不会互相干扰。

工业场景示例(PLC设备配置缓存,不同设备类型缓存不同配置):

csharp 复制代码
// 泛型缓存类:不同设备类型(T)缓存不同的配置
public class DeviceConfigCache<T> where T : Device
{
    // 静态成员:每个T对应一个独立的配置缓存
    private static readonly Dictionary<string, string> _configCache = new Dictionary<string, string>();

    // 静态构造函数:每个T只执行一次,初始化缓存
    static DeviceConfigCache()
    {
        Console.WriteLine($"初始化{typeof(T).Name}的配置缓存");
        // 模拟从文件读取配置(实际工业场景:从MES系统/配置文件加载)
        if (typeof(T) == typeof(PlcDevice))
        {
            _configCache.Add("Ip", "192.168.1.100");
            _configCache.Add("Port", "8080");
        }
        else if (typeof(T) == typeof(RobotDevice))
        {
            _configCache.Add("Ip", "192.168.1.200");
            _configCache.Add("Port", "9090");
        }
    }

    // 获取缓存的配置
    public static string GetConfig(string key)
    {
        _configCache.TryGetValue(key, out var value);
        return value;
    }
}

// 使用示例
// 第一次获取PLC配置:触发静态构造函数,初始化缓存
string plcIp = DeviceConfigCache<PlcDevice>.GetConfig("Ip"); // 192.168.1.100
// 第二次获取:直接用缓存,不重新初始化
string plcPort = DeviceConfigCache<PlcDevice>.GetConfig("Port"); // 8080

// 获取机器人配置:触发RobotDevice对应的静态构造函数
string robotIp = DeviceConfigCache<RobotDevice>.GetConfig("Ip"); // 192.168.1.200

三、实战应用类(结合工业开发)

7. 泛型在工业上位机开发中的典型应用场景有哪些?举2个实际开发中常用的例子。

核心解答 :

泛型在工业开发中用得特别多,核心是"通用组件复用",典型场景:

  1. 通用数据容器:比如不同设备的采集数据缓存(List<T>、自定义泛型缓存类)、设备列表管理;
  2. 通用接口/抽象类:比如不同设备的通信接口(IComm<T>)、数据分析接口(IAnalyzer<T>),实现"一套接口,多设备适配";
  3. 通用工具方法:比如不同类型的PLC数据转换、配置文件序列化/反序列化。

工业场景示例(2个高频实战):

csharp 复制代码
// 场景1:通用PLC通信接口(泛型接口,适配不同设备的通信数据)
public interface IPlcComm<T>
{
    bool Connect(string ip, int port);
    T ReadData(string address); // 读取不同类型的数据(int、double、string)
    bool WriteData(string address, T data);
    void Disconnect();
}

// 实现PLC通信(T=int,读取整数型数据)
public class IntPlcComm : IPlcComm<int>
{
    public bool Connect(string ip, int port) => true; // 模拟连接
    public int ReadData(string address) => 100; // 读取整数数据
    public bool WriteData(string address, int data) => true;
    public void Disconnect() { }
}

// 场景2:通用配置序列化工具(泛型方法,适配不同设备的配置类)
public class ConfigTool
{
    // 泛型方法:把配置类T序列化成JSON字符串(存文件)
    public static string SerializeConfig<T>(T config) where T : class
    {
        return JsonSerializer.Serialize(config);
    }

    // 泛型方法:从JSON字符串反序列化成配置类T
    public static T DeserializeConfig<T>(string json) where T : class, new()
    {
        return JsonSerializer.Deserialize<T>(json) ?? new T();
    }
}

// 使用示例
// 1. 通信示例
var plcComm = new IntPlcComm();
plcComm.Connect("192.168.1.100", 8080);
int data = plcComm.ReadData("D0");

// 2. 配置序列化示例
var plcConfig = new PlcConfig { Ip = "192.168.1.100", Port = 8080 };
string json = ConfigTool.SerializeConfig(plcConfig); // 序列化
PlcConfig newConfig = ConfigTool.DeserializeConfig<PlcConfig>(json); // 反序列化

8. 使用泛型时需要注意哪些坑?工业开发中怎么规避?

核心解答 :

3个高频坑+规避方法:

  1. 无约束滥用泛型:比如写了个泛型方法,却在里面调用T的方法,没加约束,编译报错。规避:先想清楚T的用途,提前加对应的约束(比如要调用T的方法,就加接口/基类约束);
  2. 协变逆变混用:比如在泛型接口里既用out T当返回值,又用T当参数,编译报错。规避:记住out=只出不进,in=只进不出,严格区分;
  3. 泛型缓存内存泄漏:比如泛型缓存存了大量大对象,且T类型多,会占用大量内存。规避:工业场景用泛型缓存只存"小体积、高频访问"的配置数据(比如设备IP、端口),不存大文件/大量数据;
  4. 忽略值类型和引用类型的差异:比如泛型类里T是值类型,new T()是默认值(0、false);是引用类型,new T()是null。规避:如果需要初始化T,加new()约束,且在代码里处理null情况。

工业场景规避示例:

csharp 复制代码
// 坑1规避:加约束保证能调用T的方法
public class SafeDeviceTool<T> where T : IndustrialDevice
{
    public void Control(T device)
    {
        device.Run(); // 有约束,确定T有Run()方法,不会报错
    }
}

// 坑3规避:泛型缓存只存小体积配置,且提供清理方法
public class SafeConfigCache<T> where T : Device
{
    private static readonly Dictionary<string, string> _smallCache = new Dictionary<string, string>();

    // 清理缓存的方法,避免内存泄漏
    public static void ClearCache()
    {
        _smallCache.Clear();
    }
}

// 坑4规避:处理值类型/引用类型的初始化差异
public class DataInitializer<T> where T : new()
{
    public T InitData()
    {
        T data = new T();
        // 处理引用类型null的情况
        if (data is string str && string.IsNullOrEmpty(str))
        {
            return (T)(object)"默认值";
        }
        return data;
    }
}

9. 你说的 "逆变" 解决的问题,在java 或java 安卓中(如果有) 是怎么解决的

在 Java(包括 Android 开发)中,没有 C# 里的 in/out 关键字 ,但通过「泛型通配符 + PECS 原则」能实现和 C# 协变/逆变完全等价的效果------其中解决"逆变"问题的核心是 ? super 子类 通配符,协变则是 ? extends 父类 通配符。

一、Java 实现协变/逆变的核心:PECS 原则

这是 Java 泛型的"黄金法则",对应 C# 的 out/in,先记死:

概念 Java 写法 核心规则(人话) 对应 C# 关键字
协变(生产) ? extends 父类 只能"读/生产"数据(类似 C# out),子类对象能当父类用 out
逆变(消费) ? super 子类 只能"写/消费"数据(类似 C# in),父类对象能当子类用 in
  • PECS = P roducer(生产者)→ E xtends;C onsumer(消费者)→ Super
  • 核心:Java 不通过接口标记(in/out),而是通过使用泛型时的通配符来控制协变/逆变,这是和 C# 最大的区别。

二、用 Java/Android 复刻"设备处理器"逆变场景(对应 C# 例子)

我们用和之前 C# 一致的「PLC 设备处理」场景,看 Java 怎么实现"父类处理器当子类处理器用"的逆变效果:

步骤 1:定义设备层级(和 C# 一致,Java 写法)
java 复制代码
// 父类:通用设备
public class Device {
    private String deviceId;
    // 构造器、get/set 省略(Android 开发建议用 Lombok 简化)
    public Device(String deviceId) { this.deviceId = deviceId; }
    public String getDeviceId() { return deviceId; }
}

// 子类:PLC 设备(Android 开发中可能是实体类,用于 RecyclerView 展示)
public class PlcDevice extends Device {
    private String plcIp; // PLC 专属属性
    public PlcDevice(String deviceId, String plcIp) {
        super(deviceId);
        this.plcIp = plcIp;
    }
    public String getPlcIp() { return plcIp; }
}
步骤 2:定义数据处理器接口(对应 C# 的 IDataProcessor)
java 复制代码
// 数据处理器接口(消费者:只接收/消费 Device 数据,对应 C# in)
public interface IDataProcessor<T> {
    void processDeviceData(T device); // 只消费 T,对应 C# in 的场景
}
步骤 3:实现通用设备处理器(父类处理器)
java 复制代码
// 通用设备处理器:处理所有 Device 子类(对应 C# 的 DeviceDataProcessor)
public class DeviceDataProcessor implements IDataProcessor<Device> {
    @Override
    public void processDeviceData(Device device) {
        // Android 开发中可能是打印日志、更新 UI、上报服务器
        System.out.println("处理通用设备:" + device.getDeviceId());
    }
}
步骤 4:Java 逆变的核心用法(? super PlcDevice)

这是关键!我们复刻 C# 中"把通用处理器当成 PLC 专属处理器用"的场景:

java 复制代码
public class PlcBatchHandler {
    // Android 开发中,这可能是 RecyclerView 的数据处理逻辑、批量上报逻辑
    public void batchProcess(List<PlcDevice> plcList, IDataProcessor<? super PlcDevice> plcProcessor) {
        // 核心:参数用 IDataProcessor<? super PlcDevice>(逆变)
        // 表示"能消费 PlcDevice 及其父类的处理器"
        for (PlcDevice plc : plcList) {
            plcProcessor.processDeviceData(plc); // 传入 PLC 设备,正常消费
        }
    }

    // 测试方法(Android 中可写在 Activity/Fragment 的 onCreate 里)
    public static void main(String[] args) {
        PlcBatchHandler handler = new PlcBatchHandler();
        // 1. 创建通用处理器(处理 Device)
        IDataProcessor<Device> deviceProcessor = new DeviceDataProcessor();
        // 2. 把通用处理器传给"需要 PLC 专属处理器"的方法(逆变核心)
        // Java 中:IDataProcessor<Device> 可以赋值给 IDataProcessor<? super PlcDevice>
        handler.batchProcess(
            Arrays.asList(new PlcDevice("PLC001", "192.168.1.100")),
            deviceProcessor // 核心:父类处理器当子类处理器用,实现逆变
        );
    }
}
执行结果(和 C# 一致)
复制代码
处理通用设备:PLC001

三、Java 逆变的核心解释(对比 C#)

  1. 为什么能这么写?
    IDataProcessor<? super PlcDevice> 表示:这个处理器能"消费" PlcDevice 或它的父类(比如 Device)。而 DeviceDataProcessor 是处理 Device 的,自然能处理 PlcDevice,这和 C# 中 IDataProcessor<in T> 允许父类处理器赋值给子类处理器的逻辑完全一致。

  2. 如果不用逆变(? super)会怎样?

    若把方法参数写成 IDataProcessor<PlcDevice>,则无法传入 IDataProcessor<Device>(编译报错),就像 C# 中没有 in 关键字时无法赋值一样。

四、Android 开发中逆变的实际场景(更贴近实战)

在 Android 中,逆变最常用的场景是集合/适配器的数据处理,比如:

java 复制代码
// Android 场景:RecyclerView 的 PLC 列表适配器
public class PlcAdapter extends RecyclerView.Adapter<PlcViewHolder> {
    private List<PlcDevice> plcList;
    private IDataProcessor<? super PlcDevice> dataProcessor; // 逆变

    public PlcAdapter(List<PlcDevice> plcList, IDataProcessor<? super PlcDevice> dataProcessor) {
        this.plcList = plcList;
        this.dataProcessor = dataProcessor;
    }

    @Override
    public void onBindViewHolder(PlcViewHolder holder, int position) {
        PlcDevice plc = plcList.get(position);
        holder.tvDeviceId.setText(plc.getDeviceId());
        // 点击 item 时处理数据(用通用处理器处理 PLC 数据)
        holder.itemView.setOnClickListener(v -> dataProcessor.processDeviceData(plc));
    }

    // 其他方法省略...
}

// 使用时:传入通用处理器
List<PlcDevice> plcList = new ArrayList<>();
IDataProcessor<Device> deviceProcessor = new DeviceDataProcessor();
PlcAdapter adapter = new PlcAdapter(plcList, deviceProcessor); // 逆变生效
recyclerView.setAdapter(adapter);

核心价值 :Android 中一个通用的 DeviceDataProcessor 可以适配所有设备的列表适配器(PLC、机器人、传感器),不用为每个适配器写专属处理器,和 C# 逆变的"复用通用逻辑"完全一致。

五、C# 逆变 vs Java/Android 逆变的核心区别

维度 C# 逆变 Java/Android 逆变
实现方式 接口定义时加 in 关键字 使用泛型时加 ? super 子类 通配符
作用范围 接口级别(全局生效) 使用场景级别(局部生效)
核心原则 in = 只消费 T PECS 原则(Consumer Super)

六、总结

Java/Android 中没有 C# 那样的 in/out 关键字,但通过 ? super 子类 通配符(遵循 PECS 原则),完全能解决 C# 中"逆变"要解决的核心问题------让处理父类的逻辑,能安全地处理子类对象,实现通用逻辑的复用,同时保证类型安全。

核心记住:

  • Java 协变:? extends 父类(只能读,对应 C# out);
  • Java 逆变:? super 子类(只能写,对应 C# in)。
相关推荐
蒸蒸yyyyzwd6 小时前
AI应用相关可能的面试问题
笔记
叼烟扛炮7 小时前
[Git 实战] 学习笔记:从生成 SSH 密钥到解决 TortoiseGit 推送报错全指南
linux·运维·笔记·git·学习
蒸蒸yyyyzwd8 小时前
学习笔记 八股手撕
笔记·学习
揣一尾游鱼yu9 小时前
Spring上(IOC)笔记
java·笔记·spring·ioc
小李不想当小白11 小时前
BKP备份寄存器与RTC实时时钟(STM32标准库学习笔记)
笔记·stm32·单片机·嵌入式硬件·学习·嵌入式
瑞码空间13 小时前
JSP与Servlet核心开发与CRUD实战笔记
java·笔记·servlet·jsp
La Pulga13 小时前
【FreeRTOS源码学习笔记】链表的实现
数据结构·笔记·stm32·mcu·链表·嵌入式·freertos
wuyoutrans16 小时前
以色列专利OA答复翻译,重点要避开哪些坑?
笔记
宝绿特塑业再生科技16 小时前
HDPE再生颗粒户外家具表面粉化机理与改性技术全解析
笔记·户外家具·再生塑料·塑木桌椅
凉、介17 小时前
深入理解 ARMv7-A | Generic Timer
笔记·学习·嵌入式·arm