1. 程式人生 > >C語言點滴學習筆記

C語言點滴學習筆記

資料型別

原碼、反碼、補碼

原碼:最高位為符號位,最高位為0表示正數,1表示負數
反碼:正數與原碼一樣,負數則對原碼除符號位外取反得到
補碼:正數與原碼一樣,負數則對原碼除符號位外取反再加一得到
計算機中,整數使用補碼錶示。
若用原碼錶示,則a+(-a)不為零,若用反碼錶示,則會出現兩個0的表示。用補碼可解決相加不為零,出現兩個0的表示的問題。

整數溢位

#include <stdio.h>
#include <limits.h>
int main() {
    printf("INT_MAX=%d\n",INT_MAX);
    printf
("INT_MAX+1=%d\n",INT_MAX+1); printf("INT_MAX+INT_MAX=%d\n",INT_MAX+INT_MAX); printf("---------------------------\n"); printf("INT_MIN=%d\n",INT_MIN); printf("INT_MIN-1=%d\n",INT_MIN-1); printf("INT_MIN+INT_MIN=%d\n",INT_MIN+INT_MIN); printf("---------------------------\n"); printf
("CHAR_MIN=%d\n",CHAR_MIN); printf("CHAR_MAX=%d\n",CHAR_MAX); printf("LONG_MAX=%ld\n",LONG_MAX); printf("LONG_LONG_MAX=%lld\n",LLONG_MAX); printf("SHORT_MAX=%d\n",SHRT_MAX); return 0; }

輸出(不同機器上不一樣)

INT_MAX=2147483647
INT_MAX+1=-2147483648
INT_MAX+INT_MAX=-2
—————————
INT_MIN=-2147483648
INT_MIN-1=2147483647
INT_MIN+INT_MIN=0
—————————
CHAR_MIN=-128
CHAR_MAX=127
LONG_MAX=2147483647
LONG_LONG_MAX=9223372036854775807
SHORT_MAX=32767

避免溢位的技巧

    int a,b;
    if((unsigned)(a)>INT_MAX) ;/*有符號正數a上溢位了*/
    if((unsigned)(a)<=INT_MAX); /*有符號負數a下溢位了*/

    if(a>INT_MAX-b) ;/*有符號數加法a+b會發生上溢位 */
    if(a<INT_MIN-b) ;/*有符號數減法a-b會發生下溢位*/

    if(a+b<a) ;/*無符號數加法已經發生上溢位*/
    if(a>b) ;/*無符號數減法a-b會發生下溢位*/

儘量不要使用無符號數,不然有符號數和無符號數混用發生隱式型別轉換會出現很多難以發現的問題。

浮點數

#include<float.h>

printf("%e\n",FLT_MAX);//float能儲存的最大範圍
    printf("%e\n",FLT_MIN);//最小範圍
    printf("%d\n",FLT_DIG);//有效位
    printf("%f\n",FLT_EPSILON);
    printf("%d\n",FLT_MAX_EXP);
    printf("%d\n",FLT_MIN_EXP);
    /*
3.402823e+038
1.175494e-038
6
0.000000
128
-125
*/
//FLT_EPSION的作用是比較兩個浮點數
    float a=10.0000001f,b=10.000000f;
    if(fabs(a-b)<FLT_EPSILON)
        printf("equals");
    else
        printf("not euqals");

sizeof

sizeof是C語言的關鍵字,返回型別為size_t的無符號整形。

    #include <mem.h>

    char str1[] = "abcdefg";
    char str2[] = "abc";
    if (strlen(str2) - strlen(str1)>0){
        printf("str2 is longer than str1\n");
    }
    //這裡仍然會列印,strlen返回size_t,因為無符號整數溢位了,所以應該這樣判斷
    if (strlen(str2) >strlen(str1));

因為有位元組對齊,struct的長度在不同平臺上可能不同,通常用sizeof來計算變數所佔記憶體大小。

    int a[] = {0, 1, 2, 3, 4, 5};
    char c[] = {'a', 'b'};
    int *ip = a;
    char *cp = c;
    printf("sizeof(ip)=%d\n", sizeof(ip));
    printf("sizeof(cp)=%d\n", sizeof(cp));
    printf("sizeof(a)=%d\n", sizeof(a));
    printf("sizeof(c)=%d\n", sizeof(c));
    printf("sizeof(a)/sizeof(a[0])=%d\n", sizeof(a) / sizeof(a[0]));
    /*
sizeof(ip)=4
sizeof(cp)=4
sizeof(a)=24
sizeof(c)=2
sizeof(a)/sizeof(a[0])=6
*/
//對陣列名是獲得整個陣列佔用位元組數;對指標,指標都只儲存一個地址,所以任何型別指標都佔用相同的位元組數。可以用sizeof(a)/sizeof(a[0])計算陣列長度。
    char* str="hello";
    printf("%s\n",str);
    char str1[]="hello";
    printf("%s\n",str1);

    char str2[2];
    str2[0]='a';
    str2[1]=0;
    printf("%s\n",str2);
/*hello
hello
ahello*/
//設定了長度的char陣列要自己補0作結束符

