C语言:指针的基础详解

目录

1. 内存

2. 取地址&

3. 指针变量

4. 解引用

4.1 *解引用

4.2 []解引用

4.3 ->解引用

5. 指针变量的大小

5.1 结论

6. 指针运算

7. void* 指针

8. const修饰指针

8.1 const修饰变量

8.2 const修饰指针变量

8.3 结论

9. 野指针

9.1 为什么会出现野指针?

9.1.1 指针未初始化

9.1.2 指针越界访问

9.1.3 指针指向的空间释放

9.2 如何规避野指针

10. assert断言

11. 指针的作用


1. 内存

我们都知道,手机,电脑里都是有内存的,电脑CPU在处理数据时,需要的数据从内存中读取,处理后再放回内存中

每个内存单元为1个字节

具体的换算单位如下:

1byte = 8bit
1KB = 1024byte
1MB = 1024KB
1GB = 1024MB
1TB = 1024GB
1PB = 1024TB

 CPU为了更好的处理数据,找到这个数据,就需要有地址,有了地址才能找到所需要的数据

每个字节都有属于自己的名字(地址),有了这个名字才能更好的找到它

那我们如何获得地址呢?

2. 取地址&

&符号当然可以取地址了

int a = 10;
printf("%p\n", &a);
//%p是打印地址的

 我们知道 int 有4个字节,这4个字节都有地址

如上图中,如果a是占这4个字节

那么打印出来的地址将会是较小的那个地址0x006FFD70

我们只要得到了较小的那个地址,剩下的三个地址就知道了

3. 指针变量

我们获得了地址之后,我们怎么存储起来方便我们后续使用呢?

所以就有了指针变量起作用的时候了

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

在int的后面加上了个 * 号,它就是指针类型了,而这个p就是指针变量,且是整型的指针变量

我们看待这个指针的时候要把 int 和 * 看成一对,int* 就和上面的 int类似,都是个类型,它两组成了一个整型指针类型,这个p就是个名字,和 a 是一样的

然后我们就可以把整型a的地址放进这个指针变量里了

4. 解引用

我们现在有了地址,当然可以到地址里面去使用地址内部的东西了

具体使用方法需要解引用

当我们解引用后,就可以拿到内部的东西了

4.1 *解引用

#include <stdio.h>int main()
{int a = 10;int* p = &a;printf("%d\n", *p);return 0;
}
输出:10

 对我们刚刚创建的指针变量名字前面加上个*即可解引用

4.2 []解引用

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

是不是很像数组?其实指针变量也可以算是一个数组,这与我们直接创建一个数组是一样的

#include <stdio.h>int main()
{int p[] = {1,2,3,4,5};printf("%d\n", p[0]);return 0;
}

数组名本质就是地址,地址加上解引用符号才能获得值,大同小异

4.3 ->解引用

可以把它叫做箭头解引用,要用到它得学到结构体指针才会用到它

#include <stdio.h>struct s
{int n;
};int main()
{struct s s1 = { 10 };struct s* s2 = &s1;printf("%d\n", s2->n);return 0;
}
输出:10

 把结构体指针解引用的符号就是使用箭头->

5. 指针变量的大小

指针变量也是有大小的,并且这个和运行环境有着部分关系

#include <stdio.h>int main()
{printf("%zd\n", sizeof(char*));printf("%zd\n", sizeof(short*));printf("%zd\n", sizeof(int*));printf("%zd\n", sizeof(double*));return 0;
}

 

在x86的环境下结果是4,当我们改成x64后

结果变成了8

直接说结论

5.1 结论

1.指针变量的大小和类型无关,只要是指针类型变量,在相同的平台下,大小都是相同的

2.32位平台下地址是32个bit位,指针大小是4个字节

3.64位平台下地址是64个bit位,指针大小是8个字节

6. 指针运算

指针也是可以进行加减运算的,但是不能进行乘除运算,因为乘除运算没有意义

如果是int类型的指针,那么它每+1就跳过4个字节,char类型指针+1就跳过一个字节,由指针类型决定

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

 

7. void* 指针

void*指针是一种特殊的指针,并且它不能进行指针运算和解引用运算,但是它可以接受任意类型的地址

例如:

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

