C語言快速回顧(二)

前言

在Android音視頻開發中,網上知識點過于零碎,自學起來難度非常大,不過音視頻大牛Jhuster提出了《Android 音視頻從入門到提高 - 任務列表》,結合我自己的工作學習經歷,我準備寫一個音視頻系列blog。C/C++是音視頻必備編程語言,我準備用幾篇文章來快速回顧C語言。本文是音視頻系列blog的其中一個, 對應的要學習的內容是:快速回顧C語言的字符串,指針,指針和數組,預處理器。


音視頻系列blog

音視頻系列blog: 點擊此處跳轉查看


目錄

在這里插入圖片描述


1 字符串

1.1 字符串字面量

C語言字符串字面量是指直接在代碼中寫入的字符串,通常用雙引號 " 括起來的字符序列。字符串字面量在C語言中用于表示文本數據。

例如,以下是一些字符串字面量的示例:

"This is a string literal."
"Hello, World!"
"12345"
"Special characters: !@#$%^&*"

需要注意的是,字符串字面量在存儲時會自動在末尾添加一個空字符 '\0',用于表示字符串的結束。因此,字符串字面量的長度比其包含的字符數量多一個。

你可以將字符串字面量賦值給字符數組或字符指針變量,如下所示:

char str1[] = "Hello, World!"; // 字符數組
char *str2 = "This is a string."; // 字符指針

在上面的示例中,str1 是一個字符數組,會自動分配足夠的空間來存儲字符串 “Hello, World!”,并自動添加空字符。str2 是一個字符指針,它指向存儲字符串 “This is a string.” 的位置。注意,str2 指向的是一個字符串字面量,這意味著該字符串的內容是固定的,不能直接修改。

字符串字面量在C語言中廣泛使用,用于表示文本、消息、錯誤信息等。它們在輸入輸出、賦值、函數調用等情境中都是常見的。


1.2 字符串變量

C語言中的字符串變量通常是字符數組或字符指針,用于存儲和處理字符串數據。字符串變量允許你在程序中操作文本和字符數據。

以下是使用字符數組和字符指針來創建字符串變量的示例:

  1. 字符數組(Character Array): 字符數組是一種固定大小的數組,用于存儲字符串。可以初始化為一個字符串字面量,也可以逐個字符賦值。

    char str1[20] = "Hello, World!"; // 初始化為字符串
    char str2[10]; // 聲明一個字符數組
    strcpy(str2, "Welcome"); // 復制字符串到字符數組
    
  2. 字符指針(Character Pointer): 字符指針指向字符串的首字符,可以指向字符數組或字符串字面量。

    char *ptr1 = "Hello, World!"; // 指向字符串字面量
    char *ptr2; // 聲明一個字符指針
    ptr2 = "Welcome"; // 指向字符串字面量
    

字符串變量的操作包括初始化、賦值、連接、拷貝、比較等。你可以使用字符串庫函數來處理這些操作。例如:

#include <stdio.h>
#include <string.h>int main() {char str1[20] = "Hello";char str2[20] = "World";// 字符串連接strcat(str1, " ");strcat(str1, str2);// 字符串長度printf("Length of str1: %lu\n", strlen(str1));// 字符串比較if (strcmp(str1, "Hello World") == 0) {printf("Strings are equal.\n");} else {printf("Strings are not equal.\n");}return 0;
}

在上面的示例中,我們演示了如何初始化字符數組、使用字符指針,以及如何使用字符串庫函數來連接字符串、計算字符串長度和比較字符串。

無論使用字符數組還是字符指針,字符串變量都允許你在C程序中處理文本數據,進行各種操作和處理。


1.3 字符串的讀和寫

在C語言中,可以使用標準庫函數來讀取和寫入字符串。主要的函數是 printf()scanf() 用于輸出和輸入字符串,以及 gets()fgets() 用于從用戶輸入中讀取字符串。

以下是這些函數的使用示例:

  1. 輸出字符串: 使用 printf() 函數輸出字符串。

    #include <stdio.h>int main() {char str[] = "Hello, World!";printf("String: %s\n", str);return 0;
    }
    
  2. 輸入字符串: 使用 scanf() 函數輸入字符串。但是,scanf() 在讀取字符串時可能會有問題,因為它會在遇到空格、制表符或換行符時停止讀取。

    #include <stdio.h>int main() {char str[100];printf("Enter a string: ");scanf("%s", str);printf("You entered: %s\n", str);return 0;
    }
    
  3. 安全讀取字符串: 為了安全地讀取字符串,推薦使用 fgets() 函數。

    #include <stdio.h>int main() {char str[100];printf("Enter a string: ");fgets(str, sizeof(str), stdin);printf("You entered: %s", str);return 0;
    }
    
  4. 從文件中讀取字符串: 使用 fgets() 函數從文件中讀取字符串。

    #include <stdio.h>int main() {char str[100];FILE *file = fopen("text.txt", "r");if (file) {fgets(str, sizeof(str), file);printf("Read from file: %s", str);fclose(file);} else {printf("Failed to open file.\n");}return 0;
    }
    

在使用這些函數時,請注意字符數組的大小,以避免緩沖區溢出。特別是使用 scanf() 時要小心,它可能會導致緩沖區溢出問題。推薦使用 fgets() 進行安全的字符串輸入。

需要注意的是,C語言中沒有內置的字符串類型,字符串實際上是以字符數組或字符指針的形式表示的。這使得字符串處理在某些情況下需要特別小心,以確保內存和緩沖區的正確管理。


1.4 訪問字符串中的字符

在C語言中,可以使用下標(索引)來訪問字符串中的單個字符。C語言中的字符串實際上是字符數組,每個字符都是數組中的一個元素。字符串中的字符使用從0開始的索引進行訪問。

以下是訪問字符串中字符的示例:

#include <stdio.h>int main() {char str[] = "Hello, World!";// 使用下標訪問字符串中的字符printf("Character at index 0: %c\n", str[0]);printf("Character at index 7: %c\n", str[7]);return 0;
}

