探索指针(3)-C语言

news2025/1/12 6:54:06

目录

1.字符指针变量

一. 什么是字符指针?

二.字符指针的使用

三.常量字符串与字符指针的关系

四.字符数组和字符串常量

2.数组指针变量

一. 指向数组的指针

3. 二维数组传参的本质

指针形式的函数参数

4.函数指针变量

一. 函数指针的声明

二. 初始化和使用函数指针 

三.typedef关键字的使用 

5.函数指针数组

一.函数指针数组的声明

二.函数指针的使用

6.转移表

一.基本概念

二.转移表实际应用


1.字符指针变量

一. 什么是字符指针?

字符指针(char *)是一种指向字符类型数据的指针。

二.字符指针的使用

代码示例:

int main()
{
	char a = 'c';
	char* ptr = &a;
	*ptr = 'a';

	printf("%c", a);

	return 0;
}

代码输出 :

a

三.常量字符串与字符指针的关系

常量字符串存储位置:

常量字符串存储在只读数据区。这意味着你不能修改这些字符串的内容,否则会导致未定义行为,通常是运行时错误(例如,段错误)。

什么是常量字符串?

常量字符串是用双引号括起来的字符序列,例如 "Hello, World!"。在C语言中,常量字符串是隐式的以空字符 \0 结尾的字符数组。

代码示例:

#include <stdio.h>

int main() {
    const char *str = "Hello, World!";
    printf("%s\n", str);

    // 尝试修改常量字符串(这是不允许的,会导致运行时错误)
    // str[0] = 'h'; // 运行时错误:段错误

    return 0;
}

在这个示例中,字符串 "Hello, World!" 存储在只读数据区,指针 str 指向这个只读数据区的地址。如果尝试修改字符串内容,程序会崩溃。 

四.字符数组和字符串常量

需要区分的是,字符数组和常量字符串的存储位置和可修改性是不同的。

 字符数组:

#include <stdio.h>

int main() {
    char str[] = "Hello, World!";
    printf("%s\n", str);

    // 修改字符数组的内容
    str[0] = 'h';
    printf("%s\n", str);

    return 0;

在这个示例中,str 是一个字符数组,存储在栈区或数据区,可以修改其内容。 

2.数组指针变量

引言:

整型指针->存储整型的地址       字符指针->存储字符的地址

那么自然我们可以知道  数组指针->存储数组的地址

一. 指向数组的指针

数组指针变量是一个指针,指向一个数组的首地址。数组指针变量可以通过不同的方式来声明和使用。

声明和初始化

指向数组的指针的声明形式如下:

type (*pointer_name)[size];

其中,type是数组元素的类型,size是数组的大小。

例如,声明一个指向长度为5的int数组的指针:

 int (*ptr)[5];

初始化和使用

可以使用一个实际的数组来初始化数组指针变量:

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int (*ptr)[5] = &arr;  // 指针ptr指向数组arr

    // 访问数组元素
    printf("Element 0: %d\n", (*ptr)[0]);
    printf("Element 1: %d\n", (*ptr)[1]);
    printf("Element 2: %d\n", (*ptr)[2]);
    printf("Element 3: %d\n", (*ptr)[3]);
    printf("Element 4: %d\n", (*ptr)[4]);

    return 0;
}

 代码输出:

Element 0: 1
Element 1: 2
Element 2: 3
Element 3: 4
Element 4: 5

在这个示例中,ptr是一个指向长度为5的int数组的指针,通过*ptr可以解引用指针并访问数组元素。 

3. 二维数组传参的本质

当你将一个二维数组传递给一个函数时,本质上是将一个指向数组首元素的指针传递给函数。

那么这里我们会提出一个疑问,二维数组的首元素是什么?是arr[0] [0]吗?

二维数组的声明和内存布局

示例声明

int arr[2][3] = { {1, 2, 3}, {4, 5, 6} };

在内存中,二维数组arr的布局是连续的,存储顺序如下:

| 1 | 2 | 3 | 4 | 5 | 6 | 

二维数组的首元素

二维数组的首元素是第一个一维数组的首元素。在上面的例子中,二维数组arr的首元素是arr[0],也就是一个一维数组{1, 2, 3}

常见的传参代码示例:

#include <stdio.h>

// 函数定义
void printArray(int arr[2][3]) {
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}
int main() {
    int arr[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };

    printArray(arr);  // 传递二维数组
    return 0;
}


这里我们的形参写成了,int arr[2][3] 这只是我们为了方便起见,但其本质是传了一个数组指针

指针形式的函数参数

另一种表示方法是使用指针形式的函数参数:

#include <stdio.h>

// 函数定义
void printArray(int (*arr)[3], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

int main() {
    int arr[2][3] = {
        {1, 2, 3},
        {4, 5, 6}
    };

    printArray(arr, 2);  // 传递二维数组
    return 0;
}


 在这个示例中,printArray函数的参数是一个指向具有3个元素的一维数组的指针,并且还接受一个表示行数的参数。

4.函数指针变量

函数指针变量是C语言中一个强大的特性,它允许你将函数的地址存储在变量中,并通过该变量调用函数。这对于实现回调、动态函数调用等功能非常有用。

我们先看一段代码,看下函数是否有地址

int add(int x, int y)
{
	return x + y;
}

int main()
{
	printf("add  = %p\n", add);
	printf("&add = %p\n", &add);

	return 0;
}

输出结果 

add  = 00007FF6015213DE
&add = 00007FF6015213DE

很明显,函数也是有地址,且函数名就代表了它自身的地址。 

一. 函数指针的声明

函数指针的声明形式如下:

return_type (*pointer_name)(parameter_types);

其中:

  • return_type 是函数的返回类型。
  • pointer_name 是指针变量的名称。
  • parameter_types 是函数的参数类型列表。

示例

声明一个指向返回类型为 int 且参数为 intint 的函数的指针:

 int (*func_ptr)(int, int);

二. 初始化和使用函数指针 

代码示例:

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

int main()
{
    int (*func_ptr)(int, int) = add;
    int result = func_ptr(2, 3); // 调用 add 函数,result 的值为 5
    printf("%d", result);

    return 0;
}

注:*func_ptr(2,3)和func_ptr(2,3)效果是相同的。

下面我们来看两段代码,尝试自己去分析以下

1.(*(void (*)())0)();

2. void (*signal(int , void(*)(int)))(int);

代码一分解解析

  1. (void (*)())0 的解析

    • (void (*)()) 表示将整数0强制类型转换为一个函数指针。
    • void (*)() 表示一个函数指针,指向一个不返回任何值(void)的函数,并且不接受任何参数。
  2. *(void (*)())0 的解析

    • *(void (*)())0 表示将地址0强制类型转换为一个函数指针,并且对该指针进行解引用。
    • 在C语言中,对地址0进行解引用通常是一种访问操作系统中的特定内存区域或硬件的方法,这通常是未定义行为,因为地址0通常是一个无效的地址,不应该被访问或解引用。
  3. (*(void (*)())0)() 的解析

    • 最外层的 () 表示调用函数指针指向的函数。
    • 因此,(*(void (*)())0)() 意味着调用地址0处的函数。

代码二分解解析

  1. void (*)(int) 的解析

    • void (*)(int) 表示一个函数指针,指向一个不返回任何值(void)的函数,并且接受一个 int 类型的参数。
  2. signal 函数的声明

    • signal 是一个函数,它接受两个参数:
      • 第一个参数是 int 类型。
      • 第二个参数是一个指向不返回任何值的函数,并且接受一个 int 参数的函数指针。
    • signal 函数返回一个函数指针,该指针指向一个不返回任何值的函数,并且接受一个 int 参数。

        3. void (*signal(int , void(*)(int)))(int)解析

                代表了函数signal的返回值是void (*)(int) ,参数是int和void(*)(int)

三.typedef关键字的使用 

1. 创建新的数据类型名称

使用 typedef 可以为现有的数据类型创建一个新的名称。这种方式有助于简化复杂的类型声明,使代码更加清晰易懂。

示例: 

typedef unsigned long long int ullong;  
// 创建 unsigned long long int 的别名 ullong

在这个示例中,ullong 现在代表了 unsigned long long int 这个数据类型。 

2. 简化复杂的指针声明

当涉及到复杂的指针类型时,typedef 可以提高代码的可读性,使得指针的声明更加直观和易于理解。

typedef int (*operation)(int, int); 
 // 创建一个函数指针类型的别名 operation

示例:

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

typedef int (*operation)(int, int); 

int main()
{
    operation func_ptr = add;
    int result = func_ptr(2, 3); // 调用 add 函数,result 的值为 5
    printf("%d", result);

    return 0;
}

 在这个示例中,operation 现在表示一个函数指针类型,它可以指向一个接受两个 int 参数并返回 int 类型值的函数。

5.函数指针数组

一.函数指针数组的声明

type (*FuncPtr[size])(type);

type(*)(type)代表该数组存储的函数指针类型

size表示该数组的带下

FuncPtr表示该数组的名字 

二.函数指针的使用

代码示例:

#include <stdio.h>

void func1(void) {
    printf("Function 1\n");
}

void func2(void) {
    printf("Function 2\n");
}

void func3(void) {
    printf("Function 3\n");
}

// 重定义一个函数指针类型
typedef void (*FuncPtr)(void);



int main() {
    int i=0;

    // 定义函数指针数组并初始化
    FuncPtr funcArray[] = {func1, func2, func3};

    // 循环调用函数指针数组中的函数
    for (i = 0; i < 3; ++i) {
        funcArray[i]();  // 调用索引为i的函数指针指向的函数
    }

    return 0;
}

输出结果: 

Function 1 Function 2 Function 3

6.转移表

一.基本概念

转移表的基本思想是,将函数指针存储在数组中,数组的索引或者数组中存储的值可以作为输入条件,通过索引或值直接调用对应的函数。

二.转移表实际应用

下面是通过转移表实现加减乘除的代码示例:

int add(int x, int y)
{
	return x + y;
}

int sub(int x, int y)
{
	return x - y;
}

int div(int x,int y)
{
	return x / y;
}

int mul(int x, int y)
{
	return x * y;
}

void menu()
{
	printf("******************************\n");
	printf("**********  0.exit  **********\n");
	printf("***** 1.add ******2.sub*******\n");
	printf("******3.div ******4.mul*******\n");
	printf("******************************\n");
}
enum cal
{
	EXIT,
	ADD,
	SUB,
	DIV,
	MUL
};
int main()
{
	menu();
	int(*arr[5])(int, int) = { 0,add,sub,div,mul };//转移表

	int input = 0;
	do {
		input = 0;
		int x = 0;
		int y = 0;

		printf("请输入你需要的操作:");
		scanf("%d", &input);

		if (input != 0&&input>=0&&input<=5)
		{
			printf("请输入需要操作的两个数:");
			scanf("%d %d", &x, &y);
		}

		switch (input)
		{
		case EXIT:
			printf("退出成功\n");
			break;
		case ADD:
			printf("%d\n", arr[ADD](x, y));
			break;
		case SUB:
			printf("%d\n", arr[SUB](x, y));
			break;
		case DIV:
			printf("%d\n", arr[DIV](x, y));
			break;
		case MUL:
			printf("%d\n", arr[MUL](x, y));
			break;
		default:
			printf("输入错误请重新输入\n");
			break;
		}
	} while (input);


	return 0;
}

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

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

相关文章

常见锁策略之可重入锁VS不可重入锁

可重入锁VS不可重入锁 有一个线程,针对同一把锁,连续加锁两次,如果产生了死锁,那就是不可重入锁,如果没有产生死锁,那就是可重入锁. 死锁 我们之前引入多线程的时候不是讲了一个加数字的案例么,我们今天以它来举例 当我们这样写的时候会出现什么问题? 分析:第一个synchron…

alibaba EasyExcel 简单导出数据到Excel

导入依赖 <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>4.0.1</version> </dependency> 1、alibaba.excel.EasyExcel导出工具类 import com.alibaba.excel.EasyExcel; import …

揭秘,PyArmor库让你的Python代码更安全

PyArmor 概述: PyArmor 是一个用于加密和保护 Python 源代码的工具,旨在防止代码被逆向工程和未经授权的使用.通过将 Python 源代码编译为加密的字节码,PyArmor 提供了一种有效的方法来保护知识产权和敏感算法. 安装 pip install pyarmor安装完成后,可以通过以下命令验证安装…

SaaS联盟分销系统如何高效管理推广渠道的实用指南

随着众多SaaS企业不断向PLG模式转型&#xff0c;传统的推广方式&#xff0c;比如广告投放推流、第三方cookie数据追踪等方式的成本效益比低下&#xff0c;更多的SaaS企业选择可“低成本&#xff0c;高回报”的联盟营销策略。比如Figama、Unbounce、Looka这些中小型SaaS企业&…

SCI丨5分期刊,JCR一区

SCI&#xff0c;5分&#xff0c;JCR Q1&#xff0c;中科大类3小类2区 1 基于复杂网络与xxx能源汽车节能数值分析 2 基于热能损失优化的xxx与性能管理 3 基于xxxLCA技术的绿色制造工艺优化研究 4 基于xxx入侵检测技术的物联网智能制造监控系统设计 6 基于物联网技术xxx电力系…

鸿蒙认证值得考吗?

鸿蒙认证值得考吗&#xff1f; 鸿蒙认证&#xff08;HarmonyOS Certification&#xff09;是华为为了培养和认证开发者在鸿蒙操作系统&#xff08;HarmonyOS&#xff09;领域的专业技能而设立的一系列认证项目。这些认证旨在帮助开发者和企业工程师提升在鸿蒙生态中的专业技能…

通过混合栅极技术改善p-GaN功率HEMTs的ESD性能

来源&#xff1a;Improved Gate ESD Behaviors of p-GaN PowerHEMTs by Hybrid Gate Technology&#xff08;ISPSD 24年&#xff09; 摘要 本工作中&#xff0c;首次证明了混合栅极技术在不增加额外面积和寄生效应的前提下&#xff0c;能有效提升p-GaN HEMTs的栅极静电放电(E…

刷代码随想录有感(124):动态规划——最长公共子序列

题干&#xff1a; 代码&#xff1a; class Solution { public:int findLength(vector<int>& nums1, vector<int>& nums2) {vector<vector<int>>dp(nums1.size() 1, vector<int>(nums2.size() 1, 0));int res 0;for(int i 1; i <…

[单master节点k8s部署]16.监控系统构建(一)Prometheus介绍

prometheus prometheus是继k8s之后&#xff0c;第二个被托管到CNCF的项目&#xff0c;是一个开源的监控报警系统。 1.prometheus支持多维数据模型&#xff0c;每一个时间序列数据都由metric度量指标名称和它的标签label组成一组键值对。 2.Prometheus有自己的PromQL查询语言…

mac|Mysql WorkBench导入文件失败(修改编码)

⚠️&#xff1a;表格中有中文的不适用表格中有中文的不适用表格中有中文的不适用表格中有中文的不适用 我有一个excel表&#xff0c;想导入到mysql数据库中&#xff0c;但是Workbench的导入格式只支持csv&#xff0c;通过excel、wps将excel另存为csv文件进行导入 导入会因为编…

Swift 中强大的 Key Paths(键路径)机制趣谈(下)

概览 在上一篇博文 Swift 中强大的 Key Paths(键路径)机制趣谈(上)中,我们介绍了 Swift 语言中键路径机制的基础知识,并举了若干例子讨论了它的一些用武之地。 而在本文中我们将再接再厉,继续有趣的键路径大冒险,为 KeyPaths 画上一个圆满的句号。 在本篇博文中,您将…

高考服务系统

摘 要 每年有大批考生在进行填写高考志愿时并不很清楚自己的高考分数适合那些高校以及专业。高考考生面临着未被高校录取&#xff0c;被调剂专业&#xff0c;甚至可能复读的问题。若能让考生轻松查询到高校录取、高校专业、高校招生等相关信息&#xff0c;能减少很大一部分考生…

2007-2023年36家商业银行绿色信贷、期末贷款总额、银行总资产等相关指标数据(2023年无缺失)

2007-2023年36家商业银行绿色信贷数据&#xff08;2023年无缺失&#xff09; 1.时间&#xff1a;2007-2023年&#xff0c;2023年无缺失 2.来源&#xff1a;银行年报和社会责任报告 3.指标:绿色信贷余额、期末贷款总额、绿色信贷比率、总资产收益率、流动性比率、拨备覆盖率、…

svn忽略上传文件node_modules文件

文章目录 1.点击svn项目右键-》选中svn的属性2. 点击 新建3. 点击其他4. 选择属性 svn:global-ignores5. 输入忽略文件 1.点击svn项目右键-》选中svn的属性 2. 点击 新建 3. 点击其他 4. 选择属性 svn:global-ignores 5. 输入忽略文件

通信软件开发之业务知识:PON口割接什么意思?

一 PON口割接&#xff08;原创总结&#xff09; 在通信领域&#xff0c;PON口割接指的是对无源光网络&#xff08;Passive Optical Network&#xff0c;PON&#xff09;端口进行的切换或调整操作。简单来说&#xff0c;就是对光纤网络中的某个端口进行重新连接或重新分配&…

C++基础(四):C++入门(三)

通过前面的学习&#xff0c;我们已经掌握了一些最基本的C入门知识&#xff0c;这一篇博客我们主要聚焦于&#xff1a;C/C内存管理和C11的一些新特性&#xff0c;为后续深入学习做好铺垫。 目录 一、C/C内存管理 1.1 C/C内存分布 1.2 C语言中动态内存管理方式&#xff1a;ma…

漆包线行业生产管理革新:万界星空科技MES系统解决方案

一、引言 在科技日新月异的今天&#xff0c;万界星空科技凭借其在智能制造领域的深厚积累&#xff0c;为漆包线行业量身打造了一套先进的生产管理执行系统&#xff08;MES&#xff09;解决方案。随着市场竞争的加剧&#xff0c;漆包线作为电气设备的核心材料&#xff0c;其生产…

再回首,什么是人工智能?

文章目录 一、说明二、什么是人工智能&#xff1f;三、人工智能的类型&#xff1a;弱人工智能与强人工智能四、深度学习与机器学习五、生成模型的兴起六、人工智能应用6.1 语音识别6.2 客户服务6.3 计算机视觉6.4 供应链6.5 天气预报6.6 异常检测 七、人工智能的历史&#xff1…

AI赋能视创云展元宇宙展厅,打造沉浸式智能观展体验!

在AI人工智能迅猛发展的背景下&#xff0c;视创云展将元宇宙展厅与“AI智能助手”深度融合&#xff0c;这无疑是一次前瞻性的创新尝试&#xff0c;极大地丰富了展览展示的形式与内容&#xff0c;为参观者打造了一个既沉浸又智能的观展新纪元。 一、沉浸式体验 元宇宙展厅利用虚…

将excel表格转换为element table(下)

在‘将excel表格转换为element table(上)’我们把excel 转换后通过数据重构绑定到了element table上&#xff0c;现在要做的就是根据源文件进行行列进行合并操作 先看看最终处理的结果 这里在一步步分析实现步骤。 先分析一下合并的逻辑 大致思路理理如上。 思路有了接下来…