【C语言从入门到入土】第六章 指针(下)

4.通过指针引用数组,重要面试

4.1定义一个指针变量指向数组

01指向数组首元素的地址

在这里插入图片描述

02等于数组名:指向数组起始位置

在这里插入图片描述

#include <stdio.h>
int main()
{
 01
   /*回顾我们之前的写法
    *    int a =10;
    *    int *p;
    *    p =&a;    */
 02
   //现在我们变成了数组
    int arr[3] = {1,2,3};
    int *p;
    01.p = &arr[0];//指针指向数组的首地址
    //还有另外一种写法
    02.p =arr;   //等于整个数组名也是取的数组的首地址,,,注意前面不要 + &
    printf("address of a is %p\n ",&a);//指向一个固定的地址
	//数组名就是数组的首地址,数组的首地址就是首个元素的地址
	printf("address of p is %p\n",p);
    return 0;
}
4.2指针增量和数组的关系

我们知道数组在一个内存中是一个连续的地址,那么指针的增量与数组之间又存在着什么关系呢?

在这里插入图片描述

#include <stdio.h>
int main()
{
  int arr[3] = {1,2,3};
  int *p;
  p =arr;
printf("第0元素的数值是 %d\n",*p);
printf("第1元素的数值是 %d\n",*(p+1));//这里我们的+1并不是值+1,而是指针偏移,类型为整型,偏移四个字节,字符型,偏移一个字节
printf("第2元素的数值是 %d\n",*(p+2));//注意必须要加括号,下面的实操只是凑巧加起来是那个数,*p会首先结合然后再相加
    return 0;
}

在这里插入图片描述

但是通过上面的方法有没有感觉很蠢,如果他有一百个元素呢?难道需要输入一百次嘛?所以我们出现了现在的for循环

#include <stdio.h>
int main()
{
  int arr[3] = {1,2,3};
  int *p;
  p =arr;
  for(int i =0;i<3;i++){
    printf("第%d元素的数值是 %d\n",i,*(p+i)); 
    printf("地址是 %p\n",(p+i)); 
  }
    return 0;
}

在这里插入图片描述

4.3通过指针引用数组
#include <stdio.h>
int main()
{
  int arr[3] = {1,2,3};
  int *p;
  p =arr;
 //方法一
    for(int i =0;i<3;i++){
    printf("%d\n",*p++); //这个的意思是指针先*取值,然后在此基础上面再+1
  }
  p =arr;  //这里我们需要注意的是重新给指针变量p 赋值,要不然经过上面的一系列操作,指针已经跑飞了
  //方法二
    for(int i =0;i<3;i++){
    printf("%d\n",*p);
    p++;   //指针进行 偏移
  } 
    return 0;
}

在这里插入图片描述

1.下标法

就是之前学过的数组的下标法遍历arr[ i ];

2.指针法
2.1偏移

上面的例子都是说的偏移,在此不再赘述

2.2取内容
  1. 指针当作数组名,下标法访问
  2. 数组名拿来加
///这个与上面的指针偏移不一样,所以不会跑飞,注意区别他们的不同的地方
#include <stdio.h>
int main()
{
    int arr[3]={1,2,3};
    int *p =arr;
   //方法一:
    for(int i =0;i<3;i++)
    {
        printf("第%d个元素是%d\n",i,p[i]);//见怪不怪哈哈哈,他确实可以这样写
    }
    //方法二:
    for(int i =0;i<3;i++)
    {
        printf("第%d个元素是%d\n",i,*(p+i));//见怪不怪哈哈哈,他确实可以这样写
        printf("第%d个元素是%d\n",i,*(arr+i));//见怪不怪哈哈哈,他确实可以这样写
    }    
    return 0;
}

在这里插入图片描述

数组名和指针的区别

1.arr++可行否??

///这个与上面的指针偏移不一样,所以不会跑飞,注意区别他们的不同的地方
#include <stdio.h>
int main()
{
    int arr[3]={1,2,3};//那这个数组是一个常量
    int *p =arr;//首先p是一个保存地址的变量,那他保存的地址是可以改的,这叫做变量
   //方法一:
    for(int i =0;i<3;i++)
    {
        printf("第%d个元素是%d\n",i,*p++);//,他这样可以写
    }
    //方法二:
    for(int i =0;i<3;i++)
    {
        printf("第%d个元素是%d\n",i,*arr++);//那这样呢?数组常量可以吗?
    }    //编译不过,指针常量
    return 0;
}

