c語言學習8

来源:https://www.cnblogs.com/c-learnmore/archive/2023/07/20/17569665.html
-Advertisement-
Play Games

一、Django入門 Django 是一個功能強大且高效的Web應用程式框架,它採用了Python語言,幫助開發人員快速構建可擴展和可維護的Web應用程式。本文將深入探討Django框架的核心概念和優勢。 1. Django簡介 Django 是一個開源的Web應用程式框架,由 Adrian Hol ...


指針複習:
什麼是指針:
數據類型 定義指針變數 整型 記憶體編號 訪問對應記憶體
為什麼使用指針:
1、函數之間共用變數
輸入、輸出
2、提高傳參效率
指針變數4\8位元組
3、使用堆記憶體時
如何使用指針:
定義:類型* 變數名_p;
變數名以p結尾與普通變數以示區分
一個只能定義一個指針變數
初始化為NULL
類型決定了能夠連續訪問的位元組數
賦值:變數名_p = 有效地址
p = &變數名
p = malloc(位元組數)
解引用:
變數名_p;
通過指針變數中存儲的整數編號去訪問記憶體
該過程很可能產生段錯誤,是由於賦值時的記憶體地址非法導致的
指針需要註意的問題:
空指針:值為NULL的指針叫做空指針
對空指針解引用一定段錯誤,用於初始化以及函數返回值的錯誤標誌
如何避免空指針帶來的段錯誤:
來歷不明的指針使用前先判斷
函數的返回值\函數的參數
野指針:指針的值不確定
對野指針解引用的後果:
1、一切正常
2、段錯誤
3、臟數據
野指針的危害比空指針要大,因為無法分辨是否是野指針
如何不產生野指針:
1、指針一定要初始化
2、不要返回局部變數的地址
3、堆記憶體釋放後,指向堆記憶體的指針及時置空
指針運算:
指針 + n 前進n個元素寬度
指針 - n 後退n個元素寬度
指針 - 指針 計算出兩個指針之間間隔了多少個元素,必須類型相同才能相減
指針與const:
const int* p\ int const p 保護指針指向的記憶體不能修改
int
const p 保護指針的指向不能修改

    當函數的參數是指針,但是又不想被函數共用修改時,考慮使用const保護

指針數組與數組指針:
    指針數組:成員是指針變數的數組
        int* arr[10];
    數組指針:專門指向數組的指針
        int (*p)[10];
        指向長度為10,成員為int類型的數組的指針

指針與數組名區別:
    數組名就是數組的首地址,它與數組首地址是映射關係,相當於一個特殊的指針,但是它是個常量,不能修改
    數組作為函數的參數傳遞時,蛻變成指針,因此長度丟失
    指針是變數,它與存儲的地址之間是指向關係,是可以更改的
    當一個指針指向數組首地址時,指針可以當做數組名使用,數組名也可以當做指針使用
        int* p;
        int arr[10]
        p[i] == *(p+i)
        arr[i] == *(arr+i)
        sizeof(arr) 計算數組的總位元組
        sizeof(&arr) sizeof(&arr[0])
        sizeof(p)   4/8

二級指針:
    指向指針的指針,存儲的是指針變數的地址
    定義:int** 變數名_pp;
    賦值:變數名_pp = &指針變數;
    解引用:*變數名_pp == 指針變數;
        **指針變數 == *指針變數 == 數據
    註意:
        當函數間需要共用普通變數時,傳遞一級指針
        當函數間需要共用指針變數時,傳遞二級指針(也就是說當函數內部想要修改指針的值或者指針的所指向的內容,都需要用到二級指針)

一、函數指針
函數名就是一個地址(整數),代表了該函數在代碼段中的位置

函數指針就是專門指向某種函數的指針,它裡面存儲的是該函數在代碼段中的位置(函數名)
例子:
int (*funcp)(const char*, ...) = scanf;
    funcp是指向返回值為int,參數為const char*和...這樣函數的指針
funcp("%d",&num);

typedef 返回值類型 (*FP)(參數類型1,參數類型2,...);
FP 相當於函數指針類型 可以用於定義函數指針變數
FP funcp;
funcp(實參1,實參2)

