C语言指针详解

C语言指针详解

  • 字符指针
    • 1.如何定义
    • 2.类型和指向的内容
    • 3.代码例子
  • 指针数组
    • 1.如何定义
    • 2.类型和内容
  • 数组指针
    • 1.如何定义
    • 2.类型和指向类型
    • 3.数组名vs&数组名
    • 数组指针运用
  • 数组参数&指针参数
    • 一维数组传参
    • 二维数组传参
    • 一级指针传参
    • 二级指针传参
  • 函数指针
    • 1.如何定义
    • 2.类型和指向内容
    • 3.函数名vs&函数名
    • 4.两个有趣的代码
  • 函数指针数组
    • 1.如何定义
    • 2.类型和内容
    • 3.1代码例子(switch语句实现计算器)
    • 3.2代码例子(函数指针数组实现计算器)
  • 指向函数指针数组的指针
    • 1.如何定义
    • 2.类型和指向内容
  • 回调函数
  • 一个小知识点如何找到指针和数组的类型和(指向)存储内容

博主主页zoro-1
祝大家有个好心情,给大家分享一下我拍的彩虹
在这里插入图片描述
在这里插入图片描述

字符指针

1.如何定义

int main() {
char ch='w';
char *pc=&ch;
*pc='w';
return 0;
}

2.类型和指向的内容

譬如char pc=&ch;
将=左边pc去掉就是指针类型char

将*和pc去掉就是指针指向的内容char

3.代码例子

例1

int main()
{
const char* pstr = "hello";//这里是把一个字符串放到pstr指针变量里了吗?
printf("%s\n", pstr);
return 0;}

这时我就要问一个问题了,pstr存储的是什么?
没错pstr存储的是hello的首元素地址

例2

#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");
return0;

大家猜猜这段代码会输出什么?
在这里插入图片描述
代码解释:str1,str2是存放char的数组的数组名,那么他们存放的就是数组首元素的地址,即使他们的内容相同,但地址是随机的,而str3,str4是被const修饰的指针变量,存放的也是字符的地址只不过这里的hello bit.在这里是存放在常量池的下一个str4不需要开辟新的空间,所以str3,str4他们指向的是同一个hello bit.所以相同

一个小知识点:
如果 const 用于修饰字符串常量,那么该字符串常量将存储在常量存储区(Constant Storage Area)。
常量存储区是用于存储常量字符串和全局常量的特殊内存区域,其中的数据在程序运行期间保持不变。

指针数组

1.如何定义

int main(){
int arr[5]={1,2,3,4,5};
int arr1[5]={2.3.4.5.6};
int arr2[5]={3,4,5,6,7};
int*p[3]={arr,arr1,arr};
}

2.类型和内容

去掉名字p就是类型
去掉p【3】就是存储的内容int*

数组指针

1.如何定义


int arr[5]={1,2,3,4,5};
int (*p)[10]=&arr;
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指
向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

2.类型和指向类型

去掉名字p就是类型
去掉*p就是指向内容

3.数组名vs&数组名

数组名是数组的首元素地址
但有两个例外:
1.&arr得arr表示整个数组的地址
2.sizeof(arr)表示整个数组的大小

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

大家猜猜这段代码执行结果是什么,如果看过我之前的指针初阶就应该知道他们的输出内容一样
在这里插入图片描述
代码解释:arr是数组首元素地址,&arr是整个数组的地址,虽然他们输出的内容一样,但是他们的权重不一样,
arr+1会跳过4字节,&arr+1会跳过整个数组也就是10*4,一共40个字节

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

在这里插入图片描述
这段代码就能很好解释&arr和arr的区别了

数组指针运用

我们很少用在一维数组,多数用在二维数组,接下来我用代码让大家感受一下数组指针
一维数组:

#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>
void print_arr1(int arr[3][5], int row, int col)
{
int i = 0;
for(i=0; i<row; i++)
{
for(j=0; j<col; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
void print_arr2(int (*arr)[5], int row, int col)
{
int i = 0;
for(i=0; i<row; i++)
{
for(j=0; j<col; j++)
{
printf("%d ", arr[i][j]);//相当于*(*(arr+i)+j)
}
printf("\n");
}
}
#include <stdioh>
int main()
{
int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
print_arr1(arr, 3, 5);
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
我们发现这两个遍历数组的1方法只有接收数组第一个形参不一样这里写成int arr[3][5]是为了大家好理解,
编译器会将int arr[3][5]转化成int (*arr)[5]
print_arr2(arr, 3, 5);
return 0;
}

在这里插入图片描述

数组参数&指针参数

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

一维数组传参

#include <stdio.h>
void test(int arr[])//ok?
显然是可以的传一维数组用一维数组接收
{}
void test(int arr[10])//ok?
显然是可以的只是加上了长度
{}
void test(int *arr)//ok?
这种可以int*arr是整型指针,一维数组传过来是第一个元素地址,第一个元素也是整型
{}
void test2(int *arr[20])//ok?
这种显然可以的传的是指针数组,也是用指针数组接收
{}
void test2(int **arr)//ok?
传的是指针数组的首元素地址,而元素又是指针所以用二级指针接收,二级指针解引用一次得到首元素(首元素就是指针int*{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}

二维数组传参

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

总结:
一维整形数组传参数,可以用一维整形数组接收,也可以用整形指针接收
一维指针数组,可以用一维指针数组接收,也可以用二维指针接收
二维整形数组传参数,可以用二维整形数组接收,也可以用数组指针接收

一级指针传参

#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);
//也可以直接写成printf(p,sz);
return 0;
}

二级指针传参

当函数参数是二级指针,可以接收哪些参数

void test(char **p)
{
}
int main()
{
char c = 'b';
char*pc = &c;
char**ppc = &pc;
char* arr[10];
test(&pc);
test(ppc);
test(arr);//Ok?
return 0;
}

函数指针

上面介绍了整形指针,数组指针,那么函数有没有指针呢?
答案是有

1.如何定义

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

2.类型和指向内容

去掉名字p就是类型
去掉*p就是指向内容

3.函数名vs&函数名

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

注:函数名是函数地址,&函数名也是函数地址,没有区别
在这里插入图片描述

4.两个有趣的代码

:推荐《C陷阱和缺陷》
这本书中提及这两个代码。
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

*代码1解释是将0看做成地址强制转化成函数指针然后解引用,调用函数
代码2解释signal()是一个函数有两个参数int,viod(*)(int),返回值是一个函数指针
代码2简化
typedef void(pfun_t)(int);
pfun_t signal(int, pfun_t);

函数指针数组

有指针数组,那么有没有函数指针数组呢
答案是有

1.如何定义


#include <stdio.h>
#include <string.h>int Add(int x, int y)
{return x + y;
}int Sub(int x, int y)
{return x - y;
}int Mul(int x, int y)
{return x * y;
}int Div(int x, int y)
{return x / y;
}int main()
{//int (*pf1)(int, int) = Add;//int (*pf2)(int, int) = Sub;//int (*pf3)(int, int) = Mul;//int (*pf4)(int, int) = Div;//函数指针数组//int (*pfArr[4])(int, int) = {Add, Sub, Mul, Div};//return 0;
}

2.类型和内容

去掉pfArr就是类型
去掉pfArr【4】就是存储的内容函数指针

3.1代码例子(switch语句实现计算器)

如果让你们实现自算器的简易功能,你们会怎么实现

int Add(int x, int y)
{return x + y;
}int Sub(int x, int y)
{return x - y;
}int Mul(int x, int y)
{return x * y;
}int Div(int x, int y)
{return x / y;
}
//目录
void menu()
{printf("***************************\n");printf("*****  1.add  2.sub  ******\n");printf("*****  3.mul  4.div  ******\n");printf("*****  0.exit        ******\n");printf("***************************\n");
}
int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;do{menu();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;
}

有没有感觉上面这段代码很冗杂需要这么多次case
接下来我用函数指针数组再来实现一下

3.2代码例子(函数指针数组实现计算器)

int Add(int x, int y)
{return x + y;
}int Sub(int x, int y)
{return x - y;
}int Mul(int x, int y)
{return x * y;
}int Div(int x, int y)
{return x / y;
}//...void menu()
{printf("***************************\n");printf("*****  1.add  2.sub  ******\n");printf("*****  3.mul  4.div  ******\n");printf("*****  0.exit        ******\n");printf("***************************\n");
}
int main()
{int input = 0;int x = 0;int y = 0;int ret = 0;//函数指针数组的使用 - 转移表int (* pfArr[5])(int, int) = {NULL, Add, Sub, Mul, Div};//                            0     1    2    3    4do{menu();printf("请选择:>");scanf("%d", &input);if (input >= 1 && input <= 4){printf("请输入两个操作数:");scanf("%d %d", &x, &y);ret = pfArr[input](x, y);printf("ret = %d\n", ret);}else if(input == 0){printf("退出计算器\n");}else{printf("选择错误,重新选择\n");}} while (input);return 0;
}

这里我们将函数储存在数组里面,直接通过下标调用是不是感觉很方便

指向函数指针数组的指针

这里给大家表演一个套娃,是不是感觉很绕口,这里我们不过多解释只讲定义(用处不多);

1.如何定义

void test(const char* str)
{printf("%s\n", str);
}int main()
{void (*pf)(const char*) = test;//pf是函数指针变量void (*pfArr[10])(const char*);//pfArr是存放函数指针的数组void (* (*p) [10])(const char*) = &pfArr;//p指向函数指针数组的指针return 0;
}

2.类型和指向内容

去掉p就是类型
去掉*p就是指向内容

回调函数

在这里插入图片描述

这个意思就是有函数A,B,main方法调用函数A将函数B地址作为参数传到函数A,A中利用函数地址调用函数B
这个我会在下一篇博客讲解qsort时举例讲解,

一个小知识点如何找到指针和数组的类型和(指向)存储内容

*类型都是去掉名字
指针指向的内容是去掉(指针名字)
数组存储内容是去掉名字和【】(中括号)

更多指针相关内容请听下回讲解,看到这里了,不妨给博主给个三连,要是想持续收听,也可以关注博主, 让我们一起变得更强吧,大家加油!!!!!

在这里插入图片描述

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

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

相关文章

Java ~ Collection/Executor ~ DelayQueue【总结】

前言 文章 相关系列&#xff1a;《Java ~ Collection【目录】》&#xff08;持续更新&#xff09;相关系列&#xff1a;《Java ~ Executor【目录】》&#xff08;持续更新&#xff09;相关系列&#xff1a;《Java ~ Collection/Executor ~ DelayQueue【源码】》&#xff08;学…

Elasticsearch笔记

迈向光明之路&#xff0c;必定荆棘丛生。 文章目录 一、Elasticsearch概述二、初识ES倒排索引1. 正向索引2. 倒排索引 三、ES环境搭建1. 安装单机版ES2. 安装Kibana3. 安装ik分词器3.1 在线安装ik插件3.2.离线安装ik插件&#xff08;推荐方式&#xff09;3.3 自定义词典 四、ES…

Linux上定位线上CPU飙高

【模拟场景】 写一个java main函数&#xff0c;死循环打印 System.out.println(“111111”) &#xff0c; 将其打成jar包放在linux中执行 1、通过TOP命令找到CPU耗用最厉害的那个进程的PID 2、top -H -p 进程PID 找到进程下的所有线程 可以看到 pid 为 94384的线程耗用cpu …

VUE3-04

1. 编写代码过程中的问题与解决 1.1 错误&#xff1a;cant read property of undefined(name) &#xff08;1&#xff09;首先定位错误的位置 &#xff08;2&#xff09;逐一排查问题&#xff1a;注释代码&#xff1b;debugger&#xff1b;console.log &#xff08;3&#xff0…

数组传参,指针传参

文章目录 一维数组传参二维数组传参一级指针传参二级指针传参 一维数组传参 二维数组传参 一级指针传参 二级指针传参

基于stm32单片机的直流电机速度控制——LZW

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 一、实验目的二、实验方法三、实验设计1.实验器材2.电路连接3.软件设计&#xff08;1&#xff09;实验变量&#xff08;2&#xff09;功能模块a&#xff09;电机接收信号…

Java【Spring】项目创建、存储和获取 Bean 的基本方式

文章目录 前言一、创建 Spring 项目1, 创建 Maven 项目2, 添加 Spring 依赖3, 创建启动类 二、存储 Bean 的基本方式1, 创建 Bean2, 存储 Bean 三、获取 Bean 的基本方式1, 获取上下文对象2, 获取 Bean3, 使用 Bean 总结 前言 各位读者好, 我是小陈, 这是我的个人主页, 希望我的…

Python基础入门教程(上)

目录 一、你好Python 1.1、Python安装 win版 Linux版 1.2、第一个Python程序 二、Python基本语法 2.1、字面量 2.2、注释 2.3、变量 2.4、数据类型 type()函数 字符串类型的不同定义方式 2.5、数据类型转换 ​编辑 2.6、标识符 2.7、运算符 2.8、字符串扩展 …

Vue2基础五、工程化开发

零、文章目录 Vue2基础五、工程化开发 1、工程化开发和脚手架 &#xff08;1&#xff09;开发 Vue 的两种方式 核心包传统开发模式&#xff1a;基于 html / css / js 文件&#xff0c;直接引入核心包&#xff0c;开发 Vue。工程化开发模式&#xff1a;基于构建工具&#xf…

【Python】数据分析+数据挖掘——探索Pandas中的索引与数据组织

前言 在数据科学和数据分析领域&#xff0c;Pandas是一个备受喜爱的Python库。它提供了丰富的数据结构和灵活的工具&#xff0c;帮助我们高效地处理和分析数据。其中&#xff0c;索引在Pandas中扮演着关键角色&#xff0c;它是一种强大的数据组织和访问机制&#xff0c;使我们…

【Unity2D】角色动画的切换

动画状态转换 第一种方法是设置一个中间状态&#xff0c;从中间状态向其余各种状态切换&#xff0c;且各状态向其他状态需要设置参数 实现动作转移时右键点击Make Transition即可 实现动画转移需要设置条件 点击一种动画到另一种动画的线 &#xff0c;然后点击加号添加Condi…

玩转LaTeX(三)【数学公式(基础)、​矩阵、多行公式】

数学公式基础 导言区&#xff08;引包&#xff09; \usepackage{amsmath} %带星号的eqution 正文区 \begin{document}%数学公式初步 \section{简介} \LaTeX{}将排版内容分为文本模式和数学模式。文本模式用于普通文本排版&#xff0c;数学模式用于数学公式排版。 …

【LeetCode】206.反转链表

题目 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#xff1a;[2,1]示例 3&#xff1a; …

oracle,获取每日24*60,所有分钟数

前言&#xff1a; 为规范用户的时间录入&#xff0c;因此我们采用下拉的方式&#xff0c;让用户选择需要的时间&#xff0c;因此我们需要将一天24小时的时间拆分为类似00:00,00:01...23:00,23:01,23:59。因此我们需要生成24*601440行的下拉复选值。具体效果如下图所示。 思路 1…

【Golang 接口自动化03】 解析接口返回XML

目录 解析接口返回数据 定义结构体 解析函数&#xff1a; 测试 优化 资料获取方法 上一篇我们学习了怎么发送各种数据类型的http请求&#xff0c;这一篇我们来介绍怎么来解析接口返回的XML的数据。 解析接口返回数据 定义结构体 假设我们现在有一个接口返回的数据resp如…

❤ yarn 和npm 的使用

❤ yarn 和npm 的使用 yarn 版本1的使用 yarn 简介 Yarn是facebook发布的一款取代npm的包管理工具。 yarn特点&#xff1a; 1&#xff0c;速度超快。 Yarn 缓存了每个下载过的包&#xff0c;所以再次使用时无需重复下载。 同时利用并行下载以最大化资源利用率&#xff0c;因…

【代理模式】了解篇:静态代理 动态代理~

目录 1、什么是代理模式&#xff1f; 2、静态代理 3、动态代理 3.1 JDK动态代理类 3.2 CGLIB动态代理类 4、JDK动态代理和CGLIB动态代理的区别&#xff1f; 1、什么是代理模式&#xff1f; 定义&#xff1a; 代理模式就是为其他对象提供一种代理以控制这个对象的访问。在某…

华为nat64配置

1.前期环境准备 环境拓扑 拓扑分为两个区域,左边为trust区域,使用IPv4地址互访,右边为untrust区域,使用IPv6地址互访 2.接口地址配置 pc1地址配置 pc2地址配置 FW接口配置 (1)首先进入防火墙配置界面 注:防火墙初始账号密码为user:admin,pwd:Admin@123,进入之后…

8.docker仓库

文章目录 Docker仓库本地私有仓库Docker HarborDocker harbor部署访问页面创建用户下载私有仓库镜像harbor同步 Docker仓库 本地私有仓库 ##先下载 registry 镜像docker pull registry##修改配置文件&#xff0c;在 daemon.json 文件中添加私有镜像仓库地址vim /etc/dock…

SQL-每日一题【1070. 产品销售分析 III】

题目 销售表 Sales&#xff1a; 产品表 Product&#xff1a; 编写一个 SQL 查询&#xff0c;选出每个销售产品 第一年 销售的 产品 id、年份、数量 和 价格。 结果表中的条目可以按 任意顺序 排列。 查询结果格式如下例所示&#xff1a; 示例 1&#xff1a; 解题思路 前置知…