一、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;