int的長度可能與CPU字長一樣,只是在現在的平臺下是4位元組而已,我們只能保證sizeof(char)<=sizeof(short)<=sizeof(int)<=sizeof(long)

表示式和運算子

C 語言中只有三個半運算子,其運算順序是固定的,分別是邏輯與運算子“&&”,邏輯或運算子“||”,還有C 語言中唯一的一個三目運算子“?:”,另外半個是逗號運算子“,”。之所以稱它為半個,是因為當逗號用來分開函式的實參時,運算順序也是不固定的。

int m = 1;
printf("%d %d", m, m=7); /* bad smell ,輸出7 7*/
a[m] = m++; /* 可能給a[8]賦值 */

f() + g() * h()//哪怕加了括號,也不能保證哪個函式先執行
a=f();//所以最好用臨時變數儲存中間值
b=g();
c=h()
a+b*c;

表示式運算的順序,完全是根據編譯器和平臺確定的。所以一個建議就是,凡是涉及到改變某個值的表示式,例如自增(減)運算子,等號等,最好能夠自成一行,不要混雜在其他表示式的裡面。

C語言的點陣圖,相當於Java的boolean陣列

#define BITMASK(b) (1 << ((b) % CHAR_BIT))
#define BITSLOT(b) ((b) / CHAR_BIT)
#define BITSET(a, b) ((a)[BITSLOT(b)] |= BITMASK(b))
#define BITCLEAR(a, b) ((a)[BITSLOT(b)] &= ~BITMASK(b))
#define BITTEST(a, b) ((a)[BITSLOT(b)] & BITMASK(b))
#define BITNSLOTS(nb) ((nb + CHAR_BIT - 1) / CHAR_BIT)

    printf("%d\n", CHAR_BIT);
    printf("%d\n",BITMASK(23));
    char bitarray[BITNSLOTS(100)]; /* 容納100位 */
    printf("%d\n", sizeof(bitarray));
    BITSET(bitarray, 23); /* 把第23位置1 */
    if(BITTEST(bitarray, 35)) /* 判斷35位是否為1 */

輸入輸出

int fscanf ( FILE * stream, const char * format, … )
scanf相當於fscanf(stdin,…)
printf相當於fprintf(stdout,…)
輸出到stdout的內容,首先儲存到緩衝區;而stderr的,直接輸出到螢幕
呼叫fflush(stdout)將緩衝區內的內容輸出到螢幕

    int ch;
/* first */
    ch = getchar();
    putchar('1');
    putchar(ch);
    putchar('\n');
/* second */
    ch = getchar();
    putchar('2');
    putchar(ch);
    putchar('\n');

getchar()從標準輸入流得到一個字元(包括空白字元),putchar(char x)輸出一個字元到標準輸出流,相當於putc(char x, stdout)
空白字元:空格、tab、回車

char str[30]; char c;
gets(str);
puts(str);

c = getchar();
putchar(c);
  1. 鍵盤輸入都被儲存在輸入緩衝區內,直到使用者輸入回車,輸入函式才會去緩衝區讀取。輸入函式從緩衝區讀取時,如果緩衝區為空,命令列介面會暫停,等待使用者輸入;否則輸入函式會從緩衝區讀入對應的資料。

  2. 利用gets 函式讀入字串時,空格和tab 都是字串的一部分。

  3. gets 函式讀入字串的時候,以回車或EOF 為字串的終止符,它同時把回車從緩衝區讀走,但自己不要回車。fgets則會要。

scanf

int scanf(“格式控制字串”,引數地址表);
格式控制字串中通常包含三大類的內容。

  • [空白字元] 以空格和tab 為主。

  • [非空白字元] 除了空白字元和%開頭的格式說明符。

  • [格式說明符] 以%開始的格式說明符遵循下面的格式:%[*][width][modifiers]type。可選的星號代表讀入的資料將被忽略掉,可選的width 代表最多讀入資料的寬度(列數),函式按此寬度從輸入資料中擷取所需資料;可選的modifiers 主要有h 和l,分別用來輸入短整型、長整型和double 型別;type 主要包含c(代表字元),d(代表整數),s(代表字串)等,當然,還有其他一些型別,大家可以參考本書網站中“擴充套件內容”網頁中的“scanf 的官方定義”連結。

• 引數地址列表是由若干變數的地址組成的列表,與格式說明符相對應,讀入的資料根據格式說明符的格式儲存到對應的變數中去。

• 返回值代表成功匹配並被讀入的變數的個數。如果沒有資料被成功讀入,函式返回零。

為什麼scanf有&

  • C 語言中函式遵循的是單向值傳遞,我們不能改變傳入函式實參的值。但是很明顯,scanf 函式必須要改變傳入函式實參的值。為了解決這個問題,我們傳入變數的地址,函式的形參宣告為指標來接受這個地址。陣列不用加,因為本身就表示首地址。

