C指針

指針

1.指針基本介紹

  • 指針是C語言的精華,也是C語言的難點
  • 指針,就是內存的地址;所謂指針變量,也就是保存了內存地址的變量,關於指針的基本使用,在講解變量的時候作了入門級的介紹
  • 獲取變量的地址,用&,好比:int num=10,獲取num的地址:&num
  • 指針類型,指針變量存的是一個地址,這個地址指向的空間存的纔是值,好比:int *ptr = & num;ptr就是指向int類型的指針變量,即ptr是int *類型
  • 獲取指針類型所指向的值,是用:(取值符號),好比:int * ptr,使用ptr獲取ptr指向的值

2.什麼是指針

指針是一個變量,其值爲另外一個變量的地址,即內存位置的直接地址,就像其餘變量或常量同樣,在使用指針存儲其餘變量地址以前,對其進行聲明,指針變量聲明的通常形式爲編程

int *ip;//一個整型的指針
double *dp;//一個double型的指針
float *fp;//一個浮點型的指針
char *ch;//一個字符型指針
void main(){
    int num=1;
    //定義一個指針變量,指針
    //說明
    //1.int *表示類型爲指針類型
    //2.名稱ptr,ptr就是一個int *類型
    //3.ptr指向了一個int類型變量的地址
    int *ptr=#
    //num的地址是多少
    //說明1:若是要輸出一個變量的地址,使用格式是%p
    //說明2:&num表示取出num這個變量對應地址
    printf("num的值=%d num地址=%p",num,&num);
    //指針變量自己也有地址&ptr
    //指針變量存放的地址*ptr
    //獲取指針指向的值*ptr
    printf("ptr的地址是%p ptr存放的值是一個地址爲%p ptr指向的值=%d",&ptr,ptr,*ptr);
    gatchar();
}

3.指針的算數運算

指針是一個用數值表示的地址,能夠對指針執行算數運算,能夠對指針進行四種算數運算:++,--,+,-數組

3.1 指針遞增操做(++)

#include <stdio.h>

const int MAX=3;//常量
int main(){
    int var[]={10,100,200};
    int i,*ptr;//ptr 是一個int *指針
    ptr =var;//ptr指向了var數組的首地址
    for(i=0;i<MAX;i++){
        printf("var[%d]地址=%p",i,ptr);
        printf("存儲值:var[%d]=%d",i,*ptr);
        ptr++;//ptr=ptr+1(一個int字節數);ptr存放值+4字節
    }
    getchar();
    return 0;
}
  • 數組在內存中是連續分佈的
  • 當對指針進行++時,指針會按照它指向的數據類型字節數大小增長,好比 int *指針,每++,就會增長4個字節

3.2 指針遞減操做(--)

#include <stdio.h>
const int MAX=3;
int main(){
    int var[]={10,100,200};
    int i,*ptr;
    //指針中最後 一個元素的地址
    ptr=&var[Max-1];
    for(i=MAX;i>0;i++){
        //反向遍歷
        printf("ptr存放的地址=%p",ptr);
        printf("存儲值:var[%d]=%d",i-1,*ptr);
        ptr--;
    }
    getchar();
    return 0;
}
  • 數組在內存中是連續分佈的
  • 當對指針進行--時,指針會按照它指向的數據類型字節數大小減小,好比int *指針,沒--,就減小4個字節

3.3 指針+,-操做

#include <stdio.h>
int main(){
    int var[]={10,20,100};
    int i,*ptr;
    ptr=var;
    ptr+=2;//ptr的存儲地址+2個字節
    printf("var[2]=%d var[2]的地址 ptr存儲的地址=%p ptr指向的值=%d",var[2],&var[2],ptr,*ptr);
    getchar();
    return 0;
    
}
  • 能夠對指針按照指定的字節數大小進行+或者-的操做,能夠快速定位你要的地址

3.4 練習

int main(){
    int var[]={10,100,200,400,8,12};
    int i,*ptr;
    ptr=&var[2];
    ptr-=2;
    printf("ptr指向的值=%d",*ptr);
    getchar();
    return 0;
}

4.指針的比較

指針能夠用關係運算符進行比較,如==,<<=,>>=若是p1和p2指向兩個變量,好比同一個數組中的不一樣元素,則可對p1和p2進行大小比較dom

