C语言详解指针

目录

一、指针的概念

1.1内存与地址

 例子:

二、变量的指针与指针变量

2.1、指针变量的定义及使用

1、指针变量的定义

2、指针变量的使用

2.2 指针变量的大小

2.3、指针+-整数

 2.4、void*指针

三、指针的运算

1、指针+- 整数

2、指针-指针

3、指针的关系运算

6. 野指针

6.1、野指针成因

6.2、如何规避野指针

6.3、注意指针不要越界

6.4、当指针不再使用时,可以将其置为NULL,指针使用前,判断其有效性

6.2、assert函数

四、多级指针及指针数组

(1)多级指针

五、计算器(转移表)的使用

1、计算器的实现(switch)

什么是转移表?

对于指针的学习还很多,今天先讲到这里了,点点赞吧!!!


一、指针的概念

要知道指针的概念,要先了解变量在内存中如何存储的。在存储时,内存被分为一块一块的。每一块都有一个特有的编号。而这个编号可以暂时理解为指针,就像酒店的门牌号一样。

1.1内存与地址

在讲内存和地址之前,我们想有个⽣活中的案例:

假设你要去酒店,酒店有100个房间,但是房间没有编号,你的⼀个朋友来找你玩,如果他想要找到你,就得一个一个去找,这样的效率是很低的,那我们给定每个房间编号

⼀楼:101,102,103...

⼆楼:201,202,203....

...

你的朋友得到房间号,就可以快速找到你的房间,找到你。

如果把上面的例子对找到计算机中: 

如: 你的朋友 就相当于计算器

         而你就是房间(地址)的内存了

         101 102 103  就相当于地址           

 在计算机中我们把内存单元的编号也称为地址。C语言中给地址起了新的名字叫:指针 

 例子:

void main(){int x = 1, int y = 2;} 

这段代码非常简单,就是两个变量的声明,分别赋值了 1、2。我们把内存当做一个酒店,而每个房间就是一块内存。那么“int x = 1;”和“int y = 2;”的实际含义如下:

去酒店订了两个房间,门牌号暂时用 px、py 表示
让 1 住进 px,让 2 住进 py
其中门牌号就是 px、py 就是变量的地址
x 和 y 在这里可以理解为具体的房间,房间 x 的门牌号(地址)是 px,房间 y 的门牌号(地址)是 py。而 1和 2,通过 px、py 两个门牌,找到房间,住进 x、y。

如果你想要更直观的观察,可以观看上节中VS调试的监视

VS实⽤调试技巧-CSDN博客icon-default.png?t=N7T8https://blog.csdn.net/Asuku_/article/details/137396302?spm=1001.2014.3001.5502

二、变量的指针与指针变量

变量的指针就是变量的存储地址,指针变量就是存储指针的变量。

2.1、指针变量的定义及使用

1、指针变量的定义

指针变量的定义形式如:数据类型 *指针名;例如:

//分别定义了 int、float、char 类型的指针变量

 int *x;

float *f;

char *ch;

 这里的指针变量是x,f,ch,并非  *x  ,  *f,  *ch.

数据名为int* , float*, char*

2、指针变量的使用

我们要怎样取地址呢?这就要用到——取地址运算符& 和 指针运算符*(间接寻址符)

取地址运算符&:单目运算符&是用来取操作对象的地址。例:&i 为取变量 i 的地址。对于常量表达式、寄存器变量不能取地址(因为它们存储在存储器中,没有地址)。


指针运算符*(间接寻址符):与&为逆运算,作用是通过操作对象的地址,获取存储的内容。例:x = &i,x 为 i 的地址,*x 则为通过 i 的地址,获取 i 的内容。

int main() {int a = 10;//输入一个整型变量a,变量的值为10int* pa = &a;//输入一个整型的指针变量pa来接收存放a的地址printf("%d",*pa);//(*)表示对pa存放地址的解引用return 0;
}

2.2 指针变量的大小