當我們呼叫scanf(“%c”,&c)讀取一個字元的時候,輸入中的任何字元,包括空白字元,都不會被忽略。
當利用scanf 讀入一個整數的時候,情況有些不同,輸入中的空格、回車、tab 鍵會被忽略。
與函式gets 不同,scanf 會忽略輸入字串前面的空格、tab 和回車等空白字元,並且把字串後面的空白字元當成輸入字串資料的結束。同時,它也不把輸入字串後面的空白字元讀入。

scanf 函式按照格式控制字串給定的格式讀取。如果讀取失敗,scanf 函式會退出,但是它不會從緩衝區內讀走不匹配資料。

while((c = getchar()) != '\n' && c != EOF)
; /*`清空緩衝區內多餘字元`*/

使用scanf 函式的時候,一個好的習慣就是別忘了判斷它的返回值。根據返回值的不同,你就可以判斷這個函式是否成功地讀入了你所希望的資料;同時,要保證格式控制符和儲存輸入的變數型別嚴格一致。
d是整數,c是字元,s是字串,f是單精度浮點數,hd是short int,ld是long,lf是雙精度浮點數。

sscanf

比起scanf 函式,sscanf 函式可以對記憶體中儲存的資料重新進行解析,這就增加了程式的容錯性。

    char line[100];
    gets(line);
    int a, b, c;
    char d[10];
    if (sscanf(line, "%d %s %d", &a, d, &b) == 3)
        printf("%d %s %d", a, d, b);
    else if (sscanf(line, "%d %d %d", &a, &b, &c) == 3)
        printf("%d %d %d", a, b, c);
    else
        printf("other form");

printf

與scanf的區別

  • printf 可以用“%f”輸出float 和double 兩種型別;scanf 必須使用“%f”輸入float,“%lf”輸入double。

  • 在兩個函式的格式控制字串中,星號“*”有不同的解釋。(scanf的*是忽略一個讀入資料;printf是從引數獲取,如printf(“%*d”, width, num))

  • scanf 格式控制字串中不使用“\n”。

printf的控制格式中,%m.n代表

  • 浮點數和字串時,m代表至少要輸出m位,如果要輸出的小於m位,則用空格填充,否則正常輸出。
  • n作用於浮點數時,代表小數點後輸出幾位;用作字串時,代表最多輸出幾個字元。

輸入規則小結

1)鍵盤輸入都被儲存在輸入緩衝區內,直到使用者輸入回車,輸入函式才去緩衝區讀取。輸入函式從緩衝區讀取時,如果緩衝區為空,程式會暫停;否則輸入函式會從緩衝區讀入對應的資料。

2)getchar 函式每次讀任意一個字元(包括回車“”)。

3)利用gets 讀入字串時,空格和tab 都是字串的一部分。gets 以回車和EOF 為字串的終止符,不會讀入回車,而fgets會讀入回車。

4)scanf 中輸入數字“%d”或字串“%s”的時候,scanf 忽略空格、tab、回車等空白字元。scanf 函式利用“%s”提取字串時,空格、tab 作為字串輸入的截止。

5)如果緩衝區不空,scanf 按自己的格式提取,提取成功,從緩衝區提走資料;提取失敗,不從緩衝區提走資料。scanf 函式返回成功提取的資料的個數。

6)掌握利用while 迴圈清空緩衝區的方法,但是不要用fflush(stdin)。

7)如果你的程式要求對使用者輸入的各種不規範格式或錯誤要求有很高的容錯程度,嘗試一下fgets 和sscanf 的組合來完成使用者輸入的讀取。

安全輸入

fgets 函式可以讓使用者指定輸入的最大長度限制,可以用這樣來預防溢位攻擊。這樣,如果你輸入超過8 個字元的一個字串,fgets 只取出前面的7 個字元,然後在後面加上“\0”,儲存到“in”這個陣列中。fgets 一般是從檔案中讀取字串,如果你想讓其完成從鍵盤上輸入,可以將標準輸入stdin 傳入這個函式中。
fgets會讀入回車,但因為可以控制長度,不會把資料覆蓋到記憶體的其他地方,就不會產生溢位攻擊。所以儘量不要用gets,而用fgets。

函式、模組和巨集定義

預處理指令

包括三種,分別為檔案包含、條件編譯和巨集替換。

  • 檔案包含即 #include<stdio.h>這種,<>表示前處理器從標準庫路徑開始搜尋,“”括起的標頭檔案表示從使用者的工作路徑開始搜尋,所以對於標準標頭檔案,用<>,對於自己的,用“”。
  • 條件編譯語句如#if #ifndef #ifdef #endif #undef,適用場景很多,如除錯程式碼中
#ifdef DEBUG
printf("debug information");
#endif

如果是在除錯中,加入#define DEBUG即可。
還有可以使得程式支援跨平臺編譯,有的標頭檔案在不同的平臺,可能所在的路徑是不一樣的。

#ifdef linux
#include<ext/hash_map>
#else
#include<hash_map>
#endif

還可以防止對標頭檔案的重複包含
標頭檔案a.h的內容

#ifndef _A_H
#define _A_H
void funA();
#endif
  • 巨集替換,如#define NAME stuff會在預處理階段,把原始碼中的NAME替換成stuff,只是簡單的替換,不做任何語法和語義方面的檢查。主要有兩種形式