我们看到arr++是用不了的,

在这里插入图片描述

2.sizeof的使用

#include <stdio.h>
int main()
{
    int arr[3]={1,2,3};
    int *p =arr;
    printf("sizeof arr is %d\n",sizeof(arr));//一个元素四个字节,一共三个,所以12个字节
    printf("sizeof arr is %d\n",sizeof(p));//在OS中,用8个字节来表示一个地址
    printf("sizeof int is %d\n",sizeof(int));
    printf("sizeof pointer is %d\n",sizeof(int *));//在OS中,用8个字节来表示一个地址
    printf("sizeof pointer is %d\n",sizeof(char *));//在OS中,用8个字节来表示一个地址
    return 0;
}

在这里插入图片描述

两种方法效率对比

在这里插入图片描述

编程案例

1.函数封装数组初始化,遍历

练习函数数组指针结合

//首先来回顾一下我们以前写的代码
#include <stdio.h>
void initarr(int arr[],int size)
    //第二种传递形式参数的方法
    void printfarr(int *parr,int size)
//练习函数指针数组结合
{
    for(int i=0;i<size;i++)
    {
       printf("请输入第%d个元素\n",i+1);
       scanf("%d",&arr[i])
           /*第二种方法
           scanf("%d",parr);//因为这个本身就是地址了
     指针偏移      parr++;     */
    }
}
void printfarr(int arr[],int size)
{
    for(int i=0;i<size;i++)
    {
       printf("%d\n",arr[i]);
    }
}
int main()
{
    int arr[5];
    int size = sizeof(arr)/sizeof(arr[0]);
    
    initarr(arr,size);//实际参数,数组的首地址:名,元素的地址
    printfarr(&arr[0],size);//那么我们知道,指针变量是存放地址的变量
    return 0;
}
#include <stdio.h>
void initarr(int *parr,int size)
//练习函数指针数组结合
{
    for(int i=0;i<size;i++)
    {
       printf("请输入第%d个元素\n",i+1);
       scanf("%d",parr++);//因为这个本身就是地址了 
    }
}
void printfarr(int *parr,int size)
{
    for(int i=0;i<size;i++)
    {
          printf("%d\n",*parr++);
    }
}
int main()
{
    int arr[5];
    int size = sizeof(arr)/sizeof(arr[0]);
    
    initarr(arr,size);//实际参数,数组的首地址:名,元素的地址
    printfarr(&arr[0],size);//那么我们知道,指针变量是存放地址的变量
    return 0;
}
2.将数组中的n个元素按逆序存放,函数封装

如何逆序存放呢? 第0个跟第4个换,第1个跟第3个换,2不动即可,,直接做一个临时变量,茶杯法直接交换即可

如果是奇数的话

在这里插入图片描述

那如果是偶数呢?结果同样成立

在这里插入图片描述

#include <stdio.h>
void initarr(int *parr,int size)
//练习函数指针数组结合
{
    for(int i=0;i<size;i++)
    {
       printf("请输入第%d个元素\n",i+1);
       scanf("%d",parr++);//因为这个本身就是地址了 
    }
}
void reversedarr(int *parr,int size)
//练习函数指针数组结合
{
    for(int i=0;i<size/2;i++)
    { 
       int j=size-1-i;
        int tmp;
        tmp = parr[i];
        parr[i] = parr[j];
        parr[j] = tmp;
        //以下是对上面的函数用指针来实现,自我感觉变抽象了哈哈哈哈
        tmp = *(parr+i);
        *(parr+i) = *(parr+j);
        *(parr+j) = tmp;
    }
	putchar('\n');
}
void printfarr(int *parr,int size)
{
    for(int i=0;i<size;i++)
    {
          printf("%d  ",*parr++);//因为这个本身就是地址了
    }
}
int main()
{
    int arr[5];
    int size = sizeof(arr)/sizeof(arr[0]);
    
    initarr(arr,size);//实际参数,数组的首地址:名,元素的地址
    printfarr(&arr[0],size);//那么我们知道,指针变量是存放地址的变量
    reversedarr(arr,size);
    printfarr(&arr[0],size);
    return 0;
}