前⾯的内容我们了解到,32位机器假设有32根地址总线,每根地址线出来的电信号转换成数字信号后是1或者0,那我们把32根地址线产生的2进制序列当做⼀个地址,那么⼀个地址就是32个bit位,需要4 个字节才能存储。
如果指针变量是⽤来存放地址的,那么指针变的大小就得是4个字节的空间才可以。
同理64位机器,假设有64根地址线,⼀个地址就是64个⼆进制位组成的⼆进制序列,存储起来就需要 8个字节的空间,指针变的大小就是8个字节。
#include <stdio.h>
//指针变量的⼤⼩取决于地址的大小
//32位平台下地址是32个bit位(即4个字节)
//64位平台下地址是64个bit位(即8个字节)
int main()
{
printf("%zd\n", sizeof(char *));
printf("%zd\n", sizeof(short *));
printf("%zd\n", sizeof(int *));
printf("%zd\n", sizeof(double *));
return 0;
}

怎么回事呢?想必你也发现些许奥秘,没错,对于指针来说,指针变量的大小与类型无关、只要指针类型的变量,在相同的平台下,大小都是相同的。

2.3、指针+-整数

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

让我们打印一下吧!!! 

 我们可以看得出来 ,char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节。 这就是指针变量的类型差异带来的变化。

结论:指针的类型决定了指针向前或者向后⾛⼀步有多⼤(距离)。

 

 2.4、void*指针

在指针类型中有⼀种特殊的类型是 void* 类型的,可以理解为无具体类型的指针(或者叫泛型指
针),这种类型的指针可以⽤来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进 行指针的+-整数和解引用的运算。
我们来看一下这个代码:
#include <stdio.h>
int main()
{int a = 10;int* pa = &a;char* pc = &a;return 0;
}

显示从int*到char*的类型不兼容,编译器会给一个报错,用void*则不会出现这个问题

#include <stdio.h>
int main()
{int a = 10;void* pa = &a;void* pc = &a;return 0;
}

这里我们看到,void*可以用来接收不同类型的指针,但是注意的是void*不能用来指针的运算。

三、指针的运算

指针的基本运算有三种,分别是:
指针+- 整数
指针-指针
指针的关系运算

1、指针+- 整数

因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素。
#include <stdio.h>
//指针+- 整数
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int *p = &arr[0];
int i = 0;
int sz = sizeof(arr)/sizeof(arr[0]);
for(i=0; i<sz; i++)
{
printf("%d ", *(p+i));     //p+i 这⾥就是指针+整数
}
return 0;
}

2、指针-指针

//指针-指针
#include <stdio.h>
int my_strlen(char *s)
{
char *p = s;
//每次++,到达下一个元素,到最后一个为NULL时,p指向c
while(*p != '\0' )
p++;
return p-s;     
//返回地址的差值,因为是char*类型,每个地址间跳过一个字节,所以返回的}
int main( )
{
printf("%d\n", my_strlen("abc"));
return 0;
}

3、指针的关系运算

//指针的关系运算
#include <stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = &arr[0];int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);while (p < arr + sz) //指针的大小比较{printf("%d ", *p);p++;}return 0;
}

6. 野指针

概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

6.1、野指针成因

指针未初始化:
#include <stdio.h>
int main()
{int *p;//局部变量指针未初始化,默认为随机值
*p = 20;return 0;}
指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = &arr[0];
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
指针指向的空间释放
#include <stdio.h>
int* test()
{
int n = 100;
return &n;
}int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
那我们如何规避野指针的出现呢

6.2、如何规避野指针

指针初始化,如果我们明确指针的指向的话,该指哪就指向哪;当我们不知道指向哪时, 可以给指针赋值NULL. NULL 是C语⾔中定义的⼀个标识符常量,值是0;0也是地址,这个地址是⽆法使⽤的,读写该地址 会报错。
#include <stdio.h>
int main()
{
int num = 10;
int*p1 = &num
int*p2 = NULL;return 0;
}

6.3、注意指针不要越界

⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是 越界访问。

6.4、当指针不再使用时,可以将其置为NULL,指针使用前,判断其有效性

