十一、指針和引用(一)

来源: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
  • 下麵是一個標準的IDistributedCache用例: public class SomeService(IDistributedCache cache) { public async Task<SomeInformation> GetSomeInformationAsync (string na ...
  • 這個庫提供了在啟動期間實例化已註冊的單例,而不是在首次使用它時實例化。 單例通常在首次使用時創建,這可能會導致響應傳入請求的延遲高於平時。在註冊時創建實例有助於防止第一次Request請求的SLA 以往我們要在註冊的時候實例單例可能會這樣寫: //註冊: services.AddSingleton< ...
  • 最近公司的很多項目都要改單點登錄了,不過大部分都還沒敲定,目前立刻要做的就只有一個比較老的項目 先改一個試試手,主要目標就是最短最快實現功能 首先因為要保留原登錄方式,所以頁面上的改動就是在原來登錄頁面下加一個SSO登錄入口 用超鏈接寫的入口,頁面改造後如下圖: 其中超鏈接的 href="Staff ...
  • Like運算符很好用,特別是它所提供的其中*、?這兩種通配符,在Windows文件系統和各類項目中運用非常廣泛。 但Like運算符僅在VB中支持,在C#中,如何實現呢? 以下是關於LikeString的四種實現方式,其中第四種為Regex正則表達式實現,且在.NET Standard 2.0及以上平... ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他們的程式記憶體會偶發性暴漲,自己分析了下是非托管記憶體問題,讓我幫忙看下怎麼回事?哈哈,看到這個dump我還是非常有興趣的,居然還有這種游戲幣自助機類型的程式,下次去大玩家看看他們出幣的機器後端是不是C#寫的?由於dump是linux上的程式,剛好win ...
  • 前言 大家好,我是老馬。很高興遇到你。 我們為 java 開發者實現了 java 版本的 nginx https://github.com/houbb/nginx4j 如果你想知道 servlet 如何處理的,可以參考我的另一個項目: 手寫從零實現簡易版 tomcat minicat 手寫 ngin ...
  • 上一次的介紹,主要圍繞如何統一去捕獲異常,以及為每一種異常添加自己的Mapper實現,並且我們知道,當在ExceptionMapper中返回非200的Response,不支持application/json的響應類型,而是寫死的text/plain類型。 Filter為二方包異常手動捕獲 參考:ht ...
  • 大家好,我是R哥。 今天分享一個爽飛了的面試輔導 case: 這個杭州兄弟空窗期 1 個月+,面試了 6 家公司 0 Offer,不知道問題出在哪,難道是杭州的 IT 崩盤了麽? 報名面試輔導後,經過一個多月的輔導打磨,現在成功入職某上市公司,漲薪 30%+,955 工作制,不咋加班,還不捲。 其他 ...
  • 引入依賴 <!--Freemarker wls--> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency> ...
  • 你應如何運行程式 互動式命令模式 開始一個互動式會話 一般是在操作系統命令行下輸入python,且不帶任何參數 系統路徑 如果沒有設置系統的PATH環境變數來包括Python的安裝路徑,可能需要機器上Python可執行文件的完整路徑來代替python 運行的位置:代碼位置 不要輸入的內容:提示符和註 ...