C 標準I/O庫粗略實現

来源:http://www.cnblogs.com/zhangyachen/archive/2017/12/12/8030217.html
-Advertisement-
Play Games

本文同時發表在 "https://github.com/zhangyachen/zhangyachen.github.io/issues/123" 寫一下fopen/getc/putc等C庫的粗略實現,參考了K&R,但是有幾點根據自己理解的小改動,下麵再具體說一下^_^ 寫這篇文章主要是幫助自己理解 ...


本文同時發表在 https://github.com/zhangyachen/zhangyachen.github.io/issues/123

寫一下fopen/getc/putc等C庫的粗略實現,參考了K&R,但是有幾點根據自己理解的小改動,下麵再具體說一下^_^

寫這篇文章主要是幫助自己理解下標準I/O庫大體是怎麼工作的。

fopen與open之間的關係

操作系統提供的介面即為系統調用。而C語言為了讓用戶更加方便的編程,自己封裝了一些函數,組成了C庫。而且不同的操作系統對同一個功能提供的系統調用可能不同,在不同的操作系統上C庫對用戶屏蔽了這些不同,所謂一次編譯處處運行。這裡open為系統調用,fopen為C庫提供的調用。

image

C庫對的讀寫操作封裝了一個緩衝區。試想假如用戶頻繁的對文件讀寫少量字元,會頻繁的進行系統調用(read函數),而系統調用比較耗時。C庫自己封裝了一個緩衝區,每次讀取特定數據量到緩衝區,讀取時優先從緩衝區讀取,當緩衝區內容被讀光後才進行系統調用將緩衝區再次填滿。

image

FILE結構體

上面我們看到一個結構體,裡面有5個參數,分別記錄了:緩衝區剩餘的字元數cnt、下一個字元的位置ptr、緩衝區的位置base、文件訪問模式flag、文件描述符fd。
其中文件描述符就是系統調用open返回的文件描述符fd,是int類型。ptr與base上面圖中已經展示了。cnt是緩衝區剩餘字元數,當cnt為0時,會系統調用read來填滿緩衝區。flag為文件訪問模式,記錄了文件打開方式、是否到達文件結尾等。

結構體的具體定義如下,對應調用fopen返回的文件指針FILE *fp = fopen(xxx,r)

typedef struct _iobuf{
    int cnt;                //緩衝區剩餘位元組數
    char *base;     //緩衝區地址
    char *ptr;      //緩衝區下一個字元地址
    int fd;         //文件描述符
    int flag;             //訪問模式
} FILE;     //別名,與標準庫一致

結構體中有flag欄位,flag欄位可以是以下幾種的並集:

enum _flags {
    _READ = 1,      
    _WRITE = 2, 
    _UNBUF = 4,  //不進行緩衝
    _EOF = 8,       
    _ERR = 16
};

我們註意到其中有一個欄位,標識不進行緩衝,說明此種情況下每一次讀取和輸出都調用系統函數。一個例子就是標準錯誤流stderr : 當stderr連接的是終端設備時,寫入一個字元就立即在終端設備顯示。
而stdin和stdout都是帶緩衝的,明確的說是行緩衝。本文不考慮行緩衝,預設都是全緩衝,即緩衝區滿了才刷新緩衝區。(詳細可以參考《UNIX環境高級編程》標準I/O庫章節)。

現在我們可以初始化stdin、stdout與stderr:

FILE _iob[OPEN_MAX] = {
    {0,NULL,NULL,_READ,0},
    {0,NULL,NULL,_WRITE,1},
    {0,NULL,NULL,_WRITE|_UNBUF,2}
};

_ferror/_feof/_fileno

//判斷文件流中是否有錯誤發生
int _ferror(FILE *f){
    return f-> flag & _ERR;
}
//判斷文件流是否到達文件尾
int _feof(FILE *f){
    return f-> flag & _EOF;
}
//返迴文件句柄,即open函數的返回值
int _fileno(FILE *f){
    return f->fd;
}

_fopen