当指针变量指向⼀块区域的时候,我们可以通过指针访问该区域,后期不再使⽤这个指针访问空间的 时候,我们可以把该指针置为NULL。因为约定俗成的⼀个规则就是:只要是NULL指针就不去访问, 同时使⽤指针之前可以判断指针是否为NULL。
我们可以把指针想为野狗,不管理的野狗是非常危险的,我们可以将野狗栓在一棵树(NULL),就相对安全了;

6.2、assert函数

assert.h 头文件定义了宏 assert() ,⽤于在运行时确保程序符合指定条件,如果不符合,就报
错终止运行。这个宏常常被称为“断⾔”。
assert(p != NULL );

 运行这个语句的时候,判断p是否为空,如果不为空则,程序继续运行,如果为空,则终止程序,并给出错误的信息提示。

四、多级指针及指针数组

(1)多级指针

指针变量作为一个变量也有自己的存储地址,而指向指针变量的存储地址就被称为指针的指针,即二级指针。依次叠加,就形成了多级指针。

int p = 10;
//设置一个变量为p
int* pc = &p;
//取p的地址存进pc
int** pt = &pc;
//取pc的地址存进pt,这里的pt为二级指针

 先用一个简单的数组来举例:

int nums[2][2] = {	{1, 2},{2, 3}};
#include<stdio.h>int main()
{int arr[2][2] = { {1,2},{2,3} };int* pc = &arr;printf("%d ", *pc);printf("%d\n", pc);printf("%d ", *(pc+1));printf("%d\n", (pc + 1));printf("%d ", *(pc+2));printf("%d\n", (pc + 2));printf("%d ", *(pc+3));printf("%d\n", (pc + 3));return 0;
}

我们可以看出二维数组地址的运用,可以看作一维数组;以此来推断,面对多维数组地址的运用时,我们不必害怕,可以当作一维数组

4.3、指向函数的指针

C 语言中,函数不能嵌套定义,也不能将函数作为参数传递。但是函数有个特性,即函数名为该函数的入口地址。我们可以定义一个指针指向该地址,将指针作为参数传递。

对于函数参数传递时,如果是传址函数时,则可以改变该地址的函数值;如为传值,则不然;

#include<stdio.h>void comp_int(int*comp) {*comp = 10;
}int main()
{int a = 10;int b = 20;comp_int(&b);if (a == b) {printf("相等");}return 0;
}

这里的输出为:相等,则我们可以通过传址改该地址的值;

五、计算器(转移表)的使用

1、计算器的实现(switch)

对于一个计算器,需要有最基础的(加减乘除),来人,上代码:

这时候有人就会觉得这个代码有点冗长,说:小伞,小伞有没有办法,能将代码变得便洁吗?

那当然是有的呀!

#define _CRT_SECURE_NO_WARNINGS#include<stdio.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 a = 0;int x, y;int ret;do{printf("*************************\n");printf("    1:add     2:sub      \n");printf("    3:mul     4:div      \n");printf("    0:exit               \n");printf("*************************\n");printf("请选择:");scanf("%d", &a);switch (a){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 (a);return 0;
}

如果我们使用switch语句来实现这样一个简易的计算器我们会发现,每当我要添加一个功能的时候。都需要增加一个case语句,比如我要增加一个&运算,我得再加上一个case语句。因此我们可以使用函数指针数组(转移表)来实现,会简易很多。

什么是转移表?

其实很简单,它所指的就是运用函数指针数组以数组方式去调用里面的函数,从而在某些情况下替代冗长的switch函数,就叫转移表。
单纯的文字说明实在有些单调,这里通过模拟实现计算器来进一步解释说明转移表。

上代码:

#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;//p[]={add,sub,mul,div}//这里我们想要将函数的地址存进来//int(*p[])(int,int)//返回类型   函数指针  指向函数的参数int(*p[5])(int , int )= { 0, add, sub, mul, div }; //转移表//函数指针的数组     下标    0   1    2    3    4do{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 = (*p[input])(x, y); //printf("ret = %d\n", ret);}else if (input == 0){printf("退出计算器\n");}else{printf("输⼊有误\n");}} while (input);return 0;
}