5.指针与二维数组

父子数组,为了研究清楚地址的概念,把二维回归到一维数组

二维数组本质还是数组,不同点是数组元素还是个数组(子数组),以往的我们 int arr[ ]={1,2,3};如果arr+1 = 1;那么这次我们,如果是a[0]+1呢?如下图,a[0]是第一行第一列的地址,a[0]+1=3;

在这里插入图片描述

首地址的表示:1.数组名 2.首个元素的地址。

**下面我们来思考几个问题?? 1.a是谁的地址 2.a[0]又是谁的地址 3.a跟 (a+0)呢?

  1. a是父数组,地址是第一整行的地址,a+1向下偏移一行,4*4=16个字节
  2. a[0]是子数组,地址为第一行第一列的地址,
  3. 以往的 int *p = arr; int arr[ ]={1,2,3}, 我们知道指针变量是存放地址的变量,那么arr就是数组的首地址, *arr就是取内容之后,即 *arr=1;所以 *a就是第一行第一列的地址,与a[0]等价,也与 *(a+0)等价

在这里插入图片描述

那么a[0]+1又是什么意思呢?

  1. a[0]+1第0行第一列的地址,是地址的意思,,*(a+0)+1
  2. 也可以说是第0个子数组的第1个元素的地址
  3. 而第0个子数组的第1个元素表示方式是a[0] [1],不要乱

在这里插入图片描述

//一定要记住arr[0]=*arr=*(arr+0)
#include <stdio.h>
int main()
{
    int arr[3][4] ={{1,2,3,4};{5,6,7,8};{9,10,11,12}};
    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,*(arr+0)+1);
    return 0;
}

在这里插入图片描述

可以看到父数组偏移了16个字节,子数组是4个字节

树立认知

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

//一定要记住arr[0]=*arr=*(arr+0)
#include <stdio.h>
int main()
{
    int arr[3][4] ={{1,2,3,4};{5,6,7,8};{9,10,11,12}};
    for(int i= 0;i<3;i++){
        for(int j =0;j<4;j++)
        {//这是我们以前的写法,工作后也可以这样写,但面试就要用指针了
           printf("address:0x%p,data:%p\n",&arr[i][j],arr[i][j]); //arr[0]+0 = &arr[0][0]
           printf("address:0x%p,data:%p\n",arr[i]+j,*(arr[i]+j)); //笔试题考,这个就是指针的偏移来取到地址
           printf("address:0x%p,data:%p\n",*(arr+i)+j,*(*(arr+i)+j)); //把arr[0]=*(arr+0)替代,是不是傻眼了
        }
    }
    return 0;
}

在这里插入图片描述

小总结(嵌入式工程师笔试题会考)

在这里插入图片描述

6.数组指针

数组指针,一个指向数组的指针

指针数组,一堆指针组成的一个数组

在这里插入图片描述

#include <stdio.h>
int main()
{
 	int arr[3][4] ={{11,22,33,44},{55,66,77,88},{44,55,66,77}};
    //那么之前我们说的arr++,是否可以呢?arr++增加的数值是一整个数组(arr[0][0],arr[1][0]),
    //而p++是单个偏移(arr[0][0],arr[0][1])
    int i,j;
    int *p;
    //01.
    p = arr;
    //02.
    p = &arr[0][0];
    for(i=0;i<3;i++)
    {
        for(j=0;j<4;j++)
        {
            printf("%p\n",arr++);
            printf("%p\n",p++);
        }
    }
    return 0;
}

在这里插入图片描述

例题

输出二维数组任意行列的数

#include <stdio.h>

void tips_input(int *hang,int *lie)
{
    printf("输入你想要的行列\n");
    scanf("%d%d",&hang,&lie);
    puts("done!  ");  
}
int get_data(int (*p)[4],int hang,int lie)
{
    int data;
    data = *(*(p+hang)+lie);
    return data;
    //第二种简单的写法  return arr[hang][lie];
    
}
int main()
{
    int arr[3][4] = {{11,22,33,44},{55,66,77,88},{99,01,02,03}};
    int hang,lie;
    int data;
    //1.提醒用户输入想要的行列值
    tips_input(&hang,&lie);
    //2.找到行列值所对应的数
    data = get_data(arr,hang,lie);
    //3.打印出来
    printf("第%d行,第%d列的数为%d",hang,lie,data);

    return 0;
}

