C語言學習筆記——指針

1.簡單指針  *p:

(1)用於數組

int *p 定義指針

該指針表示指向某個變量的地址;

當指針與自增符號結合時,簡單舉例如下:

int m[5]={1,3,5,7,9};p=m;

A.*p++;     B.*++p;    C.++*p;    D.x=*p++;   E.x=*++p;    F.x=(*p)++;   G.x=*(p++) ; 

首先,看個簡單的例子,int m=0,n;   n=m++; n=++m;  n=(m++);中第一個式子得到m=1,n=0第二個式子得到m=1,n=1.

其中m++和++m的區別在於是 先賦值還是先計算。易搞混的是第三個式子n=(m++)得到的結果仍然是m=1,n=0 此處須說明,雖然括號將m++擴起優先計算,但是在C中,m++的含義是在計算其他賦值等式子之後再單獨進行++,可以理解爲單獨一句m=m+1,因此優先級考慮就與擴號無關了。

由此再看ABC:先來比較*與++的優先級:*做乘號,比++優先級低,但是與此處無關;*做取內容符號時,優先級同++p中的++,但是低於p++中的++。

在AB中,二者意思相同,均爲將指針後移一個單位,A中容易混淆。須記住它不是 *p = *p + 1; 它卻是 *p = *(p+1); 而且是後加加;由於++和*優先級相同,C選項中先計算*p,取到p指向的內容,再對內容做自增,與前二者不同,代碼如下:

#include<stdio.h>
main() 
{
	int m[5]={1,3,5,7,9},*a,*b,*c;
	a=m;
	b=m;
	c=m;
	*a++;
	*++b;
	++*c;
	printf("this is *a: %d \n",*a);
	printf("this is *b: %d \n",*b);
	printf("this is *c: %d \n",*c);
}
輸出結果見下



對於DEFG,結合上面兩個方面也就不難理解:

D先將p指針內容給x,隨後指針自增,得到*p爲3,x爲1;

E中先指向下一位再賦值,x=*p=3;

F中x仍爲指針原本指向的值x=1;此處先取到p的值,再對值自增,得到*p最後爲2;同時m[0]的值同步變爲2;

G中由上方分析可知,()的存在對式子和++的先後順序無影響,結果同D。


(2)用於字符串

字符串中,指針在用法上與數組有很大區別:

a.指針指向字符串須與字符串同數據類型;

b.講指針指向字符串後,指針即可代替該字符串進行使用,表示整串輸出字符串內容,而不是地址,如用puts(指針名)直接輸出整個字符串;

c.二者用取內容運算符*,均表示相應數組在對應位置的內容,字符串指針p的類型是string,*p則是char(具體應用在輸出格式上),注意,字符串指針*p指單個char變量,判斷是否結束,用*p==‘\0’;

d.一個易錯點!:

如下寫法會正常編譯,但是在運行時出現卡死:

int main() {
	char *s1="abc",*s2="def";
	s1[2]=s2[1] ;//這裏出錯

	return 0;
}:
如果把字符串指針當做數組來用,必須先定義已有的數組,再令字符串指針指向該數組,否則會出錯。正確寫法:
int main() {
	char s1[]="abc",s2[]="def";
        char *p1=s1,*p2=s2
	p1[2]=p2[1] ;//這個時候才能把字符串數組當指針用

	printf("%c",s1[2]);
	return 0;
}


2.指針數組*p[],數組指針(*q)[]:

     二者被我多次搞混,出現語法等錯誤,故放在一起討論

     *p[n]表示的是一個指針數組,從符號優先級可以看出來,其和*(p[n])等價。即一個指針的集合,包含n個指針,他們相互獨立。這種用法與二級指針**p相似

其常見應用:表示二維字符數組:

int main( )
2.{  char  *p[ ]={"PROGRAM","BASIC","C","JAVA"};
3.   int    i;
4.   for (i=3;i>=0;i--)  printf("%c",*p[i]);
5.   printf("\n"); 
6.return 0;
7.}
結果:JAVACBASICPROGRAM

其結果爲倒敘輸出四個單詞,char *p[n]表示一串指針時 等價於 n個char *p 等價於char  **p(二級指針詳細部分見下)



另外 用二維整型數組表示*p[n]容易出現的一種錯誤,剛好與(*q)[n]做簡單對比:

int main(int argc, char *argv[]) {
	int a[3][4]={1,2,3,4,
				5,6,7,8,
				9,10,11,12},i,j;  
	int *p[3];        //這裏的角標是3,因爲這是一個含有3個指針的數組,每個指針對應上面二維數組的一行 
	*p=a;             //這裏用的*p,區別於下面。因爲這裏的p相當於二級指針,對應的*p纔是一級指針,指向數組
	for(i=0;i<3;i++)  
	    for(j=0;j<4;j++)  
	        printf("%3d",a[i][j]) ;  
	printf("\n");
	for(i=0;i<3;i++)  
	for(j=0;j<4;j++)  
	    printf("%3d",*(*(p+i)+j)) ;
	
}

此處在輸入12個數之後  得到的結果只有前四個與上吻合,原因是指針數組內部變量相互獨立。輸出如下:




下面再來看 (*p)[n]

如int(*P)[10],p先和*結合,意味着p是一個指針,他指向int [10],即p是一個指向一個數組的指針

int main(int argc, char *argv[]) {
	int a[3][4]={1,2,3,4,
				5,6,7,8,
				9,0,1,2},i,j;  
	int (*p)[4];     //注意這裏角標是4.原因是這個指針指向int [4]的數組
	p=a;             //注意這裏直接p=a,區別上面的*p=a;因爲這裏的p是一級的指針,指向數組
	for(i=0;i<3;i++)  
	    for(j=0;j<4;j++)  
	        printf("%3d",a[i][j]) ;  
	printf("\n");
	for(i=0;i<3;i++)  
	for(j=0;j<4;j++)  
	    printf("%3d",*(*(p+i)+j)) ;
	
}

