C語言指針易混淆知識點總結

来源:https://www.cnblogs.com/best-doraemon/p/18316971
-Advertisement-
Play Games

指針 定義 指針是一個變數,存儲另一個變數的記憶體地址,它允許直接訪問和操作記憶體中的數據,使得程式能夠以更靈活和高效的方式處理數據和記憶體。 獲取變數地址:使用取地址符 &。 訪問地址上的數據:使用解引用符 *。 例子1 指針是存儲另一個變數地址的變數。通過使用取地址符 & 和解引用符 *,我們可以靈活 ...


指針

定義

指針是一個變數,存儲另一個變數的記憶體地址,它允許直接訪問和操作記憶體中的數據,使得程式能夠以更靈活和高效的方式處理數據和記憶體。

獲取變數地址:使用取地址符 &

訪問地址上的數據:使用解引用符 *

例子1

指針是存儲另一個變數地址的變數。通過使用取地址符 & 和解引用符 *,我們可以靈活地訪問和操作記憶體中的數據 。

#include <stdio.h>

int main() {
    int var = 10;     // 定義一個整數變數
    int *p = &var;    // 定義一個指向整數的指針,並將其初始化為變數 var 的地址

    printf("Address of var: %p\n", &var); // 輸出變數 var 的地址
    printf("Address stored in pointer p: %p\n", p); // 輸出指針 p 中存儲的地址
    printf("Value of var using pointer: %d\n", *p); // 通過指針 p 解引用獲取 var 的值

    // 修改 var 的值,通過指針 p
    *p = 20;
    printf("New value of var: %d\n", var); // 輸出修改後的 var 的值

    return 0;
}

例子2

指針類型決定了它指向的變數類型,以及通過指針可以訪問的數據大小。不同類型的指針在操作時會有不同的步長,比如:

int *p = (int *)a;:將 char 類型數組的首地址強制轉換為 int 指針類型。由於 int 類型通常占用 4 個位元組,因此通過 p 訪問數據時,每次會讀取 4 個位元組的數據。

char *q = a;:將 char 類型數組的首地址賦值給 char 指針類型。char 類型占用 1 個位元組,因此通過 q 訪問數據時,每次只會讀取 1 個位元組的數據。

#include <stdio.h>

int main() {
    char a[12] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C};
    int *p = (int *)a; // 將 char 數組的地址賦值給 int 指針
    char *q = a;       // 將 char 數組的地址賦值給 char 指針
    
    // 使用 int 指針訪問數據
    printf("Using int pointer:\n");
    for (int i = 0; i < 3; i++) {
        printf("p[%d]: 0x%08x\n", i, *(p + i));
    }
    
    // 使用 char 指針訪問數據
    printf("Using char pointer:\n");
    for (int i = 0; i < 12; i++) {
        printf("q[%d]: 0x%02x\n", i, *(q + i));
    }

    return 0;
}

/*
輸出
Using int pointer:
p[0]: 0x04030201
p[1]: 0x08070605
p[2]: 0x0c0b0a09

Using char pointer:
q[0]: 0x01
q[1]: 0x02
q[2]: 0x03
q[3]: 0x04
q[4]: 0x05
q[5]: 0x06
q[6]: 0x07
q[7]: 0x08
q[8]: 0x09
q[9]: 0x0a
q[10]: 0x0b
q[11]: 0x0c
*/

一級指針和二級指針

一級地址(一級指針)

定義

一級地址指的是指向普通變數的指針,也就是直接存儲變數的地址的指針。在C中,通常我們操作的是一級地址,例如指向整數、浮點數或其他基本數據類型的指針。

例子

int *ptr;  // ptr 是一個指向整數的指針,是一級地址
float *ptr_float;  // ptr_float 是一個指向浮點數的指針,也是一級地址

二級地址(二級指針)

定義

二級地址是指向指針的指針,也就是存儲另一個指針的地址的指針。在C中,可以通過二級指針來操作指向指針的指針,用來間接修改指針指向的值或者傳遞指針的引用。

例子

int x = 10;
int *ptr1 = &x;  // ptr1 是一個指向 x 的指針,是一級地址
int **ptr2 = &ptr1;  // ptr2 是一個指向 ptr1 的指針,是二級地址

例子

分析表達式 *(*(&arr + 1) - 1) 的值:

int arr[5] = {1, 2, 3, 4, 5};
  1. arr[5] = {1, 2, 3, 4, 5}:這定義了一個包含 5 個整數的數組 arr,其元素分別是 {1, 2, 3, 4, 5}
  2. &arr:這是數組 arr 的地址。需要註意的是,&arr 的類型是 int (*)[5],即指向一個包含 5 個整數的數組的指針。
  3. &arr + 1:這是 &arr 指針加 1。在這個上下文中,&arr 被看作是一個指向整個數組的指針,因此 &arr + 1 將指向緊隨 arr 之後的記憶體位置。也就是說,它指向 arr 後面的記憶體地址,而不是數組中的下一個元素。&arr + 1 的類型仍然是 int (*)[5]
  4. *(&arr + 1):這是對 &arr + 1 解引用。&arr + 1 是一個指向數組的指針,對其解引用後,得到的仍然是一個指向數組末尾之後的指針。它的類型是 int *,即指向數組末尾之後的指針。
  5. *(&arr + 1) - 1:這將剛纔得到的指針減去 1。由於指針的減法是以元素為單位的,這個操作將指針向後移動一個 int 的大小。因為 *(&arr + 1) 指向的是數組 arr 末尾之後的位置,減去 1 後,這個指針將指向數組 arr 的最後一個元素。
  6. *(*(&arr + 1) - 1):最後,對指針 *(&arr + 1) - 1 解引用,即獲取這個指針所指向的值。這個指針現在指向 arr 的最後一個元素,所以這個表達式的值就是 arr 的最後一個元素的值。

因此,*(*(&arr + 1) - 1) 的值是 5,即數組 arr 的最後一個元素。

指針的自增運算

*++p

這個表達式是先對指針 p 進行自增操作,然後對新的指針進行解引用,得到新的指針所指向的值。

步驟

  1. ++p:指針 p 先自增,指向下一個元素( 自增的是指針 )。
  2. *:對自增後的指針解引用,得到新指針所指向的值。

++*p

這個表達式是對指針 p 所指向的值進行解引用,然後對解引用得到的值進行自增操作。

步驟

  1. *p:對指針 p 進行解引用,得到指向的值。
  2. ++:對解引用得到的值進行自增操作( 自增的是指針指向的值 )。

函數地址與數組地址

函數地址

函數名就是函數的入口地址,因此可以直接把函數名賦給函數指針,也可以加上取地址符號 &。取地址符號 & 是可選的,它只是顯式地說明瞭編譯器隱式執行的任務。獲取函數的地址時,加不加取地址符號 & 都可以

#include <stdio.h>

int test(int i) {
    return i;
}

int main(void) {
    int (*p1)(int) = test;
    int (*p2)(int) = &test;
    printf("%p, %p\n", (void *)p1, (void *)p2);
    return 0;
}

數組地址

數組名本身是數組第一個元素的地址。例如,對於 int 類型的數組來說,數組名 arr 的類型是 int*,它是一個整型指針,不是數組指針。如果要取整個數組的地址,必須在數組名前面加上取地址符號 &,這樣才能賦值給數組指針。獲取數組的地址時,必須加上取地址符號 &

在下麵的例子中, p1 是一個整型指針,指向數組的第一個元素,而 p2 是一個數組指針,指向整個數組。對 p1p2 分別進行加 1 操作,p1 會加 4 個位元組(假設 int 為 4 位元組),而 p2 會加 20 個位元組(5 個 int)。

#include <stdio.h>

int main(void) {
    int array[5] = {1, 2, 3, 4, 5};
    int* p1 = array;         // 指向數組第一個元素
    int(*p2)[5] = &array;    // 指向整個數組
    printf("p1 %p\n", (void*)p1);
    printf("p2 %p\n", (void*)p2);
    printf("p1+1 %p\n", (void*)(p1+1));  // p1 加 1,增加 4 個位元組(假設 int 為 4 位元組)
    printf("p2+1 %p\n", (void*)(p2+1));  // p2 加 1,增加 20 個位元組(5 個 int)
    return 0;
}

指針和數組

不需要嚴格區分的情況

訪問數組元素

對於數組和指針,都可以使用下標形式或指針形式來訪問元素。

#include <stdio.h>