在上述示例中,我們使用 str[0] 訪問了字符串的第一個字符(H),使用 str[7] 訪問了字符串的第八個字符(W)。

需要注意的是,C語言的字符串以空字符 '\0' 結尾,表示字符串的結束。你可以使用循環來遍歷字符串中的每個字符,直到遇到空字符為止。

例如,下面的示例演示了如何遍歷整個字符串并打印每個字符:

#include <stdio.h>int main() {char str[] = "Hello";// 遍歷字符串并打印每個字符for (int i = 0; str[i] != '\0'; i++) {printf("%c ", str[i]);}return 0;
}

在上述示例中,循環遍歷字符串中的每個字符,直到遇到空字符為止。這樣可以逐個打印字符串中的字符。


1.5 使用C語言的字符串庫

C語言的標準庫(也稱為C標準庫或C庫)提供了許多用于處理字符串的函數。這些函數被定義在頭文件 <string.h> 中,你可以通過包含該頭文件來使用這些函數。以下是一些常見的C語言字符串庫函數:

  1. strlen() 計算字符串的長度(不包括空字符)。

    size_t strlen(const char *str);
    
  2. strcpy() 復制一個字符串到另一個字符串。

    char *strcpy(char *dest, const char *src);
    
  3. strncpy() 從源字符串復制指定數量的字符到目標字符串。

    char *strncpy(char *dest, const char *src, size_t n);
    
  4. strcat() 連接兩個字符串,將一個字符串附加到另一個字符串的末尾。

    char *strcat(char *dest, const char *src);
    
  5. strncat() 將指定數量的字符從源字符串附加到目標字符串。

    char *strncat(char *dest, const char *src, size_t n);
    
  6. strcmp() 比較兩個字符串,返回一個整數表示比較結果。

    int strcmp(const char *str1, const char *str2);
    
  7. strncmp() 比較兩個字符串的指定數量字符,返回一個整數表示比較結果。

    int strncmp(const char *str1, const char *str2, size_t n);
    
  8. strstr() 在一個字符串中查找另一個子字符串,返回子字符串的第一個匹配位置。

    char *strstr(const char *haystack, const char *needle);
    
  9. strchr() 在字符串中查找指定字符的第一個匹配位置。

    char *strchr(const char *str, int c);
    
  10. strtok() 分割字符串為多個子字符串,使用指定的分隔符。

   char *strtok(char *str, const char *delimiters);
  1. sprintf() 將格式化的數據寫入字符串。

    int sprintf(char *str, const char *format, ...);
    
  2. sscanf() 從字符串中按照指定格式讀取數據。

    int sscanf(const char *str, const char *format, ...);
    
  3. memset() 將指定值設置給一段內存。

    void *memset(void *s, int c, size_t n);
    
  4. memcpy() 復制一段內存內容到另一段內存。

    void *memcpy(void *dest, const void *src, size_t n);
    
  5. memmove() 安全地復制一段內存內容到另一段內存,避免重疊問題。

    void *memmove(void *dest, const void *src, size_t n);
    

這些函數是C語言字符串操作的基礎工具,它們使得處理字符串變得更加簡單和高效。可以根據需要使用這些函數來完成不同的字符串操作任務。


1.6 字符串常見用法

C語言字符串在編程中有許多常見的用法,涵蓋了從字符串操作到字符串輸入輸出的多個方面。以下是一些常見的C語言字符串用法示例:

  1. 字符串賦值和初始化: 可以使用字符數組或字符指針來初始化字符串變量,也可以將字符串字面量賦值給字符串變量。

    char str1[] = "Hello, World!"; // 使用字符數組初始化
    char *str2 = "Welcome"; // 使用字符指針初始化
    char str3[20]; // 未初始化的字符數組
    strcpy(str3, "C programming"); // 復制字符串到字符數組
    
  2. 字符串連接: 使用 strcat() 函數將一個字符串連接到另一個字符串的末尾。

    char str1[20] = "Hello";
    char str2[] = " World!";
    strcat(str1, str2); // 連接字符串
    
  3. 字符串長度: 使用 strlen() 函數計算字符串的長度(不包括空字符)。

    char str[] = "Hello";
    int length = strlen(str); // 計算字符串長度
    
  4. 字符串比較: 使用 strcmp() 函數比較兩個字符串是否相等。

    char str1[] = "Hello";
    char str2[] = "World";
    int result = strcmp(str1, str2); // 比較字符串
    
  5. 字符串輸入輸出: 使用 printf() 輸出字符串,使用 scanf()fgets() 輸入字符串。

    char str[] = "C programming";
    printf("String: %s\n", str); // 輸出字符串char input[50];
    printf("Enter a string: ");
    scanf("%s", input); // 使用 scanf 輸入字符串char buffer[100];
    printf("Enter a string: ");
    fgets(buffer, sizeof(buffer), stdin); // 使用 fgets 安全輸入字符串
    
  6. 遍歷字符串: 使用循環遍歷字符串中的每個字符。

    char str[] = "Hello";
    for (int i = 0; str[i] != '\0'; i++) {printf("%c ", str[i]);
    }
    
  7. 將字符串轉換為數字: 使用 atoi()strtol() 將字符串轉換為整數,使用 atof() 將字符串轉換為浮點數。

    char numStr[] = "12345";
    int num = atoi(numStr); // 轉換為整數
    
  8. 從字符串中提取子字符串: 使用 strtok() 函數將字符串分割為子字符串。

    char str[] = "apple,banana,cherry";
    char *token = strtok(str, ",");
    while (token != NULL) {printf("%s\n", token);token = strtok(NULL, ",");
    }
    

這些只是C語言字符串的一些常見用法示例。字符串在C編程中扮演著重要角色,用于處理文本和字符數據,進行各種操作和處理。


1.7 字符串數組

