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修改內容。
以上個人筆記,如有錯誤請指出,會持續進行補充