回調模式的函數:
void qsort(void *base, size_t nmemb, size_t size,
        int (*compar)(const void *, const void *));

二、萬能指針:void*
在C語言中,任意類型的指針可以自動轉換為void,void類型的指針也可以自動轉換為任意類型

三、堆記憶體
什麼是堆記憶體:
是進程的一個記憶體段(text、data、bss、stack、heap)
由程式員手動管理
特點是足夠大,缺點是使用麻煩

為什麼要使用堆記憶體:
1、隨著程式的複雜數據量變多
2、其它記憶體段的申請和釋放不受控制,堆記憶體的申請釋放受控制

如何使用堆記憶體:
註意:C語言中沒有任何控制堆記憶體的語句,只能通過C標準庫提供的函數進行使用
#include <stdlib.h>
void *malloc(size_t size);
功能:從堆記憶體中申請size個位元組的記憶體,申請成功會得到連續的記憶體
返回值:成功時返回申請到的連續記憶體的首地址,失敗返回NULL
註意:malloc不會專門對申請到的記憶體清理為0

void free(void *ptr);
功能:釋放一段堆記憶體,只是釋放使用權,不會專門清理記憶體數據
ptr:要釋放的堆記憶體的首地址
註意:free不能連續釋放同一個地址和非法地址
    但是可以free(NULL)

void *calloc(size_t nmemb, size_t size);
功能:從堆記憶體中申請nmemb個大小為size位元組的一塊連續記憶體
返回值:成功時返回申請到的連續記憶體的首地址,失敗返回NULL
註意:通過calloc申請到的記憶體會全部清理為0
    依然是一塊連續的堆記憶體

void *realloc(void *ptr, size_t size);
功能:改變已有堆記憶體塊的大小
ptr:待調整的記憶體塊首地址
size:是調整後的記憶體塊的位元組數
返回值:是調整後的記憶體塊首地址,有可能會改變,因此必須重新接收新地址
    如果不能在原記憶體塊的基礎上調整:
        1、申請一塊新的符合要求的記憶體塊
        2、把原記憶體中的內容拷貝到新記憶體中
        3、把原記憶體釋放並返回新記憶體的首地址

malloc的記憶體管理機制:
1、當首次向malloc申請記憶體時,malloc會向操作系統申請堆記憶體,操作系統會直接分配33頁(1頁=4096位元組)記憶體給malloc管理,但這樣不意味著可以越界訪問,因為malloc可能會把記憶體分配給"其他人"使用,這樣就產生了臟數據
2、每個記憶體塊之間一定會有一些空隙(4~12位元組),一部分空隙是為了記憶體對齊,其中一定有4個位元組用於記錄malloc的維護信息,如果維護信息被破壞會影響下一次的free的調用

使用堆記憶體時需要註意的問題:
記憶體泄漏:
記憶體無法使用,也無法被釋放,當再次需要時只能重新申請,然後又重覆以上過程,日積月累後會導致系統中可用的記憶體越來越少
註意:程式一旦結束,屬於它的所有資源都會被操作系統回收
如何儘量避免記憶體泄漏:
誰申請的誰釋放,誰知道該釋放誰釋放
如何判斷定位記憶體泄漏:
1、查看記憶體的使用情況
windows 任務管理器 Linux 命令ps -aux
2、代碼分析工具mtrace,檢查malloc、free的使用情況
3、封裝新的malloc和free函數,記錄調用信息到日誌中
void
zzxx_malloc(size_t size)
{
void
p = malloc(size);
// 記錄時間、行數、所屬函數等信息到日誌中
return p;
}
void zzxx_free(void* ptr)
{
free(ptr);
// 記錄到日誌中
}

記憶體碎片:
    已經被釋放但是又無法繼續使用的記憶體叫做記憶體碎片,是由於申請和釋放的時間不協調導致的,記憶體碎片無法避免只能儘量減少
    *如何減少記憶體碎片:
        1、儘量使用棧記憶體,棧記憶體不會產生記憶體碎片
        2、不要頻繁地申請和釋放記憶體
        3、儘量申請大塊記憶體自己管理

