进阶C语言:指针的进阶(1)

关于指针这个概念从初识C语言就开始进行了解,一直到了进阶C语言,通过前面初识C语言和初阶C语言对指针的学习还是不够深入,所以在进阶中再来对指针进行深入的学习
指针的主题:
1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4. 指针的运算。

1.字符指针

在指针的类型中我们知道有一种指针类型为字符指针 char*

使用方法:

#include <stdio.h>
int main()
{
    char ch = 'w';
    char* pc = &ch;
    *pc = 'w';
    return 0;
}

另外一种使用方法:

#include <stdio.h>
int main()
{
    const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
    printf("%s\n", pstr);
    return 0;
}
const char* pstr = “hello bit.”;其实本质上是将hello bit.这个字符串的首字符的地址存放在了pstr里面,也就是把h的地址存进去了

看下面一道例题:

//代码输出的结果是什么?
#include <stdio.h>

int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    const char* str3 = "hello bit.";
    const char* str4 = "hello bit.";

    if (str1 == str2)
        printf("str1 and str2 are same\n");
    else
        printf("str1 and str2 are not same\n");

    if (str3 == str4)
        printf("str3 and str4 are same\n");
    else
        printf("str3 and str4 are not same\n");

    return 0;
}

在这里我们就有必要来仔细分析一下了:

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。

2.指针数组

在前面的章节中我们也了解过了指针数组,指针数组是一个数组,数组的每一元素都是指针
int* arr1[10]; //整形指针的数组
char *arr2[4]; //一级字符指针的数组
char **arr3[5];//二级字符指针的数组

3.数组指针

数组指针和指针数组无非就是名字颠倒一下嘛,但是数组指针和指针数组的内容完全不相同,接下来就来深入了解一下数组指针

3.1数组指针的定义

数组指针是指针还是数组呢?可以结合前面学过的来对比一下:
整形指针:是一个指针、指着指向的类型是整形
字符指针:是一个指针、指针指向的类型是字符类型
因此:数组指针也是指针,只不过它的指向的类型是一个数组
//下面哪一个是数组指针?
int *p1[10];
int (*p2)[10];
解释:
int (*p)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。
//所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

3.2数组名和&数组名

对于下面的数组:

int arr[10];

arr 和 &arr 分别是啥?

我们知道arr是数组名,数组名表示数组首元素的地址。

那&arr数组名到底是啥?

我们看一段代码:

#include <stdio.h>
int main()
{
    int arr[10] = { 0 };
    printf("%p\n", arr);
    printf("%p\n", &arr);
    return 0;
}

可以看到数组名和&数组名的结果都是一样的,但是在前面了解过:数组名是首元素的地址,而&数组名是整个数组的地址。

再来看一段代码:

#include <stdio.h>
int main()
{
    int arr[10] = { 0 };
    printf("arr   = %p\n", arr);
    printf("&arr  = %p\n", &arr);
    printf("arr+1 = %p\n", arr + 1);
    printf("&arr+1= %p\n", &arr + 1);
    return 0;
}

3.3数组指针的使用

那数组指针该怎么使用呢?
#include <stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
    int(*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
    //但是一般很少这样写代码
    return 0;
}
可以使用数组指针来访问二维数组
#include <stdio.h>
int main()
{
    int arr1[] = { 1,2,3,4,5 };
    int arr2[] = { 2,3,4,5,6 };
    int arr3[] = { 3,4,5,6,7 };
    //arr[i] == *(arr+i)
    //arr是一个存放整型指针的数组
    int* arr[] = { arr1, arr2, arr3 };
    int i = 0;
    for (i = 0; i < 3; i++)
    {
        int j = 0;
        for (j = 0; j < 5; j++)
        {
            printf("%d ", *(arr[i]+j));
        }
        printf("\n");
    }
    return 0;
}
#include <stdio.h>
void print_arr(int(*arr)[5], int row, int col)
{
    int i = 0;
    int j = 0;
    for (i = 0; i < row; i++)
    {
        for (j = 0; j < col; j++)
        {
            printf("%-2d ", *(arr[i] + j));
        }
        printf("\n");
    }
}
int main()
{
    int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
    print_arr(arr, 3, 5);
    return 0;
}
下列代码分表示什么意思?
int arr[5];
//整形数组
int *parr1[10];
//指针数组
int (*parr2)[10];
//数组指针
int (*parr3[10])[5];
//parr3是一个数组,数组中存放的是指针,而这个存放的指针又指向的是一个数组

4.数组参数、指针参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

4.1一维数组传参