#define PI 3.14 //object-like
#define PERIMETER(r) (2*PI*(r)) //function-like

C++中提倡儘量避免使用#define。是因為容易出錯嗎?define可以做到的全域性變數和函式也能做到?但全域性變數和函式會有編譯器幫你糾正一些錯誤?

static 和const

記憶體區

靜態變數和全域性變數都儲存在一塊叫作靜態儲存區的記憶體區內。這個區的值會自動初始化為0。除此之外還有
- 存放字串常量的常量儲存區:當char *p="hello";時,字串hello就放在常量儲存區,p放在棧上
- 程式碼段,和常量儲存區都是隻讀的
- 堆:malloc分配的記憶體,使用完畢後必須用free釋放,這裡的堆不是資料結構的最大堆、最小堆
- 棧:存放函式內區域性變數、函式引數、返回資料等,函式結束後自動釋放

全域性變數和靜態變數

全域性變數的一個特點是所有函式都能訪問它;而靜態變數只在定義它的範圍內可見,比如定義在一個.c檔案中,那麼只有這個檔案的函式可以讀寫它。如果用static來修飾函式,就可以在不同的.c檔案中定義同名函式。所以static有靜態和隱藏兩個作用。
如果要定義一個允許被其他.c檔案使用的全域性變數,則在任一.c檔案中定義,不能加static修飾符,在其他.c檔案使用這個變數前,用extern修飾符對這個變數進行宣告,extern是告訴編譯器,這個全域性變數已經在某個.c檔案中定義了。

編譯和連結

編譯器只是理解程式,把理解的結果翻譯成對應的機器碼,並不需要生成可執行檔案,而連結器需要在記憶體中定位這個識別符號,需要唯一地找到它。比如我們在某個地方聲明瞭一個函式f,並使用了它,但沒有定義(即具體實現),在編譯階段不會報錯,到了連結階段才會報錯。
編譯器每次只編譯一個.c檔案,對其他檔案一無所知。

宣告和定義

對宣告的變數或函式,編譯器並補申請記憶體,只是保留一個引用;當執行連結時,把正確的記憶體地址連結到那個引用上。對定義的變數或函式,編譯器要分配一段記憶體。
可能在其他書上 定義 叫做 實現,宣告 叫做 定義,但理解意思就好~

int foo();//宣告一個函式
extern int num;//宣告一個變數
int i=0;//定義一個變數
int foo(){//定義一個函式
}

標頭檔案

理解這些,就知道標頭檔案是用來放宣告的,因為#include語句其實是把當前語句替換成整個標頭檔案的內容。重複定義會造成編譯器不知道選哪個定義而隨便選一個導致出錯(也可能不會隨便選,反正別這麼做)。
而標頭檔案的避免重複包含只是為了提高編譯效率!
所以標頭檔案只應該放這些
- 用於避免重複包含的#ifndef ... #end
- 巨集定義
- struct、union、enum等型別的typedef型別定義
- 全域性變數及函式的宣告(變數前加extern修飾,函式不實現)

庫函式

數學相關

大部分在math.h
取整形絕對值的abs()在stdlib.h
三角函式和反三角函式都是以弧度為引數。如求sin45°,應寫sin(45*pi/180);

字串相關

ctype.h裡有字元型別判斷的,如isalnum, islower, isupper, issspace
string.h裡有strcpy, strcat, strlen, strcmp。strcpy, strcat函式會改變傳入的字串的內容,需要確保傳入的地址有足夠的空間容納改變後的字串,也需要確保源地址和目標地址不會發生重疊,這兩個函式對傳入的空間不做容量檢查。

    char str[11]="hello";
    char str1[11]="world";
    strcat(str1,str);
    printf("%s\n",str1);

    strcpy(str,str1);
    printf("%s\n",str);

為了解決字串函式的溢位問題,引入了strncpy和strncat函式,允許指定複製或追加的字元的個數。

    char str[11]="hello";
    char str1[11]="world";
    strncat(str,str1,3);
    printf("%s\n",str);
    strncpy(str1,str,6);
    printf("%s\n",str1);

strlen原始碼

size_t strlen(const char *string) {
    const char *eos;
    while (*eos++) {
        return (eos - string - 1);
    }
}

strcpy原始碼

char *strcpy(char *dst, const char *src) {
    char *cp = dst;
    while (*cp++ = *src++);
    return (dst);
}

字元和數字相互轉換

字串轉數字

    double d;
    int i;
    long l;
    char num1[] = "123";
    char num2[] = "1.001";
    char num3[] = "1234567890";
    d = atof(num2);
    i = atoi(num1);
    l = atol(num3);
    printf("%lf %d %ld", d, i, l);

數字轉字串

    double d = 123.45;
    int i = 123;
    long l = 1234567890;
    char num1[20];
    char num2[20];
    char num3[20];
    sprintf(num1, "%lf", d);
    sprintf(num2, "%d", i);
    sprintf(num3, "%ld", l);

時間函式