FILE *_fopen(char *file,char *mode){

    int fd;
    FILE *fp;   

    if(*mode != 'r' && *mode != 'w' && *mode != 'a') {
        return NULL;
    }   

    for(fp = _iob; fp < _iob + OPEN_MAX; fp++) {   //尋找一個空閑位置
        if (fp->flag == 0){ 
            break;
        }
    }
    if(fp >= _iob + OPEN_MAX){
        return NULL;
    }

    if(*mode == 'w'){
        fd = creat(file,PERMS);
    }else if(*mode == 'r'){
        fd = open(file,O_RDONLY,0);
    }else{      //a模式
        if((fd = open(file,O_WRONLY,0)) == -1){
            fd = creat(file,PERMS);
        }
        lseek(fd,0L,2);     //文件指針指向末尾
    }
    if(fd == -1){
        return NULL;
    }

    fp->fd = fd;
    fp->cnt = 0;        //fopen不分配緩存空間
    fp->base = NULL;
    fp->ptr = NULL;
    fp->flag = *mode == 'r' ? _READ : _WRITE;

    return fp;
}

fopen的處理過程:

  • 判斷打開模式的合法性。
  • 在_iob中尋找一個空閑位置,找不到的話說明程式打開的文件數已經到達的最大值,不能再打開新的文件。
  • 如果是w模式,創建一個新文件。如果是r模式,以只讀方式打開文件。如果是a模式,首先打開文件,如果打開失敗則創建文件,否則通過系統調用lseek將文件指針置到末尾。
  • 對FILE結構體進行初始化,註意fopen不會分配緩衝區。

_getc
getc的作用是從文件中返回下一個字元,參數是文件指針,即FILE:

int _getc(FILE *f){
    return --f->cnt >= 0 ? *f->ptr++ : _fillbuf(f);
}

對照上面的圖示:當緩衝區中還有剩餘字元待讀取時,讀取該字元並返回,並將緩衝區指針向後移動一個char單位,否則就調用_fillbuf函數填滿緩衝區,_fillbuf的返回值就是待讀取的字元。

這裡有一個問題:當讀取到最後一個字元時,cnt為0,但是ptr已經越界了,如下圖:
image

這種情況雖然是非法的,但是C語言中保證:數組末尾之後的第一個元素(即&arr[n],或者arr + n)的指針算術運算可以正確執行。下麵的例子也是上述的一種應用場景:

int a[10] = {1,2,3,4,5,6,7,8,9,10};

for(int *x = a;x < a + 10; x++){
    printf("%d\n",*x);
}

當for迴圈到最後一步時,x也指向了a[10],雖然是非法的,但是C語言保證可以正確執行,只要不出現如下情況就ok:

int a[10] = {1,2,3,4,5,6,7,8,9,10};

int *x;
for(x = a;x < a + 10; x++){
    printf("%d\n",*x);
}

*x = 11;     //越界進行值訪問

_fillbuf
我們看下_getc中的_fillbuf的實現,_fillbuf是當緩衝區沒有可以讀取的字元時,通過系統調用read讀取一定位元組的數據填滿緩衝區,供之後使用:

int _fillbuf(FILE *f){

    int bufsize;

        if((f->flag & (_READ | _EOF | _ERR)) != _READ){     //判斷文件是否可讀
        return EOF;
    }

    bufsize = f->flag & _UNBUF ? 1 : BUFSIZ;

    if(f->base == NULL){            //沒有分配過緩衝區
        if((f->base = (char *)malloc(bufsize)) == NULL){
            return EOF;
        }
    }

    f->ptr = f->base;
        int n = read(f->fd,f->ptr,BUFSIZ);      //系統調用read
        if(n == 0){         //到達文件結尾
        f->base = NULL;
        f->cnt = 0;
        f-> flag |= _EOF;
        return EOF;
        }else if(n == -1){      //出錯
                f->cnt= 0;
        f->flag |= _ERR;
                return EOF;
    }else{
        f->cnt = --n;
        return *f->ptr++;   
    }
}

_fillbuf的處理過程:

  • 判斷文件是否可讀
  • 判斷調用read函數時應該讀取的位元組數,當文件設置了無緩衝時,讀取1個位元組,否則讀取BUFSIZ個位元組,BUFSIZ在
  • 判斷是否分配過緩衝區(fopen不會分配緩衝區,會再第一次調用getc時分配)。
  • 調用系統函數read。
  • 判斷read返回值,分為到達文件結尾、出錯和正常讀取三種情況。
  • 正常情況下返回緩衝區第一個字元給getc函數,並將cnt減1。

這裡註意,到達文件結尾和出錯都是返回EOF,區別是前者會將flag的_EOF位置1,後者會將flag的_ERR位置1,上游可以通過feofferror函數進行判斷(這兩個函數在上面已經實現過了)。