对于指针的学习还很多,今天先讲到这里了,点点赞吧!!!

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

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

相关文章

一套3种风格经典的wordpress免费主题模板

wordpress免费企业主题 https://www.wpniu.com/themes/39.html 免费wordpress企业模板 https://www.wpniu.com/themes/43.html 免费wordpress企业主题 https://www.wpniu.com/themes/44.html

使用docker部署数据可视化平台Metabase

目前公司没有人力开发数据可视化看板&#xff0c;因此考虑自己搭建开源可视化平台MetaBase。在此记录下部署过程~ 一、镜像下载 docker pull metabase/metabase:latest 运行结果如下&#xff1a; 二、创建容器 docker run -dit --name matebase -p 3000:3000\ -v /home/loc…

Python编程之旅:深入探索强大的容器——列表

在Python编程的世界中&#xff0c;容器&#xff08;Containers&#xff09;是一种用于存储多个项目的数据结构。其中&#xff0c;列表&#xff08;List&#xff09;是最常用且功能强大的容器之一。无论是初学者还是资深开发者&#xff0c;掌握列表的使用方法和技巧都是提升Pyth…

四.吊打面试官系列-数据库优化-Mysql锁和事务原理

前言 本篇文章主要讲解两块内容&#xff1a;Mysql中的锁和ACID原理&#xff0c;这2个部分是面试的时候被问的蛮多的看完本篇文章之后相信你对Mysql事务会有更深层次的理解&#xff0c;如果文章对你有所帮助请记得好评 一.Mysql中的锁 1.锁的分类 在Mysql中锁也分为很多种&a…

揭秘AI精准输出:如何构建完美的AIGC提示词?

揭秘AI精准输出&#xff1a;如何构建完美的AIGC提示词&#xff1f;&#x1f916; 文章目录 揭秘AI精准输出&#xff1a;如何构建完美的AIGC提示词&#xff1f;&#x1f916;摘要引言正文&#x1f4d8; 提示词的基本概念1. 什么是提示词&#xff1f;2. 提示词的作用 &#x1f4d…

Redis安装和使用(Ubuntu系统)

本节内容包括Redis简介、安装Redis和Redis实例演示等&#xff0c;Redis在Window系统安装教程可参考Redis安装与运行_厦大数据库实验室博客 Redis是一个键值&#xff08;key-value&#xff09;存储系统&#xff0c;即键值对非关系型数据库。Redis提供了Python、Ruby、Erlang、P…

【面试八股总结】排序算法(二)

参考资料 &#xff1a;阿秀 一、堆排序 堆排序基本思想是先把数组构造成一个大顶堆(父亲节点大于其子节点)&#xff0c;然后把堆顶(数组最大值&#xff0c;数组第一个元素)和数组最后一个元素交换&#xff0c;这样就把最大值放到了数组最后边。把数组长度n-1,再进行构造堆把剩…

开源AI聊天机器人应用程序模板; WrenAI用AI从数据中获取洞见;模拟多个代理人(agents)之间语言互动的仿真系统;语音数据集标注

✨ 1: gemini-chatbot 使用Next.js构建的开源AI聊天机器人应用程序模板 Gemini-chatbot是一个使用Next.js构建的开源AI聊天机器人应用程序模板。它利用了Vercel AI SDK、Google Gemini以及Vercel KV来提供一个功能丰富、可定制的聊天体验。这个聊天机器人可以支持多种不同的A…

GitHub repository - commits - branches - releases - contributors

GitHub repository - commits - branches - releases - contributors 1. commits2. branches3. releases4. contributorsReferences 1. commits 在这里可以查看当前分支的提交历史。左侧的数字表示提交数。 2. branches 可以查看仓库的分支列表。左侧的数字表示当前拥有的分…

我们试用了6款最佳Appium替代工具,有些甚至比Appium更好

Appium是一款知名的自动化测试工具&#xff0c;用于在iOS、Android和Windows等移动平台上运行测试。就开源移动测试自动化工具而言&#xff0c;虽然替代品有限&#xff0c;但它们确实存在。我们找到了一些优秀的Appium替代品&#xff0c;它们也可以满足自动化测试要求&#xff…

