天天看點

【C語言】C語言筆記整理一、記憶體中的一些概念二、文法三、連結清單四、字元串

目錄:

  • 一、記憶體中的一些概念
    • 1.棧與堆的差別:
    • 2.malloc()函數
    • 3.記憶體洩漏(Memory Leak)
    • 4.補碼
  • 二、文法
    • 1.逗号表達式
      • 表達式說明:
    • 2.switch語句
      • 表達式說明:
    • 3.結構體
      • ①基本結構體說明
      • ②通路結構體成員的方式
      • ③函數間傳遞結構體
  • 三、連結清單
    • 1.連結清單與數組的對比:
    • 2.連結清單的基本概念
  • 四、字元串
    • 1.字元與字元串的差別
    • 2.string.h庫重要函數

一、記憶體中的一些概念

1.棧與堆的差別:

name describe difference
棧(stack) 存放臨時/局部變量 由編譯器自動配置設定/釋放記憶體
堆 (heap) 是自由存儲區,存放動态資料,例如malloc()申請的空間 一般由程式員配置設定/釋放記憶體

棧(stack):

//example
int a;
char s[] = "adc";
char *p;
char *p1 = "123456";
           

堆(heap):

//example
int * p = (int*)malloc(4);
           

2.malloc()函數

  • malloc (Memory allocation)函數隻有一個形參,并且形參是整形.
  • 上述例子中的malloc(4)中的“4” 表示程式配置設定4個位元組.
  • malloc函數隻能傳回第一個位元組的位址
  • p本身所占的記憶體是靜态配置設定的,p所指向的記憶體是動态配置設定的.
  • 函數原型:void *malloc(unsigned int size)
  • 由于malloc()函數隻傳回的是第一個位元組的位址, 是以導緻p所指向的變量并不知道有多少個位元組 ,是以上述例子中(int *)malloc(4)中的強制類型轉換(int *)的作用為:将malloc()函數傳回的位址轉換為一個整形變量的位址.

free函數:釋放動态記憶體

  • 函數原型 void * free (void *p) //釋放其指向的動态記憶體

3.記憶體洩漏(Memory Leak)

是指程式中己動态配置設定的堆記憶體由于某種原因程式未釋放或無法釋放,造成系統記憶體的浪費,導緻程式運作速度減慢甚至系統崩潰等嚴重後果。

4.補碼

  • 計算機中的資料存儲方式為補碼形式
1)采用補碼的原因或好處如下,采用補碼運算具有如下兩個特征:
  • ①因為使用補碼可以将符号位和其他位統一處理,同時,減法也可以按加法來處理,即如果是補碼表示的數,不管是加減法都直接用加法運算即可實作。
  • ②兩個用補碼表示的數相加時,如果最高位(符号位)有進位,則進位被舍棄。
2)這樣的運算有兩個好處:
  • ①使符号位能與有效值部分一起參加運算,進而簡化運算規則。進而可以簡化運算器的結構,提高運算速度; ( 即減法運算可以用加法運算表示出來)
  • ②加法運算比減法運算更易于實作。使減法運算轉換為加法運算,進一步簡化計算機中運算器的線路設計。

舉例一個4位數組,說明原碼反碼補碼的關系:【第一位為符号位:0正 1負】

數字 原碼 反碼 補碼
5(正數) 0101 0101 0101
-6 (負數) 1110 1001 1010

「正數的原碼反碼補碼都相等;負數,反碼等于原碼符号位不變,其餘各位取反,補碼等于反碼加1」

//example

當 計算 5-6 時,我們可以看做 5+(-6) ,運算時均化成補碼形式進行運算
由上表得到5與(-6)的補碼
即: 5+(-6)  -->   0101(補碼)+1010(補碼) = 1111(補碼)
           

由上計算出的資料為 1111(補碼),是以想得到原始資料,需要将 補碼-1,除符号位均取反

得到:1001 --> -1【驗證計算5-6 = -1,資料正确】

  • 補碼+補碼=補碼,本質未改變

二、文法