#include<stdio.h>
int main(){
    int var[]={10,100,200};
    int* ptr;
    ptr=var;
    if(ptr==var[0]){
        printf("ok");//錯誤,類型不同
    }
    if(ptr==&var[0]){
        printf("ok2");
    }
    if(ptr===var){
        printf("ok3");
    }
    if(ptr>=&var[1]){
        printf("ok4");
    }
    getchar();
    return 0;
}
#include<stdio.h>
const int MAX = 3;
int main(){
    int var[]={10,100,200};
    int i,*ptr;
    ptr=var;
    i=0;
    while(ptr<=&var[MAX-2]){
        printf("address of var[%d]=%p",i,ptr);
        printf("value of var[%d]=%d",i,*ptr);
        ptr++;
        i++;
    }
    getchar();
    return 0;
}

5.指針數組

5.1 基本介紹

要讓數組的元素指向int或者其餘數據類型的地址(指針)。可使用指針數組函數

5.2 指針數組定義

數據類型 *指針數組名[大小];佈局

  • 好比:int *ptr[3];
  • ptr 聲明爲一個指針數組
  • 由三個整數指針組成,所以,ptr中的每一個元素,都是一個指向int值的指針

5.3 指針數組快速入門和內存佈局

#include<stdio.h>

const int MAX=3;
int main(){
    int var[]={10,100,200};
    int i,*ptr[3];
    for(i=0;i<MAX;i++){
        ptr[i]=&var[i];//賦值爲整數的地址
    }
    for(i=0;i<MAX;i++){
        printf("value of var[%d]=%d ptr[%d]自己的地址=%p",i,*ptr[i],i,&ptr[i]);
    }
    getchar();
    return 0;
}

5.4 指針數組應用實例

  • 請編寫程序,定義一個指向字符的指針數組來存儲字符串列表,並經過遍歷該指針數組,顯示字符串信息(即定義一個指針數組,該數組的每一個元素,指向的是一個字符串)
#include <stdio.h>
void main(){
    //定義一個指針數組,該數組的每一個元素,指向的是一個字符串
    char *bookd[]={
        "三國演義"
        "西遊記"
        "紅樓夢"
        "水滸傳"
    };
    char *pStr="abc";
    int i,len;
    for(i=0;i<len;i++){
        printf("books[%d]指向字符串是=%s pStr指向的內容=%s",i,books[i],pStr);
    }
    getchar();
}

6.指向指針的指針(多重指針)

6.1 基本介紹

  • 指向指針的指針是一種多級間接尋址的形式,或者 說是一個指針鏈,一般,一個指針包含一個變量的地址,當定義一個指向指針的指針時,第一個指針包含了第二個指針的地址,第二個指針指向包含實際值的位置

address --------------->address--------------->value指針

pointer--------------------pointer------------------variablecode

6.2 多重指針快速入門

  • 一個指向指針的指針變量必須以下聲明,即在變量名前放置兩個星號。例如,下面聲明瞭一個指向int類型指針的指針對象

    • int **ptr;//ptr的類型是int * *
  • 當一個目標值被一個指針簡介指向另外一個指針時,訪問這個值須要使用兩個星號運算符,好比**ptrip

  • 案例演示內存

#include<stdio.h>
int main(){
    int var;
    int *ptr;//一級指針
    int **pptr;//二級指針
    int ***ppptr;//三級指針
    var =3000;
    
    ptr=&var;//var變量的地址賦給ptr
    
    pptr=&ptr;//將ptr存放的地址賦給pptr
    
    ppptr=&pptr;//表示將pptr存放的地址賦給ppptr
    
    printf("var的地址=%p var=%d ",&var,var);
    printf("ptr的自己的地址=%p ptr存放的地址=%p *ptr=%d",&ptr,ptr,*ptr);
    printf("pptr自己的地址=%p pptr存放的地址=%p **ptr=%d",&pptr,pptr,**pptr);
    printf("ppptr自己地址=%p ppptr存放的地址 ***ppptr=%d",&ppptr,ppptr,***ppptr);
    getchar();
    rerurn 0;
    
}

7.傳遞指針(地址)給函數

當函數的形參類型是指針類型時,是使用該函數時,須要傳遞指針,或者地址,或者數組給該形參

7.1傳地址或指針給指針變量

#include<stdio.h>

void test(int *p);//函數聲明,接收int*
void main(){
    int num=90;
    int *p=&num;
    //將num的地址賦給P
    test2(&num);//傳地址
    printf("main()中的num=%d",num);
    test2(p);
    printf("main()中的num=%d",num);
    getchar();
}    
void test2(int *p){
    *p+=1;//*p就是訪問num的值
}

7.2 傳數組給指針變量

數組名自己就表明數組首地址,所以傳數組的本質就是傳地址

