發表文章

目前顯示的是 12月, 2019的文章

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); 指標其實很簡單,就只是一個別名或位置而已,根本不用涉及不同類型的記憶體管理。 此文章內容參考"21 century c"一書,在此做筆記 如須刪除請告知 謝謝

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"一書,在此做筆記 如須刪除請告知 謝謝

C語言筆記-指標(1)

指標最容易搞混的就是: int array[] int *ptr_array 這兩種之間的差別 int array[10]: 程式會做出已下的行為 在棧上分配一個可以容納10個整數的空間 把array當成一個指標 將指標與分配好的位址綁定   這個空間是由系統自動分配出來的,所以不能改變其大小,如果拖離範圍後被釋放了,依然可以再拿回這個空間。    但array這個指標不能指向其他地方,因為它與分配好的位址是綁定的。 int *ptr_array: 程式指會做一件事,就是將array聲明為一個指標 這個指標沒有綁定任何位址,可以重心只想任何地方,以下都是合法使用:      ptr_array=malloc(sizeof(int)*10);                      or      ptr_array=array; 所以 int array[]和int *ptr_array 是不一樣的,已前看過幾本書都是把它們話上等號 不過在函示傳遞就沒有什麼區別了: int fun(int *ptr_array,int array[]); 本質上他們做的事情都是一樣的 此文章內容參考"21 century c"一書,在此做筆記 如須刪除請告知 謝謝

C語言筆記-const(4) char const **的問題

圖片
#include <stdbool.h> #include <strings.h> bool check ( char const ** in){ return ( ! strcasecmp(in[ 0 ], "123" ) && ! strcasecmp(in[ 1 ], "456" )) || ( ! strcasecmp(in[ 0 ], "abc" ) &&! strcasecmp(in[ 1 ], "def" )); } int test () { char * a[] = { "123" , "456" }; return check( & a[ 0 ]); } 我用Devc++編譯慧無法通過: 需要在這裡做一個類型轉換才行: return check((char const**)&a[0]); 此文章內容參考"21 century c"一書,在此做筆記 如須刪除請告知 謝謝

C語言筆記-const(3) 衝突

