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

server/2024/12/23 3:15:56/

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/server/104903.html

相关文章

ant design pro 的表分层级如何处理

ant design pro 如何去保存颜色ant design pro v6 如何做好角色管理ant design 的 tree 如何作为角色中的权限选择之一ant design 的 tree 如何作为角色中的权限选择之二ant design pro access.ts 是如何控制多角色的权限的ant design pro 中用户的表单如何控制多个角色ant des…

STM32(五):定时器——输出比较

定时器输出比较功能&#xff1a;输出PWM波形 OC&#xff08;Output Compare&#xff09;输出比较 输出比较可以通过比较CNT与CCR寄存器值的关系&#xff0c;来对输出电平进行置1、置0或翻转的操作&#xff0c;用于输出一定频率和占空比的PWM波形。 每个高级定时器和通用定时器…

玻璃钢一体化预制泵站的设计原则

诸城市鑫淼环保小编带大家了解一下玻璃钢一体化预制泵站的设计原则 1、总体布置应合理&#xff0c;特别是排灌结合或自排、自引与提水相结合的泵站以及闸站结合的&#xff0c;在布置上应力求紧凑&#xff0c;充分利用建筑物进行调节。 2、在泵型的选择上应力求使设计扬程与水泵…

Java开发笔记-mysql语句查询指定索引

今天同事遇到一个奇怪的sql查询的问题&#xff1a;一条sql有时候执行素的很快(0.xxs)&#xff0c;有时候执行很慢(20s)&#xff0c;不知道是什么问题. 猜测&#xff1a;1、是不是第一次执行&#xff0c;被mysql缓存了&#xff1f;后面几次直接拿缓存的结果。 2、是不是网络的原…

vue3 - 04 - watch的使用

watch 一、 watch 基础认识1. 监视 ref 定义的【基本类型】数据2. 监视 ref 定义的【对象类型】的数据3. 监视 reactive 定义的【对象类型】的数据4. 监视 ref 或 reactive 定义的【对象类型】数据中的【某个属性】5. 监视上诉的多个数据 二、watchEffect 一、 watch 基础认识 …

uniapp 网络请求自动处理loading

文章目录 背景整理思路V1版本V2版本V3版本 背景 最近在写uniapp&#xff0c;发现执行网络请求的时候经常要处理Loading效果。 比如&#xff0c;在发送网络请求之前&#xff0c;触发Loadng&#xff1b;无论请求成功还是失败都要关闭Loading&#xff1b;请求失败的时候我们还要…

职业院校云计算实训室建设方案全景剖析

在信息化社会的今天&#xff0c;云计算作为一项关键技术&#xff0c;正在迅速改变着教育和培训的方式。本文旨在探讨如何通过"职业院校云计算实训室建设方案"&#xff0c;为学生提供一个现代化、高效的学习和研究环境&#xff0c;以适应云计算技术的发展和市场需求。…

力扣221题详解:最大正方形的多种解法与模拟面试问答

在本篇文章中&#xff0c;我们将详细解读力扣第221题“最大正方形”。通过学习本篇文章&#xff0c;读者将掌握如何使用多种方法来解决这一问题&#xff0c;并了解相关的复杂度分析和模拟面试问答。每种方法都将配以详细的解释&#xff0c;以便于理解。 问题描述 力扣第221题…