C语言指针学习

2023-11-06

开始好好学习C语言啦,指针是C语言比较难的地方,但是非常重要,所以单独在此记录一下。有执念的人最可怕,一定要好好学习哇!!!

1、指针是什么?

  • 含义:
    • 指针其实就是一个十六进制数,他也有自己的内存地址,只不过他存储的数值是所指向变量的内存地址;
  • 类型:
    • 所有的实际类型所对应的指针的类型都是一样的,都是一个代表内存地址的十六进制数,唯一的区别是其指向的变量或常量的数据类型不同;
  • 作用:
    • 间接寻址,传递数据 ;
  • 基本用法:
    • int a; int *p=&a ;
    • 此时 p=&a(a的内存地址) , *p=a(a的数值);
    • 给指针变量赋值时,要将一份数据的地址赋给它,不能直接赋给一个整数,例如int *p = 1000;是没有意义的,使用过程中一般会导致程序崩溃。

2、null指针

  • 如果没有地址可以给到指针时,建议置为null,如int *p=null; 其地址为0x0, 但是大多情况下程序不允许服务地址为0的内存,因此如果指针指向0,则表示不指向任何东西;

3、指针的运算

  • 指针在递增和递减时跳跃的字节数取决于指针所指向变量数据类型长度,比如 int 就是 4 个字节,char是1个字节。
  • ++:如本来是1000 ,然后p++,就变成了1004,再++,就变成了1008

4、数组指针

一维数组指针

  • 是一个指向一维数组的指针,一开始的时候是指向数组的第一个元素;
  • int str[]={2,6,8}; int *p=str; 不需要使用取地址符,此时p=str=&str[0], 指向数组的第一个元素;
  • p+1,就相当于数组的索引增加了1,此时p指向str[1]的地址;其实str++也是一样的;
  • * (p+1),就相当于str[1],即数组的第二个元素的数值;
  • 注意:*p+1表示的是将*p这个字符的ASCII码加1。如*p本来等于str[0],即为数组里面的第一个元素2,而2的ASCII码为50,然后50+1=51,那么*p+1的值为3,而不是数组里面的第二个元素6。

二维数组指针

  • 是一个指向二维数组的指针,一开始的时候是指向数组的第一行元素;
  • int str[2][3]={{2,6,8},{1,2,3}}; int (*p)[3]=str; 不需要使用取地址符,此时p=str[0]=&str[0], 指向数组的第一行元素;
  • p+1,就相当于数组的行标增加了,此时p指向第二行;
  • *(p+1),表示指向第二行的第一个元素;
  • *(p+1)+1,表示指向第二行的第二个元素;
  • *(*(p+1)+1),表示第二行第二个元素的数值,很明显,增加一个 * 表示取地址上的数据;
  • 等价关系:

    str+i == p+i
    str[i] == p[i] == *(str+i) == *(str+i)
    str[i][j] == str[i][j] == *(str[i]+j) == *(str[i]+j) == ((str+i)+j) == ((str+i)+j)

  • 注意:指针数组和二维数组指针在定义时非常相似,只是括号的位置不同:

    int *(p1[5]); //指针数组,可以去掉括号直接写作 int *p1[5];
    int (*p2)[5]; //二维数组指针,不能去掉括号

5、指针数组

  • 数组里面每个元素都是一个指向int值指针:int *str[10];
  • 示例:
    const char *names[] = {
                    "Zara Ali",
                    "Hina Ali",
    };
    //等同于:
    char str[]={
                    "Zara Ali",
                    "Hina Ali",
    };
    const char*names[4];
    *names[0]=&str[0]=names;
    names[0]=str[0]=*names;
    names[1]=str[1]=*(names+1);
    

6、二级指针

  • int **p;
  • 是一个指向指针的指针,简单来说,二级指针的数值就是一级指针的地址,是一个十六进制数,而它的地址则是本身的地址;如图所示:
    (图片来源:菜鸟教程)