記憶體清理函數:
#include <strings.h>
void bzero(void *s, size_t n);
功能:把一塊記憶體全部清理為0
s:記憶體塊的首地址
n:要清理的記憶體位元組數

#include <string.h>
void *memset(void *s, int c, size_t n);
功能:把記憶體塊按位元組設置為c
s:記憶體塊的首地址
c:想要設置的ASCII碼值
n:要設置的記憶體位元組數
返回值:返回設置後的記憶體首地址 s 
    鏈式調用:一個函數的返回值可以作為另一個函數的參數     
    free(memset(p,0,100));

堆記憶體中定義二維數組:
指針數組:
類型名* arr[n];
for(int i=0; i<n; i++)
{
arr[i] = malloc(sizeof(類型)*m);
}
申請到 n行m列 的二維數組,每行記憶體可能不連續
註意:每一行的m值可不同,可以得到不規則的二維數組
缺點:容易產生記憶體碎片
優點:可以不規則、容易申請成功

數組指針:
    類型名 (*arrp)[m] = malloc(sizeof(類型)*m*n);
    申請到 n行m列 的二維數組,並且全部記憶體都是連續的
    優點:不容易產生記憶體碎片
    缺點:相對而已對記憶體要求更高

註意:無論哪種方式申請,最後都是當做二維數組訪問arr[i][j]

堆記憶體管理:
C語言沒有管理堆記憶體的語句,只能使用標準庫的函數
#include <stdlib.h>
void* malloc(size_t size);
註意:void* 在C++編譯器中是不能自動轉換成其它類型的指針,如果想讓代碼也在C++編譯器中相容,需要強制類型轉換
int* p = (int*)malloc(4);
註意:C編譯器不允許在main函數之前調用函數,但C++允許

    void free(void* ptr);
    註意:不要重覆釋放,不要釋放非法地址,但可以釋放NULL

一、字元串
字元:人能看得懂的符號或圖案,在記憶體中以整數形式存儲,根據ASCII碼表中的對應關係顯示出相應的符號或圖案
'\0' 0 空字元
'0' 48
'A' 65
'a' 97

串:是一種數據結構,存儲類型相同的若幹個數據
    對於串型結構的處理是批量性的,會從頭開始直到遇到結束標誌停止

字元串:
    由字元組成的串型結構,結束標誌是 '\0'

二、字元串的存在形式
字元數組:
char str[10] = {'a','b','c',...};
由char組成的數組,註意要為'\0'預留位置,初始化麻煩
使用的是棧記憶體,數據可以修改

字元串字面值:
    "由雙引號包含的若幹個字元"
    末尾會隱藏一個'\0',定義也方便
    字元串字面值就是以地址形式存在的,是常量,數據存儲在代碼段中,不能修改,否則段錯誤
    註意:相同內容的多份字元串字面值,在代碼段中只會存在一份
    註意:sizeof("xxxx") 計算出 字元個數+1

常用方式:
    字元數組[] = "字元串字面值";
    會自動為'\0'預留位置
    註意:賦值完成後,該字元串在記憶體中有兩份,一份在代碼段,另一份在棧記憶體(可修改)

三、字元串的輸入和輸出
scanf %s 地址
缺點:不能輸入空格

char *gets(char *s);
功能:輸入字元串到s中 能夠輸入空格
返回值:s 鏈式調用
缺點:有警告,輸入的長度不受限制,有風險

char *fgets(char *s, int size, FILE *stream);
功能:輸入長度最多為 size-1 的字元串,會自動為'\0'預留位置
    超出部分不接收,不足時最後的'\n'也會一起接收

輸出:
printf %s 地址

int puts(const char *s);
功能:輸出一個字元串,並且會自動在末尾列印一個'\n'
功能:成功輸出的字元個數

四、輸出緩衝區
緩衝區機制可以提高數據的讀寫速度,還可以讓低速的設備與高速的CPU之間系統工作
程式要顯示的數據並不會立即顯示到屏幕上,而是先存儲到輸出緩衝區中,當滿足一定條件時才會從輸出緩衝區顯示到屏幕上
1、遇到'\n'
2、遇到輸入語句
3、當緩衝區滿了4k
4、程式正常結束時
5、fflush(stdout); 手動刷新輸出緩衝區

