C 語言測試: 指標 - 小狐狸事務所

文章推薦指數: 80 %
投票人數:10人

C 語言允許多重指標, 但因較複雜而用得不多. 宣告多重指標的方式是使用連續的"*", 例如: char ***ppp; //宣告三重指標ppp 從下面範例可以觀察三重指標 ... 2017年9月21日星期四 C語言測試:指標 暑假期間我家二哥把C語言最重要的陣列與指標看過一遍,對於指標的意義與用法有了基本了解,但指標究竟用來幹嘛卻沒有具體概念.老實說我也沒有,因為除了寫Arduino有用到基本的C語言技巧外,我從來沒用C寫過像樣的東西.所以就趁這個機會把C語言測試一下,就從最難處理的指標開始吧! C語言雖然是高階語言,但指標功能卻讓C語言也擁有低階的記憶體操作能力.許多高階語言也有支援指標,例如C++,FORTRAN,PASCAL,BASIC,Perl,C#等等,其中C++除了完整支援C的指標功能外,也新增了SmartPointers以提供較原始指標更安全的功能.部分支援指標的高階語言因為安全性等原因而對指標做了限制,例如PASCAL與C#,而Java則完全不支援指標,參考: #Support_in_various_programming_languages 指標的主要用途如下: 函式之間若要傳遞字串或陣列使用指標做傳址呼叫可避免資料的複製與搬移 需要從函式中傳回一個以上的值時必須使用指標,因為return只能傳回一個一般變數值. 較複雜之資料結構需要利用指標以鏈結串列方式實作. 使用指標處理字串較方便.  事實上,許多C語言內建函式內部都是使用指標實作的. 以下的測試程式我參考了下列幾本書的範例加以改編: C語言初學指引第四版 (博碩,陳錦輝) C語言程式設計剖析(全華,簡聰海) C語言從零開始(博碩,資訊教育研究室) C語言入門(易習,丁國棟) C語言程式設計實例入門第四版(博碩,高橋麻奈) C語言程式設計與應用(全華,陳會安) 使用工具包括Windows上的DevC++,樹莓派的gcc,以及線上C語言編譯器TutorialsPoint(此為Linux主機,使用gcc編譯,不須存檔現打現譯): # https://www.tutorialspoint.com/compile_c_online.php 一.指標變數的宣告與賦值 1.指標是甚麼? 指標是C語言中一種特別的資料型態,專門用來儲存某個資料的記憶體位址,其內容用來指向該資料.編譯器使用CPU的間接定址法來存取指標變數所指之記憶體位址.指標最早出現在PL/I語言中,參考: # https://zh.wikipedia.org/wiki/指標_(電腦科學) 指標變數與一般變數不同,其儲存的內容是記憶體位址,因此占用的記憶體大小是固定的,即等於CPU的位址寬度,例如32位元系統之位址匯流排寬度是32位元(4個bytes),可定址2^32=4GBytes的DRAM,其指標變數長度為4個Bytes;而64位元系統位址匯流排64位元(8個bytes),定址能力為2^64=16GB之DRAM, 其指標變數長度為8個Bytes. 2.指標變數的宣告 指標變數宣告方式是在變數名稱前面加*號(ANSIC)或是在資料型態後面加*(C++新增方式),例如: int*ptr;   //ANSIC的宣告方式 int*ptr;   //C++新增的宣告方式 這都是宣告指向一個整數資料的指標變數ptr.指標變數宣告之後,編譯器會在記憶體中指配記憶位址來儲存所指之位址.指標變數在賦值之前其內容為記憶體之前的殘留值或隨機初始值,此未初始化的指標變數是懸空的狀態(dangling),不可拿來使用,因為它可能指向不允許存取的系統保留區或其他程序正在使用的記憶體位址,可能導致運算結果錯誤或系統當機. 指標變數除了指向一般變數與陣列外,也可以指向函數,稱為函數指標,其宣告方式例如: int(*ftpr)(intx,inty); 這是宣告一個指向函數的指標fptr,詳如後述. 3.指標變數的賦值(初始化): 指標變數在使用前必須"初始化(賦值)"以指向某一個記憶體位址;亦即透過指定運算將一個記憶體位址存入指標內.指標賦值有兩種方式:  使用取址運算子 & (referenceoperator)   將本身就是位址的陣列名稱或函數名稱指派給指標  取址運算子&可以取得任意變數(包含指標變數)之記憶體位址,例如: inta=100;           //宣告一般變數a並賦值100 int*ptr;             //宣告指向整數資料的指標 ptr=&a;        //取得a的記憶體位址並賦值給指標ptr 也可以在宣告指標的同時予以賦值,上面的程式可改為: inta=100; int*ptr=&a;     //取得a的記憶體位址並賦值給指標ptr 4.陣列指標: 指標也可以指向陣列元素,這種指標稱為陣列指標.陣列的每一個元素相當於一般的變數,因此可以用取址運算子&取得任一元素之記憶體位址,然後指派給指標,例如: inta[]={0,1,2,3,4,5}; int*ptr=&a[0];       //宣告整數指標ptr指向整數陣列a的開頭位址 ptr=&a[3];           //指標改為指向陣列a的第四個元素 將指標指向陣列開頭除了用&a[0]取得第一個元素的開頭位址外賦值外,也可以直接用陣列名稱賦值,因為陣列名稱本身就是指向其第一個元素的開始位址,例如: int*ptr=a;     //宣告整數指標ptr指向整數陣列a的開頭位址(等於&a[0]) 從下面的範例可知,&a[0]與a的內容是一樣的,都是陣列a的開始位址: #include intmain(){   inta[]={0,1,2,3,4,5};   printf("a=%p\n",a);   printf("&a[0]=%p\n",&a[0]);   int*ptr=a;   printf("ptr=%p\n",ptr);   return0;   } 結果如下: a=0x7ffff331aa90(陣列開始位址) &a[0]=0x7ffff331aa90(陣列開始位址) ptr=0x7ffff331aa90(指向陣列開始位址) 陣列其實是一種特殊的指標,在編譯器的符號表中,a儲存了第一個元素的開頭位址&a[0],其內容是不可更改的(否則會找不到陣列的開頭),因此陣列又稱為常數指標.一般的指標是變數,其所儲存的內容(記憶體位址)是可以改變的,可以做遞增遞減等算術運算,例如++ptr或ptr--等等,但是陣列a是常數指標,不允許做a++或--a等運算. 下列程式碼將編譯失敗,因為陣列的起始位址是不能更改的:   intx=5;   inta[]={0,1,2,3,4,5};   inta=&x;   //不可更改陣列位址 在DevC++會出現如下錯誤訊息,表示a已經被宣告為陣列,不能更改a的位址: [Error]conflictingdeclaration'inta' [Note]previousdeclarationas'inta[6]' 陣列指標除了可使用*(ptr+i) 來存取陣列元素外,也可以把指標當成陣列名稱,使用索引 ptr[i] 來存取陣列元素,例如: #include intmain(){   inta[]={0,1,2,3,4,5};   int*ptr=a;   printf("a[2]=*(ptr+2)=%d\n",*(ptr+2));  //使用取值運算子   printf("a[2]=ptr[2]=%d\n",ptr[2]);      //陣列指標也可以使用索引存取陣列元素   return0;   } 執行結果如下: a[2]=*(ptr+2)=2 a[2]=ptr[2]=2 指標既然可以指向陣列,當然也可以在函數中當虛引數(參數)接收呼叫者傳遞之陣列.在函數之間傳遞陣列是以傳址呼叫方式將陣列名稱(即起始位址)傳入函數,例如下列求陣列元素和的程式,被呼叫的函式必須宣告一個同型態陣列來接收引數: intsum(inta[],intlen){  //宣告一個整數陣列接收引數   ints=0;   for(inti=0;i intmain(){   inta=2;   int*ptr=1000;    return0;  } 此程式在TutorialsPoint編譯會得到下列錯誤訊息: $gcc-omain*.c main.c:Infunction‘main’: main.c:3:14:warning:initializationmakespointerfrominteger withoutacast[-Wint-conversion] int*ptr=1000; ^~~~ $main 若在DevC++編譯則是: [Error]invalidconversionfrom'int'to'int*'[-fpermissive] 測試2:顯示未初始化之指標內容 #include intmain(){   int*ptr;   printf("未初始化指標ptr位址=%p\n", &ptr);   printf("未初始化指標ptr內容=%p",ptr);   return0;   } 此程式第一個printf()顯示用&取址運算子取得之指標變數ptr本身的位址,在64位元系統為8個bytes;第二個printf()則顯示指標變數之內容.注意,雖然指標的內容是整數(記憶體位址),但是在printf()中顯示指標內容必須使用%p,不是%d. 上面程式在TutorialsPoint執行結果未初始化指標內容是nil(無值): $gcc-omain*.c $main 未初始化指標ptr位址=0x7ffedc8ffa08 未初始化指標ptr內容=(nil) 而在DevC++結果是未初始化指標內容是1,這是記憶體殘留值: 未初始化指標ptr位址=000000000022FE48   未初始化指標ptr內容=0000000000000001  可見指標只要一經宣告,編譯器就會指派一個位址給它,例如上面的 0x7ffedc8ffa08與0x22FE48,此位址每次執行都可能會不同. 存取指標所指之資料須使用取值運算子*(dereferenceoperator),例如: inta=100;      //宣告一般變數a並賦值100 int*ptr;        //宣告指向整數資料的指標 ptr=&a;        //取得a的記憶體位址並賦值給指標ptr(指向a) *ptr=50;        //將a的值被改為50了 這裡ptr指標指向一般變數a,因此*ptr=50就會把a的內容改為50,例如: 測試3:利用指標更改所指位址內容 #include intmain(){   inta=100;   printf("a的位址=%p\n",&a);   printf("a的初始值=%d\n",a);   int*ptr;            //宣告指標ptr   ptr=&a;       //指標ptr指向a   printf("ptr的內容=%p\n",ptr);   //ptr內容=a的位址   printf("*ptr的初始值=%d\n",*ptr);   *ptr=50;   printf("*ptr的新值=%d\n",*ptr);   printf("a的新值=%d",a);   return0;   } 執行結果如下: a的位址=0x7ffd5899e0d4 a的初始值=100 ptr的內容=0x7ffd5899e0d4(與a的位址一樣) *ptr的初始值=100 *ptr的新值=50 a的新值=50(a的值被改了) 7.用指標當函數參數進行傳址呼叫 C語言函數的return只能傳回一個值,如果要傳回多個值必須利用指標或陣列的傳址呼叫才能達成,在"C語言從零開始"這本書的11-7節以及"最新C語言程式設計實例入門"的9-3節提到的兩個變數數值交換函數swap()唯一極佳範例,改寫如下: 測試4:利用指標傳址呼叫交換兩個變數之數值 #include voidswap(int*x,int*y){ //函數的參數為指標(不需傳回值)   inttmp=*x;   *x=*y;   *y=tmp;   } intmain(){   inta=5,b=15;   printf("交換前:a=%db=%d\n",a,b);   swap(&a,&b);  //以傳址呼叫將a,b之位址傳給函數   printf("交換後:a=%db=%d\n",a,b);   return0;   } 由於要傳回的值有兩個(即交換後的a,b),因此將a,b的位址傳給swap()去運算,因為指標指向了變數的位址,直接在變數上進行交換動作,因此也用不到return將值傳回來,故傳回值宣告為void.執行結果如下: 交換前:a=5b=15 交換後:a=15b=5 如果將swap()改成如下用傳值呼叫的話,由於傳進去的是資料的副本,如果不將資料傳回來的話就是做白工: intswap(intx,inty){ //傳值呼叫:無效的數值交換   inttmp=x;   x=y;   y=tmp;   } 交換後無法將兩個值同時傳回來等於做白工,一定要用指標才行. 二.指標的長度: 指標變數與一般變數不同,其儲存的內容是記憶體位址(整數),因此不論指標指向哪一種型態的資料,其所占記憶體大小是固定的, ,即等於CPU的位址寬度,例如32位元系統之位址匯流排寬度是32位元(4個bytes),可定址2^32=4GBytes的DRAM,其指標變數長度為4個Bytes;而64位元系統位址匯流排為64位元(8個bytes),定址能力為2^64=16GB之DRAM, 其指標變數長度為8個Bytes.例如: 測試5:指向各種型態的指標都占用相同大小的記憶體  #include intmain(){   charc;   inti;   floatf;   doubled;   char*cptr=&c;   int*iptr=&i;   float*fptr=&f;   double*dptr=&d;   printf("變數名稱  記憶體位址     占用記憶體(bytes)\n");   printf("========  ================ ==================\n");   printf(" c \t  %p\t %d\n",&c,sizeof(c));   printf(" i \t  %p\t %d\n",&i,sizeof(i));   printf(" f \t  %p\t %d\n",&f,sizeof(f));   printf(" d \t  %p\t %d\n",&d,sizeof(d));   printf("cptr \t  %p \t %d\n",&cptr,sizeof(cptr));   printf("iptr \t  %p \t %d\n",&iptr,sizeof(iptr));   printf("fptr \t  %p \t %d\n",&fptr,sizeof(fptr));   printf("dptr \t  %p \t %d\n",&dptr,sizeof(dptr));   return0;   } 在Win1064位元系統的DevC++執行結果如下: 可見指標內容不管所指資料類型為何都是8bytes(64位元).在TutorialsPoint上也是8bytes: 而在樹莓派B上則是4個bytes,因為其CPU是32位元的. 三.指標的運算 指標儲存的是記憶體位址,事實上也就是整數,若呼叫printf()輸出時用%d會顯示整數值,而用%p才會顯示位址值,例如: intmain(){   inta=5;   int*ptr=&a;   printf("ptr=%d\n",ptr); //以整數格式輸出指標內容   printf("ptr=%p\n",ptr); //以位址格式輸出指標內容   return0;   } 執行結果如下: ptr=1065793828 ptr=0x7fff3f86b924 因此除了上述使用"="運算子進行指定(賦值) 運算外,指標還可以進行算術運算,但只限於與整數之加減運算以及遞增遞減運算,不允許乘除等運算,因為指標運算之用途只是為了用來計算記憶體位址之位移以存取資料而已,指標乘除運算並無意義. 1.指標加減運算:  指標的值可以與整數進行加減來改變指標所指向之位址,但因為指標儲存的是記憶體位址,不是一般的整數,因此加減的整數其單位並非bytes,而是指標所指之資料型態,例如當指標指向陣列時,ptr+i中的i是指陣列元素移位個數 : inta[]={0,1,2,3,4,5};  //宣告一個整數陣列a int*ptr=&a[0];   //宣告指標ptr指向陣列開頭元素a[0] ptr=ptr +2;         //指標向下移動2個int單位,指向a[2] 指標ptr原先指向a[0],指標加2並非ptr所儲存的位址加2個bytes,而是加2個整數單位,例如DevC++中整數是4個bytes,則ptr+2就是指標移位2*4=8bytes;而在樹莓派的gcc編譯器,整數占2個bytes,則ptr +2就是移位2*2=4個bytes. 測試6:指標加減指向陣列元素 #include intmain(){   inta[]={0,1,2,3,4,5};   printf("元素\t位址\n");   for(inti=0;i<5;i++){     printf("a[%d]\t%p\n",i,&a[i]);     }   int*ptr=&a[0];  //指標指向a[0]   printf("ptr=%p*ptr=%d\n",ptr,*ptr);   ptr=ptr+2;     //指標指向a[2]   printf("ptr=%p*ptr=%d\n",ptr,*ptr);   ptr=ptr-1;     //指標指向a[1]   printf("ptr=%p*ptr=%d",ptr,*ptr);   return0;   } 在TutorialsPoint測試結果如下: 元素位址 a[0]0x7fff4003fa10 a[1]0x7fff4003fa14 a[2]0x7fff4003fa18 a[3]0x7fff4003fa1c a[4]0x7fff4003fa20 ptr=0x7fff4003fa10*ptr=0 ptr=0x7fff4003fa18*ptr=2 ptr=0x7fff4003fa14*ptr=1 TutorialsPoint的int占4個bytes,陣列的每個元素在記憶體內會在儲存在連續位址中,因此指標ptr向前移動2個單位,實際上位址是由 0x7fff4003fa10 向前移動8個bytes到 0x7fff4003fa18. 下面範例是參考"C語言初學指引第四版"加以改編: 測試7 : 指標加減在各類型資料的位址的移位距離  #include intmain(){   inta;   shortint*p;   int*q;   float*r;   double*s;   char*t;   printf("指標移位前位址\n");   p=(shortint*)&a;  //強制轉型為shortint   q=&a;   r=(float*)&a;     //強制轉型為float   s=(double*)&a;   //強制轉型為double   t=(char*)&a;     //強制轉型為char   printf("=============\n");   printf("p=%p\n",p);   printf("q=%p\n",q);   printf("r=%p\n",r);   printf("s=%p\n",s);   printf("t=%p\n",t);   printf("指標移位後位址\n");   p=p+1;   q=q+1;   r=r+1;   s=s+1;   t=t+1;   printf("=============\n");   printf("p=%p\n",p);   printf("q=%p\n",q);   printf("r=%p\n",r);   printf("s=%p\n",s);   printf("t=%p\n",t);    return0;   } 這裡用p,q,r,s,t五個指標分別指向五個不同的資料類型,先印出移位前所指向之位址,再讓指標向前移動一個單位,然後印出所指向的新位址,這會因不同資料型態而有所不同.注意,因為a是int類型,因此除了q以外,其他指標都必須取址後予以強制轉型. 在TutorialsPoint測試結果如下: 指標移位前位址 ============= p=0x7ffd2bd96354 q=0x7ffd2bd96354 r=0x7ffd2bd96354 s=0x7ffd2bd96354 t=0x7ffd2bd96354 指標移位後位址 ============= p=0x7ffd2bd96356(shortint占2個bytes) q=0x7ffd2bd96358(int占4個bytes) r=0x7ffd2bd96358(float占4個bytes) s=0x7ffd2bd9635c(double占8個bytes) t=0x7ffd2bd96355(char占1個bytes) 可見在TutorialsPoint的主機上短整數shortint類型移位一個單位為2個bytes;整數int為4個bytes;浮點數float為4個bytes;倍準數double為8個bytes;字元char為1個byte. 3.指標差值運算: 兩個指向相同資料類型的指標變數可以相減,得到的差是一個整數,表示兩個指標間之距離,其單位是該資料型別的長度.如果這兩個指標指向同一陣列,則差值表示兩個指標之間隔了多少元素. 測試8:兩個指標間之距離 #include intmain(){   inta[]={0,1,2,3,4,5};   printf("元素\t位址\n");   for(inti=0;i<5;i++){     printf("a[%d]\t%p\n",i,&a[i]);     }   int*ptr1=&a[1];   int*ptr2=&a[2];   printf("ptr1=%p*ptr1=%d\n",ptr1,*ptr1);   printf("ptr2=%p*ptr2=%d\n",ptr2,*ptr2);   printf("ptr2-ptr1=%d\n",ptr2-ptr1);   printf("ptr1-ptr2=%d",ptr1-ptr2);   return0;   } TutorialsPoint執行結果: 元素位址 a[0]0x7ffdcf7a8540 a[1]0x7ffdcf7a8544 a[2]0x7ffdcf7a8548 a[3]0x7ffdcf7a854c a[4]0x7ffdcf7a8550 ptr1=0x7ffdcf7a8544*ptr1=1 ptr2=0x7ffdcf7a8548*ptr2=2 ptr2-ptr1=1 ptr1-ptr2=-1 可見ptr2與ptr1位址差距為4bytes,由於int長度為4bytesm,所以ptr2-ptr1為1個整數單位. 注意,兩個相減的指標必須是指向同類型,否則無法通過編譯.其次,兩個指標相減是允許的,但兩個指標的相加運算卻是不允許的,因為兩個位址相加沒有意義.例如將上面的ptr2-ptr1改為ptr2+ptr1將無法通過編譯. 4.遞增(++)與遞減--)運算: 遞增與遞減運算就是指標加1或減1運算,各有前置(先做運算)與後置(後做運算)之分: 前遞增 ++ptr:先加1再處理 後遞增ptr++:先處理再加1 前遞減--ptr:先減1再處理 後遞減ptr--:先處理再減1 ++與--是單元運算子, 如果只是做變數的單元運算,前置或後置對運算結果相同.例如: #include intmain(){   inta=1;   ++a; //前遞增單元運算a=2   printf("a=%d\n",a);   a=1;   a++; //後遞增單元運算a=2   printf("a=%d\n",a);   return0;   } 不論前遞增或後遞增,單元運算的結果都是a=2: $gcc-omain*.c $main a=2 a=2 結果無差別的原因是上述單元運算沒有再進行指定處理. 有差異的情況是單元運算後還進一步再做處理(即指定運算),例如: #include intmain(){   inta=1;   intb=++a;  //前遞增後指定給b,故b=2   printf("b=%d\n",b);   a=1;   b=a++;    //指定給b後再做後遞增,故b=1   printf("b=%d\n",b);   return0;   } 結果: b=2 b=1 參考: # 30天C語言巔峰之路(Day13:運算子-遞增與遞減運算子) 指標變數做遞增遞減運算時,由於指標的取址&與取值*運算子與遞增++遞減--運算子優先等級一樣,都是第二級(第一級是括號),因此當&,*, ++,--,()混合運算時需仔細判斷,例如*ptr++是先取ptr所指之值後,再將ptr指向下一個資料下列;而*++ptr則是先將指標增量指向下一個資料後,再取出其值. 下面的範例使用一個陣列a與指標ptr來測試*, ++,()的7種混合運算: inta[5]={0,1,2,3,4}; 測試9:指標*,++, ()的混合運算 #include intmain(){   inta[5]={0,1,2,3,4};   printf("元素\t值\t位址\n");   for(inti=0;i<5;i++){     printf("a[%d]\t%d\t%p\n",i,a[i],&a[i]);     }    int*ptr=a;   printf("ptr=a=%p\n",ptr);   intx=*(ptr++);   printf("x=*(ptr++)x=%dptr=%p*ptr=%d\n",x,ptr,*ptr);   x=*(++ptr);   printf("x=*(++ptr)x=%dptr=%p*ptr=%d\n",x,ptr,*ptr);   x=(*ptr)++;   printf("x=(*ptr)++x=%dptr=%p*ptr=%d\n",x,ptr,*ptr);   x=++(*ptr);   printf("x=++(*ptr)x=%dptr=%p*ptr=%d\n",x,ptr,*ptr);   x=*ptr++;   printf("x=*ptr++x=%dptr=%p*ptr=%d\n",x,ptr,*ptr);   x=*++ptr;   printf("x=*++ptrx=%dptr=%p*ptr=%d\n",x,ptr,*ptr);   x=++*ptr;   printf("x=++*ptrx=%dptr=%p*ptr=%d\n",x,ptr,*ptr);   return0;   } 在TutorialsPoint執行結果如下: 元素值位址 a[0]00x7ffe4aa8a9e0 a[1]10x7ffe4aa8a9e4 a[2]20x7ffe4aa8a9e8 a[3]30x7ffe4aa8a9ec a[4]40x7ffe4aa8a9f0 ptr=a=0x7ffe4aa8a9e0 x=*(ptr++)x=0ptr=0x7ffe4aa8a9e4*ptr=1 x=*(++ptr)x=2ptr=0x7ffe4aa8a9e8*ptr=2 x=(*ptr)++x=2ptr=0x7ffe4aa8a9e8*ptr=3 x=++(*ptr)x=4ptr=0x7ffe4aa8a9e8*ptr=4 x=*ptr++x=4ptr=0x7ffe4aa8a9ec*ptr=3 x=*++ptrx=4ptr=0x7ffe4aa8a9f0*ptr=4 x=++*ptrx=5ptr=0x7ffe4aa8a9f0*ptr=5 結果摘要如下表:  運算式 相當於 執行結果 說明  x=*(ptr++); x=*ptr;  ptr=ptr+1; x=0  *ptr=1 先取值*ptr給x,再遞增ptr  x=*(++ptr); ptr=ptr+1;  x=*ptr; x=2  *ptr=2 先遞增ptr再取值給x  x=(*ptr)++; x=*ptr;  *ptr=*ptr+1; x=2  *ptr=3 先取值*ptr給x,再把*ptr加1  x=++(*ptr); *ptr=*ptr+1;  x=*ptr;  x=4  *ptr=4 把*ptr加1同時設給x  x=*ptr++; x=*ptr;  ptr=ptr+1; x=4  *ptr=3 先取值*ptr給x,再把*ptr加1  x=*++ptr; ptr=ptr+1;  x=*ptr; x=4  *ptr=4 先遞增ptr再取值給x,  與 x=*(++ptr) 同  x=++*ptr x=*ptr+1; x=5  *ptr=5 把*ptr加1同時設給x,與x=++(*ptr)同 可知*(++ptr)與*++ptr作用相同,而 ++(*ptr)與 ++*ptr作用相同,因為++,--,*這些第二優先等級的運算子,運算順序為由右向左之故. 5.指標比較運算: 兩個指標可以在if敘述中使用==,>,=,<=,或!=等關係運算子進行比較運算,用來判斷所儲存的記憶體位址是否相等,或何者之記憶體位址較高或較低. #include intmain(){   inta,b;   int*ptr1=&a;   int*ptr2=&b;   printf("ptr1=%p\n",ptr1);   printf("ptr2=%p\n",ptr2);   if(ptr2>ptr1){     printf("變數b位址高於變數a位址\n");     }   else{     printf("變數a位址高於變數b位址\n");     }   return0;   } 在Win10上的DevC++執行結果如下: ptr1=000000000022FE3C ptr2=000000000022FE38 變數a位址高於變數b位址 可見DevC++在編譯時,變數位址是依據宣告先後由高位址往低位址指派.在TutorialsPoint上執行的結果也是如此: $gcc-omain*.c $main ptr1=0x7ffe5964392c ptr2=0x7ffe59643928 變數a位址高於變數b位址 在ArduinoNano編譯也是高位址先指派: voidsetup(){   Serial.begin(9600);   inta,b;   int*ptr1=&a;   int*ptr2=&b;   Serial.println((long)ptr1);   Serial.println((long)ptr2);   if(ptr2>ptr1){     Serial.println("變數b位址高於變數a位址\n");     }   else{     Serial.println("變數a位址高於變數b位址\n");     }   } voidloop(){} 結果如下: 2298 2296 變數a位址高於變數b位址 注意,必須指向相同資料類型的指標才可以互相比較,若將上面程式中的b與ptr2改為float類型,則編譯時將會失敗,出現如下錯誤訊息(DevC++): comparisonbetweendistinctpointertypes'float*'and'int*'lacksacast[-fpermissive] 四.指標的指標:  指標變數用來儲存資料的記憶體位址,指標除了用來指向一般變數,也可以用來指向另一個指標變數,稱為雙重指標.C語言允許多重指標,但因較複雜而用得不多.宣告多重指標的方式是使用連續的"*",例如: char***ppp; //宣告三重指標ppp 從下面範例可以觀察三重指標儲存的位址之關係: 測試10:三重指標 #include intmain(){   inta=100;   int*ptr1=&a;      //指向整數的指標   int**ptr2=&ptr1;   //指向整數指標的指標   int***ptr3=&ptr2;  //指向整數指標的指標的指標   printf("&a=%pa=%d\n",&a,*ptr1);   printf("&ptr1=%pptr1=%p*ptr1=%d\n",&ptr1,ptr1,*ptr1);   printf("&ptr2=%pptr2=%p*ptr2=%p\n",&ptr2,ptr2,*ptr2);   printf("&ptr3=%pptr3=%p*ptr3=%p\n",&ptr3,ptr3,*ptr3);   return0;   } TutorialsPoint執行結果如下: $gcc-omain*.c $main &a=0x7fff5e45bb2ca=100 &ptr1=0x7fff5e45bb20ptr1=0x7fff5e45bb2c*ptr1=100 &ptr2=0x7fff5e45bb18ptr2=0x7fff5e45bb20*ptr2=0x7fff5e45bb2c &ptr3=0x7fff5e45bb10ptr3=0x7fff5e45bb18*ptr3=0x7fff5e45bb20 在上面的範例中,指標ptr1指向一個整數變數a,儲存的是a的位址7fff5e45bb2c,而指標ptr2又指向ptr1;儲存的是ptr1的位址7fff5e45bb20;指標ptr3又指向ptr2,儲存的是ptr2的位址7fff5e45bb18,形成三重指標.由於最終是指向一個整數,因此這三個指標都必須宣告為int. 五.函數指標:  上面測試指標賦值時提到,指標也可以指向函數, 稱為函數指標.因為函數名稱在符號表中儲存的是函數在記憶體中的開始位址,如同陣列名稱是陣列的開始位址一樣,可以直接將函數名稱指派給指標,使其指向該函數.函數指標可以讓我們在程式中動態地呼叫不同的函數. 1.函數指標的宣告與呼叫方式 : 函數指標的宣告方式如下: 傳回值類型(*指標名稱)([參數類型]);   例如: int(*fptr)(int,int);  //宣告具有兩個int參數,傳回值為int的函數指標fptr 函數指標的賦值只要將函數名稱指派給指標即可(不必用&),賦值後指標即指向該函數: 指標名稱=函數名稱;  例如: fptr=swap;  //函數指標指向函數swap() 這樣就可以利用函數指標來呼叫函數,呼叫方式有下列兩種: (*指標名稱)([參數]); 指標名稱([參數]);      例如: (*fptr)(5,25);  //呼叫函數指標fptr fptr(5,25);     //呼叫函數指標fptr 注意,第一種呼叫方式*fptr外面的括弧是必須的,*fptr要先結合,表示fptr是一個指標變數;然後再與後面的括號結合,表示它指向一個函數(括號優先順序高於*運算子). 下面範例使用指標fptr先後指向add()與sub()這兩個函數,統一使用ftpr()或(*fptr)()來呼叫不同函數: 測試11:函數指標與函數位址 #include intadd(intx,inty){   returnx+y;   } intsub(intx,inty){   returnx-y;   } intmain(){   printf("add=%p\n",add);   printf("sub=%p\n",sub);   int(*fptr)(intx,inty);   fptr=add;   //指標fptr指向add()函數   printf("fptr=add=%p\n",fptr);   printf("ftpr(8,3)=add(8,3)=8+3=%d\n",fptr(8,3)); //用函數指標名稱呼叫函數   fptr=sub;   //指標fptr指向sub()函數   printf("fptr=add=%p\n",fptr);   printf("ftpr(8,3)=sub(8,3)=8-3=%d\n",fptr(8,3));   return0;   } 在TutorialsPoint執行結果如下: add=0x4004d7 sub=0x4004eb fptr=add=0x4004d7 ftpr(8,3)=add(8,3)=8+3=11 fptr=add=0x4004eb ftpr(8,3)=sub(8,3)=8-3=5 可見透過指派函數名稱可以用同一名稱fptr()呼叫不同的函數,雖然這跟直接呼叫add()與sub()沒兩樣,但若配合條件判斷可達到動態呼叫不同函數之效. 如同陣列名稱代表陣列開始位址,函數名稱代表函數開頭位址,因此可以像陣列與指標一樣當引數傳給另一個函數,這時被呼叫函數中接收此引數的參數必須宣告為函數指標. 下列範例是我參考"易習C語言入門"第8-5節範例改寫而來,其中兩個函數add與sub被當成呼叫compute()函數時的引數,因此在compute()中必須使用函數指標來接收才行: 測試12:用函數指標接收函數參數 #include intadd(intx,inty){   returnx+y;   } intsub(intx,inty){   returnx-y;   } intcompute(intx,inty,int(*fptr)(intx,inty)){ //用intfptr(intx,int,y)亦可   return(*fptr)(x,y);   } intmain(){   printf("compute(8,3,add)=%d\n",compute(8,3,add)); //以函數add當引數   printf("compute(8,3,sub)=%d\n",compute(8,3,sub));  //以函數sub當引數   return0;   } 在TutorialsPoint執行結果如下: compute(8,3,add)=11 compute(8,3,sub)=5 注意,接收引數的函數指標不一定要用*運算子,可以直用函數指標的名稱,所以上面的compute()也可以改為: intcompute(intx,inty,intfptr(intx,inty)){ //以函數指標接收函數引數   return(*fptr)(x,y);   } 下面的範例則是參考"C語言程式設計應用"10-7-2改寫,此程式中利用一個額外函數compare()的第三參數(函數指標)來呼叫max()與min()這兩個函數: 測試13:用函數指標接收函數參數 #include intmax(int,int); //函數原型宣告 intmin(int,int); //函數原型宣告 intcompare(int,int,int(*)(int,int)); //函數原型宣告(第三參數為函數指標) intmain(){    intx=5,y=15;   printf("x=%dy=%d最大值=%d\n",x,y,compare(x,y,max)); //以函數名當引數   printf("x=%dy=%d最小值=%d\n",x,y,compare(x,y,min));  /以函數名當引數   return0;   }   intmax(intx,inty){   if(x>y){returnx;}   else{returny;}   }   intmin(intx,inty){   if(x intadd(intx,inty){   returnx+y;   } intsub(intx,inty){   returnx-y;   } intmul(intx,inty){   returnx*y;   } intmain(){   int(*ptr[3])(int,int); //宣告一個函數指標陣列,用來指向三個函數   ptr[0]=add; //函數指標賦值為函數名稱以指向函數   ptr[1]=sub;   ptr[2]=mul;   charop[]={'+','-','*'};   intopr1=15;   intopr2=6;   for(inti=0;i<3;i++){     printf("%d%c%d=%d\n",opr1,op[i],opr2,ptr[i](opr1,opr2));  //透過函數指標呼叫函數     }    return0;   } 在TutorialsPoint執行結果如下: 15+6=21 15-6=9 15*6=90 六.泛型指標 void: 在上面測試4的傳址呼叫中,swap()函數交換的是兩個整數變數之值;如果要交換的是兩個字元變數的話,這個swap()函數就不能用了,必須另外為char參數撰寫新的函數.在下列範例中,交換整數的函數改為iswap(),而交換字元的函數則為cswap(),對於不同類型參數須分別呼叫這兩個函數: 測試15:函數指標 #include voidcswap(char*x,char*y){ //函數的參數為指標(不需傳回值)   chartmp=*x;   *x=*y;   *y=tmp;   } voidiswap(int*x,int*y){ //函數的參數為指標(不需傳回值)   inttmp=*x;   *x=*y;   *y=tmp;   } intmain(){   inti1=5,i2=15;   printf("交換前:i1=%di2=%d\n",i1,i2);   iswap(&i1,&i2);  //以傳址呼叫將i1,i2之位址傳給函數   printf("交換後:i1=%di2=%d\n",i1,i2);    charc1='A',c2='B';   printf("交換前:c1=%cc2=%c\n",c1,c2);   cswap(&c1,&c2);  //以傳址呼叫將c1,c2之位址傳給函數   printf("交換後:c1=%cc2=%c\n",c1,c2);   return0;   } 在TutorialsPoint執行結果如下: 交換前:i1=5i2=15 交換後:i1=15i2=5 交換前:c1=Ac2=B 交換後:c1=Bc2=A 上面範例只是導入泛型void指標之前的楔子.接下來先回顧測試12與13,這兩個範例除了展示以函數指標當作函數參數的方法外,同時也展示了如何利用額外定義的一個compute()與compare()函數來當作統一的呼叫介面,藉由傳入不同的函數指標當引數來呼叫不同的函數之方法.這是因為這兩個範例中的兩個函數介面都一樣,即傳回值與參數型態,參數個數都相同,這樣才統一得起來.然而,上面這個整數與字元交換的範例由於介面不同,無法像測試12,13那樣使用統一的函數來呼叫,必須為int與char分別寫一個函數iptr()與cptr(): 測試16:函數指標 #include voidiswap(int*,int*); //函數原型宣告 voidcswap(char*,char*); //函數原型宣告 voidiptr(int*,int*,void(*)(int*,int*)); //函數原型宣告 voidcptr(char*,char*,void(*)(char*,char*)); //函數原型宣告 intmain(){   inti1=5,i2=15;   printf("交換前:i1=%di2=%d\n",i1,i2);   iptr(&i1,&i2,iswap);  //以傳址呼叫將i1,i2之位址傳給函數   printf("交換後:i1=%di2=%d\n",i1,i2);   charc1='A',c2='B';   printf("交換前:c1=%cc2=%c\n",c1,c2);   cptr(&c1,&c2,cswap);  //以傳址呼叫將c1,c2之位址傳給函數   printf("交換後:c1=%cc2=%c\n",c1,c2);   return0;   }   voidiswap(int*x,int*y){ //函數的參數為指標(不需傳回值)   inttmp=*x;   *x=*y;   *y=tmp;   } voidcswap(char*x,char*y){ //函數的參數為指標(不需傳回值)   chartmp=*x;   *x=*y;   *y=tmp;   } voidiptr(int*x,int*y,void(*ptr)(int*x,int*y)){   return(*ptr)(x,y); //以函數指標ptr呼叫函數   } voidcptr(char*x,char*y,void(*ptr)(char*x,char*y)){   return(*ptr)(x,y);  //以函數指標ptr呼叫函數   } 上面的程式中,函數被當成引數傳遞給iswap()與cswap()的第三個參數,所以在這兩個函數中必須以指標函數來接收.把函數當引數傳遞原先的目的是希望能將呼叫函數統一起來,但由於int與char是不同類型而無法達成,使得上面程式的寫法看起來挺彆扭的.既然無法統一,還不如像測試15那樣直接呼叫iswap()與cswap()算了,不需要再多寫iptr()與cptr()這兩個轉手的函數. 但救星來了,在"C語言程式設計與應用"的17-7-3節介紹了C語言的泛型指標void ,當兩個函數的參數是不同資料類型時,則可以將其每一個參數都宣告為void類型,任何型態的引數傳入時會被轉換成void類型,函數執行完再轉回原來型態,這樣就可以傳遞任何型態之參數,使得上面測試16函數無法統一的困境得到解決,如下列範例所示: 測試17: 使用泛型指標呼叫不同函數(交換兩個整數或字元) #include voidiswap(int*,int*); //函數原型宣告 voidcswap(char*,char*); //函數原型宣告 voidswap(void*,void*,void(*)(void*,void*)); //函數原型宣告 intmain(){     inti1=5,i2=15;   printf("交換前:i1=%di2=%d\n",i1,i2);   swap((void*)&i1,(void*)&i2,(void(*)(void*,void*))iswap);   printf("交換後:i1=%di2=%d\n",i1,i2);    charc1='A',c2='B';   printf("交換前:c1=%cc2=%c\n",c1,c2);   swap((void*)&c1,(void*)&c2,(void(*)(void*,void*))cswap);   printf("交換後:c1=%cc2=%c\n",c1,c2);   return0;   } voidcswap(char*x,char*y){ //函數的參數為指標(不需傳回值)   chartmp=*x;   *x=*y;   *y=tmp;   } voidiswap(int*x,int*y){ //函數的參數為指標(不需傳回值)   inttmp=*x;   *x=*y;   *y=tmp;   } voidswap(void*a,void*b,void(*vptr)(void*,void*)){ //泛型函數:參數類型全部是void  return(*vptr)(a,b);  } 可見透過void泛型指標,測試14中的iptr()與cptr()兩個函數就被統一為swap()一個函數了.注意,這裡泛型函數swap()中的參數a,b都被宣告為泛型指標void*,使得不管傳入的指標是int或char類型都被強迫改成void類型.第三個參數是函數指標,用來接收呼叫者傳入的函數名稱,其參數正是對應第一與第二參數,因此也都要宣告為void*.而swap()的內容就是呼叫函數指標所指之函數並回傳結果,這時被迫轉型為void的參數又被轉回原來之類型.由於iswap()與cswap()都無傳回值,故swap()雖有return,其傳回值型態亦為void. 在"C語言程式設計與應用"書中10-7-3節的範例是比較兩個整數與兩個字元,如果相等傳回0,大於傳回1,小於傳回-1.我將其改寫如下以與上例做比較: 測試17:使用泛型指標呼叫不同函數(比較整數或字元大小) #include inticmp(int*,int*); //函數原型宣告 intccmp(char*,char*); //函數原型宣告 intcmp(void*,void*,int(*)(void*,void*)); //函數原型宣告 intmain(){   inti1=10,i2=5;   intr=cmp((void*)&i1,(void*)&i2,(int(*)(void*,void*))icmp);   printf("i1=%di2=%d傳回值=%d\n",i1,i2,r);   i1=5,i2=5;   r=cmp((void*)&i1,(void*)&i2,(int(*)(void*,void*))icmp);   printf("i1=%di2=%d傳回值=%d\n",i1,i2,r);   i1=5,i2=10;   r=cmp((void*)&i1,(void*)&i2,(int(*)(void*,void*))icmp);   printf("i1=%di2=%d傳回值=%d\n",i1,i2,r);      charc1='A',c2='B';   r=cmp((void*)&c1,(void*)&c2,(int(*)(void*,void*))ccmp);   printf("c1=%cc2=%c傳回值=%d\n",c1,c2,r);   c1='A',c2='A';   r=cmp((void*)&c1,(void*)&c2,(int(*)(void*,void*))ccmp);   printf("c1=%cc2=%c傳回值=%d\n",c1,c2,r);   c1='B',c2='A';   r=cmp((void*)&c1,(void*)&c2,(int(*)(void*,void*))ccmp);   printf("c1=%cc2=%c傳回值=%d\n",c1,c2,r);     return0;   }   inticmp(int*x,int*y){   if(*x==*y){return0;}   elseif(*x>*y){return1;}   else{return-1;}   }   intccmp(char*x,char*y){   if(*x==*y){return0;}   elseif(*x>*y){return1;}   else{return-1;}   }   intcmp(void*x,void*y,int(*vptr)(void*,void*)){   return(*vptr)(x,y);   } 此程式中由於兩個比較函數icmp()與ccmp()都傳回int類型資料,因此泛型函數cmp()也是傳回int.在呼叫cmp()時,傳入參數前兩個用(void*)強迫轉型為void類型,第三個參數則用(int(*)(void*,void*))將icmp與ccmp強迫轉型為泛型函數. 執行結果如下: i1=10i2=5傳回值=1 i1=5i2=5傳回值=0 i1=5i2=10傳回值=-1 c1=Ac2=B傳回值=-1 c1=Ac2=A傳回值=0 c1=Bc2=A傳回值=1 #include intimax(int*,int*); charcmax(char*,char*); voidvptr(void*,void*,void(*)(void*,void*)); intmain(){   inti1=5,i2=10;   printf("i1=%di2=%dmax=%d\n",i1,i2,vptr((void*)&i1,(void*)&i2,(void(*)(void*,void*))imax));   charc1='A',c2='B';   printf("i1=%di2=%dmax=%c\n",i1,i2,vptr((void*)&c1,(void*)&c2,(void(*)(void*,void*))cmax));   return0;   }   intimax(int*x,int*y){   if(*x>*y){return*x;}   else{return*y;}   }   charcmax(char*x,char*y){   if(*x>*y){return*x;}   else{return*y;}   }   voidvptr(void*x,void*y,void(*max)(void*,void*)){   return(*max)(x,y);   } 參考: # 指標與動態記憶體配置介紹 # 为什么说指针是C语言的精髓? # C語言泛型編程技巧 # 避免編譯時產生錯誤-error:voidvaluenotignoredasitoughttobe # C语言中的泛型编程(一) # printf()與scanf() 張貼者: 小狐狸事務所 於 9/21/201705:13:00下午 以電子郵件傳送這篇文章 BlogThis! 分享至Twitter 分享至Facebook 標籤: C語言 3則留言 : 321 提到... 請問最後那個範例式故意式犯錯誤的嗎?因為程式無法正確執行會出現invaliduseofvoidexpression但也無法向他上面那個範例直接給予voidr=... 2022年3月17日上午9:57 321 提到... 你好想請問,在測試17中如果將c2改成int型態後,會發現傳入cswap的型態還是char(1bytes),但是回傳回swap後印出來的型態是宣告的int(4bytes)。

