第四十二条 慎用可变参数

java 1.5 版本中,新增了可变参数方法,可变参数的个数是从0开始,理论上可以无限制大的个数,可变参数机制通过创建数组,把可变参数装到数组里,数组的大小为可变参数的个数,然后再将数组传递给方法,可变参数就是这么工作的。看个例子,求可变参数中的综合,

    static int sum(int... args) {
        int sum=0;
        for(int arg : args)
            sum += arg;
        return sum;
    }

sum(1,2,3)的值为6;sum()的值为0。这样用起来很方便,但如果有时候,需要编写1个或多个参数的方法,不需要0个,怎么办?我们在方法中对参数进行校验?

    static int min(int... args) {
        if (args.length == 0)
            throw new IllegalArgumentException("Too few arguments");
        int min = args[0];
        for (int i = 1; i < args.length; i++)
            if (args[i] < min)
                min = args[i];
        return min;
    }

这个方法中,我们对参数做了校验,如果可变参数长度为0,则提示异常,只有大于0时,才能执行逻辑操作。这么做有个小问题,如果用户传递的可变参数长度为0,则会运行失败,并且代码不美观,我们可以提出相对应的方案,一种是修改 throw new IllegalArgumentException("Too few arguments"); 方法,改为显示校验,例如返回 0,这种方法不太好;另外一种是修改方法,例如

    static int min(int firstArg, int... remainingArgs) {
        int min = firstArg;
        for (int arg : remainingArgs)
            if (arg < min)
                min = arg;
        return min;
    }

改成这样,就能保证至少有一个参数,并且比较简明清晰。可变参数这样用起来比较好,但不意味着可变参数可以随意用,例如 Arrays.asList() 的代码,意思是把多个参数转换为一个list类型的数据,

      List<String> homophones = Arrays.asList("to", "too", "two");
      System.out.println(homophones);
      List<Integer> asList = Arrays.asList(1,2,3);
      System.out.println(asList);
      int[] intValus = { 1, 2, 3 };
      System.out.println(Arrays.asList(intValus));
      

执行代码,打印的结果是 [to, too, two]  [1, 2, 3]  [[I@6d06d69c] ,这是为什么呢?看 Arrays 的源码,

    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

    private static class ArrayList<E> extends AbstractList<E>
            implements RandomAccess, java.io.Serializable {
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }

        @Override
        public int size() {
            return a.length;
        }
    ...
    }

我们知道,调用asList(T... a)方法后,返回的结果是一个 Arrays 内部的 ArrayList 集合,这个集合里面没有重写toString()方法,那么调用的是基类AbstractCollection里的toString(),

    public String toString() {
        Iterator<E> it = iterator();
        if (! it.hasNext())
            return "[]";

        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (;;) {
            E e = it.next();
            sb.append(e == this ? "(this Collection)" : e);
            if (! it.hasNext())
                return sb.append(']').toString();
            sb.append(',').append(' ');
        }
    }

