定義指針
/*指針的概念:1.為了方便訪問內(nèi)存中的內(nèi)容,給每一個內(nèi)存單元,進行編號,那么我們稱這個編號為地址,也就是指針。2.指針也是一種數(shù)據(jù)類型,指針變量有自己的內(nèi)存,里面存儲的是地址,也就是那些編號。四要素1.指針本身的類型 例如:float* int* ...2.指針指向的類型 例如:float int ...3.指針本身的內(nèi)存4.指針指向的內(nèi)存運算符*:1.定義指針時,通過 * 符號,來表示定義的是一個指針,并且是指針自身的類型的組成部分2.其他時候,表示解析引用(取內(nèi)容:通過內(nèi)存編號,讀取內(nèi)存中的內(nèi)容)&:取(首)地址符,作用:取出(內(nèi)存的)首地址
*/# include <stdio.h>int main()
{// 定義指針(指針:pointer)float* p_name; // 指針本身的類型:float* 指針指向的類型:floatint * p1;int *p2; // * 符號偏不偏移不影響其功能return 0;
}
指針初始化與賦值
/*知識儲備:// 初識化:定義的同時,給值int a = 0;// 賦值:先定義,再給值int b;b = 0;
*/# include <stdio.h>int main()
{ // 初始化int num = 6;int val = 8;// 初始化int* p1 = # // 對于指針變量p1,如果進行給值,必須給地址(內(nèi)存編號)// 賦值int* p2;p2 = &val;// 自行體會int* p3 = p1;// 直接存入地址(不推薦使用,因為你不知道自己隨便寫的地址里面是什么!!)int* p4 = (int*)123456; // 將 整型123456 強轉(zhuǎn)為 int*類型 的“地址”int* p5 = (int*)0XAB25; // 計算機中的內(nèi)存地址通常用16進制數(shù)表示// 直接使用地址:置空(即:"使用0地址,NULL:0X0")int* p6 = NULL; // 等價于 int* p6 = (int*)0X0;// 目的:為了給暫無指向的指針,提供指向,保證安全,將內(nèi)存中的0地址特殊化// 數(shù)組名就是數(shù)組的首地址int arr[3] = { 1, 2, 3 };// 數(shù)組類型:int [3]// 元素類型:int// arr 類型:int*int* p7 = arr;return 0;
}
探究內(nèi)存
/*1.變量在內(nèi)存中所占的字節(jié)數(shù)所有的指針變量,不論類型,在內(nèi)存中所占的字節(jié)數(shù)都是一樣的,都是4個字節(jié)(或者8個字節(jié))(8個字節(jié)是因為時代的發(fā)展,部分好的計算機性能得到提升,一般都是4個字節(jié))2.指針本身的內(nèi)存,以及指針指向的內(nèi)存指針本身的內(nèi)存:4個字節(jié)(指針變量只需要存儲,所指向的變量的首地址)指針指向的內(nèi)存:看你所指向的類型,視情況而定
*/# include <stdio.h>int main()
{ double num = 12.0;double* p1 = #printf("%f \n", num);printf("%f \n", *p1); // 利用指針,取得 num 的值return 0;
}
指針的偏移
內(nèi)存區(qū)域劃分-基礎(chǔ)
/*
內(nèi)存區(qū)域的劃分
四個:常量區(qū),棧區(qū),堆區(qū),靜態(tài)全局區(qū)
五個:常量區(qū),棧區(qū),堆區(qū),靜態(tài)全局區(qū),代碼區(qū)1.代碼區(qū):存代碼
2.常量區(qū):存常量
3.靜態(tài)全局區(qū):靜態(tài)(static)變量,全局變量
4.棧區(qū):普通局部變量
5.堆區(qū):由程序員手動申請,手動釋放
*/# include <stdio.h>int a; // 普通“全局”變量(初識值默認為零)
// 作用域:當(dāng)前項目
// 生命周期:程序開始到結(jié)束static int b; // 靜態(tài)“全局”變量(初識值默認為零)
// 作用域:當(dāng)前文件
// 生命周期:程序開始到結(jié)束int main()
{ int c; // 普通局部變量(無初始值)// 作用域:當(dāng)前語塊// 生命周期:當(dāng)前語塊static int d; // “靜態(tài)”局部變量(初識值默認為零)// 作用域:當(dāng)前語塊// 生命周期:程序開始到結(jié)束return 0;
}
# include <stdio.h>void func()
{static int num; // 只會定義一次printf("%d \n", num);num++;printf("%d \n", num);}int main()
{ func();func();func();return 0;
} /*運行結(jié)果:011223請按任意鍵繼續(xù). . .
*/
空類型指針
/*void* 指針1.不能自增自減2.不能偏移3.不能讀取內(nèi)容但是!可以接收任何類型的指針而不需要強轉(zhuǎn)類型可以利用這個特點,將 void* 指針當(dāng)作通用的存放地址的“容器”e.g.int a = 6,b = 8.8;int* p1 = &a;double* p2 = &b;void* p0 = NULL; // 當(dāng)作存放“內(nèi)存地址”的容器使用p0 = p1;p0 = p2;...*/# include <stdio.h>int main()
{ void* p0 = NULL;return 0;
}
簡單開辟內(nèi)存
/*簡單開辟內(nèi)存1.申請---有兩個函數(shù)能夠?qū)崿F(xiàn)申請內(nèi)存的功能:A. malloc(參數(shù):需要字節(jié)的總數(shù));B. calloc(參數(shù):每個需要的字節(jié)數(shù),個數(shù));返回值都是 void* 類型的指針2.使用3.釋放free(參數(shù):首地址)如果不釋放的話,會導(dǎo)致“內(nèi)存泄露”4.置空如果不置空的話,會出現(xiàn)“野指針”
*/# include <stdio.h>int main()
{ /* malloc */double* p = (double*)malloc(sizeof(double)); // 申請一個double類型大小的內(nèi)存(8字節(jié))*p = 3.14; // 使用printf("%lf \n", *p);free(p); // 通過 p 里面存儲的首地址,找到相對應(yīng)的內(nèi)存,從這里開始釋放,一直釋放到,申請內(nèi)存的時候,做了標(biāo)記的地方p = NULL; // 通過置空,讓指針不再指向已經(jīng)被釋放掉的內(nèi)存/* calloc */float* p1 = (float*)calloc(sizeof(float),1);*p1 = 3.14f;printf("%f \n", *p1);free(p1);p1 = NULL;printf("進階運用 \n");// 進階運用p = (double*)malloc(sizeof(double)*10); // 申請10個 double 類型大小的連續(xù)的內(nèi)存(補充:因為上面將p定為 double* 而且置空過了,所以可再度利用)for (int i = 0; i < 10; i++ ){ *(p + i) = 10 + i; // 給值printf("%lf \n", *(p + i)); // 展示值}free(p);p = NULL;/*對于上面 for 循環(huán)部分的補充:p:里面存的是:申請的內(nèi)存的首地址在一次申請中,申請的內(nèi)存是連續(xù)的*(p + i) <===> p[i] // 注意!它不是數(shù)組!*/return 0;
}
# include <stdio.h>int main()
{ // 指針布局int row = 3;int** pp = (int**)calloc(sizeof(int*), row);int len = 4;for (size_t i = 0; i < row; i++) // size_t是什么?點我跳轉(zhuǎn)學(xué)習(xí){pp[i] = (int*)calloc(sizeof(int), len);}// 內(nèi)容展示for (size_t i = 0; i < row; i++){for (size_t j = 0; j < len; j++){ pp[i][j] = i * 10 + j; // 給值printf("%-5d", pp[i][j]); // 展示值,注意!這里不是二維數(shù)組!(看不懂請回顧上頁內(nèi)容)}printf("\n");}// 釋放內(nèi)存for (size_t i = 0; i < row; i++){free(pp[i]);pp[i] = NULL;}free(pp);pp = NULL;return 0;
}
自動擴容
# include <stdio.h>int main()
{ int len = 5; // 默認長度int* p = (int*)calloc(sizeof(int), len);int num = 1;for (size_t i = 0; num != 0; i++) // 用戶不輸入0結(jié)束,就一直獲取數(shù)據(jù)并復(fù)制到開辟的內(nèi)存中{scanf("%d", &num);p[i] = num; // 數(shù)據(jù)復(fù)制到開辟的內(nèi)存中}for (size_t i = 0; p[i] != 0; i++){printf("%-5d", p[i]); // 展示數(shù)據(jù)}free(p);p = NULL;return 0;
}
/*擴容的本質(zhì)是:將小內(nèi)存中的所有內(nèi)容拷貝到大內(nèi)存中,然后,再繼續(xù)對大內(nèi)存進行別的操作
*/# include <stdio.h>int main()
{ // 長度int len = 5;// 首次申請內(nèi)存int* p = (int*)calloc(sizeof(int), len);int* temp = p; // 成為p的分身,以防萬一// 重復(fù)輸入數(shù)據(jù)(并復(fù)制到內(nèi)存中)int num = 1;int i = 0;while (scanf("%d", &num), num != 0){if (i < len) // 沒滿的情況下{temp[i++] = num; // 存完一次,記錄一下}else // 滿了的情況下{len += 5;p = (int*)calloc(sizeof(int), len); // 重新申請更大的內(nèi)存for (int j = 0; j < i; j++){p[j] = temp[j];}free(temp);temp = NULL;temp = p; // 繼續(xù)成為當(dāng)前p的分身temp[i++] = num;}}// 輸出數(shù)據(jù)printf("--------------------\n");for (int j = 0; j != i; j++){printf("%d \n", temp[j]);}free(p);p = NULL;temp = NULL;return 0;
}
內(nèi)存區(qū)域劃分-進階
/*內(nèi)存區(qū)域的劃分1.代碼區(qū)存儲代碼2.常量區(qū)存儲常量3.全局區(qū)(靜態(tài)全局區(qū))存儲: 1.靜態(tài)變量 2.全局變量# include <stdio.h>int c; // 普通全局變量static int d; // 靜態(tài)全局變量int main(){int a; // 普通局部變量static int b; // 靜態(tài)局部變量int c; // 注意這個c不是上面的c,它們只是名字看起來一樣而已a = 10; // 普通局部變量沒有默認初始值,所以需要自己賦值printf("a = %d \n", a);printf("b = %d \n", b);printf("c = %d \n", c);printf("d = %d \n", d);// 通過以上 printf,可以總結(jié)規(guī)律:靜態(tài)全局區(qū),默認的初始值為0// 作用域和生命周期作用域 生命周期普通全局變量 當(dāng)前項目 程序開始到程序結(jié)束靜態(tài)全局變量 當(dāng)前文件 程序開始到程序結(jié)束普通局部變量 當(dāng)前語塊 當(dāng)前語塊靜態(tài)局部變量 當(dāng)前語塊 程序開始到程序結(jié)束return 0;}4.棧區(qū)存儲:普通局部變量從定義時系統(tǒng)自動分配內(nèi)存,離開當(dāng)前語塊系統(tǒng)就會自動回收內(nèi)存 5.堆區(qū)由程序員手動申請和釋放
*/
指針與函數(shù)
/*1.指針函數(shù)返回值類型是指針的函數(shù)2.函數(shù)指針指向函數(shù)的指針
*/
// 1.指針函數(shù)// 下面的代碼是一個錯誤的例子,你能發(fā)現(xiàn)它的錯誤嗎?
# include <stdio.h>int* test(); // 聲明int main()
{ int* temp = test();printf("%d \n", *temp);return 0;
} int* test()
{int num = 10;int* p = #return p; // 返回了棧區(qū)變量的首地址(非常嚴重的問題!詳情見下)
}/*下面的內(nèi)容是在棧區(qū),當(dāng)運行完畢,系統(tǒng)會回收其內(nèi)存資源:int* test(){int num = 10;int* p = #return p;}當(dāng)函數(shù)返回棧區(qū)變量 num 的內(nèi)存地址之后,函數(shù)運行完畢,系統(tǒng)回收 num 內(nèi)存,供以后"某某東西"使用所以,返回的地址不但沒有作用,還會導(dǎo)致以后非法訪問內(nèi)存的問題出現(xiàn)*/
// 2.函數(shù)指針/*函數(shù)指針的定義返回類型說明符 (*函數(shù)指針變量名)(參數(shù)列表);
*/# include <stdio.h>int func(); // 聲明int main()
{ // 定義函數(shù)指針,并進行初始化int(*p)() = func; // 即:定義了指針p,而且 p 等于 func(func里面存的是函數(shù)的首地址)func();p();return 0;
}int func()
{printf("成功執(zhí)行了 func 函數(shù)!\n");return 6;
}
// 函數(shù)指針,知識擴展1# include <stdio.h>int func(); // 聲明typedef int funcType(); // 將 int...() 取別名為 funcTypeint main()
{ funcType* p = func;p();return 0;
}int func()
{printf("成功執(zhí)行了 func 函數(shù)!\n");return 6;
}
// 函數(shù)指針,知識擴展2# include <stdio.h>int func(int a, int b); // 聲明typedef int(*pfunc)(int a, int b);int main()
{ pfunc p = func; // 這樣也能定義函數(shù)指針int res = p(1,2);printf("%d \n", res);return 0;
}int func(int a, int b)
{printf("成功執(zhí)行了 func 函數(shù)!\n");return a + b;
}
指針與數(shù)組
/*1.指針數(shù)組......2.數(shù)組指針......
*/
// 1.指針數(shù)組/*# include <stdio.h>int main()
{ // 定義并初始化"數(shù)組"int arr1[3] = { 1, 2, 3 }; // 數(shù)組 arr1[3] -> 里面存的都是 int 類型// 定義并初始化"指針數(shù)組"int* arr2[3] = { 地址1,地址2,地址三 }; // 數(shù)組 arr2[3] -> 里面存的都是 int* 類型arr2[2] = 新地址;return 0;
} */
// 2.數(shù)組指針/*定義"數(shù)組"指針:所指向的數(shù)組里面存的數(shù)據(jù)類型 (*數(shù)組指針名稱)[所指向的數(shù)組的長度];
*/# include <stdio.h>int main()
{ int arr[3] = { 1, 2, 3 }; // 建立一個數(shù)組。
// arr 里面存的是數(shù)組內(nèi)存的首地址,而 [] 表示內(nèi)存里面存的那一堆東西是數(shù)組,3 表示數(shù)組長度,int 表示數(shù)組里面存的數(shù)據(jù)是 int 類型int(*p)[3]; // 長度為 3 的數(shù)組指針p = arr;printf("%d \n", p[1]);return 0;
}
// 數(shù)組指針,知識擴展# include <stdio.h>typedef int(*pType)[3]; // 定義類型:int(*pType)[3],取別名為:pTypeint main()
{ int arr[3] = { 1, 2, 3 };pType p; // 變量 p 的類型為 pType,而屬于這種類型的變量 p 必然滿足 int(*pType)[3] 模板格式p = arr; // arr里面儲存的“數(shù)組的內(nèi)存首地址”復(fù)制給變量 pprintf("%d \n", (*p)[0]); // 注意!不要寫成 p[0],雖然 p 獲得了 arr 里面存的首地址,但是 *p 才是代表數(shù)組整體return 0;
} /*同理:# include <stdio.h>typeof int pArr[3];int main(){int arr[3] = { 1, 2, 3 };pArr p;p = arr;}
*/
使用const修飾指針
/*const:常量,被它修飾的變量會具有常量的屬性,使用 const 修飾指針包含三種情況1.常量指針(指向常量的指針)指向常量的指針 type const *p; 或者 const type *p;可以改變指向,但是不能用 *p 修改指向變量的值2.指針常量它是常量,本身不能改變,也就是不能改變指向因為指向不能改,所以必須初始化但是可以通過取內(nèi)容修改指向的內(nèi)存中的內(nèi)容3.常量指針常量("常量指針"常量即:指針常量)指針本身是一個常量,指向的也是常量const int * const p = &a;不能改變指向,也不能改變指向的內(nèi)存的內(nèi)容
*/# include <stdio.h>int main()
{const int num = 0; // 變量 num 使用 const 修飾了就不能被修改了// 1.常量指針int a = 0, b = 9;const int * p = &a; // const -> intp = &b; // 可以改變指向// 2.指針常量int c = 6;int* const p1 = &c; // const -> p1*p1 = 10; // 可以修改內(nèi)容// 3.常量指針常量int d = 8;const int* const p = &d; // const -> int 和 preturn 0;}
指針和結(jié)構(gòu)體
/*指針與結(jié)構(gòu)體:指針與結(jié)構(gòu)體結(jié)合起來使用包含兩種情況:一.指針成員結(jié)構(gòu)體變量的成員中存在指針二.結(jié)構(gòu)體指針指向結(jié)構(gòu)體變量的指針
*/
// 指針成員# include <stdio.h>typedef struct
{int n;int m;int* p; // 定義指針}MyStruct;int main()
{MyStruct mystr;mystr.n = 0;mystr.m = 0;mystr.p = NULL; // 地址置空return 0;}
// 結(jié)構(gòu)體指針# include <stdio.h>typedef struct
{int n;int m;}MyStruct;int main()
{MyStruct mystr;mystr.n = 0;mystr.m = 0;MyStruct* p = NULL; // 定義一個指針 p ,它的類型是 MyStruct*,即該指針指向的是有 MyStruct 類型的變量(首地址)p = &mystr; // mystr 便符合條件,可以將首地址 &mystr 賦值給 p// 注意!通過指針訪問“結(jié)構(gòu)體中的元素”的時候,用 -> 符號,而不是用 . 符號p->n = 9;p->m = 8;return 0;}
使用指針的注意事項
/*注意事項1.避免野指針推薦:每次定義指針都進行初始化(有指向就給指向,沒指向就置空 )2.注意類型匹配3.防止內(nèi)存泄漏只有堆區(qū)是自己申請,自己釋放,其他地方都是系統(tǒng)分配,系統(tǒng)回收總結(jié):指針能夠直接操作內(nèi)存,必須在自己明確用途的情況下使用,否則很可能會造成嚴重后果!
*/