C语言复习 -- 指针,这篇够了

指针  --   地址

指针就是地址,地址就是指针

& -- 取地址运算符


* 取值运算符 (解引用)
得到地址 -- 要访问他的值 -- * 取值运算符 --  对地址解引用
a=*(&a)

----------------------------


指针变量  -- 存放指针的变量
 

初始化方式 -- int *p;


int a=10;
    int *p=&a;
    printf("%p\n",&a);
    printf("%d\n",*(&a));
    
        printf("%p\n",p);
    printf("%d\n",*(p));s


访问变量的两种方式:


1. 直接访问 -- 用变量名
2.间接访问 -- 将变量地址存放到一个指针变量中,再通过 * 取值间接访问变量


定义指针的时候要求变量类型要一致  --   int 类型的数据就要用 int * 类型的指针来存放  不然可能会出现数据丢失情况
such as: 使用char * 来存放打 int类型变量 的 地址 ,当用 * 取地址的时候 char -- 一个字节--8bit 位 取不完4字节32bit的数据
 
不同类型指针的增加步幅也不一样 -- int *pi; char *pc; 这里++pi的一次增加4bit的地址数据,而pc一次增加1bit的地址数据


//验证程序:
#include<stdio.h>


int main()
{
    
    int a=0x1234;
    int *p=&a;
    char *pc=&a;
    puts("& 和 * 的用法演示:");
    puts("使用a取地址和值:\n");
    printf("%p\n",&a);
    printf("%d\n",*(&a));
    
    puts("使用指针p取地址和值:\n");
    printf("%p\n",p);
    printf("%d\n",*(p));
    
    puts("不同类型指针的演示;");
    printf("int 地址-- %p\n",p);
    printf("char 地址-- %p\n",pc);
    printf("int -- 数值 0x%x\n",*p);
    printf("char -- 数值 0x%x\n",*pc);

    puts("自增差异");
    printf("int -- 0x%p\n",++p);
    printf("char -- 0x%p\n",++pc);
    
    return 0;
}

test1:实现 一个交换函数 -- 要求交换两个变量的值
#include<stdio.h>

void sswap(int *a,int *b)
{
    int t=*a;
    *a=*b;
    *b=t;
    
}


int main()
{
    int a=3;
    int b=4;
    printf("交换前\na= %d\nb= %d",a,b);
    sswap(&a,&b);
    printf("交换后\na= %d\nb= %d",a,b);
    
    return 0;
}
-------------------------------------------------------------------------


volatile对应的变量可能在你的程序本身不知道的情况下发生改变
比如多线程的程序,共同访问的内存当中,多个程序都可以操纵这个变量
你自己的程序,是无法判定何时这个变量会发生变化
还比如,他和一个外部设备的某个状态对应,当外部设备发生操作的时候,通过驱动程序和中断事件,系统改变了这个变量的数值,而你的程序并不知道。
对于volatile类型的变量,系统每次用到他的时候都是直接从对应的内存当中提取,**而不会利用cache当中的原有数值,**以适应它的未知何时会发生的变化,系统对这种变量的处理不会做优化——显然也是因为它的数值随时都可能变化的情况。


指针的第二个场景  -- 指向 固定的地址 -- 单片机 armbootloader -- 会用到
volatile unsigned int *p=(volatile unsigned int *)0x000000000061FE2B;    

test

-- 输入3个数,要求封装一个函数实现输出的时候他们是从大到小
#include<stdio.h>


void mySwap(int *a,int *b)
{
    int t=*a;
    *a=*b;
    *b=t;
}


void mySort(int *a,int *b,int *c) //使用指针排序后赋值回去
{
// ab ac bc  -- abc    
    
    
    if(*a<*b)mySwap(a,b);
    if(*a<*c)mySwap(a,c);
    if(*b<*c)mySwap(b,c);
    
}
int main()
{
    int a,b,c;
    scanf("%d%d%d",&a,&b,&c);
    mySort(&a,&b,&c);
    printf("%d %d %d\n",a,b,c);
    return 0;
}


通过指针引用数组 --  传参是  -- 数组首地址 

p=arr;
p=&arr[0];


指针的增量与数组的关系

