【指針】(適合考研、專升本)

指針

  • &與*是兩個作用相反的運算符。

  • 二級指針只能保存一級指針變量的地址和指向指針數組,其余情況不考慮。

     ? ?int *p[2];int a=12;int b=15;*p=&a;*(p+1)=&b;printf("%d\n%d\n",**p,**(p+1));int **r=p;printf("%d\n",**r);

  • 普遍變量名是一個值,數組名等屬于一個指針常量(即前者為一個值,后者是一個地址)

  • 對于二維字符數組,話不多說看代碼

     ? ?char p[][5]={"1951","hell","than"};printf("%s\n",*p);//1951
    ?char p2[][5]={"19512","hell0","thank"};printf("%s\n",*p2);//19512hell0thank
    ?char p2[][5]={{'1','9'},"hell0","thank"};printf("%s\n",*p2);//19
  • 使用%s輸出字符串時,printf第二個參數必須是地址

  • sizeof(指針)的值是4(個別操作系統是8),因為指針的值實際上是無符號整數(unsigned int),注意指針常量除外

     ? ?int arr[10];printf("%d\n",sizeof(arr));//40int *p=arr;printf("%d\n",sizeof(p));//4
  • 指針數組就是保存指針的數組(每一個數組元素都是一個地址)

    int b=10,c=23,*a[2]={&b,&c};
    printf("%d\n",**a);//10
  • 指針在做函數參數帶回返回值時,在函數內對該指針指向的地址進行更改可以影響到外面的指針,但是對該指針的值進行改變不會影響外面的指針

    void fn(int *p,int A){p=&A;//首先對p進行賦值,不會影響到外面的值,其次形參A在函數調用后地址會銷毀。
    }
    int main(){int *p=NULL;int A=12;fn(p,A);printf("%d\n",*p);//報錯!
    }
  • 函數使用指針帶回返回值時,對于一級指針來說有以下兩種用法(切記不能初始化NULL再代入)

     ? ?int a,*p=&a;fn(&a);//或者pprintf("%d\n",*p);//12
  • 初始化為NULL的指針不允許訪問值和改變。

     ? ?int *p=NULL;printf("%d\n",*p);//報錯

初識指針與指針變量

指針:指針就是地址。(因地址指向變量的存儲單元,故形象的將地址稱為指針)

指針變量:用于保存地址的變量。指針變量的值就是地址,用來指向一個變量、函數、數組等。(即指針)

  • c語言對變量的訪問本質上都是通過地址訪問的,分為直接訪問間接訪問(每定義一個變量,系統自動為該變量分配內存空間,變量名指向的是這片內存空間的首地址,故直接訪問直接引用變量名,間接訪問只是使用指針變量保存這個地址再通過引用指針訪問)

  • 內存以字節為單位,一個字節就是一個內存單元。例如一個int基類型變量占用4個存儲單元。

  • c語言的內存地址是一個無符號整數。

  • 普通變量前面不要加*就可以按地址訪問是因為普通變量不是指針變量(數組名是一個指針常量)

  • c語言對變量的訪問實質上是訪問變量所占用的內存單元。

指針變量的定義與初始化

定義:

格式:數據類型 *標識符例如 int *p;
指針變量的初始化可以在定義時初始化,也可以定義之后初始化(兩者無條件等價)例如 int *p=&a;    ? int *p;p=&a;(注意這里不能使用*p)  
指針變量初始化指向的對象一般是變量、數組、函數等
  • 在定義時*是區別指針變量與其他變量的字符。調用時表示指針運算,即取指向對象的值或該對象。算數表達式中表示乘

  • 指針變量的最左邊的數據類型稱為指針變量的基類型,它指定該指針變量可以指向的變量類型,不能省略(即指針變量是基本數據類型派生出來的類型,不能離開基類型而單獨存在)

  • 指針變量只能指向同類型的變量,所以初始化時應保證=兩邊的數據類型一致。(指針變量保存的是變量在內存中的首地址,地址都是無符號整型。例如int類型指針可以保存一個float類型變量的地址,因地址都是無符號整型,但是這個int指針卻無法指向這個float類型變量,因數據類型不同)

  • 在說明一個指針時要說明其是指向什么類型的指針

  • 指針變量只能保存地址,即不能賦常量或自己給一個地址。但是空指針允許賦值為0或者NULL。例int *p=0(NULL)。

    此外int類型指針初始化一個常量值編譯可以通過,但是概念上是錯誤的。例如 int *p=100。

  • 未初始化的指針變量指向的是一個隨機地址。

  • 切記不要使用未初始化的指針

    int main(){void fn(int *);int *s;fn(s);//報錯,因為傳入的指針s未初始化,指向一個隨意的空間
    }
    ?
    void fn(int *s){*s=12;
    }
  • 常見的指針初始化

    指針=&變量名
    指針=數組名
    指針=指針
    指針=NULL(0)

    NULL是標準頭文件<stdio.h>中定義的符號常量

  • 所有類型的指針變量的內存占用都是4。(unsigned int)

  • 使用指針常見的錯誤:

    1.使用未初始化的指針
    2.指針賦常量值
    3.間接訪問void類型指針
    4.指針與初始化對象類型不一致。
  • 未初始化的指針不能使用,但是未初始化的指針的地址可以使用。

    void fn(int **p,int *b){*p=b;**p=123;
    }
    ?
    int main(){int a=12,*p;fn(&p,&a);printf("%d\n",*p);
    }
  • 指針做函數參數時,指針本身的值不能改變,但是可以改變指針下面變量的值或指針的指向。

