第四章總結(三)指針初探

指針類型

下面通過代碼進行講解

#include<iostream>
using namespace std;


int main()
{
	int* pt = new int;
	double* ptt = new double;
	cout << pt << endl;
	cout << ptt << endl;
	
	*pt = 100.00;
	*ptt = 12.1234;

	cout << *pt << endl;
	cout << *ptt << endl;
	return 0;
}

在這裏插入圖片描述
這裏可以看到,我們通過new申請得到的兩塊地址的長度是一致的,但是爲什麼在cout的時候,編譯器知道該輸出int還是輸出double呢?這就與指針的類型有關了
我們在new int的時候,new找到一個長度爲四字節的內存塊,把起始地址放在pt中,ptt也是同理,,由於地址在計算機中的表示形式都是一樣的,最起碼在同一臺計算機中是這樣的,所以pt和ptt存儲的內容長度都一致,但是由於pt在定義的時候就定義成了int *類型的變量,所以在cout<<*pt的時候,編譯器就知道,從pt存儲的那塊地址開始的四個字節按照int的方式進行解釋

delete

delete用來釋放new申請得到的空間,注意 是new 得到的空間,另外new和delete應該成對出現,然後不要對同一個指針,因爲第一次delete已經把指針所保存的空間歸還給操作系統了,連續兩次delete雖然不會報錯,但是會返回錯誤的結束碼.
對於數組的申請
int *pt = new int[5]; delete [] pt;

指針和數組

基本操作

直接講解書中代碼,進行引出

#include<iostream>
using namespace std;


int main()
{	
	double wages[3]{ 1000.0, 2000.0, 3000.0 };
	short stacks[3]{ 3,2,1 };

	double* pw = wages;
	short* ps = &stacks[0];
	short(* ps_2)[3] = &stacks;
	
	cout << "pw= " << pw << " *pw=" << *pw << endl;
	pw = pw + 1;
	cout << "after add 1 to pw\n";
	cout << "pw= " << pw << " *pw=" << *pw << endl;
	cout << "ps= " << ps << " *ps=" << *ps << endl;
	ps = ps + 1;
	cout << "after add 1 to ps\n";
	cout << "ps= " << ps << " *ps=" << *ps << endl;
	cout << "*(stacks+1)=" << *(stacks + 1) << endl;

	cout << "sizeof(wages):" << sizeof(wages) << endl;
	cout << "sizeof(ps):" << sizeof(ps) << endl;

	cout << "&stacks[2]=" << &stacks[2] << endl;
	cout << "ps_2+1=" << ps_2 + 1 << endl;
	return 0;
}

運行結果
pw= 010FFAB4 *pw=1000
after add 1 to pw
pw= 010FFABC *pw=2000
ps= 010FFAA4 *ps=3
after add 1 to ps
ps= 010FFAA6 *ps=2
*(stacks+1)=2
sizeof(wages):24
sizeof(ps):4
&stacks[2]=010FFAA8
ps_2+1=010FFAAA

對結果進行解釋,由於C++一般情況下將數組名結束爲數組的第一個元素的地址(特殊情況下面會講),所以pw的值也就是&wages[0],所以pw也就是wages[0],然後pw=pw+1,對指針變量加1後,其增加的值等於指向的類型佔用的字節數,這點很重要!!! 所以這裏pw的值和pw都編程了wages[1]的了
指針ps的結果和pw一致,然後對於 *(stacks + 1) ,可以看出這種寫法與stacks[1]的結果一致,所以大多數情況下,可以用相同的方式使用指針名和數組名,所以ps[0]這種寫法也可以得到stacks[0]的值

最後的兩個sizeof的結果,如果是sizeof(wages),得到的結果就是元素的大小*數組的長度,如果是sizeof(指針)的話,得到的結果與編譯器或操作系統的位數有關,在vs2019中,如果最上面設置的是x86,則指針大小得到4,如果是x64,則得到8

不解釋爲首元素地址的特殊情況

  1. 對數組取地址
    同時也是上面這段代碼最後一個需要注意的地方,就是short(* ps_2)[3] = &stacks;,我們這裏對數組進行了取地址,這時就會得到整個數組的地址,由於整個數組是一個有着3個short元素的數組,雖然ps_2和&stacks[0](也就是stack)的值是一樣的,但是從概念上說,&stacks[0]是一個4字節的內存塊的地址,而ps_2是一個12字節的內存塊的地址,因此最後將ps_2+1得到的地址比&stacks[2]還大了四個字節

指針數組和數組指針的簡單理解