这样想在char* 里存一个整型地址会报警告 

 

但是我们可以使用void*放

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

8. const修饰指针

我们知道const修饰的变量是不可改变的,但是如果硬要改还是能改的

8.1 const修饰变量

#include <stdio.h>int main()
{const int a = 10;a = 5;return 0;
}

 

但是如果我们使用指针变量来改变呢?

#include <stdio.h>int main()
{const int a = 10;int* p = &a;*p = 5;printf("%d\n", a);return 0;
}

我们会发现它还是改变了

这是因为我们const的不够彻底,我们应该要让p就算拿到了a的地址也不能改变

8.2 const修饰指针变量

const的放置位置是有几种情况的

const int* p = &a;
int const * p = &a;
int* const p = &a;

 第一行和第二行的const都放在了*p的前面,所以限制的是*p,作用是*p不能改变

#include <stdio.h>int main()
{int a = 10;const int* p = &a;//int const * p = &a;*p = 5;//不可改变printf("%d", *p);return 0;
}

 

第三行const仅仅只放在p的前面,那么它只能限制p,p的地址就不能改变

#include <stdio.h>int main()
{int a = 10;int b = 9;int* const p = &a;//int const * p = &a;p = &b; //不可改变printf("%d", *p);return 0;
}

 

8.3 结论

主要是看const的位置在哪里,如果const是在*p的前面,限制的是*p,*p不可改变,如果是在*的后面p的前面,限制的是p,那么p不可改变

9. 野指针

概念:指针指向一片未知的区域

9.1 为什么会出现野指针?

9.1.1 指针未初始化

#include <stdio.h>int main()
{int* p;*p = 20;return 0;
}

 *p并不知道指向了哪里,默认为随机值,会随机在一片地址把值改成20

9.1.2 指针越界访问

#include <stdio.h>int main()
{int a[5] = { 1,2,3,4,5 };int* p = a; //等同于int* p = &a[0];for (int i = 0; i < 6; i++){*(p++) = i;}return 0;
}

p一直加到了超越数组的位置,那么就是一片未知的区域,所以就变成了野指针 

9.1.3 指针指向的空间释放

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

这样*p虽然能获得值并且打印出来,函数每次调用都是建立栈空间,但是使用过后,函数建立的栈帧会消失, 那么地址就不存在了,p原本还指向这个函数,但函数消失后就变成了未知的区域,所以p就变成了野指针

9.2 如何规避野指针

1. 注意指针的初始化

2.小心指针越界访问

3.指针不使用时及时置空NULL

4.避免返回局部变量,如上述9.1.3

10. assert断言

#include <assert.h>
assert(p != NULL);

 assert函数的作用是如果括号内为真,这个函数不会有任何反应,但如果为假,那么会报错,并且告诉你在第几行代码出错

assert()的使用对程序员是非常友好的,其一就是能精确报错,其二就是如果已经确定程序没有任何问题,不需要再做任何断言,我们是可以一键注释的

#define NDEBUG
#include <assert.h>

我们只需要在头文件前包括上这个宏定义即可         

11. 指针的作用

在我们使用一个函数的时候如果不使用指针变量的话,只能传值,不能改变本身

例如:

#include <stdio.h>void Swap1(int a, int b)
{int tmp = a;a = b;b = tmp;
}int main()
{int x = 5;int y = 6;Swap1(x, y);printf("%d %d\n", x, y);return 0;
}

 

这样是无法使两个值改变的,因为我们这里传的x和y只是它的值,并不是x和y的本身,所以交换a和b是不起交换x和y的作用的

但如果我们使用指针传它的名字(地址)过去,就可以找到x和y本身,然后将它们交换了

#include <stdio.h>void Swap(int* a, int* b)
{int tmp = *a;*a = *b;*b = tmp;
}int main()
{int x = 5;int y = 6;Swap(&x, &y);printf("%d %d\n", x, y);return 0;
}

 

在函数内部要注意解引用


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

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

相关文章

[嵌入式系统-15]:RT-Thread -1- 简介与技术架构