fun(int *p){*p=12;
}
int main(){int a;fun(&a);//傳入a無法改變a的值,但是傳入a的地址可以。即指針做函數參數時,指針本身的值不能改變,但是可以改變指針下面變量的值或指針的指向。一集指針:如果函數參數是指針變量,那么不能改變指針變量的指向,此時是因為指針本身是按值傳遞的,但可以改變指針指向的變量的值。如果是指針常量(即上面那樣,一個變量的地址),那么也不能改變指針本身的值,但是此時的原因是因為是常量,依然可以改變其指向的變量的值二級指針:如果函數參數是指針,那么不能改變二級指針的指向,但是可以改變二級指針保存的一集指針的地址的指向,跟甚至改變這個一集指針指向的變量的值。如果是指針常量(一個一集指針的地址),那么此時也不能改變指針本身的值,它也屬于一個常量,是一集指針在內存中的地址,這個是無法改變的由系統分配的,但依然可以改變其指向的指針的值,跟甚至改變這個一集指針指向的變量的值。
}

指針變量的引用

引用:

對指針變量的引用分為三種情況:
1.給指針變量單獨初始化例如 int *p;p=&a;(注意這里不能加*,否則表示的是該指針指向的對象)
2.引用指針變量指向對象的值:例如  int *p=&a;*p=100;printf("%d\n",*p);
3.引用指針變量的值(一般沒意義)例如  int *p=&a;printf("%d\n",p);(等價于&a)
  • 訪問指針指向對象的值可以使用*也可以使用[]。(其中[]里面的值可以是負數)

    注意:再引用時*右邊與[]左邊的操作數必須是地址(指針),且已指向明確的對象。

    int a=10086,*pa=&a;
    printf("a=%d\t*pa=%d\tpa[0]=%d\n",a,*pa,pa[0]);//a=10086      *pa=10086 ? ? ? pa[0]=10086
    //即*pa指向的a,pa[0]也指向a(常見于數組的指針)
    int arr[4]={1,2,3,4};
    int *p=arr,*p2=&arr[2];
    printf("*p=%d\t*p2=%d\n",p[0],p2[0]);//*p=1 ?  *p2=3
    printf("%d\n",p2[-1]);//2
  • 以 int *p為例,p表示地址或該指針變量,*p則表示該地址保存的值或指向的對象。

    一般情況下:

    當p出現在=左邊時表示對指針變量賦值或重新賦值,即改變指向的對象。相反在右邊時則表示引用其保存的地址(即給另一個指針)

    int a=123,*pa=&a,b=456,*pb=&b;
    printf("%d\t%d\n",*pa,*pb);//123  456
    pb=pa;//改變pb指向a(因為pa指向a)
    printf("%d\t%d\n",*pa,*pb);//123  123

    當*p出現在=左邊時表示引用其所指向的對象,即改變該對象的值。相反在右邊則表示引用該對象的值。

    int a=123,*pa=&a,b=456,*pb=&b;
    printf("%d\t%d\n",*pa,*pb);//123 456
    *pb=*pa;//改變變量b的值為a的值
    printf("%d\t%d\n",a,b);//123  123

    其余情況大多引用指針所指向的值(即*p),因為討論指針的值(地址)一般沒有意義。一種特殊情況是scanf函數的地址可以直接

    放指針變量,因為指針本就是地址。(即p等價&a,*p等價a與p[0])

    1.常見的printf("%d\n",*p)、int a=*pa+*pb等等
    ?
    2.int a,*pa=&a;scanf("%d",pa);(該語句等價于scanf("%d",&a)語句與scanf("%d",&*pa)語句)//注意這里&*pa(*pa等價a,變量賦值需加&)printf("a=%d\n",a);
  • 未初始化的指針指向一個不確定的內存空間,所以c不允許使用未初始化的指針

    特別注意如果函數返回的值是用指針傳出的話,函數內不能創建一個指針,讓這個需要帶返回值的指針指向這個函數內的指針或他倆指向同一片空間,因為函數結束后這里面創建的指針會銷毀,導致返回值指針指向一個銷毀的空間(數據結構鏈表與現線性表)

  • void類型指針可以指向任何類型,但必須強制轉換后才能使用

    其他類型也可以保存void類型指針,使用時候不需要強制轉換。

       int a=12;void *m=&a;int *x=m;printf("%d\n",*x);//void類型地址給int會自動類型轉換printf("%d\n",*(int *)m);//int類型地址給void不會自動轉換

指針變量作函數參數

  • 若函數的參數為指針,則聲明時候可省略形參標識符,但是要加*(數組要加[])。例如 int fn(int *)。

  • 函數參數為指針時,可以通過形參指針改變實參指針指向的對象的值,因為兩者指向同一地址。無法通過形參指針改變實參指針的值(地址),因為兩者在參數傳遞上仍然屬于值傳遞。

  • 指針作為函數參數,可以得到多個變化的值。(函數只能返回一個值)

