C语言--不可不学的指针

news/2024/12/23 6:26:09/

1. 指针是什么

内存是什么?

内存是电脑上特别重要的存储器,计算机中的程序的运行都是在内存中进行的,为了有效使用内存,我们会把内存划分为一个个小的内存单元,比如把4G/8G/16G/32G的内存划分为一个个以字节为单位的空间,一个内存单元的大小是一个字节,每个字节(内存单元)都会有一个编号,这个编号就是地址,即编号==地址==指针

指针就是地址,在我们日常的口语中,指针即为指针变量。

指针变量:存放在指针中的任何值都被当做指针变量

指针变量是用来存放地址的,地址是唯一一个标识内存单元的。

int main()
{int a = 100;int * pa = &a;*pa = 0;return 0;
}

&a:

&取地址符号,取出a的地址(a是int类型,占4个字节)

int * pa:

pa,指针变量,是专门用来存放地址(指针)的

*,表示pa是指针变量

int,pa指向的a的类型是int

int*,pa这个指针变量的类型

a 是int类型,占用四个字节的空间,将a四个字节的第一个字节的地址存放在pa变量中

指针的大小:在32位平台是4个字节,在64位平台是8个字节。

2. 指针和指针类型

指针定义方式:type+*

type* p;

type是p指向的对象的类型

*说明p是指针变量

p解引用访问的大小是sizeof(type)

char*
short*
int*
long*
float*
double*

无论是什么类型的指针,大小都一样

//无论什么类型的指针,大小都是一样的
int main()
{printf("%d\n", sizeof(char*));//1printf("%d\n", sizeof(short *));//2printf("%d\n", sizeof(int *));//4printf("%d\n", sizeof(long*));//printf("%d\n", sizeof(float *));//4printf("%d\n", sizeof(double*));//8return 0;
}

我们可以通过sizeof观察一下

指针类型的意义:

char*类型的指针是为了存放char变量的地址

short*类型的指针是为了存放short变量的地址

int*类型的指针是为了存放int变量的地址

......

指针类型可以决定指针解引用的时候访问多少字节(指针的权限)。

我们以如下代码为例,一个是int*一个是char*:

int main()
{int a = 0x11223344;int * pa = &a;*pa = 0;return 0;
}
int main()
{int a = 0x11223344;char* pa = &a;*pa = 0;return 0;
}

我们可以观察到,当pa类型为int*时,访问了四个字节

我们同样也可以观察到,当pa类型为char*时,访问了一个字节

指针类型也会决定指针+1操作(指针运算)的步长。

我们借助如下代码来研究:

//决定了+1操作的步长
//+/-n 决定了加减n*sizeof(type)个字节
int main()
{int a = 10;int* pa = &a;char* pc = &a;printf("%p\n", pa);printf("%p\n", pc);printf("%p\n", pa+1);printf("%p\n", pc+1);return 0;
}

观察结果我们可以发现,前两个打印出来的结果是一样的,后两个一个是加4以后的结果,一个是加1以后的结果。由此,指针类型也会决定指针+1操作(指针运算)的步长。

3. 野指针

定义:野指针指向的定义和位置是不可知的(随机的,不正确的,没有明确限制的),很危险

成因:

1.指针未初始化

2.指针越界访问(当指针指向范围超出数组范围,p就是野指针。比如在循环中或者函数的返回值)

例子如下:

int main()
{int* p;*p = 20;printf("%d\n", *p);return 0;
}

在此段代码中,局部变量p没有进行初始化时,内容是随机的

这里面p就是一个野指针

如何规避野指针?

1.指针初始化(明确知道指针应当初始化的地址,不知道就暂时置NULL(意思是0))

2.小心指针越界

3.指针指向的空间释放时,及时置NULL

4.避免返回局部变量的地址

如下段代码是一个野指针的案例,程序仍可正常运行,但会出现警告:

int* test()
{int a = 110;return &a;
}int main()
{int* p = test();printf("%d\n", *p);return 0;
}

