C語言 16 系統庫

天航星發表於2024-09-27

前面瞭解瞭如何使用#include引入其他檔案,接著來了解一下系統提供的一些常用庫。

字串

計算字串長度:

#include <stdio.h>
#include <string.h>

int main() {
    char* c = "Hello World!";
    // 使用strlen計算長度,注意返回值型別是size_t(別名而已,本質上就是unsigned long)
    printf("%lu", strlen(c));   
}
12

拼接字串:

#include <stdio.h>
#include <string.h>

int main() {
    // 現在有兩個字串,但是我們希望把他們拼接到一起
    // 注意不能這樣寫 char* a = "Hello",*b = "World!"; 
    // 如果直接用指標指向字串常量,是無法進行拼接的,因為大小已經固定了
    // 這裡需要兩個引數,第一個是目標字串,將第二個引數的字串拼接到第一個字串中(注意要裝得下才行)
    char a[20] = "Hello", *b = "World!";  
    strcat(a, b);
    printf("%s", a);
}
HelloWorld!

複製字串:

#include <stdio.h>
#include <string.h>

int main() {
    char str[10], *c = "Hello";
    // 使用cpy會直接將後面的字串複製到前面的字串陣列中(同樣需要前面裝得下才行)
    strcpy(str, c);
    printf("%s", str);
}
Hello

比較字串:

#include <stdio.h>
#include <string.h>

int main() {
    char *a = "AAA", *b = "AAB";
    // strcmp會比較兩個字串,並返回結果
    // 把字串str1和str2從首字元開始逐個字元的進行比較
    // 直到某個字元不相同或者其中一個字串比較完畢才停止比較
    // 字元的比較按照ASCII碼的大小進行判斷
    // 比較完成後,會返回不匹配的兩個字元的ASCII碼之差
    int value = strcmp(a, b);
    printf("%d", value);
}
-1

數學

接著來看用於處理數學問題的相關庫:

#include <math.h>

這裡要用到math.h,它提供了數學計算函式,比如求算術平方根、三角函式、對數等。

#include <stdio.h>
#include <math.h>

int main() {
    int a = 2;
    // 使用sqrt可以求出非負數的算術平方根(底層採用牛頓逼近法計算)
    double d = sqrt(a);
    printf("%lf", d);
}
1.414214

能夠開根,也可以做乘方:

#include <stdio.h>
#include <math.h>

int main() {
    int a = 2;
    // 使用pow可以快速計算乘方,這裡求的是a的3次方
    double d = pow(a, 3);  
    printf("%lf", d);
}
8.000000

有了這個函式,寫個水仙花數更簡單了:

#include <stdio.h>
#include <math.h>

int main() {
    for (int i = 0; i < 1000; ++i) {
        int a = i % 10, b = i / 10 % 10, c = i / 10 / 10;
        if (pow(a, 3) + pow(b, 3) + pow(c, 3) == i) {
            printf("%d 是水仙花數!\n", i);
        }
    }
}
0 是水仙花數!
1 是水仙花數!
153 是水仙花數!
370 是水仙花數!
371 是水仙花數!
407 是水仙花數!

當然也可以計算三角函式:

#include <math.h>
#include <stdio.h>

int main() {
    // 這裡我們使用正切函式計算tan180度的值,注意要填入的是弧度值
    // M_PI也是預先定義好的π的值,非常精確
    printf("%f", tan(M_PI));
}
-0.000000

當然某些沒有不存在的數可能算出來會得到一個比較奇怪的結果:

#include <math.h>
#include <stdio.h>

int main() {
    // 這裡計算tan90°,我們知道tan90° = sin90°/cos90° = 1/0 不存在
    printf("%f", tan(M_PI / 2));
}
16331239353195370.000000

當然還有兩個比較常用的函式:

#include <stdio.h>
#include <math.h>

int main() {
    double x = 3.14;
    printf("不小於x的最小整數:%f\n", ceil(x));
    printf("不大於x的最大整數:%f\n", floor(x));
}
不小於x的最小整數:4.000000
不大於x的最大整數:3.000000

當然也有快速求絕對值的函式:

#include <math.h>
#include <stdio.h>

int main() {
    double x = -3.14;
    printf("x的絕對值是:%f", fabs(x));
}
x的絕對值是:3.140000

通用

最後再來介紹一下通用工具庫stdlib,這個庫裡面提供了大量的工具函式:

// 工具庫已經提供好了快速排序的實現函式,可以直接用
// 引數有點多,第一個是待排序陣列,第二個是待排序的數量(一開始就是陣列長度),第三個是元素大小,第四個是排序規則(提供函式實現)
// void __cdecl qsort(void *_Base,size_t _NumOfElements,size_t _SizeOfElements,int (__cdecl *_PtFuncCompare)(const void *,const void *));

在開始使用之前還要先補充一點知識,因為qsort的原型定義,使用的是 void 型別的指標。

怎麼 void 還有指標呢?void 不是空嗎?

void 指標是一種特殊的指標,表示為“無型別指標”,由於 void 指標沒有特定的型別,因此它可以指向任何型別的資料。也就是說,任何型別的指標都可以直接賦值給 void 指標,而無需進行其他相關的強制型別轉換。

所以這裡之所以需要 void 指標,其實就是為了可以填入任何型別的陣列,而第三個引數實際上就是因為是 void 指標不知道具體給進來的型別是什麼,所以需要告訴函式使用的型別所佔大小是多少。

而最後一個引數實際上就是前面介紹的函式回撥了,因為函式不知道你的比較規則是什麼,是從小到大還是從大到小呢?所以需要編寫一個函式來對兩個待比較的元素進行大小判斷。

好了,現在瞭解了之後,就可以開始填入引數了:

#include <stdio.h>
#include <stdlib.h>

// 引數為兩個待比較的元素,返回值負數表示a比b小,正數表示a比b大,0表示相等
int compare(const void* a, const void* b) {  
    // 這裡因為判斷的是int所以需要先強制型別轉換為int *指標
    int *x = (int*)a, *y = (int*)b;          
    // 其實直接返回a - b就完事了,因為如果a比b大的話算出來一定是正數,反之同理
    return *x - *y;                          
}

int main() {
    int arr[] = {5, 2, 4, 0, 7, 3, 8, 1, 9, 6};
    // 工具庫已經提供好了快速排序的實現函式,可以直接用
    // 引數有點多,第一個是待排序陣列,第二個是待排序的數量(一開始就是陣列長度),第三個是元素大小,第四個是排序規則(提供函式實現)
    qsort(arr, 10, sizeof(int), compare);
    for (int i = 0; i < 10; ++i) {
        printf("%d ", arr[i]);
    }
}
0 1 2 3 4 5 6 7 8 9

這樣,就可以對陣列進行快速排序了。


通用庫中還提供了exit函式用於終止程式:

#include <stdlib.h>

int main() {
    // 直接終止程式,其中引數是傳遞給父程序的(但是現在只是簡單程式)
    exit(EXIT_SUCCESS);   
}

不過乍一看,貌似和直接在 main 裡面 return 沒啥區別,反正都會結束。


還有兩個會在後續學習資料結構中用的較多的函式:

#include <stdio.h>
#include <stdlib.h>

int main() {
    // 可以使用malloc函式來動態申請一段記憶體空間
    int* p = (int*)malloc(sizeof(int));  
    // 申請後會返回申請到的記憶體空間的首地址
    *p = 128;
    printf("%d", *p);
}
128

malloc 用於向系統申請分配指定 size 個位元組的記憶體空間,返回型別是 void* 型別,如果申請成功返回首地址,如果失敗返回 NULL 空地址(比如系統記憶體不足了就可能會申請失敗)

申請到一段記憶體空間後,這段記憶體空間就可以往上面隨便讀寫資料了,實際上就是和變數一樣,只不過這個記憶體空間是自主申請的,並不是透過建立變數得到的,但是使用上其實沒啥大的區別。

不過要注意,這段記憶體使用完之後記得清理,就像函式執行完會自動銷燬其中的區域性變數一樣,如果不清理那麼這段記憶體會被一直佔用:

#include <stdio.h>
#include <stdlib.h>

int main() {
    int* p = (int*)malloc(sizeof(int));
    *p = 128;
    printf("%d", *p);
    // 使用free函式對記憶體空間進行釋放,歸還給系統,這樣這段記憶體又可以被系統分配給別人用了
    free(p);   
    // 指標也不能再指向那個地址了,因為它已經被釋放了
    p = NULL;  
}

128

記憶體資源是很寶貴的(不像硬碟幾個 T 隨便用,我們的電腦可能 32G 的記憶體都算高配了),不能隨便浪費,所以一般情況下 malloc 和 free 都是一一對應的,這樣才能安全合理地使用記憶體。


環境:

  • GCC 11.4.0
  • VSCode 1.93.1

相關文章