系統時間為 從1970年1月1日0時0分0秒到這一刻所經過的秒數,可呼叫time_t time(time_t *timer)獲取,time_t是long,通過typedef定義的。有兩種獲得系統時間的方法

time_t now;
time(&now);
now=time(NULL);

隨機數

撲克洗牌

void wash(int *pai) {
    srand(time(NULL));
    int i;
    for (i = 0; i < 54; i++) {
        int c = rand() % 54;
        int t = pai[i];
        pai[i] = pai[c];
        pai[c] = t;
    }
}

產生100個0到1的隨機數

void produce100(double *arr) {
    srand(time(NULL));
    int i, randN;
    for (i = 0; i < 100; i++) {
        randN = rand();
        arr[i] = (double) randN / RAND_MAX;
    }
}

系統相關函式

exit

與return相比,有幾個優點

  1. 在程式任何一個地方呼叫都可以終止當前程式,而return只有在main呼叫才能終止
  2. 當exit函式被呼叫時,會執行一些額外操作,如重新整理所有用於流的緩衝區,關閉開啟的檔案,還可以通過atexit函式註冊一些退出函式。

system

把傳入的字串引數傳送給宿主作業系統。如在Linux下,system("ls"); 就是列出當前目錄檔案

signal

在程式收到指定訊號時,指定你要呼叫的回撥函式。
使用任何庫函式,一定要包含與之對應的正確的標頭檔案,否則可能發生難以發現的邏輯錯誤

陣列

陣列變數就是此陣列的首地址,如int array[10]; array等價於&array[0]。
定義一個數組變數的時候,編譯器會根據陣列的長度宣告一塊連續的記憶體給陣列。
陣列本身不會執行越界檢查。(C語言信任程式)

陣列的初始化與陣列間的賦值

    int arr[3];//隨機值
    int arr1[3]={1};//當指定第一個元素後,後面的全為零
    int arr2[3]={1,2,3};
    memset(arr,0, sizeof(arr));//這樣是把arr這塊記憶體每個位元組設為0,而不是把arr每個元素值設為0

    memcpy(arr,arr2, sizeof(arr2));//陣列間賦值的高效方法

陣列與函式

向一個函式傳遞一個數組,其實是把陣列的首地址傳入。因為如果採用值傳遞,我們需要在棧上再構造一個數組,然後把資料複製到新的陣列,這樣效率很低。所以用的是引用傳遞,傳遞的是地址值。(這裡的引用傳遞不同於Java的,這裡其實還是傳值,不過傳的是地址值)
若不希望函式能改變引數中陣列的值,可以把形參宣告為const int arr[]
C語言的函式不支援用return返回一個數組。可以用全域性變數,返回指標,改變實參這些代替。

二維陣列

因為C語言不檢查陣列越界,所以這裡arr[0][4]arr[1][1]指向的是同一個元素

    char arr[2][3]={'a'};
    printf("%c ",arr[0][0]);
    arr[0][4]='z';
    printf("%c",arr[1][1]);

定義一個數組的時候,必須指定陣列的長度,或者通過初始化每個元素的方法來隱含指定長度

指標

這一章之前寫過了,在這裡

以下是重讀的筆記
任何指標型別變數都有兩個屬性:本身儲存的地址和指向變數的型別。
指標本身只儲存地址,編譯器根據指標指向的變數的型別從指標儲存的地址向後定址。
野指標:當定義一個指標沒有初始化的時候,那麼它指向的就是一個不確定的地址。
定義一個指標型別的變數時,暫時不知道指向哪就讓它指向NULL。

void

對於void型別指標,它只儲存一個地址,不包含指向變數的型別資訊,所以對其進行算術運算和進行取值操作都是不允許的。
void型別指標一般用在函式的引數和返回值中,以達到泛型的目的。如memcpy和memset,操作的物件僅僅是一片記憶體,不關心這片記憶體儲存的是什麼型別。

指標和陣列

當a[i]用在一個表示式中的時候,編譯器自動將其轉換為指標加偏移量*(a+i)的形式。
當陣列型別被用於宣告函式形參的時候,編譯器會自動將其轉換為指標。
但編譯器並不是一看到陣列a[i]就將其轉化為指標,如sizeof(a)的時候,返回的是整個陣列的長度,而不是指標長度。

指標陣列

    char *prompt[]={"1.語文","2.MATH","3.English",NULL};
    char **temp;
    temp=prompt;
    while(*temp){
        printf("%s\n",*temp);
        temp++;
    }

使用指標陣列的一個優點就在於對多個字串的排序,排序的過程並不需要移動真實的字串,只需要改變指標的指向。

動態記憶體分配

malloc和calloc

    char *str=malloc(sizeof(char)*10);
    memset(str,1, sizeof(char)*10);
    str=calloc(10,sizeof(char));

calloc會將所分配的記憶體空間中的每一位都初始化為零。
使用calloc比呼叫malloc加memset要塊。但calloc在不同平臺好像有點差異,所以最好別用?
使用這兩個函式記得包含頭件<stdlib.h>

realloc