#include<stdio.h>
double getAverage(int *arr,int size);//函數聲明
double getAverage2(int *arr,int size);

int main(){
    int balance[5]={1000,2,3,17,40};
    double avg;
    //傳遞一個指向數組的指針做爲參數
    avg=getAverage(balance,5);
    printf("Average value is:%f",avg);
    getchar();
    return 0;
}
//說明:arr是一個指針
double getAverage(int *arr,int size){
    int i,sum=0;
    double avg;
    for(i=0;i<size;i++){
        //arr[0]=arr+0;
        //arr[1]=arr+一個字節
        //arr[2]=arr+2個int 字節
        sum+=arr[1];
        printf("arr存放的地址=%p",arr);
    }
    avg=(double)sum/size;
    return avg;
}
double getAverage2(int *arr,int size){
    int i,sum=0;
    double avg;
    for(i=0;i<size;++i){
        sum+=*arr;
        printf("arr存放的地址=%p",arr);
        arr++;//指針的++運算,會對arr存放的地址作修改
    }
    avg=(double)sum/size;
    return avg;
}
  • 若是在getAverage()函數中,經過指針修改了數組的值,那麼main函數的balance數組的值是否會相應變化?
    • 會的,由於getAverage函數中的指針,指向的就是main函數的數組

8.返回指針的函數

C語言容許函數的返回值是一個指針(地址),這樣的函數稱爲指針函數

8.1 快速入門案例

//請編寫一個函數strlong(),返回兩個字符串中較長的一個
#include<stdio.h>
#include<string.h>
char *strlong(char *str1,char *str2){
    //函數返回的char*(指針)
    printf("str1的長度%d str2的長度%d",strlen(str1),strlen(str2));
    if(strlen(str1)>=strlen(str2)){
        return str1;
    }else{
        return str2;
    }
}
int main(){
    char str1[30],str2[30],*str;//str是一個指針類型,指向一個字符串
    printf("請輸入第一個字符串");
    gets(str1);
    printf("請輸入第二個字符串");
    gets(str2);
    str=strlong(str1,str2);
    pritnf("longer string :%s",str);
    getchar();
    return 0;
}

8.2 指針函數注意事項和細節

  • 用指針做爲函數返回值時須要注意,函數運行結束後會銷燬在他內部定義的全部局部變量,局部數組和形參,函數返回的指針不能指向這些數據
  • 函數運行結束後會銷燬該函數的全部局部變量,這裏所謂的銷燬並非件局部數據佔用的內存所有清零,而是程序放棄對它的使用權限,後面的代碼可使用這塊內存
  • C語言不支持在調用函數時返回局部變量的地址,若是確實有這樣的需求,須要定義局部變量爲static變量
#include <stdio.h>
int *fun(){
    //int n=100;//局部變量,在func返回時,就會銷燬
    static int n=100;//若是這個局部變量是static性質的,那麼n存放數據的空間在靜態數據區
    return &n;
}
int main(){
    int *p=func();
    int n;
    printf("okook");//多是使用到局部變量int n=100佔用空間
    printf("okoook");
    printf("okoook");
    n=*p;
    printf("value =%d",n);
    getchar();
    return 0;
}

8.3 應用實例

編寫一個函數,他會生成10個隨機數,並使用表示指針的數組名(即第一個數組元素的地址)來返回他們

#include<stdio.h>
#include<stdlib.h>
//編寫一個函數,返回一個一位數組
int *f1(){
    static int arr[10];//必須加上static,讓arr的空間在靜態數據區分配
    int i=0;
    for(i=0;i<10;i++){
        arr[i]=rand();
    }
    return arr;
}
void main(){
    int *p;
    int i;
    p=f1();
    //p指向是在f1生成的數組的首地址(即第一個元素的地址)
    for(i=0;i<10;i++){
        printf("%d",*(p+i));
    }
    getchar();
}

9.函數指針

9.1 基本介紹

  • 一個函數老是佔用一段連續的內存區域,函數名在表達式中有時也會被轉換爲該函數所在內存區域的首地址,這和數組名很是相似
  • 把函數的這個首地址(或稱入口地址)賦予一個指針變量,使指針變量指向函數所在的內存區域,而後經過指針變量就能夠找到並調用該函數,這種指針就是函數指針

9.2 函數指針定義

return type(*pointerName)(param list);

  • returnType爲函數指針指向的函數返回值類型
  • pointername爲函數指針名稱
  • param list爲函數指針指向的函數的參數列表
  • 參數列表中能夠同時給出參數的類型和名稱,也能夠只給出參數的類型,省略參數的名稱
  • 注意()的優先級高於*,第一個括號不能省略,若是寫做returnType *pointerName(param list);就成了函數原型,它代表函數的返回值類型爲returnType *

