C++指標變數的基本寫法
指標變數與應用——動態陣列
在C++中,有一種神奇的變數,它不可以表示一個值,但是可以表示某個元素的地址,通過地址來訪問這個元素。
打個比方:你有一張地圖和一個座標,你就可以通過訪問這個座標來達到你訪問座標所表示的元素的目的。指標變數就是這個“座標”。
下面我們來具體看看指標變數的應用。
1、指標變數的性質
正如上面所說,指標變數不可以表示一個值,但是可以指向別的元素的地址,通過這個地址來間接訪問這個元素的值。
由於它的性質,指標變數不可以直接=一個元素,賦值時要注意。
具體操作下面會講到。
2、指標變數的宣告
如何宣告一個指標變數? 有如下表達式:
資料型別+“*”+指標名
通常我們這樣賦值:
int main() { int *p=NULL; return 0; }
這樣我們就定義了一個指標型別的變數p,NULL是空記憶體,這個記憶體裡什麼元素都沒有,你可以之後再給p賦一個元素的地址。(可以不用=NULL,但是這是個人習慣,類似於return 0好習慣這種……)
這個語句的意義是:定義一個int型別的指標p,指向空地址。
那麼怎麼把一個元素的地址賦給一個指標變數呢?
有如下語句:
#include<cstdio> using namespace std;int main() { int a; int *p=NULL; p=&a; return 0; } /*int main() { int a; int *p=&a; return 0; }*/
上面兩個主函式的效果是一樣的。
我們說說這兩段程式碼的意義:
相信大家都用過scanf( ),在我們輸入變數名前要加一個符號“&”,這就是地址符,表示變數名的地址。
我們的指標要指向一個地址,當然就是:指標名=&變數名啦!
3、用指標變數呼叫元素值
既然我們會賦值了,下一步就是呼叫元素值,但是指標指向的是一個地址,不能直接參與運算,這時候我們要用到間接運算子“*”。(就是定義的時候那個星號)
如果我們有一個元素a,需要用指標來輸出它,怎麼操作?
對於這個問題,有如下程式:
#include<cstdio> using namespace std; int main() { int a; scanf("%d",&a); int *p=&a;//定義一個指標變數p指向元素a printf("%d",*p);//間接運算子+指標名錶示指標所指元素 return 0; }
程式碼註釋已經很詳盡了,我們的指標指向一個元素,用“*”+指標名即可訪問指標所指元素
注意:通過指標操作元素值和直接操作元素值是一樣的,比如下面這段程式碼:
#include<cstdio> using namespace std; int main() { int a,b,s,t,*pa=NULL,*pb=NULL; pa=&a,pb=&b; a=10,b=20; s=*pa+*pb; t=*pa**pb; printf("%d %d",s,t); return 0; }
程式給出的結果是30 200。
4、指標的+、-運算
首先我們給出一個基本的定義:
當我們定義陣列的時候,系統會給出連續的地址,比如a[5],假設a[0]的地址是0,那麼a[1]的地址就是1……以此類推。
此時,我們直接把地址+1(指標+1),就可以訪問陣列的下一個元素。
#include<cstdio> using namespace std; int main() { int a[5],*p=&a[0]; for(int i=0;i<5;i++) scanf("%d",&a[i]); for(int i=0;i<5;i++) { printf("%d ",*p); p++; } return 0; }
對於p--,同理。這個語句輸出了a[ ]中的所有變數。
5、指標與陣列
指向陣列的指標叫陣列指標,眾所周知,一個數組的地址是連續的,首地址就是他所佔有的幾個單元的首地址,我們可以把陣列名賦給指標,也可以把陣列中某個元素的地址賦給它。
有以下語句:
int a[5],*p=a;
則以下三個語句
&a[0],a,*p,均指向同一個單元——陣列的首地址。
那麼可以推導:&a[i]、a+i、p+i,均指向陣列a中a[i]的地址。
有如下程式碼:
#include<cstdio> using namespace std; int main() { int a[5],*pa=a; for(int i=0;i<5;i++) scanf("%d",&a[i]); for(int i=0;i<5;i++) printf("%d %d %d %d\n",*(pa+i),pa[i],a[i],*(a+i)); return 0; }
我們輸如5個數:1 2 3 4 5
系統給出了5行:
1 1 1 1
2 2 2 2
3 3 3 3
4 4 4 4
5 5 5 5
這說明上面4個語句:*(pa+i),pa[i],a[i],*(a+i)是等價的。
程式碼說明和注意事項:
1、a(陣列名)可以加“*”變為常量指標,a是開始元素,根據指標的加減原理,a+i是第i個元素的地址。
2、a是常量名,不能直接進行指標的+、-操作(這裡指的是p++、p--這種賦值操作非法,但是a+i這種是合法的),但是pa是指標變數,可以進行加減操作。
6、指標申請系統空間
我們用如下語句可以申請一個系統空間給指標p:
int *p=new(int);
此時*p的內容不確定。
這個語句是動態陣列的基礎。
7、用指標當陣列名
1、原理:之前說過,如果我們一次申請多個空間,系統會發給我們連續的新地址,可以當做陣列用。
2、具體操作
有如下程式碼:
#include<cstdio> using namespace std; int main() { int n,*p; scanf("%d",&n); p=new int[n+1];//申請連續的n+1個空間給指標p for(int i=1;i<=n;i++) scanf("%d",&p[i]); for(int i=1;i<=n;i++) printf("%d ",p[i]); return 0; }
如果我們輸入:
5
1 2 3 4 5
系統給出
1 2 3 4 5
上面的程式碼你可以理解有一個數組,陣列名就是指標名,其餘操作和第5個板塊中提到的一樣。(通過陣列名+下標訪問)
我們還可以改成這個樣子:
#include<cstdio> using namespace std; int main() { int n,*p; scanf("%d",&n); p=new int[n+1];//申請連續的n+1個空間給指標a for(int i=1;i<=n;i++) scanf("%d",&p[i]); for(int i=1;i<=n;i++) { p++;//由於p預設指向第0個元素,所以先++ printf("%d ",*p); } return 0; }
這裡使用指標訪問而不使用陣列名訪問,和上面的程式碼是等價的。當然你也可以寫成這樣:printf("%d ",*(p+i));在上面提到過,這幾種寫法是等價的。
8、動態陣列與空間複雜度優化
前面扯了那麼多指標的基本定義和寫法,終於到了今天的正題了——利用指標建立動態陣列。
我們給出一個情景:現在有一個巨大(行列<=10000000)但是稀疏(大部分元素是0)的矩陣,我們要對這個矩陣進行操作,怎麼辦呢?
顯然,這樣的程式碼是絕對行不通的。
#include<cstdio> #define N 10000100 using namespace std; int n[N][N];
如果這麼寫,你的空間複雜度是絕對過不了的。
我們要進行優化才行。
記得指標可以申請空間嗎?我們可以利用這個特性,避免儲存無效資料(0),我們為每一次輸入的有效資料開一個新的記憶體單元,這樣就不會爆記憶體啦!
我們看下面這個例題:
一本通例題8.7:
【問題描述】
矩陣可以認為是N*M的二維陣列。現在有一個巨大但稀疏的矩陣。
N,M的範圍是1<=N,M<=100000,有K個位置有資料,1<=K<=100000。
矩陣輸入的方式是從上到下(第1行到第N行),從左到右(從第1列到第M列)掃描,記錄有資料的座標位置(x,y)和值(v)。這是按照行優先的方式儲存資料的。
現在要求按照列優先的資料,即從左到右,從上到下掃描,輸出有資料的座標和位置。
【輸入格式】
第1行:3個整數N,M,K,其中1<=N,M,K<=100000;下面有K行,每行三個整數:a,b,c,表示第a行第b列有資料c。資料在int範圍內,保證是行優先的次序。
【輸出格式】
1行,K個整數,是按照列優先次序輸出的數
【樣例輸入】
4 5 9
1 2 12
1 4 23
2 2 56
2 5 78
3 2 100
3 4 56
4 1 73
4 3 34
4 5 55
【樣例輸出】
73 12 56 100 34 23 56 78 55
【樣例解釋】
0 | 12 | 0 | 23 | 0 |
0 | 56 | 0 | 0 | 78 |
0 | 100 | 0 | 56 | 0 |
73 | 0 | 34 | 0 | 55 |
對於這個矩陣,我們可以這樣存:
73 | 12 | 34 | 23 | 78 |
—— | 56 | —— | 56 | 55 |
—— | 100 | —— | —— | —— |
—— | —— | —— | —— | —— |
注:標記“------”的都是沒有使用的記憶體,這樣我們就節省了11個記憶體單元,對於大資料的時候,我們還能節省更多的記憶體,保證不會超出空間限制。
這個思路的大體意思就是:忽略x的值,把第y列第一次輸入的資料當做第y列的第一個資料,然後是第二個……
下面來看程式碼實現:
#include<cstdio> using namespace std; const int LP=100001; int n,m,k; int x[LP],y[LP],d[LP],c[LP];//記錄資料,記錄第n個數據在x行,y列,c是第y列的資料總數。 int *a[LP];//最多有LP列,所以我們開LP長度的指標陣列 int main() { scanf("%d%d%d",&n,&m,&k); for(int i=1;i<=k;i++) { scanf("%d%d%d",&x[i],&y[i],&d[i]);//輸入x,y,d c[y[i]]++;//第y[i]列的資料個數++ } for(int i=1;i<=m;i++) a[i]=new int[c[i]];//為每一列申請空間來存資料 for(int i=1;i<=k;i++) { *a[y[i]]=d[i];//收集資料到第y列中 a[y[i]]++;//第y列的指標指向下一個地址,準備下一次收集 } for(int i=1;i<=m;i++)//列優先 { a[i]-=c[i];//因為前面收集資料的時候每一列的指標都指向了該列的最後一個元素,所以要先減去該列的元素數,讓它指向第一個元素 for(int j=1;j<=c[i];j++,a[i]++)//從第1列開始輸出,j用來統計輸出到第i列第幾個元素,如果輸出到最後一個元素,跳出迴圈 printf("%d ",*a[i]);//指標每次+1,指向下一個元素並輸出它 } return 0; }
a[i]=new int c[[i]];這一句的意思是給a[i]這個指標新申請c[i]個空間,等同於我們開了LP個一維的指標陣列,這些陣列每一個都有一個專用的指標a[i],每個陣列有c[i]個元素。
到這裡,我們已經講完了利用指標開動態陣列陣列的具體做法,這樣可以很有效率的優化你的程式,趕緊用起來吧!!!