1.逗号表達式

表達式說明:

表達式1,表達式2,表達式3,…… ,表達式n 

逗号表達式的要領: 
(1) 逗号表達式的運算過程為:從左往右逐個計算表達式。 
(2) 逗号表達式作為一個整體,它的值為最後一個表達式(也即表達式n)的值。 
(3) 逗号運算符的優先級别在所有運算符中最低。 
(4) 與指派運算符一起,且未加括号時,先執行指派運算符 
(優先級:括号>指派運算符>逗号)
           
//example
void main(void) 
{ 
	int x,y,z; 
	x=y=1; 
	z=x++,y++,++y; 
	printf(“%d,%d,%d\n”,x,y,z); 
} 

           
[A]2,3,3 [B]2,3,2 [C]2,3,1 [D]1,1,1 
正确答案選: C
           

2.switch語句

表達式說明:

// switch語句
switch(表達式)
{
	case 常量表達式1:語句1;//break;
	case 常量表達式2:語句2;//break;
		...
	case 常量表達式n:語句n;//break;
	default :	語句n+1;  //break;
}
           

3.結構體

①基本結構體說明

struct 結構名   //結構體名可了解為一個标簽名
{
	成員清單;
	
}變量名;
           

推薦的方式①:

/* 建立人屬性 結構體  标簽:Person */
struct Person
{
	int age;  		//年齡
	float stature;	//身高
	char sex[10];	//姓名
};

/* 定義“小王” 人物屬性 并 進行賦初值 */
struct Person XiaoWang = {21, 178.5, "male"};
           

推薦的方式②:

/* 用typedef建立 新類型 */
typedef struct Per
{
	int age;  		//年齡
	float stature;	//身高
	char sex[10];	//姓名
}Person; 

/* 定義“小王” 人物屬性 并 進行賦初值 */
Person XiaoWang = {21, 178.5, "male"};
           

C語言結構體定義有多種方式可參考::C 結構體 菜鳥教程.

②通路結構體成員的方式

方式1.  結構體變量名.成員名
方式2.  指針變量名->成員名
           

首先定義:

/* 用typedef建立 新類型 */
typedef struct Per
{
	int age;  		//年齡
	float stature;	//身高
	char sex[10];	//姓名
}Person; 

int main(void)
{
	Person XiaoWang = {21, 178.5, "male"};
	Person * pst = &XiaoWang;
	
	return 0;
}
           

方式1

XiaoWang.age = 18;
           

方式2

其含義為: pst所指向的那個結構體變量中的age這個成員

pst->age = 18;  //内部轉換成(*pst) .age = 18;
           

③函數間傳遞結構體

首先定義JY901_t (t類型結構體ype)

定義JY901 結構體變量

struct JY901_t
{
	float Acc[3]; 		  //加速度
	float Gyro[3]; 			//角速度
	float Angle[3];			//角度
	short Mag[3];
	float Temperature;	//溫度
};

struct JY901_t JY901 = {0}; //JY901真實值結構體
           

錯誤示例:↓ ↓↓

/* JY901 資料轉換 */
void JY901_Convert(struct JY901_t  Arr) //
{
		static u8 i = 0;
		for(i = 0;i < 3;i++){	
				Arr.Acc[i] = (float)stcAcc.a[i]/32768*16;
				Arr.Gyro[i] = (float)stcGyro.w[i]/32768*2000;
				Arr.Angle[i] = (float)stcAngle.angle[i]/32768*180;
				Arr.Mag[i] 	  = stcMag.h[i];
		}
		pArr.Temperature = (float)stcAcc.T/100;
}

static void time_out(void* parameter)// 定時器1逾時函數  進行JY901子產品資料轉換
{
    rt_enter_critical(); /* 排程器上鎖,上鎖後,将不再切換到其他線程,僅響應中斷 */
    
	JY901_Convert(JY901); //調用資料轉換函數

    rt_exit_critical();	/* 排程器解鎖 */
}

           

