筑基九层 —— 指针详解
目录
前言:
指针详解
前言:
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。在…...
SpringBoot-17-MyBatis动态SQL标签之常用标签
文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...
web vue 项目 Docker化部署
Web 项目 Docker 化部署详细教程 目录 Web 项目 Docker 化部署概述Dockerfile 详解 构建阶段生产阶段 构建和运行 Docker 镜像 1. Web 项目 Docker 化部署概述 Docker 化部署的主要步骤分为以下几个阶段: 构建阶段(Build Stage):…...
C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
CMake基础:构建流程详解
目录 1.CMake构建过程的基本流程 2.CMake构建的具体步骤 2.1.创建构建目录 2.2.使用 CMake 生成构建文件 2.3.编译和构建 2.4.清理构建文件 2.5.重新配置和构建 3.跨平台构建示例 4.工具链与交叉编译 5.CMake构建后的项目结构解析 5.1.CMake构建后的目录结构 5.2.构…...
聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
AspectJ 在 Android 中的完整使用指南
一、环境配置(Gradle 7.0 适配) 1. 项目级 build.gradle // 注意:沪江插件已停更,推荐官方兼容方案 buildscript {dependencies {classpath org.aspectj:aspectjtools:1.9.9.1 // AspectJ 工具} } 2. 模块级 build.gradle plu…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?
现有的 Redis 分布式锁库(如 Redisson)相比于开发者自己基于 Redis 命令(如 SETNX, EXPIRE, DEL)手动实现分布式锁,提供了巨大的便利性和健壮性。主要体现在以下几个方面: 原子性保证 (Atomicity)ÿ…...
MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...