由此可见,打印的还是List里面对象的toString(),并且把它们装到了中括号里。再返回上面的代码,List<String> homophones = Arrays.asList("to", "too", "two");所以打印时是字符串的toString,即 [to, too, two]。List<Integer> asList = Arrays.asList(1,2,3); 这个List里的对象是Integer,所以打印的是int的值,即 [1, 2, 3]。而 int[] intValus = { 1, 2, 3 };List<int[]> valus = Arrays.asList(intValus); 这个返回的List里面的对象是 int[],并且List集合长度为1,至于一个数组,因此打印的是数组toString,即数组的地址值,[[I@6d06d69c]。看到这里大家就明白了,如果传入的是对象,那么打印的就是对象的toString,我们如果重写了对象的toString()方法,就能看到了对应的值,否则,如果没有重写自己的toString()方法,那么打印的就是父类的toString()方法,顶层基类就是Object了。同样道理,如果我们把字符串数组传递进去,会是什么呢

      String[] strs = {"to", "too", "two"};
      List<String> asList3 = Arrays.asList(strs);
      System.out.println(asList3);

我们发现,Arrays.asList(strs) 的类型是 List<String>而不是 List<String[]>,所以打印的结果是 [to, too, two]。 为什么会是这样呢,记得前面泛型提过,泛型不要和数组混着用,这里大概是泛型推导,String 比较特殊,具体的原因我也没闹明白,暂且算是特殊情况吧。如果高手看到了,希望留言,指导一下。

对于上面的int[] intValus = { 1, 2, 3 }; System.out.println(Arrays.asList(intValus)); 打印的是list里对象数组的地址值 [[I@6d06d69c],怎么打印数组里的每一个值呢,我们可以用另外一个方法,Arrays.toString(a)方法,打印的是 [1, 2, 3] ,看一下源码

    public static String toString(int[] a) {
        if (a == null)
            return "null";
        int iMax = a.length - 1;
        if (iMax == -1)
            return "[]";

        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0; ; i++) {
            b.append(a[i]);
            if (i == iMax)
                return b.append(']').toString();
            b.append(", ");
        }
    }
原来,这个方法就是遍历数组的,通过 StringBuilder 把数组的值遍历,并加上了中括号及逗号分隔符,然后以 String 字符串的形式返回,所以才有了 [1, 2, 3] 。

在重视性能的情况下,使用可变参数要小心,可变参数方法每次调用都会导致进行一次数组分配和初始化,所以可以写几个用到概率比较高的方法,多余的参数再用可变参数来表示

    public void foo(){}
    public void foo(int a1){}
    public void foo(int a1, int a2){}
    public void foo(int a1, int a2, int a3){}
    public void foo(int a1, int a2, int a3, int... rest){}

就这样,除非必要,尽量不要使用可变参数方法。

<think>好的,用户的问题是关于STM32中如何处理可变长度参数的实现方法和用法。首先,我需要明确用户的需求。他们可能在使用STM32进行串口通信或其他需要接收不定长数据的场景,比如与ESP-01S模块通信,处理AT指令的回复。用户可能已经参考了站内的引用资料,其中提到了使用HAL库轮询方式接收变长数据,以及生成PWM波的方法,但具体到可变长度参数的处理,可能需要更详细的解释。 接下来,我需要回忆STM32中处理可变长度参数的常见方法。通常,可变长度参数在C语言中是通过va_list、va_start、va_arg、va_end这些宏来实现的,比如在printf函数中。但在嵌入式系统中,尤其是STM32这样的微控制器上,处理可变参数函数需要注意栈空间、参数传递方式等问题。 不过,用户的问题可能更偏向于如何接收和处理可变长度的数据,比如串口接收不定长的数据帧,而不是函数中的可变参数。结合用户提供的参考引用[1],里面提到使用HAL库轮询方式接收变长数据,这可能涉及到串口空闲中断或者超时机制来判断数据接收完成。因此,我需要确认用户的问题到底是关于C语言的可变参数函数,还是串口通信中的可变长度数据接收。 从用户的提问“可变长度参数实现方法及用法”来看,可能两者都有可能。但结合参考引用中的内容,更可能是指串口接收可变长度数据。因此,需要分情况讨论: 1. C语言可变参数函数在STM32中的使用:这里需要解释va_list的用法,并给出示例,比如实现一个可变参数的日志函数。 2. 串口接收可变长度数据的方法:如使用空闲中断、DMA+空闲中断、超时检测等方法,并结合HAL库的API进行实现,如引用[1]中的方法。 同时,用户可能对如何具体实现这些方法,以及相关的代码示例感兴趣。需要确保示例代码正确,并符合HAL库的使用规范。此外,还需要提到注意事项,比如资源限制、中断优先级、错误处理等。 另外,考虑到用户提供的参考引用[2]中提到了PWM的配置,可能用户对HAL库的外设配置有一定了解,因此可以假设他们具备一定的HAL库使用经验,但需要详细步骤说明。 最后,生成相关问题时要围绕实现方法、优化、应用场景等方面,帮助用户深入理解。</think>在STM32中处理可变长度参数可分为两种场景:**函数参数的可变长度实现**和**通信协议中的变长数据处理**。以下是具体实现方法及用法说明: --- ### 一、C语言可变参数函数的实现 在STM32中可通过`stdarg.h`头文件实现类似`printf`的可变参数函数,步骤如下: ```c #include <stdarg.h> void Debug_Log(const char* format, ...) { va_list args; va_start(args, format); char buffer[128]; vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); HAL_UART_Transmit(&huart1, (uint8_t*)buffer, strlen(buffer), 100); } // 调用示例 Debug_Log("传感器读数: %d, 温度: %.1f℃", sensor_value, temp); ``` **注意事项**: 1. 栈空间需充足,避免缓冲区溢出 2. 参数类型必须严格匹配 3. 在中断上下文中慎用可变参数函数 --- ### 二、串口接收变长数据的方法 参考引用[1]的轮询方式,推荐以下三种常用方法: #### 1. 空闲中断检测法(推荐) ```c // 初始化时使能空闲中断 __HAL_UART_ENABLE_IT(&huart1, UART_IT_IDLE); // 中断回调函数 void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if(__HAL_UART_GET_FLAG(huart, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(huart); // 处理接收缓冲区的数据 Process_ReceivedData(rx_buffer, data_length); // 重启接收 HAL_UART_Receive_DMA(huart, rx_buffer, MAX_LEN); } } ``` #### 2. DMA+超时检测 ```c // 启动DMA接收 HAL_UART_Receive_DMA(&huart1, rx_buf, MAX_LEN); // 定时器超时回调 void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim == &htim6) { uint16_t remain = __HAL_DMA_GET_COUNTER(huart1.hdmarx); uint16_t received = MAX_LEN - remain; if(received > 0) { Process_ReceivedData(rx_buf, received); HAL_UART_Receive_DMA(&huart1, rx_buf, MAX_LEN); } } } ``` #### 3. 轮询方式(参考引用[1]) ```c uint8_t rx_char; uint8_t buffer[256]; uint16_t index = 0; while(1) { if(HAL_UART_Receive(&huart1, &rx_char, 1, 10) == HAL_OK) { buffer[index++] = rx_char; if(rx_char == '\n' || index >= sizeof(buffer)) { // 结束符检测 Process_ReceivedData(buffer, index); index = 0; } } } ``` --- ### 三、应用场景对比 | 方法 | 适用场景 | 优点 | 缺点 | |--------------|-----------------------------|-----------------------|----------------------| | 空闲中断 | 高速不定长数据(如ESP-01S通信) | 实时性强,CPU占用低 | 需要中断配置 | | DMA+超时 | 大数据量传输 | 高效利用DMA | 需要定时器配合 | | 轮询方式 | 简单低速率场景 | 实现简单 | CPU占用率高 | ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值