發表文章

目前顯示的是有「C語言技術」標籤的文章

C語言筆記-用結構體讓函式可以返回多個資料

C與言的函式無法回傳一個以上的值,不過可以透過回傳一個結構來完成: 範例: #include <stdio.h> #include <stdlib.h> #include <math.h> typedef struct { double width,height; }size_s; size_s width_height ( char * papertype){ return ! strcasecmp(papertype, "A4" ) ? (size_s){.width = 210 ,.height = 297 } : ! strcasecmp(papertype, "Letter" ) ? (size_s){.width = 216 ,.height = 279 } : ! strcasecmp(papertype, "Legal" ) ? (size_s){.width = 2216 ,.height = 356 } : (size_s){.width = NAN,.height = NAN}; } int main ( void ) { size_s a4size = width_height( "A4" ); printf( "width=%g,height=%g \n " ,a4size.width,a4size.height); } 透過 condition?iftrue:else的格式來取代switch,而且也可以放在return後面 "21centry c"一書的作者很提倡這種風格。 除了用結構體之外,也可以用指標透過參數在函式內修改來完成: void width_height ( char * papertype, double * width, double * height); 這種風格是我常用的方法,也是最常見的格式,不過很容易混淆輸入跟輸出。

C語言筆記-結構體的初始化

圖片
一般程式常看到這種結構的初始化方式: direction_s upright={NULL,0,1,1,0}; direction_s upright = { NULL , 0 , 1 , 1 , 0 }; 這種填充式的結構初始化很糟糕,因為必須要記住所有的結構成員順序,這種方式很不人性化 初始化應該使用上標籤: #include <stdio.h> #include <stdlib.h> typedef struct { int one; double two,three,four; }n_str; n_str d = { 100 , .four = 10 , .two = 30 , }; int main ( void ) { printf( "%d %g %g %g" ,d.one,d.two,d.three, d.four); } 從上面的程式碼中可以看到初始化結構成員,順序不用與聲明的一致,編譯器也是可以成功編譯 未聲明的原素初始化為0,沒有初始過的都是設為0 可以混和聲明 在"21 century c"中有這個範例可以參考: #include <stdio.h> #include <stdlib.h> typedef struct { char * name; int left,right,up,down; }direction_s; void this_row (direction_s d); void draw_box (direction_s d); int main ( void ) { direction_s D = {.name = "left" ,.left = 1 }; draw_box(D); D = (direction_s){ "upper right" ,.up = 1 ,.right = 1 }; draw_box(D); draw_box((direction_s){}); return 0 ; } void this_row ...

C語言筆記-用__VA_ARGS__ 完成free_all() 函式向量化

free()是一個長見的函式,不過他一次只能接受一個參數 因此常常在函式尾巴必須加上一堆釋放記憶體的工作: free(ptr1); free(ptr2); free(ptr3); 這樣的程式碼很不美觀,很冗長,寫成這樣: free_all(ptr1,ptr2,ptr3); 完成方法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <stdio.h> #include <stdlib.h> #define Fn_apply(type,fn,...){\ void *stopper_for_apply=(int[]){0};\ type **list_for_apply=(type*[]){__VA_ARGS__,stopper_for_apply};\ \ for(int i=0;list_for_apply[i]!=stopper_for_apply;i++)\ fn(list_for_apply[i]);\ } #define Free_all(...) Fn_apply(void, free,__VA_ARGS__); int main ( void ) { int * x = malloc( 1000 ); int * y = malloc( 1000 ); int * z = malloc( 1000 ); Free_all(x,y,z); } 第5行:     提高安全性, 第6行:     需要一個終止符,卻保不與任何使用中的指標匹配,包括任何的NULL指標。 因此我們使用複合常量形式定義醫包含整數的陣列,並定義一個指向這個陣列的指標。 for迴圈的終止條件所觀察的是指標本身,而不是他指向的東西

C語言筆記-用__VA_ARGS__ 製作Foreach