void *realloc(void *ptr, size_t size)

  • 第一個引數所指向的指標,或者是NULL,或者是malloc、calloc、realloc三個函式返回的指標。
  • 返回值一般用一個臨時指標變數儲存,因為一旦realloc呼叫失敗,將返回NULL,所以判斷非NULL再賦值

free

即使在函式內部,用malloc申請的記憶體也不會伴隨著函式的退出而釋放,必須用free來顯示釋放。否則記憶體洩漏,記憶體被逐步佔用而不釋放,電腦會越來越慢(分配新的記憶體時找得慢?),直到宕機。

  • free後的記憶體並不返還給作業系統,而是對同一程式中未來的malloc函式的呼叫可用。
  • free函式中的指標必須是malloc、calloc、realloc函式返回的指標
  • free函式並不能改變傳入的指標的值
    而使用被釋放的記憶體或者重複釋放記憶體都會發生錯誤。
    所以當free完後同時把這個指標指向NULL,就在就不會 使用或重複釋放 已經釋放的記憶體了。

記憶體操作函式

memmove函式可以用在源記憶體和目標記憶體去重疊的情況,而memcpy不可以。memmove從最後開始複製來解決這個問題,而不是從頭開始複製。

記憶體使用的效率建議

反覆呼叫malloc和free,堆將變得很“碎”,這樣malloc的效率將很低,因為需要花費很長時間來找到 一塊合適的地方。所以如果需要頻繁使用多個較少的記憶體空間,更好的辦法是預先分配一塊大記憶體。這就是資源池中的“記憶體池”了

動態陣列

C語言中的陣列在記憶體中連續分佈的。所以可以利用calloc或者malloc來申請一塊用來容納動態陣列的連續的記憶體。(C++裡直接用vector就好了)

字串

字串以一個“\0”字元為截止符。申請char陣列時要預留一個截止符位置。

char *cp="const";//const在常量儲存區,cp在靜態儲存區
char ca[]="static";//static和ca在靜態儲存區
int main() {
    char *p="const1";//const1在常量儲存區,p在棧上
    char a[]="stack";//都在棧上
    return 0;
}

函式和指標

void f(int *ptr){
    ptr=(int *)malloc(sizeof(int));
    *ptr=999;
    //記憶體洩漏
}
int main() {
    int *p,k=5;
    p=&k;
    f(p);
    printf("%d",*p);
    return 0;
}

這裡輸出的是5,所以其實還是值傳遞的,ptr和p不是同一個地址值,只是值相同,所以只是改變了ptr的指向,沒有改變原本ptr指向的記憶體的值。
如果單純想改變*p的值,直接*p=123;即可。如果申請了記憶體,可通過返回指向該記憶體的指標 (如下)

int *f(int *ptr){
    ptr=(int *)malloc(sizeof(int));
    *ptr=999;
    return ptr;
}
int main() {
    int *p,k=5;
    p=f(p);
    printf("%d",*p);
    free(p);
    return 0;
}

或 用指向指標型別的指標做到。(如下)

void f(int **ptr){
    *ptr=(int *)malloc(sizeof(int));
    **ptr=999;
}
int main() {
    int *p,k=5;
    p=&k;
    f(&p);
    printf("%d",*p);
    free(p);
    return 0;
}

函式指標

函式指標可以達到“泛型”的一種概念。
C語言用一個函式指標定義一個介面規範,而C++用多個函式指標來定義。(虛基類維護的一個虛擬函式表裡儲存著類中所有對應虛擬函式的函式指標)

複雜宣告

void (*ap[10])(void (*)());
//一個長度為10的指標陣列,指標是函式指標,設為a,
// a指向一個返回值為void,引數為一個函式指標(設為b)的函式,
// b指向一個返回值為void,沒有形參的函式

int (*frp(int))(char *, char *);
//這是一個函式,函式本身接受一個int形參,返回值是一個函式指標,
// 指向的函式返回一個int,接受兩個字元指標作為形參

void (*fp)();
//這是一個函式指標,指向一個返回值為void,沒有形參的函式。

第二個函式可以這麼用frp(1)("hello", "world"); 因為frp的返回值是一個函式。而frp要這樣定義

int CE(char *c, char *e){
    return 1;
}
int (*frp(int))(char *, char *){
    return CE;
}

用typedef定義一個複雜宣告

void (*ap[10])(void (*)());

typedef void (*pfv)();
//定義了一個函式指標,指向一個返回值為void,沒有形參的函式
typedef void (*pf_taking_pfv)(pfv);
//定義了一個函式指標,引數為pfv
pf_taking_pfv ap[10];
//定義了一個指標陣列,指標為pf_taking_pfv
void (*f1())();

typedef void (*pfv1)();
pfv1 f1();

結構體

實際上,struct就是所有成員都是public的一個class。
定義結構體變數(用這種方法最好)

typedef struct {
    int id;
    char name[20];
} STUD;
STUD student1;

還有其他方法比如

struct student{
    int id;
    char name[20];
} student1;
struct student student2;
struct {
    int id;
    char name[20];
} student1;

