1. 程式人生 > >c語言指標學習【轉】

c語言指標學習【轉】

  前言

  近期俄羅斯的隕石、四月的血月、五月北京的飛雪以及天朝各種血腥和混亂,給人一種不詳的預感。佛祖說的末法時期,五濁惡世 ,十惡之世,人再無心法約束,道德淪喪,和現在正好吻合。尤其是在天朝,空氣,水,食品,你能告訴還有沒有問題的嗎?不知大難至,世人依舊忙。禍福相依,危中有機。那些高階生命,出於慈悲,會救渡我們,但是你要去思考,去發現機緣。 最近較閒,沒事就學點基礎知識,整天在上層晃,感覺暈的厲害,接地氣。關於指標我上學的時候學過一點,我的老師說“指標很難呢“,當時以為這老師挺謙虛的。後來才知道其實他想說"我也搞不懂",不懂就別亂比喻的了,把指標比喻成門牌號,信封郵寄地址,現在我看到指標就想起門牌號,信封地址。想想都是淚。

  地址

   說到指標,先說說地址,看一段小程式

複製程式碼
#include "stdio.h"

int main()
{
    int a = 10; int *p = &a; printf("%p\n", p); return 0; } // output 0x7fff8b6a378c
複製程式碼

  每當我看到指標的輸出 像這種"0x7fff8b6a378c"時候,頭都大了,那時候老師說是地址,搞得糊里糊塗的。那什麼是地址呢?當然我幫你百科一下。是系統 RAM 中的特定位置,通常以十六進位制的數字表示,系統通過這個地址,就可以找到相應的內容。當使用80386時,我們必須區分以下三種不同的地址:邏輯地址、線性地址、實體地址;在進行C語言指標程式設計中,可以讀取指標變數本身值(&操作),實際上這個值就是邏輯地址,它是相對於你當前程序資料段的地址(偏移地址),不和絕對實體地址相干,比如上面那個"0x7fff8b6a378c"

就是邏輯地址。邏輯地址不是被直接送到記憶體匯流排,而是被送到記憶體管理單元(MMU)。MMU由一個或一組晶片組成,其功能是把邏輯地址對映為實體地址,即進行地址轉換。下面是轉換關係圖。

 

  關於記憶體地址怎麼轉換可以參考一下的博文。《再論邏輯地址、線性地址》、 《我理解的邏輯地址、線性地址、實體地址和虛擬地址

  指標

  c語言相比彙編算應該算是高階了,卻保留的了操作地址中高效的又抽象的形式。那麼指標到底是什麼呢? 在那本經典《c 程式設計語言》 是這樣描述 : ”指標是一種儲存變數地址的變數“,指標是一個特殊的變數,它裡面儲存的數值被解釋成為記憶體裡的一個地址,指標與地址不要混在一起,指標是儲存地址一個變數,地址是記憶體分配。指標可以指向這個記憶體地址,也可以指向另一個記憶體地址,當指標指向一個記憶體地址,它們之間才發生聯絡,通過這個指標去操作這塊記憶體,所以指標把我們帶入到地址層面去操作資料,在php,java 這些高階語言沒有這一層的操作。舉個例子 複製程式碼
//字串翻轉例子

