筑基九层 —— 指针详解
目录
前言:
指针详解
前言:
1.CSDN由于我的排版不怎么好看,我的有道云笔记比较美观,请移步有道云笔记
2.修炼必备
1)入门必备:VS2019社区版,下载地址:Visual Studio 较旧的下载 - 2019、2017、2015 和以前的版本 (microsoft.com)
2)趁手武器:印象笔记/有道云笔记
3)修炼秘籍:牛客网 - 找工作神器|笔试题库|面试经验|实习招聘内推,求职就业一站解决_牛客网 (nowcoder.com)
4)雷劫必备:leetcode 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台
注:遇到瓶颈怎么办?百度百科_全球领先的中文百科全书 (baidu.com)
指针详解
——指针是C语言中最核心的部分,不了解指针,就掌握不了C语言的精髓
1.指针是什么?
1)指针是内存中一个最小单元的编号,即指针就是地址
2)指针变量是用来存放内存地址的变量
图解:
2.我们如何取出变量的地址?
——使用&符号取出变量的地址,使用相应数据类型的指针变量保存该地址
#include <stdio.h>int main()
{int num = 10;int* p = #//取出了num的地址赋给了p//打印查看地址printf("&num = %p\n", &num);printf("p = %p\n", p);return 0;
}
运行结果:
3.指针的大小
——32位平台下的指针大小是4个字节,64位的平台下指针的大小是8个字节
#include <stdio.h>int main()
{printf("%d\n", sizeof(char*));printf("%d\n", sizeof(short*));printf("%d\n", sizeof(int*));printf("%d\n", sizeof(long*));printf("%d\n", sizeof(long long*));printf("%d\n", sizeof(float*));printf("%d\n", sizeof(double*));return 0;
}
32位平台运行结果:

64位平台运行结果:

4.指针和指针类型
1)指针的定义的方式
a.指针的定义方式:数据类型*;
char*:字符指针
int*:整型指针
long long*:长长整型指针
float*:单精度指针
double*:双精度指针
b.一般情况下,那种类型的指针则存储那种类型变量的地址
#include <stdio.h>int main()
{char c = 'a';int num = 10;float f = 1.342;double data = 13.14;//一般情况,那种类型的指针变量存储那种类型变量的地址char* ch = &c;int* p = #float* p1 = &f;double* p2 = &data;return 0;
}
2)指针的类型决定了指针向前或向后走一步有多大的字节距离
#include <stdio.h>int main()
{char c = 'c';int num = 10;char* p1 = &c;int* p2 = #printf("%p\n", p1);printf("%p\n", p1+1);printf("%p\n", p2);printf("%p\n", p2+1);return 0;
}
运行结果:

3)指针的类型决定了指针在解引用的时候能操作几个字节【访问权限多大】
#include <stdio.h>int main()
{int num = 0x44332211;char* p1 = #int* p2 = #printf("%d\n", *p1);printf("%d\n", *p2);*p1 = 0;*p2 = 0;return 0;
}
调试查看结果:


5. 野指针的问题
野指针就是指针指向的位置是不可知的【随机、不正确、无限制】
1)指针未初始化
#include <stdio.h>int main()
{int* p;printf("%p\n", p);return 0;
}
运行结果:

2)指针越界访问
#include <stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;for (int i = 0; i <= 11; i++){//p的访问范围超过了数组的下标范围,p就是野指针printf("%d ", *p);p++;}return 0;
}
3)返回局部变量的地址
#include <stdio.h>
int test()
{int a = 10;return &a;
}int main()
{int *p = test();return 0;
}
4)指针指向的空间未释放
6.防止野指针的问题
1)指针初始化
2)小心指针越界
3)指针指向空间释放,及时置为NULL
4)避免返回局部变量的地址
5)使用指针之前检查指针的有效性
#include <stdio.h>void test(int* p)
{//检查指针的有效性if(p == NULL){}
}int main()
{int* p = NULL;//不知道指针指向哪里的时候置为NULLtest(p);return 0;
}
7.指针运算
1)指针+-整数
#include <stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p;for (p = arr; p < &arr[10]; p++){printf("%d ", *p);}return 0;
}
2)指针-指针【地址-地址】
两个指针指向的是同一块空间且类型是一致的,两者相减得到是元素个数
#include <stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p1 = arr;int* p2 = &arr[10];//指针-指针printf("%d\n", p2 - p1);//10return 0;
}
3)指针的关系运算
#include <stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p;for (p = &arr[10]; p > &arr[0];){*--p = 0;}return 0;
}
为什么这种方法也可以,但是不使用呢?
#include <stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p;for (p = &arr[9]; p >= &arr[0]; p--){*p = 0;}return 0;
}
C语言中的标准规定,允许指向数组元素的指针能与指向数组最后一个元素的后面那个内存位置的指针比较,但不允许与数组元素第一个元素前面的那一个内存地址的指针比较
8.指针与数组
1)数组名是数组首元素的地址,&数组名是取出整个数组的地址
#include <stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;printf("arr = %p\n", arr);printf("arr + 1 = %p\n", arr + 1);printf("&arr = %p\n", &arr);printf("&arr + 1 = %p\n", &arr + 1);return 0;
}
运行结果:

2)指针能指向数组的任意一个元素,即数组每个元素的地址指针均能获取
#include <stdio.h>int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;for (int i = 0; i < 10; i++){printf("%p == %p\n", &arr[i], p+i);}return 0;
}
运行结果:

9.二级指针【指针的指针】
int num = 10;
int* p = #//*p代表这是指针,int表示指向的类型是int类型
int**pp = &p;//*pp代表是指针,int*表示指向的类型是int*类型
#include <stdio.h>int main()
{int num = 10;int* p = #//*p表示这是一个指针,指向的类型是intint** pp = &p;//*pp表示这是一个指针,指向的类型是int*printf("%d\n", num);//10printf("%d\n", *p);//10printf("%d\n", **pp);//10return 0;
}
10.字符指针的使用方式
1)字符指针指向一个字符变量
2)字符指针指向一个常量字符串【常量字符串的首元素地址赋给字符指针】
#include <stdio.h>int main()
{char ch = 'a';//字符指针指向一个字符变量char* p1 = &ch;printf("%c\n", *p1);//字符指针指向一个常量字符串char* p2 = "abcdef";printf("%c\n", *p2);printf("%s\n", p2);return 0;
}
运行结果:

一道简单的笔试题:
#include <stdio.h>int main()
{char str1[] = "abcdef";char str2[] = "abcdef";const char* arr1 = "abcdef";const char* arr2 = "abcdef";if (str1 == str2){printf("str1 and str2 are same\n");}else{printf("str1 and str2 are not same\n");}if (arr1 == arr2){printf("arr1 and arr2 are same\n");}else{printf("arr1 and arr2 are not same\n");}return 0;
}
运行结果:

为什么?当const char*是存储字符串常量的时候,两个指针均指向字符串常量首元素地址
11.指针数组【数组】
整型数组:数据是整型的数组
浮点数组:数据是浮点型的数组
指针数组:数据是指针的数组【即数组中的元素是指针类型】
图解三种数组类型:

#include <stdio.h>int main()
{int arr1[5] = { 1,2,3,4,5 };int arr2[5] = { 2,3,4,5,6 };int arr3[5] = { 3,4,5,6,7 };int* arr[3] = { arr1,arr2,arr3 };//存储了三个数组的首元素地址for (int i = 0; i < 3; i++){for (int j = 0; j < 5; j++){printf("%d ", *(arr[i] + j));//arr[i][j]}printf("\n");}return 0;
}
运行结果:

12.数组指针【指针】
——指向数组的类型 (*p)[大小] = &所指数组名
int num = 10; int* p = #//指向int的指针
double data = 13.14; double* p = &data;//指向double的指针
int arr[10]; int (*p)[10] = &arr;//指向数组的指针
——如何判断数组指针?
()的结合性比[]高,所以先与*()里面的*号结合,再与[]结合 -> 指针
#include <stdio.h>void printArr(int(*p)[5], int row, int col)
{for (int i = 0; i < row; i++){for (int j = 0; j < col; j++){//printf("%d ", p[i][j]);//printf("%d ", *(*(p + i) + j));printf("%d ", *(p[i] + j));}printf("\n");}
}int main()
{int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };int row = sizeof(arr) / sizeof(arr[0]);int col = sizeof(arr[0]) / sizeof(arr[0][0]);printArr(arr, row, col);return 0;
}
13.数组传参和指针传参
1)一维数组传参
#include <stdio.h>//一维数组传参
//传参方式1
void test(int arr[]){}
//传参方式2
void test1(int arr[10]){}
//传参方式3
void test2(int* arr){}//一维指针数组传参
//传参方式1
void demo1(int* p[10]){}
//传参方式2
void demo2(int** arr){}
//解释:*arr接收str数组,另一个*表示元素是指针int main()
{int arr[5] = { 0 };test(arr);test1(arr);test2(arr);//一维指针数组传参int* str[10] = { 0 };demo1(str);demo2(str);return 0;
}
2)二维数组的传参方式
#include <stdio.h>//传参方式1
void test(int arr[3][3]){}
//传参方式2
void test1(int arr[][3]){}
//传参方式3
void test2(int(*arr)[3]){}int main()
{int arr[3][3] = { 0 };test(arr);test1(arr);test2(arr);return 0;
}
3)一级指针传参
#include <stdio.h>
//传参方式1
void test(int* p){}
//传参方式2
void test1(int* *p){}int main()
{int* p = NULL;test(p);return 0;
}
4)二级指针传参
#include <stdio.h>void test(int** p) {}int main()
{int** p = NULL;test(p);return 0;
}
14.函数指针
——指向的函数返回值 (*p)(指向函数的形参) = &所指函数名
int* p; //指向int的指针
double* p; //指向double的指针
int (*p)(int,int);//指向函数的指针
//解答:*和p结合,说明是指针,然后和(int,int)结合,说明是函数,int是返回值
#include <stdio.h>int add(int x, int y)
{return x + y;
}int main()
{//函数是有地址的//printf("%p\n", &add);int (*p)(int, int) = &add;int ret = p(5, 3);printf("%d\n", ret);//8return 0;
}
思考以下代码
//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);
解释
代码1:把0强制转为为void(*)()的函数指针,在0地址处有一个函数,
函数无返回值,无形参,这个地方表调用代码2:函数名是signal,参数为int和函数指针void(*)(int),
signal的返回类型是函数指针,该函数指针的函数返回值是void,参数是int
15.函数指针数组
——把函数地址存储在数组里面,这个数组就叫做函数指针数组
int (*p[])(形参);
——使用途径:转移表
#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;
}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[])(int, int) = { NULL, Add, Sub, Mul, Div };//0 1 2 3 4do{menu();printf("请选择:>");scanf("%d", &input);if (input == 0){printf("退出计算器\n");break;}else if (input >= 1 && input <= 4){printf("请输入两个操作数:>");scanf("%d %d", &x, &y);ret = pfArr[input](x, y);printf("%d\n", ret);}else{printf("选择错误\n");}} while (input);return 0;
}
相关文章:
筑基九层 —— 指针详解
目录 前言: 指针详解 前言: 1.CSDN由于我的排版不怎么好看,我的有道云笔记比较美观,请移步有道云笔记 2.修炼必备 1)入门必备:VS2019社区版,下载地址:Visual Studio 较旧的下载 -…...
内存清理、动画制作、CPU检测等五款实用软件推荐
人类与99%的动物之间最大差别在于是否会运用工具,借助好的工具,能提升几倍的工作效率。 1.内存清理软件——MemReduct MemReduct是一款内存清理软件,现在越来越多的软件由于硬件的普遍发展,对内存的使用都开始肆无忌惮起来&…...
RocketMQ 5.0 学习笔记
1. 需求 背景:业务需要,平台将使用rocketMQ来实现消息的发送与消费,替代redis的消息功能。 需要在搭建好rocketMQ平台后,进行研究和验证。 技术:Springboot RocketMQ5.0 使用场景:签到活动,…...
796.子矩阵的和
输入一个 n行 m列的整数矩阵,再输入 q个询问,每个询问包含四个整数 x1,y1,x2,y2,表示一个子矩阵的左上角坐标和右下角坐标。 对于每个询问输出子矩阵中所有数的和。 输入格式 第一行包含三个整数 n,m,q。 接下来 n…...
【PySide6】信号(signal)和槽函数(slot),以及事件过滤器
说明 在PYQT中,父控件可以通过两种方式响应子控件的事件: 通过信号(signal)和槽函数(slot)机制连接子控件和父控件父控件可以通过设置eventFilter()方法来监听响应子控件的事件 一、信号(signal)和槽函数(slot) 示例 在PYQT中,每个组件都…...
canal admin管理端配置(二)
下载安装 下载地址: 下载解压即可 配置 修改canal.admin-1.1.5\conf\application.yml server:port: 8089 #端口根据是否冲突修改 spring:jackson:date-format: yyyy-MM-dd HH:mm:sstime-zone: GMT8spring.datasource:address: 192.0.16.12:3306#数据库ip和端口…...
Servlet 生命周期
Servlet的生命周期有四个阶段:加载并实例化、初始化、请求处理、销毁。主要涉及到的方法有init、service、doGet、doPost、destory等 加载并实例化 Servlet容器负责加载和实例化Servelt。当Servlet容器启动时,或者在容器检测到需要这个Servlet来响应第一…...
redis集群模式登陆
总结redis单机模式时,登陆redis的命令格式: ./redis-cli -h 地址 -p 端口redis集群模式时,登陆redis的命令格式: ./redis-cli -h 地址 -p 端口 -c举例1:redis单机模式下登陆rootubuntu:/usr/local/redis/redis-7.0.0/b…...
04-useMemo 、React.memo、useCallback
useMemo 、React.memo、useCallback 一、useMemo 基本用法 缓存数据,模拟 Vue 中的计算属性。 同样useMemo跟vue中component一样,也是有缓存的,会将结果缓存下来 import React, { useMemo, useState } from react;export default functio…...
windows下安装emqx Unable to load emulator DLL@if ===/ SET data_dir=“
1.报错内容 I:\0-software\02-emqx\emqx-5.0.19-windows-amd64\bin>emqx start Unable to load emulator DLL (I:\0-software\02-emqx\emqx-5.0.19-windows-amd64\erts-12.3.2.9\bin\beam.smp.dll) 此时不应有 SET。 I:\0-software\02-emqx\emqx-5.0.19-windows-amd64\bin&…...
Redis常见问题(未完待续)
Redis常见问题Redis为什么快 ?Redis为什么快 ? 根据官方数据,Redis 的 QPS 可以达到约 100000(每秒请求数); 基于内存 对于磁盘数据库来说,首先要将数据通过 IO 操作读取到内存里再读取&#x…...
2024秋招BAT核心算法 | 详解图论
图论入门与最短路径算法 图的基本概念 由节点和边组成的集合 图的一些概念: ①有向边(有向图),无向边(无向图),权值 ②节点(度),对应无向图,…...
凝聚共识,锚定未来 | 第四届OpenI/O 启智开发者大会NLP大模型论坛成功举办!
2023年2月24日下午,第四届OpenI/O启智开发者大会NLP大模型分论坛在深圳人才研修院隆重举办。该论坛以“开源集智创新探索中文NLP大模型生态发展”为主题,众多业内人士和研发者在此共享NLP领域的前沿动态和研发经验,畅想中国NLP领域的发展前景…...
99.【Git】
Git(一)、什么是版本控制1.什么是版本控制2、常见的版本控制工具(二)、版本控制分类1、本地版本控制2、集中版本控制 SVN3、分布式版本控制 Git(三)、Git与SVN的主要区别1、Git历史(四)、Git下载与环境配置1.git下载2、启动Git(五)、常用的Linux命令1.Linux常用命令(六)、Git必…...
Linux驱动交叉编译把驱动文件放入开发板,以及printk函数打印级别
上一篇介绍了一个最简单的驱动程序和驱动程序大体结构,但那还是用本地编译只能在Ubuntu上运行,我们该怎么编译一个能加载到开发板上呢,就需要交叉编译,交叉编译通常都是在嵌入式开发中使用到的。 交叉编译 理解交叉编译前先了解…...
力扣(LeetCode)433. 最小基因变化(2023.03.07)
基因序列可以表示为一条由 8 个字符组成的字符串,其中每个字符都是 ‘A’、‘C’、‘G’ 和 ‘T’ 之一。 假设我们需要调查从基因序列 start 变为 end 所发生的基因变化。一次基因变化就意味着这个基因序列中的一个字符发生了变化。 例如,“AACCGGTT”…...
网络基础(2)
目录1. 端口号2. 套接字socket3. 网络通信3.1 sockaddr与sockaddr_in3.2 接口服务端3.2.1 创建套接字,打开网络文件3.2.2 给该服务器绑定端口和ip(特殊处理)3.2.3 初始化相关服务器3.2.4 提供服务客户端3.2.5 绑定3.2.6 使用服务4. makefile实…...
掌握Spring Cloud Gateway:构建高性能API网关的原理和实践
Spring Cloud Gateway 是一个基于 Spring Boot 的 API 网关,用于构建微服务架构中的网关服务。它提供了统一的路由、请求转发、过滤器、负载均衡、熔断等功能,帮助开发者更好地管理和控制微服务系统的请求流量。 本文将介绍 Spring Cloud Gateway 的原理…...
NAST概述
一、NATS介绍 NATS是由CloudFoundry的架构师Derek开发的一个开源的、轻量级、高性能的,支持发布、订阅机制的分布式消息队列系统。它的核心基于EventMachine开发,代码量不多,可以下载下来慢慢研究。 不同于Java社区的kafka,nats…...
【JS知识点】——原型和原型链
文章目录原型和原型链构造函数原型显式原型(prototype)隐式原型(\_\_proto\_\_)原型链总结原型和原型链 在js中,原型和原型链是一个非常重要的知识点,只有理解原型和原型链,才能深刻理解JS。在…...
地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
深入剖析AI大模型:大模型时代的 Prompt 工程全解析
今天聊的内容,我认为是AI开发里面非常重要的内容。它在AI开发里无处不在,当你对 AI 助手说 "用李白的风格写一首关于人工智能的诗",或者让翻译模型 "将这段合同翻译成商务日语" 时,输入的这句话就是 Prompt。…...
【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...
学校招生小程序源码介绍
基于ThinkPHPFastAdminUniApp开发的学校招生小程序源码,专为学校招生场景量身打造,功能实用且操作便捷。 从技术架构来看,ThinkPHP提供稳定可靠的后台服务,FastAdmin加速开发流程,UniApp则保障小程序在多端有良好的兼…...
浅谈不同二分算法的查找情况
二分算法原理比较简单,但是实际的算法模板却有很多,这一切都源于二分查找问题中的复杂情况和二分算法的边界处理,以下是博主对一些二分算法查找的情况分析。 需要说明的是,以下二分算法都是基于有序序列为升序有序的情况…...
Android 之 kotlin 语言学习笔记三(Kotlin-Java 互操作)
参考官方文档:https://developer.android.google.cn/kotlin/interop?hlzh-cn 一、Java(供 Kotlin 使用) 1、不得使用硬关键字 不要使用 Kotlin 的任何硬关键字作为方法的名称 或字段。允许使用 Kotlin 的软关键字、修饰符关键字和特殊标识…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
Unity | AmplifyShaderEditor插件基础(第七集:平面波动shader)
目录 一、👋🏻前言 二、😈sinx波动的基本原理 三、😈波动起来 1.sinx节点介绍 2.vertexPosition 3.集成Vector3 a.节点Append b.连起来 4.波动起来 a.波动的原理 b.时间节点 c.sinx的处理 四、🌊波动优化…...
SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...
Redis的发布订阅模式与专业的 MQ(如 Kafka, RabbitMQ)相比,优缺点是什么?适用于哪些场景?
Redis 的发布订阅(Pub/Sub)模式与专业的 MQ(Message Queue)如 Kafka、RabbitMQ 进行比较,核心的权衡点在于:简单与速度 vs. 可靠与功能。 下面我们详细展开对比。 Redis Pub/Sub 的核心特点 它是一个发后…...