下面哪段代码设计合理
#include <stdio.h>
//代码1
void test(int arr[])
{}
//代码2
void test(int arr[10])
{}
//代码3
void test(int* arr)
{}
int main()
{
    //整形数组传参
    int arr[10] = { 0 };
    test(arr);
    return 0;
}
代码1:传的是数组名,用数组来接收,OK
代码2:传的是数组名,用数组来接受,OK
代码3:传的是数组名,用int类型的指针来接收,OK
#include <stdio.h>
//代码1
void test(int *arr[10])
{}

//代码
void test(int** arr)
{}

int main()
{
    //指针数组传参
    int* arr2[10] = { 0 };
    test(arr);
    return 0;
}
代码1:传的是数组名,用一个指针数组来接收,OK
代码2:传的是数组名(一级指针的地址),用二级指针来接受,OK
总结:一维数组传参,形参的部分可以是数组,也可以是指针,当形参是指针的时候,要注意指针的类型

4.2二维数组传参

#include <stdio.h>

void test(int arr[3][5])//ok?
{}
//实参用二维数组,形参也使用二维数组,合理

void test(int arr[][])//ok?
{}
//形参使用二维数组时省略了行和列,这样子是不合理的
//就算不知道有多少行,但得知道有一行有多少个元素,这样才可以确定下一行从哪里结束

void test(int arr[][5])//ok?
{}
//形参使用二维数组,省略了行,但没有省略列,合理

void test(int* arr)//ok?
{}
//形参使用整形指针,不合理,二维数组的数组名是第一行的地址,因此需要使用数组指针来接受

void test(int* arr[5])//ok?
{}
//形参使用指针数组来接收,不合理

void test(int(*arr)[5])//ok?
{}
//形参使用数组指针来接受,合理

void test(int** arr)//ok?
{}
//二级指针接受的是一级指针的地址,但是实参是数组名,所以不合理

int main()
{
    int arr[3][5] = { 0 };
    test(arr);
}
总结:二维数组传参,可以是数组,也可以是指针,如果是数组,行可以省略,但是列不能省略,如果是指针,实参是一行的地址,因此要使用数组指针

4.3一级指针传参

#include <stdio.h>
void print(int* p, int sz)
{         //实参传递指针,形参用指针接收
    int i = 0;
    for (i = 0; i < sz; i++)
    {
        printf("%d\n", *(p + i));
    }
}
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9 };
    int* p = arr;
    int sz = sizeof(arr) / sizeof(arr[0]);
    //一级指针p,传给函数
    print(p, sz);
    return 0;
}
当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
1.可以传一级指针
2.可以传数组名
3.可以传地址

4.4二级指针传参

#include <stdio.h>
void test(int** ptr)
{
    printf("num = %d\n", **ptr);
}
int main()
{
    int n = 10;
    int* p = &n;
    int** pp = &p;
    test(pp);
    test(&p);
    return 0;
}
当函数的参数为二级指针的时候,可以接收什么参数?
1.可以传二级指针
2.可以传指针数组
3.可以传一级指针变量的地址

5.函数指针

函数指针是指针,但是它指向的对象是一个函数

看下面的一段代码:

#include <stdio.h>
void test()
{
    printf("hehe\n");
}
int main()
{
    printf("%p\n", test);
    printf("%p\n", &test);
    return 0;
}

输出的是两个地址,这两个地址是 test 函数的地址。

那我们的函数的地址要想保存起来,怎么保存?

下面我们看代码:

#include <stdio.h>
void test()
{
    printf("hehe\n");
}
int main()
{
    //下面哪个变量可以存放test函数的地址
    void (*ptr1)();
    void* ptr2();
    return 0;
}
函数指针,首先得是一个指针,因此*要先和变量名结合,ptr1先和*结合,因此ptr1就是一个函数指针,可以用来存放test函数。ptr1是一个函数指针,指向的是一个test函数,没有函数参数,返回类型是void。

6.函数指针数组

数组是一个存放相同类型数据的存储空间

比如

整形数组:数组中每个元素都是整形

字符数组:数组中每个元素都是字符类型

指针数组:数组中每个元素都是指针

那么,要把函数的地址存放到一个数组中去,就需要函数指针的数组,那函数指针的数组该怎么定义呢?

函数指针数组是一个数组,而数组的内容是一个函数指针,因此函数指针数组就可以写成下面的格式:
void (*ptr[10]) ()

那函数指针数组有什么用途呢?

转移表
可以将许多的函数调用放在一个数组里面,然后访问的时候以下标的形式来访问数组的内容
例如:写一个计算器