#include<stdio.h>


int main()
{
    int arr[3]={1,2,3};
    int  *p;
    //p=arr;
    p=&arr[0];
    // printf("首元素:%d\n",*p);
    // printf("1元素:%d\n",*(p+1));
    // printf("2元素:%d\n",*(p+2));
    
    
    for(int i=0;i<3;++i)
    {
        printf("下标:%d 的元素为%d\n地址:0x%p\n",i,*(p+i),p+i);
    }
    
return 0;
}


访问数组的两种方式 :


1.常用 下标  优点 -- 可读性差
2. 指针  -- 优点: 高效

使用指针遍历数组 用完之后记得 重新赋值指针 不然给后面再访问的时候会数组越界

p=arr; --- 重新回到数组的首元素
        for(int i=0;i<3;++i)
    {
        printf("%d ",*p++);
    }
    puts("");
    p=arr;
        for(int i=0;i<3;++i)
    {
        printf("%d ",*p++);
    }
    puts("");
    p=arr;

-------------------------------------------

数组名 和 指针的混用:


可行:  都是指针(地址)


1.p[n] -- 指针当做数组名,下标访问
// 觉得奇怪可以 想象 函数传入指针作为参数去访问数组的时候也是这么用的
such as:
void func(int *arr)
{
arr[0]=1;
}

2.数组名拿来 操作    -> *arr==arr[0], arr +1=arr[1]

#include<stdio.h>


int main()
{
    int arr[3]={1,2,3};
    int  *p;
    p=arr;
   for(int i=0;i<3;++i)
   {
       printf("%d ",p[i]);
   }
    puts("");
      for(int i=0;i<3;++i)
   {
       printf("%d ",*(arr+i));
   }

    

    
return 0;
}


不可行:(指针变量可以改变,指针常量不可修改)


指针 -- 指针变量  
数组名 -- 指针常量  -- 数组名确定数组开头位置 不能更改 


不能 ++ , *arr++  x
但是可以通过+i访问  *(arr+i) √

sizeof 的差别


sizeof(arr)  -- 数组大小 比如 int[3]  = 4*3=12bit
sizeof(p)  --   操作系统用 8个字节表示一个指针(地址)
//注意:只要是 指针就是 8字节大小  sizeof(int*0 和 sizeof(char*)大小一样


    int arr[3]={1,2,3};
    int  *p;
    p=arr;
     char *pc=arr;
    printf("arr sizeof :%d\n",sizeof(arr));
 printf("p sizeof :%d\n",sizeof(p));
 printf("pc sizeof :%d\n",sizeof(pc));

gcc test.c  -g
// -g -- gdb  调试(debug)
//进入debug 模式 后 r --running运行  q --退出


//注: 使用函数对数组操作 -- 完成后不需要赋初值了,因为函数每次调用都对形参赋初值

在 scanf 中,%d 和 %i 的行为不同


    %d 假设基数为10,而 %i 自动检测基数。因此,两个说明符在与输入说明符一起使用时的行为不同。对 %i 而言,012是10;对 %d 而言,012就是12。
    %d 取整数值作为有符号十进制整数。它接受负值和正值,但值应为十进制,否则将打印垃圾值。(注意:如果输入是八进制格式,如012,那么 %d 将忽略 0 并将输入视为 12)考虑以下示例。
    %i 取十进制、十六进制或八进制类型的整数值。要输入十六进制格式的值 - 值前面应添加“0x”,输入八进制格式的值 - 值前面应添加“0”。


指针 常见错误:
段错误 -- Segmentaion fault --  一般都是 野指针

test  :

使用指针初始化函数来初始化数组:
#include<stdio.h>


void initArr(int *parr,int len)
{
    int i;
    for(i=0;i<len;++i)
    {
        printf("请输入第%i个数:",i+1);
        scanf("%d",&parr[i]);
    }
    
    
    
}
void printArr(int  *parr,int len)
{
    int i;
    for(i=0;i<len;++i)
    {
        printf("%i ",parr[i]);
        
    }
    puts("");
}

int main()
{
    int arr[5];
    int len =sizeof(arr)/sizeof(arr[0]);
initArr(arr,len);
printArr(arr,len);
    
return 0;
}