五、輸入緩衝區
程式中輸入的數據並不會立即從鍵盤接收到變數中,而是當按下回車後先存儲到輸入緩衝區中,然後再從緩衝區中讀取到變數記憶體中

情況1:需要輸入的是整型\浮點型時,而緩衝區中的數據是字元型或符號時,此時讀取會失敗,並且該數據會繼續殘留在輸入緩衝區中,會繼續影響剩下的輸入
    解決:根據scanf的返回值判斷輸入是否有問題,如果讀取失敗,則先清理輸入緩衝區後重新輸入,直到讀取成功為止,可以設置一個清楚函數,使用int n;while((c=getchar())!='\n'&&c!=EOF));來實現對輸入緩衝區的清空。

情況2:通過fgets可以指定讀取size-1個字元,但是如果輸入超過size-1那麼字元會殘留在輸入緩衝區中,繼續影響接下來的輸入
    解決方法1:
    int len = 0;
    while(str1[len]) len++; //len是'\0'的下標
    if('\n' != str1[len-1])// '\0'前面不是'\n'則清理
    {    
        scanf("%*[^\n]");
    //從緩衝區中讀取任意類型數據並丟棄,直到遇到'\n'停止
        scanf("%*c");
    //從緩衝區中讀取任意字元類型數據並丟棄
    } 
    解決方法2:
    void clear_input_buffer() {
        int ch;
        while ((ch = getchar()) != '\n' && ch != EOF);

}
方法3:
stdin->_IO_read_ptr = stdin->_IO_read_end;
// 把輸入緩衝區的位置指針從當前位置,移動到末尾,相當於清理輸入緩衝區
註意:只能在Linux系統下使用

情況3:當先輸入整型或浮點型,再輸入字元型時,輸入完整型或浮點型後按下的回車或空格,會殘留在輸入緩衝區,剛好被後面的字元型接收,影響輸入
    解決:在%c或者gets()前面加空格
        scanf(" %c");

六、字元串相關函數
#include <string.h>
size_t strlen(const char *s);
功能:計算字元串的長度,不包括'\0'

char *strcpy(char *dest, const char *src);
功能:把src拷貝給dest,相當於給dest賦值 =
返回值:dest的首地址,鏈式調用

char *strcat(char *dest, const char *src);
功能:把src追加到dest的末尾 相當於+=
返回值:dest的首地址,鏈式調用

int strcmp(const char *s1, const char *s2);
功能:比較兩個字元串,根據字典序,誰出現早誰小,一旦比較出結果就立即返回
返回值:
    s1 > s2 正數
    s1 == s2 0
    s1 < s2 負數      
   
char *strncpy(char *dest, const char *src, size_t n);   //它用於將一個字元串(src)的前 n 個字元複製到另一個字元串(dest)中
char *strncat(char *dest, const char *src, size_t n);   //用於將一個字元串(src)的前 n 個字元連接(追加)到另一個字元串(dest)的末尾
int strncmp(const char *s1, const char *s2, size_t n);  //用於比較兩個字元串(s1 和 s2)的前 n 個字元。

int atoi(const char *nptr);
功能:把字元串轉換成int類型
double atof(const char *nptr);
功能:把字元串轉換成double類型

char *strstr(const char *haystack,const char *needle);
功能:在haystack中查找是否存在子串needle
返回值:needle在haystack中第一次出現的位置,如果找不到返回NULL

int sprintf(char *str, const char *format, ...);
功能:把各種類型的數據轉換成字元串並輸入到str中

int sscanf(const char *str, const char *format, ...);   //從一個字元串中,提取各種類型的數據
功能:從字元串中解析出各種類型的數據,並存儲到對應的變數中

void *memcpy(void *dest, const void *src, size_t n);    //請註意,如果源和目標記憶體區域重疊,memcpy 的行為是未定義的。在這種情況下,應使用 memmove 函數,因為它可以處理重疊的記憶體區域
功能:把src記憶體的數據拷貝n個位元組到dest中

