十一、指針和引用(一)

来源: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 微服務框架,幫助我們輕鬆構建和管理微服務應用。 本框架不僅支持 Consul 服務註 ...
  • 先看一下效果吧: 如果不會寫動畫或者懶得寫動畫,就直接交給Blend來做吧; 其實Blend操作起來很簡單,有點類似於在操作PS,我們只需要設置關鍵幀,滑鼠點來點去就可以了,Blend會自動幫我們生成我們想要的動畫效果. 第一步:要創建一個空的WPF項目 第二步:右鍵我們的項目,在最下方有一個,在B ...
  • Prism:框架介紹與安裝 什麼是Prism? Prism是一個用於在 WPF、Xamarin Form、Uno 平臺和 WinUI 中構建鬆散耦合、可維護和可測試的 XAML 應用程式框架 Github https://github.com/PrismLibrary/Prism NuGet htt ...
  • 在WPF中,屏幕上的所有內容,都是通過畫筆(Brush)畫上去的。如按鈕的背景色,邊框,文本框的前景和形狀填充。藉助畫筆,可以繪製頁面上的所有UI對象。不同畫筆具有不同類型的輸出( 如:某些畫筆使用純色繪製區域,其他畫筆使用漸變、圖案、圖像或繪圖)。 ...
  • 前言 嗨,大家好!推薦一個基於 .NET 8 的高併發微服務電商系統,涵蓋了商品、訂單、會員、服務、財務等50多種實用功能。 項目不僅使用了 .NET 8 的最新特性,還集成了AutoFac、DotLiquid、HangFire、Nlog、Jwt、LayUIAdmin、SqlSugar、MySQL、 ...
  • 本文主要介紹攝像頭(相機)如何採集數據,用於類似攝像頭本地顯示軟體,以及流媒體數據傳輸場景如傳屏、視訊會議等。 攝像頭採集有多種方案,如AForge.NET、WPFMediaKit、OpenCvSharp、EmguCv、DirectShow.NET、MediaCaptre(UWP),網上一些文章以及 ...
  • 前言 Seal-Report 是一款.NET 開源報表工具,擁有 1.4K Star。它提供了一個完整的框架,使用 C# 編寫,最新的版本採用的是 .NET 8.0 。 它能夠高效地從各種資料庫或 NoSQL 數據源生成日常報表,並支持執行複雜的報表任務。 其簡單易用的安裝過程和直觀的設計界面,我們 ...
  • 背景需求: 系統需要對接到XXX官方的API,但因此官方對接以及管理都十分嚴格。而本人部門的系統中包含諸多子系統,系統間為了穩定,程式間多數固定Token+特殊驗證進行調用,且後期還要提供給其他兄弟部門系統共同調用。 原則上:每套系統都必須單獨接入到官方,但官方的接入複雜,還要官方指定機構認證的證書 ...
  • 本文介紹下電腦設備關機的情況下如何通過網路喚醒設備,之前電源S狀態 電腦Power電源狀態- 唐宋元明清2188 - 博客園 (cnblogs.com) 有介紹過遠程喚醒設備,後面這倆天瞭解多了點所以單獨加個隨筆 設備關機的情況下,使用網路喚醒的前提條件: 1. 被喚醒設備需要支持這WakeOnL ...
  • 前言 大家好,推薦一個.NET 8.0 為核心,結合前端 Vue 框架,實現了前後端完全分離的設計理念。它不僅提供了強大的基礎功能支持,如許可權管理、代碼生成器等,還通過採用主流技術和最佳實踐,顯著降低了開發難度,加快了項目交付速度。 如果你需要一個高效的開發解決方案,本框架能幫助大家輕鬆應對挑戰,實 ...