The character read is returned as an int value.
If the End-of-File is reached or a reading error happens, the function returns EOF and the corresponding error or eof indicator is set. You can use either ferror or feof to determine whether an error happened or the End-Of-File was reached.

_putc

int _putc(int x,FILE *f){
    return --f->cnt >= 0 ? *f->ptr++ = x : _flushbuf(x,f);
}

與_getc的實現相似,將寫入的字元放到ptr指向的位置,並將ptr向後移動一位。當緩衝區滿時,調用_flushbuf將緩衝區內容刷新到文件中。

_flushbuf

int _flushbuf(int x,FILE *f){

    if((f->flag & (_WRITE | _EOF | _ERR)) != _WRITE){     //判斷文件是否可寫
        return EOF;
    }

    int n;
    int bufsize = f->flag & _UNBUF ? 1 : BUFSIZ;
    if(f->base != NULL){
        n = write(f->fd,f->base,f->ptr - f->base);      //判斷需要寫入多少位元組
        if(n != f->ptr - f->base){
            f->flag |= _ERR;
            return EOF;
        }
    }else{
        if((f->base = (char *)malloc(bufsize)) == NULL){
            f->flag |= _ERR;
            return EOF;
        }
    }

    if(x != EOF){
        f->cnt = bufsize - 1;
        f->ptr = f->base;
        *f->ptr++ = x;
    }else{          //當寫入EOF時,代表強制刷新緩衝區內容到文件中
        f->cnt = bufsize;
        f->ptr = f->base;
    }
    return x;
}

_flushbuf的處理過程:

  • 判斷文件是否可寫。
  • 當已分配過緩衝區時,將緩衝區的內容通過系統調用write寫入文件中。
  • 當沒有分配過緩衝區時,分配緩衝區。
  • 判斷當寫入的字元為EOF時,說明調用此函數的目的為強制刷新緩衝區,不寫入字元。將cnt賦值為BUFSIZ,ptr賦值為緩衝區首地址base。
  • 當寫入字元不為EOF時,說明緩衝區已滿,需要將緩衝區刷新到文件中。cnt為BUFSIZE - 1,將寫入的字元x放到到緩衝區的第一格,然後將ptr向後移動一個char單位。

註意,調用write函數時,寫入的位元組數不能寫死為1或者BUFSIZ:

n = write(f->fd,f->base,f->ptr - f->base);      //判斷需要寫入多少位元組

image

如上圖,我們需要寫入base至ptr之間的數據,而不是BUFSIZ,因為我們可能會強制刷新緩衝區而不是等到緩衝區滿了才刷新緩衝區。

還有一點:當我們想要強制刷新緩衝區時,第一個參數x該傳入什麼呢?K&R傳遞的是字元0,但是我認為這樣會污染緩衝區,所以我的實現是傳入一個特殊字元EOF,根據EOF來做不同的處理:

if(x != EOF){
    f->cnt = bufsize - 1;
    f->ptr = f->base;
    *f->ptr++ = x;
}else{          //當寫入EOF時,代表強制刷新緩衝區內容到文件中
    f->cnt = bufsize;
    f->ptr = f->base;
}

當緩衝區滿時,刷新緩衝區後緩衝區的表現:
image
當強制刷新緩衝區時,緩衝區的表現:
image
但是按照K&R的方式來強制刷新緩衝區時,緩衝區的表現:
image

這樣會污染緩衝區,所以我的實現是傳入EOF來強制刷新緩衝區。

_fflush
_fflush(FILE *f)的作用是把緩衝區內容寫入文件。當參數為空時,會刷新所有文件:

int _fflush(FILE *f){

        int res = 0;
    if(f == NULL){
        for(int i = 0; i < OPEN_MAX; i++){          //當參數為NULL時,刷新所有的文件流
                       if((f->flag & _WRITE) && (_fflush(&_iob[i]) == -1)){  //有一個出錯即返回-1
                           res = EOF;
                       }
        }
        }else{
            if(f->flag & _WRITE){
                    _flushbuf(EOF,f);
            }else{
                res = EOF;
            }
        }

    if(f->flag & _ERR){     //出錯
                res = EOF;
    }
        return res;
}

_fflush的處理過程:

  • 判斷參數是否為空,如果為空的話,遍歷_iob數組,將所有文件流都強制刷新。
  • 如果參數不為空,判斷文件是否可寫,再調用_flushbuf進行刷新,註意此處傳遞給_flushbuf的參數是EOF。還有一點就是判斷_flushbuf是否出錯不是判斷返回值是否為-1,因為參數為EOF時的返回值也為-1,所以此處用flag & _ERR判斷是否出錯。