7.函数指针

指向一个函数地址的指针,类似于一个函数的地址入口,定义“函数地址”,数组名是一个地址,那么函数名也是一个地址

在这里插入图片描述

1.如何定义一个函数指针

跟普通变量一样,

int a;
int *p;

char c;
char *p;

int getData(int a, int b);//函数传参
int (*p)(int a,int b);//函数指针的调用
//如何使用函数指针
#include <stdio.h>
void printf_hello()
{
    printf("欢迎来到我的世界\n");
}
int main()
{
    printf_hello();//这是以前我们调用函数
    void (*p)();  		 //1.定义一个函数指针
    p = printf_hello;	 //2.指针指向函数
    (*p)();				//3.把函数指针里面的内容调用出来
//函数调用概念和变量一样
    return 0;
}
//OK,现在我有两个函数,类型不同,有两种访问的方式,1.直接访问(函数调用) 2.间接访问(函数指针)
//下面来着重介绍一下函数指针的用法
#include <stdio.h>
void printf()
{
    puts("欢迎来到我的世界");
}
int idata(int dataone)
{
    return ++dataone;
}
int main()
{
    //1.定义指针  2.指针指向函数  3.调用函数指针
    void (*p1)();
    int (*p2)(int a);//注意实参也不要丢了啊啊啊啊(原先丢了一次编译会出错)
    p1 = printf;
    p2 = idata;
    (*p1)();
    printf("将P2呈现出来是%d\n",(*p2)(12));
    return 0;
}
2.好用之处

根据程序运行过程的不同情况,调用不同的函数(Java接口)

练习题

在这里插入图片描述

#include <stdio.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 dataHandler(int a,int b,int(*p)(int ,int))//我们强调的是函数类型,而具体的形参名如果用不到的话可以不定义
{
    int ret;
    ret = (*p)(a,b);
    return ret;
}
int main()
{
    int a = 10;
    int b = 20;
    int cmd;
    int ret;
    //1.定义函数指针
    int (*pfunc)(int ,int );
    //2.根据你输入的值来决定指针指向那个函数
    printf("请输入1(求大者),2(求小者),3(求和)\n");
    scanf("%d",&cmd);
    switch(cmd)
    {
        case 1:   pfunc = getMax;         break;
   	    case 2:   pfunc = getMin;         break;
        case 3:   pfunc = getSum;         break;
        default: 
            printf("输入错误。@请输入1(求大者),2(求小者),3(求和)");
            //exit(-1);
        break;
    }
    //3.将函数指针调用出来
   // ret = (*pfunc)(a,b);//这是一种简单的写法,还有一种封装函数的方法,了解一下
    ret = dataHandler(a,b,pfunc);
	printf("result of is %d",ret);
    return 0;
}

回调函数的底层逻辑,

  1. 线程 int pthread_create(pthread_t *id,const pthread_attr_t attr, voidstart_rtn)(void), void *restrict arg);
  2. QT的信号与槽

8.指针数组

1.定义,注意和数组指针的区别(面试会考)

简单来说就是,一个由指针组成的数组,

在这里插入图片描述

//数组指针的使用
#include <stdio.h>
int main()
{
    int a,b,c,d;
    a = 10;
    b = 20;
    c = 30;
    d = 40;
    int *p[4] = {&a,&b,&c,&d};
    for(int i = 0;i<4;i++)
    {
     printf("%d\n",*p[i]); 
    }
    return 0;
}
2.函数指针的使用
//函数指针数组
#include <stdio.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 main()
{
    int a = 10;
    int b = 20;
    int cmd;
    int ret;
    //1.定义函数指针
//这个由上面得来,那我如果想要函数指针数组呢?
    int (*pfunc[3])(int ,int ) ={getMax,getMin,getSum};
    //3.将函数指针调用出来
    for(int i = 0;i<3;i++)
    {
        ret = (*pfunc[i])(a,b);//这是一种简单的写法,还有一种封装函数的方法,了解一下
        printf("result[%d] of is %d",i,ret);
    }
	
    return 0;
}

9.指针函数

一个返回值是指针的函数