記憶體對齊的目的是使處理器能夠更快速的進行定址。
兩個相同型別的結構體變數不能直接用等號來進行賦值,也不能直接用==來判斷是否相等(C++中可以通過過載做到)。正確的方法是編寫一個函式來做這個工作(引數用結構體型別的指標,不然拷貝一次效率低)。

列舉

列舉型別是一種基本資料型別。

typedef enum DAY {
    MON = 1, TUE, WED
}Day;

int main() {
    Day today;
    today=MON;
    printf("%d",today);
    today=2;
    printf("%d",today);
    return 0;
}

書上說列舉變數只能用列舉符賦值,但好像在C11裡面已經可以用任意整形賦值?

檔案

FILE是C語言中定義的一個結構型別,定義在stdio.h中。
FILE *就是人們常說的檔案控制代碼了。

r+, w+

r代表讀,w代表寫,+代表讀寫。如果檔案不存在,r+就失敗,而w+會新建檔案;如果檔案存在,r+不清空檔案,而w+清空檔案。
同時讀寫,常見的做法是使用一個臨時檔案專門用來寫,寫完之後在替換。因為如果對一個檔案同時讀寫,寫的時候一旦出錯,會破壞原檔案內容。

斷行識別符號

在電傳打字機時代,每秒鐘可以打10個字元,但是換行要0.2秒,要是這段時間有新的字元傳過來,那新傳過來的就丟失了。於是工程師們在每行後面加兩個表示結束的字元。一個叫回車(\r),告訴打字機把列印頭定位在左邊界。另一個叫換行(\n),告訴打字機把紙向下移一行。
後來計演算法出現,這兩個概念也被搬到計算機上。但一些程式設計師認為兩個字元表示結尾太浪費了,於是出現分歧。

  • UNIX/Linux下:用換行表示
  • DOS/Windows下:用回車+換行表示
  • Mac下:用回車表示

而C語言的I/O補直接讀寫硬碟,而是通過呼叫OS的API完成,所以我們可以統一用\n來表示換行。

文字格式和二進位制格式

開啟模式中增加字母‘b’即可以二進位制模式開啟檔案。
當用fwrite函式時,一旦位元組流發現0x0A,就會轉換為0x0D 0x0A。
要想不轉換,應該以二進位制模式b開啟檔案。

feof

feof返回真,則檔案位置指標一定指向末位的EOF,反過來不成立。
feof內部是通過判斷一個標誌位來返回真的。而這個標誌位當fgetc、fgets、fscanf、fread讀取到了EOF的時候就會設定。

int main() {
    FILE *fp=fopen("test","r");
    if(fp==NULL)
        printf("can not open file\n");
    char str[100];
    while(!feof(fp)){
        fgets(str,100,fp);
        printf("%s",str);
    }
    return 0;
}

幾乎所有的讀函式,當讀取到末位或讀取出錯的時候,都會返回相同的值,所以要分別使用feof和ferror來判斷哪一種情況發生。

int c;
while(c = fgetc(fp)){
    if(c==EOF){
        if(feof(fp)!=0)
        break;
        if(ferror(fp)!=0)
        error_handle;
    }else{
        do_something;
    }
}

異常

異常底層的實現藉助於棧回退技術。

tips

  1. 用一個int變數來控制迴圈,如for(...;...&&flag;...),這樣可以方便的跳出多重迴圈

END

2017/5/17 之後繼續學Linux和csapp,有時間就繼續看C++ Primer

相關推薦

C語言點滴學習筆記

資料型別 原碼、反碼、補碼 原碼:最高位為符號位,最高位為0表示正數,1表示負數 反碼:正數與原碼一樣,負數則對原碼除符號位外取反得到 補碼:正數與原碼一樣,負數則對原碼除符號位外取反再加一得到 計算機中,整數使用補碼錶示。 若用原碼錶示,則a+

C語言學習筆記3——字符串

store 寫代碼 inf 變量類型 density scanf() 想要 限定符 tor 1. 字符串(charcacter string)是一個或多個字符的序列 2. C語言沒有專門用於存儲字符串的變量類型。字符串都被存儲在char類型的數組種。 3. 數組由連續的

C語言學習筆記7——指針與多維數組

一個 聲明 %d mage 分享圖片 技術分享 pan 最好 include 1. 聲明一個指向多維數組的指針 int (* pz) [2]; //pz指向一個內涵兩個int類型元素的數組 int * pax[2]; //pax 是一個內含兩個指針元素的

C語言基礎學習筆記day3

date:‘2018-9-9’ nandian:迴圈的巢狀 day5 @toc 一、一維陣列 1.定義陣列 #include<stdio.h> int main() { int array[10]; //定義了一個數組,陣列名為array,

C語言程式設計 學習筆記 動態記憶體分配(malloc)

如果輸入程式時,先告訴你個數,然後再輸入,要記錄每個資料(類似動態陣列) C99之前應該怎麼做呢? malloc()函式的作用就在此: int *a = (int*)malloc(n*sizeof(int)); malloc()函式的作用是向記憶體申請一個n*

C語言基礎學習筆記:day5 指標

