文章篇幅較長,如有需要請先收藏???
文章目錄
- 一、指標是什么
- 二、指標常見的錯誤
- 1.未初始化就使用
- 2.指標越界訪問
- 3.有趣的代碼+習題
- 三.動態記憶體管理
- 1.malloc,free,calloc,realloc的基本使用
- 2.malloc的使用
- 2.calloc的使用
- 3.realloc
- 3.常見的動態記憶體泄露
- 1.對NULL指標的解參考
- 2.對動態開辟的記憶體進行越界訪問
- 3.對非動態開辟的記憶體使用free
- 4.對同一塊動態記憶體多次釋放
- 5.動態開辟記憶體未釋放
- 檔案指標
- 總結
一、指標是什么
指標是編程語言中的一個物件,利用地址,它的值將指向電腦存盤器中另一個地方的值,并且可以通過地址能找到所需的變數單元,可以說,地址指向該變數單元,通過指標可以找到以它為地址的記憶體單元
二、指標常見的錯誤
1.未初始化就使用
代碼如下(示例):
#include<stdio.h>
int main()
{
int* p;//區域變數未初始化是隨機值
*p = 3;//這里訪問了未初始化的空間報錯
return 0;
}

2.指標越界訪問
代碼如下(示例):
int main()
{
int a[10] = { 0 };
int* p = a;
//這個地方 a表示陣列名,陣列名sizeof(a)和單獨&a的時候是指向整個陣列的地址,
//其他情況都是首元素地址
for (int i = 0; i <= 10; i++)
{
*p = 1;
p++;
}
return 0;
}
這里注意了,指標越界了其實不會造成影響,但是這里修改了指標指向內容的值會報錯
3.有趣的代碼+習題
(內含對堆疊區的一些理解)
猜猜代碼是在哪出錯
1.
#include<stdio.h>
int* test()
{
int a = 10;
return &a;
}
int main()
{
int* p = test();
*p = 20;
return 0;
}

所以這里當然在main的時候p就已經是一塊隨機的空間,可能已經被分配給其他使用,這里在win測驗的,這個記憶體圖實際上沒多大意義,vs2019,x64中驗證的,vs編譯器本身為了安全,會對地址行程重新分配,但是要記住以下幾點:1.堆疊區記憶體的使用習慣:先使用高地址空間,在使用低地址空間;2.陣列隨著下標的增長地址是由低到高變化的(Linux標準的,也是編譯器沒有經過任何加工的布局,下道題我們用x86驗證,就可以模擬上述規則了),
指標的加減與指標的型別有關,加減1次走過指標的型別大小
void*是不能解參考和進行加減的
2.剛提了一嘴這個型別的題目,那接下來再看一道
來猜猜看結果會如何
#include <stdio.h>
int main()
{
int i = 0;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (i = 0; i <= 12; i++)
{
arr[i] = 0;
//printf("%d\n", arr[i]);
}
return 0;
}
答案:死回圈,(vs2019 x86)
3.回歸主題,這里回到指標
自行做一下,對一下答案
牢記:sizeof里面得到的結果是型別屬性,不是值屬性,是不會去計算的,
sizeof(陣列名) - 陣列名表示整個陣列的 - 計算的是整個陣列的大小
&陣列名 – 陣列名表示整個陣列,取出的是整個陣列的地址
此外的所有陣列名都是陣列的首元素大小
知道這些后,可以嘗試寫一下題,答案在后面的注釋,超詳細,這里地址以32位的計算啦
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));//16
printf("%d\n", sizeof(a + 0));//這里的a就是首元素地址,是地址就是4位元組(32位)
printf("%d\n", sizeof(*a));//這里的a也是首元素地址,1的地址被解參考找到1,int的大小就是4
printf("%d\n", sizeof(a + 1));//4*這里的a也是首元素地址,這里是第二個元素的地址,地址的大小是多少就不用說了吧
printf("%d\n", sizeof(a[1]));//4 --2的大小,那肯定是4個位元組
printf("%d\n", sizeof(&a));//4 --這里就是整個元素的地址,滿足&陣列名
printf("%d\n", sizeof(*&a));//16 -- 這里就是整個陣列解參考,拿到整個陣列的元素
printf("%d\n", sizeof(&a + 1));//4 --看下面那張圖 &a + 1陣列后面的空間的地址
printf("%d\n", sizeof(&a[0]));//4 -- a[0]是第一個元素,&a[0]第一個元素的地址
printf("%d\n", sizeof(&a[0] + 1));//4 --a[0]+1是第二個元素,&a[0] + 1第二個元素的地址