注意二者在表示上略有不同 此處n=4;等於二維數組的列寬;另外在賦予地址時,此處不需要間接引用符*,只需將q指針指向數組第一個內存位置即可。
輸出結果如下:




3.二級指針**p

二級指針表示指向指針的指針,可以理解爲指針數組*p[]


假如如此定義:int **p,(*q)[5];

這時的*(p+i)和q[i]是等價的;

int  main( )
{  char *a[6]= {"ABCD","EFGH","IJKL","MNOP","QRST","UVWX"};
   char      **p;
   int       i;
   p=a;
   for( i=0; i<6; i++ )    printf("%s",*(p+i));
   printf("\n");
   return 0;
}
如上,其中的*(p+i)就等價於a[i],等價於int *q="...."

二級指針一個很重要的用法就是在調用函數過程中使用,使在函數內部可以改變原先指針的指向:

void change_ptr(void **ptr, void *dest)

{

     *ptr = dest;

}
change_ptr(&ptr,dest)
注意此處調用時  要取二級指針對應項的地址,才能在函數內部使二級指針指向指針,通過二級指針改變指針本身的內容。
可以這樣改變指針內容是因爲,C中進入一個子函數的時候,會默認把原函數中需帶入的變量拷貝一份進入子函數;這時拷貝一個指針的地址,等於在函數中可以改變這個指針本身

關於二維數組和二級指針,指針數組相關,可以參考下面這個文章:

點擊打開鏈接



4.指向結構的指針

指向結構的指針用p->xxx來表示,其中xxx是結構內部定義的一個項;

如定義struct list{}a此時 p->xxx等價於 a.xxx等價於*(p).xxx





補充部分:

1.關於free(q): free和malloc要成對出現

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main()
{
  char *str = (char *)malloc(100);
  strcpy(str, "hello");
  free(str);
  if(str != NULL)
    {
      strcpy(str, "world");
      printf("%s\n", str);
    }
      return 0;
}

free後的指針,要及時進行NULL,否則會成爲野指針,影響穩定性;

另外free()不能用在這種情況下:

int *p=0;
p=malloc(100*1024);
p++;
free(p);
free()必須釋放指定分配的內存,不可以是進行移動、賦值後的指針。


2.關於兩字符串相減

C中把字符串認爲是指針,字符串相減就是指針相加減,如

char s1="abcdef",s2="def";

int n=s2-s1;

得到的值n是很大的數,因爲兩字符串地址不相連


第二種情況

char *s1="abcdefg",*s2;

s2=s1;

s2+=5;

int n=s2-s1;

這裏得到的n就是二者地址差,因爲二者的字符串內容在同一段地址上,s2指針在後移之後是"fg",二者地址差是5.


第三種情況:兩個指針相減:

int a[]={2,4,6,8,10,12};
int *p1 = a;
int *p2 = &a[3];
printf("p2-p1=%d",p2-p1)
這裏可能有如下兩種結果:

  I.得到的是二者地址差,二者差3個int型的地址,也就是3*4=12或3*8=24;

 II.得到的就是二者指向位置之差,即3.

經過編譯運算,這裏是第II種情況。指針相減不能得到地址差,而是得到“地址差÷sizeof(數據類型)”。



3.指針用於多個返回值

 指針可用於函數中來返回多個值,具體用法:

設置swap函數,交換兩個變量的值:

void swap(int *x1,int *x2);
int main(int argc, char *argv[]) {
	int a,b;
	scanf("%d",&a);
	scanf("%d",&b);
	swap(&a,&b);
	printf("a=%d,b=%d",a,b);
	return 0;
}
void swap(int *x1,int *x2){
	int temp;
	temp=*x1;
	*x1=*x2;
	*x2=temp;
}
 函數部分曾經錯寫爲:

int *temp;temp=x1;x1=x2;x2=temp;

這裏的交換是交換過程操縱兩個指針變量,修改對應地址的內容,而不是對指針進行交換,函數內部的交換在返回main時失效。



4.不要這樣寫指針

int *p=5;
 這種寫法容易使程序崩潰。道理很簡單:當我們定義一個指針int * p 的時候,這個指針會隨機指向某個位置,如果我們這時修改指針指向的內存,使之變爲“5”這個數字,那麼該地址中原本的內容會被覆蓋掉,如果是一些重要數據,則會導致崩潰。因此一般情況下,定義指針後要用已經定義的變量去“引導”指針指向固定好的位置。
int a = 5;
in *p = a;

此外,在C中,數組p[]和指針*p在C中是同一個東西,即數組是一個常量指針,同時定義數組p[]和指針*p就會出現衝突錯誤。

易錯點:

當對一個指針進行自增操作(q++)時,不是地址值+1,而是地址值+n。如:

 int *p ,則p++表示地址+4/+8;

 char *q,則q++表示地址值+1。


另外,注意區分:*(p+1)和 *p + 1。二者區別一個是地址++,一個是內容++。


 5.const 與 指針

有如下三種寫法:

int a;
const int *p1=&a;  //寫法1
int const *p2=&a ;     //寫法2
int *const p3=&a ;     //寫法3
這裏三者區別,只看 const 與 * 誰前誰後:

const 在 * 之前,p可以改變地址,但是不能利用p去改變指向處的變量值,如1,2;

const 在 * 之後,不可變的是指針p,因此指針p指向的地址不能變,如3;

另外,三者對變量a無影響,仍然可以直接通過a修改內容。



以上個人筆記,如有錯誤請指出,會持續進行補充

   

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章