注:本筆記為直接上傳,因各個markdown筆記語法的差異性,在顯示上略有區別。 如需原版請聯絡:[email protected]。(郵件主題為:學習筆記,正文需要的筆記名,可以直接複製該筆記的網址)。同時歡迎各位一起學習交流。 day5指標 文章目錄

C語言基礎學習筆記day1

注:本筆記為直接上傳,因各個markdown筆記語法的差異性,在顯示上略有區別。 如需原版請聯絡:[email protected]。(郵件主題為:學習筆記,正文需要的筆記名,可以直接複製該筆記的網址)。同時歡迎各位一起學習交流。 基本入門知識 1.第一個c程式 #

C語言程式設計 學習筆記 連結串列

接可變陣列 但如果我們可以使用BLOCK,將其都拼接在一起,並不是用上面的方法複製貼上。每一個BLOCK會有一個單元指向的是下一個BLOCK的地址,這樣就不會有上述的問題了 所以對於一個單元,它裡面應該分成兩部分: 1.資料 2.下一個單元的地址(指標) 這樣指向的下一個資料結構也應是

C語言程式設計 學習筆記 12.3 多個原始碼檔案、標頭檔案、宣告

我們經常在做“分而治之”的事情(多個.c檔案): 1.main()裡的程式碼太長了適合分成幾個函式 2.一個原始碼檔案太長了適合分成幾個檔案 3.兩個獨立的原始碼檔案不能編譯成可執行的程式 對於(1),我們可以舉以下例子: 有個主函式main.c,有另外一個函式

《手把手教你學C語言學習筆記(4)---程式碼規範

程式設計過程中需要遵守編譯器的各種約定,例如以下程式碼: 1 #include <stdio.h> 2 3 int main(int argc, char **argv) 4

慕課網c語言入門學習筆記

#include<stdio.h> int main()新標準中是int 而非void 一個c程式中只有一個主函式,main是唯一入口 printf(); return 是函式的返回值,函式型別不同,返回值也不同良好規範 1、一個說明或一個語句佔一行,例如:包含

C語言程式設計 學習筆記 字串(II)(字串輸入輸出,字串陣列,程式引數)

字串輸入輸出: char str[8]; scanf("%s",&str); printf("%s",str); scanf表示讀入一個單詞(到空格、tab、回車為止) scanf是不安全的,因為這樣不知道要讀入的內容的長度,在一些情況中會出現問題:

C語言指標學習筆記

指標 1.        表示式中,陣列可以解讀成“指向它的初始元素的指標”(有三個例外),和在後面加不加[]沒有關係 2.        向一個函式傳遞陣列時,實際上傳遞的是一個指向初始元素的指標 3.        C中三種記憶體領域的壽命 靜態變數和全域性變數的壽命從

5、C語言深度學習筆記--C語言中的基本元素

一、C語言中的字符集1、⼀般來說,程式設計語⾔的字符集都可分為兩組:⼀組叫源字符集,另⼀組叫執⾏字符集。所謂“源字符集”是指在寫C原始碼時⽤的字符集,也就是呈現在C源⽂件中的字符集。⽽“執⾏字符集”是指編譯構建完源⽂件後的⽬標⼆進位制⽂件中所表⽰的字符集,它將⽤於運⾏在當前的

《手把手教你學C語言學習筆記(1)---C語言的特點

學習C語言的原因,主要是需要使用C語言程式設計,我用故我學,應該是最主要的原因了。 C語言的定位:C語言嚴格意義上只能算是中級語言,是面向過程程式語言的集大成者,雖然這種語言有很多的問題,但總體

C++程式設計語言學習筆記1-容器

容器 容器名 資料結構 vector<T> 可變大小向量 list<T> 雙向連結串列 forward_list<T> 單向連結串列 deque<T> 雙端佇列 set<T&g

學習C語言入門心得筆記

 當我們看到C語言是第一感覺一定是茫然的 , 那一串串字元就不知道什麼意思,它又具有什麼樣的特點 ,到底有什麼作用 C語言的特點為 a.語言簡潔.方便。靈活 b.運算子豐富,範圍廣 c.資料型別豐富,提供了整體,實效。字元型,陣列型別,指標型別,結構體型別,共同體型別等資料

C語言點滴》書後筆記

溢位的邊界: 無符號整型UINT_MAX+1=0 有符號整型INT_MIN=INT_MAX+1 為避免溢位可適當使用double代替int Float:有效數字為6-7,數值範圍為 -3.4E+38 和 3.4E+38 Double:有效數字為15-16,數值範圍為-1.7E-308~1

傳智播客c/c++公開課學習筆記--C語言與木馬惡意程式碼分析和360安全防護揭祕

【課程簡介】 C/C++語言是除了彙編之外,最接近底層的計算機語言,目前windows,linux,iOS,Android等主流作業系統都是用C/C++編寫的,所以很多病毒、木馬也都是用C/C++實現的。課程的目的就是通過C語言揭祕木馬和各種遠端控制軟體的實現原理以及如

C程式設計語言學習筆記(一)——導言

Book 1.5.4 while (c = getchar() != EOF) { if(c == ‘ ‘ || c == ‘\n’) flag= 1; else