在實際操作中會出現const帶出來的問題, 一個const的指標,有時候需要把它當做輸入參數給一個沒有const標記的函示 這時候可以用強制轉換成其他非常數的指標就好: void set(int *a,int *b){     a[0]=1; } int main(void){     int a[10]={};     int const *b=a;     set(a,(int*)b); //這麼一來就可以跳過編譯器給的警告了 } 此文章內容參考"21 century c"一書,在此做筆記 如須刪除請告知 謝謝

C語言筆記-const(2)

這是一個在網路上或書上常看到的: int const int const* int *const int *const* int const** int const*const* 要讀這種宣告就是由右往左讀: int const=一個常數整數 int const*=指向一個常數整數的指標 int *const=指向一個整數的常數指標 int *const*=一個指向常數指標的指標,常數指標指向一整數 int const**=一個指向指標的指標,指標指向常數整數 int const*const*=一個指向常數指真的指針,常數指針指向常數整數 int const 可以寫成const int

C語言筆記-const(1)

const是一個常見的常數,函數輸入參數如果是非指標的數據,通常就是理解成這數據在此函數中不會被修改,單純做傳遞 如果輸入參數有指標的話就很曖昧了。 在傳遞的指標數據前面加上const做修飾就很容易看出這參數在函示中並不會被修改,這對看code的人來說非常方便,就算未來自己看道也可以很快清楚。 不過還是能透過指標來修改const修飾過的變數: void set(int *a,int const *b){     a[0]=1; } int main(void){     int a[5]={};     int const *b=a;     set(a,b); } 把結果輸出可以看到有const修飾的b也是被修改了

C語言筆記 -避免頭文件(.h)重複包含

如果在a.c中 include 一個a.h 在b.c中include 一個a.h 如果出現編譯錯誤,表示頭文件重複包含了 解決方法有兩個 1.C與言標準提供這個辦法: 在頭文件中加入 #ifndef A_H #define A_H //your code #endif 2.加上pragma once 在頭文件上面加上 #pragma once   這樣編譯器就不會發生二次包含 此文章內容參考"21 century c"一書,在此做筆記 如須刪除請告知 謝謝

C語言筆記 -用宏定義做一個可返回的斷言

做一個斷言失敗會回傳的宏定義 #include <stdio.h> #include <stdlib.h> #define Claim(assertion,returnval)  if(!(assertion))  \           {fprintf(stderr,#assertion" failed to be true.  \ Return " # returnval"\n");return returnval;} int test(int x,int y){     Claim(x==y,-1);     return 0; } 如果沒有回傳值的函數可以這樣做: void doSomethings(int x,int y) {      Claim(x==y, );      return; } 此文章內容參考"21 century c"一書,在此做筆記 如須刪除請告知 謝謝

C語言筆記 -預處理器的技巧(2)

前偏:C語言筆記 -預處理器的技巧(1) 如果想要將兩個非字串連在一起,可以用兩個#: ## #include <stdio.h> #include <math.h> #define Setup_list(name,...)                                                            \         double *name  ## _list=(double[]){__VA_ARGS__,NAN}; \         int name ##_len=0;                                                                 \         for(name##_len=0;!isnan(name ## _list[name ##_len]);)       \                     name ## _len ++; int main() {     Setup_list(items,1,2,3,6);     double sum=0;     for(double *ptr=items_list;!isnan(*ptr);ptr++)         sum+=*ptr;     printf("sum:%g\n",sum); } 輸出結果為12 --------------------------------------------------- 也可以這樣使用 #define Length(in) in ## _len int main() {      int sum=0;      Setup_list(next_set,-1,2,4.5,0.1);      for(int i=0;i<Length(next_set);i++);         sum+=next_set_list[i];      printf("sum:%g\n",sum); } 此程式碼皆為"C成續設計新思維(第2版)"中的程式碼,在此做筆記 紀錄 如須刪除請告知 謝謝

C語言筆記 -預處理器的技巧(1)

圖片
預處理器保留的標記是 # ,有三種用法: 1.標記一個指令 2.輸入的字 字串化 3.連結符號 #用在宏定義中:把輸入參數轉換成一個字串 #include <stdio.h> #include <stdlib.h> #define  Printf_(cmd)   printf(#cmd ":%g\n",cmd); int main(){    double*plist=(double[]){11,22,33};   doublelist[]={55,66,77};         Printf_(sizeof(plist)/(sizeof(double)+0.0));    Printf_(sizeof(list)/(sizeof(double)+0.0)); } 在這裡 #cmd會把cmd轉成字串: 此文章內容參考"21 century c"一書,在此做筆記 如須刪除請告知 謝謝

C與言筆記-宏定義 define 的注意事項與陷阱

這幾篇會對宏定義#define做個筆記: ------------------------------------------------------------------------------------------- 宏定義主要執行文本交換,以下有些陷阱要避開: 陷阱1: #define double(x) 2*x 執行 int main(void) {     printf("double(4):%d\n",double(4)); } 結果為8 -------------------- 執行 int main(void) {     printf("double(1+1):%d\n",double(1+1)); } 結果不會是預期的8而是10 因為替換後變成 printf("double(1+1):%d\n",1+1*8); 為了預防這種錯誤,最好事都加上括號() #define double(x)  (2*x) 最常見的宏定義就是: #define max(a,b)   ((a)>(b)?(a):(b)) --------------------------------------------------------- 陷阱2: 宏定義的兩端要加上大括號{} #define increament(a,b)    \         (a)++;                          \         (b)++; 沒有加大括號,如果把他放在if後面會有此風險: int x=1,y=0; if(x>y)     increament(x,y); 展開的話變成: int x=1,y=0; if(x>y)     (x)++; (y++); 這是一種常見的宏定義陷阱 --------------------------------------------------------- 陷阱3: 就算加上大括號還是有陷阱在裡面要注意!! #define increase(a,b)  {\         (a)++;               

isnan與宏定義來做一個可調整輸入參數的功能

#include <math.h> #include <stdio.h> double sum_(double in[]) {     double sum=0;     for( int i=0;! isnan (in[i]);i++)         sum+=in[i];     return sum; } #define sum(...) sum_((double[]){__VA_ARGS__,NAN}) int main(){ double SUM=sum(3,4); printf("3+4=%g\n",SUM); } 再翻以前不知道看哪本書的時候從書上整理下來的code 利用__VA_ARGS__ 讓輸入可以更有彈性 不過這個方法有個問題:     我在幾個IDE中build,都發現isnan在不是double型態的情況下讀到NAN也不會返回正確的值 此文章內容參考"21 century c"一書,在此做筆記  如須刪除請告知 謝謝