1. 程式人生 > 其它 >嵌入式C學習第四次任務

嵌入式C學習第四次任務

技術標籤:mysql

1.結構體的引數傳遞:

結構體做函式引數有三種傳遞方式:
一是傳遞結構體變數,這是值傳遞,二是傳遞結構體指標,這是地址傳遞,三是傳遞結構體成員,當然這也分為值傳遞和地址傳遞。

以傳引用呼叫方式傳遞結構比用傳值方式傳遞結構效率高。以傳值方式傳遞結構需要對整個結構做一份拷貝。

下面看一個列子,student結構體中包含該學生的各種資訊,我們在change函式中對其進行部分修改,再在主函式中輸出其結果

1.下面傳遞結構體變數

#include<stdio.h>
#include<string.h>
#define format "%d\n%s\n%f\n%f\n%f\n"
struct student
{
    int num;
    char name[20];
    float score[3];
};
void change( struct student stu );
int main()
{
    
    struct student stu;
    stu.num = 12345;
    strcpy(stu.name, "Tom");
    stu.score[0] = 67.5;
    stu.score[1] = 89;
    stu.score[2] = 78.6;
    change(stu);
    printf(format, stu.num, stu.name, stu.score[0], stu.score[1],stu.score[2]);
    printf("\n");
    return 0;
}
 
void change(struct student stu)
{
    stu.score[0] = 100;
    strcpy(stu.name, "jerry");
}

2.地址傳遞

#include<stdio.h>
#define format "%d\n%s\n%f\n%f\n%f\n"
struct student
{
    int num;
    char name[20];
    float score[3];
};
void change( struct student* stu );
int main()
{
    
    struct student stu;
    stu.num = 12345;
    strcpy(stu.name, "Tom");
    stu.score[0] = 67.5;
    stu.score[1] = 89;
    stu.score[2] = 78.6;
    change(&stu);
    printf(format, stu.num, stu.name, stu.score[0], stu.score[1],stu.score[2]);
    printf("\n");
    return 0;
}
 
void change(struct student* p)
{
    p->score[0] = 100;
    strcpy(p->name, "jerry");
}

可以看到,通過地址傳遞修改了結構體內的資料

用&stu做實參,&stu是結構體變數stu的地址。在呼叫函式時將該地址傳送給形參p(p是指標變數)。這樣p就指向stu。

在change函式中改變結構體內成員的值,在主函式中就輸出了改變後的值

3.結構體成員的地址傳遞和值傳遞

這個類似於單一變數的傳遞,當然是地址傳遞才能修改。

把一個完整的結構體變數作為引數傳遞,要將全部成員值一個一個傳遞,費時間又費空間,開銷大。如果結構體型別中的成員很多,或有一些成員是陣列,則程式執行效率會大大降低。在這種情況下,用指標做函式引數比較好,能提高執行效率。

原文地址:https://blog.csdn.net/lin37985/article/details/38582027

2.檔案的包含:

在C語言中檔案包含是指一個原始檔可以將另一個原始檔的全部內容包含進來。該命令的作用是在預編譯時,將指定原始檔的內容複製到當前檔案中。檔案包含是C語言預處理命令三個內容之一。

一個大程式,通常分為多個模組,並由多個程式設計師分別程式設計。有了檔案包含處理功能,就可以將多個模組共用的資料(如符號常量和資料結構)或函式,集中到一個單獨的檔案中。這樣,凡是要使用其中資料或呼叫其中函式的程式設計師,只要使用檔案包含處理功能,將所需檔案包含進來即可,不必再重複定義它們,從而減少重複勞動和定義不一致造成的錯誤。

檔案包含的特點:

① 編譯預處理時,預處理程式將查詢指定的被包含檔案,並將其複製插入到#include命令出現的位置上

② 常用在檔案頭部的被包含檔案,稱為"標題檔案"或"頭部檔案",常以"h"(head)作為字尾,簡稱標頭檔案。在標頭檔案中,除可包含巨集定義外,還可包含外部變數定義、結構型別定義等。

③ 一條包含命令,只能指定一個被包含檔案。如果要包含多個檔案,則要用多條包含命令。例如,檔案f1.h中要使用到檔案f2.h和檔案f3.h的內容,則可在檔案f1.h中用兩個檔案包含命令分別包含檔案f2.h和檔案f3.h,即在檔案f1.h中定義:

#include "f2.h"

#include "f3.h"

在使用多個#include命令時,順序是一個值得注意的問題。上例中,如果檔案f1.h包含檔案f2.h,而檔案2要用到檔案f3.h,則在f1.h中#include定義的順序應該是:

