【原創】淺談指針(二)


上期鏈接

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程序過程中,經常使用這種“違背標准的技巧”。

完。下期再見。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM