指針變量與應用——動態數組
在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]個元素。
到這里,我們已經講完了利用指針開動態數組數組的具體做法,這樣可以很有效率的優化你的程序,趕緊用起來吧!!!