傳入JY901_Convert() 函數的 結構體形參為 一個局部靜态變量。當調用該函數時,并不能做到資料轉換,因為在子函數執行結束後,局部靜态變量會被系統釋放掉,是以得到的值為空。

  • 是以,函數傳參盡量用指針來代替。

正确示例:↓ ↓↓

/* JY901 資料轉換 */
void JY901_Convert(struct JY901_t * pArr) //傳入結構體指針類型
{
		static u8 i = 0;
		for(i = 0;i < 3;i++){	
				pArr->Acc[i] = (float)stcAcc.a[i]/32768*16;
				pArr->Gyro[i] = (float)stcGyro.w[i]/32768*2000;
				pArr->Angle[i] = (float)stcAngle.angle[i]/32768*180;
				pArr->Mag[i] 		= stcMag.h[i];
		}
		pArr->Temperature = (float)stcAcc.T/100;
}

static void time_out(void* parameter)// 定時器1逾時函數  進行JY901子產品資料轉換
{
    rt_enter_critical();/* 排程器上鎖,上鎖後,将不再切換到其他線程,僅響應中斷 */

	JY901_Convert(&JY901);//調用資料轉換函數  

    rt_exit_critical();	/* 排程器解鎖 */
}

           

由下圖可得到 JY901結構體所占用位元組數為 48

從記憶體占用的角度來講:我們進行函數傳參時應該使用指針傳遞,因為在32位系統中一個指針長度僅 4 個位元組數

【C語言】C語言筆記整理一、記憶體中的一些概念二、文法三、連結清單四、字元串

指針傳參好處:

  • 快速的傳遞資料
  • 耗用記憶體小
  • 執行速度快

三、連結清單

1.連結清單與數組的對比:

name advantage disadvantage
數組 存取速度快,查找友善 需要一個連續的記憶體空間,插入和删除元素效率低
連結清單 插入删除元素效率高 查找某個位置元素效率低

2.連結清單的基本概念

①首節點
  • 存放第一個有效資料的節點
②尾節點
  • 存放最後一個有效資料的節點
③頭節點
  • 頭節點的資料類型與首節點類型一緻
  • 頭節點是首節點前面的那個節點
  • 頭結點并不存放有效資料
  • 設定頭節點的作用為友善對連結清單的操作
④頭指針
  • 存放頭結點位址的指針變量
typedef struct Node
{
   int data;
   struct Node *next;
}LinkList_Type;
           
//建立表頭 傳回表頭
LinkList_Type *creatList(void)
{
	LinkList_Type *headNode = (LinkList_Type*)malloc(sizeof(LinkList_Type));

	headNode->next = NULL;

	return headNode;
}
           
//建立節點  
LinkList_Type * creatNode(int data)
{
	LinkList_Type * newNode = (LinkList_Type *)malloc(sizeof(LinkList_Type));

	newNode->data = data;
	newNode->next = NULL;
	return newNode;
}
           
//列印連結清單  周遊輸出
void printList(LinkList_Type * headNode)
{
	LinkList_Type *p = headNode->next;
	while(p != NULL)//如果目前位置的節點不為空
	{
		printf("%d \t",p->data);
		p = p->next;//指向下一個節點
	}
	printf("\n");
}
           
//頭插法:插入節點【棧】後進先出          ps:尾插法 【隊列】 先進先出
void insertNode_byHead(LinkList_Type * headNode,int data)
{
	LinkList_Type * newNode = creatNode(data);
	newNode->next = headNode->next;//指向原本headNode 所指向的下一個
	headNode->next = newNode; //替代原本headNode指向的節點
}
           
/* 删除指定的節點 */
void deleteNode_byNode(struct node* head,int pos)
{
    struct node* frontNode = head;     //指定節點的前一個節點
    struct node* posNode = head->next; //指定節點
    static int count = 0; //計數
    if(NULL == head->next){
        printf("This Linklist is empty.\n");
        return;
    }
    else{
        while(count != pos){//當未達到指定節點
            frontNode = posNode;
            posNode = posNode->next;
            count ++;
            if(NULL == posNode){
                printf("No find the point data node\n");
                return;
            }
        }
        frontNode->next = posNode->next;
        free(posNode);//釋放指定節點
    }
}
           