C語言字符串數組是一種數組,其中的每個元素都是字符串(字符數組)。字符串數組允許你同時存儲和操作多個字符串。每個字符串都是以字符數組的形式存儲,其中的每個字符占據一個數組元素的位置,以空字符 '\0' 結尾表示字符串的結束。

以下是聲明和初始化字符串數組的基本語法:

char string_array[num_strings][max_length];

在上述語法中,num_strings 是字符串數組中的字符串數量,max_length 是每個字符串的最大長度(包括空字符 '\0')。

以下是一個示例,展示如何聲明、初始化和訪問字符串數組:

#include <stdio.h>int main() {char names[3][20] = {"Alice","Bob","Charlie"};// 輸出字符串數組for (int i = 0; i < 3; i++) {printf("Name %d: %s\n", i + 1, names[i]);}return 0;
}

在這個示例中,我們聲明了一個包含3個字符串,每個字符串最大長度為20的字符串數組。然后通過循環遍歷輸出了字符串數組的內容。

字符串數組非常適用于存儲和處理一組相關的字符串,例如名字列表、詞匯表等。與一維字符數組不同,字符串數組允許你一次存儲和處理多個字符串,使代碼更加模塊化和易于維護。


2 指針

2.1 指針定義與使用

C語言中的指針是一種變量,用于存儲內存地址。指針可以指向不同類型的數據,例如整數、字符、浮點數、數組、結構體等。指針允許你直接訪問內存中的數據,進行動態內存分配、傳遞函數參數等操作。

以下是指針的定義和使用示例:

  1. 定義指針: 指針變量存儲某個變量的內存地址。

    int x = 10; // 整數變量
    int *ptr;   // 整數指針變量
    ptr = &x;   // 指針指向 x 的地址
    
  2. 訪問指針指向的值: 使用解引用運算符 * 可以訪問指針所指向的值。

    int value = *ptr; // 獲取指針指向的值
    printf("Value: %d\n", value);
    
  3. 修改指針指向的值: 通過指針可以修改所指向內存的值。

    *ptr = 20; // 修改指針指向的值
    printf("New value: %d\n", x);
    
  4. 指針的算術運算: 指針可以進行加法、減法等運算,以在內存中移動。

    int arr[5] = {1, 2, 3, 4, 5};
    int *arrPtr = arr; // 指向數組的第一個元素
    arrPtr++; // 指向下一個元素
    
  5. 指針和函數: 可以將指針作為參數傳遞給函數,以在函數中修改變量的值。

    void modifyValue(int *ptr) {*ptr = 100; // 修改指針指向的值
    }int main() {int num = 50;modifyValue(&num);printf("Modified value: %d\n", num);return 0;
    }
    
  6. 動態內存分配: 使用 malloc() 函數動態分配內存,返回一個指向分配內存的指針。

    int *dynamicPtr = (int *)malloc(sizeof(int));
    *dynamicPtr = 100;
    free(dynamicPtr); // 釋放動態分配的內存
    
  7. 指向數組: 數組名本身就是指向數組第一個元素的指針。

    int arr[3] = {1, 2, 3};
    int *arrPtr = arr; // 指向數組的第一個元素
    
  8. 指向字符串: 字符串可以使用字符指針或字符數組來表示。

    char *str = "Hello"; // 字符指針
    char strArr[] = "World"; // 字符數組
    

指針在C語言中非常重要,它提供了對內存的底層訪問,可以實現靈活的數據處理和操作。同時,指針也需要小心使用,避免野指針(指向無效內存)和內存泄漏等問題。


2.2 指針大小,野指針和空指針

在C語言中,指針是一種變量,用于存儲內存地址。指針的大小取決于編譯器和系統的架構,通常在32位系統上為4字節,在64位系統上為8字節。這表示指針變量本身存儲了一個內存地址,該地址指向存儲的數據。

  1. 野指針(Wild Pointer): 野指針是指沒有正確初始化的指針,或者指向無效內存地址的指針。使用野指針可能導致程序崩潰或產生不可預測的結果。

    int *wildPtr; // 野指針,未初始化
    int x;
    int *wildPtr2 = &x; // 野指針,指向未分配的內存
    
  2. 空指針(Null Pointer): 空指針是指不指向任何有效內存地址的指針。在C語言中,空指針通常用宏 NULL 表示,它的值為0。

    int *nullPtr = NULL; // 空指針
    

    空指針可以用于表示指針變量沒有有效值,也可以用于初始化指針變量。

    int *ptr = NULL;
    if (ptr == NULL) {printf("Pointer is NULL\n");
    }
    

正確使用指針是很重要的,應該始終初始化指針并確保它指向有效的內存。避免使用野指針,對于未初始化的指針,最好將其設置為NULL。


2.3 泛型指針

在C語言中,泛型指針(Generic Pointer)通常指的是 void 指針,它是一種通用的指針類型,可以指向任何數據類型。void 指針可以存儲任何類型的內存地址,但它不知道所指向的內存內容的數據類型,因此在使用時需要進行類型轉換。

以下是泛型指針的基本用法示例:

#include <stdio.h>int main() {int x = 10;float y = 3.14;char ch = 'A';// 使用 void 指針存儲不同類型的地址void *ptr;ptr = &x;printf("Value at int pointer: %d\n", *(int *)ptr);ptr = &y;printf("Value at float pointer: %f\n", *(float *)ptr);ptr = &ch;printf("Value at char pointer: %c\n", *(char *)ptr);return 0;
}

在上述示例中,我們聲明了一個 void 指針 ptr,然后將它分別指向不同類型的變量(整數、浮點數、字符)。在使用 printf 時,我們需要使用類型轉換將 void 指針轉換為正確的指針類型,并使用解引用操作符 * 獲取所指向內存的值。

請注意,使用 void 指針需要格外小心,因為它無法提供編譯時的類型檢查,容易導致類型不匹配的錯誤。在進行類型轉換時,務必確保轉換的類型與實際內存中的數據類型匹配,以避免未定義的行為和錯誤。


2.4 指針類型以及使用