這可以說明文中所說的傳回值型態亦為void.但是!交換過後資料類型為何還是跟交換前一樣?交換後宣告型態不變?這樣存放不屬於該型態類型資料不會有問題嗎?所占記憶體空間位置不同於該值存放類別不會有問題嗎?現實中有沒有類似的範例?還是根本不會這樣使用?程式碼如下:#includevoidiswap(int*,int*);//函數原型宣告voidcswap(char*,char*);//函數原型宣告voidswap(void*,void*,void(*)(void*,void*));/*改成使用泛型指標呼叫不同函數voidiptr(int*,int*,void(*)(int*,int*));//函數原型宣告voidcptr(char*,char*,void(*)(char*,char*));//函數原型宣告*/intmain(){inti1=5,i2=15;printf("交換前:i1=%di2=%d\n",i1,i2);swap((void*)&i1,(void*)&i2,(void(*)(void*,void*))iswap);//iptr(&i1,&i2,iswap);//以傳址呼叫將i1,i2之位址傳給函數printf("交換後:i1=%di2=%d\n",i1,i2);charc1='A';intc2=20;printf("交換前:c1=%cc2=%d\tsizeof:c1=%d,c2=%d\n",c1,c2,sizeof(c1),sizeof(c2));printf("交換前address:c1=%pc2=%p\n",&c1,&c2);swap((void*)&c1,(void*)&c2,(void(*)(void*,void*))cswap);//cptr(&c1,&c2,cswap);//以傳址呼叫將c1,c2之位址傳給函數printf("交換後:c1=%dc2=%c\tsizeof:c1=%d,c2=%d\n",c1,c2,sizeof(c1),sizeof(c2));printf("交換後address:c1=%pc2=%p\n",&c1,&c2);return0;}voidiswap(int*x,int*y){//函數的參數為指標(不需傳回值)inttmp=*x;*x=*y;*y=tmp;}voidcswap(char*x,char*y){//函數的參數為指標(不需傳回值)chartmp=*x;printf("tmp=%csizeof:%d\t",tmp,sizeof(tmp));*x=*y;printf("*x=%dsizeof:%d\t",*x,sizeof(*x));*y=tmp;printf("*y=%csizeof:%d\n",*y,sizeof(*y));}voidswap(void*x,void*y,void(*vptr)(void*,void*)){return(*vptr)(x,y);}/*改成使用泛型指標呼叫不同函數voidiptr(int*x,int*y,void(*ptr)(int*x,int*y)){return(*ptr)(x,y);//以函數指標ptr呼叫函數}voidcptr(char*x,char*y,void(*ptr)(char*x,char*y)){return(*ptr)(x,y);//以函數指標ptr呼叫函數}*/ 2022年3月17日上午11:12 小狐狸事務所 提到... 嗨321,真是不好意思,太久沒用C語言,細節都忘了,容我找時間看一下,看能否恢復功力. 2022年3月18日下午4:19 張貼留言 較新的文章 較舊的文章 首頁 訂閱: 張貼留言 ( Atom ) 總網頁瀏覽量 小狐狸事務所 載入中… 訂閱 發表文章 Atom 發表文章 留言 Atom 留言 Translate 聯絡表單 名稱 以電子郵件傳送 * 訊息 * 網誌存檔 ►  2022 ( 209 ) ►  六月 ( 26 ) ►  五月 ( 35 ) ►  四月 ( 42 ) ►  三月 ( 41 ) ►  二月 ( 39 ) ►  一月 ( 26 ) ►  2021 ( 453 ) ►  十二月 ( 28 ) ►  十一月 ( 38 ) ►  十月 ( 43 ) ►  九月 ( 47 ) ►  八月 ( 36 ) ►  七月 ( 25 ) ►  六月 ( 38 ) ►  五月 ( 30 ) ►  四月 ( 35 ) ►  三月 ( 37 ) ►  二月 ( 41 ) ►  一月 ( 55 ) ►  2020 ( 483 ) ►  十二月 ( 42 ) ►  十一月 ( 33 ) ►  十月 ( 35 ) ►  九月 ( 43 ) ►  八月 ( 35 ) ►  七月 ( 30 ) ►  六月 ( 46 ) ►  五月 ( 46 ) ►  四月 ( 39 ) ►  三月 ( 48 ) ►  二月 ( 48 ) ►  一月 ( 38 ) ►  2019 ( 459 ) ►  十二月 ( 38 ) ►  十一月 ( 39 ) ►  十月 ( 34 ) ►  九月 ( 34 ) ►  八月 ( 27 ) ►  七月 ( 50 ) ►  六月 ( 38 ) ►  五月 ( 26 ) ►  四月 ( 51 ) ►  三月 ( 47 ) ►  二月 ( 32 ) ►  一月 ( 43 ) ►  2018 ( 394 ) ►  十二月 ( 38 ) ►  十一月 ( 36 ) ►  十月 ( 30 ) ►  九月 ( 33 ) ►  八月 ( 31 ) ►  七月 ( 26 ) ►  六月 ( 30 ) ►  五月 ( 48 ) ►  四月 ( 26 ) ►  三月 ( 38 ) ►  二月 ( 26 ) ►  一月 ( 32 ) ▼  2017 ( 317 ) ►  十二月 ( 35 ) ►  十一月 ( 30 ) ►  十月 ( 24 ) ▼  九月 ( 34 ) APCS(大學程式先修檢測)105年3月觀念題解析 向露天livinghuang採購零件一批 如何在樹莓派上編譯執行C程式 ArduinoConESP8266學習筆記(三):從NTP伺服器取得網路時間 希雅(Sia)的水晶吊燈 ArduinoConESP8266學習筆記(二):在網頁上控制LED ArduinoConESP8266學習筆記(一):環境設定與韌體上傳 2017年第38周記事 C語言測試:指標 母親三周年忌日 高師大新版校友借書證 2017年第37周記事 鄉村基礎木作第七課:完成框架組裝 使用Python輕量級網頁框架Flask架站 樹莓派RaspberryPi文章列表 約瑟夫斯問題 使用rosserial讓兩個Arduino板與樹莓派通訊 工作日誌升版 ESP-01專用轉接板加工 WeMOSD1Mini新舊版比較 2017年第36周記事 MicroPythononESP8266的看門狗WDT無法設定時限問題 MicroPython基於IPythonParallel的動態程式佈署 還書一批 製作ESP-01模組轉接板 MicroPythononESP8266(二十三):超音波模組HC-SR04測試 樹莓派語音聲控GoogleHomePi 追求成功還是追求卓越 2017年第35周記事 工業自動化:樹莓派之PLC應用 MicroPythononESP8266(二十二):UART串列埠測試 姊姊上台北讀書 大學程式設計先修檢測APCS 幫姊姊辦中信LinePay金融卡 ►  八月 ( 30 ) ►  七月 ( 27 ) ►  六月 ( 28 ) ►  五月 ( 19 ) ►  四月 ( 22 ) ►  三月 ( 21 ) ►  二月 ( 30 ) ►  一月 ( 17 ) ►  2016 ( 296 ) ►  十二月 ( 17 ) ►  十一月 ( 23 ) ►  十月 ( 28 ) ►  九月 ( 24 ) ►  八月 ( 28 ) ►  七月 ( 33 ) ►  六月 ( 28 ) ►  五月 ( 26 ) ►  四月 ( 24 ) ►  三月 ( 24 ) ►  二月 ( 25 ) ►  一月 ( 16 ) ►  2015 ( 296 ) ►  十二月 ( 18 ) ►  十一月 ( 22 ) ►  十月 ( 19 ) ►  九月 ( 25 ) ►  八月 ( 27 ) ►  七月 ( 24 ) ►  六月 ( 15 ) ►  五月 ( 23 ) ►  四月 ( 32 ) ►  三月 ( 51 ) ►  二月 ( 22 ) ►  一月 ( 18 ) ►  2014 ( 323 ) ►  十二月 ( 29 ) ►  十一月 ( 18 ) ►  十月 ( 16 ) ►  九月 ( 15 ) ►  七月 ( 11 ) ►  六月 ( 27 ) ►  五月 ( 41 ) ►  四月 ( 38 ) ►  三月 ( 43 ) ►  二月 ( 38 ) ►  一月 ( 47 ) ►  2013 ( 267 ) ►  十二月 ( 22 ) ►  十一月 ( 23 ) ►  十月 ( 35 ) ►  九月 ( 28 ) ►  八月 ( 13 ) ►  七月 ( 32 ) ►  六月 ( 27 ) ►  五月 ( 13 ) ►  四月 ( 19 ) ►  三月 ( 18 ) ►  二月 ( 12 ) ►  一月 ( 25 ) ►  2012 ( 49 ) ►  十二月 ( 2 ) ►  八月 ( 5 ) ►  七月 ( 5 ) ►  六月 ( 2 ) ►  五月 ( 4 ) ►  四月 ( 5 ) ►  三月 ( 4 ) ►  二月 ( 10 ) ►  一月 ( 12 ) ►  2011 ( 178 ) ►  十二月 ( 9 ) ►  十一月 ( 17 ) ►  十月 ( 5 ) ►  九月 ( 1 ) ►  八月 ( 7 ) ►  七月 ( 7 ) ►  六月 ( 5 ) ►  五月 ( 12 ) ►  四月 ( 29 ) ►  三月 ( 29 ) ►  二月 ( 21 ) ►  一月 ( 36 ) ►  2010 ( 201 ) ►  十二月 ( 18 ) ►  十一月 ( 30 ) ►  十月 ( 23 ) ►  九月 ( 8 ) ►  八月 ( 11 ) ►  七月 ( 9 ) ►  六月 ( 16 ) ►  五月 ( 14 ) ►  四月 ( 47 ) ►  三月 ( 5 ) ►  二月 ( 3 ) ►  一月 ( 17 ) ►  2009 ( 256 ) ►  十二月 ( 18 ) ►  十一月 ( 29 ) ►  十月 ( 6 ) ►  九月 ( 38 ) ►  八月 ( 25 ) ►  七月 ( 24 ) ►  六月 ( 17 ) ►  五月 ( 16 ) ►  四月 ( 4 ) ►  三月 ( 24 ) ►  二月 ( 34 ) ►  一月 ( 21 ) ►  2008 ( 303 ) ►  十二月 ( 28 ) ►  十一月 ( 18 ) ►  十月 ( 26 ) ►  九月 ( 29 ) ►  八月 ( 30 ) ►  七月 ( 62 ) ►  六月 ( 59 ) ►  五月 ( 48 ) ►  四月 ( 2 ) ►  三月 ( 1 ) 文章標籤 ♥ ( 19 ) ♥♥ ( 10 ) ♥♥♥ ( 9 ) 人工智慧 ( 86 ) 八字學 ( 1 ) 大數據 ( 6 ) 小狐狸 ( 73 ) 小狐狸生態 ( 22 ) 工作 ( 48 ) 手機 ( 29 ) 日文 ( 3 ) 木工 ( 23 ) 比特幣 ( 1 ) 主機 ( 72 ) 占星術 ( 6 ) 生活 ( 1129 ) 生活雜記 ( 8 ) 多媒體 ( 4 ) 好文 ( 6 ) 好書 ( 391 ) 好站 ( 250 ) 自作專案 ( 3 ) 自動化 ( 28 ) 系統 ( 4 ) 其他 ( 65 ) 易經 ( 1 ) 物理學 ( 10 ) 物聯網 ( 250 ) 信號處理 ( 4 ) 架站 ( 13 ) 相術 ( 2 ) 科學 ( 22 ) 英文 ( 9 ) 計算語言學 ( 3 ) 音樂 ( 72 ) 飛控 ( 25 ) 食譜 ( 70 ) 修行 ( 22 ) 借書 ( 5 ) 旅行 ( 44 ) 旅遊 ( 1 ) 書籤 ( 2 ) 索引 ( 30 ) 能源 ( 80 ) 動畫 ( 2 ) 區塊鏈 ( 3 ) 專案 ( 20 ) 排版 ( 3 ) 控制系統 ( 1 ) 教育 ( 17 ) 理財 ( 122 ) 軟體開發 ( 8 ) 通訊 ( 2 ) 備忘 ( 2 ) 創客 ( 31 ) 單晶片 ( 30 ) 智慧音箱 ( 1 ) 無人機 ( 18 ) 硬體 ( 38 ) 紫微斗數 ( 2 ) 統計 ( 4 ) 統計學 ( 10 ) 評論 ( 169 ) 雲端 ( 21 ) 園藝 ( 1 ) 新知識 ( 1 ) 新產品 ( 1 ) 新軟體 ( 7 ) 資安 ( 7 ) 資料科學 ( 31 ) 資料庫 ( 4 ) 農業 ( 23 ) 電子零件 ( 27 ) 電子學 ( 18 ) 電腦 ( 210 ) 電腦技術 ( 12 ) 電路學 ( 1 ) 演算法 ( 8 ) 網頁技術 ( 218 ) 網路 ( 43 ) 網路爬蟲 ( 23 ) 網購 ( 316 ) 語言學 ( 55 ) 語音技術 ( 4 ) 語音辨識 ( 10 ) 影像辨識 ( 2 ) 數學 ( 21 ) 線上工具 ( 2 ) 線性代數 ( 1 ) 論文 ( 1 ) 養生 ( 20 ) 樹莓派 ( 187 ) 機器人 ( 6 ) 機器學習 ( 152 ) 歷史 ( 59 ) 戲劇 ( 22 ) 還書 ( 116 ) 韓文 ( 1 ) 韓劇 ( 10 ) 醫藥 ( 54 ) 繪圖 ( 3 ) 藝術 ( 2 ) 讀書劄記 ( 9 ) 邏輯設計 ( 17 ) Android ( 13 ) AngularJS ( 1 ) Anime ( 1 ) App ( 30 ) Appfog ( 4 ) Arduino ( 179 ) ASP ( 11 ) AutoIt ( 15 ) AWS ( 16 ) Azure ( 1 ) BananaPi ( 3 ) BigData ( 1 ) Blynk ( 7 ) Bootstrap ( 23 ) brython ( 1 ) C語言 ( 19 ) Chart.js ( 2 ) Chatbot ( 5 ) Cheatsheet ( 2 ) CKeditor ( 1 ) Corona ( 3 ) CSS ( 10 ) D3.js ( 6 ) DataMining ( 1 ) Django ( 19 ) DSP ( 3 ) EasyUI ( 48 ) ESP32 ( 52 ) ESP8266 ( 144 ) Excel ( 9 ) ExtJS ( 24 ) Fintech ( 16 ) Firebase ( 3 ) Flask ( 3 ) FPGA ( 1 ) GAE ( 40 ) GAS ( 2 ) GCP ( 4 ) Git ( 10 ) GitHub ( 3 ) Go ( 1 ) Google ( 24 ) Highcharts ( 2 ) HomeAssistant ( 1 ) HTML5 ( 39 ) IC設計 ( 5 ) Java ( 100 ) JavaFX ( 1 ) Javascript ( 87 ) Jetson ( 10 ) Joomla ( 3 ) jqPlot ( 1 ) jQuery ( 123 ) jQueryMobile ( 30 ) jQueryUI ( 35 ) JSP ( 2 ) Julia ( 12 ) Kali ( 1 ) Keras ( 1 ) Kotlin ( 1 ) Line ( 1 ) Linux ( 12 ) LoRa ( 12 ) Lua ( 6 ) MacOS ( 11 ) Masonite ( 1 ) Mathematics ( 2 ) Matplotlib ( 5 ) mermaid.js ( 1 ) Micro:bit ( 3 ) MicroPython ( 91 ) MongoDB ( 3 ) Nginx ( 3 ) NLP ( 29 ) NLTK ( 5 ) Node.js ( 14 ) NoSQL ( 1 ) OpenCV ( 2 ) p5.js ( 14 ) Pandas ( 3 ) PHP ( 109 ) Praat ( 23 ) Python ( 412 ) PyTorch ( 11 ) R語言 ( 42 ) RaspberryPi ( 131 ) Ruby ( 1 ) scikit-learn ( 2 ) SciPy ( 4 ) SpaCy ( 4 ) SQL ( 15 ) SQLite ( 1 ) STM32 ( 2 ) Swift ( 6 ) TCP/IP ( 6 ) TensorFlow ( 19 ) tensorflow.js ( 3 ) Three.js ( 1 ) TinyMCE ( 1 ) tkinter ( 16 ) UAV ( 3 ) Ubuntu ( 2 ) Verilog ( 1 ) VHDL ( 1 ) VoIP ( 1 ) Vue ( 6 ) WebGL ( 3 ) Wireshark ( 2 ) WordPress ( 18 ) WSH ( 5 ) 常用連結 QR-Code產生器 爾灣分校Python函式庫 神秘金字塔 MyGitHib Google協作平台 Hinet信箱 Dropbox box Aliexpress bittrix24 CCompiler CH.Tseng G.T.Wang部落格 HomePi HTMLCleaner IBMSwiftSandbox Inside Kej'sRetriever muckibu.de OneDrive Pinterest Python機器學習 Rmanual Rapidgator TutorialsPoint VirusTotal Youtube-mp3 Zamzar線上轉檔 中華電信Hibox 台大網路測速 線上C編譯器 線上OCR 螢幕錄影 訂台鐵與高鐵票 高師大圖書館 高科大圖書館 高雄市立圖書館 線上方程式 音頻轉換器 CSS色碼表 Youtube轉mp3 熱門文章 Arduino基本語法筆記 Arduino的程式語法基於C/C++,其實就是客製化的C/C++語言,其程式架構仿自廣為藝術與設計界人士熟悉的Processing語言,而其開發工具ArduinoIDE則是衍生自以Processing為基礎的電子開發設計平台Wiring.由於... Arduino串列埠測試(UART) 今天要下午才進辦公室,早上都在家,所以研究測試了一下Arduino的串列埠,紀錄整理如下.所謂串列埠是源自IBMPC的RS-232通訊協定,也就是個人電腦後面的COM埠(9針公座DB-9),現在新的桌上型電腦與筆電大都沒有接出COM埠了,... GooglePlay安裝或更新App一直顯示"正在等待下載"問題 最近我的Note8手機要更新Yahoo股市App時一直顯示 "正在等待下載",但是進度條卻不動:我按GooglePlay左上角的三條槓選"設定",點"應用程式下載偏好設定"改... Python內建GUI模組tkinter測試(一):建立視窗 最近因為玩樹苺派的關係,接觸到Python內建的GUI開發模組Tkinter(意思是TkInterface),初步覺得比用Java的Swing還要來得容易,因此就來學看看唄!Tk原先是為Tcl語言所開發的GUI套件,因為是T... Java複習筆記:陣列 陣列是程式員最常用的資料結構,Java的陣列屬於傳統的固定式陣列,亦即陣列元素資料類型必需相同,而且元素個數必須先宣告才能使用,不像Javascript等動態語言之陣列允許異質性資料,且長度不需先宣告即可使用.當然,Java陣列也不支援關聯性陣列,Java... jQueryUI的日期選擇器datepicker測試 近兩周都在玩jQueryUI的日曆小工具(或日期選擇器datepicker),就是讓使用者可以直接在日曆上選取日期的小工具,而且日期格式可以指定.這個widget小工具根據書上講是jQueryUI最古老,功能選項也最龐大的一個,照官網範例三兩下就看到... 邏輯設計筆記序向篇:Latch(電栓)與Flip-Flop(正反器) 這個月為了邏輯設計授課努力打了200多頁簡報,為了查閱方便整理在此,同時也讓我的外籍學生們可利用Google翻譯以母語複習上課內容.由於本次協同授課負責序向邏輯部分,因此先整理序向邏輯,組合邏輯以後有機會再整理.雖然說現在設計IC大都改用VHDL或V... ★ESP8266WiFi模組ATcommand測試 這是我今年四月以$180向露天賣家XLAN買的超小型WiFi模組(現已降價為$145),採用上海樂鑫科技的ESP8266晶片,板子型式是ESP-01,這是Arduino最經濟的WiFi方案,因為目前買一塊內建WiFi的Arduin... ★ESP8266WiFi模組與Arduino連線測試 經過兩個月來的摸索,對於Arduino經由ESP8266連上網路的實驗終於來到實作階段.這當中也分心去研究如何製做ESP8266的轉接板,雖已經有腹案,但覺得還是先把連網實驗做完再來搞定轉接板好了.這兩塊板子互連主要有兩個障礙,一是開發階段串列埠不夠的問題... Arduino的聲音測試(一) 很早就想做這個實驗,因為只需要ArduinoNANO加上一個蜂鳴器就可以進行了,頂多加一顆按鈕開關,這跟物聯網無關,因此不需要用到ESP8266上網.趁著中秋連假,就把這個簡單的小實驗做完吧!以下實驗所用的程式是參考下列幾本書裡面的範例加以修改來的:... 關於我自己 小狐狸事務所 熱愛自由不想被拘束,無法忍受無聊而不斷學習的射手座,因為記性不好必須在部落格紀錄思考學習與生活點滴的平凡上班族. 檢視我的完整簡介 追蹤者



請為這篇文章評分?