有了指針,就有了自由訪問(wèn)內(nèi)存空間的手段。
指針的基本概念:
每個(gè)變量都被存放在從某個(gè)內(nèi)存地址開(kāi)始的若干字節(jié)中。
指針也叫作指針變量,大小為4個(gè)字節(jié)(64位計(jì)算機(jī)為8個(gè)字節(jié))的變量,其內(nèi)容代表一個(gè)內(nèi)存地址。
通過(guò)指針,能夠?qū)υ撝羔樦赶虻膬?nèi)存空間進(jìn)行讀寫。
如果把內(nèi)存的每一個(gè)字節(jié)都想象成一個(gè)房間,那么內(nèi)存地址相當(dāng)于房間號(hào),而指針里存放的就是房間號(hào)。
指針變量的賦值、定義:
類型說(shuō)明符 *變量名
int *p; p是一個(gè)指針,變量p的類型是int*。
char *c;c是一個(gè)指針,變量c的類型是char*。
float *d;d是一個(gè)指針,變量d的類型是float*。
如何訪問(wèn)int型變量a前面一個(gè)字節(jié)?
int a;
char*p=(char*)&a;
--p;
printf(“%c”,*p);//可能會(huì)導(dǎo)致運(yùn)行出錯(cuò)
*p=‘A’;//可能會(huì)導(dǎo)致運(yùn)行出錯(cuò)(可能不讓訪問(wèn))
(以上方法是錯(cuò)誤的)
int *p1,*p2;int n=4;
char *pe1,*pe2;
p1=(int*)100;
p2=(int*)200;
cout<<"1)"<<p2-p1<<endl;
通過(guò)指針訪問(wèn)其指向的內(nèi)存空間:
int *p=(int*)40000;
*p=5000;
int n=*p;
若干=sizeof(int),因?yàn)閕nt*p。
指針定義的總結(jié):
T*p;T可以是任何類型,如float,int char。
p的類型是T*。
*p的類型是T。
通過(guò)表達(dá)式*p可以讀寫從地址p開(kāi)始的sizeof(T)個(gè)字節(jié)。
*是間接引用運(yùn)算符。
指針用法:
char ch=‘A’;
char *pc=&ch;
&取地址符
指針的作用:
不需要通過(guò)變量就可以直接對(duì)內(nèi)存空間進(jìn)行操作。通過(guò)指針,程序能訪問(wèn)的內(nèi)存空間就不僅限于變量所占有的內(nèi)存空間。
指針的互相賦值:
不同類型的指針,如果不經(jīng)過(guò)強(qiáng)制類型轉(zhuǎn)換,不可以相互賦值。
int*pn,char*pc,char c=0x65;
pn=pc;//類型不匹配,編譯出錯(cuò)
pn=*c;//類型不匹配,編譯出錯(cuò)
pn=(int*)&c;
int n=*pn;//n值不確定
*pn=0x12345678;//編譯沒(méi)毛病,但運(yùn)行會(huì)錯(cuò)誤,可能會(huì)導(dǎo)致程序的崩潰
指針的運(yùn)算:
1.兩個(gè)同類型的指針變量可以比較大小(比較地址的大小)
地址p1<地址p2? ?==? ?p1<p2? ?值為真
地址p1=地址p2? ?==? ?p1==p2? ?值為真
地址p1>地址p2? ?==? ?p1>p2? ?值為真
2.兩個(gè)同類型的指針變量,可以相減
兩個(gè)T*類型的指針p1和p2
p1-p2=(地址p1-地址p2)/sizeof(T)
3.指針變量加減一個(gè)整數(shù)的結(jié)果是一個(gè)指針
p:T*類型的指針
n:整數(shù)類型的變量或常量
p+n:T*類型的指針,指向地址:地址p+n*sizeof(T)
n+p,p-n,*(p+n),*(p-n)含義自明
4.指針變量可以自增自減
T*類型的指針p指向地址n
p++,++p:p指向n+sizeof(T)
p--,--p:p指向n-sizeof(T)
5.指針可以用下標(biāo)運(yùn)算符“[? ]”進(jìn)行運(yùn)算
p是一個(gè)T*類型的指針,n是整數(shù)類型的常量或變量。
p【n】=*(p+n)
空指針:
地址0不能訪問(wèn)。指向地址0的指針就是空指針。
可以用“NULL”關(guān)鍵字對(duì)任何類型的指針進(jìn)行賦值,NULL實(shí)際上就是整數(shù)0。
int *p1=NULL;char *pc=NULL;int *p2=0;
指針可以作為條件表達(dá)式使用,如果指針的值為NULL,則相當(dāng)于假,反之為真。
if(p)等價(jià)于if(p!=NULL)。
指針作為函數(shù)參數(shù):
#include<bits/stdc++.h>
using namespace std;
void swap(int *p1,int *p2)//p1 形參
{
int temp=*p1;
*p1=*p2;
*p2=temp;
}
int main(){
int n=3,m=4;//n實(shí)參
swap(&n,&m);
cout<<m<<' '<<n<<endl;
return 0;
}
指針和數(shù)組:
數(shù)組的名字是一個(gè)指針常量,指向數(shù)組的初始地址。
T a[n];
a的類型是T*
可以用a給T*類型的指針賦值
a是編譯時(shí)其值就已經(jīng)確定了的,不能夠?qū)進(jìn)行賦值
作為函數(shù)形參時(shí),T*p和T p[ ]是等價(jià)的
void(int T*p){cout<<sizeof(p)};
void(int T p[ ]){cout<<sizeof(p)};
代碼:
#include<bits/stdc++.h>
using namespace std;
int main(){
int a[200];int *p;
p=a;//p指向數(shù)組a的初始地址,亦指p指向了a[0]
*p=10;//使得a[0]=10
*(p+1)=20;//a[1]=20
p[0]=30;//p[i]和*p[i+1]是等效的,使得a[0]=30
p[4]=40;//a[4]=40
for(int i=0;i<10;i++)//對(duì)數(shù)組a的前十個(gè)元素進(jìn)行賦值
*(p+i)=i;
++p;//p指向a[1]
cout<<p[0]<<endl;//輸出1? ? ? p[0]等效于*p,p[0]即是a[1]
p=a+6;//p指向a[6]
cout<<*p<<endl;//輸出6
return 0;
}
翻轉(zhuǎn)
代碼:
#include<bits/stdc++.h>
using namespace std;
void reverse(int *p,int size){
for(int i=0;i<size;i++){
int temp=p[i];
p[i]=p[size-i];
p[size-i]=temp;
}
}
int main(){
int a[5]={1,2,3,4,5};
reverse(a,sizeof(a/sizeof(int));
for(int i=0;i<5;i++)
cout<<*(a+i)<<',';
return 0;
}
動(dòng)態(tài)數(shù)組:
指針可以動(dòng)態(tài)申請(qǐng)空間,如果一次申請(qǐng)多個(gè)變量空間,系統(tǒng)給的地址是連續(xù)的,就可以當(dāng)做數(shù)組使用,這就是傳說(shuō)中的動(dòng)態(tài)數(shù)組的一種。
#include<bits/stdc++.h>
using namespace std;
int n;
int *a;
int main(){
cin>>n;
a=new int[n+1];
for(int i=1;i<=n;i++)
cin>>a[i];
for(int i=2;i<=n;i++)
a[i]+=a[i-1];
for(int i=1;i<=n;i++)
cout<<a[i]<<' ';
return 0;
}
動(dòng)態(tài)數(shù)組的優(yōu)點(diǎn):
在OI中,對(duì)于大數(shù)據(jù)可能超空間的情況是比較糾結(jié)的事,用小數(shù)組只能得部分分,大數(shù)組可能爆空間同時(shí)又爆零,使用動(dòng)態(tài)數(shù)組可以在確保小數(shù)據(jù)沒(méi)問(wèn)題的前提下,盡量滿足大數(shù)據(jù)的需求。
指針和字符串:
字符串常量的類型是char*
字符數(shù)組名的類型是char*,就是一個(gè)地址。
注意:可以使用字符數(shù)組名或者字符指針輸出一個(gè)字符串,而面對(duì)一個(gè)數(shù)值型數(shù)據(jù)是不能企圖用數(shù)組名輸出它的全部元素的。
如:
int i[10];
……
printf("%d\n",i);
代碼:
#include<bits/stdc++.h>
using namespace std;
int main(){
char*p="please input your name:\n";
cout<<p;
char name[20];
char*pname=name;
cin>>pname;
cout<<"your name is "<<pname;
return 0;
}
字符串指針作函數(shù)參數(shù):
將一個(gè)字符串從一個(gè)函數(shù)傳遞到另一個(gè)函數(shù),可以用地址傳遞的方法,即用字符數(shù)組名作參數(shù)或用指向字符的指針變量做參數(shù)。在被調(diào)用的函數(shù)中可以改變字符串的內(nèi)容,在主調(diào)函數(shù)中可以得到改變了的字符串。
例題:
輸入一個(gè)長(zhǎng)度最大為100的字符串,以字符數(shù)組的方式存儲(chǔ),再將字符串倒序儲(chǔ)存,輸出倒序儲(chǔ)存后的字符串(這里以字符指針為函數(shù)參數(shù))
#include<bits/stdc++.h>
using namespace std;
void swapp(char &a,char &b){
char t;
t=a;
a=b;
b=t;
}
void work(char *str){
int len=strlen(str);
for(int i=0;i<=len/2;i++)
swapp(str[i],str[len-i-1]);
}
int main(){
char s[110];
char *str=s;
gets(s);
work(str);
printf("%s",s);
return 0;
}
字符串操作函數(shù):
void指針:
void*p;
可以用任何類型的指針對(duì)void指針進(jìn)行賦值或初始化
double d=1.54;
void*p=&d;
void*p1;
p1=&d;
因sizeof(void)沒(méi)有定義,所以對(duì)于void*類型的指針p,*p無(wú)定義,++p,--p,p+=n,p-=n,p+n,p-n等均無(wú)定義。
內(nèi)存操作庫(kù)函數(shù)memset:
頭文件cstring中聲明:
void*memset(void*dest,int ch,int n);
將從dest開(kāi)始的n個(gè)字節(jié),都設(shè)置成ch,返回值是dest,ch只有最低的字節(jié)起作用。
內(nèi)存操作庫(kù)函數(shù)memcpy:
頭文件cstring中聲明:
void*memcpy(void*dest,void*src,int n);
將地址src開(kāi)始的n個(gè)字節(jié),拷貝到地址dest。返回值是dest。
注意!有缺陷,在dest區(qū)間和src區(qū)間有重疊時(shí)可能出問(wèn)題!
試手:
1.輸入n個(gè)數(shù),使用指針變量訪問(wèn)輸出。
#include<bits/stdc++.h>
using namespace std;
int a[111],n;
int main(){
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i];
int *p=&a[1];
for(int i=1;i<=n;i++){
cout<<*p<<' ';
p++;
}
return 0;
}
2.無(wú)類型指針運(yùn)用:
#include<bits/stdc++.h>
using namespace std;
int a=10;
double b=3.5;
void *p;
int main(){
p=&a;
cout<<*(int *)p<<endl;
p=&b;
cout<<*(double*)p<<endl;
return 0;
}
3.多重指針:
#include<bits/stdc++.h>
using namespace std;
int a=10;
int *p;
int **pp;
int main(){
p=&a;
pp=&p;
cout<<a<<' '<<*p<<' '<<**pp;
return 0;
}
多重指針除了可以多次間接訪問(wèn)數(shù)據(jù),OI上主要的應(yīng)用是動(dòng)態(tài)的多維數(shù)組。
4.訪問(wèn)數(shù)組
#include<bits/stdc++.h>
using namespace std;
int main(){
int a[5],i,*pa=a;
for(i=0;i<5;i++)
cin>>a[i];
for(i=0;i<5;i++)
cout<<*(pa+i);
return 0;
}
5.行列轉(zhuǎn)換問(wèn)題
描述:矩陣可以認(rèn)為是N*M的二維數(shù)組,現(xiàn)在有一個(gè)巨大但稀疏的矩陣。
N,M范圍是:1<=N,M<=100000,有K個(gè)位置有數(shù)據(jù),K的數(shù)據(jù)范圍是1<=K<=100000。
矩陣輸入的方式是從上到下,從左到右掃描,極路由數(shù)據(jù)的坐標(biāo)位置和值,這是按照行優(yōu)先的方式保存數(shù)據(jù)的。現(xiàn)在要求按照列優(yōu)先的方式輸出數(shù)據(jù),即從左到右,從上到下掃描,輸出有數(shù)據(jù)的坐標(biāo)和值。
輸入格式:
第一行,三個(gè)整數(shù)N,M,K;下面有K行,每行三個(gè)整數(shù):a,b,c,表示的a行b列有數(shù)據(jù)c,數(shù)據(jù)在int范圍內(nèi),保證是行優(yōu)先的次序。
輸出格式:
一行,K個(gè)整數(shù),是按照列優(yōu)先次序輸出的數(shù)。
樣例輸入:
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
樣例解釋:
分析:
由于N,M可能會(huì)很大,直接開(kāi)二維數(shù)組太大,不可行。解決問(wèn)題的方法有很多種,下面的程序使用了指針和動(dòng)態(tài)數(shù)組,根據(jù)每一列的實(shí)際數(shù)據(jù)個(gè)數(shù)來(lái)申請(qǐng)?jiān)摿械目臻g,使每列的數(shù)組長(zhǎng)度不同。算法是O(N+M+K)的時(shí)間復(fù)雜度(即程序的運(yùn)算量)。
參考程序:
#include<cstdio>
using namespace std;
const int LP=100001;
int n,m,k;
int x[LP],y[LP],d[LP];
int c[LP];//每列的數(shù)據(jù)個(gè)數(shù)
int *a[LP];//每列一個(gè)指針,準(zhǔn)備申請(qǐng)數(shù)組
/*這里固定大小為L(zhǎng)P的一個(gè)指針數(shù)組,占用空間為5*4*LP字節(jié),約2M
還可以在輸入n,m后,在申請(qǐng)動(dòng)態(tài)數(shù)組,當(dāng)n,m較小時(shí),占用更小的空間
a[i]表示第i列的指針*/
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 [i]和y[i]是第i個(gè)數(shù)據(jù)所在的行號(hào)和列號(hào)
c[y[i]]++//統(tǒng)計(jì)c數(shù)組中每列數(shù)據(jù)的個(gè)數(shù);
}
for(int i=1; i<=m; i++)
a[i]=new int [c[i]];//第i列指針申請(qǐng)數(shù)組空間
for(int i=1; i<=k; i++) {//收集k個(gè)數(shù)據(jù)到相應(yīng)的列中
*a[y[i]]=d[i];//數(shù)據(jù)放在相應(yīng)列的數(shù)組中,也可以寫成a[y[i]][0]=d[i]
a[y[i]]++;//數(shù)組指針移到下一個(gè)位置
}
for(int i=1; i<=m; i++) {//列優(yōu)先
a[i]-=c[i];//指針回到每列的前面
for(int j=1; j<=c[i]; j++,a[i]++)
printf("%d ",*a[i]);
}
return 0;
}
說(shuō)明:特別的,可以把指針當(dāng)數(shù)組名用。
指針與函數(shù):
編寫一個(gè)函數(shù),將三個(gè)整型變量排序,并將三者中的最小值賦給第一個(gè)變量,次小值賦給第二個(gè)變量,最大值賦給第三個(gè)變量。
#include<bits/stdc++.h>
using namespace std;
void swap(int *x,int*y){
int t=*x;
*x=*y;
*y=t;
}
void sort(int *x,int *y,int *z){
if(*x>*y)swap(x,y);
if(*x>*z)swap(x,z);
if(*y>*z)swap(y,z);
}
int main(){
int a,b,c;
cin>>a>>b>>c;
sort(&a,&b,&c);
cout<<a<<' '<<b<<' '<<c;
return 0;
}
函數(shù)返回指針:
一個(gè)函數(shù)可以返回整數(shù)值,字符值,實(shí)型值等,也可以返回指針聯(lián)系的數(shù)據(jù)(即地址)。
定義:
類型名*函數(shù)名(參數(shù)列表);
如
int *a(int a,int b)
a是函數(shù)名,調(diào)用它后得到一個(gè)指向整型數(shù)據(jù)的指針(地址)
注意:在*a的兩側(cè)沒(méi)有括號(hào);在a的兩側(cè)分別為*運(yùn)算符和()運(yùn)算符,由于()的優(yōu)先級(jí)高于*,因此a先與()結(jié)合。在函數(shù)前面有一個(gè)*,表示此函數(shù)是返回指針類型的函數(shù)。最前面的int表示返回的指針指向整型變量。
例題:
編寫一個(gè)函數(shù),用于在一個(gè)包含N個(gè)整數(shù)的數(shù)組中找到第一個(gè)質(zhì)數(shù),若有則返回函數(shù)的地址,否則返回NULL(空指針)。
#include<bits/stdc++.h>
using namespace std;
int n,a[10001];
bool isprime(int n) {
if(n<2)return false;
if(n==2) return true;
for(int i=2; i<=sqrt(n); i++)
if(n%i==0)
return false;
return true;
}
int *find() {
for(int i=1; i<=n; ++i)
if(isprime(a[i]))
return &a[i];
return NULL;
}
int main() {
cin>>n;
for(int i=1; i<=n; i++)
cin>>a[i];
int *p=find();
if(p!=NULL)
cout<<p<<endl<<*p<<endl;
else
printf("can't find!");
return 0;
}
函數(shù)指針和函數(shù)指針數(shù)組:
使用函數(shù)指針調(diào)用函數(shù)示例:
#include<bits/stdc++.h>
using namespace std;
int t(int a){
return a;
}
int main(){
cout<<t<<endl;//顯示函數(shù)地址
int(*p)(int a);//定義函數(shù)指針變量p
p=t;//將t的地址賦給函數(shù)指針p
cout<<p(5)<<","<<(*p)(10)<<endl;
//輸出p(5)是C++的寫法,(*p)(10)是兼容C
return 0;
}
函數(shù)指針的基本操作:
1.聲明函數(shù)指針
注意:一定要這樣聲明!int (*fp)(int)
2.獲取函數(shù)的地址
3.使用函數(shù)指針來(lái)調(diào)用函數(shù)
使用typedef定義函數(shù)指針示例:
#include<bits/stdc++.h>
?using namespace std;?
int sum(int a,int b){?
?return a+b;?
}?
typedef int (*LP)(int,int);?
int main(){?
?LP p=sum;
?cout<<p(2,5);?
?return 0;
?}
在軟件開(kāi)發(fā)編程中,函數(shù)指針的一個(gè)廣泛應(yīng)用是菜單功能函數(shù)的調(diào)用,通常選擇菜單的某個(gè)選項(xiàng)都會(huì)調(diào)用相應(yīng)的功能函數(shù),而且有些軟件的菜單會(huì)根據(jù)情況發(fā)生變化(上下文敏感)如果使用switch/case或if語(yǔ)句處理起來(lái)會(huì)比較復(fù)雜,不利于代碼的維護(hù),可以考慮使用函數(shù)指針數(shù)組方便靈活的實(shí)現(xiàn)。
結(jié)構(gòu)體指針:
當(dāng)一個(gè)指針變量用來(lái)指向一個(gè)結(jié)構(gòu)體變量時(shí),稱之為結(jié)構(gòu)體指針。
定義:
結(jié)構(gòu)體名*結(jié)構(gòu)體指針變量名
char stu {
char name[20];
char sex;
float score;
} *p;
或
char stu {
char name[20];
char sex;
float score;
} ;
stu *p;
結(jié)構(gòu)體指針變量必須要賦值后才能使用,賦值是把結(jié)構(gòu)體變量的首地址賦予該指針變量,不能把結(jié)構(gòu)名賦予該指針變量。
如p=&stu是錯(cuò)誤的;
引用結(jié)構(gòu)體指針變量指向的結(jié)構(gòu)體變量的成員的方法如下:
1.指針名->成員名
2.(*指針名).成員名
示例:
#include<bits/stdc++.h>
using namespace std;
struct stu{
char name[20];
char sex;
float score;
}s[3]={{"xiaoming",'f',356},
{"xiaoliang",'f',350},{"xiaohong",'m',0}};
int main(){
stu*p;
printf("Name? ? ? Sex? ? Score\n");
for(p=s;p<s+3;p++)
printf("%-9s%3c%7d\n",p->name,p->sex,p->score);
return 0;
}
這里p++起到了移動(dòng)指針的作用
自引用結(jié)構(gòu):
自引用結(jié)構(gòu)是實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)結(jié)構(gòu)的基石,包括動(dòng)態(tài)的鏈表,堆,棧,樹(shù),無(wú)不是自引用結(jié)構(gòu)的具體實(shí)現(xiàn)。