註意,這裡我們只針對可寫的文件流進行操作,忽略了只讀的文件流:

If the stream was open for reading, the behavior depends on the specific implementation. In some implementations this causes the input buffer to be cleared.

針對只讀的文件流,不同系統處理的方式不一樣,有的系統會清空緩衝區。

_fclose

int _fclose(FILE *f){
    int ret;
    if((ret = _fflush(f)) != EOF){
        free(f->base);
        f->base = NULL;
        f->ptr = NULL;
        f->fd = 0;
        f->flag = 0;        
                f->cnt=0;
    }

    return 0;
}

fclose調用fflush函數,保證在文件關閉前將緩衝區中的內容刷到文件中,並且釋放掉緩衝區的記憶體空間。

_fseek
關於fseek的介紹請看fseek

int _fseek(FILE *f,long offset,int origin){

    int rc;

    if(f->flag & _READ) {
        if(origin == 1) {
            offset -= f->cnt;
        }
        rc = lseek(f->fd,offset,origin);
                f->cnt = 0;             //將緩衝區剩餘字元數清0
    }else if(f->flag & _WRITE) {
                rc = _fflush(f);       //強制刷新緩衝區
        if(rc != EOF) {
            rc = lseek(f->fd,offset,origin);
        }
    }

    return rc == -1 ? EOF : 0;
}

當文件流為可讀時,見下圖:
image

由於有緩衝區的存在,我們直覺上的文件指針位置和真實的文件指針位置是不同的,差了cnt個單位長度。所以當我們設置移動offset個長度時,真實的文件指針需要移動offset-cnt個單位長度(offset為正數或者負數)。
之後我們需要將cnt置為0,以便下次讀取時將緩衝區的數據更新。
當origin為0或者2時,直接調動lseek即可。

而當文件流為可寫時,見下圖:
image

真實的文件指針位置與我們直覺上的文件指針位置差了ptr - base個單位長度,即我們新寫入緩衝區的內容長度,所以我們直接調用_fflush即可。(K&R中直接調用的write,但是我覺得這樣沒有重置ptr指針的位置和cnt,這樣的話base與ptr之間的內容會被刷入到文件中兩次)。

當文件是以a模式打開時,fseek無效:

a+     Open for reading and appending (writing at end of file).  The file is created if it does not exist.  The initial file 
       position for  reading is at the beginning of the file, but output is always appended to the end of the file.

_getchar

int _getchar(){
  return _getc(stdin);
}

我們可以發現,_getchar調用的就是_getc,只不過_getc可以傳入任意的文件指針,而對_getchar來說,_getc傳入的是stdin,也就是{0,NULL,NULL,_READ,0}

image

  • 當調用getchar時,首先去stdin結構體中的緩存取數據,如果緩存為空,會在_fillbuf中的int n = read(f->fd,f->ptr,BUFSIZ); //系統調用read處阻塞住,等待用戶輸入字元。
  • 當標準輸入(stdin)連接的是終端時,終端I/O會採用規範模式輸入處理:對於終端輸入以行為單位進行處理,對於每個讀請求,終端設備輸入隊列會返回一行(用戶輸入的字元會緩存在終端輸入隊列中,直到用戶輸入一個行定界符,輸入隊列中的數據會返回給read函數)。
  • 這一行數據會緩存在標準I/O緩衝區中,下次調用getchar時會返回緩衝區第一個字元。當緩衝區數據被讀光時,重覆上述過程。

_putchar

int _putchar(int x){
    return _putc(x,stdout);
}

我們可以發現,_putchar調用的就是_putc,只不過_putc可以傳入任意的文件指針,而對_putchar來說,_putc傳入的是stdout,也就是{0,NULL,NULL,_WRITE,1}

image

  • 調用putchar時,數據會緩存在stdout中的緩衝中。
  • 當stdout的緩衝被裝滿時,會調用write將數據寫入到stdout中,stdout將數據寫入到終端設備輸出隊列中,輸出隊列將數據寫入到終端。

完整代碼

#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

#define EOF -1
#define BUFSIZ 1024
#define OPEN_MAX 20      //打開的最大文件數
#define PERMS 0666

typedef struct _iobuf{
    int cnt;        //緩衝區剩餘位元組數
    char *base;     //緩衝區地址
    char *ptr;      //緩衝區下一個字元地址
    int flag;       //訪問模式
        int fd;         //文件描述符
} FILE;     //別名,與標準庫一致