test2  -- 数组翻转  

#include<stdio.h>


void initArr(int *parr,int len)
{
    int i;
    for(i=0;i<len;++i)
    {
        printf("请输入第%i个数:",i+1);
        scanf("%d",&parr[i]);
    }
    
    
    
}
void printArr(int  *parr,int len)
{
    int i;
    for(i=0;i<len;++i)
    {
        printf("%i ",parr[i]);
        
    }
    puts("");
}
void swap(int *a,int *b)
{
    int t=*a;
    *a=*b;
    *b=t;
}

void rollArr(int *parr,int  len)
{
    int  i;
    for(i=0;i<len>>1;++i)
    {
        swap(&parr[i],&parr[len-1-i]);
        
    }
    
    
}

int main()
{
    int arr[6];
    int len =sizeof(arr)/sizeof(arr[0]);
initArr(arr,len);
puts("数组翻转前");
printArr(arr,len);
rollArr(arr,len);
    
puts("数组翻转后");
printArr(arr,len);
    
return 0;
}

----------------------------

二维数组

基础认知:


二维数组的地址:

a[3][4]
a -- 父数组
a[0] a[1] a[2]  -- 子数组 ,每个里面有4个元素

偏移量


arr+1  -- 从a[0]->a[1]  --4* int=16  -- 行
a[0]+1 --从a[0][0]->a[0][1] --1*int =4   -- 列

数组首地址 2 种表达;
1.数组名也是数组元素的首地址--arr
2.数组第一个元素的地址--&arr[0]

a[1]   --  &a[1][0]

对二维数组解引用  -- *a = a[0] --第一行的元素的数组

a[i]  等效于 *(a+i)


#include<stdio.h>

int main()
{
int arr[3][4]={{11,22,33,44},{12,13,14,15},{44,55,66,77}};    
    printf("arr 是父地址: %p, 偏移1后的地址:%p\n",arr,arr+1);
    printf("arr[0] 是子数组地址: %p, 偏移1后的地址:%p\n",arr[0],arr[0]+1);
    printf("arr[0] 是子数组地址: %p, 偏移1后的地址:%p\n",*(arr+0),*(arr+0)+1);    
    return 0;
}


#include<stdio.h>

int main()
{int i,j;
int arr[3][4]={{11,22,33,44},{12,13,14,15},{44,55,66,77}};    
    
    for(i=0;i<3;++i)
        for(j=0;j<4;++j)
        {
                       printf("address:%d\tdata:%d\n",&arr[i][j],arr[i][j]);
            printf("address:%d\tdata:%d\n",arr[i]+j,*(arr[i]+j));
            printf("address:%d\tdata:%d\n",*(arr+i)+j,*(*(arr+i)+j));
            
            printf("=================================================\n");
        }
    
    
    return 0;
}

总结:以下三种表达方式等效
&arr[i][j],arr[i][j]
arr[i]+j,*(arr[i]+j)
*(arr+i)+j,*(*(arr+i)+j)  // 从上一步: arr[i]= *(arr+i)


表格总结


===================================
表示形式                                     含义                                                地址
a                                                  二维数组名,0行首地址-a[0]          2000      
a+1   &a[1]                                 1行首地址                                        2016 
a[0] *(a+0) *a                             0行0列元素地址                               2000
a[1]  *(a+1)                                1行0列元素地址                                2016
a[1]+2 *(a+1)+2 &a[1][2]          1行2列元素地址                               2024
 
*(a[1]+2) *(*(a+1)+2 )  a[1][2]    1行2列元素的值                  上表元素值13

===================================


 

数组指针:

产生背景:

对于二维数组 ,如果我们想用指针遍历他的所有元素:
1. int *p=&arr[0][0];  *p++;---   因为二维数组也是连续的内存空间,一直往后找即可
2. int *p=arr;  *p++;      -- 不推荐使用 ,会出发warning,因为虽然arr是第一行首地址,但arr+1 会产生歧义 无法做到一次跳一个子数组
概念: 定义一个指针,指向一个数组

如果我们需要实现arr++ 我们可以使用数组指针
int (*p)[4] = arr;  //这时候才不会报错

