十一、指針和引用(一) 1、指針 1)思考 在電腦程式中,有一條鐵律那就是萬物皆內粗,而我們知道,記憶體就是一個個小格,存放著高電平或者低電平,也就是0或者1,我們要表達的一切都是通過這種二進位的方式放到記憶體中,當我們讀取、寫入,其實局勢在對應的記憶體空間執行讀或者寫操作 我們今天就研究研究, ...
十一、指針和引用(一)
1、指針
1)思考
在電腦程式中,有一條鐵律那就是萬物皆內粗,而我們知道,記憶體就是一個個小格,存放著高電平或者低電平,也就是0或者1,我們要表達的一切都是通過這種二進位的方式放到記憶體中,當我們讀取、寫入,其實局勢在對應的記憶體空間執行讀或者寫操作
我們今天就研究研究,當我們讀取和寫入的時候,背後的故事,首先我們需要知道我們讀取和寫入的記憶體地址,因為記憶體里有很多個小格,就好比一棟樓有很多住戶,你要找好朋友張三,你就要知道他家的地址,你不能挨個去敲門,因為這樣會被打死...,其次來說,效率也很低。
再者來講,你還要知道你到底要讀取多少格的內容,或者寫入多少格的內容,因為不同的數據類型,占用的記憶體空間也是不同的
總結:操作記憶體,即讀取和寫入操作時,需要知道記憶體地址和記憶體的大小
2)記憶體空間模擬圖
在電腦中,記憶體的最小單位為位元組,每8個bit算一個記憶體地址,要操作記憶體,需要知道記憶體的地址和記憶體的大小
3)指針語法
C/C++提供了讓我們直接操作記憶體的機會,這種機會就是利用指針,利用指針操作記憶體需要知道兩個要素:即要操作的記憶體地址和要操作的記憶體大小。
指針的本質就是一種特殊的變數類型,指針本身就是一種變數。
利用int類型的指針,可以操作int類型的數據,int類型占4個位元組,所以int類型的指針操作的也是4個位元組的記憶體。
//指針語法
數據類型* 變數名稱; //數據類型解決指針記憶體大小的問題
//示例
int* pStudentId;
#include <iostream>
int main()
{
int* a{ }; //聲明一個int類型的指針,指針指向的是記憶體地址
std::cout << a;
}
4)指針的其他聲明方法
//指針聲明
數據類型 *變數名稱;
//示例
int *pStudentId; //*號靠近變數名
int* a{},b; //a是int類型的指針,b是int類型的變數
int *a{},*b; //a和b都是int類型的指針
//指針聲明建議方式:多個指針分開寫
int* a;
int* b;
5)取址運算符(讀取記憶體地址)
既然指針是用來操作記憶體的,那麼如何獲得一個變數的記憶體地址呢?可以通過取值運算符&獲取變數的地址。取值運算符&是一個一元運算符,用於獲取變數的地址,也可稱為引用運算符。
#include <iostream>
int main()
{
int a = 500;
int* pa{ &a }; //聲明一個int類型的指針pa,將a的地址賦值給指針pa
std::cout << pa; //輸出a記憶體地址00F9FA98
}
//註:局部變數,每一次運行程式,記憶體地址是會發生變化的
6)間接運算符(操作記憶體地址)
通過取值運算符&可以獲取到變數的記憶體地址,通過間接運算符*可以操作記憶體地址。
通過地址來操作記憶體空間,雖然效果一樣,但是原理不一樣
//間接運算符*
#include <iostream>
int main()
{
int a = 500;
int* pa{ &a }; //聲明一個int類型的指針pa,將a的地址賦值給指針pa
//pa=0x5000 //表示修改a的記憶體地址
std::cout << "a的記憶體地址為:" << pa << std::endl;
*pa = 1000; //在變數a的記憶體空間中寫入1000(修改a的記憶體空間),即修改變數a的值
std::cout << "a的值為:" << a << std::endl; //輸出a的值為1000
}
// *pa可以當作來用,*pa是直接操作變數的記憶體空間;直接修改a的值,是通過操作系統來修改a的值,本質不同。
7)指針聲明、取值及操作示例
//指針聲明、取值及操作示例
#include <iostream>
int main()
{
int a{ 5000 }; //聲明一個int類型的指針,並初始化為空指針
int* pa{ &a }; //pa等於a的記憶體地址
std::cout << "a的記憶體地址:" << pa << std::endl << "a的初始值:" << *pa << std::endl;
*pa = 1000; //通過記憶體修改值
std::cout << "修改後a的值:" << *pa << std::endl;;
std::cout << "++++++++++++++++++++++++++++++++++++++++++++" << std::endl;
char c = 65;
char* pc = &c;
std::cout << *pc << std::endl;
(*pc)++; //要對指針進行++,需要使用括弧
std::cout << *pc << std::endl;
}
2、指針數組
要深刻理解,指針的本質起始就是一種特殊的變數類型,因此指針也可以通過數組的方式聲明。對變數可以操作什麼,那麼對指針就能夠操作什麼
1)指針數組的聲明
//指針數組的聲明語法
int* ptrArray[10]; //即什麼10個int類型的指針
2)指針數組的使用
#include <iostream>
int main()
{
int studentId[5]{ 1001,1002,1003,1004,1005 };
//取數數組中每個元素的記憶體地址,查看是否連續
int* ptrStudentId[5]{};
for (int i = 0; i < 5; i++)
{
ptrStudentId[i] = &studentId[i]; //指針數組的小標即對應數組的小標
std::cout << "第" << i << "個元素的記憶體地址為" << ptrStudentId[i] <<",值為" <<studentId[i]<< std::endl;
}
}
2)指針二維數組
#include <iostream>
int main()
{
int studentId[2][2]
{
{1001,1002},
{2001,2002}
};
int* ptrStudent[2][2]; //聲明一個二維數組指針
for (int x = 0; x < 2; x++)
{
for (int y = 0; y < 2; y++)
{
ptrStudent[x][y] = &studentId[x][y]; #使指針獲取到二維數組的地址
std::cout << "記憶體地址:" << ptrStudent[x][y] << " 值:" << *ptrStudent[x][y] << std::endl;
}
}
}
3、指針補充
1)指針的大小
指針也是一種特別的數據類型,也是一個變數,因此也需要記憶體空間來存放指針。
指針的本質是一個記憶體地址,而記憶體地址的本質是一個整數。為了能夠完美表達記憶體地址,不管是什麼類型的指針,在32位操作系統下指針的大小都為4個位元組,64位操作系統下為8位元組,即需要4個位元組來存放指針
//可以通過sizeof()計算指針的大小
#include <iostream>
int main()
{
int a{ 100 };
int* ptr{ &a };
char ch{ 65 };
char* ctr{ &ch };
std::cout << sizeof(ptr) << "\n"; //輸出4
std::cout << sizeof(ctr) << "\n"; //輸出4
}
//在x86和x64操作系統下,指針的大小不同
2)指針的類型轉化
不能將一個類型的地址賦值給另外一個類型的指針。只要是指針,就說明是一個記憶體地址,就可以進行類型轉化
![1700208369312](D:\2023年學習\逆向\筆記\12 【CC++ 基礎語法】指針和引用(一)\image\1700208369312.png)
類型的意義在於告訴編譯器,同樣的一個地址,顯示的結果不同,在於變數的類型,如果是int類型的指針,顯示內容時,按照int的規則進行處理
#include <iostream>
int main()
{
int a{ 9999 };
unsigned b{ 9999 };
int* ptra{ &a };
//ptra = &b; //&b是一個地址,說明就是一個整數,整數就可進行數據類型轉化
ptra =(int*)&b; //對&b進行地址轉化,轉化為int類型的指針
std::cout <<"b的初始值為:" << b << std::endl;
*ptra = 5200;
std::cout << "通過間接運算符修改後,b的值為:" << b << std::endl;
std::cout << "通過間接運算符修改後,b的值為:" << *ptra << std::endl;
std::cout << std::endl;
*ptra = -1;
std::cout << "通過間接運算符修改後,b的值為:" << b << std::endl; //b的類型為unsigned,所以輸出的為正數
std::cout << "通過間接運算符修改後,b的值為:" << *ptra << std::endl; //*ptra的類型的int型指針,所以輸入為-1
//同一個記憶體地址,但是因為數據類型的不同,輸出的值也不同
char* ctr{};
ctr = (char*)ptra;
//A的16進位為41,而char類型,只能夠修改指針中的一個位元組,其他位元組的值無法修改,即0xFFFFFF41,轉化為10進位為4294967105
*ctr = 'A';
std::cout << "轉化為char類型指針後b的值為:" << b << std::endl;
}
4、指針的計算
//指針的計算
int a[]{1001,1002,1003,1004,1005};
int* ptr{&a[0]};
計算(*ptr)++和*ptr++的結果? //*ptr++相當於*(ptr++)
(*ptr)++ 相當於a[0]++,即1001+1==1002
#include <iostream>
int main()
{
int a[]{ 1001,1002,1003,1004,1005 };
int* ptr{ &a[0] };
std::cout << ptr << std::endl;
std::cout << *ptr << std::endl;
(*ptr)++; //*ptr即a的值,即將a的值+1,即1002
std::cout << ptr << std::endl;
std::cout << *ptr << std::endl;
*ptr++; //++的優先順序高,即先ptr++,即地址進行++,地址++,一次增加數據類型的長度
std::cout << ptr << std::endl;
std::cout << &a[1] << std::endl; //ptr+1指向了數組的下一個元素的起始地址
std::cout << *ptr << std::endl;
//指針+1的時候,數值的變化是+1*指針類型的大小
}
5、指針的指針
//指針的指針
int a[]{1001,1002,1003,1004,1005};
int* ptr{&a[0]};
如何表示ptr指針的記憶體?
註:ptr是一個int類型的指針,因此ptr是一個變數,在32位操作系統下占用4個位元組記憶體,因此ptr也有地址
//指針的指針示例
#include <iostream>
int main()
{
int a[]{ 1001,1002,1003,1004,1005 };
int* ptr{ &a[0] };
int** pptr{ &ptr }; //指針的指針,取指針ptr的地址
std::cout <<"a的地址:" << ptr << std::endl;
std::cout <<"通過地址取a的值:" << *ptr << std::endl;
std::cout <<"取a的指針的地址:" << pptr << std::endl;
std::cout <<"取a的指針的值:" << *pptr << std::endl; //是一個地址
std::cout <<"取a指針的值(地址)的值:" << **pptr << std::endl;
std::cout << "++++++++++++++++++++++++++++++++\n";
*pptr = &a[1];
std::cout << *ptr << std::endl;
}
{{uploading-image-986560.png(uploading...)}
多級指針
代碼 | 值 | 記憶體地址 |
---|---|---|
int a{500}; | 500 | 0x50000 |
int* ptr{&a}; | 0x50000 | 0x50100 |
int** pptr(&ptr) | 0x50100 | 0x50200 |
int*** ppptr | 0x50200 | 0x50300 |
6、常量指針、指針常量、指向常量對象的常量指針
1)常量指針
所謂的常量指針,即這個指針指向一個常量的記憶體地址,常量指針中,不能對其指向的記憶體地址進行改變,但是指針指向的地址可以改變
//常量指針語法
const 變數類型* 變數名{&常量名}
//示例
int const a{1000};
int const b{1500};
const int* ptrA{&a};
ptrA = &b; //正確,可以修改常量指針的指向
*ptrA = 500; //錯誤,不可以修改常量指針記憶體中的地址
//常量指針示例
#include <iostream>
int main()
{
const int a{ 1000 };
const int b{ 2000 };
const int* ptr{ &a }; //常量指針
std::cout << ptr << std::endl;;
//*ptr = 9000; //錯誤,當指針指向常量時,不可以修改記憶體地址里的值
ptr = &b; //但是可以修改常量指針的指向
std::cout << ptr << std::endl;
}
//註:常量指針可以修改指向,但是不可以修改記憶體里的值
2)指針常量
所謂的指針常量,即這個指針變數是一個常量,一旦初始化就不可以再指向其它記憶體地址,但是記憶體地址里的數據可以讀寫。(即指針是一個常量)
//指針常量語法
變數類型* const
//示例
int const a{1000};
int const b{1500};
int* const ptrA{&a};
ptrA = &b; //錯誤,不可以修改常量指針的指向
*ptrA = 500; //正確,可以修改常量指針記憶體中的值
//指針常量示例
#include <iostream>
int main()
{
int a{ 1000 };
int b{ 2000 };
int* const ptr{ &a }; //指針常量,const修飾的是ptr
std::cout << *ptr << std::endl;;
//ptr = &b; //錯誤,指針指向的記憶體空間不可以修改
*ptr = 9000; //正確,可以修改記憶體空間的值
std::cout << a << std::endl;
}
3)指向常量的常量指針
指向常量的常量指針,即這個指針變數是一個常量,一旦初始化就不可以再指向其他記憶體地址,因為其本事就是一個指向常量的指針,所以其執行的記憶體區域也不可以修改。
//指向常量的常量指針語法
const 變數類型* const
//示例
int const a{1000};
int const b{1500};
const int* ptrA{&a};
ptrA = &b; //錯誤,不可以修改常量指針的指向
*ptrA = 500; //錯誤,不可以修改常量指針記憶體中的值
//指向常量的常量指針示例
#include <iostream>
int main()
{
const int a{ 1000 };
const int b{ 2000 };
const int* const ptr{ &a };
//ptr = &b; //錯誤,不允許修改記憶體地址的指向
//*ptr = 9999; //錯誤,不允許修改記憶體地址的值
std::cout << *ptr << std::endl;
}
7、項目:通過指針實現游戲技能
需求:設計麟江湖的技能釋放模型,要求用戶按下相應技能快捷鍵後開始釋放技能,技能數據如下,假設角色的當前等級下最高內力為1000,最高生命為3000,基礎攻擊力為50
快捷鍵 | 技能名稱 | 技能效果 |
---|---|---|
1 | 治愈 | 消耗100內力,生命值恢復最大生命值的10% |
2 | 金剛掌 | 消耗50內力,對遠程目標造成基礎攻擊+50點傷害 |
3 | 麻痹數 | 消耗50內力,禁止目標攻擊三個回合 |
4 | 鷹抓功 | 10個回合內,對目標造成傷害將恢復傷害量20%的內力傷害量60%的生命 |
5 | 絕處逢生 | 消耗100內力,對目標造成基礎攻擊+已損失血量的傷害 |
6 | 易筋經 | 消耗300內力,將內力和生命值進行互換,攻擊力提高1000% |
#include <iostream>
#include <conio.h>
struct Role
{
int Hp;
int maxHp;
int Mp;
int maxMp;
int act; //攻擊力
int cantact; //禁止攻擊
int bufcount; //回合
bool cant;
};
int main()
{
int inkey, damage;
Role user{ 3000,3000,1000,1000,50,0,false };
Role boss{ 30000,30000,1000,1000,190,0,false };
int* pUserHp = &user.Hp; //使用指針取值代替人物血量
int* pBossHp = &boss.Hp; //使用指針取值代替boss血量
lfight:
system("cls");
printf("生命[%d/%d] BOSS生命[%d/%d]\n", *pUserHp, user.maxHp, *pBossHp, boss.maxHp);
printf("內力[%d/%d] 攻擊力[%d]\n", *pUserHp, user.maxMp, user.act);
printf("請輸入你的技能:");
inkey = _getch();
damage = 0;
switch (inkey)
{
case 49:
if (*pUserHp > 99)
{
*pUserHp -= 100;
user.Hp += 300;
user.Hp = user.Hp > user.maxHp ? user.maxHp : user.Hp;
}
break;
case 50:
if (*pUserHp >= 50)
{
*pUserHp -= 50;
user.Hp -= 50 + user.act;
}
break;
case 51:
if (*pUserHp >= 50)
{
*pUserHp -= 50;
boss.cantact = 3;
}
break;
case 52:
user.bufcount = 10;
break;
case 53:
if (*pUserHp >= 100)
{
pUserHp -= 100;
damage = user.maxHp - user.Hp + user.act;
}
break;
case 54:
if ((*pUserHp >= 300) && (!user.cant))
{
int ls = user.maxHp;
user.maxHp = user.maxMp;
user.maxMp = ls;
ls = user.Hp;
user.Hp = *pUserHp;
*pUserHp = ls;
user.act *= 10;
user.cant = true;
}
break;
}
if (boss.cantact > 0)
{
boss.cantact--;
}
else user.Hp -= boss.act;
*pBossHp -= damage;
if (user.bufcount > 0)
{
user.bufcount--;
user.Hp += damage * 0.6;
*pUserHp += damage * 0.2;
user.Hp = user.Hp > user.maxHp ? user.maxHp : user.Hp;
*pUserHp = *pUserHp > user.maxMp ? user.maxMp : *pUserHp;
}
if (user.Hp < 1)
{
system("cls");
printf("你死了,游戲結束!!");
}
else if (*pBossHp < 1)
{
system("cls");
printf("擊敗BOSS,游戲結束!!");
}
else goto lfight;
}