所以這裏把ps_2聲明成了 short (*)[3],這是一個數組指針,這個怎麼理解呢,由於括號的優先級,所以ps_2先和星號結合,成爲一個指針,然後指向的元素是有着三個元素的short數組(數組指針,首先是個指針,指向一個數組,所以叫數組指針)
然後如果我們去掉括號的話,就成了 short *ps_2[3],ps_2將先和[3]結合,所以就解釋爲了,ps_2是一個數組,數組的元素是指向short類型的指針,先是一個數組,再是一個指針,所以叫它指針數組

指針和字符串

同樣也是講解書中代碼,然後引出相關知識

#include<iostream>
using namespace std;


int main()
{	
	char animal[20]{ "bear" };
	const char* bird = "wren";
	char* ps;
	cout << animal << " and " << bird << endl;
	//cout << ps << endl; 編譯錯誤,使用了未初始化的局部變量ps
	//cout << ps << endl; 如果把ps初始化爲nullptr,則運行不會報錯,但是會返回錯誤的結束碼

	cout << "Enter a kind of animal:";
	cin >> animal;

	ps = animal;
	cout << ps << "!\n";
	cout << "Before using strcpy():\n";
	cout << animal << " at " << (int*)animal << endl;
	cout << ps << " at " << (int*)ps << endl;

	ps = new char[strlen(animal) + 1];
	strncpy_s(ps,strlen(animal) + 1,animal,strlen(animal));
	cout << "after use:\n";
	cout << animal << " at " << (int*)animal << endl;
	cout << ps << " at " << (int*)ps << endl;
	delete[] ps;
	return 0;
}

bear and wren
Enter a kind of animal:tigger
tigger!
Before using strcpy():
tigger at 00BFFE4C
tigger at 00BFFE4C
after use:
tigger at 00BFFE4C
tigger at 00EC4968
  1. 爲什麼"wren"一定要通過const char *去存儲?
    因爲"wren"實際表示的是字符串的地址,因此將地址只能賦給指針,而且由於字符串字面值是常量,所以要是const,表示 bird 是一個指針,指向一個const char的數據,不能對指針指向的數據進行修改
  2. 一般來說,提供給cout一個指針,他將打印地址,但如果指針類型爲char *,則cout將顯示指向的字符串,但如果要顯示地址得話,就需要想上面一樣轉換一下再輸出
  3. 如果要進行字符串副本的話,不能直接用 = 因爲這樣實際上是讓兩個指針指向了一個地址,就像上面 ps =animal之後的結果,兩個的地址一樣, 這樣並不安全,通常的做法是 先給ps開闢空間,然後用strcpy函數進行拷貝
    當然,如果使用string的話,就避免了這些問題,這些就不用我們考慮了,之前有介紹string的自動擴容
  4. C++不保證字符串字面值被唯一的存儲,也就是說如果在程序中多次使用了 “bear” 這個字符串常量,則編譯器可能存儲該字符串的多個副本,也可能只存儲一個副本,這種與編譯器有關的我們就不做深究了,我在vs2019和DEV C++上測試,都是隻存儲了一個

堆,棧和內存泄漏

#include<iostream>
using namespace std;


int main()
{	
	{
		int* a = new int;
	}
	delete a; //直接報錯,因爲a的作用域已經結束了
	return 0;
}

書上說的就是這麼兩漢代碼,我們在{}中 new 了一個空間給a,但是花括號結束也就代表着出了a的作用域,在括號外就無法通過a去delete了

二級指針初探

#include<iostream>
using namespace std;

struct mytest {
	int test;
};
int main()
{	
	mytest t1, t2, t3;
	t1.test = 1996;
	t2.test = 1997;
	t3.test = 1998;
	mytest * arp[3] = { &t1, &t2, &t3 };
	mytest* *pd = arp;
	cout << pd[0]->test << endl;//1996
	cout << (*(pd + 0))->test << endl;//1996
	cout << (*pd)[0].test << endl;//1996
	cout << pd[0]->test << endl;//1996
	
	cout << arp[0]->test << "--" << (*(arp + 0))->test << "--" << (*(*(arp + 0))).test << endl;//1996--1996--1996
}

由於arp是一個指針數組,所以他的每一個元素都是指針,所以arp[0]就需要用間接成員運算符也就是箭頭去訪問成員,由於前面介紹過 arp[0] 和 (arp+0)是一回事,但是最後那個((*(arp + 0)))就直接拿到的是這個結構體,所以可以使用 . 去訪問元素

然後arp由於是一個數組的名稱,因此它是第一個元素的地址,但其第一個元素爲指針,所以pd就是一個指針變量,它指向一個mytest 的指針,所以*pd就是結構體指針,需要用箭頭去訪問,然後(*pd)[0].test相當於做了兩次 * 號操作,所以可以直接用 . 去訪問

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章