一、stdint.h簡介
- 配置MDK支持C99
二、位操作
- 如何給寄存器某個位賦值(清零置一)
三、宏定義
- 帶參數(shù)的宏定義
四、條件編譯
- 頭文件的條件編譯和代碼條件編譯
五、extern聲明
六、類型別名(typedef)
- 類型別名應(yīng)用
七、結(jié)構(gòu)體
- 應(yīng)用舉例(定義&使用)
- 應(yīng)用舉例(ST源碼,使用類型別名)
八、指針
- 指針使用的兩大最常見問題
九、代碼規(guī)范
- 其他規(guī)范
十、總結(jié)
【C語言】關(guān)鍵字
一、stdint.h簡介
stdint.h
是 C 語言標(biāo)準(zhǔn)庫中的一個頭文件,它在 C99 標(biāo)準(zhǔn)中被引入,目的是提供更為可移植性的整數(shù)類型定義。這個頭文件定義了一組與位數(shù)相關(guān)的整數(shù)類型,例如有符號整數(shù)(integers)和無符號整數(shù)(unsigned integers),以確保在不同平臺上的整數(shù)類型的大小和范圍是一致的。
以下是 stdint.h
中定義的一些常見的整數(shù)類型:
-
int8_t
:8 位有符號整數(shù) -
int16_t
:16 位有符號整數(shù) -
int32_t
:32 位有符號整數(shù) -
int64_t
:64 位有符號整數(shù) -
uint8_t
:8 位無符號整數(shù) -
uint16_t
:16 位無符號整數(shù) -
uint32_t
:32 位無符號整數(shù) -
uint64_t
:64 位無符號整數(shù)
此外,還有一些標(biāo)準(zhǔn)宏定義用于表示最小和最大的整數(shù)值,例如:
-
INT8_MIN
,INT16_MIN
,INT32_MIN
,INT64_MIN
:有符號整數(shù)的最小值 -
INT8_MAX
,INT16_MAX
,INT32_MAX
,INT64_MAX
:有符號整數(shù)的最大值 -
UINT8_MAX
,UINT16_MAX
,UINT32_MAX
,UINT64_MAX
:無符號整數(shù)的最大值
這些類型和宏的定義旨在提供可靠的整數(shù)大小和范圍,以便程序員可以編寫更加可移植的代碼,而不用擔(dān)心不同平臺上整數(shù)類型的差異。
在 MDK5.34 路徑下的 include
文件夾中,stdint.h
可能包含有適用于 ARM Cortex-M 微控制器的特定定義。這使得在嵌入式系統(tǒng)中使用 stdint.h
更加方便,因為可以根據(jù)具體的微控制器架構(gòu)來定義整數(shù)類型。
配置MDK支持C99
Options for Target 目標(biāo)選項 > C/C++ > C99 Mode
二、位操作
按位運算符、邏輯運算符
位操作是一種對二進(jìn)制位進(jìn)行操作的方法,常用于處理底層硬件控制、優(yōu)化算法和處理位圖等場景。以下是常見的位操作運算符及其含義:
-
&
(按位與):- 對兩個二進(jìn)制數(shù)的每一位執(zhí)行邏輯與操作。結(jié)果中的每一位都是兩個數(shù)對應(yīng)位上的最小值。
- 例如:
1010 & 1100 = 1000
-
|
(按位或):- 對兩個二進(jìn)制數(shù)的每一位執(zhí)行邏輯或操作。結(jié)果中的每一位都是兩個數(shù)對應(yīng)位上的最大值。
- 例如:
1010 | 1100 = 1110
-
^
(按位異或):- 對兩個二進(jìn)制數(shù)的每一位執(zhí)行邏輯異或操作。結(jié)果中的每一位都是兩個數(shù)對應(yīng)位上的不同值。
- 例如:
1010 ^ 1100 = 0110
-
~
(按位取反):- 對一個二進(jìn)制數(shù)的每一位執(zhí)行邏輯取反操作,即將 0 變?yōu)?1,將 1 變?yōu)?0。
- 例如:
~1010 = 0101
-
<<
(左移):- 將一個二進(jìn)制數(shù)的所有位向左移動指定的位數(shù),右側(cè)補零。
- 例如:
1010 << 2 = 101000
-
>>
(右移):- 將一個二進(jìn)制數(shù)的所有位向右移動指定的位數(shù),左側(cè)根據(jù)符號位補零或補一(對于有符號整數(shù))。
- 例如:
1010 >> 2 = 10
這些位操作運算符在低級別的編程和嵌入式系統(tǒng)開發(fā)中經(jīng)常被使用,因為它們可以高效地操作二進(jìn)制數(shù)據(jù)。在處理寄存器、位掩碼和優(yōu)化算法時,位操作非常有用。
如何給寄存器某個位賦值(清零置一)
在 C 語言中,對寄存器的某個位進(jìn)行賦值通常使用位操作來實現(xiàn)。
常見的三種方式:
方法一:
temp &= 0xFFFFFFBF; // 清除位6,將其置為0
temp |= 0x00000040; // 將位6設(shè)置為1
這里使用了按位與運算 &
和按位或運算 |
。首先,temp &= 0xFFFFFFBF;
將位6清零,然后 temp |= 0x00000040;
將位6設(shè)置為1。
方法二:
temp &= ~(1 << 6); // 清除位6,將其置為0
temp |= 1 << 6; // 將位6設(shè)置為1
這里使用了位掩碼和按位異或運算。temp &= ~(1 << 6);
使用位掩碼 ~(1 << 6)
清零位6,然后 temp |= 1 << 6;
將位6設(shè)置為1。
方法三:
temp ^= 1 << 6; // 切換(翻轉(zhuǎn))位6的值
這里使用了按位異或運算 ^
。temp ^= 1 << 6;
會將位6的值進(jìn)行翻轉(zhuǎn),即如果位6原來是0,則變成1;如果原來是1,則變成0。
這些方法的選擇通常取決于編碼的風(fēng)格和習(xí)慣,方法二是比較常見和推薦的一種方式,因為它直接使用位操作符,更加清晰和易讀。
三、宏定義
宏定義是 C 語言中的一種預(yù)處理指令,用于創(chuàng)建代碼中的簡單文本替換。
通過宏定義,你可以為一個標(biāo)識符(宏名)指定一個特定的文本,在程序編譯前會進(jìn)行文本替換,提高了代碼的可讀性、易改性,同時也可以用于一些常量或者簡單表達(dá)式的定義。
你提到的宏定義的基本語法為:
#define 標(biāo)識符 字符串
其中,標(biāo)識符是宏定義的名字,字符串是與該標(biāo)識符相關(guān)聯(lián)的文本。這個文本可以是常數(shù)、表達(dá)式、格式串等。
下面是兩個宏定義的例子:
#define PI 3.14159
這個宏定義將標(biāo)識符 PI
關(guān)聯(lián)到常數(shù) 3.14159
,在代碼中使用 PI
將被替換為 3.14159
。
#define HSE_VALUE 8000000U
這個宏定義將標(biāo)識符 HSE_VALUE
關(guān)聯(lián)到常數(shù) 8000000U
,在代碼中使用 HSE_VALUE
將被替換為 8000000U
。
在實際的程序開發(fā)中,宏定義經(jīng)常用于定義常數(shù)、配置參數(shù)、簡化代碼等方面。需要注意的是,在使用宏定義時,要確保宏名和關(guān)聯(lián)的文本不會與其他部分產(chǎn)生沖突,以免造成意外的替換。
帶參數(shù)的宏定義
宏定義為什么要使用do{……}while(0)形式
帶參數(shù)的宏定義是 C 語言中一種強大的工具,允許在代碼中實現(xiàn)簡單的代碼生成。帶參數(shù)的宏定義的語法如下:
#define 宏名(參數(shù)列表) 代碼塊
其中,宏名是宏定義的名字,參數(shù)列表是傳遞給宏的參數(shù),代碼塊是宏的具體實現(xiàn)。
例子中定義了一個帶參數(shù)的宏 LED1(x)
,它接受一個參數(shù) x
。這個宏用于控制 LED1 的狀態(tài),根據(jù)傳入的參數(shù) x
的值來設(shè)置 GPIO 的輸出狀態(tài)。
在宏定義中,使用了 do { ... } while(0)
結(jié)構(gòu)。這種結(jié)構(gòu)的目的是創(chuàng)建一個語句塊,盡管在 C 語言中并不需要這樣的結(jié)構(gòu),但在宏定義中使用它的好處在于可以確保宏在被調(diào)用時總是作為一個語句塊執(zhí)行。
這樣設(shè)計的原因是避免在宏的使用過程中受到語法的限制,例如:
if (condition)
LED1(1);
else
do_something_else();
如果沒有 do { ... } while(0)
結(jié)構(gòu),上面的代碼可能會因為缺少大括號而導(dǎo)致語法錯誤。使用這種結(jié)構(gòu),即使在 if
語句中使用宏,也能確保宏始終作為一個整體執(zhí)行,避免了潛在的錯誤。
總的來說,帶參數(shù)的宏定義是一種強大的代碼生成工具,但在使用時需要小心,確保宏定義的展開不會引發(fā)意外的行為。
四、條件編譯
【預(yù)處理命令】
條件編譯是一種在編譯階段根據(jù)條件選擇性地包含或排除代碼的技術(shù)。在 C 語言中,條件編譯主要通過預(yù)處理指令來實現(xiàn)。以下是常用的條件編譯指令:
-
#if
:編譯預(yù)處理條件指令,類似于if
語句,根據(jù)條件判斷是否編譯一段代碼。#if condition // code to be compiled if condition is true #endif
-
#ifdef
:判斷某個宏是否已被定義,如果宏已經(jīng)定義,則編譯相應(yīng)的代碼。#ifdef MACRO_NAME // code to be compiled if MACRO_NAME is defined #endif
-
#ifndef
:判斷某個宏是否未被定義,如果宏未定義,則編譯相應(yīng)的代碼。#ifndef MACRO_NAME // code to be compiled if MACRO_NAME is not defined #endif
-
#elif
:若前面的條件不滿足,則判定新的條件,類似于else if
。#if condition1 // code to be compiled if condition1 is true #elif condition2 // code to be compiled if condition2 is true #endif
-
#else
:若前面的條件不滿足,則執(zhí)行后面的語句,類似于else
。#if condition // code to be compiled if condition is true #else // code to be compiled if condition is false #endif
-
#endif
:#if
,#ifdef
,#ifndef
的結(jié)束標(biāo)志,標(biāo)識條件編譯塊的結(jié)束。#if condition // code to be compiled if condition is true #endif
這些條件編譯指令允許根據(jù)不同的條件選擇性地包含或排除代碼,從而實現(xiàn)在不同情況下編譯不同的代碼。這在處理不同平臺、不同配置或不同功能需求時非常有用。
頭文件的條件編譯和代碼條件編譯
頭文件的條件編譯和代碼條件編譯都是 C 語言中常用的技術(shù),用于在編譯時根據(jù)條件選擇性地包含或排除代碼。
頭文件的條件編譯:
#ifndef _LED_H
#define _LED_H
#include "./SYSTEM/sys/sys.h"
// 此處是頭文件的內(nèi)容
#endif
-
#ifndef _LED_H
:如果宏_LED_H
未被定義,則執(zhí)行以下代碼。 -
#define _LED_H
:定義宏_LED_H
,表示頭文件已被包含,防止重復(fù)包含。 -
#include "./SYSTEM/sys/sys.h"
:包含其他頭文件或聲明。
這樣的結(jié)構(gòu)確保頭文件內(nèi)容只會在第一次被包含時有效,避免了重復(fù)包含的問題。
代碼條件編譯:
#if SYS_SUPPORT_OS
// code
#endif
-
#if SYS_SUPPORT_OS
:如果宏SYS_SUPPORT_OS
的值為真(非零),則編譯以下代碼塊;否則,忽略該代碼塊。 -
// code
:在條件滿足時編譯的代碼塊。
這樣的結(jié)構(gòu)可以根據(jù) SYS_SUPPORT_OS
宏的值來選擇性地編譯一段代碼。如果 SYS_SUPPORT_OS
宏為真,那么 // code
部分將被編譯;否則,該部分將被忽略。
總的來說,這兩種條件編譯技術(shù)在軟件開發(fā)中常用于處理不同的編譯環(huán)境、配置選項,以及實現(xiàn)代碼的可移植性和靈活性。
五、extern聲明
extern
關(guān)鍵字在 C 語言中用于聲明一個變量或函數(shù),表示該變量或函數(shù)是在其他文件中定義的,以便在當(dāng)前文件中引用。
示例中使用了 extern
來聲明變量和函數(shù):
-
extern uint16_t g_usart_rx_sta;
:這行代碼聲明了一個uint16_t
類型的全局變量g_usart_rx_sta
,表示該變量在其他文件中定義,當(dāng)前文件中只是引用該變量。 -
extern void delay_us(uint32_t nus);
:這行代碼聲明了一個返回類型為void
、帶有一個uint32_t
類型參數(shù)的函數(shù)delay_us
。同樣,它表示該函數(shù)在其他文件中定義,當(dāng)前文件中只是引用該函數(shù)。
通過這樣的聲明,編譯器知道這些變量和函數(shù)在其他文件中定義,而在當(dāng)前文件中只是引用。在鏈接階段,鏈接器會負(fù)責(zé)將這些引用與實際的定義關(guān)聯(lián)起來。
這種機制在多個文件構(gòu)成的大型項目中非常有用,允許不同的源文件之間共享變量和函數(shù),從而實現(xiàn)模塊化開發(fā)。
六、類型別名(typedef)
typedef
是 C 語言中用于為現(xiàn)有數(shù)據(jù)類型創(chuàng)建新的名字(類型別名)的關(guān)鍵字。這可以用來簡化復(fù)雜的類型聲明,提高代碼的可讀性。
你的示例中使用了 typedef
來創(chuàng)建三個新的數(shù)據(jù)類型別名:
typedef unsigned char uint8_t;
typedef unsigned short int uint16_t;
typedef unsigned int uint32_t;
這里分別為 unsigned char
、unsigned short int
和 unsigned int
這三種數(shù)據(jù)類型創(chuàng)建了新的名字 uint8_t
、uint16_t
和 uint32_t
。這些新的名字可以在代碼中像普通的數(shù)據(jù)類型一樣使用。
例如,你可以使用這些類型別名來聲明變量:
uint8_t myByte; // 使用 uint8_t 聲明變量
uint16_t myInt; // 使用 uint16_t 聲明變量
uint32_t myLong; // 使用 uint32_t 聲明變量
這樣做的好處是,使得代碼更具可讀性,并且如果以后需要更改底層的數(shù)據(jù)類型,只需要修改 typedef
部分而不需要修改整個代碼。這也有助于提高代碼的可維護性。
類型別名應(yīng)用
在示例中,使用了 typedef
來創(chuàng)建了一個名為 GPIO_TypeDef
的結(jié)構(gòu)體類型別名,使得在定義變量時更加簡潔、易讀。
首先,未使用 typedef
的結(jié)構(gòu)體定義:
struct GPIO_TypeDef
{
__IO uint32_t CRL;
__IO uint32_t CRH;
// 其他成員...
};
struct GPIO_TypeDef gpiox;
在這個定義中,struct GPIO_TypeDef
是結(jié)構(gòu)體類型的標(biāo)簽,需要在每次聲明變量時都加上 struct
關(guān)鍵字。
為了避免每次都寫完整的類型,可以使用 typedef
來創(chuàng)建結(jié)構(gòu)體類型別名:
typedef struct
{
__IO uint32_t CRL;
__IO uint32_t CRH;
// 其他成員...
} GPIO_TypeDef;
GPIO_TypeDef gpiox;
這樣,typedef
關(guān)鍵字允許你在定義結(jié)構(gòu)體的同時為其創(chuàng)建了一個類型別名 GPIO_TypeDef
,在后續(xù)的代碼中可以直接使用 GPIO_TypeDef
來聲明變量,而不再需要寫 struct
。
這種方式提高了代碼的可讀性,使得結(jié)構(gòu)體類型的使用更加簡潔,并且如果以后需要修改結(jié)構(gòu)體的定義,只需修改一處 typedef
部分而不會涉及到變量聲明的地方。
七、結(jié)構(gòu)體
結(jié)構(gòu)體(struct),它是由若干基本數(shù)據(jù)類型集合組成的一種自定義數(shù)據(jù)類型,也被稱為聚合類型。結(jié)構(gòu)體允許你將不同類型的數(shù)據(jù)組織在一起,形成一個新的數(shù)據(jù)類型。
以下是結(jié)構(gòu)體的基本定義語法:
struct 結(jié)構(gòu)體名
{
成員列表;
} 變量名列表(可選);
你的例子中定義了一個名為 student
的結(jié)構(gòu)體,并聲明了兩個結(jié)構(gòu)體變量 stu1
和 stu2
:
struct student
{
char *name; /* 姓名 */
int num; /* 學(xué)號 */
int age; /* 年齡 */
char group; /* 所在學(xué)習(xí)小組 */
float score; /* 成績 */
} stu1, stu2;
這個結(jié)構(gòu)體包含了五個成員:姓名(name
)、學(xué)號(num
)、年齡(age
)、所在學(xué)習(xí)小組(group
)和成績(score
)。而后面的 stu1
和 stu2
是兩個結(jié)構(gòu)體變量的實例。
你可以通過點操作符來訪問結(jié)構(gòu)體的成員,例如:
stu1.num = 12345;
stu1.age = 20;
stu1.score = 95.5;
這樣的定義和使用方式使得你能夠更靈活地組織和操作一組相關(guān)的數(shù)據(jù)。
應(yīng)用舉例(定義&使用)
示例定義了一個名為 student
的結(jié)構(gòu)體,然后聲明了兩個結(jié)構(gòu)體變量 stu3
和 stu4
。接著,對其中一個結(jié)構(gòu)體變量 stu3
進(jìn)行了賦值操作。這樣的結(jié)構(gòu)體定義和使用方式在實際編程中非常常見,用于組織和管理相關(guān)的數(shù)據(jù)。
#include <stdio.h>
// 定義結(jié)構(gòu)體
struct student
{
char *name; /* 姓名 */
int num; /* 學(xué)號 */
int age; /* 年齡 */
char group; /* 所在學(xué)習(xí)小組 */
float score; /* 成績 */
};
int main()
{
// 聲明結(jié)構(gòu)體變量
struct student stu3, stu4;
// 對結(jié)構(gòu)體變量進(jìn)行賦值
stu3.name = "張三";
stu3.num = 1;
stu3.age = 18;
stu3.group = 'A';
stu3.score = 80.9;
// 輸出結(jié)構(gòu)體變量的值
printf("姓名: %s\n", stu3.name);
printf("學(xué)號: %d\n", stu3.num);
printf("年齡: %d\n", stu3.age);
printf("小組: %c\n", stu3.group);
printf("成績: %.2f\n", stu3.score);
return 0;
}
這個簡單的程序演示了結(jié)構(gòu)體的定義和使用。在實際的應(yīng)用中,結(jié)構(gòu)體通常用于表示實體的屬性,比如學(xué)生、員工等。通過定義結(jié)構(gòu)體,可以更清晰地組織數(shù)據(jù),并方便地對其進(jìn)行操作。在上述代碼中,你可以看到如何聲明結(jié)構(gòu)體、定義結(jié)構(gòu)體變量,并對結(jié)構(gòu)體成員進(jìn)行賦值和輸出。
應(yīng)用舉例(ST源碼,使用類型別名)
在這個例子中,我們看到了一種在嵌入式開發(fā)中常見的用法,即使用類型別名來定義一個結(jié)構(gòu)體,以方便后續(xù)使用。這段代碼摘自 STM32 HAL 庫中的 stm32f1xx_hal_gpio.h
頭文件,該頭文件定義了 STM32 系列微控制器的 GPIO 初始化結(jié)構(gòu)體類型。
typedef struct
{
uint32_t Pin; /* 引腳號 */
uint32_t Mode; /* 工作模式 */
uint32_t Pull; /* 上下拉 */
uint32_t Speed; /* IO 速度 */
} GPIO_InitTypeDef;
這里使用 typedef
創(chuàng)建了一個名為 GPIO_InitTypeDef
的類型別名,它是一個結(jié)構(gòu)體類型,包含了四個成員:Pin
、Mode
、Pull
和 Speed
。這樣的類型別名使得在代碼中使用該結(jié)構(gòu)體更為方便,而不需要每次都寫完整的結(jié)構(gòu)體聲明。
在實際的 STM32 項目中,你可以使用這個結(jié)構(gòu)體類型來初始化 GPIO 引腳的配置,例如:
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 初始化 GPIO 引腳配置
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
這段代碼演示了如何使用 GPIO_InitTypeDef
結(jié)構(gòu)體類型來初始化 GPIO 引腳的配置。這種方式使得代碼更為清晰,同時也提高了可讀性和可維護性。
八、指針
指針就是內(nèi)存的地址
指針變量是保存了指針的變量
以下是一些補充說明:
-
指針的定義和初始化:
char *p_str = "This is a test!";
這里定義了一個指向字符型數(shù)據(jù)的指針
p_str
,并將其初始化為指向字符串常量 “This is a test!” 的首地址。這個指針可以用來訪問字符串的各個字符。 -
指針的解引用:
*p_str;
*
運算符用于解引用指針,取得指針?biāo)竷?nèi)存地址上的值。在這個例子中,*p_str
表示取得p_str
所指向的字符串的第一個字符。 -
取地址運算符:
&p_str;
&
運算符用于取得變量的地址。在這個例子中,&p_str
表示取得p_str
變量的地址。 -
指針變量的類型:
int *p_int;
這里定義了一個指向整數(shù)型數(shù)據(jù)的指針變量
p_int
。指針變量的類型應(yīng)與所指向的數(shù)據(jù)類型相匹配。
指針是 C 語言中一個強大而靈活的特性,能夠提供直接的內(nèi)存訪問和更高效的數(shù)據(jù)處理。然而,正確地使用指針也需要謹(jǐn)慎,因為錯誤的指針操作可能導(dǎo)致程序崩潰或產(chǎn)生難以調(diào)試的 bug。
在這個例子中,buf
是一個包含 5 個 uint8_t
類型元素的數(shù)組,而 p_buf
是指向 buf
數(shù)組首元素的指針。下面解釋每個操作的效果:
uint8_t buf[5] = {1, 3, 5, 7, 9};
uint8_t *p_buf = buf;
*p_buf = 10; // 修改 buf[0] 的值為 10
// 此時 buf 數(shù)組變?yōu)?{10, 3, 5, 7, 9}
p_buf[0] = 20; // 修改 buf[0] 的值為 20
// 此時 buf 數(shù)組變?yōu)?{20, 3, 5, 7, 9}
p_buf[1] = 30; // 修改 buf[1] 的值為 30
// 此時 buf 數(shù)組變?yōu)?{20, 30, 5, 7, 9}
p_buf++; // 將指針 p_buf 移動到下一個元素,即 buf[2]
*p_buf = 40; // 修改 buf[2] 的值為 40
// 此時 buf 數(shù)組變?yōu)?{20, 30, 40, 7, 9}
p_buf[0] = 50; // 修改 buf[2] 的值為 50
// 此時 buf 數(shù)組變?yōu)?{20, 30, 50, 7, 9}
這個例子演示了指針和數(shù)組的關(guān)系。指針 p_buf
指向數(shù)組 buf
的首元素,通過指針操作可以修改數(shù)組中相應(yīng)位置的值。每次對指針的操作都會影響到指針指向的位置。
指針使用的兩大最常見問題
兩大指針使用常見問題是非常重要的,確保正確分配內(nèi)存并避免越界訪問對于程序的穩(wěn)定性和安全性至關(guān)重要。
1. 未分配(申請)內(nèi)存就用
在這種情況下,指針沒有被正確初始化,試圖通過未分配內(nèi)存的指針進(jìn)行寫操作可能導(dǎo)致不可預(yù)測的結(jié)果。正確的做法是先分配足夠的內(nèi)存,然后再使用指針。
char *p_buf = (char *)malloc(sizeof(char) * 3); // 分配內(nèi)存
if (p_buf != NULL) {
p_buf[0] = 100;
p_buf[1] = 120;
p_buf[2] = 150;
// 使用 p_buf
free(p_buf); // 釋放內(nèi)存
}
2. 越界使用
越界訪問指的是嘗試訪問數(shù)組或分配內(nèi)存之外的內(nèi)存區(qū)域。這可能導(dǎo)致程序崩潰或者產(chǎn)生不可預(yù)測的行為。正確的做法是確保指針在合法的范圍內(nèi)進(jìn)行訪問。
uint8_t buf[5] = {1, 3, 5, 7, 9};
uint8_t *p_buf = buf;
// 正確的訪問
for (int i = 0; i < 5; i++) {
p_buf[i] = p_buf[i] + 10;
}
// 錯誤的越界訪問
// p_buf[5] = 200; // 這里越界了,是錯誤的操作
總體而言,在使用指針時,始終要確保正確地分配了內(nèi)存,并在訪問數(shù)組或者其他數(shù)據(jù)結(jié)構(gòu)時保持越界訪問的風(fēng)險最小。
九、代碼規(guī)范
《嵌入式單片機 C代碼規(guī)范與風(fēng)格.pdf》
遵循這樣的代碼規(guī)范可以提高代碼的可讀性和可維護性。下面是一些簡要的解釋和例子,以更清晰地說明規(guī)范:
-
小寫字母和駝峰命名法:
int my_variable; void myFunction() { // 函數(shù)體 }
-
Doxygen 風(fēng)格注釋:
/** * @brief 這是一個函數(shù)的簡要說明 * @param 參數(shù)1 描述參數(shù)1 * @param 參數(shù)2 描述參數(shù)2 * @return 返回值說明 */ int myFunction(int param1, int param2) { // 函數(shù)體 }
-
使用空格進(jìn)行對齊:
if (condition) { // if 語句塊 } else { // else 語句塊 }
-
函數(shù)之間留有一個空行:
void function1() { // 函數(shù)體 } void function2() { // 函數(shù)體 }
-
獨立程序塊之間有一個空行:
// 程序塊1 // 程序塊2
-
全局變量和指針命名:
int g_global_variable; int *p_global_pointer;
-
語句單獨占一行,使用大括號:
if (condition) { // if 語句塊 } else { // else 語句塊 } for (int i = 0; i < 10; i++) { // for 循環(huán)體 }
以上規(guī)范是很好的實踐,可以提高代碼的一致性和可讀性,使得代碼更容易理解和維護。
其他規(guī)范
-
宏定義命名規(guī)范:
#define MAX_BUFFER_SIZE 256
使用大寫字母和下劃線,確保宏定義的可讀性和清晰性。
-
枚舉命名規(guī)范:
enum Color { RED, GREEN, BLUE };
使用大寫字母,枚舉成員使用大寫字母,用下劃線分隔。
-
文件命名規(guī)范:
文件名一般使用小寫字母,可以用下劃線或駝峰命名法。文件名應(yīng)該清晰地反映文件的內(nèi)容。
- 局部變量命名規(guī)范:
void myFunction() {
int local_variable;
// 函數(shù)體
}
使用小寫字母和駝峰命名法。
-
避免使用全局變量:
盡量避免使用全局變量,尤其是在不同模塊之間共享全局變量。推薦使用函數(shù)參數(shù)和返回值來傳遞信息。
-
避免過長的函數(shù)和復(fù)雜的嵌套:
函數(shù)應(yīng)該足夠簡潔,一個函數(shù)應(yīng)該完成一個特定的任務(wù)。過長的函數(shù)和復(fù)雜的嵌套結(jié)構(gòu)會降低代碼的可讀性。
-
常量命名規(guī)范:
const int MAX_RETRY_COUNT = 5;
使用大寫字母和下劃線,以便清晰地區(qū)分常量。
-
錯誤處理和日志記錄:
錯誤處理應(yīng)該及時和清晰,對于日志記錄,使用合適的級別,確保日志信息對于調(diào)試和問題追蹤是有用的。
-
一次只做一件事(Single Responsibility Principle):
每個函數(shù)和模塊應(yīng)該有一個清晰的目標(biāo),并只負(fù)責(zé)完成這個目標(biāo)。這有助于提高代碼的可維護性和可測試性。
這些規(guī)范可以根據(jù)團隊的具體需求進(jìn)行調(diào)整,但保持一致性和清晰性是至關(guān)重要的。文章來源:http://www.zghlxwxcb.cn/news/detail-825926.html
十、總結(jié)
文章來源地址http://www.zghlxwxcb.cn/news/detail-825926.html
到了這里,關(guān)于【正點原子STM32】C語言重點知識(配置MDK支持C99、位操作清零置一、帶參數(shù)的宏定義、頭文件的條件編譯和代碼條件編譯、關(guān)鍵字、結(jié)構(gòu)體指針、代碼規(guī)范)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!