//指定資料删除
void deleteNode_byAppoin(LinkList_Type* headNode,int posData)
{
    LinkList_Type *posNode = headNode->next;//指定節點從頭結點的下一個開始
	LinkList_Type *posNodeFront = headNode; 
	if(posNode == NULL){
		printf("無法删除,連結清單為空\n");
	}
	else {
		while(posNode->data != posData)//不等于指定的資料,繼續向下找
		{
			posNodeFront = posNode;
			posNode = posNode->next;
			if(posNode == NULL)//找到表尾,即未找到
			{
				printf("沒有找到相關資訊,無法删除\n");
				return;//結束
			}
		}
		posNodeFront->next = posNode->next;//指定節點posNode位址跳過
		free(posNode);//釋放記憶體
	}

}
           
/* 主函數測試 */
int main(void)
{

    printf("hello world!\n");         
    struct node * head = creatList();//建立表頭
    insertNode_byHead(head,1);
    insertNode_byHead(head,2);
    insertNode_byHead(head,3);
    insertNode_byHead(head,4);

    deleteNode_byData(head,1);

    deleteNode_byNode(head,2);
    printList(head);
 
    return 0;
}
           

四、字元串

1.字元與字元串的差別

名稱 符号 内容 輸出格式 辨別
字元 ’ ’ 單引号 單個字元 %c
字元串 " "雙引号 多個字元 %s ‘\0’ 作為結尾
字元串的本質為:
  • 一個字元數組
char string[20] = "hello world!";

char name[] = "張三";    //省略長度

char name_array[][20] = {"張三","李四"};//二維數組

           
// 首先因為[]優先級比*高,是以name先與[]結合,成為一個數組,再與*結合,說明數組中的元素都為指針類型,
// 再與char結合,說明指針指向的内容是字元型的.是以name是一個指向由字元型資料組成的數組的指針。
           

2.string.h庫重要函數

  • ①字元串格式化指令:sprintf()
//把格式化的資料寫入某個字元串緩沖區。
	
	函數原型:int sprintf( char *buffer, const char *format, [ argument] … );
           

MCU常常無法直接輸出浮點型,可以使用sprintf函數将資料打包後再輸出。

char str[50];
	sprintf(str , "Acc:%.3f %.3f %.3f" , JY901.Acc[0],JY901.Acc[1],JY901.Acc[2]);
	LOG_H(str);
           

下圖可見單片機輸出了浮點型資料。

【C語言】C語言筆記整理一、記憶體中的一些概念二、文法三、連結清單四、字元串
  • ②比較:strcmp()
函數原型:extern int strcmp(const char *str1,const char *str2);
	
	若str1=str2,則傳回零;
	若str1<str2,則傳回負數;
	若str1>str2,則傳回正數。
	兩個字元串自左向右逐個字元相比(按ASCII值大小相比較),直到出現不同的字元或遇'\0'為止。
           
//若argv[1] == "on" 傳回 0
if( !strcmp(argv[1],"on") ){
	gyroscope_led_array[3] = 0x00;
	LOG_H("Operation is successful! gyroscope_led on\n");
}
           
  • ③連接配接:strcat()
char hi[20] = "hello ";
char *name = "world!";
printf("%s \n",strcat(hi,name));
           
輸出結果:hello world!
           
  • ④拷貝:strcpy()
函數原型:char * strcpy(char * strDest,const char * strSrc);
           
char str[20] = "hello world!";
printf("%s \n",strcpy(str,"hey,guys!"));
           
輸出結果:hey,guys!
           
  • ⑤擷取長度:strlen()
函數原型:extern unsigned int strlen(char *s);
           
  • ⑥轉換大寫:strupr()
函數原型:extern char *strupr(char *s);
           
  • ⑦轉換小寫:strlwr()
函數原型:extern char *strlwr(char *s);
           

繼續閱讀