在C語言中,指針類型是指指針所指向的數據類型。指針類型決定了指針的操作和解引用方式。以下是C語言中常見的一些指針類型以及如何使用它們:

  1. 整型指針(int pointer): 指向整數類型的指針。

    int x = 10;
    int *ptr; // 聲明整型指針
    ptr = &x; // 指針指向整數變量 x 的地址
    

    解引用整型指針:

    int value = *ptr; // 獲取指針所指向的整數值
    
  2. 字符型指針(char pointer): 指向字符類型的指針。

    char ch = 'A';
    char *chPtr; // 聲明字符型指針
    chPtr = &ch; // 指針指向字符變量 ch 的地址
    

    解引用字符型指針:

    char character = *chPtr; // 獲取指針所指向的字符值
    
  3. 浮點型指針(float pointer): 指向浮點數類型的指針。

    float y = 3.14;
    float *fPtr; // 聲明浮點型指針
    fPtr = &y; // 指針指向浮點數變量 y 的地址
    

    解引用浮點型指針:

    float number = *fPtr; // 獲取指針所指向的浮點數值
    
  4. 指向指針的指針(pointer to pointer): 指向另一個指針的指針。

    int x = 10;
    int *ptr1 = &x;
    int **ptrPtr; // 聲明指向指針的指針
    ptrPtr = &ptr1; // 指向指針變量 ptr1 的地址
    

    解引用指向指針的指針:

    int value = **ptrPtr; // 獲取指向指針的指針所指向的整數值
    
  5. 數組指針(array pointer): 指向數組的指針。

    int arr[5] = {1, 2, 3, 4, 5};
    int *arrPtr; // 聲明數組指針
    arrPtr = arr; // 指向數組的第一個元素
    

    使用數組指針來遍歷數組:

    for (int i = 0; i < 5; i++) {printf("%d ", *(arrPtr + i)); // 輸出數組元素
    }
    
  6. 函數指針(function pointer): 指向函數的指針,可以用于調用函數。

    int add(int a, int b) {return a + b;
    }int (*funcPtr)(int, int); // 聲明函數指針
    funcPtr = add; // 指向函數 add
    int result = funcPtr(3, 4); // 通過函數指針調用函數
    
  7. void 指針(void pointer): 通用指針,可以指向任何數據類型,但需要進行強制類型轉換后才能使用。

    int x = 10;
    float y = 3.14;
    void *ptr; // 聲明 void 指針
    ptr = &x; // 指向整數變量 x 的地址
    ptr = &y; // 重新指向浮點數變量 y 的地址
    

指針類型是C語言中非常重要的概念,它們允許你在程序中操作內存中的數據。不同類型的指針允許你處理不同類型的數據,同時也可以用于傳遞參數、動態內存分配、數據結構等操作。需要小心使用指針,避免出現指針錯誤(如空指針、野指針等),并始終確保指針指向有效的內存位置。


2.5 指向指針的指針

上面提到過這個指向指針的指針,現在再詳細的介紹一下。C語言中的指向指針的指針(Pointer to Pointer)是一種非常有用的概念,它允許你在程序中操作指針變量的指針。指向指針的指針通常用于多級間接訪問,例如動態數組、多維數組、鏈表等數據結構。

以下是指向指針的指針的基本用法示例:

#include <stdio.h>int main() {int x = 10;int *ptr1 = &x; // 指向整數的指針int **ptr2 = &ptr1; // 指向指針的指針printf("Value of x: %d\n", x);printf("Value at ptr1: %d\n", *ptr1);printf("Value at ptr2 (using double indirection): %d\n", **ptr2);return 0;
}

在上述示例中,我們首先聲明一個整數變量 x 和一個指向整數的指針 ptr1,然后聲明一個指向指針的指針 ptr2,將其指向 ptr1。通過不同級別的間接訪問,我們可以訪問到 x 的值。

指向指針的指針特別在以下情況下很有用:

  1. 多級間接訪問: 可以通過多級間接訪問來訪問嵌套的數據結構,如鏈表中的節點。
  2. 動態數組: 在動態內存分配時,使用指向指針的指針來管理數組。
  3. 多維數組: 用于表示和操作多維數組,如指向指針的指針數組。
  4. 傳遞指針: 可以在函數間傳遞指向指針的指針,以實現對指針變量的修改。

以下是一個示例,演示如何使用指向指針的指針來創建一個動態二維數組:

#include <stdio.h>
#include <stdlib.h>int main() {int rows = 3, cols = 4;// 動態分配二維數組int **matrix = (int **)malloc(rows * sizeof(int *));for (int i = 0; i < rows; i++) {matrix[i] = (int *)malloc(cols * sizeof(int));}// 初始化并輸出二維數組for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {matrix[i][j] = i * cols + j;printf("%2d ", matrix[i][j]);}printf("\n");}// 釋放內存for (int i = 0; i < rows; i++) {free(matrix[i]);}free(matrix);return 0;
}

在這個示例中,我們使用指向指針的指針來創建一個動態二維數組,然后對其進行初始化和輸出,最后釋放內存。指向指針的指針讓我們能夠動態創建多維數據結構,非常有用。


2.6 指針作為參數與指針作為返回值

上面簡單的提到過指針作為參數,下面詳細的總結一下。在C語言中,指針既可以作為函數的參數,也可以作為函數的返回值。這些用法使得可以在函數間傳遞指針,實現對變量的修改,以及在函數內部動態分配內存并返回指向該內存的指針。

以下是指針作為參數和作為返回值的示例:

  1. 指針作為參數: 可以將指針作為參數傳遞給函數,這允許在函數內部修改指針所指向的數據。

    #include <stdio.h>void modifyPointer(int *ptr) {*ptr = 100; // 修改指針所指向的值
    }int main() {int num = 50;printf("Before: %d\n", num);modifyPointer(&num);printf("After: %d\n", num);return 0;
    }
    
  2. 指針作為返回值: 函數可以返回指針,通常用于動態分配內存,并返回指向該內存的指針。

    #include <stdio.h>
    #include <stdlib.h>int *createArray(int size) {int *arr = (int *)malloc(size * sizeof(int));for (int i = 0; i < size; i++) {arr[i] = i + 1;}return arr;
    }int main() {int *arr = createArray(5);for (int i = 0; i < 5; i++) {printf("%d ", arr[i]);}free(arr); // 釋放動態分配的內存return 0;
    }
    