目录 一、RT-Thread简介 1.1 简介 1.2 功能特点 1.3 发展历史 1.4 应用场合 1.5 与Linux的比较 1.6 ​​​​​​​RT-Thread优缺点 二、技术架构 2.1 分层架构​编辑 2.2 功能组件 2.3 应用程序接口RT-Thread API 2.4 应用程序接口&#xff1a;RT-Thread API、POS…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Navigation组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Navigation组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Navigation组件 鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#…

Java图形化界面编程——AWT概论 笔记

2.3 Container容器 2.3.1 Container继承体系 Winow是可以独立存在的顶级窗口,默认使用BorderLayout管理其内部组件布局;Panel可以容纳其他组件&#xff0c;但不能独立存在&#xff0c;它必须内嵌其他容器中使用&#xff0c;默认使用FlowLayout管理其内部组件布局&#xff1b;S…

分布式文件系统 SpringBoot+FastDFS+Vue.js【二】

分布式文件系统 SpringBootFastDFSVue.js【二】 六、实现上传功能并展示数据6.1.创建数据库6.2.创建spring boot项目fastDFS-java6.3.引入依赖6.3.fastdfs-client配置文件6.4.跨域配置GlobalCrosConfig.java6.5.创建模型--实体类6.5.1.FastDfsFile.java6.5.2.FastDfsFileType.j…

leetcode:343.整数拆分

解题思路&#xff1a; 拆分的越多越好&#xff08;暂且认为&#xff09;&#xff0c;尽可能拆成m个近似相等的数&#xff0c;会使得乘积最大 dp含义&#xff1a;将i进行拆分得到最大的积为dp[i] 递推公式&#xff1a;j x dp[i-j](固定j&#xff0c;只通过凑dp[i-j]进而实现所…

Java学习第十四节之冒泡排序

冒泡排序 package array;import java.util.Arrays;//冒泡排序 //1.比较数组中&#xff0c;两个相邻的元素&#xff0c;如果第一个数比第二个数大&#xff0c;我们就交换他们的位置 //2.每一次比较&#xff0c;都会产生出一个最大&#xff0c;或者最小的数字 //3.下一轮则可以少…

寒假 6

1.现有无序序列数组为{23,24,12,5,33,5,34,7}&#xff0c;请使用以下排序实现编程。 函数1:请使用冒泡排序实现升序排序 函数2︰请使用简单选择排序实现升序排序 函数3:请使用直接插入排序实现升序排序 函数4∶请使用插入排序实现升序排序 #include <stdio.h> #inclu…

找负环(图论基础)

文章目录 负环spfa找负环方法一方法二实际效果 负环 环内路径上的权值和为负。 spfa找负环 两种基本的方法 统计每一个点的入队次数&#xff0c;如果一个点入队了n次&#xff0c;则说明存在负环统计当前每个点中的最短路中所包含的边数&#xff0c;如果当前某个点的最短路所…

Visual Studio Code连接远程MS Azure服务器的方法

1. 开启远程MS Azure服务器 Step 1.1. 登录MS Azure账号&#xff0c;https://azure.microsoft.com/en-us/get-started/azure-portal Step 1.2. 开启远程MS Azure服务器 2. 通过Visual Studio Code连接MS Azure远程服务器 Step 2.1. 安装Remote-SSH Extension Step 2.2. 选择…

使用REQUESTDISPATCHER对象调用错误页面

使用REQUESTDISPATCHER对象调用错误页面 问题陈述 InfoSuper公司已经创建了一个动态网站。发生错误时,浏览器中显示的堆栈跟踪很难理解。公司的系统分析师David Wong让公司的软件程序员Don Allen创建自定义错误页面。servlet引发异常时,应使用RequestDisapatcher对象向自定义…

【教程】Kotlin语言学习笔记(二)——数据类型(持续更新)

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 【Kotlin语言学习】系列文章 第一章 《认识Kotlin》 第二章 《数据类型》 文章目录 【Kotlin语言学习】系列文章一、基本数据…

【计算机网络】网络层之IP协议

文章目录 1.基本概念2.协议头格式3.网段划分4.特殊的IP地址5.IP地址的数量限制6.私有IP地址和公网IP地址7.路由 1.基本概念 IP地址是定位主机的&#xff0c;具有一个将数据报从A主机跨网络可靠的送到B主机的能力。 但是有能力就一定能做到吗&#xff0c;只能说有很大的概率。…