9.3 案例

用函數指針來實現對函數的調用,返回兩個整數中的最大值

#include<stdio.h>
//說明
//max 函數
//接收兩個Int,返回較大數
int max(int a,int b){
    return a>b?a:b;
}
int main(){
    int x,y,maxVal;
    //說明:函數指針
    //1.函數指針的名字 pmax
    //2.int 表示該函數指針指向的函數是返回int類型
    //3.(int,int)表示該函數指針指向的函數形參是接收兩個int
    //4.在定義函數指針時,也能夠寫上形參名 int(*pmax)(int x,int y)=max
    int(*pmax)(int,int)=max;
    printf("Input two numbers:");
    
    scanf("%d %d",&x,&y);
    //(*pmax)(x,y)經過函數指針去調用函數max
    maxVal=(*pmax)(x,y);
    printf("max value:%d pmax=%p pmax自己的地址=%p",maxVal,pmax,&pmax);
    getchar();
    getchar();
    return 0;
}

10.回調函數

10.1 基本介紹

  • 函數指針變量能夠做爲某個寒湖是的參數來使用,回調函數就是一個經過函數指針調用的函數
  • 簡單的講,回調函數是由別人的函數執行時調用你傳入的函數(經過函數指針完成)

10.2 應用實例

使用回調函數的方式,給一個整型數組int arr[10]賦10個隨機數

#include<stdio.h>
#include<stdlib.h>
//回調函數
//1.int (*f)(void)
//2.f就是函數指針,它能夠接收的函數是(返回int,沒有形參的函數)
//3.f在這裏被initArray調用,充當了回調函數角色
void initArray(int *array,int arrSize,int (*f)(void)){
    int i;
    for(i=0;i<arraySize;i++){
        array[i]=f();//經過函數指針調用看getNextRandomValue函數
    }
}
//獲取隨機值
int getNextRandomValue(void){
    return rand();//rand系統函數,會返回一個隨機整數
}
int main(void){
    int myarray[10];
    //說明
    //1.調用initArray函數
    //2.傳入了一個函數名getNextRandomValue(地址),須要使用函數指針接收
    initArray(myarray,10,getNextRandomValue);
    //輸出賦值後的數組
    for(i=0;i<10;i++){
        printf("%d",myarray[i]);
    }
    printf("\n");
    getchar();
    return 0;
}

11.指針注意事項及細節

  • 指針變量存放的是地址,從這個角度看指針的本質就是地址
  • 變量聲明的時候,若是沒有確切的地址賦值,爲指針變量賦一個null值是一個含的編程習慣
  • 賦值爲null值的指針被稱爲空指針,null指針是一個定義在標準庫<stdio.h>中的值爲零的常量,#define null 0
  • 指針使用一覽
#include<stdio.h>
void main(){
    int *p=null;
    int num=34;
    p=&num;
    
    printf("*p=%d",*p);
    getchar();
}

12.動態內存分配

12.1 C程序中,不一樣數據在內存中分配說明

  • 全局變量--內存中的靜態存儲區
  • 非靜態的局部變量--內存中的動態存儲區--stack棧
  • 臨時使用的數據--創建動態內存分配區域,須要時隨時開闢,不須要時及時釋放--heap堆
  • 根據須要向系統申請所須要大小的空間,因爲未在聲明部分定義其爲變量或者數組,不能經過變量名或者數組名來引用這些數據,只能經過指針來引用