概念

在这里插入图片描述

练习题:在这里插入图片描述

#include <stdio.h>
int* get_pos_person(int pos,int (*p)[4])
{
    int *p2;
    p2 = (int*)(p+pos);//因为这两个变量的类型不一样,所以要强转一下
    return p2;
}
int main()
{
    int arr[3][4] ={{11,22,33,44},{15,23,54,84},{61,51,48,25}};
    int pos;
    int *ppos;
    printf("请输入想看的学生号数(0,1,2):\n");
    scanf("%d",&pos);
    ppos = get_pos_person(pos ,arr);
    for(int i =0;i<4;i++)
    {
        printf("他的成绩分别为%d\n",*(ppos++));
    }
    return 0;
}

10.二级指针

一级指针指向一个变量,存放他的地址;那么这个一级指针它本身也有一个地址,如果我再定义一个指针来存放这个一级指针的地址,那么这个指针就是二级指针,以此类推可延伸至三级乃至多级指针

认知考虑的时候,其实所有东西跟一级指针一样,写法:int **p;

#include <stdio.h>
int main()
{
    int data = 100;
    int  *p  = &data;
    int **p2 = &p;//二级指针
    
    printf("data的值为%d\n",data);
    printf("data本身的地址为%p\n",&data);
    
    printf("p存放的值为%d\n",*p);
    printf("p存放的值为%p\n",p);
    printf("p的地址为%p\n",&p);
    
    printf("p2存放的值为%p\n",*p2);
    printf("p2存放的值为%p\n",p2);
    printf("p2的地址为%p\n",&p2);
    printf("p2存放的地址的值(也就是一级指针存放的值为)%d\n",**p2);
    return 0;
}

在这里插入图片描述
差别就是保存的是指针变量的地址。当你通过函数调用来修改调用函数指针指向的时候,就像通过函数调用修改某变量的值的时候一样

在这里插入图片描述

//1.如果我们不返回一个int型指针呢?只有一个空类型,应该怎么传参呢?(此代码为上面已用过的)
//2.当你通过函数调用来修改调用函数指针指向的时候,就像通过函数调用修改某变量的值的时候一样
#include <stdio.h>
//int* get_pos_person(int pos,int (*p)[4])
void get_pos_person(int pos,int (*p)[4],int **ppos)
{
    //第一个括号是强转的意思。,转为指针变量
    *ppos = (int *)(p+pos);//我们修改的ppos的地址,用一个二级指针来承接一级指针的地址然后改动一级指针的地址
}
int main()
{
    int arr[3][4] ={{11,22,33,44},{15,23,54,84},{61,51,48,25}};
    int pos;
    int *ppos;
    printf("请输入想看的学生号数(0,1,2):\n");
    scanf("%d",&pos);
    get_pos_person(pos ,arr,&ppos);
    for(int i =0;i<4;i++)
    {
        printf("他的成绩分别为%d\n",*(ppos++));
    }
    return 0;
}

二级指针不能简单粗暴指向二维数组

在这里插入图片描述

11.总结

中小公司大概率考题

在这里插入图片描述

相关推荐

  1. C语言入门入土】第一前言

    2024-06-09 15:22:02       7 阅读
  2. C++入门精通 (STL常用算法)

    2024-06-09 15:22:02       27 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-09 15:22:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-09 15:22:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-09 15:22:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-09 15:22:02       18 阅读

热门阅读

  1. 数学学习基本理念与方法

    2024-06-09 15:22:02       10 阅读
  2. 看屏幕久了如何休息眼睛

    2024-06-09 15:22:02       9 阅读
  3. C++的算法:欧拉道路与欧拉回路

    2024-06-09 15:22:02       9 阅读
  4. C++细节梳理之模版

    2024-06-09 15:22:02       8 阅读
  5. Websocket前端与后端:深度探索与实战应用

    2024-06-09 15:22:02       9 阅读
  6. 基于springboot的欢迪迈手机商城源码数据库

    2024-06-09 15:22:02       9 阅读
  7. Python Number(数字)

    2024-06-09 15:22:02       8 阅读
  8. web前端读书心得:探索技术的深度与广度

    2024-06-09 15:22:02       9 阅读
  9. 游戏心理学Day08

    2024-06-09 15:22:02       9 阅读