練習:自己重新實現strlen\strcpy\strcat\strcmp四個函數
size_t str_len(const char *s);
char *str_cpy(char *dest, const char *src);

strlen的實現:  //計算字元串長度
    size_t my_strlen( const char *str){
        size_t length=0;
        while (str[length]!= '\0'){
            length++;
        }
        return length; 
    }
strcpy的實現:  //誰將*src的內容添加到*dest當中
    char *my_strcpy(char *dest const char* src){
        char *dest_start=dest;
        while('\0'!=*src){
            *dest=*src;
            dest++;
            src++;
        } 
        *dest='\0'; //添加空字元終止符
        return dest_start;
    }
strcat實現:    //*dest後面添加*src
    char *my_strcat(char *dest const char *src ){
        char *dest_start=dest;
        while(*dest!='\0'){
            dest++;
        }
        while (*src=!'\0')
        {
            *dest=*src;
            dest++;
            src++;
        }
        *dest='\0';
        return *dest_start;
        
    }
strcmp的實現://比較兩個字元串。如果兩個字元串相等,返回 0;如果 str1 在字典順序上小於 str2,返回負數;如果 str1 在字典順序上大於 str2,返回正數。
    int my_strcmp(const char* dest const char* src){
            while(*dest!='\0' && *src!='\0'){
                if(*dest==*src){
                    retun *dest-*src;
                }
                dest++;
                src++;
            }
            return *dest-*src;

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

-Advertisement-
Play Games
更多相關文章
  • # 1.打開文件 位於自動導入的模塊IO中,無需手動導入。 ``` f = open('D:\M\test.txt') ``` 若文件不存在,則報錯 ``` Traceback (most recent call last): File "d:\M\github\Python\Demo\t14.py ...
  • ```cpp #include #include #include #pragma comment( lib, "Winmm" ) static int counter = 0; static int64_t ticks_per_second; void __stdcall on_timer(HWN ...
  • 本系列博客將利用C++實現一系列數值演算法。數值演算法離不開矩陣,但是C++並未自帶矩陣這一對象,直接使用數組又會帶來諸多不便,因此我們需要做一些預備工作————編寫一個矩陣類,實現矩陣的基本功能。一般來說,讀者可以直接使用Eigen庫進行矩陣計算,從頭開始造輪子僅僅是為了滿足筆者個人的需要。 #一、成 ...
  • `PB`應用程式就是由許多共同協作完成特定任務的視窗組成的集合。 視窗在應用程式的開發工作中占有很大的比重,是非常重要的一個 `PB `對象 ### 一、視窗類型 ![視窗類型](https://img2023.cnblogs.com/blog/2381533/202307/2381533-2023 ...
  • 存在層次上 synchronized: Java的關鍵字,在jvm層面上 Lock: 是一個介面 鎖的釋放 synchronized: 1、以獲取鎖的線程執行完同步代碼,釋放鎖 2、線程執行發生異常,jvm會讓線程釋放鎖 Lock: 在finally中必須釋放鎖,不然容易造成線程死鎖 鎖的獲取 sy ...
  • # Spring 的依賴註入 @[toc] ## 每博一文案 ```tex "在千千萬萬個選擇里",我永遠選擇去做哪些我認為值得的事,我可能幹得很漂亮,也可能搞得一塌糊塗。 但沒關係,重要的是我為之努力過。”我們很難做好每件事,讓人生不留下任何遺憾,儘力而為就好“享受 生活的過程,接受結果。”人生是 ...
  • * Cobar(已經被淘汰沒使用了) * TDDL * 淘寶根據自己的業務特點開發了 TDDL (Taobao Distributed Data Layer) * 基於JDBC規範,沒有server,以client-jar的形式存在,引入項目即可使用 * 開源功能比較少,阿裡內部使用為主 * Myc ...
  • 在本頁中,我們將瞭解 Java 對象和類。在面向對象的編程技術中,我們使用對象和類來設計程式。 Java中的對象既是物理實體又是邏輯實體,而Java中的類只是邏輯實體。 # 什麼是Java中的對象 具有狀態和行為的實體稱為對象,例如椅子、自行車、記號筆、筆、桌子、汽車等。它可以是物理的或邏輯的(有形 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...