在上述示例中,modifyPointer 函數接受一個指針作為參數,通過解引用修改了指針所指向的值。createArray 函數動態分配了一個整數數組,并返回指向該數組的指針。

指針作為參數和作為返回值使得函數能夠更靈活地處理數據,并在需要時進行修改或動態分配內存。但是在使用指針時,需要小心避免野指針、內存泄漏和越界訪問等問題。


3 指針和數組

3.1 指針的算術運算

C語言中,指針的算術運算(Pointer Arithmetic)允許你對指針進行加法、減法等運算,以便在內存中移動指針位置。這在處理數組、字符串和數據結構等情況下非常有用。

以下是指針的算術運算示例:

#include <stdio.h>int main() {int arr[] = {10, 20, 30, 40, 50};int *ptr = arr; // 指向數組的第一個元素printf("Array elements: ");for (int i = 0; i < 5; i++) {printf("%d ", *(ptr + i)); // 輸出數組元素}printf("\n");// 指針加法ptr = ptr + 2; // 指向第三個元素printf("Third element: %d\n", *ptr);// 指針減法ptr = ptr - 1; // 回到第二個元素printf("Second element: %d\n", *ptr);return 0;
}

在上述示例中,我們首先聲明了一個整數數組 arr,然后將一個指向數組第一個元素的指針 ptr 初始化為數組的起始地址。使用指針的算術運算,我們可以遍歷數組元素并進行加法、減法等操作。

指針算術的規則:

  • 指針加法和減法的結果是一個新的指針,指向移動后的內存位置。
  • 指針加法會根據指針所指向數據類型的大小移動指針位置。例如,整型指針加1,實際上會移動4字節(在32位系統上)或8字節(在64位系統上)。
  • 指針減法會倒退指針位置,也遵循相同的數據類型大小。
  • 指針可以與整數值進行加法和減法運算,這將會移動指針多個單位。

需要注意的是,指針算術要小心避免越界訪問,確保在合法范圍內進行操作,以避免訪問無效內存。

?


3.2 指針用于數組處理

C語言中,指針在數組處理中扮演著非常重要的角色,可以通過指針來訪問和操作數組的元素,以及實現動態內存分配和釋放。以下是指針在數組處理中的一些常見用法:

  1. 遍歷數組元素: 可以使用指針來遍歷數組的元素,以便訪問和操作數組中的數據。

    #include <stdio.h>int main() {int arr[] = {10, 20, 30, 40, 50};int *ptr = arr; // 指向數組的第一個元素for (int i = 0; i < 5; i++) {printf("%d ", *(ptr + i)); // 輸出數組元素}printf("\n");return 0;
    }
    
  2. 傳遞數組給函數: 可以將數組傳遞給函數,并在函數內部使用指針來操作數組。

    #include <stdio.h>void printArray(int *arr, int size) {for (int i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n");
    }int main() {int arr[] = {10, 20, 30, 40, 50};int size = sizeof(arr) / sizeof(arr[0]);printArray(arr, size);return 0;
    }
    
  3. 動態內存分配: 使用指針和動態內存分配函數(如 malloc)可以動態創建數組。

    #include <stdio.h>
    #include <stdlib.h>int main() {int size;printf("Enter the size of the array: ");scanf("%d", &size);int *arr = (int *)malloc(size * sizeof(int));if (arr == NULL) {printf("Memory allocation failed.\n");return 1;}for (int i = 0; i < size; i++) {arr[i] = i + 1;}for (int i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n");free(arr);return 0;
    }
    
  4. 多維數組處理: 指針可以用于處理多維數組,可以通過適當的指針算術來訪問多維數組的元素。

    #include <stdio.h>int main() {int matrix[3][4] = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}};int *ptr = &matrix[0][0];for (int i = 0; i < 3; i++) {for (int j = 0; j < 4; j++) {printf("%2d ", *(ptr + i * 4 + j));}printf("\n");}return 0;
    }
    

指針在數組處理中具有靈活性和效率,它允許你直接訪問數組元素并進行操作,同時也能夠處理動態內存分配和多維數組。然而,在使用指針處理數組時,需要小心越界訪問和內存泄漏等問題。 ?


3.3 用數組名作為指針

在C語言中,數組名可以被視為指向數組第一個元素的指針。這是一種方便的用法,允許你通過數組名來訪問數組元素,或者將數組名傳遞給函數來操作數組。

以下是使用數組名作為指針的示例:

  1. 訪問數組元素: 可以使用數組名加索引的方式來訪問數組元素。

    #include <stdio.h>int main() {int arr[] = {10, 20, 30, 40, 50};printf("First element: %d\n", arr[0]);printf("Second element: %d\n", arr[1]);return 0;
    }
    
  2. 傳遞數組給函數: 可以將數組名作為指針參數傳遞給函數,并在函數內部操作數組。

    #include <stdio.h>void printArray(int *arr, int size) {for (int i = 0; i < size; i++) {printf("%d ", arr[i]);}printf("\n");
    }int main() {int arr[] = {10, 20, 30, 40, 50};int size = sizeof(arr) / sizeof(arr[0]);printArray(arr, size);return 0;
    }
    
  3. 數組指針算術: 可以使用數組名作為指針,并進行指針算術來遍歷數組元素。

    #include <stdio.h>int main() {int arr[] = {10, 20, 30, 40, 50};int *ptr = arr; // 數組名作為指針for (int i = 0; i < 5; i++) {printf("%d ", *(ptr + i));}printf("\n");return 0;
    }
    

需要注意的是,雖然數組名可以被視為指針,但是數組名并不是真正的指針變量。數組名的值是數組的首地址,但是數組名不能進行賦值和修改。例如,以下代碼是無效的:

int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr; // 有效
arr = ptr; // 無效,數組名不能被重新賦值

使用數組名作為指針可以簡化代碼并提高可讀性,但也要注意在使用時避免越界訪問和其他指針相關的問題。


3.4 指針和多維數組

C語言中,指針在處理多維數組時發揮了重要作用。多維數組實際上是數組的數組,因此在處理多維數組時,可以使用指針來訪問和操作數組的元素。以下是指針在多維數組處理中的一些常見用法:

  1. 使用指針遍歷多維數組: 可以使用指針來遍歷多維數組的元素,通過適當的指針算術來訪問數組的不同維度。

    #include <stdio.h>int main() {int matrix[3][4] = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}};int *ptr = &matrix[0][0]; // 指向數組的第一個元素for (int i = 0; i < 3; i++) {for (int j = 0; j < 4; j++) {printf("%2d ", *(ptr + i * 4 + j)); // 使用指針算術訪問元素}printf("\n");}return 0;
    }
    
  2. 傳遞多維數組給函數: 可以將多維數組傳遞給函數,并在函數內部使用指針來操作數組。

    #include <stdio.h>void printMatrix(int (*matrix)[4], int rows, int cols) {for (int i = 0; i < rows; i++) {for (int j = 0; j < cols; j++) {printf("%2d ", matrix[i][j]);}printf("\n");}
    }int main() {int matrix[3][4] = {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}};printMatrix(matrix, 3, 4);return 0;
    }
    

在傳遞多維數組給函數時,需要使用指針來聲明函數參數,以便正確傳遞數組的維度信息。

需要注意的是,多維數組的內存布局是連續的,但在C語言中多維數組與指針的關系是復雜的。例如,二維數組可以被視為指向一維數組的指針數組,但它不是真正的指針數組。在處理多維數組時,要小心越界訪問和內存布局問題。


4 預處理器

4.1 預處理器的工作原理

C語言預處理器是C編譯器的一個重要組成部分,它在實際的編譯過程之前對源代碼進行預處理,執行一系列的文本替換和宏展開操作,以及處理條件編譯等任務。預處理器的主要任務是對源代碼進行預處理,生成經過處理的中間代碼,然后再由編譯器進一步處理生成目標代碼。

預處理器的工作原理如下:

  1. 文本替換和宏展開: 預處理器會根據預處理指令對源代碼進行文本替換和宏展開。例如,通過#define指令定義的宏會被展開為相應的文本。

    #define PI 3.14159
    float area = PI * radius * radius;
    

    在上述代碼中,預處理器會將PI宏展開為3.14159,生成實際的表達式。

  2. 頭文件包含: 預處理器可以通過#include指令將外部文件(頭文件)的內容插入到源代碼中。這有助于模塊化和代碼重用。

    #include <stdio.h>
    int main() {printf("Hello, World!\n");return 0;
    }
    

    在上述代碼中,#include <stdio.h>會將標準輸入輸出庫的內容插入到源代碼中。

  3. 條件編譯: 預處理器可以根據條件指令,如#ifdef#ifndef#if#else#elif#endif,在編譯時決定是否包含某些代碼塊。

    #define DEBUG 1
    #ifdef DEBUGprintf("Debug mode is enabled.\n");
    #endif
    

    在上述代碼中,如果DEBUG宏被定義,那么printf語句會被包含在編譯中。

  4. 其他預處理指令: 預處理器還支持其他指令,如#undef(取消宏定義)、#line(設置行號和文件名)、#error(產生錯誤消息)、#pragma(編譯器指示)等。

    #line 42 "mycode.c"
    #error This is an error message.
    #pragma warning(disable: 1234)
    

預處理器在編譯之前處理源代碼,將源代碼轉換為經過宏展開和文本替換后的中間代碼。這些經過處理的中間代碼會交給編譯器進行實際的編譯,生成目標代碼和最終的可執行文件。這種分階段的處理使得C語言具有靈活性和可維護性。


4.2 預處理指令

C語言預處理器指令是在編譯過程之前對源代碼進行預處理的指令,用于進行文本替換、宏展開、條件編譯等操作。以下是一些常用的C語言預處理指令:

  1. #define: 定義宏,用于進行簡單的文本替換和宏展開。

    #define PI 3.14159
    
  2. #include: 包含外部文件的內容,通常用于包含頭文件。

    #include <stdio.h>
    
  3. #ifdef / #ifndef / #endif: 條件編譯,根據條件是否定義了宏來決定是否編譯代碼塊。

    #ifdef DEBUGprintf("Debug mode is enabled.\n");
    #endif
    
  4. #if / #elif / #else: 在條件為真時編譯代碼塊,類似于if語句。

    #if defined(X) && (Y > 0)// code to be compiled if X is defined and Y is positive
    #elif defined(Z)// code to be compiled if Z is defined
    #else// code to be compiled if none of the above conditions are true
    #endif
    
  5. #undef: 取消宏定義。

    #undef PI
    
  6. #pragma: 發送編譯器特定的指示,如關閉警告、設定對齊方式等。

    #pragma warning(disable: 1234)
    
  7. #error: 產生編譯錯誤并輸出自定義錯誤消息。

    #ifdef DEBUG#error Debug mode is not supported in this version.
    #endif
    
  8. #line: 設置行號和文件名,用于調試和錯誤報告。

    #line 42 "mycode.c"
    

這些預處理指令在編譯之前會被預處理器處理,將源代碼中的宏展開、文件包含、條件編譯等操作轉換成中間代碼,然后再由編譯器進行實際的編譯。預處理器指令使得C語言具有更高的靈活性和可維護性,能夠根據不同的需求來進行定制化的編譯過程。


4.3 宏定義

C語言宏定義是一種預處理器指令,用于在源代碼中進行文本替換和宏展開,以便在編譯階段生成相應的代碼。宏定義可以用于定義常量、函數宏、條件編譯等,以提高代碼的可讀性和維護性。宏定義使用#define關鍵字進行定義。