12.2 動態內存分配的相關函數

  • 頭文件#include<stdlib.h>聲明瞭四個關於動態內存分配的函數

  • 函數原型void *malloc(unsigned int size) //memory allocation

    • 做用:在內存的動態存儲區(堆區)中分配一個長度爲size的連續空間
    • 形參size的類型爲無符號整型,函數返回值是所分配區域的第一個字節的地址,即此函數是一個指針型函數,返回的指針指向該分配域的開頭位置
    • malloc(100):開闢100字節的臨時空間,返回值爲其第一個字節的地址
  • 函數原型 void *calloc(unsigned n,unsigned size)

    • 做用:在內存的動態存儲區中分配n個長度爲size的連續空間,這個空間通常比較大,足以保存一個數組
    • 用calloc函數能夠爲一維數組開闢動態存儲空間,n爲數組元素個數,每一個元素長度爲size
    • 函數返回值指向所分配域的起始位置的指針,分配不成功,返回null
    • p=calloc(50,4);//開闢50*4個字節臨時空間,把起始地址分配給指針變量p
  • 函數原型void free(void *p)

    • 做用:釋放變量p所指向的動態空間,使這部分能從新被其餘變量使用
    • p是最近一次調用calloc或malloc函數時的函數返回值
    • free函數無返回值
    • free(p);//釋放p所指向的已經分配的動態空間
  • 函數原型 void *realloc(void *p,unsigned int size)

    • 做用:從新分配malloc或calloc函數得到的動態空間大小,將p指向的動態空間大小改變爲size,p的值不變,分配失敗返回null
    • realloc(p,50);//將p所指向的已分配的動態空間,該爲50字節
  • 返回類型說明-

    • C99標準把以上malloc,calloc,realloc函數的基類型定爲void 類型,這種指針稱爲無類型指針,即不指向哪種具體的類型數據,只表示用來指向一個抽象的類型的數據,即僅提供一個純地址,而不能指向任何具體的對象
    • C99容許使用基類型爲void的指針類型,能夠定義一個基類型爲void的指針變量(即void *型變量),它不指向任何類型的數據。請注意:不要把「指向void類型」理解爲能指向任何的類型的數據,而應理解爲指向空類型或不指向肯定的類型的數據,在將它的值賦給另外一指針變量時由系統對它進行類型轉換,使之適合於被賦值的變量的類型。
int a=3; //定義a 爲整型變量
int *p=&a;//p1指向int型變量
char *p2;//p2指向char型變量
void *p3;//p3爲無類型指針變量(基類型爲void類型)
p3=(void *)p1;//將p1的值轉換爲void *類型,而後賦值給p3
p2=(cahr *)p3;//將p3的值轉換爲char *類型,而後賦值給p2
printf("%d",*p1);//合法,輸出a的值
p3=&a;printf("%d",*p3);//錯誤,p3是無指向的,不能指向a
  • 說明:當把void 指針賦值給不一樣基類型的指針變量(或相反時),編譯系統會自動進行轉換,沒必要用戶本身進行強制轉換,例如:p3=&a;

  • 至關於「p3=(void )&a;"賦值後獲得p3的純地址,但並不指向a,不能經過 *p3輸出a的值

12.3 應用實例

  • 動態建立數組,輸入5個學生的成績,另一個函數檢測成績低於60分的,輸出不合格的成績
#include<stdio.h>
#include<stdio.h>
int main(){
    void check(int *);
    int *p,i;
    //在堆區開闢一個5*4的空間,並將地址(void *),轉成(int *),賦給p
    p=(int *)malloc(5*sizeof(int));
    for(i=0;i<5;i++){
        scanf("%d",p+i);
    }
    check(p);
    free(p);//銷燬堆區p指向的空間
    getchar();
    getchar();
    return 0;
}
void check(int *p){
    int i;
    printf("不及格的成績有:");
    for(i=0;i<5;i++){
        if(p[i]<60){
            printf("%d",p[i]);
        }
    }
}

12.4 動態內存分配的基本原則

  • 避免分配大量的小內存塊,分配堆上的內存有一些系統開銷,因此分配許多小的內存塊比分配幾個大內存塊的系統開銷大

  • 僅在須要時分配內存,只要使用完堆上的內存塊,就須要即便釋放它(若是使用動態分配內存,須要遵照原則:誰分配,誰釋放),不然可能出現內存泄露

  • 老是確保釋放以分配的內存,在編寫分配內存的代碼時,就要肯定在代碼的什麼地方釋放內存

  • 在釋放內存以前,確保不會無心中覆蓋堆上已經分配的內存地址,不然程序就會出現內存泄漏,在循環中分配內存時,要特別當心

  • 指針使用一覽

變量定義	類型表示	含義
int i;     int        定義整型變量
int *p;		int *		定義p爲指向整型數據的指針變量
int a[5]    int[5]      定義整型數組a,它有5個元素
int *p[4];  int *[4]    定義指針數組p,它由4個指向整型數據的指針元素組成
int (*p)[4]  int(*)[4]    p爲指向包含4個元素的一維數組的指針變量
int f()      int()        f爲返回整型函數值的函數
int *p()      int *()    p爲返回一個指針的函數,該指針指向整型數據
 int (*p)()   int (*)()   p爲指向函數的指針,該函數返回一個整型值
int **p;      int **      p是一個指針變量,它指向一個整型數據髮指針變量
void  *p      void  *     p是一個指針變量,基類型爲void(空類型),不指向具體的對象
相關文章
相關標籤/搜索