在test()函数中,return &a;在main()函数中,p就是一个野指针。a的空间进入函数test创建,出函数test()则销毁。

5.指针使用之前检查指针的有效性

int main()
{int* p;  //野指针int* ptr = NULL;//置NULLreturn 0;
}

如上所示,p时野指针,可以类比为一条会乱咬人的野狗,当置NULL以后,相当于给野狗身上拴上绳子

ptr是一个空指针,不能直接使用,没有指向任何有效空间

4. 指针运算

4.1指针+/-整数

不使用下标进行对数组的访问

//指针+/-整数
int main()
{int arr[10] = { 0 };int* p = &arr[0];int i = 0;int sz = sizeof(arr) / sizeof(arr[0]);for (i = 1; i <= sz; i++){*p = i;p++;}p = arr;for (i = 0; i < sz; i++){printf("%d ", *(p + i));}return 0;
}

在这段代码中,第一个for循环进行对于数组arr的初始化,p刚开始指向数组的首元素地址,*p对p进行解引用,修改p的值为i,则数组中第一个元素为1,然后依次进行循环,最终数组结果为{1,2,3,4,5,6,7,8,9,10}。在第一个for循环中,对于p进行修改,p++以后,p的值进行更改,增加四个字节的地址(p是int*类型),*p进而指向数组中的第2个元素。

第二个for循环中,是对赋值后的数组进行打印。p = arr;中,数组名代表数组首元素的地址,在此过程中,*(p + i)并未像第一个for循环对于p值进行修改,而是进行了类似于p+1,p+2...的操作进行对数组一个个的访问。

代码运行结果如下:

在这里,我们需要注意的一点是,[ ]仅仅只是一个操作符,i和arr是它的操作数

即arr[i]===*(arr+i)==*(i+arr)==i[arr]

 4.2指针-指针(地址-地址)

指针-指针得到的数值的绝对值是它们之间的元素个数

前提条件: 指针1和指针2指向了同一块空间,否则是无意义的

//指针和指针相减的前提条件是二者指向了同一块空间
int main()
{int arr[10] = { 0 };char ch[5] = { 0 };printf("%d\n", &arr[4] - &arr[0]);printf("%d\n", &arr[0] - &arr[4]);printf("%d\n", &ch[0] - &arr[4]);return 0;
}

运行结果:

4.3指针的关系运算

指针是有大小的,指针的关系运算就是比较指针的大小

//倒着初始化数组内容
#define N_VALUES 5
int main()
{float values[N_VALUES];//浮点数数组valuesfloat* vp;for (vp = &values[N_VALUES]; vp > &values[0];){*--vp = 0;}return 0;
}

标准规定:允许指向数组元素的指针与指向数组的最后一个元素后面的那个内存位置的指针进行比较,但是不允许与第一个元素之间的那个内存位置的指针进行比较。

即为P可以和P2进行比较但不能和P1进行比较

5. 指针和数组

指针变量就是指针变量,不是数组

指针变量的大小是4/8个字节,专门用来存放地址

数组就是数组,不是指针,数组是一块连续的空间,可存放1个或者多个类型相同的数据。

联系:

数组中,数组名其实是数组首元素的地址,数组名==地址==指针

数组是通过指针来访问的

当我们知道数组首元素的地址的时候,因为数组是连续存放的,所以通过指针可以遍历数组。

6. 二级指针

二级指针变量用来存放一级指针变量地址

指针变量也是指针,是变量就有地址,指针变量的地址存在二级指针中~

int**pp;

int*:pp指向的类型

*(第二个):说明pp是指针变量

int main()
{int a = 10;int* pa = &a;int** p = &pa;printf("%p\n", pa);printf("%p\n", p);return 0;
}

理解方式如下:

7. 指针数组

存放指针(地址)的数组,数组的每个元素都是指针