extern FILE _iob[OPEN_MAX];

//八進位
enum _flags {
    _READ = 01,     
    _WRITE = 02,    
    _UNBUF = 04,     //不進行緩衝
    _EOF = 010,     
    _ERR = 020  
};

FILE _iob[OPEN_MAX] = {
        {0,NULL,NULL,_READ,STDIN_FILENO},
        {0,NULL,NULL,_WRITE,STDOUT_FILENO},
        {0,NULL,NULL,_WRITE|_UNBUF,STDERR_FILENO}
};

#define stdin (&_iob[0])
#define stdout (&_iob[1])
#define stderr (&_iob[2])

int _ferror(FILE *f){
    return f-> flag & _ERR;
}

int _feof(FILE *f){
    return f-> flag & _EOF;
}

int _fileno(FILE *f){
    return f->fd;
}

//返回第一個字元
int _fillbuf(FILE *f){

    int bufsize;

    if((f->flag & (_READ | _EOF | _ERR)) != _READ){     //判斷文件是否可讀
        return EOF;
    }

    bufsize = f->flag & _UNBUF ? 1 : BUFSIZ;

    if(f->base == NULL){            //沒有分配過緩衝區
        if((f->base = (char *)malloc(bufsize)) == NULL){
            return EOF;
        }
    }

    f->ptr = f->base;
    int n = read(f->fd,f->ptr,BUFSIZ);      //系統調用read
    if(n == 0){         //到達文件結尾
        f->base = NULL;
        f->cnt = 0;
        f-> flag |= _EOF;
        return EOF;
    }else if(n == -1){      //出錯
        f->cnt= 0;
        f->flag |= _ERR;
        return EOF;
    }else{
        f->cnt = --n;
        return *f->ptr++;   
    }
}

int _flushbuf(int x,FILE *f){

    if((f->flag & (_WRITE | _EOF | _ERR)) != _WRITE){
        return EOF;
    }

    int n;
    int bufsize = f->flag & _UNBUF ? 1 : BUFSIZ;
    if(f->base != NULL){
        n = write(f->fd,f->base,f->ptr - f->base);      //判斷需要寫入多少位元組
        if(n != f->ptr - f->base){
            f->flag |= _ERR;
            return EOF;
        }
    }else{
        if((f->base = (char *)malloc(bufsize)) == NULL){
            f->flag |= _ERR;
            return EOF;
        }
    }

    if(x != EOF){
        f->cnt = bufsize - 1;
        f->ptr = f->base;
        *f->ptr++ = x;
    }else{          //當寫入EOF時,代表強制刷新緩衝區內容到文件中
        f->cnt = bufsize;
        f->ptr = f->base;
    }
    return x;
}

/**
 * @brief _fflush
 * @param f
 * @return
 */
int _fflush(FILE *f){

        int res = 0;
    if(f == NULL){
                for(int i = 0; i < OPEN_MAX; i++){          //當參數為NULL時,刷新所有的文件流
                       if((f->flag & _WRITE) && (_fflush(&_iob[i]) == -1)){  //有一個出錯即返回-1
                           res = EOF;
                       }
        }
        }else{
            if(f->flag & _WRITE){
                    _flushbuf(EOF,f);
            }else{
                res = EOF;
            }
        }

        if(f->flag & _ERR){     //出錯
                res = EOF;
    }
        return res;
}

int _fclose(FILE *f){
    int ret;
    if((ret = _fflush(f)) != EOF){
        free(f->base);
        f->base = NULL;
        f->ptr = NULL;
        f->fd = 0;
        f->flag = 0;     //@TODO
    }

    return 0;
}

int _fseek(FILE *f,long offset,int origin){

    int rc;

    if(f->flag & _READ) {
        if(origin == 1) {
            offset -= f->cnt;
        }
        rc = lseek(f->fd,offset,origin);
                f->cnt = 0;             //將緩衝區剩餘字元數清0
    }else if(f->flag & _WRITE) {
                rc = _fflush(f);       //強制刷新緩衝區
        if(rc != EOF) {
            rc = lseek(f->fd,offset,origin);
        }
    }

    return rc == -1 ? EOF : 0;
}


int _getc(FILE *f){
    return --f->cnt >= 0 ? *f->ptr++ : _fillbuf(f);
}

int _putc(int x,FILE *f){
    return --f->cnt >= 0 ? *f->ptr++ = x : _flushbuf(x,f);
}