#include "stdio.h" #include "string.h" void revstr(char *); int main() { char str[] = "Zhen Shan Ren is good!"; revstr(str); puts(str); } void revstr(char *str) { char *start, *end, temp; start = str; end = start + strlen(str) -1; while (start++ < end--) { temp = *start; *start = *end; *end = temp; } }
複製程式碼

  上面的例子是從指標的角度去處理字串,我再revstr 函式中定義了兩個指標,一個指標指向字串的首地址,另一個指標指向字串的末地址,把內容互換。 指標提供這樣便利,可以通過加、減來訪問這一塊記憶體。然後再去改變記憶體的值。如果沒有指標,只能去操作這樣邏輯地址 “0x7fff8b6a378c”去計算下一個或上一個邏輯地址,會不會瘋掉呢?所以指標把我們帶入到地址層面去操作資料。指標難點是我們不是很清楚有些複雜的資料型別的在記憶體中儲存。指來指去不知道指向那了。如果你能很清楚記憶體的分佈,就不會指錯地方!

  指標的幾個概念

   1.指標的型別

      基本資料型別比如 int、char ,還有 一些複雜的比如 int (*p)[], 指向陣列的指標,像這種的判斷就是指標名字去掉 , 指標的型別型別就是 int(*)[],其實就是指向陣列的指標

   2.指標所指向的型別

      當你通過指標來訪問指標所指向的記憶體區時,指標所指向的型別決定了編譯器將把那片記憶體區裡的內容當做什麼來看待。  你只須把指標宣告語句中的指標名字和名字左邊的指標宣告符*去掉,剩下的就是指標所指向的型別。

     例如:int*ptr:指標所指向的型別是int   int(*ptr)[3]:指標所指向的的型別是int()[3] 

   3.指標的值

     我們說一個指標的值是XX,就相當於說該指標指向了以XX為首地址的一片記憶體區域;我們說一個指標指向了某塊記憶體區域,就相當於說該指標的值是這塊記憶體區域的首地址。 

 看一段程式碼:這段程式碼是問你p1 是否和p2 相等?

複製程式碼
#include "stdio.h"

int main()
{
    char *p1,*p2,*p3; char ch[] = {'a', 'b', 'c'}; char **pp; p1 = ch; pp = &ch; p2 = *pp; if (p1 == p2) { printf("p1 == p2\n"); } else { printf("p1 != p2\n"); } printf("p3 = %p", p3); return 0; }
複製程式碼

  結果是:

//p1 != p2

//p3 = 0x4005f0dxy

&ch  指標型別為 char (*)[3], 當執行到pp=&ch 時候,編譯器會罵你 “warning: assignment from incompatible pointer type” 指標型別不匹配(在vc6下直接報錯)。看一下p3 會有一個值,未初始化指標是有記憶體地址的,而且是一個垃圾地址。不知道這個記憶體地址指向的值是什麼。這就是為什麼不要對未初始化指標取值的原因。最好的情況是你取到的是垃圾地址接下來你需要對程式進行除錯,最壞的情況則會導致程式崩潰。以後,每遇到一個指標,都應該問問:這個指標的型別是什麼?指標指的型別是什麼?該指標指向了哪裡?  

還有一個題目可以試試

複製程式碼
#include "stdio.h"

int main()
{
  int a[5] = {1,2,3,4,5}; int *p = (int *)(&a+1); printf("%d,%d", *(a+1), *(p-1)); }
複製程式碼

  答案在此

  指標與陣列 

  “陣列名就是指標”,“你就把當做指標理解”這是老師教的,卻從不給個合理的解釋,就像某組織教育無神論一樣,你要信神就是迷信,我說這就是邪惡,缺乏對人最起碼的尊重,當然在某組織的眼裡我們都是奴才。好吧,假設陣列名是指標

複製程式碼
#include "stdio.h"

int main()
{
int a[] = {1,2,3,5}; int *p = a; printf("a = %d, p =%d", sizeof(a), sizeof(p)); } //output //a= 16,p=4
複製程式碼

  從輸出結果看兩者根本就是兩個事物,只能說陣列名神似指標,陣列名的內涵在於其指代實體是一種資料結構,這種資料結構就是陣列;那麼陣列名到底是什麼:

  符號表是編譯原理中的一個概念,應用於編譯器的詞法分析和語義分析兩個階段。詞法分析的目標是讓編譯器能知道這是個陣列就好了,那麼語義分析階段就需要確定這個陣列的具體空間了。所以我們定義了一個數組,編譯器就會在符號表中加入陣列的名字a,並且根據其指定的大小,開闢一段記憶體空間,把這段記憶體空間的首地址(也就是第一個元素的地址)存入符號表,這也就是為什麼我們通過陣列名就可以去訪問陣列的元素了。編譯器這麼做是為了使我們使用陣列更加的方便,易懂。也有人說a是一個記憶體地址,也沒有什麼不妥的,因為編譯器允許我們直接把a作為陣列首地址來用。陣列是一種線性的資料結構,陣列名指向了那一片記憶體。

 --EOF--