C語言基礎學習筆記:day5 指標
注:本筆記為直接上傳,因各個markdown筆記語法的差異性,在顯示上略有區別。
如需原版請聯絡:[email protected]。(郵件主題為:學習筆記,正文需要的筆記名,可以直接複製該筆記的網址)。同時歡迎各位一起學習交流。
day5指標
文章目錄
一、指標的含義和定義
指標存放的內容是一個地址,該地址指向一個記憶體空間;
1.指標的定義
#include<stdio.h>
int main()
{
int a = 0;
int b = 10;
char buf[10];
printf("%p,%p,%p\n",&a,&b,buf); //&即是取變數地址,陣列名錶地址
int *p = &a; //得到變數a的地址,將這個地址賦值給指標變數p
//地址是一個整數,但是特殊性在於這個整數不能直接通過整數來操作
//上面的賦值等效於以下語句:
//int *p; //定義一個變數,名字叫做p,它指向於一個int的地址
//p = &a; //指標變數的值一般不能賦值一個整數,而是通過取變數地址的方式進行賦值。
return 0;
}
通過指標獲得指標所指記憶體的值,可以通過指標直接更改值的大小
2.指標的使用
#include<stdio.h>
int main()
{
int a = 3;
int *p;
p = &a; //定義指標之後應該初始化或者賦值,否則會成野指標
int b = *p; //*p 表示指標變數指向記憶體的內容
printf("b = %d\n",b);
*p = 10;//通過指標間接的修改變數的值,這時候a的值為10;
printf("a = %d\n",a);
return 0;
}
程式的執行結果為:b = 3;
a = 10;
3.空指標與野指標的區別
- 空指標: 指向NULL的指標,當一個指標不指向任何一個有效的記憶體地址的時候,應該把指標設定成NULL
- 野指標 : 沒有具體指向任何變數地址的指標(程式中應該避免野指標的存在)
- 空指標程式例子
#include<stdio.h>
int main()
{
int i = 0;
void *p; //定義無型別指標,意思是這只是一個指標變數,而不指向任何具體的資料型別;
p = NULL; //將指標賦值NULL,值為NULL的指標,我們俗稱為空指標,NULL其實就是0
return 0;
}
二、指標與陣列的關係
1.指標的相容性
指標之間賦值要根據雙方的資料型別來判斷,原則上一定是相同型別的指標指向相同型別的變數地址,不能用一種型別的指標指向另一種型別的變數地址。
#include<stdio.h>
int main()
{
float a = 3.14;
int i = a; //這是可以的:C語言會自動進行資料型別轉換,將浮點數後面的小數部分捨棄;
//int *p = &a; //這個一般不可以:這是很嚴重的錯誤,指標型別不相容
printf("i = %d\n",i);
// printf("*p = %d\n",*p);
return 0;
}
程式執行結果:i = 3
2.特殊的兩類指標
- 指向常量的指標
定義: const 資料型別 指標
特點:不能通過修改指標值(*p)來改變所指向記憶體空間存放的值
#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
const int *p = &a; //p這個指標只能指向一個常量,可以直接改變a的賦值來改變a的值,但是不能通過改變*p的值來改變a的值;
//*p = 30; //這樣不能修改,只會報錯;
a = 30; //這樣是可以的
p = &b; //這樣也是可以的
printf("%d\n",*p);
return 0;
}
程式執行結果:*p = 20
- 常量指標
定義:資料型別 *const 指標名
特點:不能修改指標指向的記憶體位置
#include<stdio.h>
int main()
{
int a = 10;
int b = 20;
int *const p = &a; //定義一個常量指標,可以通過修改常量指標修改或者讀取一個變數的值
*p = 30;
// p = &b; //這是錯誤的,常量指標一旦定義了,那麼就不能修改其指向的變數
printf("a = %d\n",*p);
return 0;
}
程式執行結果:a = 30
3.指標的加減對應指向記憶體的位置的改變
#include<stdio.h>
int main()
{
int array[100] = {1,2,3,4,5,6,7,8,9,0};
int *p = array;
p += 5; // ==這裡p在記憶體中相當於移動了5*4(8)個位元組==
p -= 3;
p[3] = 100; //==這時候實際上相當於array[5] 的值為100==
int i = 0;
for(i = 0; i < 10; i++)
{
printf("array[%d] = %d\n",i,array[i]);
}
return 0;
}
程式執行的結果:
array[0] = 1
array[1] = 2
array[2] = 3
array[3] = 4
array[4] = 5
array[5] = 100
array[6] = 7
array[7] = 8
array[8] = 9
array[9] = 0
4.通過指標相減可以得到陣列之間兩個元素之間的距離差
#include<stdio.h>
int main()
{
int array[20] = {1,23,4,78,9,2};
int *p1 = &array[3];
int *p2 = &array[7];
printf("juli = %d\n",p2 -p1);
return 0;
}
程式輸出結果:juli = 4
5.指標與陣列在地址上的關係
陣列名等效於陣列名取地址,等效於陣列首元素的地址
#include<stdio.h>
int main()
{
char buf[10] = {0,1,2,3,4};
char *p = buf;
char *p1 = &buf;
char *p2 = &buf[0];
char *p3 = &buf[1];
char *p4 = &buf[2];
p3 ++; //改變了p3所指的地址,變成了p4指向的buf[2]
printf("%d,%d,%d,%d,%d\n",p,p1,p2,p3,p4);
return 0;
}
除錯的結果為:
343472120,343472120,343472120,343472122,343472122
可見前面三個是等效的;後面可以通過指標的加減實現指向位置的改變,從而改變輸出值的大小
示例1: 用指標來求一個字串的長度,不可以使用陣列下標的方式
#include<stdio.h>
int main()
{
char s1[100] = "hello";
char s2[100] = "world";
char *p1 = s1;
int len = 0;
while(*p1) //*p1的內容為0的時候,表示字串就結束了
{
p1++;
len++;
}
printf("len = %d\n",len);
return 0;
}
示例2: 用指標來將s1和s2合併為一個字串,結果放入s1中,不可以使用陣列下標的方式;
#include<stdio.h>
int main()
{
char s1[100] = "hello";
char s2[100] = "world";
//獲取字串s1的長度
char *p1 = s1;
int len = 0;
while(*p1) //*p1的內容為0的時候,表示字串就結束了
{
p1++;
len++;
}
printf("len = %d\n",len);
//將s2元素連線到s1後面,這時候p1已經指向了s1的最後名的\0
char *p2 = s2;
while(*p2)
{
*p1 = *p2; //從s1陣列最後開始,從s2的首元素開始,因為根據上面的語句,在求出s1長度的同時,p1也指向了s1的末尾位置;
p2++;
p1++;
}
//以上的while語句等效為:
/*
while(*p2)
{
*p1++ = *p2++;
//先++在取值
}
*/
printf("s1 = %s\n",s1);
return 0;
}
6.使用指標來訪問陣列成員
#include<stdio.h>
int main()
{
int array[100] = { 0 };
int *p = array;
p[0] = 100; //這是允許的,從語法上可以和陣列方式一樣,就是通過下標的方式進行訪問
printf("sizeof(array) = %d\n",sizeof(array));
printf("sizeof(p) = %d",sizeof(p));
printf("sizeof(char *) = %d",sizeof(char *)); //char * 表示一個指向char的指標
return 0;
}
執行結果為:
sizeof(array) = 400
sizeof(p) = 8
(在32位的作業系統中值為4),因為本質上是一個int型整數
sizeof(char *) = 8
7.遊戲外掛:針對於單機遊戲(以植物大戰殭屍為例)
-
首先開啟遊戲,開始遊戲之後,比如現在陽光的數量為175,這時候開啟
cheat Engine
軟體,在值中輸入175,然後會出現所有變數值為175的變數及其地址; -
這時候繼續遊戲,消耗陽光之後,比如值變為75,繼續在軟體中輸入變數值為75,然後再次查詢就會得到唯一的變數地址,即為存放陽光變數的真正地址
-
建立工程開始寫程式,將變數值進行修改
_declpec(dllexport) //加上這個關鍵字,代表go函式是可以在其他程式中呼叫dll函式
void go()
{
int *p = 0x1CF88808; //得到陽光變數的地址,改地址可以通過以上的軟體得到
while(1)
{
if(*p < 100)
{
*p = 300; //應當避免一次性賦值很大,防止觸發系統的反作弊機制
}
}
}
-
然後將專案屬性修改為動態庫.dll
-
然後使用軟體dllinject 將這個程式新增進遊戲之後就可以了;
三、指標陣列以及多級指標
1.兩種相似定義的區別
#include<stdio.h>
int main()
{
int *s[10] = {0}; //本質上定義了一個數組,指向int*型變數
int (*p)[10] = 0; //本質上僅僅定義了一個指標變數,指向int [10]這麼大的一種資料型別
printf("sizeof(s) = %d\n",sizeof(s));
printf("sizeof(p) = %d\n",sizeof(p));
}
程式執行結果:sizeof(s) = 80
sizeof(p) = 8
2.指標陣列
定義和使用
#include<stdio.h>
int main()
{
int *a[10];//定義了一個指標陣列,一共10個成員,其中每個成員都是int *型別;
printf("%d,%d\n",sizeof(a),sizeof(a[0]));
short *b[10]; //定義了一個含有十個成員的指標陣列,其中每個成員都是short *型別
printf("%d,%d\n",sizeof(b),sizeof(b[0]));
//具體使用方法
int a1;
int a2;
a[0] = &a1;
a[1] = &a2;
return 0;
}
3.指向指標的指標
因為指標本身也為變數,也存在地址,相當於用指標指向指標的地址
#include<stdio.h>
int main()
{
int a = 10;
int *p = &a; //定義一個指標p存放變數a的地址
int **pp = &p; //定義一個二級指標,指向了一個一級指標的地址
//這裡等效於以下程式碼
int **pp;
pp = &p;
//使用
**pp = 100; //通過二級指標修改記憶體的地址
//*pp = 10; 這相當於將p指向了編號為10的這塊記憶體,pp還是正常的指標,但是p被修改成野指標了;
printf("a = %d\n",a);
return 0;
}
程式執行結果:a = 100
4.指向多維陣列的指標
——以二維陣列為例
- 定義:
#include<stdio.h>
int main()
{
int buf[2][3] = {{1,2,3},{4,5,6}};
int (*p)[3]; //定義了一個指標,只有一個成員變數,指向int [3] 這種資料型別,是一種指向二維陣列的指標;
p = buf; //表示指向二維陣列的第一行
//如果想要表示陣列的第二行,可以使用P++
//同理 p[0] = buf的首地址
printf("%d\n",sizeof(p)); //sizeof(p)在32為的作業系統中是4,在64位作業系統中值為8
printf("%d,%d\n",p,p + 1); //這裡的p+1實際上平移了1 * sizeof(int [3])大小,即平移了24個位元組;
int i = 0;
for(i = 0; i < 2; i++)
{
printf("%d\n",*p[i]);//*p[i] 表示i行首元素的值
}
return 0;
}
程式執行結果:1
,4
- 使用:
功能:將二維陣列中所有的元素打印出來
#include<stdio.h>
int main()
{
int buf[2][3] = {{1,2,3},{4,5,6}};
int (*p)[3];
p = buf;
int i;
int j;
for(i = 0; i < 2; i++)
{
for(j = 0; j < 3; j++)
{
printf("%d\n",p[i][j]);
}
}
return 0;
}
程式執行結果:
1
2
3
4
5
6
上面程式中printf(“%d\n”,p[i][j]);
等效於 printf(“%d\n”,*(*(p+i)+j));
註釋:(p+i)
表示每一行的首地址,等效於p[i]
5.各種地址的表示方法
表示方法 | 含義 |
---|---|
int buf[3][5] | 二維陣列名稱,buf代表陣列首地址 |
int (*a)[5] | 定義一個指向int[5]型別的指標變數a |
a[0] , (a+0) ,a | 表示第0行第0列元素的地址 |
a+1 | 表示第一行首地址 |
a[1] , *(a +1) | 表示第一行第0列元素的地址 |
a[1] + 2 ,*(a+1)+2 , &a[1][2] | 表示第一行第二列元素的地址 |
(a[1] + 2) , (*(a+1)+2) , a[1][2] | 表示第一行第二列元素的值 |
6.練習
目標:在不使用陣列下標的情況下,只能通過指向二維陣列的指標求出陣列中每行和每列的平均值
該程式不完整
#include<stdio.h>
int main()
{
int buf[3][5] = {{2,4,3,5,3},{7,2,6,8,1},{7,3,5,0,2}};
int (*p)[5];
p = buf;
int i;
int j;
float _aver_hang = 0;
float _aver_lie = 0;
float _sum_hang = 0;
float _sum_lie = 0;
for(i = 0; i < 3; i++)
{
for(j = 0; j < 5; j++)
{
_sum_hang += p[i][j];
_aver_hang = _sum_hang / 3.0;
}
printf("%d\n",_aver_hang);
}
return 0;
}
7.const保護函式引數以及返回值為指標的函式
通過函式的指標引數可以間接的實現形參修改實參的值;
- 無法傳值的程式
#include<stdio.h>
void test(int p)
{
p++;
}
int main()
{
int i = 0;
test(i);
printf("i = %d\n",i);
return 0;
}
程式執行結果:i = 0
;
因為形參的值不能反過來傳給實參
- 可以傳值的程式
想改變實參的值需要使用指標;
#include<stdio.h>
void test(int *p)
{
(*p)++; //*p為i的值,則對應的是i+1;
}
int main()
{
int i = 0;
test(&i); //傳給形參的是實參i的地址
printf("i = %d\n",i);
return 0;
}
程式結果為:i=1
;
8.交換兩個資料的值:
#include<stdio.h>
void swap(int *c,int *d)
{
int tmp = *c; //*c即是a的值
*c = *d;
*d = tmp;
}
int main()
{
int a = 10;
int b = 20;
swap(&a,&b); //傳遞的是地址
printf(