//strlen 的作用是從給的地址往后找\0,計算之前的字符個數
//字符陣列
char arr[] = { 'a','b','c','d','e','f' };//6個字符
printf("%d\n", sizeof(arr));//6 --整個元素的地址單獨放在sizeof內部,滿足要求,計算整個陣列的總大小
printf("%d\n", sizeof(arr + 0));//4--arr為首元素地址,即第一個元素的地址,字符的地址也是4位元組
printf("%d\n", sizeof(*arr));//1-- 第一個元素'a'放在sizeof內部
printf("%d\n", sizeof(arr[1]));//1 --第二個元素 'b'
printf("%d\n", sizeof(&arr));//4--整個陣列的地址
printf("%d\n", sizeof(&arr + 1));//4--下一個整個陣列的地址 (類似上面那張圖的情況)
printf("%d\n", sizeof(&arr[0] + 1));//4--&arr[0]的指標型別是int*,加1跳過一個整形,‘b’的地址
printf("%d\n", strlen(arr));//隨機值 --無\0
printf("%d\n", strlen(arr + 0));//同理
//printf("%d\n", strlen(*arr));//'a' -- 出錯 這里相當于strlen一個整數,錯誤的strlen的引數是(const char * str),這里把一個整數當成地址,代碼出錯
//printf("%d\n", strlen(arr[1]));//同理
printf("%d\n", strlen(&arr));//隨機值 ,整個陣列的地址,值與第一個元素的地址相同,但意義不同,圖如下
printf("%d\n", strlen(&arr + 1));//&arr的型別為char(*)[6],隨機值-6 ,整個陣列的地址,圖如下
printf("%d\n", strlen(&arr[0] + 1));//&arr[0]的型別為char*,'b'的地址找到\0就停 ,值為:前面的隨機值-1


char arr[] = "abcdef";,//這里是用常量區的"abcdef"初始化了arr,在堆疊區上開辟了一塊空間
printf("%d\n", sizeof(arr));//7 --算的是整個陣列的大小加'\0'
printf("%d\n", sizeof(arr + 0));//4 --算的是首元素地址的大小
printf("%d\n", sizeof(*arr));//1 --arr首元素地址,這里計算'a'的大小
printf("%d\n", sizeof(arr[1]));//1--‘b'的大小
printf("%d\n", sizeof(&arr));//4--整個陣列的地址的大小
printf("%d\n", sizeof(&arr + 1));//4--f后面的地址,地址的大小
printf("%d\n", sizeof(&arr[0] + 1));//地址的大小 4位元組
printf("%d\n", strlen(arr));//6
printf("%d\n", strlen(arr + 0));//6
//printf("%d\n", strlen(*arr));//err
// printf("%d\n", strlen(arr[1]));//err
printf("%d\n", strlen(&arr));//6 指標有型別差異,結果和 strlen(arr)一樣,上面解釋過相似的題目
printf("%d\n", strlen(&arr + 1));//隨機值
printf("%d\n", strlen(&arr[0] + 1));//5
char* p = "abcdef";//"abcdef"是放在常量區當中的,p是在堆疊區的,p的值指向a的首元素地址
printf("%d\n", sizeof(p));//* 地址4位元組,這里的p是存盤這個常量字串的首元素地址
printf("%d\n", sizeof(p + 1));//p是char*型別的,加減跳過一個字符,'b'地址4位元組 'b'的地址
printf("%d\n", sizeof(*p));//1 p指向a,解參考拿到a
printf("%d\n", sizeof(p[0]));//1 'a'的大小
printf("%d\n", sizeof(&p));//4 * 首元素a的地址的地址
printf("%d\n", sizeof(&p + 1));//4-- 地址的的大小,這里指向的是指標的指標后面--畫圖
printf("%d\n", sizeof(&p[0] + 1));//4--地址的的大小
printf("%d\n", strlen(p));//6
printf("%d\n", strlen(p + 1));//5 ,p是char*,往后走一個位元組為b的地址
///printf("%d\n", strlen(*p));//err
//printf("%d\n", strlen(p[0]));//err
printf("%d\n", strlen(&p));//隨機值,指標的地址
printf("%d\n", strlen(&p + 1));//隨機值,指標的地址跳過char(*)[6],如下圖
printf("%d\n", strlen(&p[0] + 1));//隨機值,指標的地址,**下圖第二個**

p的地址加一跳過一個p的大小