int main(void) {
    char array[] = "hello world!";
    char* p = array;

    array[1] = 'x';      // 使用下標訪問數組
    *(array + 1) = 'x';  // 使用指針形式訪問數組

    p[1] = 'y';          // 使用下標訪問指針
    *(p + 1) = 'y';      // 使用指針形式訪問指針

    printf("%s\n", array); // 輸出: hyllo world!
    printf("%s\n", p);     // 輸出: hyllo world!

    return 0;
}

作為函數參數傳遞

實參傳遞數組時,形參可以是數組或指針。實參傳遞指針時,形參也可以是數組或指針。編譯器會將數組參數退化為指針。

#include <stdio.h>

void printArray(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main(void) {
    int array[] = {1, 2, 3, 4, 5};
    printArray(array, 5); // 傳遞數組
    return 0;
}

需要嚴格區分的情況

使用 sizeof 計算長度

sizeof(array) 返回數組的位元組數。sizeof(pointer) 返回指針的大小(在64位系統中為8位元組,在32位系統中為4位元組)。

#include <stdio.h>

int main(void) {
    char array[] = "hello world!";
    char* p = array;

    printf("sizeof(array) = %lu\n", sizeof(array)); // 輸出: 13
    printf("sizeof(p) = %lu\n", sizeof(p));         // 輸出: 8 (在64位系統中)

    return 0;
}

計算數組長度

當數組作為參數傳遞時,在函數內部不能使用 sizeof 計算數組長度,因為數組會退化為指針。

#include <stdio.h>

void test(int arg[]) {
    printf("sizeof(arg) = %lu\n", sizeof(arg)); // 輸出指針大小,例如在64位系統中為8
}

int main(void) {
    int array[10];
    test(array); // 傳遞數組
    return 0;
}

聲明外部變數

在一個文件中定義指針 p,在另一個文件中不能聲明為數組。

//file1.c:

int* p;

// file2.c

extern int* p; // 正確:聲明為指針
// extern int p[]; // 錯誤:不能聲明為數組

指針常量和常量指針

指針常量

定義

不能改變指向的指針。也就是說,一旦初始化後,就不能再改變指向其它地址,但可以修改指針所指向地址上的內容。

例子

比如指針 p,因為它被 const 修飾,所以 p 不能被修改,它只能指向 str。如果強行對 p 進行 p++ 操作,編譯時就會報錯。我們稱指針 p 為指針常量。儘管 p 本身不能被修改,但可以通過 p 來修改 str 的內容,例如 *p = 'H';

char str[] = "hello world!";
char *const p = str;

常量指針

定義

不能改變所指向的值的指針。也就是說,通過這個指針不能修改它所指向的值,但可以修改指針的指向。

例子

p 是一個指向 char 類型常量的指針。可以修改 p 的指向,例如 p = another_str;,但不能通過 p 修改其指向的內容,例如 *p = 'H'; 是不允許的。

const char *p = "hello world!";
char const *p = "hello world!";

指針數組和數組指針

指針數組

定義

本質是一個數組,數組的每個元素都是指針。

例子1

int *p[4]; 這一聲明中,p 先與中括弧結合,表示 p 是一個有四個元素的數組,每個元素的類型都是 int *(指向整型的指針)。因此,我們稱 p 為指針數組。

int *p[4];

例子2

int a = 1, b = 2, c = 3, d = 4;
int *p[4] = { &a, &b, &c, &d };

for (int i = 0; i < 4; i++) {
    printf("%d ", *p[i]);
}
// 輸出:1 2 3 4

數組指針

定義

本質是一個指針,指向一個數組。

例子1

int (*p)[4]; 這一聲明中,p 先與 * 結合,表示 p 是一個指針,然後與中括弧結合,表示 p 指向一個有四個元素的數組,每個元素的類型都是 int。因此,我們稱 p 為數組指針。

int (*p)[4];

例子2

int arr[4] = {1, 2, 3, 4};
int (*p)[4] = &arr;

for (int i = 0; i < 4; i++) {
    printf("%d ", (*p)[i]);
}
// 輸出:1 2 3 4

函數指針和指針函數

函數指針

定義

本質是一個指針,指向一個函數。每個函數在記憶體中都有一個地址,函數調用就是跳轉到這個地址開始執行,函數指針記錄了這個地址的變數。

例子

p 是一個指針,指向一個函數,該函數有一個 int 類型的參數,返回值是 int可以通過函數名 test(1) 來調用函數,也可以通過指針 p 來調用 p(1)或者(*p)(1)

#include <stdio.h>

int test(int i) {
    return i;
}

int main(void) {
    int res = 0;
    int (*p)(int) = test;
    res = test(1);
    printf("%d\n", res);
    res = p(1);
    printf("%d\n", res);
    res = (*p)(1);
    printf("%d\n", res);
    return 0;
}

應用

往結構體中放入函數

C語言的結構體不支持成員函數,但可以通過函數指針實現類似的功能。結構體中可以定義一個函數指針,用來保存函數地址。函數名可以直接賦值給函數指針。C語言的結構體在使用時必須加上 struct 關鍵字,而 C++ 可以省略。

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

// 定義一個列印函數
void print(int a, int b) {
    printf("%d %d\n", a, b);
}

// 定義一個結構體,其中包含一個函數指針
struct Test {
    void (*p)(int, int);
};

int main(void) {
    // 動態分配結構體記憶體
    struct Test* t = (struct Test *)malloc(sizeof(struct Test));
    if (t == NULL) {
        fprintf(stderr, "Memory allocation failed\n");
        return 1;
    }
    
    // 將函數指針指向列印函數
    t->p = print;
    
    // 調用函數指針
    t->p(3, 4); // 輸出: 3 4
    
    // 釋放動態分配的記憶體
    free(t);
    
    return 0;
}

回調函數

是一種通過函數指針實現的技術,允許在一個函數中調用另一個函數。通常用於事件驅動編程或處理非同步操作。

#include <stdio.h>

// 定義一個回調函數類型
typedef void (*Callback)(int);

// 定義一個回調函數
void myCallback(int value) {
    printf("Callback called with value: %d\n", value);
}

// 定義一個執行操作並調用回調函數的函數
void performOperation(int x, Callback callback) {
    printf("Performing operation with value: %d\n", x);
    // 調用回調函數
    callback(x);
}

int main(void) {
    // 使用回調函數
    performOperation(5, myCallback);
    return 0;
}

動態函數調用

在需要根據某些條件動態選擇和調用不同函數時,函數指針非常有用。例如,在一個計算器程式中可以動態選擇不同的操作函數。

#include <stdio.h>

// 定義操作函數
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return a / b; }

