C语言指针详解(三)目录版

news2024/11/15 9:06:04

C语言指针详解(三)目录版

    • 1、字符指针变量
      • 1.1、字符指针变量的一般应用
      • 1.2、常量字符串
      • 1.3、常量字符串与普通字符串的区别
        • 1.3.1 常量字符串的不可修改性
        • 1.3.2 常量字符串的存储
    • 2、数组指针变量
      • 2.1、数组指针变量定义
      • 2.2、数组指针变量的初始化
    • 3、二维数组传参本质
    • 4、函数指针变量
      • 4.1、函数指针变量的创建
        • 4.1.1、验证函数地址的存在
        • 4.1.2、函数指针变量表达式
      • 4.2、函数指针变量的使用
      • 4.3、深入理解函数指针
        • 4.3.1、例题一
        • 4.3.2、例题二
        • 4.3.3、例题一解析
        • 4.3.4、例题二解析
        • 4.4、关键字 typedef 介绍
        • 4.4.1、typedef 表达式与实例演示
        • 4.4.2、define 与 typedef 的区别
    • 5、函数指针数组
    • 6、转移表

1、字符指针变量

我们知道若指针所指向的内容是字符,那么指针的类型就是字符指针类型 char* ,这个指针变量就是字符指针变量。

1.1、字符指针变量的一般应用

int main()
{
    char ch = 'a';
    char* pc = &ch;
    *pc = 'w';
    printf("%c", ch);
    return 0;
}

在这里插入图片描述

1.2、常量字符串

#include<stdio.h>
int main()
{
    const char* pt = "hello world.";//请思考这里是把一个字符串放到了 pt 指针当中了吗?
    printf("%s\n", pt);
    return 0;
}

如上面代码所示的 char* name = “字符串” 就是常量字符串的表达方式。那么我们思考一下注释中的问题:这里是把一个字符串放到了 pt 指针当中了吗?
答案是:否。毕竟前面是一个指针变量,其存储的应当为地址,故放入的是首字符的地址。因为字符是连续存放的,输出时系统会自动向后遍历,直至遇到 “/0”。
运行效果图:
在这里插入图片描述

1.3、常量字符串与普通字符串的区别

1.3.1 常量字符串的不可修改性

1.1 的代码和运行图中我们可见一般情况下的字符(字符串)是可以通过指针解引用来进行修改的。而常量字符串则不可修改,如下面代码示例演示

int main()
{
    char* pt = "hello world.";
    printf("%s\n", pt);
    *pt = "Hello World";
    return 0;
}

在这里插入图片描述
原因请见下文 1.3.2

1.3.2 常量字符串的存储

我们知道在存储位置中有栈区,堆区,静态区。其实在此之外还有一部分称为代码段。一般字符(字符串)存储在栈区中,而常量字符串则存储在代码段中。所以即使通过指针解引用也不能更改字符串内容。正因如此,当多个指针变量指向的常量字符串内容一致时,系统不会存储多份相同的常量字符串,而是会只存储一份,各个变量共用这一份常量字符串(即字符串地址相同)。为使各位对此更加清晰,我们通过以下代码和图解来进行进一步解析。

int main()
{
    char str1[] = "You are handsome !";
    char str2[] = "YOu are handsome !";
    char* str3 = "You are so beautiful";
    char* str4 = "You are so beautiful";

    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;
}

在这里插入图片描述
在这里插入图片描述

2、数组指针变量

2.1、数组指针变量定义

数组指针,顾名思义它是一种指针。我们已经熟知:
整型指针变量:int * p;存放的是整型变量的地址,指向整型数据的指针。
浮点型指针变量:float * p;存放的是浮点型变量的地址,指向浮点型数据的指针。
由上推知:数组指针变量应当存放的是数组的地址,指向数组的指针。
数组指针变量表达式:int (*p) [20];
在这个表达式中,p会先和*结合,表明 p 是一个指针变量,然后指针指向的是一个大小为20个整型变量的数组。
注意:因为 [ ] 的优先级要高于 * 号,所以必须加上 ()来保证 p 先和 * 结合

2.2、数组指针变量的初始化

既然数组指针变量存放的是数组的地址,那么我们就要将数组的地址给指针变量。数组地址:&数组名

int arr[10] = {0};//定义数组并初始化
int (*p) [10] = &arr;//将数组的地址赋值给指针变量 

在这里插入图片描述