看完這張圖,讓我們再來做做下面 的題
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));//48
printf("%d\n", sizeof(a[0][0]));//4
printf("%d\n", sizeof(a[0]));//a[0]為第一行的陣列名單獨放在sizeof內部 * 16
printf("%d\n", sizeof(a[0] + 1));//a[0]沒有單獨放在sizeof內部,所以是第一行第一個元素的地址,這答案是第一行第二個元素的地址 4
printf("%d\n", sizeof(*(a[0] + 1)));//第一行第二個元素的大小 *4
printf("%d\n", sizeof(a + 1)); // a這里沒有放在sizeof內部,這里表示二維陣列的首元素地址(二維陣列的首元素是第一行陣列的地址) 這里是第二行整體的地址 *4
printf("%d\n", sizeof(*(a + 1)));//這里相當于a[1]單獨放在sizeof內部,就是計算第二行所有元素的大小16
printf("%d\n", sizeof(&a[0] + 1));//第二行的全部元素,&a[0] + 1就是第二行的地址 4
printf("%d\n", sizeof(*(&a[0] + 1)));//第二行的全部元素,&a[0] + 1就是第二行的地址,解參考拿到整個元素 *16
printf("%d\n", sizeof(*a));//16 第一行的全部元素
printf("%d\n", sizeof(a[3]));//sizeof內部不會真的去計算,a[3]是陣列名 16
這邊解釋一下sizeof,代碼在下面
short s = 5;
int a = 4;
printf("%d\n", sizeof(s = a + 6));//這里的運算式就是不會真的去計算a+6的值,所以這里的值是2
printf("%d\n", s);//5 不計算的話這里自然不會改變
三.動態記憶體管理
1.malloc,free,calloc,realloc的基本使用
2.malloc的使用

翻譯出來就是在記憶體當中申請一塊連續可用的空間,回傳這塊空間的指標
*如果開辟成功,則回傳一個指向開辟好空間的指標,
*如果開辟失敗,則回傳一個NULL指標,因此malloc的回傳值一定要做檢查,
回傳值的型別是 void ,所以malloc函式并不知道開辟空間的型別,具體在使用的時候使用者自己來決定,
如果引數 size 為0,malloc的行為是標準是未定義的,取決于編譯器
👀這里我們寫一個代碼看看基本使用
int main()
{
int* ptr = (int*)malloc(sizeof(int) * 4);
if (ptr == NULL)
{
return 1;
}
else
{
for (int i = 0; i < 4; i++)
{
ptr[i] = i;
printf("%d ", ptr[i]);
}
}
//使用完一定要記得釋放否則就造成記憶體泄漏
free(ptr);
//ptr在free之后仍然指向開辟的空間,但開辟的空間已經還回去了,所以我們要手動置成NULL;
ptr = NULL;
return 0;
}
在這里說明一下在NULL在c語言是定義成地址為0,在c++則是一個整數0

2.calloc的使用
calloc

這里可以看出引數size_t num為我們要的元素數量,size_t size為每個元素的大小,我們也寫一段簡單的代碼
int main()
{
int* ptr = (int*)malloc(sizeof(int) * 10);
if (ptr == NULL)
{
return 1;
}
else
{
for (int i = 0; i < 4; i++)
{
ptr[i] = i;
printf("%d ", ptr[i]);
}
}
//使用完一定要記得釋放否則就造成記憶體泄漏
free(ptr);
//ptr在free之后仍然指向開辟的空間,但開辟的空間已經還回去了,所以我們要手動置成NULL;
ptr=NULL;
return 0;
}
calloc開辟出來的空間也是在堆區上,同然使用完之后要對記憶體進行釋放,同理也要判斷是否能開出了來,并置成NULL
👀這里給大家寫一個開不出來的情況

這里我在開2g記憶體的時候ptr回傳NULL,如果這里我們沒有對回傳值做判斷,使用的話就會造成錯誤,當然,想要開出2g或4g空間,只需要把x86改成64位就可以開辟出來了,
3.realloc
realloc函式的出現讓動態記憶體管理更加靈活,
有時會我們發現過去申請的空間太小了,有時候我們又會覺得申請的空間過大了,那為了合理的時候記憶體,
我們一定會對記憶體的大小做靈活的調整,那 realloc 函式就可以做到對動態開辟記憶體大小的調整

其中引數ptr是要調整的記憶體地址,size為調整之后的大小,注意,realloc有兩種情況
第一種:原空間后面有足夠大的空間
第二種:原空間后面沒有足夠大的空間
第一種:
第二種:
👀👀👀當然,如果realloc的記憶體過大開不了也是會回傳NULL,所以我們依舊要對回傳值進行處理,以下寫一個常用的用法
#include<stdlib.h>
#include<math.h>
int main()
{
int* ptr = (int*)malloc(sizeof(int)* 4);
if (ptr == NULL)
{
return 1;
}
else
{
for (int i = 0; i < 4; i++)
{
ptr[i] = i;
printf("%d ", ptr[i]);
}
}
//空間不夠,擴容
int* tmp = realloc(ptr, sizeof(int) * 10);
if (tmp == NULL)
{
printf("開辟空間失敗\n");
return 1;
}
else
{
ptr = tmp;
}
//使用完一定要記得釋放否則就造成記憶體泄漏
free(ptr);
//ptr在free之后仍然指向開辟的空間,但開辟的空間已經還回去了,所以我們要手動置成NULL;
ptr=NULL;
return 0;
}
當然,還有一個小tip,就是把ptr寫成NULL,可以當成malloc使用