int main(void) {
    // 定義一個函數指針數組
    int (*operations[4])(int, int) = { add, subtract, multiply, divide };
    
    int a = 10, b = 5;
    char op = '+';

    // 根據操作符選擇對應的函數
    int (*operation)(int, int) = NULL;
    switch (op) {
        case '+': operation = operations[0]; break;
        case '-': operation = operations[1]; break;
        case '*': operation = operations[2]; break;
        case '/': operation = operations[3]; break;
    }

    if (operation != NULL) {
        printf("Result: %d\n", operation(a, b));
    } else {
        printf("Invalid operation\n");
    }

    return 0;
}

實現多態行為

通過函數指針數組,可以在C語言中實現類似C++的多態行為。這種技術廣泛應用於設計模式和框架中。

#include <stdio.h>

// 定義一個基類結構體
struct Shape {
    void (*draw)(struct Shape*);
};

// 定義一個派生類結構體
struct Circle {
    struct Shape base; // 基類
    int radius;
};

// 定義一個繪製函數
void drawCircle(struct Shape* shape) {
    struct Circle* circle = (struct Circle*)shape;
    printf("Drawing a circle with radius: %d\n", circle->radius);
}

int main(void) {
    // 創建一個 Circle 對象
    struct Circle c;
    c.base.draw = drawCircle;
    c.radius = 5;

    // 調用繪製函數
    c.base.draw((struct Shape*)&c);

    return 0;
}

實現狀態機

是一種在不同狀態之間轉換的編程模式。可以通過函數指針實現狀態之間的動態切換。

#include <stdio.h>

// 定義狀態函數類型
typedef void (*StateFunction)();

// 定義狀態函數
void stateA() {
    printf("State A\n");
}
void stateB() {
    printf("State B\n");
}
void stateC() {
    printf("State C\n");
}

int main(void) {
    // 定義一個狀態函數指針數組
    StateFunction states[3] = { stateA, stateB, stateC };
    
    int currentState = 0;
    for (int i = 0; i < 5; i++) {
        // 調用當前狀態函數
        states[currentState]();
        // 切換到下一個狀態
        currentState = (currentState + 1) % 3;
    }

    return 0;
}