以下是一些常見的C語言宏定義用法:

  1. 定義常量: 使用宏定義來創建常量,以便在代碼中使用。

    #define PI 3.14159
    #define MAX_SIZE 100
    
  2. 定義函數宏: 創建函數宏來實現簡單的代碼替換。

    #define SQUARE(x) ((x) * (x))
    
  3. 帶參數的函數宏: 創建帶參數的函數宏,可以根據傳入的參數進行展開。

    #define MAX(a, b) ((a) > (b) ? (a) : (b))
    
  4. 條件編譯: 使用宏定義來進行條件編譯,根據不同的宏定義決定是否編譯某部分代碼。

    #define DEBUG
    #ifdef DEBUGprintf("Debug mode is enabled.\n");
    #endif
    
  5. 宏與字符串: 可以使用宏定義來創建字符串常量。

    #define MESSAGE "Hello, World!"
    
  6. 多行宏: 可以使用反斜杠 \ 在多行上定義宏。

    #define ADD(a, b) \do { \printf("Adding %d and %d\n", a, b); \(a) + (b); \} while (0)
    
  7. 取消宏定義: 可以使用#undef取消已定義的宏。

    #undef PI
    

宏定義在預處理階段被展開為實際的代碼,這意味著宏定義并不是在編譯階段進行類型檢查或其他語法分析,而是簡單的文本替換。因此,在使用宏定義時要小心確保正確的使用方式,避免由于展開引發意想不到的問題。


4.4 條件編譯

C語言條件編譯是一種預處理器功能,允許你根據預定義的宏或條件來選擇性地編譯代碼塊。條件編譯在不同平臺、不同編譯選項或不同情況下可以選擇性地包含或排除代碼,以便在不同環境中實現代碼的靈活性和可移植性。

條件編譯的關鍵指令包括:#ifdef#ifndef#else#elif#endif

以下是條件編譯的一些用法示例:

  1. #ifdef#endif 如果某個宏已經定義,則編譯下面的代碼塊。

    #define DEBUG
    #ifdef DEBUGprintf("Debug mode is enabled.\n");
    #endif
    
  2. #ifndef#endif 如果某個宏未定義,則編譯下面的代碼塊。

    #ifndef RELEASEprintf("This is not a release version.\n");
    #endif
    
  3. #else 如果前面的條件不成立,則編譯下面的代碼塊。

    #ifdef DEBUGprintf("Debug mode is enabled.\n");
    #elseprintf("Debug mode is not enabled.\n");
    #endif
    
  4. #elif#else 類似,但可以用于在多個條件之間進行選擇。

    #ifdef DEBUGprintf("Debug mode is enabled.\n");
    #elif defined(TESTING)printf("Testing mode is enabled.\n");
    #elseprintf("No special mode is enabled.\n");
    #endif
    

條件編譯的主要目的是根據編譯時的不同條件在代碼中進行選擇,從而允許在不同情況下使用不同的代碼塊,而不需要修改源代碼。這對于實現跨平臺兼容性、調試功能、開發和生產環境切換等方面非常有用。需要注意的是,條件編譯在編譯過程中會影響代碼的可讀性,因此應該謹慎使用,避免過度復雜的條件分支。

?

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/36409.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/36409.shtml
英文地址,請注明出處:http://en.pswp.cn/news/36409.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

目前有哪些好用的免費開源wms倉儲管理軟件?

什么是開源&#xff1f; 開源指的是軟件的源代碼是公開可見和可自由使用的。開源軟件的授權許可通常允許用戶查看、修改和分發源代碼&#xff0c;以及根據自己的需求進行定制和擴展。 開源工具的核心理念是共享和協作。通過開放源代碼&#xff0c;開源軟件鼓勵用戶之間的合作…

Tubi 前端測試:遷移 Enzyme 到 React Testing Library

前端技術發展迅速&#xff0c;即便不說是日新月異&#xff0c;每年也都推出新框架和新技術。Tubi 的產品前端代碼倉庫始建于 2015 年&#xff0c;至今 8 年有余。可喜的是&#xff0c;多年來緊隨 React 社區的發展&#xff0c;Tubi 絕大多數的基礎框架選型都遵循了社區流行的最…

CentOS-6.3安裝MySQL集群

安裝要求 安裝環境&#xff1a;CentOS-6.3 安裝方式&#xff1a;源碼編譯安裝 軟件名稱&#xff1a;mysql-cluster-gpl-7.2.6-linux2.6-x86_64.tar.gz 下載地址&#xff1a;http://mysql.mirror.kangaroot.net/Downloads/ 軟件安裝位置&#xff1a;/usr/local/mysql 數據存放位…

達夢數據庫(dm8) Centos7 高可用集群

國產數據庫-達夢 一、環境詳情二、Centos7 參數優化&#xff08;所有節點&#xff09;三、創建用戶&#xff08;所有節點&#xff09;四、開始安裝&#xff08;所有節點&#xff09;五、服務注冊啟動 當前安裝&#xff1a;在指定版本環境下 測試&#xff0c;僅供參考 官網描述&…

風丘科技將亮相 EVM ASIA 2023

風丘科技將首次亮相 EVM ASIA 2023 WINDHILL will debut EVM ASIA 2023 ——可持續移動的未來 —The Future of SUSTAINABLE Mobility EVM ASIA 2023是亞太地區電氣化的國際性展會&#xff0c;專注于新能源汽車、充電技術及汽車零件制造等。展會致力于促進包括充電站、交通…

[系統安全] 五十二.DataCon競賽 (1)2020年Coremail釣魚郵件識別及分類詳解

您可能之前看到過我寫的類似文章,為什么還要重復撰寫呢?只是想更好地幫助初學者了解病毒逆向分析和系統安全,更加成體系且不破壞之前的系列。因此,我重新開設了這個專欄,準備系統整理和深入學習系統安全、逆向分析和惡意代碼檢測,“系統安全”系列文章會更加聚焦,更加系…