3.常見的動態記憶體泄露
1.對NULL指標的解參考

2.對動態開辟的記憶體進行越界訪問
#include< LIMITS.H >
int main()
{
int* ptr = (int*)malloc(sizeof(int) * 4);
if (ptr == NULL)
{
return 1;
}
else
{
for (int i = 0; i <= 10; i++)//超出開的記憶體的
{
ptr[i] = i;
printf("%d ", ptr[i]);
}
}
return 0;
}
3.對非動態開辟的記憶體使用free
#include< LIMITS.H >
int main()
{
int a[10] = { 0 };
free(a);//這里就是對堆疊上空間free了,是錯誤的
return 0;
}
4.對同一塊動態記憶體多次釋放
#include<stdlib.h>
#include< LIMITS.H >
int main()
{
int* p = (int*)malloc(sizeof(int));
free(p);
free(p);//這里就是這個錯誤
return 0;
}
當然我們只要養成free完手動置成NULL就沒問題
對NULL進行釋放是沒有問題的
5.動態開辟記憶體未釋放
這個就是我們常見的記憶體泄露了,一定記得釋放!!!
#include<stdlib.h>
#include< LIMITS.H >
void test()
{
int* p = (int*)malloc(100);
if (NULL != p)
{
*p = 20;
}
}
int main()
{
test();
}
尤其是這種在區域作用域開辟的空間,如果沒有釋放甚至連指標都已經找不到了,所以在開辟記憶體的時候我們要小心再小心
檔案指標
每個被使用的檔案在記憶體當中都開辟了一個相應的檔案資訊區,用來存放檔案的相關資訊,以下是VS2008編譯環境提供的stdio.h頭檔案中有以下的檔案型別申明
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
FILE* pf;//檔案指標變數
不同編譯器都大同小異,知道了這個我們要操作檔案只需要FILE* pf(檔案指標)來操作就可以找到和他關聯的檔案啦;
當然,檔案的讀寫之前應該先做什么么,首先肯定要打開檔案,使用完之后也要關閉檔案!! 因為打開檔案的數量是有限的,打開了 不關閉就會造成資源的浪費,接下來寫一段簡單的代碼說明一下使用
#include<stdlib.h>
#include< LIMITS.H >
int main()
{
FILE* pf = fopen("test.dat", "w");
if (pf == NULL)
{
printf("打開失敗\n");
return 1;
}
else
{
fputs("hello world", pf);//寫入一段字串進入流中
fclose(pf);//關閉檔案
}
return 0;
}
注意:檔案若不存在,以“w”(寫的)方式則會創建該檔案,在進行寫入,若檔案存在,則會把檔案格式化,再重新寫入內容,當然如果是一次打開關閉中重復使用fputs是可以向后寫的,如果想在原有內容追加,請使用“a”

常見的讀寫函式
fseek定義檔案指標的位置和偏移量,使用
其中的origin
#include<stdlib.h>
#include< LIMITS.H >
int main()
{
FILE* pFile;
pFile = fopen("example.txt", "wb");
fputs("abcdef.", pFile);
fseek(pFile, 0, SEEK_SET);
fputs(" z" ,pFile);
fclose(pFile);
return 0;
//檔案結果: zcdef.
最后介紹一下sscanf和sprintf

sscanf就是從s這個字符陣列當中讀取資訊給后面的format
#include <stdio.h>
typedef struct Peo
{
char name[20];
int age;
}Peo;
int main()
{
Peo peo = { 0 };
char str[20]="zhangsan 12";
sscanf(str, "%s %*s %d", peo.name, &(peo.age));
printf("%s",str);
return 0;
//答案:zhangsan 12
}
sprintf則相反
#include <stdio.h>
typedef struct Peo
{
char name[20];
int age;
}Peo;
int main()
{
Peo peo = {"zhangsan",12 };
char str[20] = {0};
sprintf(str, "%s,%d", peo.name, peo.age);
printf("%s",str);
return 0;
}
//結果zhangsan,12
總結
c語言階段的指標使用基本就此告一段落,有任何問題可以在評論區或私信,歡迎大家的討論和批評,喜歡的不妨一鍵三連????
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/290481.html
標籤:其他
上一篇:C++string類實作