#include<stdio.h>

int main()
{int i,j;
int arr[3][4]={{11,22,33,44},{12,13,14,15},{44,55,66,77}};    
//int *p=&arr[0][0];
//int *p=arr;    
int (*p)[4]=arr;

    for(i=0;i<3;++i){
        for(j=0;j<4;++j)
        {
           printf("%d ",arr[i][j]);
        
        }
        puts("");
    }
    
    
    return 0;
}


===================================

二维数组 传参 -- 传的是数组指针  int (*p)[4]


test

给下标,找出二维数组的数


#include<stdio.h>

void tipsInput(int *line,int *row)
{
    puts("请输入你要查找的行列号:");
    scanf("%d%d",line,row);
    
}

int getResult(int (*p)[4],int line,int row)
{
    //return p[line][row];
    return  *(*(p+line)+row);
}


int main()
{
    int line,row,data;
    int arr[3][4]={{11,22,33,44},{12,13,14,15},{44,55,66,77}};    
    //输入提示
    tipsInput(&line,&row);
    //找到对应的数
    data=getResult(arr,line,row);
    //输出  -- 打印
     printf("第%d行%d列的数是:%d\n",line,row,data);


    
    
    
    return 0;
}

===================================


函数指针  


概念:  


也叫函数地址 , 如果程序中定义了一段函数,编译系统为函数代码 统一分配一段存储空间,
这段存储空间的起始地址(入口地址)称为这个函数的指针

如何定义: int (*p)(int  a,intb)   -- 第二个()的参数根据需要自行选择
//为什么需要 第一个()  -- 因为() 的优先级高于 *,我们要定义指针变量就得先() 

类比:

变量的两种访问方式:
1.直接访问  -- 变量名
2.间接访问 -- 指针

函数的两种访问方式:
1.直接访问  -- 函数名
2.间接访问 -- 指针指针

优点:=根据程序执行的不同情况,调用不同的函数  -- 类似java 接口

test:

#include<stdio.h>

void printW()
{
    printf("Hello World!\n");
}
int increaseF(int data)
{
    return ++data;
}

int main()
{
    void (*p1)()=printW; //定义函数指针 指向函数
     int (*p2)(int data)=increaseF;    
     (*p1)();  // 利用函数指针调用函数
    printf("含参数的函数指针测试%d\n",(*p2)(100));
    
    
    return 0;
}

//注意: 如果函数体里面用不到参数,可以只写类型,不写参数名(int a)->(int)

线程里面的回调函数  -- 底层就是用函数指针实现

输入1,2,3 分别代表求 a,b的max,min,和
思路 -- 考察函数指针 处理多个函数 的不同情况 3中情况的 返回类型 和参数 类型相同,全放到函数指针操作即可


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

int getMax(int a,int b)
{
    return a>b?a:b;
}
int getMin(int a,int b)
{
    return a<b?a:b;
}
int getSum(int a,int b)
{
    return a+b;
}

int dataHandle(int a,int b,int (*pfun)(int,int))
{
    return (*pfun)(a,b);
    
}
int main()
{
    int t,a,b;
    int (*pfun)(int a,int b);
    puts("请输入两个数字:");
    scanf("%d%d",&a,&b);
    puts("请选择你的操作:(1.求最大值 2.求最小值 3.求和)");
    getchar();
    scanf("%d",&t);
    switch(t)
    {
        case 1:
        pfun=getMax;
        break;
            case 2:
        pfun=getMin;
        break;
            case 3:
        pfun=getSum;
        break;
        default: 
        puts("错误输入,请重新输入");
        exit(-1);
        break;
    }    
    int res=dataHandle(a, b,pfun);
    printf("处理结果:%d\n",res);
    return 0;
}

===================================


指针数组  


概念:   

存放元素类型为指针的数组
定 义 int *p[4]; 

进化    -- 函数指针数组 int (*pfun[3]) (int ,int)  -- 第一个 () 决定类型