int _getchar(){
  return _getc(stdin);
}

int _putchar(int x){
    return _putc(x,stdout);
}

FILE *_fopen(char *file,char *mode){

    int fd;
    FILE *fp;   

    if(*mode != 'r' && *mode != 'w' && *mode != 'a') {
        return NULL;
    }   

        for(fp = _iob; fp < _iob + OPEN_MAX; fp++) {   //尋找一個空閑位置
                if (fp->flag == 0){
            break;
        }
    }
    if(fp >= _iob + OPEN_MAX){
        return NULL;
    }

    if(*mode == 'w'){
        fd = creat(file,PERMS);
    }else if(*mode == 'r'){
        fd = open(file,O_RDONLY,0);
    }else{      //a模式
        if((fd = open(file,O_WRONLY,0)) == -1){
            fd = creat(file,PERMS);
        }
        lseek(fd,0L,2);     //文件指針指向末尾
    }
    if(fd == -1){
        return NULL;
    }

    fp->fd = fd;
    fp->cnt = 0;        //fopen不分配緩存空間
    fp->base = NULL;
    fp->ptr = NULL;
    fp->flag = *mode == 'r' ? _READ : _WRITE;

    return fp;
}

int main(int argc,char *argv[]){


    FILE *f = _fopen("zyc.txt","a");    
    /*char c;
      for(int i = 0; i < 10; i++){
      c = _getc(f);
      }*/

    /*for(int i = 0; i < 9; i++){
      _putc('6',f);
      }

      _fseek(f,-5,1);

      for(int i = 0; i < 9; i++){
      _putc('8',f);
      }

      _fclose(f);*/

        int c;
        while((c = _getchar()) != '\n'){
            _putchar(c);            
        }

        _fclose(stdout);

    return 0;
}

上面提到的部分函數在Answer to Exercise 8-3, page 179中有更詳細的實現。

參考資料:


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

-Advertisement-
Play Games
更多相關文章
  • 各位朋友,本次LZ分享的是狀態模式,在這之前,懇請LZ解釋一下,由於最近公司事情多,比較忙,所以導致更新速度稍微慢了些(哦,往後LZ會越來越忙=。=)。 狀態模式,又稱狀態對象模式(Pattern of Objects for States),狀態模式是對象的行為模式。 狀態模式允許一個對象在其內部 ...
  • 定義: 為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用 角色: 1, 抽象角色:聲明真實對象和代理對象的共同介面。 2, 代理角色:代理對象角色內部含有對真實對象的引用,從而可以操作真實對象,同 ...
  • 為留言板-v1添加登錄功能,使用監聽器檢測會話的變化、維護活躍會話列表 ...
  • XHProf是facebook 開發的一個測試php性能的擴展,本文記錄了在PHP應用中使用XHProf對PHP進行性能優化,查找性能瓶頸的方法。 一、安裝Xhprof擴展 二、修改php.ini 配置中xhprof.output_dir指定了生成的profile文件存儲的位置,我們將其指定為/tm ...
  • Lambda函數又稱匿名函數,匿名函數就是沒有名字的函數,函數沒有名字也行?當然可以啦。有些函數如果只是臨時一用,而且它的業務邏輯也很簡單時,就沒必要非給它取個名字不可。 先來看個簡單lambda函數 x和y是函數的兩個參數,冒號後面的表達式是函數的返回值,你能一眼看出這個函數就是是在求兩個變數的和 ...
  • 一、棧 1.消失的方式不同:方法變數隨著棧方法的釋放而釋放 2.存儲的位置不同,預設複製的處理機制不同:不會給方法的屬性附初值,可以理解為類中的方法中的屬性為局部變數,無法給局部變數附初值,類的狀態由類的成員變數的值來體現,所以稱類是有狀態的對象,而方法中的變數不能預設附初值,則屬於無狀態,而且存儲 ...
  • name = 'mafen mamengmeng' # 首字元大寫 print(name.capitalize()) # 統計指定字元數 print(name.count('m')) # 字元長度 print(len(name)) # 轉換位元組數組,b''bytes 類型 print(type(na... ...
  • 本文同時發表在 "https://github.com/zhangyachen/zhangyachen.github.io/issues/125" 假設我們有如下結構體: 我們需要對結構體內的欄位進行驗證合法性: Id的值在某一個範圍內。 Name的長度在某一個範圍內。 Email格式正確。 我們可 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...