7、指针作为形参

  • 若传进去的是地址,则可以通过改变其指向来改变数值:
    void getSeconds(unsigned long *par);
    int main ()
    {
    unsigned long sec;
    getSeconds( &sec );//传地址,sec的值会随之变化
    /* 输出实际值 */
    printf("Number of seconds: %ld\n", sec );//12
    return 0;
    }
    void getSeconds(unsigned long *par)
    {
    /* 获取当前的秒数 */
    *par = 12;
    return;
    }
    
  • 还可以传入数组:
      #include<stdio.h>
      double avg1(int *arr,int size);
      int main(int argc, char const *argv[])
      {
          int str[]={1,2,3,4};
          double re=avg1(str,4);//传入数组,调用函数
          priintf("平均值:%f\n",re);
          return 0;
      }
      double avg1(int *arr,int s){
          int i=0;
          double avg;
          int sum=0;
          for ( i = 0; i < s; ++i)
          {
              sum+=(*arr++);//在这个地方,要注意指针所指向的是什么
              //也可以这样写:sum+=arr[i];  *arr就是arr[0],也是str[0]
          }
          avg=(double)sum/s;
          return avg;
      }
    

8、函数指针

  • 该函数的名称就是一个指向这个函数的首地址的指针;
  • int (*sum)(int a,int b);
  • 禁止的操作:
    • (1)禁止对指向函数的指针进行自增运算++;
    • (2)禁止对函数名赋值,函数名也不能用于进行算术运算。
  • 示例:fun1=&fun1=fun1=(&fun1),对于pfun1也是适用的。
    (不太明白,需要再多去了解一下)
    int fun1(int,int);
    int fun1(int a, int b){ 
        return a+b;
    }
    int main(){
        int (*pfun1)(int,int); 
        pfun1=fun1;//这里&fun1和fun1的值和类型都一样,用哪个无所谓 
        int a=(*pfun1)(5,7); //通过函数指针调用函数。
        //也可以这样调用:pfun1(5,7);
    }
    

9、指针函数

  • 该函数的返回值是一个指针;返回的可以是变量指针、数组指针、函数指针;
  • C 语言不支持在调用函数时返回局部变量的地址,除非定义局部变量为 static 变量。
  • int *sum(int a,int b) ;

10、函数指针数组

  • 数组里每个元素都是一个函数指针;
  • 稍微复杂,结合之前的指针数组来理解会好一些,普通的指针数组里面的每个元素就是一个普通的变量指针,而函数指针数组里面的元素是函数指针,简单的来说,就是指针类型不一样而已;
  • 示例:
    //现在有四个函数去处理文件,
    void open();
    void read();
    void write();
    void close();
    
    typedef void (*PF) ();//定义一个函数指针类型的别名
    //把上面的四种操作取地址放进一个数组中
    PF file_options[]={
        &open,&read,
        &write,&close
    };
    
    //file_options就是&file_options[0]也就是ip,因为ip指向file_options[0]。
    int *ip = file_options 
    //此时通过指针ip就可以来调用这四个操作了;
    //要注意的是:ip[1]的含义就和*(ip+1)一样,即为第二个函数read();
    
    类比一下数组指针去理解:
    int ia[4] = {0, 1, 2, 3};
    int *ip = ia;//ip=&ia[0],*ip=ip[0]=ia[0],*(ip+1)=ip[1]=ia[1]
    

11、回调函数

  • 某个函数的参数是一个函数指针 ,调用这个函数的时候我们传进去的实参是一个函数;
  • 我觉得简单一点来说,就是去通过一个函数去调用另外一个函数;可以使用一个总的函数去管理多个类似的函数,根据你传入的参数不同,从而帮你调用不同的函数。
    就好像,找代购买东西一样,你和物品之间,需要有一个联系者。
    你想买某样东西,你自己无法或者不想直接去买,那么就可以通过代购来帮你买到这个东西。代购会根据你想买的这个物品的名称,去到对应的地方买这个物品,然后再给到你。
你自己——>主函数,物品名称就是回调函数的名称
代购——>调用主函数的函数(中间函数)`在这里插入代码片`
买物品——>回调函数
  • 代码示例:
#include<stdio.h>
#include<stdlib.h>
//回调函数
int real_back(void)
{
   return rand();
}

//用来调用回调函数的函数
int use_back(int *a,int  (*fun2) (void) ) 
{
   *a=fun2();//在此调用回调函数 
   return 0;
}


int main(void)
{
   int a;
   //此时我们把回调函数real_back作为参数传入use_back函数
   use_back( &a, real_back);
   printf("%d \n",a);
   return 0;
}

13、总结

  • 常见指针变量的定义 定 义 含 义:
定  义 含  义
int *p; p 可以指向 int 类型的数据,也可以指向类似 int arr[n] 的数组。
int **p; p 为二级指针,指向 int * 类型的数据。
int *p[n]; p 为指针数组。[ ] 的优先级高于 *,所以应该理解为 int *(p[n]);
int (*p)[n]; p 为二维数组指针。
int *p(); p 是一个函数,它的返回值类型为 int *。
int (*p)(int,int); p 是一个函数指针,指向原型为 int func(int,int) 的函数。

14、 测试题

>指针学的差不多了,下面用几道比较经典的题目来看看掌握的如何吧!
  • 1、这是一道关于数组指针的题目,试着写出下面的运行结果是什么:
    #include <stdio.h>
    int main(){
        char str[20] = "c.biancheng.net";
       
        char *s1 = str;
        char *s2 = str+2;
       
        char c1 = str[4];
        char c2 = *str;
        char c3 = *(str+4);
        char c4 = *str+2;
        char c5 = (str+1)[5];

        int num1 = *str+2;
        
        printf("  s1 = %s\n", s1);
        printf("  s2 = %s\n", s2);
        
        printf("  c1 = %c\n", c1);
        printf("  c2 = %c\n", c2);
        printf("  c3 = %c\n", c3);
        printf("  c4 = %c\n", c4);
        printf("  c5 = %c\n", c5);
       
        printf("num1 = %d\n", num1);
        
        return 0;
    }
   








//运行结果:
  s1 = c.biancheng.net     
  s2 = biancheng.net
  c1 = a
  c2 = c
  c3 = a
  c4 = e
  c5 = c
num1 = 101
//如果全部正确,那就稍微得意一下下吧,学的还是挺不错的,如果不是,就还需要努努力!
1) str 既是数组名称,也是一个指向字符串的指针;指针可以参加运算,加 1 相当于数组下标加 1printf() 输出字符串时,要求给出一个起始地址,并从这个地址开始输出,直到遇见字符串结束标志\0。
s1 为字符串 str 第 0 个字符的地址,s2 为第 2 个字符的地址,所以 printf() 的结果分别为 c.biancheng.net 和 biancheng.net。
(注意:C语言标准规定,当数组名作为数组定义的标识符(也就是定义或声明数组时)、sizeof& 的操作数时,它才表示整个数组本身,在其他的表达式中,数组名会被转换为指向第 0 个元素的指针(地址)。)

2) 指针可以参加运算,str+4 表示第 4 个字符的地址,c3 = *(str+4) 表示第4个字符,即 'a'3) 其实,数组元素的访问形式可以看做 address[offset],address 为起始地址,offset 为偏移量:
c1 = str[4]表示以地址 str 为起点,向后偏移4个字符,为 'a';
c5 = (str+1)[5]表示以地址 str+1 为起点,向后偏移5个字符,等价于str[6],为 'c'4) 字符与整数运算时,先转换为整数(字符对应的ASCII码)。
num1 与 c4 右边的表达式相同,对于 num1,*str+2 == 'c'+2 == 99+2 == 101,
即 num1 的值为 101,对于 c4,101 对应的字符为 ‘e’,所以 c4 的输出值为 'e'
  • 2、一道关于指针数组和二级指针的题目:
    #include <stdio.h>
    int main(){
        char *lines[5] = {
            "COSC1283/1284",
            "Programming",
            "Techniques",
            "is",
            "great fun"
        };
        char *str1 = lines[1];
        char *str2 = *(lines + 3);
        char c1 = *(*(lines + 4) + 6);
        char c2 = (*lines + 5)[5];
        char c3 = *lines[0] + 2;
        printf("str1 = %s\n", str1);
        printf("str2 = %s\n", str2);
        printf("  c1 = %c\n", c1);
        printf("  c2 = %c\n", c2);
        printf("  c3 = %c\n", c3);
        return 0;
    }




//运行结果:
str1 = Programming
str2 = is
  c1 = f
  c2 = 2
  c3 = E
把代码改成下面这种形式,更加容易去理解:
    #include <stdio.h>
    int main(){
        char *string0 = "COSC1283/1284";
        char *string1 = "Programming";
        char *string2 = "Techniques";
        char *string3 = "is";
        char *string4 = "great fun";
       
        char *lines[5];
        lines[0] = string0;
        lines[1] = string1;
        lines[2] = string2;
        lines[3] = string3;
        lines[4] = string4;
        char *str1 = lines[1];
        char *str2 = *(lines + 3);
        char c1 = *(*(lines + 4) + 6);
        char c2 = (*lines + 5)[5];
        char c3 = *lines[0] + 2;
        printf("str1 = %s\n", str1);
        printf("str2 = %s\n", str2);
        printf("  c1 = %c\n", c1);
        printf("  c2 = %c\n", c2);
        printf("  c3 = %c\n", c3);
        return 0;
    }
