上期鏈接
https://www.cnblogs.com/jisuanjizhishizatan/p/15365167.html
前言
最近,指針確實逐漸淡出我們的生活了。但是,指針又是必不可少的,它在日常編程中又有着很大的作用。曾經noip初賽的閱讀程序寫結果,還經常考指針題,以及函數的傳參機制,例如*a++這個語句。然而近兩年,這些題目也不再出現了。
指針其實是C++中一個非常值得深究的語法。到目前為止,可能說,沒有人能夠完全理解指針。我們所學的,只是指針中,極小的一部分。
好了,讓我們開始吧,繼續今天的指針學習。
函數的傳參機制
現在,讓我們輸入兩個數,將兩數反轉后輸出。可能有人會問,標准庫不是有一個swap函數嗎?那好,我們就自己寫一個swap函數。
#include<iostream>
using namespace std;
void Swap(int a,int b){
int temp=a;a=b;b=temp;
}
int main(){
int a,b;
cin>>a>>b;
Swap(a,b);
cout<<a<<" "<<b;
return 0;
}
那好,我們來運行吧。輸入:4 5
輸出:4 5
看來這個函數有點問題啊,沒有交換變量的值。我們嘗試把他寫進主函數,像是這樣:
#include<iostream>
using namespace std;
//void Swap(int a,int b){
// int temp=a;a=b;b=temp;
//}
int main(){
int a,b;
cin>>a>>b;
//Swap(a,b);
int temp=a;a=b;b=temp;
cout<<a<<" "<<b;
return 0;
}
這樣輸出是正常的,這就奇怪了,為什么沒有交換變量的值呢?
我們來做個實驗。運行下面代碼,看看它會輸出什么?
#include<iostream>
using namespace std;
void Swap(int a,int b){
int temp=a;a=b;b=temp;
cout<<&a<<" "<<&b<<endl;
}
int main(){
int a,b;
cin>>a>>b;
Swap(a,b);
//int temp=a;a=b;b=temp;
cout<<&a<<" "<<&b<<endl;
return 0;
}
我在wandbox在線編譯器運行了這個代碼,輸出是:
0x7ffdaa89e65c 0x7ffdaa89e658
0x7ffdaa89e68c 0x7ffdaa89e688
在菜鳥在線編譯器的輸出:
0x7ffd6da5b4fc 0x7ffd6da5b4f8
0x7ffd6da5b52c 0x7ffd6da5b528
可以看到,在多個編譯器中,輸出的main函數中ab的地址和swap函數中ab地址是不同的。既然它們被保存在不同的地址,swap函數中的ab交換了,但是main函數的ab沒有交換。
就像某個上司讓他的部下交換他檔案櫃的文件,但是上司給部下的文件是檔案櫃中的復印件,那么那位部下無論怎么做,都無法把檔案櫃的文件交換。這是同樣的道理。
那我們怎么辦呢?我們可以嘗試用指針解決這個問題。這是唯一的方法。
(更准確的說,使用引用同樣可以解決問題,引用會在下一章予以介紹)
#include<iostream>
using namespace std;
void Swap(int *a,int *b){
int temp=*a;*a=*b;*b=temp;
}
int main(){
int a,b;
cin>>a>>b;
Swap(&a,&b);
cout<<a<<" "<<b<<endl;
return 0;
}
我們如果向swap函數傳遞a和b的地址,那么,swap里面的a和b,其實就是main里面的a和b。這樣一來,就可以交換了。如果那個上司告訴了他的部下檔案櫃的地址,那么部下就可以根據地址,找到櫃子里的文件,自然也就可以交換櫃子里的文件了。
有沒有發現,這里swap函數中,參數是傳遞的地址,那么參數前必須加&號。想起來了嗎?scanf也是這樣寫的!實際上,scanf也使用了指針的機制!
鏈表
struct LIST{
int n;
struct LIST* next;
};
鏈表中,需要知道下一個元素的地址才能進行查找下一個元素。因此,需要把指針作為結構體成員。最后一個元素的next置放NULL,通知程序“后面已經沒有元素了”。
如果要查找節點s的下一個元素,就是(*s).next
,注意括號雖然麻煩但是不可省略。
當然,還有一種簡寫形式,即
(*s).next=s->next
如果要刪除元素,我們可以這樣執行:
LIST *x=s->next;
s->next=s->next->next;
free(x);
連用了兩次結構體運算符->。
函數指針
上一篇文章說過,程序是保存在內存中的,自然也可以使用指針指向我們的程序中的函數。這種指針稱作函數指針。
#include<bits/stdc++.h>
using namespace std;
int f(int a){
printf("a..%d",a);
}
int (*p)(int a);
int main(){
p=f;
(*p)(5);
}
int (*p)(int a);一句表示聲明一個叫做a的函數指針。有人會問,這是指向函數的指針,先把表示指針的*號使用括號括起來是不是很奇怪?事實上,由於表示函數的()優先級比*高,如果不加括號,
int *p(int a);
編譯器會把它當作一個返回值是int*的函數p。就不是函數指針了。
把函數指針當作參數使用
stdlib.h中,有一個函數atexit,作用是“當程序正常退出時執行這一函數”。程序實例:
#include<bits/stdc++.h>
using namespace std;
void f(){
cout<<"Hello, World!";
}
int atexit(void (*func)(void));
int main(){
atexit(f);
return 0;
}
其中,atexit的參數就是一個函數指針,將地址f賦值給了atexit的參數地址func,在結束時執行f。
順便一提,既然函數可以看成指針,那么能否對其執行取數值操作呢?使用*運算符可以取到地址的數值。答案是不能。在表達式中,如果對函數地址前添加*號,f暫時會變成函數。但由於在表達式中,它又會變為“指向函數的指針”。也就是說,這種情況下,對函數用*運算符無意義。
因此,
#include<bits/stdc++.h>
using namespace std;
int main(){
(********printf)("hello");
return 0;
}
這樣的操作也能輸出hello。
從1開始的數組
眾所周知,C++的數組從0開始,但是使用某些指針的技巧,可以使數組下標從1開始計數。
#include<bits/stdc++.h>
using namespace std;
int a[10];
int *p;
int main(){
p=&a[-1];
for(int i=1;i<=10;i++)cin>>p[i];
for(int i=1;i<=10;i++)cout<<p[i]<<" ";
}
程序把p指向了不存在的元素a[-1],這樣,p[1]等於a[0],p[10]等於a[9],就可以讓下標從1到10計算了。
當然,這個程序違反了C標准,標准規定指針只能指向數組內的元素和數組最后元素的下一個元素,其他情況均屬於未定義(這與是否發生讀寫無關)。至於為什么標准允許讓指針指向數組最后元素的下一個元素(例如在上例中指向不存在的a[10]),大家可以自己探究,我將會在下期給出答案。
順帶一提,fortran的數組從1開始計數,因此,為了把fortran程序移植到c程序過程中,經常使用這種“違背標准的技巧”。
完。下期再見。