通過指針引用數組

  • []其實是變址運算符,即將 a[i] 按 a+i 計算地址,再找出此地址保存的值(數組下標表示法arr[i]其中arr不是數組名,而是指針常量)

    訪問數組元素:arr[4](arr表示數組首地址,arr+4后移4位即第5個元素)
    指針變量也可以用[]訪問:int *p=&a,printf("%d",p[0])。p代表a的地址,a+0仍表示a(注意p[0]表示是指向的對象,則&p[0]表示該對象的地址,但是不能引用這個地址進行賦值,因為=左邊只能是一個變量)

指向數組元素的指針

  • c語言中,數組名代表數組中的首元素地址,不代表整個數組。所以 int *pa=&arr[0] 等價于 int *p=arr。注意一般數組名屬于一個指針常量,即它表示一個數組的首地址,這個值是不允許改變的,即不允許進行++、--、+=、-=運算。但是作為函數形參數組名相當于一個指針變量,這個值可以改變

    int main(){int arr[]={1,5,3};arr=&arr[1];//首先數組不允許這樣重新賦值,其次數組名是一個指針常量,常量不允許重新賦值,所以這里會報錯printf("%d\n",*(arr++));//因為arr不允許進行++、--,所以這里也報錯void fn(int []);fn(arr);
    }
    void fn(int arr[]){printf("第一次指向:%d\n",*arr);//1arr=&arr[1];printf("第二次指向:%d\n",*arr);//5printf("還允許進行++運算:++arr=%d\n",*(++arr));//3
    }
  • 從二維的角度看,二維數組名代表的是首行首地址,即指向的是整個數組。每行是一個一維數組,這些一維數組的數組名代表該行首列地址。

    int arr[2][3]={{1,2,3},{4,5,6}};
    printf("%d\n",arr);//arr指向的是第一個一維數組arr[0],是一個地址
    printf("%d\n%d\n",*arr[0],*arr[1]);//1	4
  • 二維數組名的基類型是一個一維數組(數組名都屬于指針常量),面向的是第一行整個一維數組,而第一個一維數組基類型是一個數組元素,面向的是一個普通變量,所以雖然兩者都指向二維數組的首地址(行指針列指針都指向首行首地址,但面向對象不同),但是前者屬于行地址,前面加*就變成了列地址,列地址前面加&又變成了行地址,列地址前面再加一個*才表示列地址保存的對象

    int arr[2][3]={{1,2,3},{4,5,6}};
    printf("%d\n",arr);//arr指向arr[0][0],但是值是一個行地址,即面向的是整個一維數組arr[0]
    printf("%d\n",*arr);//*arr指向arr[0][0],但是值是一個列地址。即面向的一維數組arr[0]的首地址
    printf("%d\n",**arr);//**arr指向arr[0][0],值是一個數組元素。輸出數組元素arr[0][0]
    printf("%d\n",&(*arr));//*arr指向arr[0][0],但是值是一個列地址,前面加上&表示行地址,輸出一個地址
    結論:arr是行地址,*arr是列地址,&(*arr)又表示行地址,**arr表示列地址指向的對象

指針的運算

說明:指針就是地址,所以對地址的運算是沒有意義的,但是當指針指向一片連續的存儲單元時(例如數組),對指針的+、-運算是有意義的。

(1)當指針指向數組時下列運算有意義:以 int *p=&arr[3] 為例
  • 加一個整數(+、+=)。例如 p+1表示指針后移1位,指向下一個數組元素

  • 減一個整數(-、-=)。 例如 p-1表示指針前移1位,指向上一個數組元素

  • 自加運算。例如 p++,++p

  • 自減運算。例如 p--,--p

  • 兩個指針相減。例如 p1-p2 表示兩個地址間隔之差(p1、p2必須指向同一個數組,c不允許兩地址相加,例 p1+p2)

  • 兩個指針的關系運算,例如 p1>p2:值為1是表示p2在p1前面。值為0時表示p1在p2前面。

p1==p2:值為1表示p1與p2是同一個數組元素。值為0時表示p1與p2不是同一個元素。

p1 !=P2:值為1表示p1與p2不是同一個數組元素。值為0時表示p1與p2是同一個元素。

