十一、指針和引用(一)

来源:https://www.cnblogs.com/piaolaipiaoqu/archive/2023/11/17/17839930.html
-Advertisement-
Play Games

十一、指針和引用(一) 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;
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 日常開發中,我們經常遇到過tooltip這種需求。文字溢出、產品文案、描述說明等等,每次都需要寫一大串代碼,那麼有沒有一種簡單的方式呢,這回我們用指令來試試。 功能特性 支持tooltip樣式自定義 支持tooltip內容自定義 動 ...
  • 小程式作為目前一種輕量、便捷的應用、目前應用越來越廣泛了。 很多沒有開發經驗的開發同學可能初次接觸就是小程式開發,為了詳細講解下小程式開發的步驟,我會按照小程式的開發流程一步一步從零開始給大家介紹下如何開發支付寶小程式,後續教程中會更新最新版 demo 給到大家。 ...
  • 一、項目背景 公司和第三方合作開發一個感測器項目,想要通過電腦或者手機去控制項目現場的感測器控制情況。現在的最大問題在於,現場的邊緣終端設備接入的公網方式是無線接入,無法獲取固定IP,所以常規的HTTP協議通信就沒法做,現在打算使用MQTT來實現雲平臺和邊緣終端(感測器)之間的雙向通信。 二、術語定 ...
  • 一個有必要實現的需求 因為項目中需要使用canvasTexture(一個threejs3d引擎中的材質類型),繪製大量的圖片,每次使用都會請求大量的oss圖片資源,雖然重覆請求會有磁碟緩存但畢竟這個磁碟緩存時效過短, 這裡需要瞭解一下知識才能正常閱讀。 Transferable objects ht ...
  • 遇到的問題:將長度為40的數組數據賦值<el-table></el-table>,我發現loading沒有效果,後面發現是頁面卡住了,loading直接沒有出現。 經過查詢資料,發現<el-table>會有卡頓的問題,看到有的博主推薦使用一款叫umy-ui的插件,我就試了試,發現卡頓的問題解決了。 ...
  • 由於我司的業務特性,需要 APP 能夠支持即時在無網路的場景下,也能夠正常使用 APP 的功能 那麼,為了讓一個用 web 前端實現的 APP 能夠在無網路的場景下,也能夠正常運行程式,這其中的離線方案就需要實現幾個關鍵點: 代碼的離線、更新 數據的下載、上傳、更新 本篇就想來講一講,我們在離線應用 ...
  • 微服務架構可以更快地推出新產品,幫助產品更輕鬆地擴展,並更好地響應客戶需求。憑藉多種現代數據模型、在任何情況下的容錯性、用於隔離的多租戶功能以及在多個環境中部署的靈活性,Redis Enterprise 使開發人員和運營商能夠針對微服務架構優化他們的數據層。 ...
  • Java解析上傳的zip文件--包含Excel解析與圖片上傳 前言:今天遇到一個需求:上傳一個zip格式的壓縮文件,該zip中包含人員信息的excel以及excel中每行對應的人的圖片,現在需要將該zip壓縮包中所有內容解析導入到資料庫中,包括圖片,並將圖片與excel內容對應。 代碼演示: /** ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...