Jenkins上面使用pnpm打包

问题 前端也想用Jenkins的CI/CD工作流。 步骤 Jenkins安装NodeJS插件 安装完成&#xff0c;记得重启Jenkins。 全局配置nodejs Jenksinfile pipeline {agent anytools {nodejs "18.15.0"}stages {stage(Check tool version) {steps {sh node -vnpm -vnpm config…

RabbitMQ消息模型之Simple消息模型

simple消息模型 生产者 package com.example.demo02.mq.simple;import com.example.demo02.mq.util.ConnectionUtils; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection;import java.io.IOException;/*** author Allen* 4/10/2024 8:07 PM* versi…

HDFS的Shell操作

目录 一、进程启停管理 &#xff08;一&#xff09;一键启停脚本 &#xff08;二&#xff09;单进程启停 二、文件系统操作命令 &#xff08;一&#xff09;HDFS文件系统基本信息 1.前置介绍 &#xff08;二&#xff09;命令介绍 1.新旧版本命令介绍 2.创建文件夹 3.…

秋招算法刷题7

20240410 1.接雨水 方法一&#xff0c;动态规划&#xff0c;时间复杂度O&#xff08;n^2&#xff09;&#xff0c;空间复杂度O&#xff08;n&#xff09; public int trap(int[] height) { int nheight.length; if(n0){ return 0; } …

VR紧急情况模拟|V R体验中心加盟|元宇宙文旅

通过VR技术实现紧急情况模拟&#xff0c;提升安全应急能力&#xff01; 简介&#xff1a;面对突发紧急情况&#xff0c;如火灾、地震、交通事故等&#xff0c;正确的反应和应对能够有效减少伤害和损失。为了提高人们在紧急情况下的应急能力&#xff0c;我们借助先进的虚拟现实…

linux项目部署 解决Nginx浏览器刷新出现404,但是不刷新是能够正常请求成功

文章目录 目录 文章目录 安装流程 小结 概要安装流程技术细节小结 概要 提示&#xff1a;部署成功&#xff0c;访问登录页面登录也成功&#xff0c;强制刷新浏览器报404问题 进入到系统 刷新页面 解决流程 参考如图&#xff0c;再下面添加这条配置信息 location / {try_file…

Qt---控件的基本属性

文章目录 enabled(控件可用状态)geometry(位置和尺寸)简单恶搞程序 windowIcon(顶层 widget 窗口图标)使用 qrc 机制 windowOpacity(窗口的不透明值)cursor(当鼠标悬停空间上的形状)自定义鼠标图标 toolTip(鼠标悬停时的提示)focusPolicy(控件获取焦点的策略)styleSheet(通过CS…

算法练习第15天|226.翻转二叉树

226.翻转二叉树 力扣链接https://leetcode.cn/problems/invert-binary-tree/description/ 题目描述&#xff1a; 给你一棵二叉树的根节点 root &#xff0c;翻转这棵二叉树&#xff0c;并返回其根节点。 示例 1&#xff1a; 输入&#xff1a;root [4,2,7,1,3,6,9] 输出&am…

EDI是什么:EDI系统功能介绍

EDI全称Electronic Data Interchange&#xff0c;中文名称是电子数据交换&#xff0c;也被称为“无纸化贸易”。EDI实现企业间&#xff08;B2B&#xff09;自动化通信&#xff0c;帮助贸易伙伴和组织完成更多的工作、加快物流时间并消除人为错误。 目前国内企业实现EDI通信大多…

智慧公厕系统厂家,打造创新性智慧公厕的窍门

智慧公厕系统是利用物联网、大数据、云计算、网络通信、自动化控制等技术&#xff0c;监测公厕内部多个方面的变化&#xff0c;从而实现公厕的智能化管理。通过智慧公厕云管理平台&#xff0c;可以实现厕位空余智能引导、环境监测、资源消耗监测、安全防范管理等多种功能&#…