(2)引用指向數組的指針注意事項:
  • 數組名是一個指針常量不允許進行++、--、+=、-=等重新賦值的操作,但是允許進行+、-運算。(包括一維數組與二維數組,因為都屬于指針常量)

    int arr[]={1,5,3,10,78};
    int *p=arr+1;
    printf("%d\n",*p);//5
  • 以下為例說明(++*p計算得是指針指向的對象進行自增,而🌟p++是指針地址指向的位移)

    int arr[4]={1,5,89,45};
    int *p=arr;
    printf("%d\n",p+1);//1834234068(地址)
    printf("%d\n",*p+1);//2
    printf("%d\n",*(p+1));//5
    printf("%d\n",*p++);//1(++與*優先級相同,則右結合,此時先打印p當前指向的值,執行完后p指向arr[1])
    printf("%d\n",*(p++));//5(先打印當前值,執行完后p指向arr[2])
    printf("%d\tarr[0]=%d\n",++(*p),arr[0]);//90   arr[0]=2
    printf("%d\tarr[0]=%d\n",++*p,arr[0]);//91     arr[0]=3結論:*p++與*(p++)加不加括號一樣,因為優先級相同,但是計算的是指針位移后的值(注意與++*p的區別)++*p與++(*p)加不加括號都一樣,因為優先級相同,但是計算的是指針指向的元素++(注意與*p++的區別)*p+1與*(p+1)運算過程不一樣,前者計算p當前數組元素的值+1,后者是p下一個數組元素的值	 
  • ++、--、+=、-=運算會改變指針的指向,+、-運算則不會改變指針的指向

  • 引用數組元素可以使用arr[i]、*(arr+i)、*(p+i)、p[i]。

  • 對于指向二維數組元素的指針,數組名的移動是行的移動,一維數組名名的移動是列的移動

    int arr[2][3]={{1,2,3},{4,5,6}};
    printf("%d\n",**(arr+1));//4
    printf("%d\n",*(arr[0]+1));//2
  • 指針常量不能進行++、--、+=、-=等操作,但是其指向的值可以進行這些操作。

    int arr[4]={1,5,89,45};
    *arr=12;
    printf("%d\n",*arr);//12
    (*arr)++;
    printf("%d\n",*arr);//1
  • 注意:

        int a=1,b=4;int *p1=&a,*p2=&b;a=(*p1)++;b=(*p2)++;printf("%d\n%d\n",a,b);//1 4printf("%d\n%d\n",*p1,*p2);//1 4(在這種自己=自己++,后面的++不會改變自身的值)int a=1,b=4;int *p1=&a,*p2=&b;a=(*p2)++;b=(*p1)++;printf("%d\n%d\n",a,b);//5 4printf("%d\n%d\n",*p1,*p2);//5 4(這種自己=別人++,自身的值=別人的值,然后別人的值++)

指向整個數組的指針

定義:

數據類型 (*標識符)[數組長度]例如 int (*p)[4]=&a(a是一個一維數組)
  • 指向整個數組的指針基類型是一個數組,不能賦一個數組元素。例如上面不能寫成 int (*p)[4]=a

  • 指向整個數組的指針也類似一個行指針,所以(a是一個一維數組名,屬于列指針)前面加&轉變為行地址。

  • 如果是一個二維數組,那么指向整個數組的指針可以賦值二維數組名,因為二維數組名就是指向第一個一維數組。

       int arr[2]={3,9},arr2[2][2]={2,4,6,8};int (*p)[2]=arr2;printf("%d\n",**(p+1));//6
  • 注意指向整個一維數組的指針[]中間的長度不能省略。

  • 以前的錯誤:

通過指針引用字符串

  • 指向字符串的指針初始化有兩種方法

    第一種方法:(pa可以+、-、++、--、+=、-=。*pa可以+、-,不允許進行++、--、+=、-=操作,因為屬于改變常量)char *pa="A23";printf("%c\n",*(pa+1));//2printf("%c\n",*(pa+=1));//2printf("%c\n",*(++pa));//3printf("%c\n",*pa+1);//4printf("%c\n",*pa++);//3printf("%c\n",*pa+=1);//常量不允許改變第二種方法:(對pb的改變實際上對字符數組的位移(++、--、+、-、+=、-=),對*pb的改變實際上是對*pb指向的值的改變(++、--、+、-、+=、-=)char str[]="1235";char *pb=str;printf("%c\n",*(pb++));//1printf("%c\n",*(pb+=1));  // 3printf("%c\n",*(pb+1));//5printf("%c\n",*pb+1);//4printf("%c\n",*pb++);//3printf("%c\n",*pb+=1);//6結論:對于字符指針于指針數組,可以對地址進行++、--、=、-、+=、-=等操作,但是不能對地址的值進行==、--、+=、-=。以下是兩種方法使用*p改變值的對比:
    char *pa="1639";
    pa++;//能位移
    printf("%c",*(pa+1));
    *pa='4';//但是不能重新賦值
    pa="456";//此時pa指向該數組首地址4char str[]="hello";
    char *p=str;
    *p='H';//能重新賦值
    printf("%c",*p);//H
    p++;//還能位移
    printf("%c",*p);//e
  • 使用指針變量連續輸出字符串時,如果指針變量指向改變,則輸出起始位置也會改變(只有字符型才允許這樣連續輸出)

    char *pa="123";//實際上將1這個地址賦值給*pa,與下面等價
    printf("%s\n",pa);//123
    pa++;
    printf("%s\n",pa);//23
  • 使用%s輸出字符串時注意printf函數第二個參數只能是字符數組名或者指向這個數組的指針名(即只能是地址)

    #include <stdio.h>
    int main(){char *p;char str[]="i love you";p=str;printf("%c\n",*p);printf("%s\n",p);//如果寫成*p就是錯誤的,只能是地址str或者p,他們都是地址
    }
  • 在c語言中,字符串在表現形式上分為:字符數組字符指針兩種情況。

  • 使用字符數組與字符指針處理字符串的區別:

    1.賦值方式不同
    2.系統為其分配內存單元的方法不同:字符指針是一個存放指針值的單元(4),字符數組是一段連續單元
    3.修改值(地址)的方式不同:字符指針允許修改,字符數組不允許修改。

指向函數的指針

定義:

數據類型 (*指針變量名)(形參列表)例如 int (*p)(int,int)
  • 定義時指針變量括號不能省略,否則就成了定義一個返回指針值的函數

  • 定義時如果需要指向的函數有參數,則指針函數一般也要參數(也可以不帶,因為函數名是唯一的),但是初始化只需要給出函數名。

    void fn(int *p){printf("%d\n",*p);*p=14;printf("%d\n",*p);
    }
    int main(){//以下兩種方法都正確void (*p)()=fn;void (*p1)(int *)=fn;
    }
  • 函數名就是函數的入口地址。

  • 指向函數的指針初始化時只能給一個函數名(函數的入口地址)。(如果給出函數的參數則成了調用函數,將函數的返回值給指針)

  • 對于指向函數的指針,進行算數運算沒有意義。

  • 函數名代表函數的起始地址,與數組名相同,它屬于一個指針常量

  • 在調用指向函數的指針時,指針調用與函數名調用等價,但是需要注意不管是調用還是初始化,指針變量的括號都不能省略

    int main(){int add();int (*p)()=add;//*p左右的括號不能省略printf("%d\n",(*p)());//(*p)()等價于add(),注意調用時*p左右的括號也不能省略
    }
    int add(){return 10078;
    }
  • 指向函數的指針只能指向同類型的函數,例如 int (*p)(int,int) 只能指向一個int類型函數,其后面的參數int可以省略,即

    與 int (*p)()等價(因為函數名是唯一的

  • 指向函數的指針與指向變量的指針和指向數組的指針有一點不同在于有些函數類型是void類型,那么指向函數的指針也應該是void類型。(注意變量沒有void類型嗎,所以void類型指針指向其他類型變量使用需強制轉換)

    void fn(char *p1,char *p2){int n=strlen(p1);for(int i=0;i<n;i++){*p2=*p1;p1++;p2++;}
    }
    int main(){char a[]="mother",b[]="father";// fn(a,b);// void (*p)(char *,char *)=fn;與下面等價void (*p)(char *,char *);p=fn;(*p)(a,b);printf("%s\n",b);//mother
    }

使用:

  • 單獨指向一個函數,使用指針變量調用該函數

    int main(){int add();int (*p)()=add;//*p左右的括號不能省略printf("%d\n",(*p)());/*(*p)()等價于add(),注意調用時*p左右的括號也不能省略(p保存add函數的首地址,那么*p就是這個地址里面保存的指令,所以(*p)()就是執行這段指令*/
    }
    int add(){return 10078;
    }
  • 指向一個函數,使用該指針作為另一個函數的形參,即將函數的起始地址作為形參,這樣就能在主調函數中不用聲明調用另一個函數

    int main(){void fn(int (*p)());int fn2();int (*p)()=fn2;fn(p);//fn2=10086(p保存fn2的地址,所以這里等價于 fn(fn2))
    }void fn(int (*p)()){printf("fn2=%d\n",(*p)());
    }int fn2(){return 10086;
    }以上程序等價以下程序:c不允許函數嵌套定義,但是允許函數嵌套調用int main(){void fn(int ());int fn2();fn(fn2);
    }void fn(int fn2()){printf("fn2=%d\n",fn2());
    }int fn2(){return 10086;
    }結論:以上兩個程序如果不適應指針(傳函數名屬于指針常量,傳指針變量就傳地址),需要在fn函數內聲明fn2才能使用
  • 指向一個函數,但是使用該指針指向的值作為另一個函數參數,即將指向函數的返回值作為函數的參數

    int main(){void fn(int);int fn2();int (*p)()=fn2;fn((*p)());//fn2=10086(這里的(*p)()等價fn2())
    }void fn(int n){printf("fn2=%d\n",n);
    }int fn2(){return 10086;
    }

返回指針值的函數

格式:

數據類型 *函數名(形參列表){}例如 int *max(){}
  • 返回指針值的函數即返回一個地址,而不是該地址保存的值。

  • 返回指針的函數在調用時與普通函數一樣不加*,但是聲明時要加**。

    int *max(int []);//函數聲明
    max(arr);//函數調用
  • 指針函數常用來處理動態數據結構。(一般函數內定義一個靜態存儲類型數組)

指針數組

  • 說起指針數組就要想到行指針就是指針常量,列指針就是字符型指針

  • 指針數組類似一個二維數組。

定義:

數據類型 *數組名[數組長度]例如 char *p[3]={"hello","PS","GDP"};
  • 指針數組就是保存指針的數組,每個數組元素都是一個地址

  • 注意指針數組與指向整個數組的指針的區別。

  • 指針數組本質上還是一個數組,其數組名類似一個行指針,這個行指針是指針常量,不能進行改變(即不能進行++、--、+=、-=等操作,只能進行+、-等操作。

    第一種方法:char *p[]={"hello","pdf","word"};printf("%c\n",**p);//hprintf("%c\n",**(p+1));//pprintf("%c\n",**(p+2));//w(此三種表示指針常量p可以進行+、-操作)// p++;//報錯!表示指針常量p不能進行++、--、+=、-=等操作(不能更改)第二種方法:char *p2[2];*p2="one";p2[1]="three";printf("%c\n",*p2[0]);//o(等價:printf("%c\n",**p2);)printf("%c\n",**(p2+1));//t(等價:printf("%c\n",*p2[1]))// p2++;//報錯
    總結:兩種方法指針數組名和普通數組名一樣相當于一個指針常量,不能進行修改等操作,但是能進行+、-。

    指針數組名類似一個行指針,但是指針數組的元素是一個個指針變量,所以其元素可以進行改變(注意只能進行++、--、+、-操作),對整個列指針能進行+=、-=、=操作,對列指針指向的值不能進行+=、-=、=等操作,因為這些字符型指針指向的是字符串常量的其中一個地址,改變是對常量的改變。

        char *p[]={"hello","pdf","word"};(*p)++;printf("%c\n",**p);//eprintf("%c\n",*((*p)+1));//l// (**p)='E';//報錯char *p2[2];*p2="one";p2[1]="three";(*p2)++;printf("%c\n",**p2);//nprintf("%c\n",*((*p2)+1));//e // (**p)='E';//報錯// *p2[0]='N';//報錯

    總結:對于指針數組,數組名是一個指針常量,類似一個行指針,只能進行+、-運算。指針數組的元素就是一個個字符指針,類似列指針,可以進行++、--、+=、-=、+、-運算,但是列指針是一個字符指針(字符型指針的第一種定義方法可以直接對字符指針重新賦值),允許改變整個列指針的值,但是不允許改變其所指向的指針常量單個字符。

    char str[]="123";//str是一個字符數組,保存的是一個個字符。
    char *p="123";//p是一個字符指針,指向的是一個“字符串常量”的首地址。(所以不能改變第一個char *p[2]={"chinese","elgnish"};printf("%c\n",**p);//cprintf("%c\n",**(p+1));//eprintf("%c\n",*((*p)+1));//h(行指針只能進行+、-)// p++;//報錯(行指針(指針常量)不能進行改變++、--、+=、—=)// p={"hello","word"};//首先指針數組本質上是數組,不允許單獨賦值。其次指針常量不允許重新賦值(*p)++;//列指針可以進行++、--printf("%c\n",**p);//hprintf("%c\n",*(*p)+1);//i(列指針可以進行+、-)*p="chinese1";//可以直接對列指針重新賦值,因為列指針相當于一個字符指針。printf("%s\n",*p);// **p='C';//不允許改變其中的單個值,因為是字符串常量
  • 指針數組本質上是數組,所以不能像指針一樣,在定義后單獨賦值。

        char *p[2];p={"hello","word"};//報錯
  • 指針數組與二維數組的區別:二維數組列指針是指針常量,但是指針數組的列指針是指著變量

     int a[2][2]={1,6,3,9};
    //printf("%d\n",*((*a)++));錯誤,二維數組的列指針也不允許改變,即不允許++、--、+=、-=
    char *b[]={"hel","happy","today"};
    printf("%c\n",*((*b)++));//但是指針數組的列指針允許改變
  • 指針數組里面的元素不能通過scanf輸入,因為里面是一個個字符指針變量,這些字符指針都沒有初始化,且他們只能賦值一個地址,而輸入的字符串是常量,此時字符指針數組里面的字符指針都沒有初始化,屬于空指針訪問,所以不能使用

        char *s="789";//哪怕這樣都是不行的,看似初始化了一個地址,scanf("%s",s);printf("%s\n",s);

二級指針

定義:

數據類型 **變量名例如 char **p
  • 二級指針就是指向指針變量的指針(類似一個行指針)

  • 二級指針與保存指針值的指針的區別。

        int a=12;int *p=&a,*p2=p;//p2保存的就是p的值,所以兩者都指向aprintf("%d\n%d\n",*p,*p2);//12 12int a=12;int *p=&a,**p2=&p;//p2保存的是指針p的地址,指向指針變量pprintf("%d\n%d\n",*p,**p2);//12 12
  • 注意二級指針不存在以下情況

        int *p=&a,*p2=&p;//p2是一個一級指針,無法保存二級地址
  • 二級指針不能指向二維數組,雖然都類似行指針,可以使用指向數組的指針指向二維素組的首行

       int arr[2]={3,9},arr2[2][2]={2,4,6,8};
    //    int **p=arr2;//報錯!int (*p)[]=arr2;//可以使用指向數組的指針指向一維數組,一樣可以遍歷訪問printf("%d\n",**p);//2

    如果非要二級指針指向二維數組,實質上和一級指針用法相同,但是又和二級一樣,注意別這樣用

       int arr[2]={3,9},arr2[2][2]={2,4,6,8};int **p=arr2;//報錯!printf("%d\n",*p);//2printf("%d\n",*(p+1));//6
  • 二級指針的使用只能保存指針變量的地址,所以要改變二級指針的指向,需要定義一個中間指針,保存該二級指針的下一片空間,再讓該指針指向中間指針,詳細見第八節最后一題。

  • 二級指針作用:1.指向一個指針。2.指向指針數組首地址。

指針數組做main函數參數

  • 指針數組一個重要的作用就是做main函數的參數

  • 通常情況下main函數沒有參數,為空或者void。

    int main(int argc,char *argv);//argc是字符串個數,此參數不需要用戶輸入。argv是一個char類型指針數組,用戶輸入的字符串將他們的首地址保存在指針數組中,argc則自動記錄指針數組的元素個數。
  • main函數的形參的值是不可能在程序中得到的,因為main函數由系統調用,實參只能由操作系統給出。

    給main函數傳參數:
    命令名(可執行文件路徑) 參數1 參數2 ... 參數n例如 file1 china beijing注意:1.命令名就是可執行文件路徑2.各參數之間使用空格隔開(命令名也屬于參數)3.用戶輸入的所有字符串都屬于參數,包括命令名,所以argv第一個元素保存的是命令名的首地址。

動態內存分配于指向他的指針變量

動態內存分配:建立堆(棧是系統的動態存儲區)。

  • 使用以下四個函數建立堆需要引入頭文件"stdlib.h"。

  • malloc、calloc這兩個函數返回一個指向開辟空間的首地址指針,一般需要使用指著變量保存以使用這片存儲空間,使用指針變量來訪問、存儲這片空間。

建立堆的函數:
  • malloc(原型:void * malloc(unsigned int size))

    功能:建立一塊動態存儲區(堆),堆的大小由調用時傳入的參數size決定,單位為字節(size不允許為負數)。函數返回值為該區域的		 首地址。如果函數執行失敗,返回NULL。例如 malloc(100)//開辟100字節的內存空間用于臨時分配,返回值為該區域首地址。
  • calloc(原型:void * calloc(unsigned n,unsigned size))

    功能:在內存的動態存儲區建立一片連續的空間(即動態數組),參數n表示數組的個數、size表示每個元素的長度。函數執行成功返回該區域第一個字節的地址,失敗返回NULL。
  • realloc:(原形:void * realloc(void *p,unsigned int size))

    功能:對已經分配好的存儲空間重新分配大小。p是這片需要改變大小空間的首地址,size對這片內存空間重新分配大小。如果分配不成功返回NULL
  • free(原形:void free(void *p))

    功能:釋放堆,參數是需要釋放的首地址

注意:以上四個函數都是void類型,前三個返回值都是void類型指針,void類型指針是不能指向任何類型的,即返回的是一個純地址,不能使用int、float、char等類型指針變量保存,所以在使用指針變量保存這些函數返回值時候需要將函數的類型強制轉換為相應的類型。但是c編譯系統有自動類型轉換,所以實際上這不操作可以省略。

int *p=(int *)malloc(100);//函數返回值是void *,使用int *保存需要強制轉換
等價于:
int *p=malloc(100);//自動類型轉換

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/86659.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/86659.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/86659.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

電路圖識圖基礎知識-行程開關自動往返運行控制電路詳解(二十三)

行程開關自動往返運行控制電路詳解 在機床設備運行中&#xff0c;部分工作臺需在特定距離內自動往復循環&#xff0c;行程開關自動往返運行控制電路可實現該功能&#xff0c;通過行程開關自動控制電動機正反轉&#xff0c;保障工作臺有序運動&#xff0c;以下展開詳細解析。 …

SpringBoot學習day1-SpringBoot的簡介與搭建

springboot回顧springspringbootspringboot搭建&#xff08;新聞為例&#xff09;springboot中的配置文件spring集成jdbc,mybatis,阿里巴巴數據源**SpringBoot 集成日志功能**(了解)常用日志組件日志級別 springboot統一異常處理 springboot 回顧spring spring是一個輕量級的…

【牛客小白月賽117】E題——種類數小結

1 初步想法 1.1 前置知識&#xff1a;vector數組的去重操作 unique()將不重復的元素放在數組前面&#xff0c;重復元素移到后面&#xff0c;qs獲取不重復元素的后一個位置&#xff0c;之后用erase()函數去除重復元素。 qsunique(a.begin()1,a.begin()k1); a.erase(qs,a.end(…

linux之kylin系統nginx的安裝

一、nginx的作用 1.可做高性能的web服務器 直接處理靜態資源&#xff08;HTML/CSS/圖片等&#xff09;&#xff0c;響應速度遠超傳統服務器類似apache支持高并發連接 2.反向代理服務器 隱藏后端服務器IP地址&#xff0c;提高安全性 3.負載均衡服務器 支持多種策略分發流量…

MatAnyone本地部署,視頻分割處理,綠幕摳像(WIN/MAC)

大家好&#xff0c;今天要和大家分享的項目是MatAnyone&#xff0c;與上一篇分享的SAM2LONG類似&#xff0c;不過上次的分享沒有提到如何在 MAC 上部署&#xff0c;后來有小伙伴私信說希望能出一個 MAC 版本的。那正好看到MatAnyone這個項目順手就寫下來。該項目基于SAM2同樣可…

記錄下blog的成長過程

2025-06-11 新人榜83 2025-06-09 新人榜87 北京市原力月榜 80

C語言學習20250611

指針 指針類型 int p;》普通的整形變量int *p;》p先與*結合&#xff0c;表示p為指針&#xff0c;該指針指向的內容的數據類型為整型int p[3];》p為一個由整型數據組成的數組int *p[3];》因為[]比*優先級高&#xff0c;p先與方括號結合&#xff0c;所以p為一個數組&#xff0c…

【AI智能體】Dify 從部署到使用操作詳解

目錄 一、前言 二、Dify 介紹 2.1 Dify 是什么 2.2 Dify 核心特性 2.2.1 多模型支持 2.2.2 可視化編排工作流 2.2.3 低代碼/無代碼開發 2.3 Dify 適用場景 2.4 Dify 與Coze的對比 2.4.1 定位與目標用戶 2.4.2 核心功能對比 2.4.3 開發體驗與成本 2.4.4 適用場景對比…

Java爬蟲庫的選擇與實戰代碼

如果你的項目正在Java中考慮引入爬蟲能力&#xff0c;無論是做數據分析、信息聚合&#xff0c;還是競品監測&#xff0c;選對庫確實能大幅提升開發效率和運行效果。結合當前主流庫的特點與適用場景&#xff0c;我整理了一份更貼近實戰的對比分析&#xff0c;并附上可直接運行的…

詳細解釋aruco::markdetection _detectInitialCandidates函數

_detectInitialCandidates 是 OpenCV 的 ArUco 模塊中一個非常關鍵的函數&#xff0c;它負責檢測圖像中的候選 ArUco 標記。該函數的主要目標是&#xff1a; 使用多個尺度&#xff08;scale&#xff09;對輸入圖像進行自適應閾值處理&#xff1b;在每個尺度下提取輪廓并篩選出…

Android 開發中配置 USB 配件模式(Accessory Mode) 配件過濾器的配置

在 Android 開發中配置 USB 配件模式&#xff08;Accessory Mode&#xff09; 的配件過濾器&#xff08;accessory_filter.xml&#xff09;&#xff0c;需要以下步驟&#xff1a; 1. 創建配件過濾器文件 在項目的 res/xml/ 目錄下創建 accessory_filter.xml 文件&#xff08;若…

FreeRTOS互斥量

目錄 1.使用場合2.函數2.1 創建2.1.1 動態創建2.1.2 靜態創建 2.2 刪除2.3 釋放&#xff08;Give&#xff09;2.4 獲取&#xff08;Take&#xff09;2.5 ISR 版本注意事項 3.常規使用流程4.和二進制信號量的對比5.遞歸鎖5.1 死鎖5.2 概念5.2.1 問題5.2.2 解決方案&#xff1a;遞…

ThinkPad 交換 Ctrl 鍵和 Fn 鍵

概述 不知道那個大聰明設計的將fn設置在最左邊&#xff0c;xxx&#xff0c;我服了&#xff0c;你這個老六真惡心。 方法 一&#xff1a;BIOS/UEFI 設置&#xff08;推薦&#xff09; 重啟 你的 ThinkPad。 在啟動時按下 F1&#xff08;或 Enter&#xff0c;再按 F1&#xff0…

`dispatch_source_t` 計時器 vs `NSTimer`:核心差異一覽

維度GCD 計時器 (dispatch_source_t)NSTimer依賴機制直接掛在 GCD 隊列;底層走 Mach 內核定時源掛在 RunLoop,必須指定 RunLoop & mode線程上下文哪個隊列就在哪條線程回調(例中用 dispatch_get_main_queue())總在定時器所在的 RunLoop 線程(默認主線程 & NSDefau…

ubuntu22.04系統安裝部署docker和docker compose全過程!

更新系統包 首先&#xff0c;確保系統包是最新的&#xff1a; sudo apt updatesudo apt upgrade -y安裝依賴 安裝 Docker 所需的依賴包&#xff1a; sudo apt install -y apt-transport-https ca-certificates curl software-properties-common添加 Docker 官方 GPG 密鑰 添加…

企業如何增強終端安全?

在數字化轉型加速的今天&#xff0c;企業的業務運行越來越依賴于終端設備。從員工的筆記本電腦、智能手機&#xff0c;到工廠里的物聯網設備、智能傳感器&#xff0c;這些終端構成了企業與外部世界連接的 “神經末梢”。然而&#xff0c;隨著遠程辦公的常態化和設備接入的爆炸式…

VS2017----打開ui文件幾秒后閃退

問題描述 在vs2017中雙擊ui文件能夠打開,但是幾秒后就閃退了,提示報錯 問題解決 QT VS tools ----Options,把這個設置為True保存即可

深入解析Docker網橋模式:從docker0到容器網絡的完整通信鏈路

1. 簡介docker 網橋模式 Docker 啟動時默認創建 docker0 虛擬網橋&#xff08;Linux bridge&#xff09;&#xff0c;并分配私有 IP 地址范圍&#xff08;如 172.17.42.1/16&#xff09;&#xff0c;它的作用相當于一個虛擬交換機&#xff0c;讓宿主機和多個容器之間可以通信。…

Proof of Talk專訪CertiK聯創顧榮輝:全周期安全方案護航Web3生態

6月10日&#xff0c;CertiK聯合創始人兼CEO顧榮輝在Proof of Talk 2025舉辦期間&#xff0c;接受大會官方專訪&#xff0c;分享了他對Web3安全現狀的觀察以及CertiK的安全戰略布局。 顧榮輝指出&#xff0c;雖然安全的重要性被廣泛認可&#xff0c;但許多創業者和開發者仍存在…

再說一說LangChain Runnable接口

之前我們介紹過LangChain通過Runnable和LCEL來實現各個組件的快捷拼裝&#xff0c;整個過程就像拼積木一樣。 今天我們深入剖析Runnable接口的底層實現邏輯。 往期文章推薦: 16.Docker實戰&#xff1a;5分鐘搞定MySQL容器化部署與最佳實踐15.Ollama模板全解析&#xff1a;從基…