int main()
{char arr1[] = "abcdef";char arr2[] = "hello world";char arr3[] = "cuihua";//指针数组char* parr[] = { arr1,arr2,arr3 };int i = 0;for (i = 0; i < 3; i++){printf("%s\n", parr[i]);}printf("%p\n", parr[1]);return 0;
}

在上段代码中,parr就是指针数组,一个数组里面存放了3个指针。

数组名是数组首元素地址。

运行结果:


http://www.ppmy.cn/news/1514696.html

相关文章

鸿蒙(API 12 Beta3版)【DRM Kit 简介】数字版权保护

开发者通过调用DRM Kit&#xff08;Digital Rights Management Kit&#xff0c;数字版权保护服务&#xff09;提供的接口可以开发播放器应用&#xff0c;实现数字版权保护的基础操作&#xff0c;如设备证书管理、许可证管理、解密操作等&#xff1b;还可以通过接口参数配置完成…

Python办公自动化smtplib实现自动发送邮件

学好python自动化&#xff0c;走遍天下都不怕&#xff01;&#xff01; 今天主要学习如何利用python自动化分析处理数据并以附件形式发送邮箱。需要安装配置python的运行环境&#xff0c;以及电脑支持Excel文件&#xff0c;有可以正常使用的邮箱。还需要用到python的第三方模块…

如何远程连接到AWS EC2实例?

随着越来越多的企业选择云服务来支撑其业务发展&#xff0c;了解如何高效管理云环境中的资源变得尤为重要。本文九河云将指导您完成从本地计算机远程连接到AWS EC2&#xff08;Elastic Compute Cloud&#xff09;实例的过程&#xff0c;帮助您轻松进行系统管理、应用程序部署等…

【SpringBoot】优化慢启动应用的用户体验

通过深入分析SpringBoot中WebServer的启动流程&#xff0c;插入自定义的Loading页面展示逻辑&#xff0c;优化软件使用时的用户体验。 背景 Java本身的特点&#xff0c;再加上开发人员能力差&#xff0c;软件开发工程化程度低等等问题&#xff0c;经过一段时间的迭代之后&…

【ACM稳定出版,高录用稳检索】第八届电子信息技术与计算机工程国际学术会议(EITCE 2024,10月18-20)

第八届电子信息技术与计算机工程国际学术会议&#xff08;EITCE 2024&#xff09;将于2024年10月18日至20日在中国海口举办。 本次会议旨在汇集全球电子信息技术与计算机工程领域的学者、科研专家及行业实践者&#xff0c;共同探讨该领域的最新研究成果、技术进展与学术动态。会…

鸿蒙内核源码分析(共享内存) | 进程间最快通讯方式

运行机制 共享好端端的一词&#xff0c;近些年被玩坏了&#xff0c;共享单车,共享充电宝,共享办公室&#xff0c;共享雨伞… 甚至还有共享女朋友&#xff0c;真是人有多大胆&#xff0c;共享有多大产。但凡事太尽就容易恶心到人&#xff0c;自己也一度被 共享内存 恶心到了&am…

linux文本分析工具grep、sed和awk打印输出文本的单双奇偶行(grep也可以打印奇偶行)以及熟悉的ssh命令却有你不知道的一些用法

一、linux文本分析工具grep、sed和awk打印输出文本的单双奇偶行&#xff08;grep也可以打印奇偶行&#xff09; 其实sed和awk要打印输出奇偶行是很容易的事情&#xff0c;不过只能使用grep来输出奇偶行的话怎么实现呢&#xff1f;今天我就想了一下&#xff0c;如果真的只能使用…

Overleaf中插图需要的pdf图片格式制作方法(python)

Overleaf很多模板中&#xff0c;需要用pdf格式来插入图片&#xff0c;即将png/jpg图片转化成pdf&#xff0c;才能插入此图。这么一来&#xff0c;就需要额外做一步转化。 这一步用python即可完成&#xff0c;废话不多说&#xff0c;直接给代码&#xff1a; from PIL import Im…