當(dāng)前位置:首頁 > 嵌入式培訓(xùn) > 嵌入式學(xué)習(xí) > 講師博文 > C語言指針
關(guān)于指針我上學(xué)的時(shí)候?qū)W,我的老師說“指針很難呢“,把指針比喻成門牌號(hào),信封郵寄地址,現(xiàn)在我看到指針就想起門牌號(hào),信封地址。
地址:
說到指針,先說說地址,看一段小程序
#include "stdio.h"
int main()
{
int a = 10;
int *p = &a;
printf("%p\n", p);
return 0;
}
// output
0x7fff8b6a378c
每當(dāng)我看到指針的輸出 像這種"0x7fff8b6a378c"時(shí)候,頭都大了,那時(shí)候老師說是地址,搞得糊里糊涂的。那什么是地址呢?當(dāng)然我?guī)湍惆倏埔幌隆J窍到y(tǒng) RAM 中的特定位置,通常以十六進(jìn)制的數(shù)字表示,系統(tǒng)通過這個(gè)地址,就可以找到相應(yīng)的內(nèi)容。當(dāng)使用80386時(shí),我們必須區(qū)分以下三種不同的地址:邏輯地址、線性地址、物理地址;在進(jìn)行C語言指針編程中,可以讀取指針變量本身值(&操作),實(shí)際上這個(gè)值就是邏輯地址,它是相對(duì)于你當(dāng)前進(jìn)程數(shù)據(jù)段的地址(偏移地址),不和絕對(duì)物理地址相干,比如上面那個(gè)"0x7fff8b6a378c" 就是邏輯地址。邏輯地址不是被直接送到內(nèi)存總線,而是被送到內(nèi)存管理單元(MMU)。MMU由一個(gè)或一組芯片組成,其功能是把邏輯地址映射為物理地址,即進(jìn)行地址轉(zhuǎn)換。下面是轉(zhuǎn)換關(guān)系圖。
指針:
c語言相比匯編算應(yīng)該算是高級(jí)了,卻保留的了操作地址中高效的又抽象的形式。那么指針到底是什么呢? 在那本經(jīng)典《c 程序設(shè)計(jì)語言》 是這樣描述 : ”指針是一種保存變量地址的變量“,指針是一個(gè)特殊的變量,它里面存儲(chǔ)的數(shù)值被解釋成為內(nèi)存里的一個(gè)地址,指針與地址不要混在一起,指針是存儲(chǔ)地址一個(gè)變量,地址是內(nèi)存分配。指針可以指向這個(gè)內(nèi)存地址,也可以指向另一個(gè)內(nèi)存地址,當(dāng)指針指向一個(gè)內(nèi)存地址,它們之間才發(fā)生聯(lián)系,通過這個(gè)指針去操作這塊內(nèi)存,所以指針把我們帶入到地址層面去操作數(shù)據(jù),在php,java 這些高級(jí)語言沒有這一層的操作。舉個(gè)例子
//字符串翻轉(zhuǎn)例子
#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 函數(shù)中定義了兩個(gè)指針,一個(gè)指針指向字符串的首地址,另一個(gè)指針指向字符串的末地址,把內(nèi)容互換。 指針提供這樣便利,可以通過加、減來訪問這一塊內(nèi)存。然后再去改變內(nèi)存的值。如果沒有指針,只能去操作這樣邏輯地址 “0x7fff8b6a378c”去計(jì)算下一個(gè)或上一個(gè)邏輯地址,會(huì)不會(huì)瘋掉呢?所以指針把我們帶入到地址層面去操作數(shù)據(jù)。指針難點(diǎn)是我們不是很清楚有些復(fù)雜的數(shù)據(jù)類型的在內(nèi)存中存儲(chǔ)。指來指去不知道指向那了。如果你能很清楚內(nèi)存的分布,就不會(huì)指錯(cuò)地方!
指針的幾個(gè)概念:
1.指針的類型:
基本數(shù)據(jù)類型比如 int、char ,還有 一些復(fù)雜的比如 int (*p)[], 指向數(shù)組的指針,像這種的判斷就是指針名字去掉 , 指針的類型類型就是 int(*)[],其實(shí)就是指向數(shù)組的指針
2.指針?biāo)赶虻念愋停?/p>
當(dāng)你通過指針來訪問指針?biāo)赶虻膬?nèi)存區(qū)時(shí),指針?biāo)赶虻念愋蜎Q定了編譯器將把那片內(nèi)存區(qū)里的內(nèi)容當(dāng)做什么來看待。 你只須把指針聲明語句中的指針名字和名字左邊的指針聲明符*去掉,剩下的就是指針?biāo)赶虻念愋汀?/p>
例如:int*ptr:指針?biāo)赶虻念愋褪莍nt int(*ptr)[3]:指針?biāo)赶虻牡念愋褪莍nt()[3]
3.指針的值:
我們說一個(gè)指針的值是XX,就相當(dāng)于說該指針指向了以XX為首地址的一片內(nèi)存區(qū)域;我們說一個(gè)指針指向了某塊內(nèi)存區(qū)域,就相當(dāng)于說該指針的值是這塊內(nèi)存區(qū)域的首地址。
看一段代碼:這段代碼是問你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;
}
結(jié)果是:
//p1 != p2
//p3 = 0x4005f0dxy
&ch 指針類型為 char (*)[3], 當(dāng)運(yùn)行到pp=&ch 時(shí)候,編譯器會(huì)罵你 “warning: assignment from incompatible pointer type” 指針類型不匹配(在vc6下直接報(bào)錯(cuò))。看一下p3 會(huì)有一個(gè)值,未初始化指針是有內(nèi)存地址的,而且是一個(gè)垃圾地址。不知道這個(gè)內(nèi)存地址指向的值是什么。這就是為什么不要對(duì)未初始化指針取值的原因。最好的情況是你取到的是垃圾地址接下來你需要對(duì)程序進(jìn)行調(diào)試,最壞的情況則會(huì)導(dǎo)致程序崩潰。以后,每遇到一個(gè)指針,都應(yīng)該問問:這個(gè)指針的類型是什么?指針指的類型是什么?該指針指向了哪里?
還有一個(gè)題目可以試試
#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));
}
答案在此
指針與數(shù)組
“數(shù)組名就是指針”,“你就把當(dāng)做指針理解”,假設(shè)數(shù)組名是指針
#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
從輸出結(jié)果看兩者根本就是兩個(gè)事物,只能說數(shù)組名神似指針,數(shù)組名的內(nèi)涵在于其指代實(shí)體是一種數(shù)據(jù)結(jié)構(gòu),這種數(shù)據(jù)結(jié)構(gòu)就是數(shù)組;那么數(shù)組名到底是什么:
符號(hào)表是編譯原理中的一個(gè)概念,應(yīng)用于編譯器的詞法分析和語義分析兩個(gè)階段。詞法分析的目標(biāo)是讓編譯器能知道這是個(gè)數(shù)組就好了,那么語義分析階段就需要確定這個(gè)數(shù)組的具體空間了。所以我們定義了一個(gè)數(shù)組,編譯器就會(huì)在符號(hào)表中加入數(shù)組的名字a,并且根據(jù)其指定的大小,開辟一段內(nèi)存空間,把這段內(nèi)存空間的首地址(也就是第一個(gè)元素的地址)存入符號(hào)表,這也就是為什么我們通過數(shù)組名就可以去訪問數(shù)組的元素了。編譯器這么做是為了使我們使用數(shù)組更加的方便,易懂。也有人說a是一個(gè)內(nèi)存地址,也沒有什么不妥的,因?yàn)榫幾g器允許我們直接把a(bǔ)作為數(shù)組首地址來用。數(shù)組是一種線性的數(shù)據(jù)結(jié)構(gòu),數(shù)組名指向了那一片內(nèi)存。