代碼跳轉到指定位置執行

比如((void(*)())0)();代表讓程式跳轉到地址為0的地方去運行。

解析步驟

  1. (void (*)()):這是一個類型轉換,表示一個指向返回類型為 void、無參數的函數的指針。具體來說,void (*)() 是一種函數指針類型,它指向的函數沒有參數並返回 void
  2. 0:這是一個整數常量 0。在C中,可以將整數常量 0 作為空指針常量。
  3. (void (*)())0:這將整數 0 轉換為類型為 void (*)() 的函數指針。換句話說,這是將 0 解釋為一個指向無參數、返回類型為 void 的函數的指針。
  4. ((void (*)())0):這隻是對前面步驟的類型轉換進行一次包裹,結果仍然是一個類型為 void (*)() 的函數指針,指向 0 地址。
  5. ((void (*)())0)():這表示對 0 地址的函數指針進行調用。具體來說,這是試圖調用位於地址 0 處的函數。

指針函數

定義

本質是一個函數,其返回值是一個指針。

例子

錯誤示例

下麵這個例子是一個典型的錯誤,因為不能返回一個局部變數的地址。函數調用完畢後,局部變數的記憶體會被釋放,即使返回了這個地址也不能使用。

int* test() {
    int array[5] = {0};  // 局部變數
    return array;  // 錯誤:返回局部變數的地址
}

int main(void) {
    int* p = test();  // 不安全
    return 0;
}

返回堆空間地址

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

int* test() {
    int* array = (int*)malloc(sizeof(int) * 5);  // 動態分配堆空間
    if (array != NULL) {
        for (int i = 0; i < 5; ++i) {
            array[i] = i;  // 初始化數組
        }
    }
    return array;  // 返回堆空間地址
}

int main(void) {
    int* p = test();
    if (p != NULL) {
        for (int i = 0; i < 5; ++i) {
            printf("%d ", p[i]);  // 輸出:0 1 2 3 4
        }
        free(p);  // 釋放動態分配的記憶體
    }
    return 0;
}

返回全局變數地址

#include <stdio.h>

int array[5];  // 全局變數

int* test() {
    for (int i = 0; i < 5; ++i) {
        array[i] = i;  // 初始化數組
    }
    return array;  // 返回全局變數地址
}

int main(void) {
    int* p = test();
    for (int i = 0; i < 5; ++i) {
        printf("%d ", p[i]);  // 輸出:0 1 2 3 4
    }
    return 0;
}

返回靜態變數地址

#include <stdio.h>

int* test() {
    static int array[5];  // 靜態變數
    for (int i = 0; i < 5; ++i) {
        array[i] = i;  // 初始化數組
    }
    return array;  // 返回靜態變數地址
}

int main(void) {
    int* p = test();
    for (int i = 0; i < 5; ++i) {
        printf("%d ", p[i]);  // 輸出:0 1 2 3 4
    }
    return 0;
}

指針函數指針

定義

是一個指向返回指針的函數的指針。它不僅是一個指向函數的指針,而且該函數返回的也是一個指針。

例子

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

// 定義一個返回整數指針的函數
int* allocateArray(int size) {
    int* array = (int*)malloc(size * sizeof(int));  // 動態分配記憶體
    if (array != NULL) {
        for (int i = 0; i < size; ++i) {
            array[i] = i;  // 初始化數組
        }
    }
    return array;  // 返回堆空間地址
}

// 定義一個指向返回整數指針的函數的指針類型
typedef int* (*ArrayAllocator)(int);

int main(void) {
    // 定義一個指向返回整數指針的函數的指針
    ArrayAllocator allocator = allocateArray;
    
    int size = 5;
    // 使用指針函數指針調用函數
    int* array = allocator(size);
    if (array != NULL) {
        for (int i = 0; i < size; ++i) {
            printf("%d ", array[i]);  // 輸出:0 1 2 3 4
        }
        printf("\n");
        free(array);  // 釋放動態分配的記憶體
    }
    return 0;
}

懸掛指針和野指針

懸掛指針

定義

懸掛指針是指向已經釋放(通過 free 函數釋放)或者已經超出作用域的記憶體的指針。當我們試圖通過這樣的指針訪問或操作記憶體時,可能會導致未定義行為,因為那塊記憶體可能已經被操作系統重新分配給其它程式使用了。

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