1.普通方式:

//普通方式
#include <stdio.h>
void menu()
{
    printf("************************************\n");
    printf("****** 1.Add   ***** 2. Less  ******\n");
    printf("****** 3.Times ***** 4.Except ******\n");
    printf("************ 0. Exit ***************\n");
    printf("************************************\n");

}
int Add(int x, int y)
{
    return x + y;
}
int Less(int x, int y)
{
    return x - y;
}int Times(int x, int y)
{
    return x * y;
}int Expect(int x, int y)
{
    return x / y;
}
int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    do
    {
        menu();
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            printf("加法运算->");
            printf("请输入两个操作数:");
            scanf("%d %d", &x, &y);
            int add = Add(x, y);
            printf("%d + %d = %d\n", x, y, add);
            break;
        case 2:
            printf("减法运算->");

            printf("请输入两个操作数:");
            scanf("%d %d", &x, &y);
            int less = Less(x, y);
            printf("%d - %d = %d\n", x, y, less);
            break;
        case 3:
            printf("乘法运算->");

            printf("请输入两个操作数:");
            scanf("%d %d", &x, &y);
            int times = Times(x, y);
            printf("%d * %d = %d\n", x, y, times);
            break;
        case 4:
            printf("除法运算->");

            printf("请输入两个操作数:");
            scanf("%d %d", &x, &y);
            int expect = Expect(x, y);
            printf("%d / %d = %d\n", x, y, expect);
            break;
        case 0:
            printf("退出程序!\n");
            break;
        default:
            printf("选择错误,请重新选择:");
            break;
        }
    } while (input);
    return 0;
}
可以看到,如果这样子写代码太多了,而且如果需要再加入一种运算,就会变得很麻烦,所以需要使用转移表

2.转移表:

#include <stdio.h>
void menu()
{
    printf("************************************\n");
    printf("****** 1.Add   ***** 2. Less  ******\n");
    printf("****** 3.Times ***** 4.Except ******\n");
    printf("************ 0. Exit ***************\n");
    printf("************************************\n");

}
int Add(int x, int y)
{
    return x + y;
}
int Less(int x, int y)
{
    return x - y;
}int Times(int x, int y)
{
    return x * y;
}int Expect(int x, int y)
{
    return x / y;
}
int main()
{
    int input = 0;
    int x = 0;
    int y = 0;
    int ret = 0;
    do
    {
        menu();
        printf("请选择:");
        scanf("%d", &input);
        int (*p[5])(int x, int y) = { NULL, Add, Less, Times, Expect };
                                    //NULL起到占位的作用,这样子才能对应input和函数的选择
        if (input >= 1 && input <= 4)
        {
            printf("请输入两个操作数:");
            scanf("%d %d", &x, &y);
            ret = (*p[input])(x, y);  //通过下标进行访问
            printf("ret = %d\n", ret);
        }
        else if(input == 0)
        {
            printf("退出程序!\n");
        }
        else
            printf("选择错误,请重新选择:\n");

    } while (input);
    return 0;
}

如果使用转移表完成计算器这样的代码就容易很多了

7.指向函数指针数组的指针

基本概念:
指向函数指针数组的指针是一个 指针
指针指向一个 数组 ,数组的元素都是 函数指针 ;

如何定义呢?

#include <stdio.h>
void test(const char* str)
{
    printf("%s\n", str);
}
int main()
{
    char str = "hello world!";

    //函数指针
    void (*pfun)(const char*) = test;

    //函数指针数组
    void (*pfun_ptr[5])(const char* str) = { test };

    //指向函数指针数组的指针
    void (*(*ppfun_ptr)[5])(const char*) = &pfun_ptr;

    return 0;
}

8.回调函数

回调函数就是一个通过 函数指针调用的函数。如果你把 函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是 回调函数。回调函数不是由该函数的实方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
可以使用上面的计算器实现的代码来使用回调函数

代码演示:

#include <stdio.h>
void menu()
{
    printf("************************************\n");
    printf("****** 1.Add   ***** 2. Less  ******\n");
    printf("****** 3.Times ***** 4.Except ******\n");
    printf("************ 0. Exit ***************\n");
    printf("************************************\n");

}
int Add(int x, int y)
{
    return x + y;
}
int Less(int x, int y)
{
    return x - y;
}int Times(int x, int y)
{
    return x * y;
}int Expect(int x, int y)
{
    return x / y;
}

