C++指標詳解
指標是啥
眾所周知,你在程式中向計算機申請一個變數(如int a
),計算機會分配給你的變數一個空間。用int a
舉例子,計算機就給你了一個名叫a的房子,只能在裡面放int型別的值。當然,你可以直接寫a=10
,這樣計算機會幫你找到名叫a的房子,把10放進去。
那麼這個房子在哪呢?就有了地址,每個變數都會有一個地址(即它在記憶體中的位置),指標,便是一根棍子,指著這個位置,指標裡存的就是這個地址。
怎麼用
宣告
在型別後面加上*
即可,當然指標也是分型別的,如:
int* p;//一個指向(還沒確定指向哪裡)int型別房子的指標p
double* k;//指向double的k
如果你不確定這個型別,可以用void:
void* p;
但是,你如果這樣寫:
int* p,q;
事實上你是定義了一個指標和一個普通變數,所以我通常這樣寫:
int *p,*q;
注意定義指標後指標會指向一個隨機的位置,如果你對這個位置進行操作,就有可能發生記憶體錯誤。
操作
存入地址
這裡需要用到一個新運算子:&
,它稱為取地址符,用於獲取一個變數的地址。
例如:
int *p;
int a;
p=&a;
你應該發現,scanf後面如果輸入一個變數要用&
,所以scanf後面的引數就是一個地址。
你甚至可以輸出一個地址,如果用cout就直接輸出p即可,用printf需要用到格式控制符%p
int a;
scanf("%d",&a);
printf("%p",&a);
每個電腦上的結果可能會不一樣,例如我的:
輸出該地址上的值
如果你想知道指標所指向位置的值,需要再次用到*
。
int *p;
int a;
p=&a;
a=10;
printf("%d\n",*p);
a=15;
printf("%d\n",*p);
執行結果是什麼呢?
不難理解,因為一個變數的地址永遠不會變。
加/減
你可能會想,指標是不是也有+,-操作呢?答案是肯定的,但不是把這個位置的值+,-,而是把地址往後或往前移動。
可以做一個實驗:
#include<cstdio>
int main()
{
int a=1,*p,*q;
p=&a;
q=p+1;
printf("%p %p",p,q);
}
結果:
為什麼多了4呢?因為int型別是4個位元組,所以加int型別的1
會往後移4個位元組。
到這裡很想問了,那加char型別的'1'
會怎麼樣?
#include<cstdio>
int main()
{
int a=1,*p,*q;
p=&a;
q=p+'1';
printf("%p %p",p,q);
}
結果:
並不是只移了1位,因為這裡的指標q是int型別的,你的'1'
被自動轉為了ASCII碼,所以後移了很多位。
應該這樣做:
#include<cstdio>
int main()
{
char a='1',*p,*q;
p=&a;
q=p+1;
printf("%p %p",p,q);
}
這裡的+1
就成了char的後移一位,也就是1個位元組,結果:
指標陣列
很好理解:
int *p[10];
宣告一個有十個指標(p[0],p[1],p[2],…,p[9])的陣列,每個元素都是一個指標。
其他
scanf
要輸入n(n≤1000000)個數存入a陣列再輸出,一般我們會這樣寫:
#include<cstdio>
#define MAXN 1000000
int a[MAXN+5],n;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
printf("%d ",a[i]);
}
知道了指標,我們有了更裝逼的方法:
#include<cstdio>
#define MAXN 1000000
int a[MAXN+5],n;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",a+i);
for(int i=1;i<=n;i++)
printf("%d ",*(a+i));
}
結果:
指標與其他資料結構
指標與陣列
我們知道,當int a[10]
時,系統會連續開10個空間,所以我們可以這樣訪問陣列的元素:
#include<cstdio>
int main()
{
int a[10]={0,2,4,6,8,10,12,14,16,18};
int *p;
for(p=&a[0];p<&a[0]+10;p++)
printf("%d ",*p);
}
結果就不截圖了。
其中有一個:p<&a[0]+10
是什麼意思呢?
不難理解,10是陣列的大小,&a[0]是陣列的首地址,&a[0]+10就是陣列末尾地址的下一位。
這裡的p還有一個名稱,叫做陣列的迭代器,我們不說這些東西。
其實獲取陣列首地址還有一個辦法,直接這樣:
int a[10];
int *p;
p=a;
這樣就可以了,事實上,陣列你可以看成一個指標,可以這樣寫:
#include<cstdio>
int a[10]={0,2,4,6,8,10,12,14,16,18};
int main()
{
int *p;
p=a;
printf("%d\n",*a);
printf("%d\n",*(a+5));//注意加括號,否則就是輸出“a指向的值加5”了
printf("%d\n",*p);
printf("%d\n",*(++p));
}
結果:
相反的,指標也可以看做一個數組,所以經常有這樣寫的:
int sum(int *a,int n)
{
int ans=0;
for(int i=1;i<=n;i++)
ans+=a[i];
return ans;
}
那麼可以這樣用:
#include<cstdio>
int sum(int *a,int n)
{
int ans=0;
for(int i=1;i<=n;i++)
ans+=a[i];
return ans;
}
int main()
{
int A[10],n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&A[i]);
printf("%d\n",sum(A,n));
}
結果:
函式指標
你需要知道,在一個程式中,不僅僅是變數需要分配記憶體,函式也一樣,那麼函式自然也可以有指標,是函式的入口地址。函式指標宣告只比函式宣告多一個*
和一對括號,例如:
int (*Psum)(int*,int)
其中吧*Psum
括起來的括號一定不能少,不然編譯器會認為你聲明瞭一個叫Psum的函式,返回型別是int*。
也就是說可以這樣用:
#include<cstdio>
int (*Psum)(int*,int);
int sum(int *a,int n)
{
int ans=0;
for(int i=1;i<=n;i++)
ans+=a[i];
return ans;
}
int main()
{
int A[10],n;
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&A[i]);
Psum=sum;//不用'&',因為這裡函式沒有括號視為是函式的地址
printf("%d\n",(*Psum)(A,n));
}
到了這裡,我們就知道sort是如何把函式作為函式的引數的了,可以擬出一個sort(只針對int陣列):
void sort(int *begin,int *end,bool (*cmp)(int,int))
{
/*......*/
}
結構體指標
和宣告一般指標一樣,名稱前加*
即可。
要訪問這個指標所指向結構體當中的元素有2種方法:
1.’*’法:在指標前加上*
,再把它們括起來,就可以當該指標指向的結構體用了。
2.’->’法,和一般結構體用法一樣,只是把成員運算子.
變為->
即可。
詳見示例:
#include<cstdio>
struct student
{
int snum;
int age,grade;
};
student T;
student *p;
int main()
{
p=&T;
T.snum=15;
T.age=12;
T.grade=90;
printf("%d\n",(*p).snum);
printf("%d\n",p->age);
printf("%d\n",T.grade);
}
結果:
多重指標
和陣列一樣,你可以在宣告時連續打2個(或多個)*
,例如:
#include<cstdio>
int main()
{
int **p,*q;//p就是指向一個指標的指標,q是指向一個普通變數的指標
int a;
a=1;
q=&a;
p=&q;
printf("%p %p %p %p\n",p,*p,q,&a);
printf("%d %d %d",**p,*q,a);
}
猜猜結果:
前面的地址有可能會不一樣,但輸出的格式肯定是:
Q P P
A A A
為什麼*p
要用%p
輸出?因為*p
是q
,而q
是一個指標,所以用%p
。