指针如果没有初始化  -- 得到的就是野指针

    int t,a,b;
    int (*pfun[3])(int a,int b)={getMax,getMin,getSum};
    puts("请输入两个数字:");
    scanf("%d%d",&a,&b);
    puts("请选择你的操作:(1.求最大值 2.求最小值 3.求和)");
    for(t=0;t<3;++t)
    {
    printf("%d\n",(*pfun[t])(a,b));
    }


===================================


指针函数 :

概念;


返回指针值的函数: int *fun(int a,int b)

解读:

int *p;  --  int 类型的指针变量
int* p; -- 定义一个变量 类型是 int*

 指针强调 : 起始地址  和  偏移值


===================================


二级指针 


套娃 --指针套指针 --  一次拿不到 data,还要接着访问的时候就 多 *一次

#include<stdio.h>

int main()
{
    int data=10;
    int *p=&data;
    printf("data的地址:%p\n",&data);
    printf("p保存data的地址:%p 内容是:%d\n",p,*p);
    
    //想要通过p访问data
    int **p2=&p;
    printf("p2保存的是p的地址:%p    内容*p2是%p\n",p2,*p2);
    printf("**p2访问data的数值:%d\n",**p2);
    
    
    int ***p3=&p2;
    printf("p3保存的是p2的地址:%p    内容*p3是%p\n",p3,*p3);
    printf("***p3访问data的数值:%d\n",***p3);
    
    
    return 0;
}

// 类似  swap 的test:
 

#include<stdio.h>

void pSwap(int *p)
{
    p=(int *)0x000000000061FE2B;
    printf("%p\n",p);
}

int main()
{
    int a=100;
    int *p;
    pSwap(p);
    printf("%p\n",p);
    
    return 0;
}


//我们发现 得到的 指针p不一样, 因为p是作为形参传进去不能直接改变他的值
if 要修改值,我们可以:
1. 传入 int **p2  -- 传实参 -- 要修改他就传递他的地址进来
2.return int* 类型 让他接收


#include<stdio.h>

void pSwap(int **p2)
{
    *p2=(int *)0x000000000061FE2B;
    printf("2 %p\n",*p2);
}
int* ppSwap(int *p2)
{
    p2=(int *)0x000000000061FE2B;
    return p2;
}


int main()
{
    int a=100;
    int *p;
     p=ppSwap(p);
     printf("%1 %p\n",p);
    pSwap(&p);
    printf("3 %p\n",p);
    
    return 0;
}


===================================


二级指针和二维数组 差异:


二维数组 等价的指针是 数组指针 int arr[][4] -- int (*p)[4];

不能 简单的 将 二级指针 指向 二维数组:
such as:   int arr[3][4];     int **p=arr;
// 这样只是将arr 的地址 给到 二级指针 p,但是*p,不等价于 *arr,同样 **p也不能修改

if 非要操作 ,要以 数组指针作为桥梁: 
int (*p1)[4]=arr;  int **p2=&p1;  这个时候 **p2就可以操作data了

#include<stdio.h>

int main()
{
    int arr[3][4]={
        {55,66,77,88},{66,55,34,89},{23,55,78,82}
    };
    int (*p1)[4]=&arr;
    int **p2=&p1;
    **p2=123;
    printf("%d\n",arr[0][0]);
    
    
    return 0;
}

===================================


指针 检验:


1.一个整形数:  int a;
2.一个指向整形的指针: int *a;
3.一个指向指针的指针,他指向的指针指向一个整形数: int **a;
4.一个有10个整数的数组: int a[10];
5.一个有10个指针的数组,每个指针都指向一个整形数: int *a[10];
6.一个指向有10个整形数的数组的指针:  int (*a)[10];
7.一个指针的指针,被指向的指针指向有10个整形数的数组: int (**a)[10];
8.一个指向数组的指针,该数组有10个整形指针: int *(*a)[10];
9.一个指向函数的指针,该函数有一个int参数,返回值也是int --  int (*p)(int)
10.一个有10个指针 的数组 ,每个指针指向一个函数 ,该函数有个整形参数,并返回一个整形数 int (*p[10])(int);
11.一个函数的指针,指向的函数类型有两个整形参数并且返回一个函数指针的函数,
   返回函数指针指向有一个整形参数放回整形数的函数  -- int (* (*a)(int,int))(int);


 

  • 25
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值