星標公眾號,讓嵌入式知識 “投喂” 不停歇!
大家好,我是雜燴君。
本篇我們來分享函數式編程與非函數式編程在嵌入式應用中的對比。
函數式編程(或稱函數程序設計、泛函編程)是一種編程范式,它將計算視為函數的求值,避免使用共享狀態和可變數據,強調函數的純粹性和不可變性。
在嵌入式應用領域,函數式編程與非函數式編程(如常見的指令式編程)在多個方面存在顯著差異,下面將從多個方面對它們進行對比。

函數式編程:
純函數的特性使得函數式編程的代碼具有很高的可測試性。
由于函數的輸出只依賴于輸入,測試時只需要提供不同的輸入參數并驗證輸出結果是否符合預期即可,無需考慮復雜的外部環境和狀態。
我們在嵌入式軟件,有必要進行自測嗎?這篇文章中就是使用了函數式編程可測試性高的特點:


非函數式編程:
非函數式編程中存在大量的共享狀態和副作用,測試時需要模擬復雜的外部環境和狀態,增加了測試的難度和復雜度。
函數式編程:
代碼結構通常圍繞函數的組合和復用構建,函數之間的依賴關系清晰,每個函數只負責單一的任務。
這使得代碼具有較高的模塊化程度,易于理解和維護。
例如,在處理傳感器數據時,可以將數據讀取、處理等操作分別封裝成獨立的純函數,然后通過函數組合完成整個處理流程。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 模擬讀取傳感器數據
float read_sensor(void)
{
srand(time(NULL));
return (float)rand() / RAND_MAX * 100;
}
// 對傳感器數據進行平方處理
float square(float value)
{
return value * value;
}
void print_sensor_data(float value)
{
printf("Processed sensor data: %f\n", value);
}
int main(void)
{
float sensor_value = read_sensor();
float processed_value = square(sensor_value);
print_sensor_data(processed_value);
return0;
}
square 函數。非函數式編程:
非函數式編程(如指令式編程)通常使用變量、循環和條件語句來控制程序的執行流程。
代碼結構更側重于描述如何一步步完成任務,可能會涉及到較多的狀態變化和副作用。
在處理復雜邏輯時,代碼可能會變得冗長和復雜,可讀性和可維護性相對較低。
例如,在一個嵌入式控制系統中,使用命令式編程可能會有大量的循環和條件判斷來實現不同的控制邏輯,代碼的整體結構不夠清晰。
上面傳感器的例子中,使用非函數式編程的實現方式如:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
// 模擬讀取傳感器數據
float sensor_value;
void read_sensor(void)
{
srand(time(NULL));
sensor_value = (float)rand() / RAND_MAX * 100;
}
// 對傳感器數據進行平方處理
void square_sensor_data(void)
{
sensor_value = sensor_value * sensor_value;
}
void print_sensor_data(void)
{
printf("Processed sensor data: %f\n", sensor_value);
}
int main(void)
{
read_sensor();
square_sensor_data();
print_sensor_data();
return0;
}
函數式編程:
函數式編程中頻繁創建不可變數據的副本和函數調用會增加內存開銷和執行時間。
在嵌入式系統中,由于資源有限,這種開銷可能會對系統性能產生較大影響。
例如,在一個資源受限的單片機系統中,過多地使用函數式編程可能會導致內存不足或處理速度變慢。此外,一些函數式編程的特性(如遞歸調用)可能會導致棧溢出等問題。
#include <stdio.h>
#define ARRAY_SIZE 1000
// 函數式遞歸累加數組元素
int sum_array_recursive(int arr[], int index)
{
if (index == 0)
{
return arr[0];
}
return arr[index] + sum_array_recursive(arr, index - 1);
}
int main(void)
{
int arr[ARRAY_SIZE];
for (int i = 0; i < ARRAY_SIZE; i++)
{
arr[i] = i;
}
int sum = sum_array_recursive(arr, ARRAY_SIZE - 1);
printf("Sum: %d\n", sum);
return0;
}
使用遞歸的方式累加數組元素,每次遞歸調用都會在棧上分配新的棧幀。當數組規模較大時,遞歸調用會導致??臻g的大量使用,可能會引發棧溢出問題。
非函數式編程:
非函數式編程可以直接操作內存和硬件資源,通過合理的優化可以實現較高的性能和資源利用率。
在嵌入式系統中,命令式編程通??梢愿玫乜刂苾却娣峙浜歪尫牛瑴p少不必要的開銷。
上面累加數組元素的例子中,使用非函數式編程方式的實現:
#include <stdio.h>
#define ARRAY_SIZE 1000
// 非函數式累加數組元素
int sum_array(int arr[], int size)
{
int result = 0;
for (int i = 0; i < size; i++)
{
result += arr[i];
}
return result;
}
int main(void)
{
int arr[ARRAY_SIZE];
for (int i = 0; i < ARRAY_SIZE; i++)
{
arr[i] = i;
}
int sum = sum_array(arr, ARRAY_SIZE);
printf("Sum: %d\n", sum);
return0;
}
該代碼使用簡單的循環結構,直接對數組元素進行累加操作。內存使用方面,僅使用了固定大小的數組和一個整型變量來存儲結果,沒有額外的內存開銷。
函數式編程和非函數式編程在嵌入式應用中各有優缺點。
在選擇編程范式時,需要根據具體的應用場景、系統需求和資源限制來綜合考慮。
對于一些對可維護性和可測試性要求較高、對性能要求相對較低的嵌入式應用,可以考慮使用函數式編程;
而對于對性能、資源利用要求較高的嵌入式應用,非函數式編程可能是更好的選擇。
在實際開發中,也可以將兩種編程范式結合使用,充分發揮它們的優勢。