首先我们要明白,char *lines[5]定义了一个指针数组,它的每个元素的类型都是char 。
在表达式中使用 lines 时,它会转换为一个类型为char **的指针,这样*lines就表示一个指向字符的指针,而**lines表示一个具体的字符,这一点很重要,一定要好好理解!

上面的题目中:
1)、lines[1]:它是一个指针,指向字符串string1,即 string1 的首地址。
 *(lines + 3):lines + 3 为数组中第 3 个元素的地址,*(lines + 3) 为第 3 个元素的值,它是一个指针,指向字符串 string3。
 
2)*(*(lines + 4) + 6)*(lines + 4) + 6 == lines[4] + 6 == string4 + 6,
表示字符串 string4 中第 6 个字符的地址,即 f 的地址,所以 *(*(lines + 4) + 6) 就表示字符 f。

3)(*lines + 5)[5]*lines + 5 为字符串 string0 中第 5 个字符的地址,即 2 的地址,
(*lines + 5)[5]等价于*(*lines + 5 + 5),表示第10个字符,即 24)*lines[0] + 2:lines[0] 为字符串 string0 中第 0 个字符的地址,即 C 的地址;
*lines[0]也就表示第 0 个字符,即字符 C。字符与整数运算,首先转换为该字符对应的 ASCII 码,
然后再运算,所以 *lines[0] + 2 = 67 + 2 = 6969 对应的字符为 E。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C语言指针学习 的相关文章

  • 在linux上替换动态库的内存变化分析

    在linux上替换动态库的内存变化 测试static变量 static string staticString 123 void queryFreezingMoney CFmlBuf inBuf CFmlBuf outBuf string
  • 使用chatGPT辅助编写测试用例

    目录 一 问答1 0版 二 问答2 0版 2 1 问答2 1 2 2 问答2 2 2 3 问答2 3 三 问答3 0版本 四 思考 个人简介 作者简介 大家好 我是凝小飞 软件测试领域作者 支持我 点赞 收藏 留言 最近开始学习和写作 请大