void Calc(int(*pc)(int x, int y))
{
    int x = 0;
    int y = 0;
    int ret = 0;
    printf("请输入两个操作数:");
    scanf("%d %d", &x, &y);
    ret = pc(x, y); //pc存放的是函数的地址,通过pc去调用函数,被调用的函数就被称为回调函数
    printf("ret = %d\n", ret);
}

int main()
{
    int input = 0;    
    do
    {
        menu();
        printf("请选择:");
        scanf("%d", &input);
        switch (input)
        {
        case 1:
            Calc(Add);//将函数的地址传给Calc函数
            break;
        case 2:
            Calc(Less);
            break;
        case 3:
            Calc(Times);
            break;
        case 4:
            Calc(Expect);
            break;
        case 0:
            printf("退出程序!\n");
            break;
        default:
            printf("选择错误,请重新选择:");
            break;
        }
    } while (input);
    return 0;
}

另外,关于回调函数还有一个例子:在另外一篇文章中,是关于qsort函数的使用以及使用冒泡排序的思想模拟实现qsort函数 -->C语言:冒泡排序

本篇的分享到此结束,感谢大家的支持!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/1868.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

10从零开始学Java之开发Java必备软件Intellij idea的安装配置与使用

作者&#xff1a;孙玉昌&#xff0c;昵称【一一哥】&#xff0c;另外【壹壹哥】也是我哦CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者前言壹哥在前面的文章中&#xff0c;带大家下载、安装、配置了Eclipse这个更好用的IDE开发工具&#xff0c;并教会了大家如何在Ecli…

【数据结构】链表相关题目(中档题)

&#x1f680;write in front&#x1f680; &#x1f4dc;所属专栏&#xff1a;初阶数据结构 &#x1f6f0;️博客主页&#xff1a;睿睿的博客主页 &#x1f6f0;️代码仓库&#xff1a;&#x1f389;VS2022_C语言仓库 &#x1f3a1;您的点赞、关注、收藏、评论&#xff0c;是对…

百度文心一言对标 ChatGPT,你怎么看?

文心一言 VS ChatGPT接受不完美 期待进步里程碑意义文心一言初体验✔ 文学创作✔ 商业文案创作✔ 数理逻辑推算✔ 中文理解✔ 多模态生成写在最后何为文心&#xff1f;“文”就是我们中华语言文字中的文&#xff0c;“心”是希望该语言模型可以用心的去理解语言&#xff0c;用心…

学习 Python 之 Pygame 开发魂斗罗(十二)

学习 Python 之 Pygame 开发魂斗罗&#xff08;十二&#xff09;继续编写魂斗罗1. 修改玩家扣减生命值2. 解决玩家下蹲子弹不会击中玩家而是直接让玩家死亡的问题3. 完善地图4. 增加产生敌人函数&#xff0c;解决一直产生敌人的问题5. 给玩家类增加计算玩家中心的方法继续编写魂…

遗传算法原理及案例解析

一、遗传算法原理遗传算法—进化算法&#xff08;Genetic Algorithm GA&#xff09;源自达尔文进化论的物竞天择适者生存思想。它是通过模拟生物进化的过程&#xff0c;搜索全局最优解的一种算法。算法可应用于优化问题&#xff0c;当一个问题有N种解决方案时&#xff0c;如何选…

【Unity小知识】Editor编写常用方法汇总

汇总一些Unity Editor开发的常用方法和实现方式&#xff0c;会持续更新。 添加自定义菜单栏方法 using UnityEngine; using UnityEditor;public class EditorTools : EditorWindow {[MenuItem("EditorTools/自定义的编辑器方法")]public static void CustomEditroFu…

mac下搭建elasticsearch日志系统,filebeat + elasticsearch + logstash + kibana

一、下载安装 elasticsearch和kibana上一篇已经安装好&#xff0c;这篇主要讲filebeat和logstash安装和使用。 我是M1芯片的mac&#xff0c;需要安装 1.filebeat https://www.elastic.co/cn/downloads/past-releases/filebeat-8-6-0 2.logstash https://www.elastic.co/cn…

python+opencv生成较真实的车牌号码图片

本文参考github代码&#xff1a;https://github.com/loveandhope/license-plate-generator 效果&#xff1a; 一、代码目录结构&#xff1a; background目录下存放各种背景图片 font目录下存放车牌中文、字符的ttf字体 images目录下存放蓝色底牌、新能源绿色底牌、污渍&#…

Vector - CAPL - RS232串口处理

摸鱼聊天、答疑解惑首选之地 --- 车载网络哪些事儿你是否还在为VT板卡系统昂贵而发愁&#xff1f;是否为MCU log没办法而烦恼&#xff1f;当前车载网络协议测试这块&#xff0c;vector可以说是一家独大&#xff0c;因此各种骚操作一年比一年多&#xff0c;然而对于我们测试工程…