#include "f3.h"

#include "f2.h"

這樣檔案f1.c和檔案f2.h都可以使用檔案f3.h的內容。

④ 檔案包含可以巢狀,即被包含檔案中又包含另一個檔案。例如,檔案f2.h中要使用到檔案f1.h的內容,檔案f3.h要使用到檔案f2.h的內容,則可在檔案f2.h中用#include "f1.h"命令,在檔案f3.h中用#include "f2.h"命令,即定義如下:

檔案f1.h:

{

… …

}

檔案f2.h:

#include "f1.h"

int max()

{

… …

}

檔案f3.h:

#include "f2.h"

main

{

… …

}

#include命令一般用來把C語言提供的標準庫標頭檔案(如stdio.h、math.h)包含到程式中。程式設計師也可以自己定義一個頭檔案,寫入一些常用的函式原型、巨集定義、結構和聯合型別定義等,然後將它包含到程式中。例如:#include "stdio.h" (標準輸入/輸出函式庫)

#include "math.h" (數學函式庫)

#include "stdlib.h" (常用函式庫)

#include "string.h" (字串處理函式庫)

3.大小端和位元組序:

一、概念

位元組序,就是 大於一個位元組型別的資料在記憶體中的存放順序。是在跨平臺和網路程式設計中,時常要考慮的問題。

二、分類

位元組序經常被分為兩類:

1.Big-Endian(大端):高位位元組排放在記憶體的低地址端,低位位元組排放在記憶體的高地址端。

2.Little-Endian(小端):低位位元組排放在記憶體的低地址端,高位位元組排放在記憶體的高地址端。

三、高低地址與高低位元組

高低地址:

C程式對映中記憶體的空間佈局大致如下:


最高記憶體地址 0xFFFFFFFF

棧區(從高記憶體地址,往 低記憶體地址發展。即棧底在高地址,棧頂在低地址)

堆區(從低記憶體地址 ,往 高記憶體地址發展)

全域性區(常量和全域性變數)

程式碼區

最低記憶體地址 0x00000000

四、例子

對於資料 0x12345678,假設從地址0x4000開始存放,在大端和小端模式下,存放的位置分別為:

採用Little-endian模式的CPU對運算元的存放方式是從低位元組到高位元組,而Big-endian模式對運算元的存放方式是從高位元組到低位元組。

小端儲存後:0x78563412 大端儲存後:0x12345678

五、程式碼判斷當前機器是大端還是小端

void byteorder()
{
	union
	{
		short value;
		char union_bytes[sizeof(short)];
	}test;
	test.value = 0x0102;
 
	if (sizeof(short) == 2)
	{
		if (test.union_bytes[0] == 1 && test.union_bytes[1] == 2)
			cout << "big endian" << endl;
		else if (test.union_bytes[0] == 2 && test.union_bytes[1] == 1)
			cout << "little endian" << endl;
		else
			cout << "unknown" << endl;
	}
	else
	{
		cout << "sizeof(short) == " << sizeof(short) << endl;
	}
 
	return ;
}

4.位域:

有些資訊在儲存時,並不需要佔用一個完整的位元組, 而只需佔幾個或一個二進位制位。例如在存放一個開關量時,只有0和1 兩種狀態, 用一位二進位即可。為了節省儲存空間,並使處理簡便,C語言又提供了一種資料結構,稱為“位域”或“位段”。所謂“位域”是把一個位元組中的二進位劃分為幾 個不同的區域,並說明每個區域的位數。每個域有一個域名,允許在程式中按域名進行操作。 這樣就可以把幾個不同的物件用一個位元組的二進位制位域來表示。位段成員必須宣告為int、unsigned int或signed int型別(short char long)。

一、位域的定義和位域變數的說明位域定義與結構定義相仿,其形式為:

struct 位域結構名     
{ 位域列表 }; 

其中位域列表的形式為: 型別說明符 位域名:位域長度
例如:

struct bs     
{     
int a:8;     
int b:2;     
int c:6;     
};  

位域變數的說明與結構變數說明的方式相同。 可採用先定義後說明,同時定義說明或者直接說明這三種方式。例如:

View Code

說明data為bs變數,共佔兩個位元組。其中位域a佔8位,位域b佔2位,位域c佔6位。對於位域的定義尚有以下幾點說明:

1.如果一個位元組所剩空間不夠存放另一位域時,應從下一單元起存放該位域。也可以有意使某位域從下一單元開始。例如:

struct bs     
{     
unsigned a:4     
unsigned :0 /*空域*/     
unsigned b:4 /*從下一單元開始存放*/     
unsigned c:4     
}   

