資源簡介 風千鋒教后千鋒智能物聯網+嵌入式學科練習l:實現strlen函數功能unsigned int my_strlen(const char *s);練習2:實現strcpy函數功能char *my_strcpy(char *dest,const char *src);練習3:實現atoi函數功能int my_atoi(const char str)練習4:使用sscanf讀取"[ti:簡單愛]"":"號與"]"之間的內容練習5:使用sscanf讀取"[02:06.85]"02(代表分鐘)06(代表秒)到整型變量min、sec中練習6:使用strchr函數查找字符,統計helloworldhelloworldhelloworld'字符串中字符w的個數及位置。做真實的自己,用良心做教有1倉千鋒教有千鋒智能物聯網+嵌入式學科做真實的自己,用良心做教育2風干鋒教百千鋒智能物聯網+嵌入式學科練習:1.輸出0999的水仙花數水仙花數算法:一個數=它各位的立方和,例如:153=1*1*1+5*5*5+3*3*3提示:for循環,求余(%)、取整()運算符2任意給出一個年、月、日,判斷是這一年的第幾天:閏年算法:能被4整除且不能被100整除,或者能被400整除如:2012510是這一年的第131天提示:switch做真實的自己,用良心做教有1倉千鋒教有千鋒智能物聯網+嵌入式學科做真實的自己,用良心做教育2風干鋒熱百千鋒智能物聯網+嵌入式學科練習:1、任意給出一個年、月、日,判斷是這一年的第幾天:閏年算法:能被4整除且不能被100整除,或者能被400整除如:2012510是這一年的第131天提示:使用數組的方式計算,將每月的天數放在一個數組中2、打字游戲1)隨機函數A.srand(unsigned)time(NULL);以當前時間為準,設置隨機種子注意:此函數,在每次開始游戲后調用一次即可B.ch=rand(;注意:rand0函數,每調用一次,產生一個隨機數字2)獲得鍵值函數ch=getch0;/無需按下回車,可直接獲得鍵盤上按下的鍵值3)時間函數start time=time(NULL);end_time=time(NULL);4)system("cls");/清空屏幕做真實的自己,用良心做教有倉千鋒教有千鋒智能物聯網+嵌入式學科做真實的自己,用良心做教育2千鋒智能物聯網+嵌入式學科第 1章 環境搭建第 2章 c數據類型及語句第 3章 數組第 4章 函數第 5章 預處理、動態庫、靜態庫第 6章 指針第 7章 動態內存申請7.1 動態分配內存的概述在數組一章中,介紹過數組的長度是預先定義好的,在整個程序中固定不變,但是在實際的編程中,往往會發生這種情況,即所需的內存空間取決于實際輸入的數據,而無法預先確定 。為了解決上述問題,C語言提供了一些內存管理函數,這些內存管理函數可以按需要動態的分配內存空間,也可把不再使用的空間回收再次利用。7.2 靜態分配、動態分配靜態分配1、在程序編譯或運行過程中,按事先規定大小分配內存空間的分配方式。int a [10]2、必須事先知道所需空間的大小。3、分配在棧區或全局變量區,一般以數組的形式。4、按計劃分配。動態分配1、在程序運行過程中,根據需要大小自由分配所需空間。2、按需分配。1千鋒智能物聯網+嵌入式學科3、分配在堆區,一般使用特定的函數進行分配。7.3 動態分配函數stdlib.h1、malloc函數函數原型: void *malloc(unsigned int size);功能說明:在內存的動態存儲區(堆區)中分配一塊長度為 size字節的連續區域,用來存放類型說明符指定的類型。函數原型返回 void*指針,使用時必須做相應的強制類型轉換 ,分配的內存空間內容不確定,一般使用memset初始化。返回值:分配空間的起始地址 ( 分配成功 )NULL ( 分配失敗 )注意1、在調用 malloc之后,一定要判斷一下,是否申請內存成功。2、如果多次 malloc申請的內存,第 1次和第 2次申請的內存不一定是連續的char *p;p = (char *)malloc(20);例 1:#include#include#includeint main(){int i,*array,n;printf("請輸入您要申請的數組元素個數\n");scanf("%d",&n);array=(int *)malloc(n*sizeof(int));if(array==NULL){printf("申請內存失敗\n");return 0;}memset(array,0,n*sizeof(int));for(i=0;i{array[i]=i;}2千鋒智能物聯網+嵌入式學科for(i=0;i{printf("%d\n",array[i]);}free(array);//釋放 array 指向的內存return 0;}2、 free 函數(釋放內存函數)頭文件:#include函數定義:void free(void *ptr)函數說明:free函數釋放 ptr指向的內存。注意 ptr指向的內存必須是 malloc calloc relloc動態申請的內存例 2:char *p=(char *)malloc(100);free(p);//注意(1)、free后,因為沒有給 p賦值,所以 p還是指向原先動態申請的內存。但是內存已經不能再用了,p變成野指針了。(2)、一塊動態申請的內存只能 free一次,不能多次 free3、 calloc函數頭文件:#include函數定義:void * calloc(size_t nmemb,size_t size);size_t 實際是無符號整型,它是在頭文件中,用 typedef 定義出來的。函數的功能:在內存的堆中,申請 nmemb 塊,每塊的大小為 size個字節的連續區域函數的返回值:返回 申請的內存的首地址(申請成功)返回 NULL(申請失?。?br/>注意:malloc和 calloc函數都是用來申請內存的。區別:1) 函數的名字不一樣2) 參數的個數不一樣3) malloc申請的內存,內存中存放的內容是隨機的,不確定的,而 calloc函數申請的內存中的內容為 0例 3:調用方法char *p=(char *)calloc(3,100);3千鋒智能物聯網+嵌入式學科在堆中申請了 3塊,每塊大小為 100個字節,即 300個字節連續的區域。4、 realloc函數(重新申請內存)咱們調用 malloc和 calloc函數,單次申請的內存是連續的,兩次申請的兩塊內存不一定連續。有些時候有這種需求,即我先用 malloc或者 calloc申請了一塊內存,我還想在原先內存的基礎上挨著繼續申請內存?;蛘呶议_始時候使用 malloc或 calloc申請了一塊內存,我想釋放后邊的一部分內存。為了解決這個問題,發明了 realloc這個函數頭文件#include函數的定義:void* realloc(void *s,unsigned int newsize);函數的功能:在原先 s指向的內存基礎上重新申請內存,新的內存的大小為 new_size 個字節,如果原先內存后面有足夠大的空間,就追加,如果后邊的內存不夠用,則 relloc函數會在堆區找一個 newsize 個字節大小的內存申請,將原先內存中的內容拷貝過來,然后釋放原先的內存,最后返回新內存的地址。如果 newsize 比原先的內存小,則會釋放原先內存的后面的存儲空間,只留前面的 newsize個字節返回值:新申請的內存的首地址例 4:char *p;p=(char *)malloc(100)//咱們想在 100 個字節后面追加 50 個字節p=(char *)realloc(p,150);//p 指向的內存的新的大小為 150 個字節4千鋒智能物聯網+嵌入式學科例 5:char *p;p=(char *)malloc(100)//咱們想重新申請內存,新的大小為 50個字節p=(char *)realloc(p,50);//p 指向的內存的新的大小為 50個字節,100 個字節的后 50 個字節的存儲空間就被釋放了注意:malloc calloc relloc 動態申請的內存,只有在 free或程序結束的時候才釋放。7.4 內存泄露內存泄露的概念:申請的內存,首地址丟了,找不了,再也沒法使用了,也沒法釋放了,這塊內存就被泄露了。內存泄露 例 1:int main(){char *p;p=(char *)malloc(100);//接下來,可以用 p指向的內存了p="hello world";//p 指向別的地方了//從此以后,再也找不到你申請的 100 個字節了。則動態申請的 100個字節就被泄露了return 0;}內存泄露 例 2:void fun(){char *p;p=(char *)malloc(100);//接下來,可以用 p指向的內存了;;}int main()5千鋒智能物聯網+嵌入式學科{fun();fun();return 0;}//每調用一次 fun泄露 100個字節內存泄露 解決方案 1:void fun(){char *p;p=(char *)malloc(100);//接下來,可以用 p指向的內存了;;free(p);}int main(){fun();fun();return 0;}內存泄露 解決方案 2:char * fun(){char *p;p=(char *)malloc(100);//接下來,可以用 p指向的內存了;;return p;}int main()6千鋒智能物聯網+嵌入式學科{char *q;q=fun();//可以通過 q使用 ,動態申請的 100 個字節的內存了//記得釋放free(q);return 0;}總結:申請的內存,一定不要把首地址給丟了,在不用的時候一定要釋放內存。7千鋒智能物聯網+嵌入式學科第 1章 環境搭建第 2章 c數據類型及語句c語言特點我的第一個 c語言程序#includeint main()//這個我的第一個 c語言程序{printf(“hello world\n”); //printf是輸出打印的函數return 0;}1.#include 頭文件包含,一定要有2.每一個 c語言的程序有且只有一個 main函數,這是整個程序的開始位置3.C語言中()、[]、{}、“”、’’、都必須成對出現,必須是英文符號4.C語言中語句要以分號結束。5.//為注釋/*有志者,事竟成,破釜沉舟,百二秦關終屬楚;苦心人,天不負,臥薪嘗膽,三千越甲可吞吳*/2.1 關鍵字2.1.1 數據類型相關的關鍵字用于定義變量或者類型類型 變量名;char 、short、int 、long 、 float、double、struct、union、enum 、signed、unsigned、void1、 char 字符型 ,用 char定義的變量是字符型變量,占 1個字節char ch='a'; =為賦值號char ch1= ‘1’; 正確char ch2 = ‘1234’ ; 錯誤的2、 short 短整型 ,使用 short 定義的變量是短整型變量,占 2個字節1千鋒智能物聯網+嵌入式學科short int a=11; -32768 - ---327673、 int 整型 ,用 int定義的變量是整型變量,在 32位系統下占 4個字節,在 16平臺下占 2個字節int a=44; -20 億---20 億4、 long 長整型 用 long 定義的變量是長整型的,在 32位系統下占 4個字節long int a=66;5、 float 單浮點型 (實數),用 float定義的變量是單浮點型的實數,占 4個字節float b=3.8f;6、 double 雙浮點型 (實數),用 double定義的變量是雙浮點型的實數,占 8個字節double b=3.8;7、 struct 這個關鍵字是與結構體類型相關的關鍵字,可以用它來定義結構體類型,以后講結構體的時候再講8、 union 這個關鍵字是與共用體(聯合體)相關的關鍵字,以后再講9、 enum 與枚舉類型相關的關鍵字 以后再講10、signed 有符號(正負)的意思在定義 char 、整型(short 、int、long) 數據的時候用 signed 修飾,代表咱們定義的數據是有符號的,可以保存正數,也可以保存負數例 :signed int a=10;signed int b=-6;注意:默認情況下 signed 可以省略 即 int a=-10;//默認 a就是有符號類型的數據11、unsigned 無符號的意思在定義 char 、整型(short 、int、long) 數據的時候用 unsigned 修飾,代表咱們定義的數據是無符號類型的數據只能保存正數和 0。unsigned int a=101;unsigned int a=-101; //錯誤擴展:內存存儲char ch= ‘a’; //占 1個字節,存儲的是 970110 0001字節:內存的基本單位,8位為 1個字節計算機存儲時,只能存儲 1和 0的二進制組合,1和 0都分別占 1位字符型數據在內存中存儲的不是字符本身,而是存儲其 AsciI碼整型變量存儲的是其值的二進制unsigned int a = 97;12、void 空類型的關鍵字2千鋒智能物聯網+嵌入式學科char、int 、float 都可以定義變量void 不能定義變量,沒有 void 類型的變量void 是用來修飾函數的參數或者返回值,代表函數沒有參數或沒有返回值例:void fun(void){}代表 fun函數沒有返回值,fun函數沒有參數例 2:#include int main(){char a = 'a';short int b = 10;int c;long int d;float e;double f;printf("%d\n",sizeof(a));printf("%d\n",sizeof(b));printf("%d\n",sizeof(c));printf("%d\n",sizeof(d));printf("%d\n",sizeof(e));printf("%d\n",sizeof(f));return 0;}2.1.2 存儲相關關鍵字register、static、const、auto、extern1、register 是 寄存器的意思,用 register修飾的變量是寄存器變量,即:在編譯的時候告訴編譯器這個變量是寄存器變量,盡量將其存儲空間分配在寄存器中。注意:(1):定義的變量不一定真的存放在寄存器中。(2):cpu取數據的時候去寄存器中拿數據比去內存中拿數據要快(3):因為寄存器比較寶貴,所以不能定義寄存器數組(4):register只能修飾 字符型及整型的,不能修飾浮點型register char ch;register short int b;register int c;3千鋒智能物聯網+嵌入式學科register float d;//錯誤的(5):因為 register修飾的變量可能存放在寄存器中不存放在內存中,所以不能對寄存器變量取地址。因為只有存放在內存中的數據才有地址register int a;int *p;p=&a;//錯誤的,a可能沒有地址2、static 是靜態的意思static 可以修飾全局變量、局部變量、函數這個以后的課程中重點講解3、constconst 是常量的意思用 const修飾的變量是只讀的,不能修改它的值const int a=101;//在定義 a的時候用 const 修飾,并賦初值為 101從此以后,就不能再給 a賦值了a=111;//錯誤的const可以修飾指針,這個在以后課程中重點講解4、auto int a;和 int a是等價的,auto關鍵字現在基本不用5、extern 是外部的意思,一般用于函數和全局變量的聲明,這個在后面的課程中,會用到2.1.3 控制語句相關的關鍵字if 、else 、break、continue、for 、while、do、switch casegoto、default2.1.4 其他關鍵字sizeof、typedef、volatile1、sizeof使用來測變量、數組的占用存儲空間的大小(字節數)例 3:int a=10;int num;num=sizeof(a);2、typedef 重命名相關的關鍵字unsigned short int a = 10;U16關鍵字 ,作用是給一個已有的類型,重新起個類型名,并沒有創造一個新的類型以前大家看程序的時候見過類似的變量定義方法INT16 a;4千鋒智能物聯網+嵌入式學科U8 ch;INT32 b;大家知道,在 c語言中沒有 INT16 U8 這些關鍵字INT16 U8是用 typedef 定義出來的新的類型名,其實就是 short int 及 unsigned char的別名typedef起別名的方法:1、用想起名的類型定義一個變量short int a;2、用新的類型名替代變量名short int INT16;3、在最前面加 typedeftypedef short int INT16;4:就可以用新的類型名定義變量了INT16 b;和 short int b;//是一個效果例 4:#include //short int b;//short int INT16;typedef short int INT16;int main(int argc, char *argv[]){short int a=101;INT16 c=111;printf("a=%d\n",a);printf("c=%d\n",c);return 0;}3、volatile 易改變的意思用 volatile定義的變量,是易改變的,即告訴 cpu每次用 volatile變量的時候,重新去內存中取保證用的是最新的值,而不是寄存器中的備份。volatile 關鍵字現在較少適用volatile int a=10;擴展知識:命名規則:在 c語言中給變量和函數起名的時候,由字母、數字、下劃線構成必須以字母或者下滑線開頭例 5:5千鋒智能物聯網+嵌入式學科int a2;//正確的int a_2;//正確的int _b;//正確的int 2b;// 錯誤的int $a2;//錯誤的注意:起名的時候要求見名知意Linux風格stu_num駝峰風格StuNum大小寫敏感int Num;int num;C語言的程序結構一個完整的 C語言程序,是由一個、且只能有一個 main()函數(又稱主函數,必須有)和若干個其他函數結合而成(可選)main函數是程序的入口,即 程序從 main函數開始執行2.2 數據類型2.2.1 基本類型char 、short int 、int、long int、float、double擴展:常量和變量常量:在程序運行過程中,其值不可以改變的量例:100 ‘a’ “hello” 整型 100,125,-100,0 實型 3.14 , 0.125f,-3.789 字符型 ‘a’,‘b’,‘2’ 字符串 “a”,“ab”,“1232”變量:其值可以改變的量被稱為變量int a=100;a=101;字符數據 字符常量:直接常量:用單引號括起來,如:'a'、'b'、’0’等.轉義字符:以反斜杠“\”開頭,后跟一個或幾個字符、如'\n','\t'等,分別代表換行、橫向跳格.‘\\’表示的是\ ‘%%’ ‘\’’6千鋒智能物聯網+嵌入式學科 字符變量:用 char定義,每個字符變量被分配一個字節的內存空間字符值以 ASCII碼的形式存放在變量的內存單元中;注:char a;a = 'x';a變量中存放的是字符'x'的 ASCII :120即 a=120跟 a='x'在本質上是一致的.例 6:#include int main(int argc, char *argv[]){char a='x';char b=120;printf("a=%c\n",a);printf("b=%c\n",b);return 0;}ASCII 碼表例 7:#include int main(int argc, char *argv[]){unsigned int i;for(i=0;i<=255;i++){printf("%d %c ",i,i);if(i%10==0)printf("\n");}return 0;}字符串常量是由雙引號括起來的字符序列,如“CHINA”、”哈哈哈”“C program”,“$12.5”等都是合法的字符串常量.字符串常量與字符常量的不同‘a’為字符常量,”a”為字符串常量每個字符串的結尾,編譯器會自動的添加一個結束標志位'\0',即“a”包含兩個字符‘a’和’\0’7千鋒智能物聯網+嵌入式學科整型數據 整型常量:(按進制分):十進制: 以正常數字 1-9開頭,如 457 789八進制: 以數字 0開頭,如 0123十六進制:以 0x開頭,如 0x1ea=10,b=11,c=12, d=13,e=14,f=15 整型變量: 有/無符號短整型(un/signed) short(int) 2個字節 有/無符號基本整型(un/signed) int 4個字節 有/無符號長整型(un/signed) long (int) 4個字節 (32位處理器)實型數據(浮點型) 實型常量 實型常量也稱為實數或者浮點數十進制形式: 由數字和小數點組成:0.0、0.12、5.0指數形式: 123e3代表 123*10的三次方123e-3 不以 f結尾的常量是 double類型 以 f結尾的常量(如 3.14f)是 float類型 實型變量單精度(float)和雙精度(double)3.1415926753456float型: 占 4字節,7位有效數字,指數-37到 383333.333 33double型: 占 8字節,16位有效數字,指數-307到 308格式化輸出字符:%d 十進制有符號整數 %u 十進制無符號整數%x, 以十六進制表示的整數 %o 以八進制表示的整數%f float型浮點數 %lf double型浮點數%e 指數形式的浮點數%s 字符串 %c 單個字符%p 指針的值特殊應用:%3d %03d %-3d %5.2f%3d:要求寬度為 3位,如果不足 3位,前面空格補齊;如果足夠 3位,此語句無效%03d:要求寬度為 3位,如果不足 3位,前面 0補齊;如果足夠 3位,此語句無效%-3d: 要求寬度為 3位,如果不足 3位,后面空格補齊;如果足夠 3位,此語句無效%.2f:小數點后只保留 2位8千鋒智能物聯網+嵌入式學科2.2.2 構造類型概念:由若干個相同或不同類型數據構成的集合,這種數據類型被稱為構造類型例:int a[10];數組、結構體、共用體、枚舉2.2.3 類型轉換數據有不同的類型,不同類型數據之間進行混合運算時必然涉及到類型的轉換問題.轉換的方法有兩種: 自動轉換:遵循一定的規則,由編譯系統自動完成. 強制類型轉換:把表達式的運算結果強制轉換成所需的數據類型 自動轉換的原則:1、占用內存字節數少(值域小)的類型,向占用內存字節數多(值域大)的類型轉換,以保證精度不降低.2、轉換方向:1) 當表達式中出現了 char 、short int 、int 類型中的一種或者多種,沒有其他類型了參加運算的成員全部變成 int類型的參加運算,結果也是 int類型的例 8:#include int main(int argc, char *argv[]){printf("%d\n",5/2);return 0;}2) 當表達式中出現了帶小數點的實數,參加運算的成員全部變成 double類型的參加運算,結果9千鋒智能物聯網+嵌入式學科也是 double型。例 9:#include int main(int argc, char *argv[]){printf("%lf\n",5.0/2);return 0;}3) 當表達式中有有符號數 也有無符號數,參加運算的成員變成無符號數參加運算結果也是無符號數.(表達式中無實數)例 10:#include int main(int argc, char *argv[]){int a=-8;unsigned int b=7;if(a+b>0){printf("a+b>0\n");}else{printf("a+b<=0\n");}printf("%x\n",(a+b));printf("%d\n",(a+b));return 0;}4) 在賦值語句中等號右邊的類型自動轉換為等號左邊的類型例 11:#include int main(int argc, char *argv[]){int a;float b=5.8f;//5.8 后面加 f 代表 5.8 是 float 類型,不加的話,認為是 double 類型a=b;printf("a=%d\n",a);10千鋒智能物聯網+嵌入式學科return 0;}5) 注意自動類型轉換都是在運算的過程中進行臨時性的轉換,并不會影響自動類型轉換的變量的值和其類型例 12:#include int main(int argc, char *argv[]){int a;float b=5.8f;//5.8 后面加 f 代表 5.8 是 float 類型,不加的話,認為是 double 類型a=b;printf("a=%d\n",a);printf("b=%f\n",b);//b 的類型依然是 float 類型的,它的值依然是 5.8return 0;}強制轉換:通過類型轉換運算來實現(類型說明符) (表達式)功能:把表達式的運算結果強制轉換成類型說明符所表示的類型例如:(float)a; // 把 a的值轉換為實型(int)(x+y); // 把 x+y的結果值轉換為整型注意:類型說明符必須加括號例 13:#include int main(int argc, char *argv[]){float x=0;int i=0;x=3.6f;i = x;i = (int)x;printf("x=%f,i=%d\n",x,i);return 0;}說明:無論是強制轉換或是自動轉換,都只是為了本次運算的需要,而對變量的數據長度進行的臨時性轉換,而不改變數據定義的類型以及它的值11千鋒智能物聯網+嵌入式學科2.2.4 指針2.3 運算符2.3.1 運算符用算術運算符將運算對象(也稱操作數)連接起來的、符合C語法規則的式子,稱為C算術表達式運算對象包括常量、變量、函數等例如: a* b / c-1.5 + 'a'2.3.2 運算符的分類:1、雙目運算符:即參加運算的操作數有兩個例:+a+b2、單目運算符:參加運算的操作數只有一個++自增運算符 給變量值+1--自減運算符int a=10;a++;3、三目運算符:即參加運算的操作數有 3個() ():()2.3.3 算數運算符+ - * / % += -= *= /= %=10%3 表達式的結果為 1復合運算符:a += 3 相當于 a=a+3a*=6+8 相當于 a=a*(6+8)2.3.4 關系運算符(>、<、= =、>=、<=、!= )!=為不等于一般用于判斷條件是否滿足或者循環語句2.3.5 邏輯運算符1、&& 邏輯與兩個條件都為真,則結果為真if((a>b) && (aif(b2、|| 邏輯或兩個條件至少有一個為真,則結果為真if((a>b) || (a12千鋒智能物聯網+嵌入式學科3、! 邏輯非if(!(a>b)){}2.3.6 位運算符十進制轉二進制:方法 除 2求余法例: 123 十進制 轉二進制正數在內存中以原碼形式存放,負數在內存中以補碼形式存放正數的 原碼=反碼=補碼原碼:將一個整數,轉換成二進制,就是其原碼。如單字節的 5的原碼為:0000 0101;-5的原碼為 1000 0101。反碼:正數的反碼就是其原碼;負數的反碼是將原碼中,除符號位以外,每一位取反。如單字節的 5的反碼為:0000 0101;-5的反碼為 1111 1010。補碼:正數的補碼就是其原碼;負數的反碼+1就是補碼。如單字節的 5的補碼為:0000 0101;-5的補碼為 1111 1011。在計算機中,正數是直接用原碼表示的,如單字節 5,在計算機中就表示為:0000 0101。負數用補碼表示,如單字節-5,在計算機中表示為 1111 1011。無論是正數還是負數,編譯系統都是按照內存中存儲的內容進行位運算。1、&按位 與任何值與 0得 0,與 1保持不變使某位清 00101 1011&1011 0100---------------0001 00002、| 按位或任何值或 1得 1,或 0保持不變0101 0011 |1011 01001111 01113、~ 按位取反1變 0,0變 10101 1101 ~13千鋒智能物聯網+嵌入式學科1010 00104、^ 按位異或相異得 1,相同得 01001 1100 ^0101 10101100 01105、位移>>右移<< 左移注意右移分:邏輯右移、算數右移(1)、右移邏輯右移 高位補 0,低位溢出算數右移 高位補符號位,低位溢出 (有符號數)A)、邏輯右移低位溢出、高位補 00101 1010 >>30000 1011B)、算數右移:對有符號數來說低位溢出、高位補符號位。1010 1101 >> 31111 010 10101 0011 >>30000 101 0總結 右移:1、邏輯右移 高位補 0,低位溢出注:無論是有符號數還是無符號數都是高位補 0,低位溢出2、算數右移 高位補符號位,低位溢出 (有符號數)注:對無符號數來說,高位補 0,低位溢出對有符號數來說,高位補符號位,低位溢出在一個編譯系統中到底是邏輯右移動,還是算數右移,取決于編譯器判斷右移是邏輯右移還是算數右移#include int main(int argc, char *argv[]){printf("%d\n",-1>>3);return 0;}如果結果還是-1 證明是算數右移14千鋒智能物聯網+嵌入式學科(2) 左移<< 高位溢出,低位補 05<<10000 01010000 10102.3.7 條件運算符號() ():()A B:C;如果?前邊的表達式成立,整個表達式的值,是?和:之間的表達式的結果否則是:之后的表達式的結果例 14:#include int main(int argc, char *argv[]){int a;a=(3<5) (8):(9);printf("a=%d\n",a);return 0;}2.3.8 逗號預算符 ,(),()例 15:#include int main(int argc, char *argv[]){int num;num=(5,6);printf("%d\n",num);return 0;}注意逗號運算符的結果是,后邊表達式的結果2.3.9 自增自減運算符i++ i--運算符在變量的后面,在當前表達式中先用 i的值,下條語句的時候 i的值改變例 16:#include int main(){15千鋒智能物聯網+嵌入式學科int i=3;int num;num=i++;printf("num=%d,i=%d\n",num,i);//num=3 ,i=4return 0;}++i 先加 ,后用例 17:#include int main(){int i=3;int num;num=++i;printf("num=%d,i=%d\n",num,i);//num=4,i=4return 0;}例 18:#include int main(int argc, char *argv[]){int i=3;int num;num = (i++)+(i++)+(i++);printf("num=%d\n",num);return 0;}例 19:#include int main(int argc, char *argv[]){int i=3;int num;16千鋒智能物聯網+嵌入式學科num = (++i)+(++i)+(++i);printf("num=%d\n",num);return 0;}2.3.10 運算符優先級及結合性運算符優先級在表達式中按照優先級先后進行運算,優先級高的先于優先級低的先運算。優先級一樣的按結合性來運算int a;a=2+5+3*4-6運算符結合性左結合性:從左向右運算int a;a=2+3+9+10;右結合性:從右向左運算int a,b,c,d;a=b=c=d=100;優先級和結合性表:17千鋒智能物聯網+嵌入式學科注:建議當表達式比較復雜的時候,用()括起來,括號的優先級最高,優先算括號里的。這樣也防止寫錯表達式。int a;a=(2+3)*6+(9*3)+10;2.4 控制語句2.4.1 選擇控制語句1、 if語句形式:1) if(條件表達式){//復合語句,若干條語句的集合語句 1;語句 2;}如果條件成立執行大括號里的所有語句,不成立的話大括號里的語句不執行例 20:#includeint main(){int a=10;if(a>5){printf("a>5\n");}18千鋒智能物聯網+嵌入式學科return 0;}2) if(條件表達式){}else{}if else語句的作用是,如果 if的條件成立,執行 if后面{}內的語句,否則執行 else后的語句例 21:#includeint main(){int a=10;if(a>5){printf("a>5\n");}else{printf("a<=5\n");}return 0;}注意 if和 else之間只能有一條語句,或者有一個復合語句,否則編譯會出錯例 22:if()語句 1;語句 2;else語句 3;語句 4;錯誤:if和 else之間只能有一條語句,如果有多條語句的話加大括號例 23:if()19千鋒智能物聯網+嵌入式學科{語句 1;語句 2;}else{語句 3;語句 4;}正確3) if(條件表達式 1){}else if(條件表達式 2){}else if(條件表達式 3){}else{}在判斷的時候,從上往下判斷,一旦有成立的表達式,執行對應的復合語句,下邊的就不再判斷了,各個條件判斷是互斥的例 24:#include int main(void){char ch;float score = 0;printf("請輸入學生分數:\n");scanf("%f",&score);if(score<0 || score >100){printf("你所輸入的信息有錯\n");return 0;}else if( score<60)20千鋒智能物聯網+嵌入式學科{ch = 'E';}else if ( score < 70 ){ch = 'D';}else if ( score < 80 ){ch = 'C';}else if ( score < 90 ){ch = 'B';}else{ch = 'A';}printf("成績評定為:%c\n",ch);return 0;}2、 switch 語句switch(表達式)//表達式只能是字符型或整型的(short int int long int){case 常量表達式1:語句1;break;case 常量表達式2:語句2;break;default:語句3;break;}注意:break的使用例 25:#include int main(int argc, char *argv[])21千鋒智能物聯網+嵌入式學科{int n;printf("請輸入一個 1~7 的數\n");scanf_s("%d",&n);switch(n){case 1:printf("星期一\n");break;case 2:printf("星期二\n");break;case 3:printf("星期三\n");break;case 4:printf("星期四\n");break;case 5:printf("星期五\n");break;case 6:printf("星期六\n");break;case 7:printf("星期天\n");break;default:printf("您的輸入有誤,請輸入 1~7 的數\n");break;}return 0;}2.4.2 循環控制語句1、 for 循環for(表達式 1;表達式 2;表達式 3){//復合語句,循環體22千鋒智能物聯網+嵌入式學科}第一次進入循環的時候執行表達式 1,表達式 1只干一次,表達式 2,是循環的條件,只有表達式 2為真了,才執行循環體,也就是說每次進入循環體之前要判斷表達式 2是否為真。每次執行完循環體后,首先執行表達式 3例 25:for 循環求 1~100 的和#include int main(void){int i;int sum=0;for(i=1;i<=100;i++){sum = sum+i;}printf("sum=%d\n",sum);return 0;}例 26:#include 23千鋒智能物聯網+嵌入式學科int main(int argc, char *argv[]){int i,j;for(i=1;i<=9;i++){for( j=1;j<=i;j++){printf("%d*%d=%d ",j,i,j*i);}printf("\n");}return 0;}2、 while 循環1) 形式 1:while(條件表達式){//循環體,復合語句}進入 while循環的時候,首先會判斷條件表達式是否為真,為真進入循環體,否則退出循環例 27:#include int main(void){int i=1;int sum=0;while(i<=100){sum = sum+i;i++;}printf("sum=%d\n",sum);return 0;}2) 形式 2 : dodo{//循環體24千鋒智能物聯網+嵌入式學科}while(條件表達式);先執行循環體里的代碼,然后去判斷條件表達式是否為真,為真再次執行循環體,否則退出循環例 28:#include int main(void){int i=1;int sum=0;do{sum = sum+i;i++;}while(i<=100);printf("sum=%d\n",sum);return 0;}形式 1和形式 2的區別是,形式 1先判斷在執行循環體,形式 2先執行循環體,再判斷break 跳出循環continue 結束本次循環,進入下一次循環例 29:#include int main(void){int i;int sum=0;for(i=1;i<=100;i++){if(i==10)break;//將 break 修改成 continue看效果sum = sum+i;}printf("sum=%d\n",sum);return 0;}return 返回函數的意思。結束 return所在的函數,25千鋒智能物聯網+嵌入式學科在普通函數中,返回到被調用處,在 main函數中的話,結束程序3、 goto例 30:#include int main(int argc, char *argv[]){printf("test000000000000000000\n");printf("test1111111111111111\n");goto tmp;printf("test222222222222222222\n");printf("test3333333333333333\n");printf("test444444444444444444444\n");printf("test55555555555555555555\n");tmp:printf("test66666666666666666\n");return 0;}26風干于鋒教百千鋒智能物聯網+嵌入式學科第1章環境搭建1.1 Visual Studio軟件安裝1、軟件下載路徑鏈接:https:/pan.baidu.,com/s/1LaLe7 amFWF500WKc0 sTmnA提取碼:i2sg2、安裝雙擊exe可執行程序vs communiy_712910196.1584880824.ex6Visual Studio Installer開始之前,我們需要設置某些選項,以便你配置安裝。若要了解有關隱私的詳細信息,請參閱Microsoft隱私聲明。繼續即表示你同意Microsoft軟件許可條款。繼續O)做真實的自己,用良心做教有風鋒教百千鋒智能物聯網+嵌入式學科Visual Studio Installer稍等片刻正在提取文件。正在下裁:2.99MB/74.25MB860.45KB/秒正在安裝稍等一會取消(qEL一M.iammnly一1hL表治雪Aehr三d安裝詳知信已 11ab女女>Voual5udie民.心ev快刀C+中的夏面環發 c+rT: =60g打C+C:1年Tn 夏=子w2生太⊥C+nG打k52,牛右,1:3★hmI BDOst.Tect2h G03eT15aa Lies Shaw日2測m季m云Visual Studio Installer感可用Visual Studin Community 201T發人員新聞工E一8生:7M1195n(1:Anncurcirg NET G FrewH性等待一時回Miae3i.w3山u時Hn.in&cmmniyANLI Cete updilss in.Ns Wavie 1 安片NETGPreeNow avable an行明1+=月匠黑aierairgyaiml4edy wcre codted to zrneunse our Re ease4只月肛8重Z棗巴代機AE衛.下M+之30712做真實的自己,用良心做教育2風干鋒教百千鋒智能物聯網+嵌入式學科1.2注冊A)創建一個賬戶Visual Studio登錄Visual Studio巖設活同路設空使用ireShare時r作與A2山re深天數元B)輸入一個電子郵箱地址Microsoft創建帳戶someone@改為使用電話號碼獲取新的電子郵件地址下一步C)設置一個登錄密碼做真實的自己,用良心做教育3千鋒智能物聯網+嵌入式學科第 1章 環境搭建第 2章 c數據類型及語句第 3章 數組第 4章 函數第 5章 預處理、動態庫、靜態庫第 6章 指針第 7章 動態內存申請第 8章 字符串處理函數第 9章 結構體、共用體、枚舉9.1 結構體概念在程序開發的時候,有些時候我們需要將不同類型的數據組合成一個有機的整體,以便于引用。如:一個學生有學號/姓名/性別/年齡/地址等屬性int num;char name[20];char sex;int age;char addr[30];1千鋒智能物聯網+嵌入式學科顯然單獨定義以上變量比較繁瑣,數據不便于管理,所以在 C語言中就發明了結構體類型。結構體是一種構造數據類型。前面學過一種構造類型——數組:構造類型:不是基本類型的數據結構也不是指針類型,它是若干個相同或不同類型的數據構成的集合描述一組具有相同類型數據的有序集合,用于處理大量相同類型的數據運算--數組結構體類型的概念:結構體是一種構造類型的數據結構,是一種或多種基本類型或構造類型的數據的集合。9.2 結構體類型定義結構體類型的定義方法咱們在使用結構體之前必須先有類型,然后用類型定義數據結構這個類型相當于一個模具(1).先定義結構體類型,再去定義結構體變量struct 結構體類型名{成員列表};例 1:struct stu{int num;char name[20];char sex;};//有了結構體類型后,就可以用類型定義變量了struct stu lucy,bob,lilei;//定義了三個 struct stu類型的變量每個變量都有三個成員,分別是 num name sex咱們可以暫時認為結構體變量的大小是它所有成員之和(2).在定義結構體類型的時候順便定義結構體變量,以后還可以定義結構體變量struct 結構體類型名{成員列表;}結構體變量 1,變量 2;2千鋒智能物聯網+嵌入式學科struct 結構體類型名 變量 3,變量 4;例 2:struct stu{int num;char name[20];char sex;}lucy,bob,lilei;struct stu xiaohong,xiaoming;3.在定義結構體類型的時候,沒有結構體類型名,順便定義結構體變量,因為沒有類型名,所以以后不能再定義相關類型的數據了struct {成員列表;}變量 1,變量 2;例 3:struct {int num;char name[20];char sex;}lucy,bob;以后沒法再定義這個結構體類型的數據了,因為沒有類型名4.最常用的方法通常咱們將一個結構體類型重新起個類型名,用新的類型名替代原先的類型步驟 1:先用結構體類型定義變量struct stu{int num;char name[20];char sex;}bob;步驟 2:新的類型名替代變量名struct stu{int num;char name[20];3千鋒智能物聯網+嵌入式學科char sex;}STU;步驟 3:在最前面加 typedeftypedef struct stu{int num;char name[20];char sex;}STU;注意:步驟 1和步驟 2,在草稿上做的,步驟 3是程序中咱們想要的代碼以后 STU 就相當于 struct stuSTU lucy;和 struct stu lucy;是等價的。9.3 結構體變量的定義初始化及使用1、結構體變量的定義和初始化結構體變量,是個變量,這個變量是若干個相同或不同數據構成的集合注:(1):在定義結構體變量之前首先得有結構體類型,然后再定義變量(2):在定義結構體變量的時候,可以順便給結構體變量賦初值,被稱為結構體的初始化(3):結構體變量初始化的時候,各個成員順序初始化例 4:struct stu{int num;char name[20];char sex;};struct stu boy;struct stu lucy={101,"lucy",'f'};2、結構體變量的使用定義了結構體變量后,要使用變量(1).結構體變量成員的引用方法4千鋒智能物聯網+嵌入式學科結構體變量.成員名例 5:struct stu{int num;char name[20];char sex;};struct stu bob;bob.num=101;//bob 是個結構體變量,但是 bob.num 是個 int 類型的變量bob.name 是個字符數組,是個字符數組的名字,代表字符數組的地址,是個常量bob.name ="bob";//是不可行,是個常量strcpy(bob.name,"bob");例 6:#include struct stu{int num;char name[20];int score;char *addr;};int main(int argc, char *argv[]){struct stu bob;printf("%d\n",sizeof(bob));printf("%d\n",sizeof(bob.name));printf("%d\n",sizeof(bob.addr));return 0;}(2).結構體成員多級引用例 7:#include struct date{int year;5千鋒智能物聯網+嵌入式學科int month;int day;};struct stu{int num;char name[20];char sex;struct date birthday;};int main(int argc, char *argv[]){struct stu lilei={101,"lilei",'m'};lilei.birthday.year=1986;lilei.birthday.month=1;lilei.birthday.day=8;printf("%d %s %c\n",lilei.num,lilei.name,lilei.sex);printf("%d %d %d\n",lilei.birthday.year,lilei.birthday.month,lilei.birthday.day);return 0;}3、相同類型的結構體變量可以相互賦值注意:必須是相同類型的結構體變量,才能相互賦值。例 8:#include struct stu{int num;char name[20];char sex;};int main(int argc, char *argv[]){struct stu bob={101,"bob",'m'};struct stu lilei;lilei=bob;printf("%d %s %c\n",lilei.num,lilei.name,lilei.sex);return 0;}6千鋒智能物聯網+嵌入式學科9.4 結構體數組結構體數組是個數組,由若干個相同類型的結構體變量構成的集合1、結構體數組的定義方法struct 結構體類型名 數組名[元素個數];例 9:struct stu{int num;char name[20];char sex;};struct stu edu[3];//定義了一個 struct stu 類型的結構體數組 edu,這個數組有 3個元素分別是 edu[0] 、edu[1]、edu[2]1、結構體數組元素的引用 數組名[下標]2、數組元素的使用edu[0].num =101;//用 101給 edu數組的第 0個結構體變量的 num賦值strcpy(edu[1].name,"lucy");例 10:#include typedef struct student{int num;char name[20];float score;}STU;STU edu[3]={{101,"Lucy",78},{102,"Bob",59.5},{103,"Tom",85}};int main(){int i;float sum=0;for(i=0;i<3;i++){7千鋒智能物聯網+嵌入式學科sum+=edu[i].score;}printf("平均成績為%f\n",sum/3);return 0;}9.5 結構體指針即結構體的地址,結構體變量存放內存中,也有起始地址咱們定義一個變量來存放這個地址,那這個變量就是結構體指針變量。結構體指針變量也是個指針,既然是指針在 32位環境下,指針變量的占 4個字節,存放一個地址編號。1、結構體指針變量的定義方法:struct 結構體類型名 * 結構體指針變量名;struct stu{int num;char name[20];};struct stu * p;//定義了一個 struct stu *類型的指針變量變量名 是 p,p占 4個字節,用來保存結構體變量的地址編號struct stu boy;p=&boy;訪問結構體變量的成員方法:例 11:boy.num=101;//可以,通過 結構體變量名.成員名(*p).num=101;//可以,*p 相當于 p指向的變量 boyp->num=101;//可以,指針->成員名通過結構體指針來引用指針指向的結構體的成員,前提是指針必須先指向一個結構體變量。結構體指針應用場景:(1):保存結構體變量的地址例 12:typedef struct stu{int num;8千鋒智能物聯網+嵌入式學科char name[20];float score;}STU;int main(){STU *p,lucy;p=&lucy;p->num=101;strcpy(p->name,"baby");//p->name="baby";//錯誤,因為 p->name 相當于 lucy.name 是個字符數組的名字,是個常量}(2):傳 結構體變量的地址例 13:#include#includetypedef struct stu{int num;char name[20];float score;}STU;void fun(STU *p){p->num=101;(*p).score=87.6;strcpy(p->name,"lucy");}int main(){STU girl;fun(&girl);printf("%d %s %f\n",girl.num,girl.name,girl.score);return 0;}(3):傳結構體數組的地址結構體數組,是由若干個相同類型的結構體變量構成的集合。存放在內存里,也有起始地址,其實就是第 0個結構體變量的地址。9千鋒智能物聯網+嵌入式學科例 14:#include#includetypedef struct stu{int num;char name[20];float score;}STU;void fun(STU *p){p[1].num=101;(*(p+1)).score=88.6;}int main(){STU edu[3];fun(edu);printf("%d %f\n",edu[1].num,edu[1].score);return 0;}注意:(1):結構體變量的地址編號和結構體第一個成員的地址編號相同,但指針的類型不同例 15:#include struct stu{int num;char name[20];int score;};int main(int argc, char *argv[]){struct stu bob;printf("%p\n",&bob);printf("%p\n",&(bob.num));return 0;10千鋒智能物聯網+嵌入式學科}(2):結構體數組的地址就是結構體數組中第 0個元素的地址例 16:#include struct stu{int num;char name[20];int score;};int main(int argc, char *argv[]){struct stu edu[3];printf("%p\n",edu);//struct stu *printf("%p\n",&(edu[0]));//struct stu *printf("%p\n",&(edu[0].num));//int *return 0;}9.6 結構體內存分配1、結構體內存分配之前講過結構體變量大小是,它所有成員的大小之和。因為結構體變量是所有成員的集合。例 17:#includestruct stu{int num;int age;}lucy;int main(){printf("%d\n",sizeof(lucy));//結果為 8return 0;}但是在實際給結構體變量分配內存的時候,是規則的例 18:11千鋒智能物聯網+嵌入式學科#includestruct stu{char sex;int age;}lucy;int main(){printf("%d\n",sizeof(lucy));//結果為 8???return 0;}規則 1:以多少個字節為單位開辟內存給結構體變量分配內存的時候,會去結構體變量中找基本類型的成員哪個基本類型的成員占字節數多,就以它大大小為單位開辟內存,在 gcc中出現了 double類型的,例外(1):成員中只有 char型數據 ,以 1字節為單位開辟內存。(2):成員中出現了 short int 類型數據,沒有更大字節數的基本類型數據。以 2字節為單位開辟內存(3):出現了 int float 沒有更大字節的基本類型數據的時候以 4字節為單位開辟內存。(4):出現了 double類型的數據情況 1:在 vc6.0和 Visual Studio中里,以 8字節為單位開辟內存。情況 2:在 Linux 環境 gcc 里,以 4字節為單位開辟內存。無論是那種環境,double型變量,占 8字節。注意:如果在結構體中出現了數組,數組可以看成多個變量的集合。如果出現指針的話,沒有占字節數更大的類型的,以 4字節為單位開辟內存。在內存中存儲結構體成員的時候,按定義的結構體成員的順序存儲。例 19:struct stu{char sex;int age;}lucy;lucy 的大小是 4的倍數。規則 2:字節對齊(1):char 1字節對齊 ,即存放 char型的變量,內存單元的編號是 1的倍數即可。(2):short int 2字節對齊 ,即存放 short int 型的變量,起始內存單元的編號是 2的倍數即可。12千鋒智能物聯網+嵌入式學科(3):int 4字節對齊 ,即存放 int 型的變量,起始內存單元的編號是 4的倍數即可(4):long int 在 32位平臺下,4字節對齊 ,即存放 long int 型的變量,起始內存單元的編號是 4的倍數即可(5):float 4字節對齊 ,即存放 float 型的變量,起始內存單元的編號是 4的倍數即可(6):doublea.vc6.0和 Visual Studio 環境下8字節對齊,即存放 double型變量的起始地址,必須是 8的倍數,double變量占 8字節b.gcc環境下4字節對齊,即存放 double型變量的起始地址,必須是 4的倍數,double 變量占 8字節。注意 3:當結構體成員中出現數組的時候,可以看成多個變量。注意 4:開辟內存的時候,從上向下依次按成員在結構體中的位置順序開辟空間例 20://temp 8個字節#includestruct stu{char a;short int b;int c;}temp;int main(){printf("%d\n",sizeof(temp));printf("%p\n",&(temp.a));printf("%p\n",&(temp.b));printf("%p\n",&(temp.c));return 0;}結果分析:a的地址和 b的地址差 2個字節b的地址和 c的地址差 2個字節例 21:temp 的大小為 12 個字節#includestruct stu{char a;int c;short int b;}temp;int main(){13千鋒智能物聯網+嵌入式學科printf("%d\n",sizeof(temp));printf("%p\n",&(temp.a));printf("%p\n",&(temp.b));printf("%p\n",&(temp.c));return 0;}結果分析:a和 c的地址差 4個字節c和 b的地址差 4個字節例 22:struct stu{char buf[10];int a;}temp;//temp 占 16 個字節例 23:在 vc和 Visual Studio 中占 16 個字節 a和 b的地址差 8個字節在 gcc 中占 12 個字節 a和 b的地址差 4個字節#includestruct stu{char a;double b;}temp;int main(){printf("%d\n",sizeof(temp));printf("%p\n",&(temp.a));printf("%p\n",&(temp.b));return 0;}為什么要有字節對齊?用空間來換時間,提高 cpu讀取數據的效率struct stu{char a;int b;}boy;14千鋒智能物聯網+嵌入式學科存儲方式 1 存儲方式 2指定對齊原則:使用#pragma pack改變默認對齊原則格式:#pragma pack (value)時的指定對齊值value。注意:1.value只能是:1 2 4 8等2.指定對齊值與數據類型對齊值相比取較小值說明:咱們指定一個value(1):以多少個字節為單位開辟內存結構體成員中,占字節數最大的類型長度和value比較,取較小值,為單位開辟內存例 24:#pragma pack(2)struct stu{char a;int b;} ;以2個字節為單位開辟內存#include#pragma pack(2)struct stu{char a;int b;}temp;int main(){printf("%d\n",sizeof(temp));printf("%p\n",&(temp.a));printf("%p\n",&(temp.b));15千鋒智能物聯網+嵌入式學科return 0;}temp的大小為6個字節a和b的地址差2個字節例 25:#pragma pack(8)struct stu{char a;int b;} ;以4個字節為單位開辟內存#include#pragma pack(8)struct stu{char a;int b;}temp;int main(){printf("%d\n",sizeof(temp));printf("%p\n",&(temp.a));printf("%p\n",&(temp.b));return 0;}temp的大小為8個字節a和b的地址差4個字節(2):字節對齊結構體成員中成員的對齊方法,各個默認的對齊字節數和value相比,取較小值例 26:#include#pragma pack(2)struct stu{char a;int b;}temp;int main(){16千鋒智能物聯網+嵌入式學科printf("%d\n",sizeof(temp));printf("%p\n",&(temp.a));printf("%p\n",&(temp.b));return 0;}b成員是2字節對齊,a和b的地址差2個字節例 27:#include#pragma pack(8)struct stu{char a;int b;}temp;int main(){printf("%d\n",sizeof(temp));printf("%p\n",&(temp.a));printf("%p\n",&(temp.b));return 0;}a和b都按原先的對齊方式存儲如:如果指定對齊值:設為1:則short、int、float等均為1設為 2:則 char仍為 1,short為 2,int 變為 29.7 位段一、位段在結構體中,以位為單位的成員,咱們稱之為位段(位域)。struct stu{unsigned int a:2;unsigned int b:6;unsigned int c:4;unsigned int d:4;unsigned int i;} data;注意:不能對位段成員取地址17千鋒智能物聯網+嵌入式學科例 28:#includestruct stu{unsigned int a:2;unsigned int b:6;unsigned int c:4;unsigned int d:4;unsigned int i;} data;int main(){printf("%d\n",sizeof(data));printf("%p\n",&data);printf("%p\n",&(data.i));return 0;}位段注意:1、對于位段成員的引用如下:data.a =2賦值時,不要超出位段定義的范圍;如段成員a定義為2位,最大值為3,即(11)2所以data.a =5,就會取5的低兩位進行賦值 1012、位段成員的類型必須指定為整型或字符型3、一個位段必須存放在一個存儲單元中,不能跨兩個單元第一個單元空間不能容納下一個位段,則該空間不用,而從下一個單元起存放該位段位段的存儲單元:(1):char型位段 存儲單元是 1個字節(2):short int型的位段存儲單元是 2個字節(3):int的位段,存儲單元是 4字節(4):long int的位段,存儲單元是 4字節struct stu{char a:7;char b:7;char c:2;}temp;//占 3字節,b不能跨 存儲單元存儲例 29:#include18千鋒智能物聯網+嵌入式學科struct stu{char a:7;char b:7;char c:2;}temp;int main(){printf("%d\n",sizeof(temp));return 0;}結果為:3 ,證明位段不能跨其存儲單元存儲注意:不能 取 temp.b的地址,因為 b可能不夠 1字節,不能取地址。4、位段的長度不能大于存儲單元的長度(1):char型位段不能大于 8位(2):short int型位段不能大于 16位(3):int的位段,位段不能大于 32位(4):long int的位段,位段不能大于 32位例 30:#includestruct stu{char a:9;char b:7;char c:2;}temp;int main(){printf("%d\n",sizeof(temp));return 0;}分析:編譯出錯,位段 a不能大于其存儲單元的大小5、如一個段要從另一個存儲單元開始,可以定義:unsigned char a:1;unsigned char b:2;unsigned char :0;unsigned char c:3;(另一個單元)由于用了長度為 0的位段,其作用是使下一個位段從19千鋒智能物聯網+嵌入式學科下一個存儲單元開始存放將 a、b存儲在一個存儲單元中,c另存在下一個單元例:31#includestruct stu{unsigned char a:1;unsigned char b:2;unsigned char :0;unsigned char c:3;};int main(){struct m_type temp;printf("%d\n",sizeof(temp));return 0;}6、可以定義無意義位段,如:unsigned a: 1;unsigned : 2;unsigned b: 3;9.8 共用體1:共用體和結構體類似,也是一種構造類型的數據結構。既然是構造類型的,咱們得先定義出類型,然后用類型定義變量。定義共用體類型的方法和結構體非常相似,把 struct 改成 union 就可以了。在進行某些算法的時候,需要使幾種不同類型的變量存到同一段內存單元中,幾個變量所使用空間相互重疊這種幾個不同的變量共同占用一段內存的結構,在C語言中,被稱作“共用體”類型結構共用體所有成員占有同一段地址空間共用體的大小是其占內存長度最大的成員的大小例 33:typedef struct data{short int i;char ch;float f;}DATA;DATA temp1;20千鋒智能物聯網+嵌入式學科結構體變量 temp1最小占 7個字節(不考慮字節對齊)例 34:typedef union data{short int i;char ch;float f;}DATA;DATA temp2;共用體 temp2占 4個字節,即 i、ch、f共用 4個字節#includetypedef union data{short int i;char ch;float f;}DATA;int main(){DATA temp2;printf("%d\n",sizeof(temp2));printf("%p\n",&temp2);printf("%p\n",&(temp2.i));printf("%p\n",&(temp2.ch));printf("%p\n",&(temp2.f));return 0;}結果:temp2的大小為 4個字節,下面幾個地址都是相同的,證明了共用體的各個成員占用同一塊內存。共用體的特點:1、同一內存段可以用來存放幾種不同類型的成員,但每一瞬時只有一種起作用2、共用體變量中起作用的成員是最后一次存放的成員,在存入一個新的成員后原有的成員的值會被覆蓋3、共用體變量的地址和它的各成員的地址都是同一地址4、共用體變量的初始化union data a={123}; 初始化共用體只能為第一個成員賦值,不能給所有成員都賦初值例 35:#includetypedef union data{21千鋒智能物聯網+嵌入式學科unsigned char a;unsigned int b;}DATA;int main(){DATA temp;temp.b=0xffffffff;printf("temp.b = %x\n",temp.b);temp.a=0x0d;printf("temp.a= %x\n",temp.a);printf("temp.b= %x\n",temp.b);return 0;}結果:temp.b = fffffffftemp.a= dtemp.b= ffffff0d9.9 枚舉將變量的值一一列舉出來,變量的值只限于列舉出來的值的范圍內枚舉類型也是個構造類型的,類型定義類似結構體類型的定義。使用枚舉的時候,得先定義枚舉類型,再定義枚舉變量1、枚舉類型的定義方法enum 枚舉類型名{枚舉值列表;};在枚舉值表中應列出所有可用值,也稱為枚舉元素枚舉元素是常量,默認是從 0開始編號的。枚舉變量僅能取枚舉值所列元素2、枚舉變量的定義方法enum 枚舉類型名 枚舉變量名;例 37:定義枚舉類型 weekenum week //枚舉類型22千鋒智能物聯網+嵌入式學科{mon,tue,wed,thu,fri,sat,sun};enum week workday,weekday;//枚舉變量workday 與 weekday 只能取 sun….sat 中的一個workday = mon; //正確weekday = tue; //正確workday = abc; //錯誤,枚舉值中沒有 abc① 枚舉值是常量,不能在程序中用賦值語句再對它賦值例如:sun=5; mon=2; sun=mon; 都是錯誤的.② 枚舉元素本身由系統定義了一個表示序號的數值默認是從0開始順序定義為0,1,2…如在week中,mon值為0,tue值為1, …,sun值為6③ 可以改變枚舉值的默認值:如enum week //枚舉類型{mon=3,tue,wed,thu,fri=4,sat,sun};mon=3 tue=4,以此類推fri=4 以此類推注意:在定義枚舉類型的時候枚舉元素可以用等號給它賦值,用來代表元素從幾開始編號在程序中,不能再次對枚舉元素賦值,因為枚舉元素是常量。23千鋒智能物聯網+嵌入式學科第 1章 環境搭建第 2章 c數據類型及語句第 3章 數組第 4章 函數第 5章 預處理、動態庫、靜態庫第 6章 指針第 7章 動態內存申請第 8章 字符串處理函數第 9章 結構體、共用體、枚舉第 10章 文件10.1 文件的概念凡是使用過文件的人對文件都不會感到陌生1千鋒智能物聯網+嵌入式學科文件用來存放程序、文檔、音頻、視頻數據、圖片等數據的。文件就是存放在磁盤上的,一些數據的集合。在 windows下可以通過寫字板或記事本打開文本文件對文件進行編輯保存。寫字板和記事本是微軟程序員寫的程序,對文件進行打開、顯示、讀寫、關閉。作為一個程序員,必須掌握編程實現創建、寫入、讀取文件等操作對文件的操作是經常要用到的知識,比如:寫飛秋軟件傳送文件 等10.1.1 文件的分類:磁盤文件:(我們通常認識的文件)指一組相關數據的有序集合,通常存儲在外部介質(如磁盤)上,使用時才調入內存。設備文件:在操作系統中把每一個與主機相連的輸入、輸出設備看作是一個文件,把它們的輸入、輸出等同于對磁盤文件的讀和寫。鍵盤:標準輸入文件 屏幕:標準輸出文件其它設備:打印機、觸摸屏、攝像頭、音箱等在 Linux操作系統中,每一個外部設備都在/dev目錄下對應著一個設備文件,咱們在程序中要想操作設備,就必須對與其對應的/dev下的設備文件進行操作。標準 io庫函數對磁盤文件的讀取特點文件緩沖區是庫函數申請的一段內存,由庫函數對其進行操作,程序員沒有必要知道存放在哪里,只需要知道對文件操作的時候的一些緩沖特點即可。VS中對普通文件的讀寫是全緩沖的。全緩沖標準 io庫函數 ,往普通文件讀寫數據的,是全緩沖的,刷新緩沖區的情況2千鋒智能物聯網+嵌入式學科1.緩沖區滿了,刷新緩沖區2.調用函數刷新緩沖區 fflush(文件指針)3.程序結束 會刷新緩沖區10.1.2 磁盤文件的分類:一個文件通常是磁盤上一段命名的存儲區計算機的存儲在物理上是二進制的,所以物理上所有的磁盤文件本質上都是一樣的:以字節為單位進行順序存儲從用戶或者操作系統使用的角度(邏輯上)把文件分為:文本文件:基于字符編碼的文件二進制文件:基于值編碼的文件文本文件基于字符編碼,常見編碼有 ASCII、UNICODE等一般可以使用文本編輯器直接打開例如:5678的以 ASCII存儲形式為:ASCII碼:00110101 00110110 00110111 00111000二進制碼文件:基于值編碼,根據具體應用,指定某個值是什么意思一般需要自己判斷或使用特定軟件分析數據格式例如:數 5678的存儲形式為:二進制碼:00010110 00101110音頻文件(mp3):二進制文件圖片文件(bmp)文件,一個像素點由兩個字節來描述*****######&&&&&, 5 6 5*代表紅色的值 R#代表綠色的值 G&代表藍色的值 B二進制文件以位來表示一個意思。文本文件、二進制文件對比:譯碼:文本文件編碼基于字符定長,譯碼容易些;二進制文件編碼是變長的,譯碼難一些(不同的二進制文件格式,有不同的譯碼方式,一般需要特定軟件進行譯碼)。空間利用率:二進制文件用一個比特來代表一個意思(位操作);而文本文件任何一個意思至少是一個字符。所以二進制文件,空間利用率高。3千鋒智能物聯網+嵌入式學科可讀性:文本文件用通用的記事本工具就幾乎可以瀏覽所有文本文件二進制文件需要一個具體的文件解碼器,比如讀 BMP文件,必須用讀圖軟件。總結:1、文件在硬盤上存儲的時候,物理上都是用二進制來存儲的。2、咱們的標準 io庫函數,對文件操作的時候,不管文件的編碼格式(字符編碼、或二進制),而是按字節對文件進行讀寫,所以咱們管文件又叫流式文件,即把文件看成一個字節流。10.2 文件指針文件指針在程序中用來標識(代表)一個文件的,在打開文件的時候得到文件指針,文件指針就用來代表咱們打開的文件。咱們對文件進行讀、寫、關閉等操作的時候,對文件指針進行操作即可,即咱們將文件指針,傳給讀、寫、關閉等函數,那些函數就知道要對哪個文件進行操作。定義文件指針的一般形式為:FILE * 指針變量標識符;FILE 為大寫,需要包含FILE 是系統使用 typedef定義出來的有關文件信息的一種結構體類型,結構中含有文件名、文件狀態和文件當前位置等信息一般情況下,我們操作文件前必須定義一個文件指針標識 我們將要操作的文件實際編程中使用庫函數操作文件,無需關心 FILE 結構體的細節 ,只需要將文件指針傳給 io庫函數,庫函數再通過 FILE結構體里的信息對文件進行操作FILE在 stdio.h文件中的文件類型聲明:typedef struct{ short level; //緩沖區“滿”或“空”的程度unsigned flags; //文件狀態標志char fd; //文件描述符unsigned charhold; //如無緩沖區不讀取字符short bsize; //緩沖區的大小unsigned char *buffer; //數據緩沖區的位置unsigned ar*curp; //指針,當前的指向unsigned istemp; //臨時文件,指示器shorttoken; //用于有效性檢查}FILE;在緩沖文件系統中,每個被使用的文件都要在內存中開辟一塊FILE 類型的區域,存放與操作文件相關的信息4千鋒智能物聯網+嵌入式學科對文件操作的步驟:1、對文件進行讀寫等操作之前要打開文件得到文件指針2、可以通過文件指針對文件進行讀寫等操作3、讀寫等操作完畢后,要關閉文件,關閉文件后,就不能再通過此文件指針操作文件了補充:c語言中有三個特殊的文件指針無需定義,在程序中可以直接使用stdin: 標準輸入 默認為當前終端(鍵盤)我們使用的 scanf、getchar函數默認從此終端獲得數據stdout:標準輸出 默認為當前終端(屏幕)我們使用的 printf、puts函數默認輸出信息到此終端stderr:標準錯誤輸出設備文件 默認為當前終端(屏幕)當我們程序出錯使用:perror函數時信息打印在此終端總結:文件指針是個指針,它是個 FILE 類型結構體指針,用文件指針來標識一個文件。10.3 打開文件 fopen函數的聲明:FILE *fopen(const char *path, const char *mode);函數說明:fopen函數的功能是打開一個已經存在的文件,并返回這個文件的文件指針(文件的標識)或者創建一個文件,并打開此文件,然后返回文件的標識。函數的參數:參數 1:打開的文件的路徑1. 絕對路徑,從根目錄開始的路徑名稱“D:\\demo\\test\\aaa.txt”2. 相對路徑.\\test\\aaa.txt參數 2:文件打開的方式,即以什么樣的方式(只讀、只寫、可讀可寫等等)打開文件5千鋒智能物聯網+嵌入式學科第二個參數的幾種形式(打開文件的方式)讀寫權限:r w a + r:以只讀方式打開文件文件不存在返回 NULL;文件存在,且打開文件成功,返回文件指針,進行后續的讀操作例 1:FILE *fp;fp=fopen(“test.txt”,”r”); w:以只寫方式打開文件1、文件不存在,以指定文件名創建此文件,并且打開文件;2、若文件存在,清空文件內容,打開文件,然后進行寫操作;3、如果文件打不開(比如文件只讀),返回 NULLFILE *fp;fp=fopen(“test.txt”,”w”); a:以追加方式打開文件1、文件不存在,以指定文件名創建此文件(同 w)2、若文件存在,從文件的結尾處進行寫操作說明:如果不加 a的話,打開文件的時候讀寫位置在文件的開始,對文件進行讀寫的時候都是從文件開始進行讀寫的。如果加 a,打開已經存在的文件,讀寫位置在文件的末尾。 +:同時以讀寫打開指定文件模 式 功 能r或 rb 以只讀方式打開一個文本文件(不創建文件)w或 wb 以寫方式打開文件(使文件長度截斷為 0字節,創建一個文件)a或 ab 以追加方式打開文件,即在末尾添加內容,當文件不存在時,創建文件用于寫6千鋒智能物聯網+嵌入式學科r+或 rb+ 以可讀、可寫的方式打開文件(不創建新文件)w+或 wb+ 以可讀、可寫的方式打開文件(使文件長度為 0字節,創建一個文件)a+或 ab+ 以追加方式打開文件,打開文件并在末尾更改文件(如果文件不存在,則創建文件)返回值:成功:打開的文件對應的文件指針失?。悍祷?NULL以后調用 fopen函數的時候,一定要判斷一下,打開是否成功。10.4 關閉文件 fclose函數的頭文件:#include 函數的聲明:int fclose(FILE *fp);函數的說明:關閉 fp所代表的文件注意一個文件只能關閉一次,不能多次關閉。關閉文件之后就不能再文件指針對文件進行讀寫等操作了。返回值:成功返回 0失敗返回非 0可以通過返回值,來判斷關閉文件是否成功。例 6:#includeint main(){FILE *fp;int ret;fp=fopen(".\\test.txt","r+");if(fp==NULL)7千鋒智能物聯網+嵌入式學科{perror("fopen");return 0;}printf("打開文件成功\n");ret=fclose(fp);if(ret==0)printf("關閉文件成功\n");elseprintf("關閉文件失敗");return 0;}10.5 一次讀寫一個字符函數聲明:int fgetc(FILE *stream);函數說明:fgetc從 stream所標識的文件中讀取一個字節,將字節值返回返回值:以 t的方式: 讀到文件結尾返回 EOF以 b的方式:讀到文件結尾,使用 feof(文件指針)判斷結尾feof 是 C語言標準庫函數,其原型在 stdio.h中,其功能是檢測流上的文件結束符,如果文件結束,則返回非 0值,否則返回 0(即,文件結束:返回非 0值;文件未結束:返回 0值)。函數的聲明:int fputc(int c, FILE *stream)函數的說明:fputc將 c的值寫到 stream所代表的文件中。返回值:如果輸出成功,則返回輸出的字節值;如果輸出失敗,則返回一個 EOF。EOF是在 stdio.h文件中定義的符號常量,值為-1注意:打開文件的時候,默認讀寫位置在文件的開始,如果以 a的方式打開讀寫位置在文件的末尾咱們向文件中讀取字節或寫入字節的時候,讀寫位置會往文件的末尾方向偏移,讀寫多少個字節,讀寫位置就往文件的末尾方向偏移多少個字節例 7:#include int main(void)8千鋒智能物聯網+嵌入式學科{FILE *fp;char ch;fp=fopen("test.txt","r+");if(fp==NULL){printf("Cannot open the file\n");return 0;}while( (ch = fgetc(fp))!=EOF ){fputc(ch,stdout);}fclose(fp);return 0;}10.6 一次讀寫一個字符串char *fgets(char *s, int size, FILE *stream);從 stream所代表的文件中讀取字符,在讀取的時候碰到換行符或者是碰到文件的末尾停止讀取,或者是讀取了 size-1個字節停止讀取,在讀取的內容后面會加一個\0,作為字符串的結尾返回值:成功返回目的數組的首地址,即 s失敗返回 NULLint fputs(const char *s, FILE *stream);函數功能:將 s指向的字符串,寫到 stream所代表的文件中返回值:成功返回寫入的字節數失敗返回 -1例 9:#include int main(void)9千鋒智能物聯網+嵌入式學科{FILE *fp_read,*fp_write;char string1[100];if((fp_read=fopen("src.txt","r+"))==NULL){printf("Cannot open the file\n");return 0;}if((fp_write=fopen("dest.txt","w+"))==NULL){printf("Cannot open the file\n");return 0;}fgets(string1, 100, fp_read);printf("%s\n",string1);fputs(string1,fp_write);fclose(fp_read);fclose(fp_write);return 0;}10.7 讀文件 fread函數的聲明:size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);函數的說明:fread 函數從 stream 所標識的文件中讀取數據,每塊是 size 個字節,共 nmemb 塊,存放到 ptr指向的內存里返回值:實際讀到的塊數。例 1:unsigned int num;num=fread(str,100,3,fp);從 fp所代表的文件中讀取內容存放到 str指向的內存中,讀取的字節數為 ,每塊 100個字節,3塊。返回值 num,如果讀到 300個字節返回值 num為 3如果讀到了大于等于 200個字節小于 300個字節 返回值為 2讀到的字節數,大于等于 100個字節小于 200個字節 返回 1不到 100個字節返回 010千鋒智能物聯網+嵌入式學科10.8 寫文件 fwrite函數的聲明:size_t fwrite(void *ptr, size_t size, size_t nmemb, FILE *stream);函數的說明:fwrite 函數將 ptr指向的內存里的數據,向 stream 所標識的文件中寫入數據,每塊是 size 個字節,共nmemb 塊。返回值:實際寫入的塊數例 10:#include struct stu{char name[10];int num;int age;}boya[10],boyb[2];int main(){FILE *fp;int i;if((fp=fopen("test.txt","wb+"))==NULL){printf("Cannot open file!");return 0;}printf("input data\n");printf("name、num、age\n");for(i=0;i<2;i++)scanf("%s %d %d",boya[i].name,&boya[i].num,&boya[i].age);fwrite(boya,sizeof(struct stu),2,fp); //將學生信息寫入文件中rewind(fp); //文件指針經過寫操作已經到了最后,需要復位fread(boyb,sizeof(struct stu),2,fp); //將文件中的數據讀入到內存中for(i=0;i<2;i++)printf("%s %d %d\n",boyb[i].name,boyb[i].num,boyb[i].age);11千鋒智能物聯網+嵌入式學科fclose(fp);return 0;}注意:fwrite函數是將內存中的數據原樣輸出到文件中。fread函數是將文件中的數據原樣讀取到內存里。10.9 隨機讀寫前面介紹的對文件的讀寫方式都是順序讀寫,即讀寫文件只能從頭開始,順序讀寫各個數據;但在實際問題中常要求只讀寫文件中某一指定的部分,例如:讀取文件第 200--300個字節為了解決這個問題可以移動文件內部的位置指針到需要讀寫的位置,再進行讀寫,這種讀寫稱為隨機讀寫實現隨機讀寫的關鍵是要按要求移動位置指針,這稱為文件的定位.完成文件定位的函數有:rewind、fseek函數1、 rewind 復位讀寫位置rewind函數void rewind(文件指針);函數功能:把文件內部的位置指針移到文件首調用形式:rewind(文件指針);例 12:fwrite(pa,sizeof(struct stu),2,fp );rewind(fp);fread( pb,sizeof(struct stu),2,fp);2、 ftell 測文件讀寫位置距文件開始有多少個字節定義函數:long ftell(文件指針);函數功能:取得文件流目前的讀寫位置.返回值:返回當前讀寫位置(距離文件起始的字節數),出錯時返回-1. 例如:long int length;length = ftell(fp);12千鋒智能物聯網+嵌入式學科3、 fseek 定位位置指針(讀寫位置)fseek函數(一般用于二進制文件即打開文件的方式需要帶 b)函數聲明:int fseek(FILE *stream, long offset, int whence);//int fseek(文件類型指針,位移量,起始點);函數功能:移動文件流的讀寫位置.參數:whence起始位置文件開頭 SEEK_SET 0文件當前位置 SEEK_CUR 1文件末尾 SEEK_END 2位移量:以起始點為基點,向前、后移動的字節數,正數往文件末尾方向偏移,負數往文件開頭方向偏移。例 13:fseek(fp,50,SEEK_SET)fseek(fp,-50,SEEK_END);fseek(fp,0,SEEK_END);fseek(fp,20,SEEK_CUR);練習:將一個未知大小的文件(文本文件)全部讀入內存,并顯示在屏幕上參考:fseek ftell rewind fread malloc1、打開文件 fopen ,注意用 b的方式打開2、定位文件的讀寫位置到文件的末尾 fseek3、測文件的字節數 len ftell4、復位讀寫位置到文件的開始 rewind5、根據第 3步得到的字節數,申請內存 malloc 注意多申請一個字節存放’\0’6、從文件中讀取內容,存到申請的空間里 fread7、最后一個字節變成 ‘\0’8、打印讀出來的內容到屏幕上 ,printf9、關閉文件 fclose10、釋放內存 free13千鋒智能物聯網+嵌入式學科第 1 章 環境搭建第 2 章 c 數據類型及語句第 3 章 數組3.1 數組的概念數組是若干個相同類型的變量在內存中有序存儲的集合。int a[10];//定義了一個整型的數組 a,a是數組的名字,數組中有 10個元素,每個元素的類型都是 int類型,而且在內存中連續存儲。這十個元素分別是 a[0] a[1] …. a[9]a[0]~a[9]在內存中連續的順序存儲3.2 數組的分類3.2.1 按元素的類型分類1)字符數組即若干個字符變量的集合,數組中的每個元素都是字符型的變量char s[10]; s[0],s[1]....s[9];2)短整型的數組short int a[10]; a[0] ,a[9]; a[0]=4;a[9]=8;3)整型的數組int a[10]; a[0] a[9]; a[0]=3;a[0]=6;4) 長整型的數組lont int a[5];5)浮點型的數組(單、雙)float a[6]; a[4]=3.14f;double a[8]; a[7]=3.115926;6)指針數組char *a[10]int *a[10];7)結構體數組struct stu boy[10];1千鋒智能物聯網+嵌入式學科3.2.2 按維數分類一維數組int a[30];類似于一排平房二維數組int a[2][30];可以看成一棟樓房 有多層,每層有多個房間,也類似于數學中的矩陣二維數組可以看成由多個一維數組構成的。有行,有列,多維數組int a[4][2][10];三維數組是由多個相同的二維數組構成的int a[5][4][2][10];3.3 數組的定義定義一個數組,在內存里分配空間3.3.1 一維數組的定義格式:數據類型 數組名 [數組元素個數];int a [10];char b [5];定義了 5個 char類型變量的數組 b5個變量分別為 b[0] ,b[1],b[2],b[3],b[4];在數組定義的時候可以不給出數組元素的個數,根據初始化的個數來定數組的大小例 1:#include int main(int argc, char *argv[]){int a[]={1,2,3,4,5};printf("%d\n",sizeof(a));return 0;}3.3.2 二維數組的定義格式:數據類型 數組名 [行的個數][列的個數];int a [4][5];定義了 20個 int類型的變量 分別是a[0][0] ,a[0][1],a[0][2] ,a[0][3] ,a[0][4];2千鋒智能物聯網+嵌入式學科a[1][0] ,a[1][1],a[1][2] ,a[1][3] ,a[1][4];a[2][0] ,a[2][1],a[2][2] ,a[2][3] ,a[2][4];a[3][0] ,a[3][1],a[3][2] ,a[3][3] ,a[3][4];多維數組定義:int a[3][4][5]int a[8][3][4][5];擴展:二維數組在定義的時候,可以不給出行數,但必須給出列數,二維數組的大小根據初始化的行數來定例 2:#include int main(int argc, char *argv[]){int a[][3]={{1,2,3},{4,5,6},{7,8,9},{10,11,12}};printf("%d\n",sizeof(a));return 0;}3.4 數組的初始化定義數組的時候,順便給數組的元素賦初值,即開辟空間的同時并且給數組元素賦值3.4.1 一維數組的初始化a、全部初始化int a[5]={2,4,7,8,5};代表的意思: a[0]=2; a[1]=4;a[2]=7;a[3] = 8;a[4]=5;b、部分初始化int a[5]={2,4,3};初始化賦值不夠后面補 0a[0] = 2; a[1]= 4;a[2]=3;a[3]=0;a[4]=0;注意:只能省略后面元素,可以不初始化,不能中間的不初始化例 3:#include int main(int argc, char *argv[])3千鋒智能物聯網+嵌入式學科{int a[5]={2,3,5};int i;for(i=0;i<5;i++){printf("a[%d]=%d\n",i,a[i]);}return 0;}3.4.2 二維數組的定義并初始化按行初始化:a、全部初始化int a[2][2]={{1,2},{4,5}};a[0][0] =1; a[0][1] = 2; a[1][0] = 4,a[1][1]=5;b、部分初始化int a[3][3]={{1,2},{1}};a[0][0] = 1;a[0][2] =0;逐個初始化:全部初始化:int a [2][3]={2,5,4,2,3,4};部分初始化:int a[2][3]={3,5,6,8};3.5 數組元素的引用方法3.5.1 一維數組元素的引用方法數組名 [下標];//下標代表數組元素在數組中的位置int a[5];a[0] a[1] a[2] a[3] a[4];3.5.2 二維數組元素的引用方法數組名[行下標][列下標];int a [4][5];a[0][0] ,a[0][1],a[0][2] ,a[0][3] ,a[0][4];a[1][0] ,a[1][1],a[1][2] ,a[1][3] ,a[1][4];a[2][0] ,a[2][1],a[2][2] ,a[2][3] ,a[2][4];a[3][0] ,a[3][1],a[3][2] ,a[3][3] ,a[3][4];4千鋒智能物聯網+嵌入式學科例 4:#include int main(int argc, char *argv[]){int a[3][4]={{1,2,3,4},{5,6},{5}};int b[3][4]={11,12,13,14,15,16,17,18,19};int i,j;for(i=0;i<3;i++)//遍歷所有行{for( j=0;j<4;j++)//遍歷一行的所有列{printf("a[%d][%d]=%d ",i,j,a[i][ j]);}printf("\n");}for(i=0;i<3;i++)//遍歷所有行{for( j=0;j<4;j++)//遍歷一行的所有列{printf("b[%d][%d]=%d ",i,j,b[i][ j]);}printf("\n");}return 0;}3.5.3 字符數組char c1[] ={‘c’,’ ’,’p’,’r’,’o’,’g’};char c2[] = “c prog”;char a[][5] = {{‘B’,’A’,’S’,’I’,’C’},{‘d’,’B’,’A’,’S’,’E’}};char a[][6] = {“hello”,“world”}; 字符數組的引用1.用字符串方式賦值比用字符逐個賦值要多占 1個字節,用于存放字符串結束標志‘\0’;5千鋒智能物聯網+嵌入式學科2.上面的數組 c2在內存中的實際存放情況為:注:'\0'是由 C編譯系統自動加上的3.由于采用了'\0'標志,字符數組的輸入輸出將變得簡單方便.例 5:int main( ){char str[15];printf("input string:\n");scanf_s("%s",str);//helloprintf("output:%s\n",str);return 0;}6千鋒智能物聯網+嵌入式學科第 1章 環境搭建第 2章 c數據類型及語句第 3章 數組第 4章 函數4.1 函數的概念函數是 c語言的功能單位,實現一個功能可以封裝一個函數來實現。定義函數的時候一切以功能為目的,根據功能去定函數的參數和返回值。4.2 函數的分類1、從定義角度分類(即函數是誰實現的)1.庫函數 (c庫實現的)2.自定義函數 (程序員自己實現的函數)3.系統調用 (操作系統實現的函數)2、從參數角度分類1.有參函數函數有形參,可以是一個,或者多個,參數的類型隨便完全取決于函數的功能int fun(int a,float b,double c){}int max(int x,int y){}2.無參函數函數沒有參數,在形參列表的位置寫個 void或什么都不寫int fun(void){1千鋒智能物聯網+嵌入式學科}int fun(){}3、從返回值角度分類(1).帶返回值的函數在定義函數的時候,必須帶著返回值類型,在函數體里,必須有 return如果沒有返回值類型,默認返回整型。例 1:char fun()//定義了一個返回字符數據的函數{char b='a';return b;}例 2:fun(){return 1;}如果把函數的返回值類型省略了,默認返回整型注:在定義函數的時候,函數的返回值類型,到底是什么類型的,取決于函數的功能。(2).沒返回值的函數在定義函數的時候,函數名字前面加 voidvoid fun(形參表){;;return ;;}在函數里不需要 return如果想結束函數,返回到被調用的地方, return ;什么都不返回就可以了例 3:#include int max(int x,int y){2千鋒智能物聯網+嵌入式學科int z;if(x>y)z=x;elsez=y;return z;}void help(void){printf("*********************\n");printf("********幫助信息*****\n");printf("*********************\n");}int main(int argc, char *argv[]){int num;help();num = max(10,10+5);printf("num=%d\n",num);return 0;}4.3 函數的定義什么叫做函數的定義呢?即函數的實現1、函數的定義方法返回值類型 函數名字(形參列表){//函數體,函數的功能在函數體里實現}例 4:int max(int x, int y){int z;if(x>y)z=x;elsez=y;3千鋒智能物聯網+嵌入式學科return z;}注:形參必須帶類型,而且以逗號分隔函數的定義不能嵌套,即不能在一個函數體內定義另外一個函數,所有的函數的定義是平行的。例 5:void fun(void){;;;void fun2(void){;}}這個程序是錯誤的,不能再 fun的函數體中,定義 fun2函數。例 6:void fun(void){;;;}void fun2(void){;}這個程序是正確的,fun和 fun2是平行結構注:在一個程序中,函數只能定義一次給函數起名字的時候,盡量的見名知意,符合 c語言的命名規則4千鋒智能物聯網+嵌入式學科4.4 函數的聲明1、概念對已經定義的函數,進行說明函數的聲明可以聲明多次。2、為什么要聲明有些情況下,如果不對函數進行聲明,編譯器在編譯的時候,可能不認識這個函數,因為編譯器在編譯 c程序的時候,從上往下編譯的。3、聲明的方法什么時候需要聲明1)主調函數和被調函數在同一個.c文件中的時候1] 被調函數在上,主調函數在下例 7:void fun(void){printf("hello world\n");}int main(){fun();}這種情況下不需要聲明2] 被調函數在下,主調函數在上例 8:int main(){fun();}void fun(void){printf("hello world\n");}編譯器從上往下編譯,在 main函數(主調函數),不認識 fun,需要聲明怎么聲明 呢?1] 直接聲明法將被調用的函數的第一行拷貝過去,后面加分號例 9:void fun(void);5千鋒智能物聯網+嵌入式學科int main(){fun();}void fun(void){printf("hello world\n");}2] 間接聲明法將函數的聲明放在頭文件中,.c程序包含頭文件即可例 10:a.c#include”a.h”int main(){fun();}void fun(void){printf("hello world\n");}a.hextern void fun(void);2)主調函數和被調函數不在同一個.c文件中的時候一定要聲明聲明的方法:直接聲明法將被調用的函數的第一行拷貝過去,后面加分號,前面加 extern間接聲明法將函數的聲明放在頭文件中,.c程序包含頭文件即可4.5 函數的調用函數的調用方法變量= 函數名(實參列表);//帶返回值的函數名(實參列表);//不帶返回值的6千鋒智能物聯網+嵌入式學科1、有無返回值1).有返回值的,根據返回值的類型,需要在主調函數中定義一個對應類型的變量,接返回值例 11:int max(int x,int y)// x、y 形參,是個變量{}int main(){int num;//需要定義一個 num接收max 函數的返回值num=max(4,8);//4 和 8 就是實參}2).沒有返回值的函數,不需要接收返回值。例 12:void fun(void){printf("hello world\n");}int main(){fun();}2、有無形參函數名(實參列表);//帶形參的函數名();//沒有形參的注意:實參,可以常量,可以是變量,或者是表達式形參是變量,是被調函數的局部變量。4.6 函數總結在定義函數的時候,關于函數的參數和返回值是什么情況,完全取決于函數的功能。使用函數的好處?1、定義一次,可以多次調用,減少代碼的冗余度。2、使咱們代碼,模塊化更好,方便調試程序,而且閱讀方便7千鋒智能物聯網+嵌入式學科4.7 變量的存儲類別4.7.1 內存的分區:1、內存:物理內存、虛擬內存物理內存:實實在在存在的存儲設備虛擬內存:操作系統虛擬出來的內存。操作系統會在物理內存和虛擬內存之間做映射。在 32位系統下,每個進程的尋址范圍是 4G,0x00 00 00 00 ~0xff ff ff ff在寫應用程序的,咱們看到的都是虛擬地址。2、在運行程序的時候,操作系統會將 虛擬內存進行分區。1).堆在動態申請內存的時候,在堆里開辟內存。2).棧主要存放局部變量。3).靜態全局區1:未初始化的靜態全局區靜態變量(定義變量的時候,前面加 static 修飾),或全局變量 ,沒有初始化的,存在此區2:初始化的靜態全局區全局變量、靜態變量,賦過初值的,存放在此區4).代碼區存放咱們的程序代碼5).文字常量區存放常量的。4.7.2 普通的全局變量概念:在函數外部定義的變量int num=100;//num就是一個全局變量int main(){return 0;}8千鋒智能物聯網+嵌入式學科作用范圍:全局變量的作用范圍,是程序的所有地方。只不過用之前需要聲明。聲明方法 extern int num;注意聲明的時候,不要賦值。生命周期:程序運行的整個過程,一直存在,直到程序結束。注意:定義普通的全局變量的時候,如果不賦初值,它的值默認為 04.7.3 靜態全局變量 static概念:定義全局變量的時候,前面用 static 修飾。static int num=100;//num就是一個靜態全局變量int main(){return 0;}作用范圍:static 限定了靜態全局變量的,作用范圍只能在它定義的.c(源文件)中有效生命周期:在程序的整個運行過程中,一直存在。注意:定義靜態全局變量的時候,如果不賦初值,它的值默認為 04.7.4 普通的局部變量概念:在函數內部定義的,或者復合語句中定義的變量int main(){int num;//局部變量{int a;//局部變量}}作用范圍:在函數中定義的變量,在函數中有效在復合語句中定義的,在復合語句中有效。9千鋒智能物聯網+嵌入式學科生命周期:在函數調用之前,局部變量不占用空間,調用函數的時候,才為局部變量開辟空間,函數結束了,局部變量就釋放了。在復合語句中定義的亦如此。#includevoid fun(){int num=3;num++;printf("num=%d\n",num);}int main(){fun();fun();fun();return 0;}4.7.5 靜態的局部變量概念:定義局部變量的時候,前面加 static 修飾作用范圍:在它定義的函數或復合語句中有效。生命周期:第一次調用函數的時候,開辟空間賦值,函數結束后,不釋放,以后再調用函數的時候,就不再為其開辟空間,也不賦初值,用的是以前的那個變量。void fun(){static int num=3;num++;printf("num=%d\n",num);}int main(){10千鋒智能物聯網+嵌入式學科fun();fun();fun();}注意:1:定義普通局部變量,如果不賦初值,它的值是隨機的。定義靜態局部變量,如果不賦初值,它的值是 02:普通全局變量,和靜態全局變量如果不賦初值,它的值為 04.7.6 外部函數咱們定義的普通函數,都是外部函數。即函數可以在程序的任何一個文件中調用。4.7.7 內部函數在定義函數的時候,返回值前面加 static 修飾。這樣的函數被稱為內部函數。static 限定了函數的作用范圍,在定義的.c中有效。內部函數,和外部函數的區別:外部函數,在所有地方都可以調用,內部函數,只能在所定義的.c中的函數調用。擴展:在同一作用范圍內,不允許變量重名。作用范圍不同的可以重名。局部范圍內,重名的全局變量不起作用。(就近原則)11千鋒智能物聯網+嵌入式學科第 1章 環境搭建第 2章 c數據類型及語句第 3章 數組第 4章 函數第 5章 預處理、動態庫、靜態庫5.1 c語言編譯過程1:預編譯將.c 中的頭文件展開、宏展開生成的文件是.i文件2:編譯將預處理之后的.i 文件生成 .s 匯編文件3、匯編將.s匯編文件生成.o 目標文件4、鏈接將.o 文件鏈接成目標文件Linux 下 GCC編譯器編譯過程gcc -E hello.c -o hello.i 1、預處理gcc -S hello.i –o hello.s 2、編譯gcc -c hello.s -o hello.o 3、匯編gcc hello.o -o hello_elf 4、鏈接預處理有幾種啊?5.2 include#include<>//用尖括號包含頭文件,在系統指定的路徑下找頭文件#include "" //用雙引號包含頭文件,先在當前目錄下找頭文件,找不到,1千鋒智能物聯網+嵌入式學科再到系統指定的路徑下找。注意:include 經常用來包含頭文件,可以包含 .c 文件,但是大家不要包含.c因為 include包含的文件會在預編譯被展開,如果一個.c 被包含多次,展開多次,會導致函數重復定義。所以不要包含.c 文件。注意:預處理只是對 include 等預處理操作進行處理并不會進行語法檢查這個階段有語法錯誤也不會報錯,第二個階段即編譯階段才進行語法檢查。例 1:main.c:#include "max.h"int main(int argc, char *argv[]){int num;num=max(10,20);return 0;}max.hint max(int x,int y);編譯:gcc –E main.c –o main.i5.3 define定義宏用 define 去定義宏是在預編譯的時候進行替換。1、不帶參宏#define PI 3.14在預編譯的時候如果代碼中出現了 PI 就用 3.14去替換。宏的好處:只要修改宏定義,其他地方在預編譯的時候就會重新替換。注意:宏定義后邊不要加分號。例 2:#define PI 3.1415926int main(){double f;printf("%lf\n",PI);f=PI;return 0;2千鋒智能物聯網+嵌入式學科}宏定義的作用范圍,從定義的地方到本文件末尾。如果想在中間終止宏的定義范圍#undef PI //終止 PI的作用例 3:#define PI 3.1415926int main(){double f;printf("%lf\n",PI);#undef PI#define PI 3.14f=PI;return 0;}2、帶參宏#define S(a,b) a*b注意帶參宏的形參 a和 b沒有類型名,S(2,4) 將來在預處理的時候替換成 實參替代字符串的形參,其他字符保留,2 * 4例 4:#define S(a,b) a*bint main(int argc, char *argv[]){int num;num=S(2,4);return 0;}S(2+4,3)被替換成 2+4 * 3注意:帶參宏,是在預處理的時候進行替換解決歧義方法3千鋒智能物聯網+嵌入式學科例 5:#define S(a,b) (a)*(b)int main(int argc, char *argv[]){int num;num=S(2+3,5);//(2+3)*(5)return 0;}3、帶參宏和帶參函數的區別帶參宏被調用多少次就會展開多少次,執行代碼的時候沒有函數調用的過程,不需要壓棧彈棧。所以帶參宏,是浪費了空間,因為被展開多次,節省時間。帶參函數,代碼只有一份,存在代碼段,調用的時候去代碼段取指令,調用的時候要,壓棧彈棧。有個調用的過程。所以說,帶參函數是浪費了時間,節省了空間。帶參函數的形參是有類型的,帶參宏的形參沒有類型名。5.4 選擇性編譯1、#ifdef AAA代碼段一#else代碼段二#endif如果在當前.c ifdef 上邊定義過 AAA ,就編譯代碼段一,否則編譯代碼段二注意和 if else語句的區別,if else 語句都會被編譯,通過條件選擇性執行代碼而 選擇性編譯,只有一塊代碼被編譯例:6:#define AAAint main(int argc, char *argv[]){#ifdef AAA4千鋒智能物聯網+嵌入式學科printf("hello world!!\n");#elseprintf("hello China\n");#endifreturn 0;}2、#ifndef AAA代碼段一#else代碼段二#endif和第一種互補。這種方法,經常用在防止頭文件重復包含。防止頭文件重復包含:3、#if 表達式程序段一#else程序段二#endif如果表達式為真,編譯第一段代碼,否則編譯第二段代碼選擇性編譯都是在預編譯階段干的事情。5.5 靜態庫一:動態編譯動態編譯使用的是動態庫文件進行編譯gcc hello.c -o hello默認的咱們使用的是動態編譯方法二:靜態編譯靜態編譯使用的靜態庫文件進行編譯gcc -static hello.c -o hello5千鋒智能物聯網+嵌入式學科三:靜態編譯和動態編譯區別1:使用的庫文件的格式不一樣動態編譯使用動態庫,靜態編譯使用靜態庫注意:1:靜態編譯要把靜態庫文件打包編譯到可執行程序中。2:動態編譯不會把動態庫文件打包編譯到可執行程序中,它只是編譯鏈接關系例 7:mytest.c#include #include "mylib.h"int main(int argc, char *argv[]){int a=10,b=20,max_num,min_num;max_num=max(a,b);min_num=min(a,b);printf("max_num=%d\n",max_num);printf("min_num=%d\n",min_num);return 0;}mylib.cint max(int x,int y){return (x>y) x:y;}int min(int x,int y){return (x}mylib.hextern int max(int x,int y);extern int min(int x,int y);6千鋒智能物聯網+嵌入式學科制作靜態態庫:gcc -c mylib.c -o mylib.oar rc libtestlib.a mylib.o注意:靜態庫起名的時候必須以 lib開頭以.a結尾編譯程序:方法 1:gcc -static mytest.c libtestlib.a -o mytest方法 2:可以指定頭文件及庫文件的路徑比如咱們講 libtestlib.a mylib.h 移動到/home/edu下mv libtestlib.a mylib.h /home/edu編譯程序命令:gcc –static mytest.c –o mytest -L/home/edu -ltestlib -I/home/edu注意:-L是指定庫文件的路徑-l 指定找哪個庫,指定的只要庫文件名 lib后面 .a 前面的部分-I 指定頭文件的路徑方法 3:咱們可以將庫文件及頭文件存放到系統默認指定的路徑下庫文件默認路徑是 /lib 或者是/usr/lib頭文件默認路徑是/usr/includesudo mv libtestlib.a /usr/libsudo mv mylib.h /usr/include編譯程序的命令gcc -static mytest.c –o mytest -ltestlib5.6 動態庫制作動態鏈接庫:gcc -shared mylib.c -o libtestlib.so//使用 gcc編譯、制作動態鏈接庫動態鏈接庫的使用:方法 1:庫函數、頭文件均在當前目錄下gcc mytest.c libtestlib.so -o mytestexport LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH./mytest方法 2:庫函數、頭文件假設在/opt目錄gcc mytest.c -o mytest -L/home/edu -ltestlib -I/home/edu7千鋒智能物聯網+嵌入式學科編譯通過,運行時出錯,編譯時找到了庫函數,但鏈接時找不到庫,執行以下操作,把當前目錄加入搜索路徑export LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH#./mytest 可找到動態鏈接庫方法 3:庫函數、頭文件均在系統路徑下cp libtestlib.so /usr/libcp mylib.h /usr/includegcc mytest.c -o mytest -ltestlib#./mytest問題:有個問題出現了?我們前面的靜態庫也是放在/usr/lib 下,那么連接的到底是動態庫還是靜態庫呢?當靜態庫與動態庫重名時,系統會優先連接動態庫,或者我們可以加入-static 指定使用靜態庫8千鋒智能物聯網+嵌入式學科第 1章 環境搭建第 2章 c數據類型及語句第 3章 數組第 4章 函數第 5章 預處理、動態庫、靜態庫第 6章 指針6.1 指針6.1.1 關于內存那點事存儲器:存儲數據器件外存外存又叫外部存儲器,長期存放數據,掉電不丟失數據常見的外存設備:硬盤、flash、rom、u盤、光盤、磁帶內存內存又叫內部存儲器,暫時存放數據,掉電數據丟失常見的內存設備:ram、DDR物理內存:實實在在存在的存儲設備虛擬內存:操作系統虛擬出來的內存。1千鋒智能物聯網+嵌入式學科32bit 32根尋址總線0x00 00 00 000xff ff ff ff操作系統會在物理內存和虛擬內存之間做映射。在 32位系統下,每個進程(運行著的程序)的尋址范圍是 4G,0x00 00 00 00 ~0xff ff ff ff在寫應用程序的,咱們看到的都是虛擬地址。在運行程序的時候,操作系統會將 虛擬內存進行分區。1.堆在動態申請內存的時候,在堆里開辟內存。2.棧主要存放局部變量(在函數內部,或復合語句內部定義的變量)。3.靜態全局區1):未初始化的靜態全局區2千鋒智能物聯網+嵌入式學科靜態變量(定義的時候,前面加 static 修飾),或全局變量 ,沒有初始化的,存在此區2):初始化的靜態全局區全局變量、靜態變量,賦過初值的,存放在此區4.代碼區存放咱們的程序代碼5.文字常量區存放常量的。內存以字節為單位來存儲數據的,咱們可以將程序中的虛擬尋址空間,看成一個很大的一維的字符數組6.1.2 指針的概念系統給虛擬內存的每個存儲單元分配了一個編號,從 0x00 00 00 00 ~0xff ff ff ff這個編號咱們稱之為地址指針就是地址指針變量:是個變量,是個指針變量,即這個變量用來存放一個地址編號在 32位平臺下,地址總線是 32位的,所以地址是 32位編號,所以指針變量是 32位的即 4個字節。注意:1:無論什么類型的地址,都是存儲單元的編號,在 32位平臺下都是 4個字節,即任何類型的指針變量都是 4個字節大小2:對應類型的指針變量,只能存放對應類型的變量的地址舉例:整型的指針變量,只能存放整型變量的地址擴展:字符變量 char ch=‘b’; ch占 1個字節,它有一個地址編號,這個地址編號就是 ch的地址整型變量 int a=0x12 34 56 78; a占 4個字節,它占有 4個字節的存儲單元,有 4個地址編號。3千鋒智能物聯網+嵌入式學科6.1.3 指針變量的定義方法1.簡單的指針變量數據類型 * 指針變量名;int * p;//定義了一個指針變量 p在 定義指針變量的時候 * 是用來修飾變量的,說明變量 p是個指針變量。變量名是 p2.關于指針的運算符& 取地址 、 *取值例 1:int a=0x1234abcd;int *p;//在定義指針變量的時候*代表修飾的意思,修飾 p是個指針變量。p=&a;//把 a 的地址給 p 賦值 ,&是取地址符,p 保存了 a的地址,也可以說 p指向了 ap和 a的關系分析:a的值是 0x1234abcd,假如 a的地址是:0xbf e8 98 684千鋒智能物聯網+嵌入式學科int num;num=*p;分析:1、在調用的時候 *代表取值得意思 ,*p就相當于 p指向的變量,即 a,2、故 num=*p 和 num =a 的效果是一樣的。3、所以說 num的值為 0x1234abcd。擴展:如果在一行中定義多個指針變量,每個指針變量前面都需要加*來修飾int *p,*q;//定義了兩個整型的指針變量 p和 qint * p,q;//定義了一個整型指針變量 p,和整型的變量 q例 2:int main(){int a= 100, b = 200;int *p_1, *p_2 = &b; //表示該變量的類型是一個指針變量,指針變量名是 p_1 而不是*p_1.//p_1 在定義的時候沒有賦初值,p_2 賦了初值p_1=&a; //p_1 先定義后賦值printf("%d\n", a);printf("%d\n", *p_1);5千鋒智能物聯網+嵌入式學科printf("%d\n", b);printf("%d\n", *p_2);return 0;}注意:在定義 p_1的時候,因為是個局部變量,局部變量沒有賦初值,它的值是隨機的,p_1指向哪里不一定,所以 p_1就是個野指針。3.指針大小例 3:在 32 位系統下,所有類型的指針都是 4個字節#include int main(int argc, char *argv[]){char *p1;short int *p2;int *p3;long int *p4;float *p5;double *p6;printf("%d\n",sizeof(p1));printf("%d\n",sizeof(p2));printf("%d\n",sizeof(p3));printf("%d\n",sizeof(p4));printf("%d\n",sizeof(p5));printf("%d\n",sizeof(p6));return 0;}例 4:#include int main(int argc, char *argv[]){int a=0x1234abcd;int *p;p=&a;printf("&a=%p\n",&a);printf("p=%p\n",p);6千鋒智能物聯網+嵌入式學科return 0;}6.1.4 指針的分類按指針指向的數據的類型來分1:字符指針字符型數據的地址char *p;//定義了一個字符指針變量,只能存放字符型數據的地址編號char ch;p= &ch;2:短整型指針short int *p;//定義了一個短整型的指針變量 p,只能存放短整型變量的地址short int a;p =&a;7千鋒智能物聯網+嵌入式學科3:整型指針int *p;//定義了一個整型的指針變量 p,只能存放整型變量的地址int a;p =&a;注:多字節變量,占多個存儲單元,每個存儲單元都有地址編號,c語言規定,存儲單元編號最小的那個編號,是多字節變量的地址編號。4:長整型指針long int *p;//定義了一個長整型的指針變量 p,只能存放長整型變量的地址long int a;p =&a;5:float 型的指針float *p;//定義了一個 float型的指針變量 p,只能存放 float型變量的地址float a;p =&a;6:double 型的指針double *p;//定義了一個 double型的指針變量 p,只能存放 double型變量的地址double a;p =&a;7:函數指針8、結構體指針9、指針的指針10、數組指針11、通用指針 void *p;總結:無論什么類型的指針變量,在 32位系統下,都是 4個字節。指針只能存放對應類型的變量的地址編號。6.1.5 指針和變量的關系指針可以存放變量的地址編號int a=100;int *p;p=&a;在程序中,引用變量的方法1:直接通過變量的名稱int a;a=100;2:可以通過指針變量來引用變量int *p;//在定義的時候,*不是取值的意思,而是修飾的意思,修飾 p是個指針變量p=&a;//取 a的地址給 p賦值,p保存了 a的地址,也可以說 p指向了 a*p= 100;//在調用的時候*是取值的意思,*指針變量 等價于指針指向的變量注:指針變量在定義的時候可以初始化int a;8千鋒智能物聯網+嵌入式學科int *p=&a;//用 a的地址,給 p賦值,因為 p是指針變量指針就是用來存放變量的地址的。*+指針變量 就相當于指針指向的變量例 5:#include int main(){int *p1,*p2,temp,a,b;p1=&a;p2=&b;printf("請輸入:a b 的值:\n");scanf_s("%d %d",p1,p2);//給 p1 和 p2 指向的變量賦值temp = *p1; //用 p1 指向的變量(a)給 temp賦值*p1 = *p2; //用 p2 指向的變量(b)給 p1 指向的變量(a)賦值*p2 = temp;//temp給 p2 指向的變量(b)賦值printf("a=%d b=%d\n",a,b);printf("*p1=%d *p2=%d\n",*p1,*p2);return 0;}運行結果:輸入 100 200輸出結果為:a=200 b=100*p1=200 *p2=100擴展:對應類型的指針,只能保存對應類型數據的地址,如果想讓不同類型的指針相互賦值的時候,需要強制類型轉換void * p;例 6:#include int main(){int a=0x12345678,b=0xabcdef66;char *p1,*p2;printf("%0x %0x\n",a,b);p1=(char *)&a;p2=(char *)&b;printf("%0x %0x\n",*p1,*p2);9千鋒智能物聯網+嵌入式學科p1++;p2++;printf("%0x %0x\n",*p1,*p2);return 0;}結果為:0x 78 0x660x56 0xef注意:1:*+指針 取值,取幾個字節,由指針類型決定的指針為字符指針則取一個字節,指針為整型指針則取 4個字節,指針為 double型指針則取 8個字節。2:指針++ 指向下個對應類型的數據字符指針++ ,指向下個字符數據,指針存放的地址編號加 1整型指針++,指向下個整型數據,指針存放的地址編號加 410千鋒智能物聯網+嵌入式學科6.1.6 指針和數組元素之間的關系1、變量存放在內存中,有地址編號,咱們定義的數組,是多個相同類型的變量的集合,每個變量都占內存空間,都有地址編號指針變量當然可以存放數組元素的地址。例 7:int a[5];//int *p =&a[0];int *p;p=&a[0];//指針變量 p 保存了數組 a 中第 0個元素的地址,即 a[0]的地址2、數組元素的引用方法方法 1: 數組名[下標]int a[5];a[2]=100;方法 2:指針名加下標int a[5];int *p;p=a;p[2]=100;//相當于 a[2]=100;補充:c語言規定:數組的名字就是數組的首地址,即第 0個元素的地址,就是&a[0],是個常量。11千鋒智能物聯網+嵌入式學科注意:p和 a的不同,p是指針變量,而 a是個常量。所以可以用等號給 p賦值,但不能給 a賦值。p=&a[3];//正確a=&a[3];//錯誤方法 3:通過指針變量運算加取值的方法來引用數組的元素int a[5];int *p;p=a;*(p+2)=100;//也是可以的,相當于 a[2]=100解釋:p是第 0個元素的地址,p+2是 a[2]這個元素的地址。對第二個元素的地址取值,即 a[2]方法 4:通過數組名+取值的方法引用數組的元素int a[5];*(a+2)=100;//也是可以的,相當于 a[2]=100;注意:a+2 是 a[2]的地址。這個地方并沒有給 a賦值。例 8:#include int main(int argc, char *argv[]){int a[5]={0,1,2,3,4};int *p;p=a;printf("a[2]=%d\n",a[2]);printf("p[2]=%d\n",p[2]);printf("*(p+2)%d\n",*(p+2));printf("*(a+2)%d\n",*(a+2));printf("p=%p\n",p);printf("p+2=%p\n",p+2);return 0;}3、指針的運算1:指針可以加一個整數,往下指幾個它指向的變量,結果還是個地址前提:指針指向數組元素的時候,加一個整數才有意義例 9:int a[5];int *p;p=a;p+2;//p 是 a[0]的地址,p+2 是&a[2]假如 p保存的地址編號是 2000的話,p+2 代表的地址編號是 2008例 10:12千鋒智能物聯網+嵌入式學科char buf[5];char *q;q=buf;q+2 //相當于&buf [2]假如:q中存放的地址編號是 2000的話,q+2代表的地址編號是 20022:兩個相同類型指針可以比較大小前提:只有兩個相同類型的指針指向同一個數組的元素的時候,比較大小才有意義指向前面元素的指針 小于 指向后面 元素的指針例 11:#include int main(int argc, char *argv[]){int a[10];int *p,*q,n;//如果在一行上定義多個指針變量的,每個變量名前面加*//上邊一行定義了兩個指針 p 和 q ,定義了一個整型的變量 np=&a[1];q=&a[6];if(p{printf("p}else if(p>q){printf("p>q\n");}else{printf("p == q\n");}return 0;}結果是 p3.兩個相同類型的指針可以做減法前提:必須是兩個相同類型的指針指向同一個數組的元素的時候,做減法才有意義做減法的結果是,兩個指針指向的中間有多少個元素例 12:13千鋒智能物聯網+嵌入式學科#include int main(int argc, char *argv[]){int a[5];int *p,*q;p=&a[0];q=&a[3];printf("%d\n",q-p);return 0;}結果是 34:兩個相同類型的指針可以相互賦值注意:只有相同類型的指針才可以相互賦值(void *類型的除外)int *p;int *q;int a;p=&a;//p 保存 a的地址,p指向了變量 aq=p; //用 p給 q賦值,q也保存了 a的地址,指向 a注意:如果類型不相同的指針要想相互賦值,必須進行強制類型轉換注意:c語言規定數組的名字,就是數組的首地址,就是數組第 0個元素的地址,是個常量int *p;int a[5];p=a; p=&a[0];這兩種賦值方法是等價的6.1.7 指針數組1、指針和數組的關系1:指針可以保存數組元素的地址2:可以定義一個數組,數組中有若干個相同類型指針變量,這個數組被稱為指針數組 int *p[5]指針數組的概念:指針數組本身是個數組,是個指針數組,是若干個相同類型的指針變量構成的集合2、指針數組的定義方法:類型說明符 * 數組名 [元素個數];int * p[5];//定義了一個整型的指針數組 p,有 5個元素 p[0]~p[4],每個元素都是 int *類型的變量int a;p[0]=&a;int b[10];p[1]=&b[5];14千鋒智能物聯網+嵌入式學科p[2]、*(p+2)是等價的,都是指針數組中的第 2個元素。例 13:#include int main(int argc, char *argv[]){char *name[5] = {"hello","China","beijing","project","Computer"};int i;for(i=0;i<5;i++){printf("%s\n",name[i]);}return 0;}“hello”、“China”“beijing” “project” “computer” 這 5個字符串存放在文字常量區。假設:“hello ”首地址是 0x00002000“China”首地址是 0x00003000“beijing”首地址是 0x00004000“project”首地址是 0x00005000“Computer”首地址是 0x00006000則:name[0]中存放內容為 0x00002000name[1]中存放內容為 0x00003000name[2]中存放內容為 0x00004000name[3]中存放內容為 0x00005000name[4]中存放內容為 0x00006000注意:name[0] name[1] name[2] name[3] name[4] 分別是 char * 類型的指針變量,分別存放一個地址編號。3、指針數組的分類字符指針數組 char *p[10]、短整型指針數組、整型的指針數組、長整型的指針數組15千鋒智能物聯網+嵌入式學科float型的指針數組、double型的指針數組結構體指針數組、函數指針數組6.1.8 指針的指針指針的指針,即指針的地址,咱們定義一個指針變量本身指針變量占 4個字節,指針變量也有地址編號。例:int a=0x12345678;假如:a的地址是 0x00002000int *p;p =&a;則 p中存放的是 a的地址編號即 0x00002000因為 p也占 4個自己內存,也有它自己的地址編號,及指針變量的地址,即指針的指針。假如:指針變量 p的地址編號是 0x00003000,這個地址編號就是指針的地址我們定義一個變量存放 p的地址編號,這個變量就是指針的指針int **q;q=&p;//q 保存了 p的地址,也可以說 q指向了 p則 q里存放的就是 0x00003000int ***m;m=&q;16千鋒智能物聯網+嵌入式學科p q m都是指針變量,都占 4個字節,都存放地址編號,只不過類型不一樣而已6.1.9 字符串和指針字符串的概念:字符串就是以’\0’結尾的若干的字符的集合:比如“helloworld”。字符串的地址,是第一個字符的地址。如:字符串“helloworld”的地址,其實是字符串中字符’h’的地址。我們可以定義一個字符指針變量保存字符串的地址,比如:char *s =”helloworld”;字符串的存儲形式: 數組、文字常量區、堆1、字符串存放在數組中其實就是在內存(棧、靜態全局區)中開辟了一段空間存放字符串。char string[100] = “I love C!”定義了一個字符數組 string,用來存放多個字符,并且用”I love C!”給 string數組初始化,字符串“I love C!”存放在 string中。注:普通全局數組,內存分配在靜態全局區普通局部數組,內存分配在棧區。靜態數組(靜態全局數組、靜態局部數組),內存分配在靜態全局區2、字符串存放在文字常量區在文字常量區開辟了一段空間存放字符串,將字符串的首地址付給指針變量。char *str = “I love C!”定義了一個指針變量 str,只能存放字符地址編號,I love C! 這個字符串中的字符不是存放在 str指針變量中。str只是存放了字符 I的地址編號,“I love C!”存放在文字常量區3、字符串存放在堆區使用 malloc等函數在堆區申請空間,將字符串拷貝到堆區。char *str =(char*)malloc(10);//動態申請了 10個字節的存儲空間,首地址給 str賦值。strcpy(str,"I love C");//將字符串“Ilove C!”拷貝到 str指向的內存里字符串的可修改性:字符串內容是否可以修改,取決于字符串存放在哪里17千鋒智能物聯網+嵌入式學科1. 存放在數組中的字符串的內容是可修改的char str[100]=”I love C!”;str[0]=‘y’;//正確可以修改的注:數組沒有用 const修飾2. 文字常量區里的內容是不可修改的char *str=”I love C!”;*str =’y’;//錯誤,I存放在文字常量區,不可修改注:1、str指向文字常量區的時候,它指向的內存的內容不可被修改。2、str是指針變量可以指向別的地方,即可以給 str重新賦值,讓它指向別的地方。3. 堆區的內容是可以修改的char *str =(char*)malloc(10);strcpy(str,"I love C");*str=’y’;//正確,可以,因為堆區內容是可修改的注:1、str指向堆區的時候,str指向的內存內容是可以被修改的。2、str是指針變量,也可以指向別的地方。即可以給 str重新賦值,讓它指向別的地方注意:str指針指向的內存能不能被修改,要看 str指向哪里。str指向文字常量區的時候,內存里的內容不可修改str指向數組(非 const修飾)、堆區的時候,它指向內存的內容是可以修改初始化:1.字符數組初始化:char buf_aver[20]="hello world";2.指針指向文字常量區,初始化:char *buf_point="hello world";3、指針指向堆區,堆區存放字符串。不能初始化,只能先給指針賦值,讓指針指向堆區,再使用 strcpy、scanf等方法把字符串拷貝到堆區。char *buf_heap;buf_heap=(char *)malloc(15);strcpy(buf_heap,"hello world");scanf(“%s”,buf_heap);使用時賦值1. 字符數組:使用 scanf或者 strcpy18千鋒智能物聯網+嵌入式學科char buf[20]=”hello world”buf="hello kitty"; 錯誤,因為字符數組的名字是個常量,不能用等號給常量賦值。strcpy(buf,"hello kitty"); 正確,數組中的內容是可以修改的scanf("%s",buf); 正確,數組中的內容是可以修改的2. 指針指向文字常量區char *buf_point = “hello world”;1) buf_point="hello kitty"; 正確,buf_point指向另一個字符串2) strcpy(buf_point,"hello kitty");錯誤,這種情況,buf_point指向的是文字常量區,內容只讀。當指針指向文字常量區的時候,不能通過指針修改文字常量區的內容。3.指針指向堆區,堆區存放字符串char *buf_heap;buf_heap=(char *)malloc(15);strcpy(buf_heap,"hello world");scanf(“%s”,buf_heap);字符串和指針總結:1、指針可以指向文字常量區1)指針指向的文字常量區的內容不可以修改2)指針的指向可以改變,即可以給指針變量重新賦值,指針變量指向別的地方。2、指針可以指向堆區1)指針指向的堆區的內容可以修改。2)指針的指向可以改變,即可以給指針變量重新賦值,指針變量指向別的地方。3、指針也可以指向數組(非 const修飾)例:char buf[20]="hello world";char *str=buf;這種情況下1.可以修改 buf數組的內容。2.可以通過 str修改 str指向的內存的內容,即數組 buf的內容3.不能給 buf賦值 buf=“hello kitty”;錯誤的。4.可以給 str賦值,及 str指向別處。 str=“hello kitty”6.1.10 數組指針1、二維數組二維數組,有行,有列。二維數組可以看成有多個一維數組構成的,是多個一維數組的集合,可以認為二維數組的每一個元素是個一維數組。例:19千鋒智能物聯網+嵌入式學科int a[3][5];定義了一個 3行 5列的一個二維數組。可以認為二維數組 a由 3個一維數組構成,每個元素是一個一維數組。回顧:數組的名字是數組的首地址,是第 0個元素的地址,是個常量,數組名字加 1指向下個元素二維數組 a中 ,a+1 指向下個元素,即下一個一維數組,即下一行。例 14:#include int main(int argc, char *argv[]){int a[3][5];printf("a=%p\n",a);printf("a+1=%p\n",a+1);return 0;}2、數組指針的概念:本身是個指針,指向一個數組,加 1跳一個數組,即指向下個數組。3、數組指針的定義方法:指向的數組的類型(*指針變量名)[指向的數組的元素個數]int (*p)[5];//定義了一個數組指針變量 p,p指向的是整型的有 5個元素的數組p+1 往下指 5個整型,跳過一個有 5個整型元素的數組。例 15:#includeint main(){int a[3][5];//定義了一個 3 行 5 列的一個二維數組int(*p)[5];//定義一個數組指針變量 p,p+1跳一個有 5 個元素的整型數組printf("a=%p\n",a);//第 0 行的行地址printf("a+1=%p\n",a+1);//第 1 行的行地址,a 和 a +1差 20 個字節p=a;printf("p=%p\n",p);printf("p+1=%p\n",p+1);//p+1跳一個有 5 個整型元素的一維數組return 0;}例 16:數組指針的用法 120千鋒智能物聯網+嵌入式學科#includevoid fun(int(*p)[5],int x,int y){p[0][1]=101;}int main(){int i,j;int a[3][5];fun(a,3,5);for(i=0;i<3;i++){for(j=0;j<5;j++){printf("%d ",a[i][j]);}printf("\n");}}4、各種數組指針的定義:(1)、一維數組指針,加 1后指向下個一維數組int(*p)[5] ; //配合每行有 5個 int型元素的二維數組來用int a[3][5]int b[4][5]int c[5][5]int d[6][5]…..p=a;p=b;p=c;p=d;都是可以的~~~~(2)、二維數組指針,加 1后指向下個二維數組int(*p)[4][5];配合三維數組來用,三維數組中由若干個 4行 5列二維數組構成int a[3][4][5];int b[4][4][5];21千鋒智能物聯網+嵌入式學科int c[5][4][5];int d[6][4][5];這些三維數組,有個共同的特點,都是有若干個 4行 5的二維數組構成。p=a;p=b;p=c;p=d;例 17:#includeint main(){int a[3][4][5];printf("a=%p\n",a);printf("a+1=%p\n",a+1);//a 和 a+1 地址編號相差 80 個字節//驗證了 a+1 跳一個 4行 5列的一個二維數組int(*p)[4][5];p=a;printf("p=%p\n",p);printf("p+1=%p\n",p+1);//p 和 p+1 地址編號相差也 80 個字節return 0;}5、三維數組指針,加 1后指向下個三維數組int(*p)[4][5][6];p+1跳一個三維數組;什么樣的三維數組啊?由 4個 5行 6列的二維數組構成的三維數組配合:int a[7][4][5][6];6、四維數組指針,加 1后指向下個四維數組,以此類推。。。。7、注意:容易混淆的概念:指針數組:是個數組,有若干個相同類型的指針構成的集合int *p[10];數組 p有 10個 int *類型的指針變量構成,分別是 p[0] ~p[9]數組指針:本身是個指針,指向一個數組,加 1跳一個數組int (*p)[10];22千鋒智能物聯網+嵌入式學科P是個指針,p是個數組指針,p加 1指向下個數組,跳 10個整形。指針的指針:int **p;//p是指針的指針int *q;p=&q;8、數組名字取地址:變成 數組指針一維數組名字取地址,變成一維數組指針,即加 1跳一個一維數組int a[10];a+1 跳一個整型元素,是 a[1]的地址a和 a+1 相差一個元素,4個字節&a就變成了一個一維數組指針,是 int(*p)[10]類型的。(&a) +1 和&a相差一個數組即 10個元素即 40個字節。例 18:#includeint main(){int a[10];printf("a=%p\n",a);printf("a+1=%p\n",a+1);printf("&a=%p\n",&a);printf("&a +1=%p\n",&a+1);return 0;}a是個 int *類型的指針,是 a[0]的地址。&a 變成了數組指針,加 1跳一個 10個元素的整型一維數組在運行程序時,大家會發現 a和&a所代表的地址編號是一樣的,即他們指向同一個存儲單元,但是 a和&a的指針類型不同。例 19:int a[4][5];a+1 跳 5 個整型(&a)+1 跳 4 行 5列(80 個字節)。總結:c語言規定,數組名字取地址,變成了數組指針。加 1跳一個數組。23千鋒智能物聯網+嵌入式學科9、數組名字和指針變量的區別:int a[5];int *p;p=a;相同點:a是數組的名字,是 a[0]的地址,p=a即 p保存了 a[0]的地址,即 a和 p都指向 a[0],所以在引用數組元素的時候,a和 p等價引用數組元素回顧:a[2]、*(a+2)、p[2]、*(p+2) 都是對數組 a中 a[2]元素的引用。#includeint main(){int a[5] = { 0,1,2,3,4 };int* p;p = a;printf("a[2]=%d\n",a[2]);printf(" * (a + 2) = % d\n",*(a+2));printf("p[2]=%d\n", p[2]);printf(" * (p + 2) = % d\n", *(p + 2));return 0;}不同點:1、 a是常量、p是變量可以用等號’=’給 p賦值,但是不能用等號給 a賦值2、對 a取地址,和對 p取地址結果不同因為 a是數組的名字,所以對 a取地址結果為數組指針。p是個指針變量,所以對 p取地址(&p)結果為指針的指針。例:int a[5]={0,1,2,3,4};int *p=a;假如 a[0]的地址為 0x00002000,p的地址為 0x0000300024千鋒智能物聯網+嵌入式學科1、&p是指針的指針,為 int **類型,結果為 0x00003000,&p +1,往后指向一個 int* 類型的指針,地址編號差 42、&a結果是數組指針,為 int(* )[5]類型,結果還是 0x00002000,&a +1 ,往后指一個數組(有 5個整型元素的一維數組),地址編號差 20例 20:#include int main(int argc, char *argv[]){int a[5];int *p;p=a;25千鋒智能物聯網+嵌入式學科printf("a=%p\n",a);printf("&a=%p\n",&a);printf("&a +1 =%p\n",&a +1);printf("p=%p\n",p);printf("&p=%p\n",&p);printf("&p +1=%p\n",&p +1);return 0;}10、數組指針取*數組指針取 * ,并不是取值的意思,而是指針的類型發生變化:一維數組指針取* ,結果為它指向的一維數組第 0個元素的地址,它們還是指向同一個地方。二維數組指針取 *,結果為一維數組指針,它們還是指向同一個地方。三維數組指針取*,結果為二維數組指針,它們還是指向同一個地方。多維以此類推例 21:#includeint main(){int a[3][5];int(*p)[5];p = a;printf("a=%p\n", a);//a是一維數組指針,指向第 0個一維數組,即第 0 行printf("*a=%p\n", *a);//*a 是 第 0 行第 0個元素的地址,即 &a[0][0]printf("*a +1=%p\n", *a + 1);//*a +1 是第 0 行第 1 個元的地址,即&a[0][1]printf("p=%p\n",p);//p是一維數組指針,指向第 0 個一維數組,即第 0行printf("*p=%p\n",*p);//*p是第 0行第 0 個元素的地址,即 &a[0][0]printf("*p +1=%p\n", *p + 1);//*p +1 是第 0 行第 1 個元的地址,即&a[0][1]return 0;}6.1.11 指針和函數的關系6.1.11.1 指針作為函數的參數咱們可以給一個函數傳一個 整型、字符型、浮點型的數據,也可以26千鋒智能物聯網+嵌入式學科給函數傳一個地址。例:int num;scanf("%d",&num);函數傳參:(1)、傳數值:例 22:void swap(int x,int y){int temp;temp=x;x=y;y=temp;}int main(){int a=10,b=20;swap(a,b);printf("a=%d b=%d\n",a,b);//a=10 b=20}實參:調用函數時傳的參數。形參:定義被調函數時,函數名后邊括號里的數據結論:給被調函數傳數值,只能改變被調函數形參的值,不能改變主調函數實參的值(2)、傳地址:例 23:void swap(int *p1,int *p2){int temp;temp= *p1;*p1=*p2;// p2 指向的變量的值,給 p1 指向的變量賦值*p2=temp;}int main(){int a=10,b=20;swap(&a,&b);printf("a=%d b=%d\n",a,b);//結果為 a=20 b=10}27千鋒智能物聯網+嵌入式學科結論:調用函數的時候傳變量的地址,在被調函數中通過*+地址來改變主調函數中的變量的值例 24:void swap(int *p1,int *p2)//&a &b{int *p;p=p1;p1=p2;//p1 =&b 讓 p1 指向 main 中的 bp2=p;//讓 p2 指向 main 函數中 a}//此函數中改變的是 p1 和 p2 的指向,并沒有給 main 中的 a 和 b 賦值int main(){int a=10,b=20;swap(&a,&b);printf("a=%d b=%d\n",a,b);//結果為 a=10 b=20}總結:要想改變主調函數中變量的值,必須傳變量的地址,而且還得通過*+地址 去賦值。例 25:void fun(char *p){p="hello kitty";}int main(){char *p="hello world";fun(p);printf("%s\n",p);//結果為: hello world}答案分析:在 fun中改變的是 fun函數中的局部變量 p,并沒有改變 main函數中的變量 p,所以 main函數中的,變量 p還是指向 hello world。例 26:void fun(char **q){*q="hello kitty";}28千鋒智能物聯網+嵌入式學科int main(){char *p="hello world";fun(&p);printf("%s\n",p);//結果為:hello kitty}總結一句話:要想改變主調函數中變量的值,必須傳變量的地址,而且還得通過*+地址 去賦值。無論這個變量是什么類型的。(3) 給函數傳數組:給函數傳數組的時候,沒法一下將數組的內容作為整體傳進去。只能傳數組名進去,數組名就是數組的首地址,即只能把數組的地址傳進去。也就是說,只能傳一個 4個字節大小的地址編號進去例 27:傳一維數組的地址//void fun(int p[])//形式 1void fun(int *p)//形式 2{printf("%d\n",p[2]);printf("%d\n",*(p+3));}int main(){int a[10]={1,2,3,4,5,6,7,8};fun(a);return 0;}例 28:傳二維數組的地址//void fun( int p[][4] )//形式 1void fun( int (*p)[4] )//形式 2{}int main(){int a[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};fun(a);29千鋒智能物聯網+嵌入式學科return 0;}例 29:傳指針數組void fun(char **q) // char *q[]{int i;for(i=0;i<3;i++)printf("%s\n",q[i]);}int main(){char *p[3]={"hello","world","kitty"}; //p[0] p[1] p[2] char *fun(p);return 0;}6.1.11.2 指針作為函數的返回值一個函數可以返回整型數據、字符數據、浮點型的數據,也可以返回一個指針。例 30:char * fun(){char str[100]="hello world";return str;}int main(){char *p;p=fun();printf("%s\n",p);//}//總結:返回地址的時候,地址指向的內存的內容不能釋放如果返回的指針指向的內容已經被釋放了,返回這個地址,也沒有意義了。例 31:返回靜態局部數組的地址char * fun()30千鋒智能物聯網+嵌入式學科{static char str[100]="hello world";return str;}int main(){char *p;p=fun();printf("%s\n",p);//hello world}原因是,靜態數組的內容,在函數結束后,亦然存在。例 32:返回文字常量區的字符串的地址char * fun(){char *str="hello world";return str;}int main(){char *p;p=fun();printf("%s\n",p);//hello world}原因是文字常量區的內容,一直存在。例 33:返回堆內存的地址char * fun(){char *str;str=(char *)malloc(100);strcpy(str,"hello world");return str;}int main(){char *p;31千鋒智能物聯網+嵌入式學科p=fun();printf("%s\n",p);//hello worldfree(p);}原因是堆區的內容一直存在,直到 free才釋放。總結:返回的地址,地址指向的內存的內容得存在,返回的地址才有意義。6.1.11.3 指針保存函數的地址(函數指針)1、函數指針的概念:咱們定義的函數,在運行程序的時候,會將函數的指令加載到內存的代碼段。所以函數也有起始地址。c語言規定:函數的名字就是函數的首地址,即函數的入口地址咱們就可以定義一個指針變量,來存放函數的地址。這個指針變量就是函數指針變量。2、函數指針的用處:函數指針用來保存函數的入口地址。在項目開發中,我們經常需要編寫或者調用帶函數指針參數的函數。比如 Linux系統中創建多線程的函數,它有個參數就是函數指針,接收線程函數的入口地址,即創建線程成功后,新的任務執行線程函數。int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);void *thread_fun1(void *arg){}void * thread_fun2(void *arg){}int main(){pthread_t tid1,tid2;pthread_create(&tid1,NULL,thread_fun1,NULL);pthread_create(&tid2,NULL,thread_fun2,NULL);32千鋒智能物聯網+嵌入式學科。。。。}3、函數指針變量的定義返回值類型(*函數指針變量名)(形參列表);int(*p)(int,int);//定義了一個函數指針變量 p,p指向的函數必須有一個整型的返回值,有兩個整型參數。int max(int x,int y){}int min(int x,int y){}可以用這個 p存放這類函數的地址。p=max;p=min;4、調用函數的方法1.通過函數的名字去調函數(最常用的)int max(int x,int y){}int main(){int num;num=max(3,5);}2.可以通過函數指針變量去調用int max(int x,int y){}int main(){33千鋒智能物聯網+嵌入式學科int num;int (*p)(int ,int);p=max;num=(*p)(3,5);}5、函數指針數組概念:由若干個相同類型的函數指針變量構成的集合,在內存中連續的順序存儲。函數指針數組是個數組,它的每個元素都是一個函數指針變量。函數指針數組的定義:類型名(*數組名[元素個數])(形參列表)int(*p[5])(int,int);定義了一個函數指針數組,數組名是 p,有 5個元素 p[0] ~p[4],每個元素都是函數指針變量,每個函數指針變量指向的函數,必須有整型的返回值,兩個整型參數。例:#includeint max(int x, int y){int temp;if (x > y)temp = x;elsetemp = y;return temp;}int min(int x, int y){int temp;if (x < y)temp = x;elsetemp = y;return temp;}int add(int x, int y){return x + y;}int sub(int x, int y)34千鋒智能物聯網+嵌入式學科{return x - y;}int mux(int x, int y){return x * y;}int main(){int(*p[5])(int, int) = {mux,min,add,sub,mux};int num;num = (*p[2])(10,20);printf("num=%d\n", num);return 0;}6、函數指針應用舉例給函數傳參#includeint add(int x,int y){return x+y;}int sub(int x,int y){return x-y;}int mux(int x,int y){return x*y;}int dive(int x,int y){return x/y;}int process(int (*p)(int ,int),int a,int b){int ret;35千鋒智能物聯網+嵌入式學科ret = (*p)(a,b);return ret;}int main(){int num;num = process(add,2,3);printf("num =%d\n",num);num = process(sub,2,3);printf("num =%d\n",num);num = process(mux,2,3);printf("num =%d\n",num);num = process(dive,2,3);printf("num =%d\n",num);return 0;}6.1.12 經常容易混淆的指針概念第一組:1、 int *a[10];這是個指針數組,數組 a中有 10個整型的指針變量a[0]~a[9] ,每個元素都是 int *類型的指針變量2、int (*a)[10];數組指針變量,它是個指針變量。它占 4個字節,存地址編號。它指向一個數組,它加 1的話,指向下個數組。3、 int **p;這個是個指針的指針,保存指針變量的地址。它經常用在保存指針的地址:常見用法 1:int **pint *q;p=&q;常見用法 2:int **p;36千鋒智能物聯網+嵌入式學科int *q[10];分析:q是指針數組的名字,是指針數組的首地址,是 q[0]的地址。q[0]是個 int *類型的指針。 所以 q[0]指針變量的地址,是 int **類型的p=&q[0]; 等價于 p=q;例 34:void fun(char**p){int i;for(i=0;i<3;i++){printf("%s\n",p[i]);//*(p+i)}}int main(){char *q[3]={"hello","world","China"};fun(q);return 0;}第二組:1、int *f(void);注意:*f沒有用括號括起來它是個函數的聲明,聲明的這個函數返回值為 int *類型的。2、int (*f)(void);注意*f用括號括起來了,*修飾 f說明,f是個指針變量。f是個函數指針變量,存放函數的地址,它指向的函數,必須有一個 int型的返回值,沒有參數。6.1.13 特殊指針1、空類型的指針(void *)char * 類型的指針變量,只能保存 char型的數據的地址int * 類型的指針變量,只能保存 int型的數據的地址float* 類型的指針變量,只能保存 float型的數據的地址void * 難道是指向 void型的數據嗎?不是,因為沒有 void類型的變量void* 通用指針,任何類型的地址都可以給 void*類型的指針變量賦值。int *p;37千鋒智能物聯網+嵌入式學科void *q;q=p 是可以的,不用強制類型轉換舉例:有個函數叫 memsetvoid * memset(void *s,int c,size_t n);這個函數的功能是將 s指向的內存前 n個字節,全部賦值為 c。memset可以設置字符數組、整型數組、浮點型數組的內容,所以第一個參數,就必須是個通用指針它的返回值是 s指向的內存的首地址,可能是不同類型的地址。所以返回值也得是通用指針注意:void*類型的指針變量,也是個指針變量,在 32為系統下,占 4個字節2、NULL空指針:char *p=NULL;咱們可以認為 p哪里都不指向,也可以認為 p指向內存編號為 0的存儲單位。在 p的四個字節中,存放的是 0x00 00 00 00一般 NULL用在給指針變量初始化。main函數傳參:例 35:#include int main(int argc, char *argv[]){int i;printf("argc=%d\n",argc);for(i=0;i{printf("argv[%d]=%s\n",i,argv[i]);}return 0;}38風干鋒熱百千鋒智能物聯網+嵌入式學科第1章環境搭建第2章數據類型及語句第3章數組第4章函數第5章預處理、動態庫、靜態庫第6章指針第7章動態內存申請第8章字符串處理函數pragma指令的作用是:用于指定計算機或操作系統特定的編譯器功能#pragma warning(disable:4996)在c文件開始處寫上這句話,即告訴編譯器忽略4996警告,strcpy、.scanf等一些不安全的標準c庫函數在vs中可以用了。8.1測字符串長度函數頭文件:#include函數定義:size t strlen(const char*s);函數功能:測字符指針s指向的字符串中字符的個數,不包括0返回值:字符串中字符個數例1:#include 做真實的自己,用良心做教育風干鋒教百千鋒智能物聯網+嵌入式學科#include int main(char str1[20]="hello";char *str2 ="hello";printf("%d\n",sizeof(str1));//20printf("%d\n",sizeof(str2));//4printf("%d\n",strlen(str1))://5printf("%d\n",strlen(str2))://5return 0;sizeof是個關鍵字,測量數據的占用內存空間大小。如果測量的是數組的名字,則測的是數組占多少個字節如果sizeof測的是指針變量,則測的是指針變量本身占幾個字節,32平臺下結果為4strler是個庫函數,它測的是字符指針指向的字符串中字符的個數,不管指針是數組的名字,還是個指針變量。8.2字符串拷貝函數頭文件:include函數的聲明:char*strcpy(char*dest,const char*src);函數的說明:拷貝src指向的字符串到dest指針指向的內存中,0'也會拷貝函數的返回值:目的內存的地址注意:在使用此函數的時候,必須保證dst指向的內存空間足夠大,否則會出現內存污染。char *strncpy(char *dest,const char *src,size_t n);函數的說明:將src指向的字符串前n個字節,拷貝到dest指向的內存中返回值:目的內存的首地址注意:1、strncpy不拷貝02、如果n大于src指向的字符串中的字符個數,則在dest后面填充n-strlen(src)個0'例2:#include #include做真實的自己,用良心做教育風干于鋒教百千鋒智能物聯網+嵌入式學科第1章環境搭建1.1 Visual Studio軟件安裝1、軟件下載路徑鏈接:https:/pan.baidu.,com/s/1LaLe7 amFWF500WKc0 sTmnA提取碼:i2sg2、安裝雙擊exe可執行程序vs communiy_712910196.1584880824.ex6Visual Studio Installer開始之前,我們需要設置某些選項,以便你配置安裝。若要了解有關隱私的詳細信息,請參閱Microsoft隱私聲明。繼續即表示你同意Microsoft軟件許可條款。繼續O)做真實的自己,用良心做教有1風鋒教百千鋒智能物聯網+嵌入式學科Visual Studio Installer稍等片刻正在提取文件。正在下裁:2.99MB/74.25MB860.45KB/秒正在安裝稍等一會取消(qEL一M.iammnly一1hL表治雪Aehr三d安裝詳知信已 11ab女女>Voual5udie民.心ev快刀C+中的夏面環發 c+rT: =60g打C+C:1年Tn 夏=子w2生太⊥C+nG打k52,牛右,1:3★hmI BDOst.Tect2h G03eT15aa Lies Shaw日2測m季m云Visual Studio Installer感可用Visual Studin Community 201T發人員新聞工E一8生:7M1195n(1:Anncurcirg NET G FrewH性等待一時回Miae3i.w3山u時Hn.in&cmmniyANLI Cete updilss in.Ns Wavie 1 安片NETGPreeNow avable an行明1+=月匠黑aierairgyaiml4edy wcre codted to zrneunse our Re ease4只月肛8重Z棗巴代機AE衛.下M+之30712做真實的自己,用良心做教育2風干鋒教百千鋒智能物聯網+嵌入式學科1.2注冊A)創建一個賬戶Visual Studio登錄Visual Studio巖設活同路設空使用ireShare時r作與A2山re深天數元B)輸入一個電子郵箱地址Microsoft創建帳戶someone@改為使用電話號碼獲取新的電子郵件地址下一步C)設置一個登錄密碼做真實的自己,用良心做教育3 展開更多...... 收起↑ 資源列表 【千鋒教育】C語言程序設計(2022版)全冊講義.pdf 第2章 C數據類型及控制語句 課后練習.pdf 第3章 數組 課后練習.pdf 第8章 字符串處理函數 課后練習.pdf 第10章 文件操作 講義.pdf 第1章 VisualStudio軟件安裝及使用 講義.pdf 第2章 C數據類型及控制語句 講義.pdf 第3章 數組 講義.pdf 第4章 函數 講義.pdf 第4章 預處理 講義.pdf 第6章 指針 講義.pdf 第7章 動態內存申請 講義.pdf 第8章 字符串處理函數 講義.pdf 第9章 結構體 共用體 枚舉 講義.pdf 縮略圖、資源來源于二一教育資源庫