很常看到陣列或是結構內使用複合常量,例如: char ** strings = ( char * []){ "one" , "two" } 現在將它放入一個For迴圈中。循環得第一部分聲明了這個字串數組,然後開始便歷整個陣列,直到最後的NULL標記。 #include #include typedef char * string; int main ( void ) { string str = "thresad" ; for (string * list = (string[]){ "111" ,str, "ropw" , NULL }; * list; list ++ ) printf( "%s " , * list); } 這樣的程式碼不易閱讀,很雜,所以用宏定易簡化: #include #include typedef char * string; #define Foreach_string(iterator,...) for(string *iterator=(string[]){__VA_ARGS__,NULL};*iterator;iterator++) int main ( void ) { string str = "two" ; Foreach_string(i, "one" ,str, "three" ){ printf( "%s " , * i); } } 此文章內容參考"21 century c"一書,在此做筆記 如須刪除請告知 謝謝

C語言筆記-用__VA_ARGS__ 完程多列表的宏定義

如果現在想要發送兩個任意長度列表,可以這樣做 假設現在程式顯示一個訊息,同時將一個機器獨的錯誤碼發送到日誌: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <stdio.h> #include <stdlib.h> #define fileprintf(...) fprintf(stderr,__VA_ARGS__) #define doubleprintf(human,machine) do{\ printf human;\ fileprintf machine;\ }while(0) int main ( void ) { int x =- 1 ; if (x < 0 ) doubleprintf(( "x is negative(%d) \n " ,x),( "NEGVAL:x=%d \n " ,x)); } 他會被拓展成: 1 2 do {printf ( "x is negative (%d) \n " , x); fileprintf ( "NEGVAL: x=%d \n " , x);} while ( 0 ); 下面試另一個例子: 給出兩列R和C,每個單元(i,j)保持Ri Cj的乘積,此城市的核心是matrix_cross宏 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include <stdio.h> #include <stdlib.h> #include <math.h> #define make_a_list(...) (double[]){__VA_ARGS__,NAN} #define matrix_cross(list1,list2) matrix_cross_base(make_a_list list1,\ make_a_list list2) void...

C語言筆記-透過 __VA_ARGS__ 達成可變參數的宏定義

圖片
      C語言中,可改變長度的函式很容易發生問題(之後提到) 不過要做到可改變參數的宏定義式很簡單的,關鍵字是:   __VA_ARGS__ 期展開的結果是給定的原素集合 舉例: #include <stdio.h> #include <stdlib.h> #define forloop(i,loopmax,...) for(int i=0;i<loopmax;i++)\ {__VA_ARGS__} int main ( void ) { int sum = 0 ; forloop(i, 10 ,sum += i;printf( "sum to %i:%i \n " ,i,sum);); } 輸出結果為: 不過實戰上不會這樣使用。 可參考這一篇: isnan與宏定義來做一個可調整輸入參數的功能

C語言筆記-文本處理(3) 用asprintf strdup 做一個實用的可拓展字串的宏定義