随机推荐

  • 前端基础(一):Jquery

    1 dom和jquery的互相转化 2 选择器 3 过滤器 4 DOM操作 1 dom和jquery的互相转化 dom转jquery dom对象 jquery转dom 方法一 a 0 方法二 a get 0 2 选择器 1 基本选择器 id
  • 记录一个iOS使用陀螺仪3d效果的抖动问题

    使用陀螺仪的时候 遇到一个问题 就是在拖动scrollView滚动的时候 3d效果的图片会抖动 实现3d效果的代码 void updateWithGravityX double gravityX gravityY double gravit
  • python中ord什么意思_python里ord

    广告关闭 腾讯云11 11云上盛惠 精选热门产品助力上云 云服务器首年88元起 买的越多返的越多 最高返5000元 一 ord函数介绍该函数是以单个字符作为参数 返回对应的ascll数值或者unicode值 如果所给的unicode字符超出
  • 创建多线程的四种方式

    目录儿 一 创建线程的四种方式 1 继承Thread类 2 实现Runnable接口 3 实现Callable接口 4 线程池 禁止使用 Executors 构建线程池 构建线程池的正确方式 一 创建线程的四种方式 1 继承Thread类
  • 大三,改变生活的一年

    国庆假期在偶然看到了去年假期画的stm32开发板的PCB 当时接触还没有一个月 想到了大三这一年来的点点滴滴 突然就想写下点什么 对于过去的一年的总结 又提醒自己还是给小白 要继续加油 首先我先说一下自己的情况 我是一个很普通的本科生 真的
  • 《人类染色体与染色体病》学习笔记

    染色质与染色体 染色质为细丝状 当细胞分裂时 染色质复制反复盘绕高度压缩 凝集形成兴泰特定的条状染色体 以保证DNA能准确分配到两个子细胞中 染色质和染色体的化学组成 DNA和组蛋白占染色质化学总量98 以上 染色质的种类 染色质由于其折叠
  • 消息通知系统

    using UnityEngine using System Collections using System Collections Generic public delegate void NotificationDelegate No
  • 华为OD机试真题-单词接龙-2023年OD统一考试(B卷)

    题目描述 单词接龙的规则是 可用于接龙的单词首字母必须要前一个单词的尾字母相同 当存在多个首字母相同的单词时 取长度最长的单词 如果长度也相等 则取字典序最小的单词 已经参与接龙的单词不能重复使用 现给定一组全部由小写字母组成单词数组 并指
  • hive错误:Exception in thread "main" java.lang.RuntimeException: java.io.IOException: Permission denied

    用不同用户去登录hive 可能会报如下错误 Exception in thread main java lang RuntimeException java io IOException Permission denied at org a
  • 嵌入式开发中的通讯协议(UART、SPI、CAN、I2C)

    一 UART UART是一种通用串行数据总线 用于异步通信 该总线双向通信 可以实现全双工传输和接收 1 1接口 两根线 UART TX 发送 UART RX 接收 1 2如何传输 UART作为异步串口通信协议的一种 工作原理是将传输数据的
  • Spring事务传播问题 — PROPAGATION_REQUIRES_NEW

    一 描述Spring遇到嵌套事务时 当被嵌套的事务被定义为 PROPAGATION REQUIRES NEW 时 内层Service的方法被调用时 外层方法的事务被挂起 内层事务相对于外层事务是完全独立的 有独立的隔离性等等 二 实验但实验
  • 区块链中节点和区块的关系&区块链的基本概念

    可以用数学知识来理解 节点是点 区块是线 区块链是面 节点是区块链应用技术里处理信息的基本单位 很多的结点处理完信息后 会被打上时间戳 生成数据区块 把区块按时间先后顺序连接起来就区块链 区块链是一个分布式分类账本 每个区块都是账本中的一页
  • PCB该怎样布局布线,这个小小案例,让你快速了解设计思路!

    在电路设计过程中 应用工程师往往会忽视印刷电路板 PCB 的布局 通常遇到的问题是 电路的原理图是正确的 但并不起作用 或仅以低性能运行 在本文中 我将向您介绍如何正确地布设运算放大器的电路板以确保其功能 性能和稳健性 最近 我与一名实习生
  • 16进制转化为10进制总结

    十六进制转换有16进制每一位上可以是从小到大为0 1 2 3 4 5 6 7 8 9 A B C D E F16个大小不同的数 即逢16进1 其中用A B C D E F 字母不区分大小写 这六个字母来分别表示10 11 12 13 14
  • Android Https相关完全解析 当OkHttp遇到Https

    http blog csdn net lmj623565791 article details 48129405
  • 初始化MySQL时可能遇到的问题

    之前自己第一次初始化数据库时一切顺利 基本过程也已经记录在这里 然而今天换了个环境重新配置数据库的时候 出现了许许多多的问题 趁自己还记得 简单做一下记录 1 Install时出错 在输入指令 mysqld install 时出错 出错内容
  • 如何控制步进电机速度(即,如何计算脉冲频率):

    两相步进电机介绍 实际步进电机控制很简单 应用都是傻瓜了 厂家做好步进电机的驱动器 步进电机如何工作由驱动器来控制 我们不需要对步进电机做深入的了解 只要知道步进电机驱动器的应用方法即可 当然简单的步进电机工作特性 还是必须知道的 下面我会
  • python代码~玫瑰花小练习

    完整代码如下 RoseDraw py import turtle as t 定义一个曲线绘制函数 def DegreeCurve n r d 1 for i in range n t left d t circle r abs d 初始位置
  • C++中 struct tm 和 time_t 时间和日期的使用方法

    1 概念 在C C 中 对字符串的操作有很多值得注意的问题 同样 C C 对时间的操作也有许多值得大家注意的地方 下面主要介绍在C C 中时间和日期的使用方法 通过学习C C 库 你会发现有很多操作 使用时间的方法 但在这之前你需要了解一些
  • C语言指针学习

    开始好好学习C语言啦 指针是C语言比较难的地方 但是非常重要 所以单独在此记录一下 有执念的人最可怕 一定要好好学习哇 C语言指针学习 1 指针是什么 2 null指针 3 指针的运算 4 数组指针 一维数组指针 二维数组指针 5 指针数组