InnoDB文件物理結構解析5 - FIL_PAGE_INDEX

本文討論FIL_PAGE_INDEX頁的可回收垃圾記錄(Garbage/Deleted Records)&#xff0c;當我們刪除某一條記錄(delete from …)時&#xff0c;通常InnoDB并不會在物理存儲上進行完全刪除&#xff0c;而是在記錄上置一個刪除標志位&#xff0c;我們稱這些行記錄為垃圾記錄&#xff0c…

嵌入式Qt開發—Excel表格數據導出

有一個嵌入式Excel表格數據導出的需求&#xff1a;應用軟件運行于嵌入式Linux平臺上&#xff0c;在設備運行過程中&#xff0c;存儲了許多數據&#xff0c;這些數據想以表格的形式導出。考慮到Windows平臺的普遍性&#xff0c;需要將數據以excel表格形式導出&#xff0c;故選擇…

python庫打包

一、背景 想讓自己寫的python庫可以使用pip install xxx安裝。 二、環境準備 注冊PYPI賬號已經寫好的能正常使用的庫/方法/項目&#xff08;可以本地調用&#xff09;安裝依賴庫setuptools和twinw pip install setuptools pip install twine # 簡化將庫發布到PYPI流程的工…

“中國軟件杯”飛槳賽道晉級決賽現場名單公布

“中國軟件杯”大學生軟件設計大賽是由國家工業和信息化部、教育部、江蘇省人民政府共同主辦&#xff0c;是全國軟件行業規格最高、最具影響力的國家級一類賽事&#xff0c;為《全國普通高校競賽排行榜》榜單內賽事。今年&#xff0c;組委會聯合百度飛槳共同設立了“智能系統設…

C++11之后的C++標準特性宏定義方便功能特性測試

C是一個龐大的編程語言體系&#xff0c;它的高效性是可以直接連接硬件系統&#xff0c;它的靈活性是不斷迭代完善的通用語義機制&#xff0c;當下C的發展演進可謂一路狂奔。不同應用中需要知道C對應的平臺或者版本的功能特性&#xff0c;標準庫信息、C編譯器特性等&#xff0c;…

基于PHP的輕量級博客typecho

本文完成于 5 月中旬&#xff0c;發布時未在最新版本上驗證&#xff1b; 什么是 typecho &#xff1f; Typecho 是一款基于 PHP 的博客軟件&#xff0c;旨在成為世界上最強大的博客引擎。Typecho 在 GNU 通用公共許可證 2.0 下發布。支持多種數據庫&#xff0c;原生支持 Markdo…

24屆近5年南京大學自動化考研院校分析

今天給大家帶來的是南京大學控制考研分析 滿滿干貨&#xff5e;還不快快點贊收藏 一、南京大學 學校簡介 南京大學是一所歷史悠久、聲譽卓著的高等學府。其前身是創建于1902年的三江師范學堂&#xff0c;此后歷經兩江師范學堂、南京高等師范學校、國立東南大學、國立第四中…

JS 刪除的是最后一頁的最后一條,頁碼設置邏輯

刪除的場景&#xff1a; 解決思路&#xff1a; 1、計算操作后的總頁數 2、刪除成功之后的總頁數與當前總頁數進行比較 3、如果刪除成功之后的總頁數比小于當前總頁數&#xff0c;需要把當前頁碼減去1&#xff1b;否則&#xff0c;直接進行列表數據的請求 代碼實現 /*總條數…

VBA 學習筆記1 對象以及屬性

目錄 1 取得VBA對象1.1 取得工作簿對象1.2 取得工作表對象1.3 取得單元格對象1.4 取得對象的屬性1.5 文檔的方法1 進入vba 界面 方式之一&#xff1a; 快捷鍵&#xff1a;ALTERF11 運行方式之一&#xff1a; 進入vba界面&#xff0c;點擊綠色三角符號 1 取得VBA對象 1.1 取得…

DAY21

題目一 給定三個字符串str1、str2和aim&#xff0c; 如果aim包含且僅包含來自str1和str2的所有字符&#xff0c;而且在aim中屬于str1的字符 之間保持原來在str1中的順序&#xff0c;屬于str2的字符之間保持原來在str2中的順序&#xff0c;那么稱aim是str1和str2的交錯組成。實…

Springboot-Retrofit HTTP工具框架快速使用

在SpringBoot項目直接使用okhttp、httpClient或者RestTemplate發起HTTP請求&#xff0c;既繁瑣又不方便統一管理。 因此&#xff0c;在這里推薦一個適用于SpringBoot項目的輕量級HTTP客戶端框架retrofit-spring-boot-starter&#xff0c;使用非常簡單方便&#xff0c;同時又提供…

約數個數(質因子分解)

思路&#xff1a; &#xff08;1&#xff09;由數論基本定理&#xff0c;任何一個正整數x都能寫作&#xff0c;其中p1,p2..pk為x的質因子。 &#xff08;2&#xff09;由此可以推斷&#xff0c;要求一個數約數的個數&#xff0c;注意到約數就是p1,p2...pk的一種組合&#xff…

日常BUG—— SpringBoot項目DEBUG模式啟動慢、卡死。

&#x1f61c;作 者&#xff1a;是江迪呀??本文關鍵詞&#xff1a;日常BUG、BUG、問題分析??每日 一言 &#xff1a;存在錯誤說明你在進步&#xff01; 一、問題描述 我們調試程序時&#xff0c;需要使用DEBUG模式啟動SpringBoot項目&#xff0c; 有時候會發…

convert Auto-Login (cwallet.sso) Wallet into a PKCS12 compliant Wallet

一步不行嗎 &#xff1f; 1. If $JAVA_HOME is not set: a)For FMW 11g components associated with a WebLogic Domain or a FMW 12c Collocated OHS install run: $MIDDLEWARE_HOME/user_projects/domains/<domain>/bin/setDomainEnv.sh b) For FMW 11g Standalone…