void dangling_pointer_example() {
    int *p = (int *)malloc(sizeof(int));
    *p = 42;
    printf("Value: %d\n", *p); // 輸出42

    free(p); // 釋放記憶體

    // p現在是懸掛指針,訪問*p將導致未定義行為
    // printf("Value: %d\n", *p); // 不安全,可能導致崩潰或未定義行為
}

int main() {
    dangling_pointer_example();
    return 0;
}

解決方法

立即將指針設為NULL:在釋放記憶體後,將指針設置為NULL。

free(p);
p = NULL;

避免返回局部變數的指針:不要返回局部變數的指針,因為它們在函數返回後將超出作用域。

int* incorrect_function() {
    int x = 42;
    return &x; // 返回局部變數的指針,錯誤
}

野指針

定義

野指針是一個未初始化的指針,它的值是未知的,可能指向任意記憶體地址。當我們試圖通過這樣的指針訪問或操作記憶體時,可能會導致未定義行為。

#include <stdio.h>

void wild_pointer_example() {
    int *p; // 未初始化的指針
    // *p = 42; // 不安全,可能導致崩潰或未定義行為
}

int main() {
    wild_pointer_example();
    return 0;
}

解決方法

在聲明指針時初始化:聲明指針時將其初始化為NULL或有效地址。

int *p = NULL;

確保在使用前分配記憶體:在使用指針之前,確保它已經被正確初始化和分配記憶體。

int *p = (int *)malloc(sizeof(int));
if (p != NULL) {
    *p = 42;
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 定義 抽象工廠模式是一種創建型設計模式,它提供一個介面,用於創建一系列相關或依賴的對象,而無需指定它們的具體類。抽象工廠模式將對象的創建過程抽象化,允許子類通過實現具體工廠類來定製對象的創建。 為什麼使用抽象工廠模式 產品族的一致性 抽象工廠模式確保同一產品族中的對象之間的一致性。 部分遵循開閉原則 ...
  • 最近時間稍微空閑,整理下雲屏整機設備的OTA流程及方案。之前開發時有過定義/設計,這裡稍微整理總結下 整機軟體有很多模塊,系統及外設固件、Windows服務、Windows應用,比如系統點屏9969、攝像頭固件、觸摸框固件、顯卡驅動、Windows一些自研服務(用於通信以及系統修複等)、全家桶應用( ...
  • 定義 工廠方法模式是一種創建型設計模式,它定義了一個用於創建對象的介面,但由子類來決定實例化哪一個類。工廠方法使得類的實例化延遲到子類,這樣可以讓客戶端在不需要知道具體類的情況下創建對象。工廠方法模式通過使用繼承和多態性,允許子類來控制對象的創建方式,能夠更好地應對對象創建的複雜性和變化性。 為什麼 ...
  • 本文主要介紹了Python中創建自定義類時如何使用多重繼承、菱形繼承的概念和易錯點,同時講解瞭如何使用PyQtGraph庫對串口接收的數據進行繪圖。 ...
  • 定義 簡單工廠模式(Simple Factory Pattern)是一種創建型設計模式,它定義一個用於創建對象的介面,但由一個單獨的類來實現實際創建的工作。簡單工廠模式通過在一個類中集中管理對象的創建過程,可以減少客戶端與具體類之間的耦合,使得代碼結構更加清晰和易於維護。通過專門定義一個類來負責創建 ...
  • 需求場景 按著慣例,還是以一個應用場景作為代理模式的切入點。現在有一個訂單系統,要求是:一旦訂單被創建,只有訂單的創建人才可以修改訂單中的數據,其他人則不能修改。 基本實現思路 按著最直白的思路,就是查詢資料庫中訂單的創建人和當前Session中的登錄賬號ID是否一致。 class Order { ...
  • 本文主要介紹了在使用Python面向對象編程時,異常的使用場景、定義和特點,錯誤的類型和特點,並舉出實際例子來輔助講解。 ...
  • notepad++下載安裝 找到瀏覽器輸入:notepad或者 https://notepad-plus-plus.org/downloads/ 官網下載即可使用 如果官網崩了,可以在微信公眾號:A軟體安裝管家,找到安裝notepad++,下載安裝即可。 typora下載安裝 找到瀏覽器輸入:typ ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...