C语言指针详解

news/2024/5/13 1:33:44

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.mrgr.cn/p/63056820

相关文章

MySQL(一)基本架构、SQL语句操作、试图

MySQL系列文章 MySQL&#xff08;一&#xff09;基本架构、SQL语句操作、试图 MySQL&#xff08;二&#xff09;索引原理以及优化 MySQL&#xff08;三&#xff09;SQL优化、Buffer pool、Change buffer MySQL&#xff08;四&#xff09;事务原理及分析 MySQL&#xff08;五&a…

LangChain大型语言模型(LLM)应用开发(五):评估

LangChain是一个基于大语言模型&#xff08;如ChatGPT&#xff09;用于构建端到端语言模型应用的 Python 框架。它提供了一套工具、组件和接口&#xff0c;可简化创建由大型语言模型 (LLM) 和聊天模型提供支持的应用程序的过程。LangChain 可以轻松管理与语言模型的交互&#x…

NLP实验案例100个(1-5)

实验一 array数组&#xff08;01&#xff09; 一、实验目的及要求 1.安装numpy环境&#xff0c;掌握基本的数组知识以及操作。 二、实验设备&#xff08;环境&#xff09;及要求 开发环境&#xff1a;jupyter notebook 开发语言以及相关的库&#xff1a;python开发语言、nu…

【计算机视觉 | 目标检测 | 图像分割】arxiv 计算机视觉关于目标检测和图像分割的学术速递(7 月 17 日论文合集)

文章目录 一、检测相关(5篇)1.1 TALL: Thumbnail Layout for Deepfake Video Detection1.2 Cloud Detection in Multispectral Satellite Images Using Support Vector Machines With Quantum Kernels1.3 Multimodal Motion Conditioned Diffusion Model for Skeleton-based Vi…

iOS开发-NotificationServiceExtension实现实时音视频呼叫通知响铃与震动

iOS开发-NotificationServiceExtension实现实时音视频呼叫通知响铃与震动 在之前的开发中&#xff0c;遇到了实时音视频呼叫通知&#xff0c;当App未打开或者App在后台时候&#xff0c;需要通知到用户&#xff0c;用户点击通知栏后是否接入实时音视频的视频或者音频通话。 在…

计算机网络 day7 扫描IP脚本 - 路由器 - ping某网址的过程

目录 network 和 NetworkManager关系&#xff1a; 实验&#xff1a;编写一个扫描脚本&#xff0c;知道本局域网里哪些ip在使用&#xff0c;哪些没有使用&#xff1f; 使用的ip对应的mac地址都要显示出来 计算机程序执行的两种不同方式&#xff1a; shell语言编写扫描脚本 …

如何维护你的电脑:提升性能和延长使用寿命

如何维护你的电脑&#xff1a;提升性能和延长使用寿命 &#x1f607;博主简介&#xff1a;我是一名正在攻读研究生学位的人工智能专业学生&#xff0c;我可以为计算机、人工智能相关本科生和研究生提供排忧解惑的服务。如果您有任何问题或困惑&#xff0c;欢迎随时来交流哦&…

在自定义数据集上微调Alpaca和LLaMA

本文将介绍使用LoRa在本地机器上微调Alpaca和LLaMA&#xff0c;我们将介绍在特定数据集上对Alpaca LoRa进行微调的整个过程&#xff0c;本文将涵盖数据处理、模型训练和使用流行的自然语言处理库(如Transformers和hugs Face)进行评估。此外还将介绍如何使用grado应用程序部署和…

高算力AI模组前沿应用:基于ARM架构的SoC阵列式服务器

本期我们带来高算力AI模组前沿应用&#xff0c;基于ARM架构的SoC阵列式服务器相关内容。澎湃算力、创新架构、异构计算&#xff0c;有望成为未来信息化社会的智能算力底座。 ▌性能优势AI驱动&#xff0c;ARM架构服务器加速渗透 一直以来&#xff0c;基于ARM架构的各类处理器…

对比CahtGPT Bard Claude2对中文的理解

对比CahtGPT Bard Claude2对中文的理解 今天简单测试了一下目前这三个很火的模型对中文的理解能力 简单问题 鲁迅和周树人的关系 Bard CahtGPT Claude 介绍一下平凡的世界这本书 Bard CahtGPT