【小猫爪】AUTOSAR学习笔记06-Communication Stack之ComM模块

【小猫爪】AUTOSAR学习笔记06-Communication Stack之ComM模块前言1 ComM简介2 ComM功能介绍2.1 PNC 状态管理2.2 Channel状态管理2.3 通信禁止功能2.4 不同类型的NM2.5 User、PNC 与 Channel 的映射2.6 状态保存END前言 因为一个偶然的机会让我接触到了AUTOSAR&#xff0c;所以…

数据库--进阶版-11--SQL优化

1.插入数据 insert优化&#xff1a; 例如要插入下面这些 insert into tb_test values(1,tom); insert into tb_test values(2,cat); insert into tb_test values(3,jerry); 我们可以通过以下几个方面进行操作&#xff1a; >批量插入&#xff08;如果一次性要插入多条…

排好队,一个一个来:宫本武藏教你学队列(附各种队列源码)

文章目录前言&#xff1a;理解“队列”的正确姿势一个关于队列的小思考——请求处理队列的两大“护法”————顺序队列和链式队列数组实现的队列链表实现的队列循环队列关于开篇&#xff0c;你明白了吗&#xff1f;最后说一句前言&#xff1a; 哈喽&#xff01;欢迎来到黑洞晓…

货物摆放 (蓝桥杯) JAVA

题目描述 小蓝有一个超大的仓库&#xff0c;可以摆放很多货物。 现在&#xff0c;小蓝有n 箱货物要摆放在仓库&#xff0c;每箱货物都是规则的正方体。 小蓝规定了长、宽、高三个互相垂直的方向&#xff0c;每箱货物的边都必须严格平行于长、宽、高。 小蓝希望所有的货物最终摆…

可换皮肤的Qt登录界面

⭐️我叫忆_恒心,一名喜欢书写博客的在读研究生👨‍🎓。 如果觉得本文能帮到您,麻烦点个赞👍呗! 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧,喜欢的小伙伴给个三连支持一下呗。👍⭐️❤️ 可换皮肤的Qt登录界面 QSS的学习笔记 快…

真是一篇神奇的文章 动图DIY - 动图剪辑 - 录屏 - 画画动图 (无需VIP的软件推荐)

很多人想自己去搞一些 动图 &#xff0c;去了好多网址或者app&#xff0c;不是收费 &#x1f4b4; 就是 VIP &#x1f511; &#xff0c;博主也苦恼了好久&#xff0c;今天遇到一款超级、无敌、好用的软件 ScreeTogif &#xff0c;下载连接 &#x1f617;&#x1f619;&#x1…

高通开发系列 - Sensors Bring Up

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 返回高通开发系列 - 总目录 目录 问题背景高通android sensor信息Sensors Execution Environment (SEE)qxdm抓sensor log的方法android 调试…

Hadoop之Mapreduce序列化

目录 什么是序列化&#xff1a; 什么是反序列化&#xff1a; 为什么要序列化&#xff1a; Java的序列化&#xff1a; Hadoop序列化: 自定义序列化接口&#xff1a; 实现序列化的步骤&#xff1a; 先看源码进行简单分析&#xff1a; 序列化案例实操&#xff1a; 案例需…

蓝桥杯真题——自动售水机

2012年第四届全国电子专业人才设计与技能大赛“自动售水机”设计任务书1. 系统框图接下来我们将任务分块&#xff1a; 1. 按键控制单元 设定按键 S7 为出水控制按键&#xff0c;当 S7 按下后&#xff0c;售水机持续出水&#xff08;继电器接通&#xff0c;指示 灯 L10 点亮&…

Java中的JSON序列化和反序列化

文章目录Java 和 JSON 序列化JSON 简介JSON 是什么JSON 标准JSON 优缺点JSON 工具Java JSON 库JSON 编码指南Fastjson 应用添加 maven 依赖Fastjson API定义 Bean序列化反序列化Fastjson 注解JSONFieldJSONTypeJackson 应用添加 maven 依赖Jackson API序列化反序列化容器的序列…

开箱即用的密码框组件

写了一个小玩具&#xff0c;分享一下 - 组件功能&#xff1a; 初次进入页面时&#xff0c;密码隐藏显示&#xff0c;且无法查看真实密码 当修改密码时&#xff0c;触发键盘&#xff0c;输入框则会直接清空 此时输入密码&#xff0c;可以设置密码的隐藏或显示&#xff1a; …