指針是C語言的精髓,也是C語言的難點。下面是我學習途中整理的一些筆記。c++
一、指針和地址數組
和指針相關的兩個運算符:函數
&:取地址運算符 *:取指針所指向的內存單元的值學習
char c='A'; char *p=&c;
析:這個指針變量用於存放char類型變量的地址。定義的時候進行了初始化,把變量c的地址賦給了變量p,&c是用去取的變量c的地址。則變量p存放的是一個地址,此時我麼說變量p指向了變量c。
char c2=*p+1
這兒的*p 的含義是取得指針變量p 所指向的變量的值,也就是取地變量c的值'A',所以變量c2的值爲'B'。
注意:char *p 定義了一個指向char型變量的指針,p存放的是一個char型變量的地址。而以後的*p是取的指針p所指向的變量的值,&c取的是變量c的地址。能夠理解爲 「指針= =地址」spa
二、指針的大小指針
在32爲編譯器下,指針大小都是32位(4字節),在64位編譯器下,指針大小都是64位(8字節) code
三、指針的定義和使用blog
C語言中的指針是專門用來存放內存地址的變量,每個指針都有一個與之相關的數據類型,該類型決定了指針所指向的變量的類型,如一個char型指針只能指向char型變量。內存
指針定義的通常形式:數據類型 *指針變量名字符串
①char *pc ②int *pi,p
第1個和第2個的指針在定義的時候未進行初始化,所以他們所指向的變量都是未定的。使用未初始化的指針一般會致使出錯,應該絕對避免使用未初始化的指針。
char *p; int i=*p;
如上的指針並未進行初始化,指針p並無明確的指向某個變量,在此狀況下要取得p所指向的變量的值是很危險的。程序運行是很容易奔潰。
注意:避免使用未初始化的指針。在定義指針時最好將他初始化爲NULL,即明確當前指針不指向任何變量。
例:int a=10;
int * p ;
*p = &a; //這行代碼致使內存問題,它是操做野指針所指向的內存
這樣的程序在運行中是會崩潰的。p沒有初始化,它的值是一個隨機數。*p不是操做的p自己內存,是操做的p所指向的內存,操做野指針所指向的內存。正常的寫法是:p=&a
注意:所謂的空指針只是爲了作一個標誌,標誌着這個變量沒人使用。千萬不要去操做空指針所指向的空間。給這個指針變量賦值爲NULL,NULL其實也就是數字0
四、指針的操做
同類型的指針變量能夠相互賦值
int a=10; int * p1; p1 =&a; int *p2=p1; //同類型的指針變量相互賦值
*p2 = 222; printf("a=%d\n",a); //能夠有多個指針變量指向同一塊內存
返回局部變量的地址(棧區空間)
1 in func() 2 { 3 int a =10; //局部變量 4 return &a; 5 } 6
7 int main() 8 { 9 int *p; 10 p=func(); //內存會出問題 11 *p=111; 12 return 0; 13 }
解析:一、定義在{}內部的變量爲局部變量,局部變量由系統來分配空間,離開{}後系統自動回收,用戶不能在使用這塊空間。
二、p=func(),至關於把&a賦值給p ,p指向a的地址。可是func()函數調用完畢後,a會自動釋放,空間被系統回收,用戶不能在操做
三、*p=111 , 把 111 賦值給p指向的內存空間,這中操做是不行的,就是上面說的野指針問題
堆區空間的使用
只要程序不結束,只要不手動區釋放內存,空間是不會被回收的
int * func() { int * p1; //一、p指向堆區空間,堆區空間須要調用malloc函數返回 //二、只要程序不結束,用戶不free,堆區空間就一直存在 //三、int * 是指向int 型的變量,因此須要分配sizeof(int)大小的空間
p1=(int *)malloc(sizeof(int )) ; return p1; }
int main(void) { int * p2; p2 = func(); //經過函數返回堆區空間地址
if(p2==NULL) //空間分配失敗
{
printf("p2==NULL\n"); return -1; }
*p2 =111;
printf("p2=%d\n",*p);
//一、一個malloc對應一個free
//二、free(p)不是釋放p的空間,而是釋放p所指向的堆區空間
//三、free完的空間不能在使用,同一塊內存只能釋放一次
free(p); p= NULL; }
解析:一、從main函數開始,定義了一個指向int型變量的指針,而後把func()函數的返回值p1賦值給p2。
二、p1申請了一塊堆區空間,假設空間大小爲0xaabbb,func函數返回p1,則p2的值也爲0xaabbb。
三、指針變量p2也指向堆區空間。當func調用完畢,func內部的p1自動釋放,可是堆區空間不釋放。
四、 *p2=111,實際上是給p2指向的堆區空間賦值,用完以後須要釋放空間。free(p)釋放的不是p的空間,而是p所指向的堆區空間
五、指針和數組
int a[]={2,4,6,8,10}
int *p1=a;
int *p2=&a[0];
指針指向數組a的第一個元素,即a[0],指針p2也是如此。指針變量p1和p2存放的都是a[0]的地址。
在指向數組某個元素的指針上加上或減去一個整型數值,就能夠指向另一個數組元素,前提是沒有超出數組的範圍。以下:
p1=p1+3此時p1指向a[3],存放數組元素a[3]的地址。
注意:一、在指針上進行加減運算後所獲得的指針,必須指向同一個數組或指向數組存儲空間的下一個單元。
二、不能對數組名執行++,- -操做,好比a++是不合法的,這是由於a是數組名,它是數組的首地址,它的值在程序運行過程當中是固定不變的,是常量
數組元素也能夠是指針類型,這種數組稱爲指針數組也就是說指針數組的每個元素都是指針變量。
指針數組定義的通常形式爲:類型名 *數組名[數組長度] 如:char *a[5]
運算符*的優先級低於運算符[],所以a先與[5]結合,造成a[5]的形式,它顯然是一個數組。而後在與*結合,表示數組元素的類型爲指針,每一個數組元素都指向一個整型變量。這裏的a就是一個二級指針,它表示指針的指針。
#include<stdio.h>
int main() { int a[2][5]={1,3,5,7,9,2,4,6,8,10}; int (*p)[5],i; p= a; for(i=0;i<5;i++) { printf("%d",(*p)[i]); } printf("\n"); p++; for(i=0;i<5;i++) { printf("%d",(*p)[i]); } printf("\n"); return 0; }
輸出結果:1 3 5 7 9
2 4 6 8 10
解析:一、int (*p)[5],表示p是一個指針,它指向含有5個元素的一維數組,p也只能指向一個包含5個元素的一維數組,p就是該一維數組的首地址。這裏*p兩邊的括號是不可少的,由於[]的優先級bi*高。
二、p= a,p指向二維數組a的第一行。而後經過(*p)[i]訪問該行的每個元素
三、p++,使p指向二維數組的a的第二行。
注意:區別int(*p)[5]和int *p[5]。前者是一個指針,它指向一個含有5個元素的數組。後者是一個數組,它的長度爲5,數組中每個元素指向一個整型變量。
六、指針和函數
指針作函數的參數
函數的參數不只能夠是整型、字符型、實際上也能夠是指針類型。它的做用是將一個變量的地址傳送到函數中。
#include<stdio.h> #include<string.h> #include<stdlib.h>
void change(int i,int *p) { i++; if(p !=NULL) (*p)++; } int main() { int a=5,b=10; change(a,&b); printf("a=%d,b=%d\n",a,b); return 0; } 輸出結果 a=5,b=11
解析:一、主函數調用了change函數中,一個實參爲變量a,另外一個實參爲變量b的地址。由於change函數的第二個參數爲指針,因此必須傳遞一個變量地址。對於指針型實參,參數也能夠是NULL,所以change函數中必須檢查
p是否爲NULL,若是實參爲NULL,那麼語句(*p)++將致使程序崩潰。
二、函數的參數是侷限於該函數的局部變量,函數調用時,系統爲函數的局部變量分配內存。在主函數中調用change時,系統分配了8個字節的內存,4個字節保存變量i的值,4個字節保存指針變量p的值。i保存的是數值5
而指針變量p保存的是變量b的地址。i++並非做用於變量a,而是做用於剛剛分配好的4字節存儲空間,此時i的值爲6而a的值依然是5。
三、對於(*p)++,p保存的是變量b的地址,*p的含義是系統根據地址找到變量b,而後對它執行加1操做,而後b的值就變成了11,函數調用結束後,系統分配的內存也就被回收了。
注意:若是一個函數的參數中有指針,爲了程序的健壯性,在函數中須要檢查參數是否爲NULL。
七、指向字符串的指針
C語言中訪問一個字符串有多種方法。
①、用字符數組存放一個字符串
char string[ ] = "Linux C";
printf("%s\n",string);
string是一個字符數組名,它同時也是該字符數組的首地址,也就是「Linux C」這個字符串的首地址
②、用字符串指針來指向字符串(字符串指針)
char *p="Linux C" ;
printf("%s\n",p);
C語言對字符串常量的處理,在內存中開闢出一個字符數組來存儲該字符串常量,並把開闢出的字符數組的首地址賦值給p。
注意:string[0] = 'A' 是能夠的,而p[0] ='A' 是非法的,由於p指向的是字符串常量,常量的內容不可改變。把p指向另一個字符串常量或字符數組是合法的。如:p ="Hello World" ; p= string.
示例:在函數中實現字符串拷貝
#include<stdio.h> #include<stdlib.h>
void copy_string1(char src[],char dst[]) /*copy_string1(char *src,char *dst)*/ { int i; for(i=0;src[i] != '\0';i++) dst[i]=src[i]; dst[i]='\0'; } void copy_string2(char *psrc,char *pdst) { for(;*psrc != '\0';psrc++,pdst++) *pdst=*psrc; *pdst='\0'; } int main() { char a[] = "Linux C Progam",b[20],c[20]; copy_string1(a,b); copy_string2(b,c); printf("%s\n%s\n%s\n",a,b,c); return 0; }
解析:一、copy_string1函數的參數是兩個數組,而copy_string2函數的參數是兩個指向字符串的指針。這兩種方式是徹底等價的。copy_string1(char src[],char dst[])等價於copy_string1(char *src,char *dst)
二、copy_string1(a,b),把a和b的值(該值是指向數組第一個元素的指針,而這個指針就是存放數組第一個元素的內存地址)賦給src和dst,src和dst保存的是a和b的首地址。copy_string2(z,c)也同樣,
psrc和pdst也都保存這數組a和c的第一個元素地址。
三、對於copy_string1(a,b),a的值是a[0]即字符L內存地址,src的值也就是a的值,所以經過src能夠很方便的對數組a進行操做,b也是同樣的
注意:數組,如char a[20],a是指向數組第一個元素的指針,它的值不能夠被該變,它在程序運行過程當中始終指向數組的第一個元素。而在函數定義中,如copy_string1(char src[],char dst[]),src也是一個指針,它的值
是能夠改變的,也就說它能夠指向其餘字符串指針中。
字符數組由若干個元素組成,每一個元素存放一個字符。而字符串指針中存放的是地址(字符串的首地址),而不是將字符串存放在字符串指針中