十一、指針和引用(一)

来源: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
  • 1、預覽地址:http://139.155.137.144:9012 2、qq群:801913255 一、前言 隨著網路的發展,企業對於信息系統數據的保密工作愈發重視,不同身份、角色對於數據的訪問許可權都應該大相徑庭。 列如 1、不同登錄人員對一個數據列表的可見度是不一樣的,如數據列、數據行、數據按鈕 ...
  • 前言 上一篇文章寫瞭如何使用RabbitMQ做個簡單的發送郵件項目,然後評論也是比較多,也是準備去學習一下如何確保RabbitMQ的消息可靠性,但是由於時間原因,先來說說設計模式中的簡單工廠模式吧! 在瞭解簡單工廠模式之前,我們要知道C#是一款面向對象的高級程式語言。它有3大特性,封裝、繼承、多態。 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 介紹 Nodify是一個WPF基於節點的編輯器控制項,其中包含一系列節點、連接和連接器組件,旨在簡化構建基於節點的工具的過程 ...
  • 創建一個webapi項目做測試使用。 創建新控制器,搭建一個基礎框架,包括獲取當天日期、wiki的請求地址等 創建一個Http請求幫助類以及方法,用於獲取指定URL的信息 使用http請求訪問指定url,先運行一下,看看返回的內容。內容如圖右邊所示,實際上是一個Json數據。我們主要解析 大事記 部 ...
  • 最近在不少自媒體上看到有關.NET與C#的資訊與評價,感覺大家對.NET與C#還是不太瞭解,尤其是對2016年6月發佈的跨平臺.NET Core 1.0,更是知之甚少。在考慮一番之後,還是決定寫點東西總結一下,也回顧一下.NET的發展歷史。 首先,你沒看錯,.NET是跨平臺的,可以在Windows、 ...
  • Nodify學習 一:介紹與使用 - 可樂_加冰 - 博客園 (cnblogs.com) Nodify學習 二:添加節點 - 可樂_加冰 - 博客園 (cnblogs.com) 添加節點(nodes) 通過上一篇我們已經創建好了編輯器實例現在我們為編輯器添加一個節點 添加model和viewmode ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...
  • 類型檢查和轉換:當你需要檢查對象是否為特定類型,並且希望在同一時間內將其轉換為那個類型時,模式匹配提供了一種更簡潔的方式來完成這一任務,避免了使用傳統的as和is操作符後還需要進行額外的null檢查。 複雜條件邏輯:在處理複雜的條件邏輯時,特別是涉及到多個條件和類型的情況下,使用模式匹配可以使代碼更 ...
  • 在日常開發中,我們經常需要和文件打交道,特別是桌面開發,有時候就會需要載入大批量的文件,而且可能還會存在部分文件缺失的情況,那麼如何才能快速的判斷文件是否存在呢?如果處理不當的,且文件數量比較多的時候,可能會造成卡頓等情況,進而影響程式的使用體驗。今天就以一個簡單的小例子,簡述兩種不同的判斷文件是否... ...
  • 前言 資料庫併發,數據審計和軟刪除一直是數據持久化方面的經典問題。早些時候,這些工作需要手寫複雜的SQL或者通過存儲過程和觸發器實現。手寫複雜SQL對軟體可維護性構成了相當大的挑戰,隨著SQL字數的變多,用到的嵌套和複雜語法增加,可讀性和可維護性的難度是幾何級暴漲。因此如何在實現功能的同時控制這些S ...