3、二维数组传参本质

在数组章节中曾提及二维数组传参的如下写法:

#include <stdio.h>

void print_arr(int arr[4][5], int a, int b)
{
    int i = 0;
    for (i = 0; i < a; i++)
    {
        int j = 0;
        for (j = 0; j < b; j++)
        {
            printf("%d", arr[i][j]);
        }
        printf("\n");
    }
}

int main()
{
    int arr[4][5] = { {1,2,3,4,5},{2.3,4,5,6},{3,4,5,6,7},{4,5,6,7,8}};
    print_arr(arr, 4, 5);
    return 0;
}

在上述代码中,实参为二维数组,形参也为二维数组。那么下面来介绍另一种传参方式,使用数组指针进行传参。
二维数组在之前数组章节曾讲过,其可以看成每个元素是一维数组的数组,即二维数组的每个元素是一个一维数组,二维数组首元素就是第一行,是一个一维数组。
在这里插入图片描述
二维数组的数组名就是第一行的地址,是一个一维数组的地址,第一行一维数组类型为int [5],故其地址类型为int (*) [5];。如此也就证明二维数组传参实际上是传递了第一行一维数组的地址。由此可知,函数形参部分也可以写成指针形式。如下所示:

#include <stdio.h>

void print_arr(int (*p) [5], int a, int b)//数组形参设置为数组指针
{
    int i = 0;
    for (i = 0; i < a; i++)
    {
        int j = 0;
        for (j = 0; j < b; j++)
        {
            printf("%d", *( * ( p + i ) + j ) );//深入理解系统对数组arr[ i ]的编译。
            // arr[ i ] = *(arr + i)
        }
        printf("\n");
    }
}

int main()
{
    int arr[4][5] = { {1,2,3,4,5},{2.3,4,5,6},{3,4,5,6,7},{4,5,6,7,8} };
    print_arr(arr, 4, 5);
    return 0;
}

4、函数指针变量

4.1、函数指针变量的创建

根据上文内容,我们可以推断出函数指针变量是存储函数地址的变量,同时我们可以通过函数的地址来调用函数。

4.1.1、验证函数地址的存在

下面我们来验证函数地址的存在:

#include<stdio.h>

void test()
{
    printf("hehe\n");
}

int main()
{
    printf("test = %p\n", test);//函数名
    printf("&test = %p\n", &test);//取函数地址
    return 0;
}

在这里插入图片描述
由此说明函数存在地址,函数名与 &函数名 都可以得到函数的地址。

4.1.2、函数指针变量表达式

表达式如下:

int*pf)(int x , int y);
//或 int (*pf)(int , int)

在这里插入图片描述
去掉变量名即为其变量类型:

int*)(int x , int

4.2、函数指针变量的使用

在使用函数指针变量调用函数时,(*pf)()与 pf()两种方法都是可行的,因为二者表示的意思都一致,都是对应函数的地址。
代码演示:

#include<stdio.h>

int div(int a , int b)
{
    return a / b;
}

int main()
{
    int (*pf) (int, int) = div;
    printf("%d\n", (*pf)(4 , 2) );//调用方式一
    printf("%d\n", pf(4, 2));//调用方式二
    return 0;
}

在这里插入图片描述

4.3、深入理解函数指针

4.3.1、例题一
(*( void (*)() ) 0)();//请思考此行代码的意义

请思考上面代码的意义,解析见 4.3.3

4.3.2、例题二
void (*signal(int, void(*)(int)))(int);//请思考此行代码的意义

请思考上面代码得意义,解析见 4.3.4

4.3.3、例题一解析

在这里插入图片描述
入手关键点在 0 处,其前方应当为0的类型,( void (*) () ) 0 典型的强制类型转换,将0从整型 int 强制转换为函数指针类型 void ( * ) () ,从而 0 就成为一个函数指针变量,整体就是通过函数指针调用函数。

4.3.4、例题二解析

在这里插入图片描述
入手关键点在中间的函数名及其参数部分 signal(int, void(*)(int)) 我们可见在这一小部分中已经包含了signal函数的函数名和该函数内的两个参数的类型,但无参数名,根据C语言标准可知这应当是一个函数声明。故剩余部分为函数的返回类型,为 void (*) (int)函数指针类型。因为解引用操作符 * 后要跟名称,所以signal(int, void(*)(int))放在了 * 之后。综上,整体为一个函数声明

4.4、关键字 typedef 介绍
4.4.1、typedef 表达式与实例演示

typedef是用来类型的重命名的,可以简化复杂的类型,如 4.3.4 例题二中的函数指针类型。其重定义表达式如下:

typedef name1 name2;

name1 表示需要重定义类型名
name2 表示 name1 重定义后的类型名

具体演示如下:

#include<stdio.h>

int div(int a , int b)
{
    return a / b;
}

int main()
{
    typedef int(*son)(int, int);
    // * 后要跟名称,所以新的类型名要放在 * 后
    son pf = div;
        printf("%d\n", (*pf)(18 , 2) );
        printf("%d\n", pf(10, 2));
    return 0;
}

在这里插入图片描述

4.4.2、define 与 typedef 的区别

1、 本质区别:
#define 是预处理指令,用于文本替换。在编译之前,预处理器会直接将 #define 定义的宏替换成指定的文本。
typedef 是类型定义命令,用于为已存在的数据类型创建一个新的名字。
2.、作用范围:
#define 定义的宏没有作用域限制,一旦定义,就会一直有效,除非被 #undef 取消定义。
typedef 定义的类型有作用域限制,它遵循C语言的变量作用域规则。
3、 类型检查:
#define 不进行类型检查。它仅仅是在预处理阶段进行文本替换,所以不会检查替换后的类型是否正确。
typedef 会进行类型检查。当你使用 typedef 定义的新类型时,编译器会检查类型是否匹配。
4、 使用方式:
#define 可以用于定义常量、宏函数等,不仅仅限于类型。
typedef 仅用于定义类型的别名。
5、 内存分配:
#define 不会分配内存,因为它只是在预处理阶段进行文本替换。
typedef 本身也不分配内存,但它定义的类型在创建变量时会分配内存。

5、函数指针数组

我们知道数组是一个存储相同类型数据的存储空间。故函数指针数组就是一块连续的存储函数指针的空间。
表达式如下:

type (* name[number]();

name 会先和 [ ] 结合,表明 name 是数组,数组的内容是 type (*) () 类型的函数指针。(因为 * 后面要跟名称,所以 * 后面跟上数组名)

6、转移表

转移表是函数指针数组实例化的体现。
下面以简易计算器的改造为例:

//改造前的计算器
#include<stdio.h>

int add(int a, int b)
{
    return a + b;
}

int sub(int a, int b)
{
    return a - b;
}

int mul(int a, int b)
{
    return a * b;
}

int div(int a, int b)
{
    return a / b;
}

int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    do
    {
        printf("**************\n");
        printf("1:add    2:sub\n");
        printf("3:mul    4:div\n");
        printf("0:exit        \n");
        printf("**************\n");
        printf("请选择: ");
        scanf("%d", &input);
        switch (input)
        {
            case 1:
                printf("请输入操作数:");
                scanf("%d %d", &x, &y);
                ret = add(x, y);
                printf("ret = %d\n", ret);
                break;
            case 2:
                printf("请输入操作数:");
                scanf("%d %d", &x, &y);
                ret = sub(x, y);
                printf("ret = %d\n", ret);
                break;
            case 3:
                printf("请输入操作数:");
                scanf("%d %d", &x, &y);
                ret = mul(x, y);
                printf("ret = %d\n", ret);
                break;
            case 4:
                printf("请输入操作数:");
                scanf("%d %d", &x, &y);
                ret = div(x, y);
                printf("ret = %d\n", ret);
                break;
            case 0:
                printf("退出程序\n");
                break;
            default:
                printf("选择错误\n");
                break;
        }
    } while (input);
    return 0;
}

上述代码的条件分支中我们可以看到有许多重复的部分
在这里插入图片描述
这样就出现两个问题:

1、将这样重复的代码实现成函数。 2、这个函数又能完成不同的任务

如此我们有两种思路:

1、设计回调函数

2、引入函数指针数组来设计转移表。

在此我们以思路二进行改造(思路一请见《C语言指针详解(四)》)。首先我们要思考,设计转移表的话函数指针数组中的元素应当如何设置。我们一共自定义了 4 个函数,所以我们就把这四个函数的地址放入数组。具体演示如下:

#include<stdio.h>

int add(int a, int b)
{
    return a + b;
}

int sub(int a, int b)
{
    return a - b;
}

int mul(int a, int b)
{
    return a * b;
}

int div(int a, int b)
{
    return a / b;
}

int main()
{
    int x, y;
    int input = 1;
    int ret = 0;
    int (*parr[5])(int , int) = { 0 , add , sub , mul , div };//转移表
    //之所以首元素为 0  是为了便于选择,使1-4都对应具体函数
    do
    {
        printf("**************\n");
        printf("1:add    2:sub\n");
        printf("3:mul    4:div\n");
        printf("0:exit        \n");
        printf("**************\n");
        printf("请选择: ");
        scanf("%d", &input);
        if (input <= 4 && input >= 1)
        {
            printf("请输入操作数");
            scanf("%d %d", &x, &y);
            ret = (parr[input])(x, y);
            printf("ret = %d\n", ret);
        }
        else if (input == 0)
        {
            printf("退出计算器\n");
        }
        else
        {
            printf("输入错误");
        }
    } while (input);
    return 0;
}

全文至此结束!!!
写作不易,不知各位老板能否给个一键三连或是一个免费的赞呢()(),这将是对我最大的肯定与支持!!!谢谢!!!()()

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据科学家必须掌握的12个Python功能

Python 已经成为数据科学家的必选语言&#xff0c;从数据处理到机器学习&#xff0c;它几乎无所不能。本文将探讨一些Python特性&#xff0c;这些特性不仅能帮助你编写更高效、更易读、更易维护的代码&#xff0c;还特别适合数据科学的需求&#xff0c;使你的代码简洁且优雅。 …

选择 websim网站:一个用自然语言快速构建生成功能齐全的网站

Websim AI 是一个前沿的网站创建平台&#xff0c;旨在通过人工智能技术彻底改变网页设计流程。用户只需用自然语言描述他们的愿景&#xff0c;即可快速生成功能齐全的网站。该工具非常适合从初学者到经验丰富的开发人员使用&#xff0c;可以快速生成应用程序、网站原型或试验网…

【LLM大模型】“提示工程”的技术分类

尽管大模型非常强大&#xff0c;但仍然有着自身的局限。大模型可以生成看起来非常值得信赖的内容并引用外部资源&#xff0c;但是&#xff0c;大模型本身并不能直接访问互联网也不能访问互联网的资源。偏见往往会使大模型产生某些定型的内容。当被问到一个不知道答案的问题时&a…

在Pxe环境下使用kickstart自动化批量安装Linux系统

一、环境准备 1.rhel7.9操作系统 2.开启主机图形 systemctl set-default graphical.target reboot 如果你的服务器安装了图形&#xff0c;直接切换启动级别5&#xff1a; init 5 3.配置好ip&#xff0c;网关&#xff0c;dns地址 nmcli connection add ifname eth0 con-na…

初学Ubuntu及遇到的问题

初学Ubuntu及遇到的问题 Ubuntu的安装 1.安装VMware Workstation Pro&#xff08;网上有很多资料&#xff0c;本文就不细说啦~~~&#xff09; 2.在Ubuntu官网下载iso文件>https://mirrors.tuna.tsinghua.edu.cn/ubuntu-releases/20.04/ iso文件随便放在哪个位置&#xff…

免费【2024】springboot 电影院订票信息管理系统的设计与实现

博主介绍&#xff1a;✌CSDN新星计划导师、Java领域优质创作者、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流✌ 技术范围&#xff1a;SpringBoot、Vue、SSM、HTML、Jsp、PHP、Nodejs、Python、爬虫、数据可视化…

计算机毕业设计选题推荐-教务管理系统-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

六点建议有效防止晶振老化

晶振作为电子设备中的关键元件&#xff0c;其稳定性直接影响到整个系统的性能。晶振老化是导致其性能下降的主要原因之一。为了延长晶振的使用寿命&#xff0c;以下六点建议将帮助您有效防止晶振老化。 防止晶振老化的六点建议 1.避免裸手触碰&#xff1a; 不要用裸手直接触…

蓝牙耳机哪个品牌质量最好最耐用?4款主流品牌开放式耳机测评

开放式蓝牙耳机现在确实是很火爆啊。2023年期间&#xff0c;中国蓝牙耳机市场的整体销量约为8552万台&#xff0c;同比增长了7.5%。其中&#xff0c;开放式耳机市场销量达到了652万台&#xff0c;同比增长了130.2%。那其实这些数据就表明了开放式蓝牙耳机在中国市场正迅速增长&…

java.net.BindException: 地址已在使用

java.net.BindException: 地址已在使用&#xff0c;也可以说是端口被占用&#xff0c;通常出现在启动服务的时候。 安装netstat&#xff1a; sudo yum install net-tools 1、出错地方如图所示&#xff1a; 2、执行netstat -alnp | grep 8125 3、kill - 9 1899

Linux学习第58天:Regmap API 实验

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 本章思维导图如下&#xff1a; 一、Regmap API简介 1、什么是Regmap 寄存器设置 Linux 下使用 i2c_transfer 来读写 I2C 设备中的寄存器&#xff0c; SPI 接口的话使…

Java:设计模式(单例,工厂,代理,命令,桥接,观察者)

模式是一条由三部分组成的通用规则&#xff1a;它代表了一个特定环境、一类问题和一个解决方案之间的关系。每一个模式描述了一个不断重复发生的问题&#xff0c;以及该问题解决方案的核心设计。 软件领域的设计模式定义&#xff1a;设计模式是对处于特定环境下&#xff0c;经常…

Spring统一功能处理:拦截器、响应与异常的统一管理

目录 一.拦截器 二.统一数据返回格式 三.统一异常处理 一.拦截器 拦截器是Spring框架提供的核⼼功能之⼀&#xff0c;主要⽤来拦截⽤⼾的请求&#xff0c;在指定⽅法前后&#xff0c;根据业务需要执⾏预先设定的代码。 也就是说&#xff0c;允许开发⼈员提前预定义⼀些逻辑…

全球情境感知计算市场规划预测:未来六年CAGR为17.0%

随着全球数字化转型的加速和物联网技术的广泛应用&#xff0c;情境感知计算作为一种能够理解和响应环境变化的智能技术&#xff0c;正逐渐受到更多关注。本文旨在通过深度分析情境感知计算行业的各个维度&#xff0c;揭示行业发展趋势和潜在机会。 【市场趋势的演变】 1. 市场…

C++——string类及其使用

P. S.&#xff1a;以下代码均在VS2019环境下测试&#xff0c;不代表所有编译器均可通过。 P. S.&#xff1a;测试代码均未展示头文件iostream的声明&#xff0c;使用时请自行添加。 博主主页&#xff1a;Yan. yan.                        …

体系结构论文导读(三十一)(上):Soft errors in DNN accelerators: A comprehensive review

Soft errors in DNN accelerators: A comprehensive review DNN 加速器中的软错误&#xff1a;全面回顾 一、文章核心 深度学习任务覆盖了广泛应用。DNN算法被实现于不同系统上&#xff0c;从小型嵌入式设备到数据中心。DNN加速器&#xff08;例如GPU、FPGA、ASIC&#xff0…

JetBrains:Wrong tag。注释告警

报错信息如下&#xff1a; Wrong tag ‘copyright:’ &#xff0c;这个是alt enter 选择 Add copyright:to custom tags&#xff0c;虽然能解决问题&#xff0c;如果创建一个新项目又回出现这个告警提示。 其实这个问题的就是这个在Java Doc里面不存在&#xff0c;才会出现这…

Bugku-CTF-聪明的php

pass a parameter and maybe the flag files filename is random :> 传递一个参数&#xff0c;可能标记文件的文件名是随机的: 于是传一下参&#xff0c;在原网页后面加上/?a1,发现网页出现了变化 3.传入参数&#xff0c;一般情况下是文件包含&#xff0c;或者命令执行&…

linux安装weblogic

文章目录 weblogic是干什么用的weblogic安装前置条件1. 安装jdk2.安装weblogic2.1.创建用户组及用户2.2.切换用户上传安装jar包到目录下&#xff0c;配置文件进行安装准备配置文件2.3保证安装配置文件和安装jar包在同一目录&#xff0c;执行安装命令 3. 创建域来启动页面3.1 构…

Science Robotics 封面论文:美国宇航局喷气推进实验室开发了自主蛇形机器人,用于冰雪世界探索

人们对探索冰冷的卫星&#xff08;如土卫二&#xff09;的兴趣越来越大&#xff0c;这可能具有天体生物学意义。然而&#xff0c;由于地表或冰口内的环境极端&#xff0c;获取样本具有挑战性。美国宇航局的喷气推进实验室正在开发一种名为Exobiology Extant Life Surveyor&…