GitHub仓库如何使用

核心&#xff1a;GitHub仓库如何使用 目录 1.创建仓库&#xff1a; 2.克隆仓库到本地&#xff1a; 3.添加、提交和推送更改&#xff1a; 4.分支管理&#xff1a; 5.拉取请求&#xff08;Pull Requests&#xff09;&#xff1a; 6.合并代码&#xff1a; 7.其他功能&…

网络安全行业相关证书

一&#xff1a;前言 对于考证这个话题&#xff0c;笔者的意见是&#xff1a;“有比没有好&#xff0c;有一定更好&#xff0c;但不一定必须&#xff1b;纸上证明终觉浅&#xff0c;安全还得实力行”。很多人对于各种机构的考证宣传搞得是云里雾里&#xff0c;不知道网络安全行业…

虚拟局域网VLAN

概述 广播域 使用一个或多个以太网交换机互连接起来的交互式以太网&#xff0c;其所有站点都属于同一个广播域&#xff0c;随着交换式以太网规模的扩大&#xff0c;广播域响应扩大&#xff0c;从而形成一个巨大的广播域。 但是巨大的广播域会带来很多的弊端&#xff1a; 广…

Ubuntu 放弃了战斗向微软投降

导读这几天看到 Ubuntu 放弃 Unity 和 Mir 开发&#xff0c;转向 Gnome 作为默认桌面环境的新闻&#xff0c;作为一个Linux十几年的老兵和Linux桌面的开发者&#xff0c;内心颇感良多。Ubuntu 做为全世界Linux界的桌面先驱者和创新者&#xff0c;突然宣布放弃自己多年开发的Uni…

REST API的基础:HTTP

在本文中&#xff0c;我们将深入探讨万维网数据通信的基础 - HTTP。 什么是超文本&#xff1f; HTTP&#xff08;超文本传输协议&#xff09;的命名源于“超文本”。 那么&#xff0c;什么是超文本&#xff1f; 想象一下由超链接组成的文本、图像和视频的混合物。这些链接充当我…

使用TensorFlow训练深度学习模型实战(下)

大家好&#xff0c;本文接TensorFlow训练深度学习模型的上半部分继续进行讲述&#xff0c;下面将介绍有关定义深度学习模型、训练模型和评估模型的内容。 定义深度学习模型 数据准备完成后&#xff0c;下一步是使用TensorFlow搭建神经网络模型&#xff0c;搭建模型有两个选项…

Android 中 app freezer 原理详解(一):S 版本

基于版本&#xff1a;Android S 0. 前言 在之前的两篇博文《Android 中app内存回收优化(一)》和 《Android 中app内存回收优化(二)》中详细剖析了 Android 中 app 内存优化的流程。这个机制的管理通过 CachedAppOptimizer 类管理&#xff0c;为什么叫这个名字&#xff0c;而不…

k8s一站式使用笔记

前言 个人感觉比较磨心态&#xff0c;要坐住&#xff0c;因为细节太多&#xff0c;建议&#xff1a;一遍看个大概&#xff0c;二遍回来细品&#xff0c;不要当成任务&#xff0c;把握零碎时间 一、k8s安装 1、配置准备 硬件要求 内存&#xff1a;2GB或更多RAMCPU: 2核CPU或更…

【RabbitMQ】Linux系统服务器安装RabbitMQ

一、下载 首先应该下载erlang&#xff0c;rabbitmq运行需要有erland环境。 官网地址&#xff1a;https://www.erlang.org/downloads 下载rabbitmq 官网环境&#xff1a;https://www.rabbitmq.com/download.html 注意&#xff1a;el7对应centos7&#xff0c;el8对应centos8…

centos下安装ftp-读取目录列表失败-

1.下载安装ftp服务器端和客户端 #1.安装yum -y install vsftpdyum -y install ftp #2.修改配置文件vim /etc/vsftpd.conflocal_enablesYESwrite_enableYESanonymous_enableYESanon_mkdir_write_enableYES //允许匿名用户在FTP上创建目录anon_upload_enableYES //允许匿名用户…