這個位域定義中,a佔第一位元組的4位,後4位填0表示不使用,b從第二位元組開始,佔用4位,c佔用4位。

2.位域的長度不能大於資料型別本身的長度,比如int型別就能超過32位二進位。

3.位域可以無位域名,這時它只用來作填充或調整位置。無名的位域是不能使用的。例如:

struct k     
{     
int a:1     
int :2 /*該2位不能使用*/     
int b:3     
int c:2     
};  

從以上分析可以看出,位域在本質上就是一種結構型別, 不過其成員是按二進位分配的。

二、位域的使用位域的使用和結構成員的使用相同,其一般形式為: 位域變數名.位域名 位域允許用各種格式輸出。

struct bs     
{     
    unsigned a:1;     
    unsigned b:3;     
    unsigned c:4;     
} bit,*pbit;     
bit.a=1;     
bit.b=7; //注意:位域的賦值不能超過該域所能表示的最大值,如b只有3位,能表示的最大數為7,若賦為8,就會出錯   
bit.c=15;

printf("%d,%d,%d/n",bit.a,bit.b,bit.c);
pbit=&bit;
pbit->a=0;
pbit->b&=3;
pbit->c=1;
printf("%d,%d,%d/n",pbit->a,pbit->b,pbit->c);

上例程式中定義了位域結構bs,三個位域為a,b,c。說明了bs型別的變數bit和指向bs型別的指標變數pbit。這表示位域也是可以使用指標的。

程式的9、10、11三行分別給三個位域賦值。( 應注重賦值不能超過該位域的答應範圍)程式第12行以整型量格式輸出三個域的內容。第13行把位域變數bit的地址送給指標變數pbit。第14行用指標 方式給位域a重新賦值,賦為0。第15行使用了複合的位運算子"&=", 該行相當於: pbit->b=pbit->b&3位域b中原有值為7,與3作按位與運算的結果為3(111&011=011,十進位制值為 3)。同樣,程式第16行中使用了複合位運算"=", 相當於: pbit->c=pbit->c1其結果為15。程式第17行用指標方式輸出了這三個域的值。

我們再來看看下面兩個結構體定義:

struct foo2 {
char    a : 2;
char    b : 3;
char    c : 1;
};

struct foo3 {
char    a : 2;
char    b : 3;
char    c : 7;
};

我們來列印一下這兩個結構體的大小,我們得到的結果是:
sizeof(struct foo2) = 1
sizeof(struct foo3) = 2
顯然都不是我們期望的,如果按照正常的記憶體對齊規則, 這兩個結構體大小均應該為3才對,那麼問題出在哪了呢?首先通過這種現象我們可以肯定的是:帶有'位域'的結構體並不是按照每個域對齊的,而是將一些位域 成員'捆綁'在一起做對齊的。以foo2為例,這個結構體中所有的成員都是char型的,而且三個位域佔用的總空間為6 bit < 8 bit(1 byte),這時編譯器會將這三個成員'捆綁'在一起做對齊,並且以最小空間作代價,這就是為什麼我們得到sizeof(struct foo2) = 1這樣的結果的原因了。再看看foo3這個結構體,同foo2一樣,三個成員型別也都是char型,但是三個成員位域所佔空間之和為9 bit > 8 bit(1 byte),這裡位域是不能跨越兩個成員基本型別空間的,這時編譯器將a和b兩個成員'捆綁'按照char做對齊,而c單獨拿出來以char型別做對齊, 這樣實際上在b和c之間出現了空隙,但這也是最節省空間的方法了。我們再看一種結構體定義:

struct foo4 {
char    a : 2;
char    b : 3;
int c : 1;
};

在foo4中雖然三個位域所佔用空間之和為6 bit < 8 bit(1 byte),但是由於char和int的對齊係數是不同的,是不能捆綁在一起,那是不是a、b捆綁在一起按照char對齊,c單獨按照int對齊呢?我們 列印一下sizeof(struct foo4)發現結果為8,也就是說編譯器把a、b、c一起捆綁起來並以int做對齊了。就是說不夠一個型別的size時,將按其中最大的那個型別對齊。此 處按int對齊。


C99規定int、unsignedint和bool可以作為位域型別,但編譯器幾乎都對此作了擴充套件,
允許其它型別型別的存在。
使用位域的主要目的是壓縮儲存,其大致規則為:

1) 如果相鄰位域欄位的型別相同,且其位寬之和小於型別的sizeof大小,則後面的欄位將緊鄰前一個欄位儲存,直到不能容納為止
2) 如果相鄰位域欄位的型別相同,但其位寬之和大於型別的sizeof大小,則後面的欄位將從新的儲存單元開始,其偏移量為其型別大小的整數倍;
3) 如果相鄰的位域欄位的型別不同,則各編譯器的具體實現有差異,VC6採取不壓縮方式,Dev-C++,GCC採取壓縮方式;
4) 如果位域欄位之間穿插著非位域欄位,則不進行壓縮;
5) 整個結構體的總大小為最寬基本型別成員大小的整數倍。
struct s1 
{ 
int i: 8; 
int j: 4; 
int a: 3; 
double b; 
}; 

struct s2 
{ 
int i: 8; 
int j: 4; 
double b; 
int a:3; 
}; 

printf("sizeof(s1)= %d/n", sizeof(s1)); 
printf("sizeof(s2)= %d/n", sizeof(s2)); 
result: 16, 24 

第一個結構體中,i,j,a共佔15個位,不足8個位元組,按double 8位元組對齊,共16位元組

第二個結構體中,i,j共佔12位,不足8位元組,按8位元組對齊,a也按8位元組對齊,加上double共8+8+8=24個位元組

5.函式指標:

函式指標是指向函式的指標變數。 因此"函式指標"本身首先應是指標變數,只不過該指標變數指向函式。這正如用指標變數可指向整型變數、字元型、陣列一樣,這裡是指向函式。如前所述,C在編譯時,每一個函式都有一個入口地址,該入口地址就是函式指標所指向的地址。有了指向函式的指標變數後,可用該指標變數呼叫函式,就如同用指標變數可引用其他型別變數一樣,在這些概念上是大體一致的。函式指標有兩個用途:呼叫函式和做函式的引數

函式指標的宣告方法為:

返回值型別 ( *指標變數名) ([形參列表]);

注1:"返回值型別"說明函式的返回型別,"(指標變數名 )"中的括號不能省,括號改變了運算子的優先順序。若省略整體則成為一個函式說明,說明了一個返回的資料型別是指標的函式,後面的"形參列表"表示指標變數指向的函式所帶的引數列表。例如:

int func(int x); /* 宣告一個函式 */

int (*f) (int x); /* 宣告一個函式指標 */

f=func; /* 將func函式的首地址賦給指標f */

或者使用下面的方法將函式地址賦給函式指標:

f = &func;

賦值時函式func不帶括號,也不帶引數,由於func代表函式的首地址,因此經過賦值以後,指標f就指向函式func(x)的程式碼的首地址。

注2:函式括號中的形參可有可無,視情況而定

下面的程式說明了函式指標呼叫函式的方法:

例一、

ptr是指向函式的指標變數,所以可把函式max()賦給ptr作為ptr的值,即把max()的入口地址賦給ptr,以後就可以用ptr來呼叫該函式,實際上ptr和max都指向同一個入口地址,不同就是ptr是一個指標變數,不像函式名稱那樣是死的,它可以指向任何函式,就看你想怎麼做了。在程式中把哪個函式的地址賦給它,它就指向哪個函式。而後用指標變數呼叫它,因此可以先後指向不同的函式。不過注意,指向函式的指標變數沒有++和--運算,用時要小心。

不過,在某些編譯器中這是不能通過的。這個例子的補充如下。

應該是這樣的:

1.定義函式指標型別:

typedef int (*fun_ptr)(int,int);

2.宣告變數,賦值:

fun_ptr max_func=max;

也就是說,賦給函式指標的函式應該和函式指標所指的函式原型是一致的。

指標函式和函式指標的區別:

1,這兩個概念都是簡稱,指標函式是指返回值是指標的函式,即本質是一個函式。我們知道函式都有返回型別(如果不返回值,則為無值型),只不過指標函式返回型別是某一型別的指標。

其定義格式如下所示:

返回型別識別符號*函式名稱(形式引數表)

{函式體}

返回型別可以是任何基本型別和複合型別。返回指標的函式的用途十分廣泛。事實上,每一個函式,即使它不帶有返回某種型別的指標,它本身都有一個入口地址,該地址相當於一個指標。比如函式返回一個整型值,實際上也相當於返回一個指標變數的值,不過這時的變數是函式本身而已,而整個函式相當於一個"變數"。例如下面一個返回指標函式的例子:

學生學號從0號算起,函式find()被定義為指標函式,其形參pointer是指標指向包含4個元素的一維陣列指標變數。pf是一個指標變數,它指向浮點型變數。main()函式中呼叫find()函式,將score陣列的首地址傳給pointer.