鐵律1:指針是一種數據類型
1)指針也是一種變量,佔有內存空間,用來保存內存地址
測試指針變量佔有內存空間大小
2)*p操作內存
在指針聲明時,*號表示所聲明的變量爲指針
在指針使用時,*號表示 操作 指針所指向的內存空間中的值
*p相當於通過地址(p變量的值)找到一塊內存;然後操作內存
*p放在等號的左邊賦值(給內存賦值)
*p放在等號的右邊取值(從內存獲取值)
3)指針變量和它指向的內存塊是兩個不同的概念
//含義1 給p賦值p=0x1111; 只會改變指針變量值,不會改變所指的內容;p = p +1; //p++
//含義2 給*p賦值*p='a'; 不會改變指針變量的值,只會改變所指的內存塊的值
//含義3 =左邊*p 表示 給內存賦值, =右邊*p 表示取值 含義不同切結!
//含義4 =左邊char *p
//含義5 保證所指的內存塊能修改
4)指針是一種數據類型,是指它指向的內存空間的數據類型
含義1:指針步長(p++),根據所致內存空間的數據類型來確定
p++=➜(unsigned char )p+sizeof(a);
結論:指針的步長,根據所指內存空間類型來定。
注意:建立指針指向誰,就把把誰的地址賦值給指針。圖和代碼和二爲一。
不斷的給指針變量賦值,就是不斷的改變指針變量(和所指向內存空間沒有任何關係)。
鐵律2:間接賦值(*p)是指針存在的最大意義
1)兩碼事:指針變量和它指向的內存塊變量
2)條件反射:指針指向某個變量,就是把某個變量地址否給指針
3)*p間接賦值成立條件:3個條件
a)2個變量(通常一個實參,一個形參)
b) 建立關係,實參取地址賦給形參指針
c) *p形參去間接修改實參的值
Int iNum = 0; //實參
int *p = NULL;
p = &iNum;
iNum = 1;
*p =2 ; //通過*形參 == 間接地改變實參的值
*p成立的三個條件:
4)引申: 函數調用時,用n指針(形參)改變n-1指針(實參)的值。
//改變0級指針(int iNum = 1)的值有2種方式
//改變1級指針(eg char *p = 0x1111 )的值,有2種方式
//改變2級指針的(eg char **pp1 = 0x1111 )的值,有2種方式
//函數調用時,形參傳給實參,用實參取地址,傳給形參,在被調用函數裏面用*p,來改變實參,把運算結果傳出來。
//指針作爲函數參數的精髓。
鐵律3:理解指針必須和內存四區概念相結合
1) 主調函數 被調函數
a) 主調函數可把堆區、棧區、全局數據內存地址傳給被調用函數
b) 被調用函數只能返回堆區、全局數據
2) 內存分配方式
a) 指針做函數參數,是有輸入和輸出特性的。
鐵律4:應用指針必須和函數調用相結合(指針做函數參數)
編號 |
指針函數參數 內存分配方式(級別+堆棧) |
主調函數 實參 |
被調函數 形參 |
備註
|
||||
01 |
1級指針 (做輸入) |
堆 |
分配 |
使用 |
一般應用禁用 |
|||
棧 |
分配 |
使用 |
常用 |
|||||
Int showbuf(char *p); int showArray(int *array, int iNum) |
||||||||
02 |
1級指針 (做輸出) |
棧 |
使用 |
結果傳出 |
常用 |
|||
int geLen(char *pFileName, int *pfileLen); |
||||||||
03 |
2級指針 (做輸入) |
堆 |
分配 |
使用 |
一般應用禁用 |
|||
棧 |
分配 |
使用 |
常用 |
|||||
int main(int arc ,char *arg[]); 指針數組 int shouMatrix(int [3][4], int iLine);二維字符串數組 |
||||||||
04 |
2級指針 (做輸出) |
堆 |
使用 |
分配 |
常用,但不建議用,轉化成02 |
|||
int getData(char **data, int *dataLen); Int getData_Free(void *data); Int getData_Free(void **data); //避免野指針 |
||||||||
05 |
3級指針 (做輸出) |
堆 |
使用 |
分配 |
不常用 |
|||
int getFileAllLine(char ***content, int *pLine); int getFileAllLine_Free(char ***content, int *pLine); |
||||||||
指針做函數參數,問題的實質不是指針,而是看內存塊,內存塊是1維、2維。
- 如果基礎類int變量,不需要用指針;
- 若內存塊是1維、2維。
鐵律5:一級指針典型用法(指針做函數參數)
一級指針做輸入
int showbuf(char *p)
int showArray(int *array, int iNum)
一級指針做輸出
int geLen(char *pFileName, int *pfileLen);
理解
主調函數還是被調用函數分配內存
被調用函數是在heap/stack上分配內存
鐵律6:二級指針典型用法(指針做函數參數)
二級指針做輸入
int main(int arc ,char *arg[]); 字符串數組
int shouMatrix(int [3][4], int iLine);
二級指針做輸出
int Demo64_GetTeacher(Teacher **ppTeacher);
int Demo65_GetTeacher_Free(Teacher **ppTeacher);
int getData(char **data, int *dataLen);
Int getData_Free(void *data);
Int getData_Free2(void **data); //避免野指針
理解
主調函數還是被調用函數分配內存
被調用函數是在heap/stack上分配內存
鐵律7: 三級指針輸出典型用法
三級指針做輸出
int getFileAllLine(char ***content, int *pLine);
int getFileAllLine_Free(char ***content, int *pLine);
理解
主調函數還是被調用函數分配內存
被調用函數是在heap/stack上分配內存
鐵律8:雜項,指針用法幾點擴充
1)野指針 2種free形式
int getData(char **data, int *dataLen);
int getData_Free(void *data);
int getData_Free2(void **data);
2)2次調用
主調函數第一次調用被調用函數求長度;根據長度,分配內存,調用被調用函數。
3)返回值char */int/char **
4)C程序書寫結構
商業軟件,每一個出錯的地方都要有日誌,日誌級別
鐵律9:一般應用禁用malloc/new
【王保明老師經典語錄】
1)指針也是一種數據類型,指針的數據類型是指它所指向內存空間的數據類型
2)間接賦值*p是指針存在的最大意義
3)理解指針必須和內存四區概念相結合
4)應用指針必須和函數調用相結合(指針做函數參數)
指針是子彈,函數是槍管;子彈只有沿着槍管發射才能顯示它的威力;指針的學習重點不言而喻了吧。接口的封裝和設計、模塊的劃分、解決實際應用問題;它是你的工具。
5)指針指向誰就把誰的地址賦給指針
6)指針指向誰就把誰的地址賦給指針,用它對付鏈表輕鬆加愉快
7)鏈表入門的關鍵是分清楚鏈表操作和輔助指針變量之間的邏輯關係
8)C/C++語言有它自己的學習特點;若java語言的學習特點是學習、應用、上項目;那麼C/C++語言的學習特點是:學習、理解、應用、上項目。多了一個步驟吧。
9)學好指針才學會了C語言的半壁江山,另外半壁江山在哪裏呢?你猜,精彩剖析在課堂。
10) 理解指針關鍵在內存,沒有內存哪來的內存首地址,沒有內存首地址,哪來的指針啊。
代碼解析
#define _CRT_SECURE_NO_WARNINGS
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
void main00()
{
int a = 10;
char *p1 = 100; //分配4個字節的內存
char ****p2 = 100;
int *p3 = NULL;
p3 = &a;
*p3 = 20; //間接的修改a的值
//*就像一把鑰匙 通過一個地址(&a),去修改a變量的標示的內存空間
{
int c = 0;
c = *p3; //c=20
//*p放在=號左邊 寫內存
//*p放=號的右邊 讀內存
printf("c:%d \n", c);
}
{
char *p4 = NULL;
p4 = (char *)malloc(100);
p4 = (char *)malloc(200); //0xcc11
}
printf("a:%d , p1:%d , p2: %d", sizeof(a), sizeof(p1), sizeof(p2));
printf("hello...\n");
system("pause");
return ;
}
char *getStr()
{
char *tmp = NULL;
tmp = "abcdefgf";
return tmp;
}
/*
int getABC1(char *p1); int getABC1(char* p1);
int getABC2(char ** p2); int getABC2(char * *p2); int getABC2(char **p2);
int getABC3(char ***p3);
int getABC4(char (*p4)[30]); int getABC4(char (*p4) [30]);
int getABC5(char p5[10][30]); int getABC5(char p5[10][30]);
//指針做函數參數 形參有多級指針的時候,
//站在編譯器的角度 ,只需要分配4個字節的內存(32bit平臺)
//當我們使用內存的時候,我們才關心指針所指向的內存 是一維的還是二維的
*/
void main()
{
char *p = getStr81();
printf("p:%s \n", p);
*(p+2) = 'r'; //經常出現的錯誤 保證指針所指向的內存空間 可以被修改
system("pause");
return ;
}
野指針產生的原因
//指針變量和它所指向的內存空間變量是兩個不同的概念
//避免方法:
//1)定義指針的時候,指針變量*p1初始化成nuLL 2)釋放指針所指向的內存空間後,把它所指向的內存空間變量p1重置成NULL。
void main()
{
char *p1 = NULL;
p1 = (char *)malloc(100);
if (p1 == NULL)
{
return ;
}
strcpy(p1, "11112222");
printf("p1:%s \n", p1);
if (p1 != NULL)
{
free(p1);
p1 = NULL;
}
printf("hello...\n");
system("pause");
return ;
}
一級指針技術推演
int getFileLen(int *p)
{
*p = 41; //p的值是a的地址 *a的地址間接修改a的值
//在被調用函數裏面 通過形參 去 間接的修改 實參的值...
}
//形參的屬性
int getFileLen3(int b)
{
int i = 0;
b = 100;//p的值是a的地址 *a的地址間接修改a的值
}
//1級指針的技術推演
void main()
{
int a = 10; //條件1 定義了兩個變量(實參 另外一個變量是形參p)
int *p = NULL;
//修改a的值
a = 20; //直接修改
p = &a; //條件2 建立關聯
*p = 30; //p的值是a的地址 *就像一把鑰匙 通過地址 找到一塊內存空間 求間接的修改了a的值
printf("a: %d \n", a);
{
*p = 40; // p的值是a的地址 *a的地址間接修改a的值 //條件3 *p
printf("a: %d \n", a);
}
getFileLen(&a); //建立關聯: 把實參取地址 傳遞給 形參
printf("getFileLen後 a: %d \n", a);
getFileLen3(a); //無法修改
printf("getFileLen3後 a: %d \n", a);
printf("hello...\n");
system("pause");
return ;
}
二級指針技術推演
void getMem(char **p2)
{
*p2 = 400; //間接賦值 p2是p1的地址
}
void getMem2(char *p2)
{
p2 = 800; //間接賦值 p2是p1的地址
}
void main()
{
char *p1 = NULL;
char **p2 = NULL;
p1 = 0x11;
p2 = 0x22;
//直接修改p1的值
p1 = 0x111;
//間接修改p1的值
p2 = &p1;
*p2 = 100; //間接賦值 p2是p1的地址
printf("p1:%d \n", p1);
{
*p2 = 200; //間接賦值 p2是p1的地址
printf("p1:%d \n", p1);
}
getMem(&p1);
getMem2(p1);
printf("p1:%d \n", p1);
system("pause");
return ;
}
int getMem3(char **myp1, int *mylen1, char **myp2, int *mylen2)
{
int ret = 0;
char *tmp1, *tmp2;
tmp1 = (char *)malloc(100);
strcpy(tmp1, "1132233");
//間接賦值
*mylen1 = strlen(tmp1); //1級指針
*myp1 = tmp1; //2級指針的間接賦值
tmp2 = (char *)malloc(200);
strcpy(tmp2, "aaaaavbdddddddd");
*mylen2 = strlen(tmp2); //1級指針
*myp2 = tmp2; //2級指針的間接賦值
return ret;
}
int main()
{
int ret = 0;
char *p1 = NULL;
int len1 = 0;
char *p2 = NULL;
int len2 = 0;
ret = getMem3(&p1, &len1, &p2, &len2);
if (ret != 0)
{
printf("func getMem3() err:%d \n", ret);
return ret;
}
printf("p1:%s \n", p1);
printf("p2:%s \n", p2);
if (p1 != NULL)
{
free(p1);
p1 = NULL;
}
if (p2 != NULL)
{
free(p2);
p2 = NULL;
}
printf("p1:%d \n", p1);
system("pause");
return ret;
}
間接賦值成立的三個條件
/*
條件1 //定義1個變量(實參) //定義1個變量(形參)
條件2 //建立關聯:把實參取地址傳給形參
條件3://*形參去間接地的修改了實參的值。
*/
//間接賦值的應用場景
void main()
{
//1 2 3 這3個條件 寫在有一個函數
//12 寫在一塊 3 單獨寫在另外一個函數裏面 =====>函數調用
//1 23寫在一塊 ===>拋磚 ====C++會有,到時候,你別不認識......
char from[128];
char to[128] = {0};
char *p1 = from;
char *p2 = to;
strcpy(from, "1122233133332fafdsafas");
while (*p1 != '\0')
{
*p2 = *p1;
p2 ++;
p1 ++;
}
printf("to:%s \n", to);
system("pause");
return ;
}
指針的輸入與輸出特性
//指針做輸出:被調用函數分配內存 -----OK
//指針做輸入:主調用函數 分配內存
//求文件中的兩段話的長度
int getMem(char **myp1, int *mylen1, char **myp2, int *mylen2)
{
char *tmp1 = NULL;
char *tmp2 = NULL;
tmp1 = (char *)malloc(100);
if (tmp1 == NULL)
{
return -1;
}
strcpy(tmp1, "abcdefg");
*mylen1 = strlen(tmp1);
*myp1 = tmp1; //間接修改實參p1的值
tmp2 = (char *)malloc(100);
if (tmp2 == NULL)
{
return -2;
}
strcpy(tmp2, "11122233333");
*mylen2 = strlen(tmp2);
*myp2 = tmp2; //間接修改實參p1的值
return 0;
}
int getMem_Free(char **myp1)
{
/*
if (myp1 == NULL)
{
return ;
}
free(*myp1); //釋放完指針變量 所致的內存空間
*myp1 = NULL; //把實參修改成nULL
*/
char *tmp = NULL;
if (myp1 == NULL)
{
return -1;
}
tmp = *myp1;
free(tmp); //釋放完指針變量 所致的內存空間
*myp1 = NULL; //把實參修改成nULL
return 0;
}
void main00()
{
char *p1 = NULL;
int len1 = 0;
char *p2 = NULL;
int len2 = 0;
int ret = 0;
ret = getMem(&p1, &len1, &p2, &len2 );
printf("p1: %s \n", p1);
printf("p2: %s \n", p2);
getMem_Free(&p1);
getMem_Free(&p2);
system("pause");
return ;
}
int getMem_Free0(char *myp1)
{
if (myp1 == NULL)
{
return -1;
}
free(myp1); //釋放完指針變量 所致的內存空間
myp1 = NULL;
return 0;
}
void main01()
{
char *p1 = NULL;
int len1 = 0;
char *p2 = NULL;
int len2 = 0;
int ret = 0;
ret = getMem(&p1, &len1, &p2, &len2 );
printf("p1: %s \n", p1);
printf("p2: %s \n", p2);
if (p1 != NULL)
{
free(p1);
p1 = NULL;
}
if (p2 != NULL)
{
free(p2);
p2 = NULL;
}
getMem_Free0(p1); //在被調用函數中 把p1所指向的內存給釋放掉 ,但是 實參p1不能被修改成NULLL 有野指針現象
getMem_Free0(p2);
system("pause");
return ;
}