用asprintf()把一段文字添加到另一個字串尾部:   asprintf(&s,%s and other string: %s",s,add); 如果今天很多字串要處理,變成 int num=5,id=27; char *s=strdup("select"); asprintf(&s,"%sscol%i\n",s,num); asprintf(&s,"%sfrom tab\n",s); asprintf(&s,"%swhere person id=%i",s,id); 得到: selectscol5 from tab where person id=27 上面這樣看,除了不整潔 也很厭煩, 也有記憶體洩漏的風險在,因為s的原位址中的對向在asprintf向s提供一個心位置時不會被釋放掉,如果生成的字串數量是未知得,長度也是未知就可以透過宏定義來簡化: #define Sasprintf(write_to, ...){ \ char *tmp_string_for_extend=(write_to); \ asprintf(&(write_to),__VA_ARGS__); \ free(tmp_string_for_extend); \ } int main(void) { int i=666; char *q=NULL; char *k="Jack"; Sasprintf(q,"Hello~~ "); Sasprintf(q,"%s %s Nice to meet you  %d.",q,k,i); printf("%s\n",q); } Sasprintf宏加上strdup,差不多100%的字串處理都能應付了,除了一個下面提到的問題乙級偶爾需要使用free外,可以不必續考慮記憶體的問體。 這個問題是:  如果q沒有初始為NULL或是忘記使用strdup,那麼第一次使用Sasprintf宏將會釋放...

C語言筆記-文本處理(2)常數字串的陷阱,用 strdup 處理

以下程式創了兩個字串: include <stdio .h=""> int main(void) {     char *s1="First";     char *s2;     asprintf(&amp;s2,"Second");     printf("%s\n,s1);     printf("%s\n,s2); } 兩種都產生了一個字串,但編譯器會用不同的方式去處理,如果不熟悉就容易造成錯誤。 "First"就是在一個空間規劃好噁的位置上,S1直接指向這個位址,如此的簡單快速 閱讀程式碼s1跟s2的行為是相同的,但是我們無法修改也不能去釋放s1 如果加上: s2[0]='f'; s1[0]='t';   //錯誤 free(s2); free(s1);    //錯誤 s1指定的內容是絕對唯讀的。 這個錯誤在我一開始踏入這行的時候遇過,被折騰了一下子 有一個簡單的方解決方法:strdup。 這是 POSIX的標準函式,字串複製(string duplicate)的簡寫:  char *s3=strdup("First"); "First"也是以硬編碼的方式寫入,但s3視這個常數字串的一份複製,所以可以自由修改。 用strdup就可以用同一種方式處理自串,不用擔心踩雷。 如果不支援strdup,就用asprintf自己寫一個 char *strdup(char const* in){     if(!in) return NULL;     char *out;     asprintf(&amp;out,"%s",in):     return out; }

C語言筆記-文本處理(1) 善用 asprintf取代sprintf

sprintf應該都不陌生,不過sprintf()使用上世有風險的,它的原型是: int sprintf(char *str,const char *format,...) 也就是說使用前必須先建立好空間,再用sprintf()將內容填入: int temp=10; char str[20]; sprintf(str," %d * %d *= %d",temp,temp,temp*temp); str輸出來看的結果為:10 * 10 = 100 但如果今天temp為10000,輸出就是: 10000 * 10000 = 100000000 則長度超過20,造成over flow的問題 用snprintf/asprintf取代不安全的sprintf才是最好的 #include <stdio.h> void get_strings(char const *in){ char *cmd; int len = strlen("strings ") + strlen(in) + 1;  cmd=(char*)malloc(len); snprintf(cmd, len, "strings %s", in);     printf("cmd:%s\n",cmd); free(cmd); } 用snprintf,需要先確定長度,還需要自己調用malloc 使用asprintf,它會自動調用malloc void get_strings_(char const *in){ char *cmd; asprintf(&cmd, "strings %s", in);     printf("cmd:%s\n",cmd); free(cmd); } --------------------- asprintf跟sprintf很像,區別在於傳入的是字串的位址而不是字串本身, asprintf()會自動分配空間 int asprintf(char **str,char *fmt,...) _...

C語言筆記-探索goto 善用goto

goto在很多交科書上都是建議大家不要使用,不過善用goto可以讓函數更加優雅: double sum_to_first_nan ( double * vector, int vector_size, double * vector2, int vector2_size, int * error) { double sum = 0 ; * error = 1 ; for ( int i = 0 ;i < vector_size;i ++ ) { if (isnan(vector[i])) goto outro; sum += vector[i]; } for ( int i = 0 ;i < vector2_size;i ++ ) { if (isnan(vector2[i])) goto outro; sum += vector2[i]; } * error = 0 ; outro: printf( "the sum until the first nan was:%g \n " ,sum); free(vector); free(vector2); return sum; } 此文章內容參考"21 century c"一書,在此做筆記 如須刪除請告知 謝謝

C語言筆記-強制轉換的重要

在整數相除,沒做強制轉換都會回傳一個整數,這兩條都是正確的: 4/2==2 5/4==1 第二條是容易導致錯誤的地方,有時後趕code很容易忽略到這個錯誤,只要養成一個習慣就好,假設i是整數,則(i+0.0)就可以了,括號很重要! int two=2; 3/(two+0.0)==1.5 3/(2+0.0)==1.5 3/2.0==1.5 2/2.==1.5 也可以用強制轉換的方式: 3/(double)two==1.5 3/(double)2==1, 不過用前面的方式比較有美感,在/後面加上0.0是一種好習慣 但是陣列索引要注意一下,陣列索引只能是整數: 4/(double)2==2.0 //這是正確的 list[4/(double)2]  //編譯器會提出錯誤,解決方法如下: list[(int)(4/(doube)2)] //這是正確的 也可以: int index=4/(double)2; list[index]; 此文章內容參考"21 century c"一書,在此做筆記 如須刪除請告知 謝謝

C語言筆記-malloc前的強制轉換

看別人的程式多少都會看到在malloc前面加上強制轉換:      dlouble * p=(double*)malloc(sizeof(length*(sizeof(double)); 會這樣加是因為早期的malloc回傳値是char*,所以要做強制轉 現在已經不需要加了,因為malloc回傳的是一個void指標,編譯器會自動轉換

C語言筆記-指標(9) 用typedef簡化指標,函數指標

有時候看到複雜類行的指標,可以考慮用typedeg進行簡化 typedef cahr* string; 拿上一篇的範例進行簡化: #include <stdio.h> int main (){ char * list[] = { "first" , "second" , "third" , NULL }; for ( char ** p = list; * p != NULL ;p ++ ) printf( "%s \n " ,p[ 0 ]); } 聲明變的清楚易懂,list就是一個字串陣列 string *p表示p是一個指向字串的指標,所以*p表示一個字串 --------------------------------------------------------- 有一函數:                         double a_fn(int,int); 只要添加星號(括號一定要加,保證優先權),就是描述一個指向這類行的函數指標:                           double (*a_fn_type)(int,int); 前面再加上typedef來定義一種類型:                          typedef double(*a_fn_type)(int,int); 可以把它當成一個類型來使用,聲明一個接受另一個函數做為輸入參數函數:    double apply_a_fn ( a_fn_type f,int first_int,int second_in){         return f(first_in,second_in); 指標其...

C語言筆記-指標(8)指標的運算

    陣列的某個成員可以用陣列的基址加上一個偏移量來表示 聲明一個指標 double *p; 他當做基址,可以向陣列一樣使用偏移量來表示: p[0]為第一個成員,p[1]為第2個成員 以此類推 所以只要有指標的基址跟相鄰成員間的距離就可以當成陣列來使用了。 教科書常出現: p[1]同等於*(p+1) 所以 陣列第一個成員是p[0] ==*(p+0) 以下有三條規則: 可以通過指標形式 double *p,或是靜態/自動形式的double p[10]來聲明陣列 無論哪個,第n+1個陣列成員都是p[n],第一項是0不是1 如果要第n個成員的位址,使用&符號:&p[n] 第一個成員的位址&p[0]==p 簡單的程式範例: #include <stdio.h> int main ( void ){ int evens[ 5 ] = { 1 , 2 , 3 , 4 , 5 }; printf( "the first even number is%i \n " , * evens); int * positive_evens =& evens[ 1 ]; printf( "the first positive even number is%i \n " ,positive_evens[ 0 ]); } --------------小技巧--------------- 在"p+1表示陣列中下一個成員對只(&p[1])"這個規則的基礎下,在下例使用 一個備用指標來指向list的頭,然後p++在陣列中向前遍歷,直到陣咧尾部的NULL標記,從而得到整個陣列的值。 #include <stdio.h> int main (){ char * list[] = { "first" , "second" , "third" , NULL }; for ( char ** p = list; * p != NU...

C語言筆記-指標(7) *號帶來的錯亂

指標的具體涵義取決於生明的方式:   int *i; *i是個整數,所以透過int *i把*i聲名為整數 下列舉出聲名中的*i與聲名外的*i涵義不同: int *i=malloc(sizeof(int));    //正確 *i=23;                                   //正確 int *i=23;                              //錯誤 簡單的說就是: 當用於聲明時,星號表示指標 不用於聲明時,星號表示指標的值            int i=10;            int*j=&i;            int *k=j;           *j=100; 第二行是對的,因為*j是一個聲明,因此表示指標 第三行 *k是一個聲名指標,把他賦值給j是合法的 第四行,*j不在聲明中,因此他表示一個普通整數,然後把100賦值給它(i                 也會隨之改變) 結論: 在聲明中看到*i,表示他是只向某個對向的指標 在非聲明中看到*i,他是指標所指向的值 此文章內容參考"21 century c"一書,在此做筆記 如須刪除請告知 謝謝

C語言筆記-指標(6) 少用malloc來操作記憶體

malloc,讓C語言工程師容易埋下陷阱的程式,要避免malloc的陷阱最好的方式就是避免使用。 需要使用malloc的常見原因: 改變已存在的陣列長度 無法從函數中返回一個陣列 此文章內容參考"21 century c"一書,在此做筆記 如須刪除請告知 謝謝

C語言筆記-指標(5) 用memmove來複製陣列

如果要複製陣列,可以用memmove來完成 #include <assert.h> #include <string.h> int main ( void ){ int abc[] = { 1 , 2 , 4 }; int * copy1,copy2[ 3 ]; copy1 = abc; memmove(copy2,abc, sizeof ( int ) * 3 ); abc[ 0 ] = 5 ; assert(copy1[ 0 ] == 5 ); assert(copy2[ 0 ] == 1 ); } copy2是透過memmove,故abc改變不會影響 此文章內容參考"21 century c"一書,在此做筆記 如須刪除請告知 謝謝

C語言筆記-指標(4)-函數返回的陷阱

圖片
函數可以回傳指標,會有陷阱存在,過往經驗不足的時候也有吃過鱉: #include <assert.h> #include <stdio.h> typedef struct powers{ double base,square,cube; }powers; powers get_power ( double in){ powers out = { .base = in, .square = in * in, .cube = in * in * in }; return out; } int * get_even ( int count){ int out[count]; for ( int i = 0 ;i < count;i ++ ) out[i] = 2 * i; return out; //埋下問題 } int main (){ powers threes = get_power( 3 ); int * evens = get_even( 3 ); printf( "threes:%g \t %g \t %g \n " ,threes.base,threes.square,threes.cube); printf( "evens: %i \t %i \t %i \n " ,evens[ 0 ],evens[ 1 ],evens[ 2 ]); } 執行2次: 原因出函式退出時,陣列內容的內存會被釋放,但指標一然會回傳 不過通常編譯器都會提出警告! 此文章內容參考"21 century c"一書,在此做筆記 如須刪除請告知 謝謝

C語言筆記-指標(3) 結構複製,陣列創造與別名

要複製結構體,只需要一個等號就可以完成。 #include <assert.h> typedef struct { int a,b; double c,d; int * e; }test; int main ( int argc, char ** argv){ test s1 = { .b = 1 , .c = 2 , .d = 3 , .e = ( int []){ 4 , 5 , 6 } }; test s2 = s1; s1.b = 15 ; s1.c = 55 ; s1.e[ 0 ] = 7 ; assert(s2.a == 0 ); assert(s2.b == 1 ); assert(s2.c == 2 ); assert(s2.d == 3 ); assert(s2.e[ 0 ] == 7 ); } 這些斷言都會通過     這個demo可以發現 修改s1.b s1.c並不會影響s的b c值,所以這些數據是創建一份複製。     但是指標的複製仍然是只向原先結構內的數據,所以修改s1.e時s2.e也會跟著改變,解決辦法就是在結構有指標的時候用函數進行複製。 _ 指標指向一個陣列: #include <assert.h> int main (){ int abc[] = { 1 , 2 , 4 }; int * copy = abc; copy[ 0 ] = 5 ; assert(abc[ 0 ] == 5 ); } 指標被修改,則原陣列內容也被修改 此文章內容參考"21 century c"一書,在此做筆記 如須刪除請告知 謝謝

C語言筆記-指標(2) 沒有用malloc的指標

現在要把A設置給B,通常有兩種目的的可能: 把B的值複製給A,此時A做任何改編接不會影響B 把A變成B的別名。此時A做改變B也會改變 所以在寫程式必須明確說明是要複製一份還是創建一個別名。 部使用malloc可以讓程式更靈活: 假設有一個函示: void inc(int *i){         (*i)++; } 一般來說會這樣使用: int *i=malloc(sizeof(int)); *i=10; inc(i); . . . free(i); 善用這種自動分配內存的話可以直接: int i=12; inc(&i); ----------------------------------------------------------------- 邊寫程式,遇到這種把A設至成B的狀況 可以視情況決定是改成別名 此文章內容參考"21 century c"一書,在此做筆記 如須刪除請告知 謝謝