之前我們介紹了一下指針的定義和簡單應用,在此基礎上,我們要做出一些進步~
I have a 函數,I have a 指針,啊~然後。。。盤ta!!!
首先,我們需要明確一件很重要的事情:
函數傳參數,傳指針,傳引用到底有什麼區別???
傳參數
簡單的參數傳遞,相當於將變量copy了一下。傳到函數中參與運算
這就像複印文件,不管對複印件進行什麼樣的騷操,原件始終如一(除非你對原件進行毀滅性破壞【大霧)
可以說,傳參數是對主程序變量的一個有效保護
#include<iostream>
using namespace std;
void doit(int n) {
n=n*n;
}
int main() {
int n=5;
doit(n);
cout<<n<<endl;
system("pause");
return 0;
}
/*
輸出:5
*/
傳指針
當一個函數的參數是指針時,指向的是傳入參數的地址
所以函數調用和函數內部使用規則都需要嚴格遵守指針使用規範
然而這就導致在進行運算時比較繁瑣(講真就是代碼比較醜/哭)
#include<iostream>
using namespace std;
void doit(int *p) { //參數變成了int *指針類型
*p=*p * *p; //*p表示取用p指向的變量
}
int main() {
int n=5;
doit(&n); //取地址傳入
cout<<n<<endl;
system("pause");
return 0;
}
/*
輸出:25
*/
傳引用
傳引用是C++特有的操作,C中並沒有
指針和引用最大的區別:指針開闢了一塊內存空間,專門用一塊內存存儲變量的地址;而引用是個原有的內存空間起一個別名,別名和原名公用一個地址
#include<iostream>
using namespace std;
void doit(int &p) { //傳遞引用(別名)
p=p*p;
}
int main() {
int n=5;
doit(n);
cout<<n<<endl;
system("pause");
return 0;
}
/*
輸出:25
*/
在這裏我簡單解釋一下引用(別名):
int a=5;
int &_a=a;
_a=_a*_a;
cout<<a<<endl;
/*
輸出:25
*/
可以看到,在程序中我給 a 起了一個別名 _a (看清楚定義方式哦)
int &_a=a;
要格外注意的是,在定義時就要完成對於_a的初始化
動態分配和釋放內存
動態分配——new
在C++程序中,所有內存需求都是在程序執行之前通過定義所需的變量來確定的。但是可能存在程序的內存需求只能在運行時確定的情況。在這些情況下,程序就需要動態分配內存。
在這裏我就非常想@那些想要在程序裏用變量(非常量)定義數組大小的寶貝們,你們的福音來了~
new 常用格式:
<指針變量>=new<數據類型>
幾種使用形式:
- 給單個對象申請分配內存
- 給單個變量申請分配內存的同時初始化該變量
- 同時給多個變量申請分配內存
一定要注意申請的方式哦!!!
int *p; //定義指針
p=new int; //申請指向int類型的指針
*p=9 //初始化
int *p;
p=new int(8); //申請一個指向int類型的指針,變量初始化爲8
又來插一句: 括號出現,基本上是三種情況:函數,類(模板),對象(構造函數初始換對象)
這裏的括號可以視爲是進行對象的初始化
int *p;
p=new int[5]; //申請一個長度爲5的int類型數組
//-------------------------------------------
int *p;
int n;
cin>>n;
p=new int[n]; //用變量(非常量)定義數組大小
需要注意的是,用new分配內存時不一定能申請成功
畢竟針對每一個程序計算機分配的內存是有上限的,不可能滿足在程序內部無止盡動態申請內存
在以下的實驗中,我們不停申請大小爲102410244字節的數組,計算機最多允許申請大概400個
#include<iostream>
#include<new> //for "bad_alloc" exception
#include<cstdlib>
using namespace std;
int main()
{
int *ptr,t=0;
try {
while(1) {
ptr=new int[1024*1024];
memset(ptr,0,sizeof(ptr));
cout<<++t<<endl;
}
}
catch (bad_alloc e)
{
cout<<"No sufficient memory to alloc!\n";
exit(1);
}
system("pause");
return 0;
}
釋放內存——delete
當程序不再需要由new分配的內存空間時,我們可以使用delete釋放這些空間
delete 常用格式:
- delete<指針變量>
- delete[]<指針變量>
int *p;
p=new int;
*p=9;
delete p;
int *p;
p=new int(8);
delete p;
int *p;
p=new int[5];
delete []p;
打起精神來注意啦:
- 用 new 運算符申請分配內存的內存空間,必須用delete釋放
- 對於一個已分配內存的指針,只能用 delete 釋放一次
- delete 作用的指針對象必須是由 new 分配內存空間的首地址
- 用 new 運算符爲多個對象申請內存空間時,不能提供初始化
- 如果指針爲 0 ,則對此進行 delete 是合法的
int *p=0; delete p;//okk
- 在 delete 後,被刪除指針變爲懸空指針,指向曾經存放對象的內存,但是該對象已經不存在
SIZEOF
之前我嘗試簡單介紹了一下sizeof的用法,那麼下面我就嘗試着 “ 不簡單的 ” 給大家再補充一點
sizeof 是用來計算佔用的以字節爲單位的內存空間
- 對於常量和變量名,我們呢在使用時是否帶括號可以選擇:
int number; sizeof(number); //siezof number
- 而對於類型名則必須帶括號:
sizeof(char); sizeof(Gradebook);
那麼sizeof與指針有什麼關係呢?
看下面的程序:
#include<iostream>
using namespace std;
int getSize(double *s) { return sizeof(s);} //int getSize(double s[])
int main()
{
double a[20];
cout<<"The number of bytes in a is "<<sizeof(a)<<endl;
cout<<"The number of bytes returned by getSize is "<<getSize(a)<<endl;
system("pause");
return 0;
}
/*
輸出:
The number of bytes in a is 160
The number of bytes returned by getSize is 4
*/
第一個得到的結果是 20(數組大小)* 8(double所佔字節)=160
但是第二個sizeof計算的實際上是數組a地址的大小
經過不懈的實驗,會發現無論什麼類型的數組,得到的結果都是4
(別問,問就是不會)
The number of bytes returned by getSize is 4
指針的運算
指針支持算術,賦值,比較運算
而指針的運算一般與數組結合使用
int v[5]={0};
int *vptr=&v[0];
//int *vptr=v;
最初vptr指向v[0]
我們在這裏需要注意一下數組元素的地址,因爲int佔四個字節,而數組在內存中一定佔據一塊連續的內存,所以每一位地址在數值上相差4(記錄方式採用十六進制)
指針原酸與數值算數運算存在很大區別:
數值運算:
int num=0x00AFFD90; num++; num==0x00AFFD91;
指針運算:
指針vptr指向元素v[0],值爲00AFFD90
vptr++運算後,指向v[1],值爲00AFFD94
指針運算(假設指向數據類型type的指針)時,+/-n表示前移/後移n個元素,其中n稱爲offset(偏移值)
從數值上看,指針的值是加/減了n*sizeof(type)
int v[5]={0};
int *vptr1=&v[1];
int *vptr2=&v[3];
下面看這個厲害的操作:
int x=vptr2-vptr1;
cout<<x;
/*
輸出:2
*/
兩個指針相加得到的偏移量,這就啓發我們get了一個計算數組大小的新思路
需要特別注意的是:兩個指針之間進行加法操作是毫無意義的
int num=1633837924;
int *pNum=#
char *pChar=(char *)pNum;
cout<<*pChar<<endl;
cout<<pChar<<endl;
cout<<*(++pChar)<<endl; //注意前置自增和後置自增的區別
輸出:
d
dcba
c
好啦,隔着屏幕看見你們mengbier的表情後,我來解釋一下到底發生了什麼:
- 首先,不同類型的指針式之間式不能進行賦值運算的,所以我們強制轉換,把int類型的指針轉換成了char類型的指針
int num=1633837924;
//int num=0x61626364
- 注意我給出的num,在十六進制下(計算機中地址的記錄方式統一使用十六進制)是 0x61626364
我們知道int類型佔用四個字節,每個字節是八位二進制,等價於兩位十六進制(2^8-1=255)
計算機內部存儲時,實際上是把一個二進制數字每八位爲一個單位(十六進制每兩位爲一個單位) 存入字節中,那麼61626364這一串數字在計算機中就變成了這樣:
|61|62|63|64|
轉爲十進制:
|97|98|99|100|
在強制轉化成字符之後,對應的字符分別爲:
|a|b|c|d| - 在轉化過程中,字符反序
(爲什麼?別問,問就是不知道。實踐出真知) - *ptr表示當前位的一個元素
通用指針
僅有相同類型的指針之間可以進行賦值運算,特例:通用指針void *
int num=0; int *ptr=# void *p=ptr;
任意類型指針均可以賦值給通用指針,反之不成立
通用指針僅用於保存地址值,不能進行解引用,不能進行算術運算
cout<<*p<<endl; //error C2100:illegal indirection
cout<<p+1<<endl; //error C2036:'void *':nukown size
if (ptr==0) //NULL
cout<<"error"<<endl;
else ...
int a[5]={1,2,3,4,5};
int *p=a;
do {
cout<<*(p++)<<' ';
}
while (p<=&a[4]);
用const修飾指針
一句話——就近原則:
constant pointer:指針常量,指針類型是常量
int * const p;
pointer to constant:常量指針,指向常量的指針
const int *p;
分類 | non-constant data | constant data |
---|---|---|
non-constant pointer | int * p1 | const int * p2 |
constant pointer | int * const p3 | const int * const p4 |
-
p1: Nonconstant Pointer to Nonconstant data
被指向變量的值可以改變,指針的指向也可以改變
-
p2: Nonconstant Pointer to Constant data
常量指針,被指向變量的值不可以改變,但是指針的指向可以改變
-
p3: Constant Pointer to Nonconstant data
指針常量,聲明時必須同時進行初始化,或作爲形參通過實參初始化
被指向變量的值可以改變,指針的指向不可以改變
int num=100; int * const p=#
-
p4: Constant Pointer to Constant data
指向常量的指針常量,常量指針+指針常量
#include<iostream>
using namespace std;
void printCharacters(const char *ptr) {
for (;*ptr!='\0';ptr++) cout<<*ptr;
//for (int i=0;*(ptr+i)!='\0';i++) cout<<*(ptr+i);
}
int main()
{
//const char phrase[]="print characters of a string";
const char *phrase="print characters of a string";
cout<<"The string is:\n";
printCharacters(phrase);
system("pause");
return 0;
}
在上面這個實驗中,我們需要注意以下兩種定義方式是不同的:
const char *phrase="print characters of a string";
聲明一個指向常量字符串的指針,也就是說指針和字符串分別佔據空間
const char phrase[]="print characters of a string";
定義一個常量字符串,佔用空間小於上一種定義方式
如果函數的定義變成這樣:
void printCharacters(const char * const ptr) {
for (;*ptr!='\0';ptr++) cout<<*ptr;
}
//error C2166:I-value specifies const object
那麼你就會很傷心的發現編譯失敗
因爲這時候我們的形參是一個指向常量字符串的指針常量,即指針本身也是一個常量,對於指針我們是不能進行運算操作的