不再恐惧指针,指针详解

什么是指针?

通俗来说指针就相当于地址,因为我们写入的代码每个变量的数据类型不同,字节大小不同,在计算机内存中所开辟存储的大小自然不同,且指针通常存储的是内存单元中最小单元的编号

比如:int*指针的大小是4个字节,在内存存储地址分别为0x11223344 0x11223355 0x11223366 0x11223377,那么int*指针存储的就是0x11223344内存地址(内存地址是我随便编的,因为在计算机中内存存储中地址是计算机随机分配的地址)

#include<stdio.h>
int main()
{int a = 10;//%p是用来打印地址的//&a取地址符号printf("%p\n", &a);return 0;
}

在内存中的分配

每个内存都有自己对应的唯一编号,编号==地址==指针,在写代码时创建的变量、数组、结构体等都要在内存上开辟空间。

为什么一个内存单元字节要设置为1个字节呢?

经过前人的不断钻研和计算,发现给内存单元一个字节大小是最优解。

在32位的计算机上有32根物理地址线,在32根地址线在寻找地址的时候产生高电平(1)和低电平(0)

就是二进制的1和0 那么它们能产生的地址如下:

也就是2^32次方 2^32=4294967296字节 能够管理4294967296内存字节空间

差不多4GB多一点内存空间

这就是为什么64位机器比32位机器内存空间大 比32位机器寻址能力强

在进行复杂的数据处理时,性能强 速度快等优点。

#include<stdio.h>
int main()
{//在内存中开辟4个字节大小int a = 10;//int*的指针变量 通过&取地址将a中4个内存单元中最小字节存储到了指针变量p中int* p = &a;printf("%p\n", &a);printf("%p\n", p);return 0;
}

p就是一个指针变量,用来存放地址的变量。

指针大小

通过32位机器中32根地址线和64位机器中64根地址线,就能发现指针大小不同。

一般在32位机器中不管指针变量存储的是什么数据类型的地址,大小都为4个字节

一般在64位机器中不管指针变量存储的是什么数据类型的地址,大小都为8个字节

VS2019 32位环境编译

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

VS2019 64位环境编译 

指针和指针类型

在创建变量中,系统提供的内置类型有很多,比如int、short、double...等,那么指针也有类型吗?

指针也有对应的指针类型,也是整形,浮点型等,因为指针存储的就是变量的地址。

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

 

我们可以看见不同类型指针变量,能够解引用*操作的空间与指针类型有关。

int*的指针解引用4个字节  char*的指针解引用1个字节

总结:指针类型决定了指针解引用的操作权限。

(也就是指针解引用访问多少个字节空间取决于指针类型)

Type*p

*p表明是一个指针变量 p指向的对象类型是Type  p解引用的时候访问对象大小是sizeof(Type)。

int*pa=&a 的意思是 *p表明是一个指针变量  p指向的对象类型是int   

p解引用访问的对象大小字节是int(4个字节)

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

C4到C8是4个字节大小 C4到C5是一个字节大小 

总结:指针的类型决定了指针向前或者向后走一步有多大(距离) 。

什么是野指针

野指针:野指针就是指针指向的位置是未知的(随机的,不正确的,没有明确限制的)。

比如野狗就是野指针,没有主人,它就没有温暖的家,只能四处流浪。

*p就相当于野狗具有危险性 

*p1=NULL 相当于野狗拴上了狗链子,危险性大大降低。 

野指针是怎么形成的?

1.指针没有初始化

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

在*p没有初始化,局部变量没有初始化,希望默认为随机值,一般不知道指针变量要指向哪个变量,就置为空即可(NULL)。

在C语言中NULL就是0

2.指针越界访问

#include<stdio.h>
int main()
{int arr[5] = { 0 };int* p = arr;int i = 0;for (i = 0; i <= 5; i++){*(p++) = i;}return 0;
}

  

当指针指向的范围超出了数组arr范围时,就会造成野指针,因为数组arr总共就5个数,数组下标从0到4,而for循环的判断条件却是小于等于5,超出数组原本的范围,形成了数组越界访问,造成野指针的形成。在VS2019编译器会帮忙检查越界,不同编译器不同,如果你在工作时编译器不会帮你检查越界问题,而代码运行不起来时,你就要开始自己疯狂挠头了!所以要避免野指针的形成。

3.指针指向的空间释放

在动态内存管理malloc realloc  calloc free,忘记free开辟的动态内存空间,一般情况下不用释放也行,虽然不释放形成了野指针,但只要没有人用就没有危险,但一般动态内存开辟使用完,记得free也是一个好习惯。

避免野指针的形成

1.指针初始化

2.小心指针越界

3.指针指向空间free释放置为空

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

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int main()
{//指向初始化int* p = NULL;p = (int*)malloc(sizeof(20));if (p == NULL)//使用之前检查有效性(是否为空){perror("malloc fail");exit(-1);}//p!=NULL*p = 20;printf("%d\n", *p);free(p);p = NULL;return 0;
}

指针和数组

指针和数组虽然是两个不同的东西,但它们俩之间却有着千丝万缕的联系。

#include<stdio.h>
int main()
{int arr[10] = { 0 };int* p = &arr[0];int i = 0;for (i = 0; i < 10; i++){*p = i;p++;}for (i = 0; i < 10; i++){printf("%d ", arr[i]);}return 0;
}

首先我们要注意:数组名原则上会被解释为指向该数组起始元素的指针

但有两种情况下,数组名不会为视为指向起始元素的指针。

1.作为sizeof运算符的操作数出现时

sizeof(数组名)不会生成指向起始元素的指针的长度,而是生成数组整体的长度。

2.作为取地址运算符&的操作数出现时

&数组名不是指向起始元素的指针的指针,而是指向数组整体的指针。

在数组中,数组名arr和&arr[0]的地址是相同,既arr的值就是arr[0]的地址,既&arr[0]。

不管arr的元素有多少个,只要元素类型有Type型,arr的表达式类型就是Type*。

而在两个例外中可以发现,sizeof数组名是整个数组的长度,不再是指针。

而取地址数组名,是指向的不是数组首个元素,而是指向整个数组整体的指针。

&arr+1 可以发现B0到D8,正好是数组10个元素的字节长度40。

将数组名视为指针,也催生出了数组和指针的密切关系。

指针的初始值是&arr[0],写成arr也是一样的,指针p被初始化为指向数组arr的起始元素arr[0];

*p既然指数组首个元素的值,通过指针本身p++遍历数组,将自己的值赋值给数组,打印出了0~9。 

指针运算符和下标运算符

#include<stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;int i = 0;for (i = 0; i < 10; i++){printf("a[%d]=%p p+%d=%p\n",i,&arr[i],i,p+i);}printf("\n");return 0;
}

 如果将指向数组内元素p+i前写上解引用操作符*会怎么样呢?

#include<stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;int i = 0;for (i = 0; i < 10; i++){printf("a[%d]=%d p+%d=%d\n",i,*(arr+i),i,*(p+i));}printf("\n");return 0;
}

我们看见arr[i]==*(arr+i)  p+i==*(p+i)

由此我们可以推断 arr[0]==*(arr+0)  arr[1]==*(arr+1) ....

通过交换律我们发现*(p+i)==*(i+p)  这样一看是不是p[i]可以写成i[p]呢?

答案是可以的!

因为[]下标运算符,是一个具有两个操作数的双目运算操作符,下标运算符[]的操作数顺序是随意的

就像1+2==2+1一样,arr[1]和1[arr]也是等价的。

所以我们可以写成arr[i]  i[arr]  *(i+a)  *(a+i)   p[i]  i[p] ....

但为了让别人看起来简洁高效,最好不要使用i[arr]等容易出现错误的写法。

指针运算  

指针+-整数

#include<stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;int i = 0;for (i = 0; i < 10; i++){printf("a[%d]=%d ",i,*p++);}printf("\n");return 0;
}

指针-指针

#include<stdio.h>
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };printf("%d\n", &arr[9] - &arr[0]);return 0;
}

指针减去指针得到的数值是:指针和指针之间的元素。

指针-指针的前提是:指针和指针指向了同一块空间 。

指针之间可以相减但不能相加

二级指针

指针变量也是变量,是变量就要开辟内存空间,就会有所对应的地址。

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

int* *pp=&p *pp表示是一个指针变量 再通过解引用保存p的地址

*(*pp)  *pp就是访问的就是p 再通过解引用操作 访问到*p的整个空间区域 就是a

因此最后结果是100。

指针数组

指针数组是指针?还是数组?

是数组!是存放指针的数组。

整形数组和字符数组

整形数组:存放整形的数组

字符数组:存放字符的数组

 指针数组

#include<stdio.h>
int main()
{char arr1[] = "abc";char arr2[] = "bcd";char arr3[] = "adas";char* parr[] = { arr1,arr2,arr3 };int i = 0;for (i = 0; i < 3; i++){printf("%s\n", parr[i]);}return 0;
}

parr存储三个字符数组,再通过解引用保存各数组首元素值的地址,再用for循环打印出来,因为printf具有链接性,只要知道首个元素的地址就能将后面直至\0之间的内容都自动打印出来。

指针数组就是存放指针(地址)的数组。

其实通过上面的例子,我们可以变相的用指针数组模拟实现二维数组。 

int* parr[3]是什么意思?  parr[3]是一个数组,里面有3个元素,每个元素是一个整形指针。

#include<stdio.h>
int main()
{int arr1[] = { 1,2,3 };int arr2[] = { 4,5,6 };int arr3[] = { 7,8,9 };int* parr[3] = { arr1,arr2,arr3 };int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 3; j++){printf("%d ", parr[i][j]);}printf("\n");}return 0;
}

数组指针

数组指针是数组?还是指针?

是指针!

int*p是指向整形数据的指针

float*p1是指向浮点型数据的指针

那么数组指针:就是指向数组的指针

以下两种,哪个才是数组指针?

 int(*p1)[10]才是数组指针,因为下标操作符[]的优先级高于解引用*的符号的优先级,如果不加括号p就先会和[]先结合,变成了指针数组。为了避免这种情况,为了解引用先生效,要先加括号。

因此int(*p1)[10]才是一个指向数组的指针,指向的数组大小为10,p1是一个指针变量,指向一个数组。

数组指针既然指向的数组,那数组指针存放的应该是数组的地址。

但一般我们写代码时,很少这样使用数组指针。

一般是在二维数组传参时,才使用数组指针。

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{int i = 0;for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){printf("%d ", arr[i][j]);}printf("\n");}
}
void print_arr2(int(*arr)[5], int row, int col)
{int i = 0;for (i = 0; i < row; i++){int j = 0;for (j = 0; j < col; j++){printf("%d ", arr[i][j]);}printf("\n");}
}
int main()
{int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };print_arr1(arr, 3, 5);print_arr2(arr, 3, 5);return 0;
}

二维数组其实本质就是一维数组。

二维数组的每一行可以理解为二维数组的一个元素,而二维数组的每一行又是一个一维数组。

二维数组名,也是就是首元素的地址,也就是第一行的地址-一维数组的地址arr。

arr[0][0]是首元素地址 也就是arr 第一行的地址。

数组传参和指针传参

一维数组传参

#include <stdio.h>
void test1(int arr[])test1传的数组首元素地址,而int arr[]本质上是指针 用来存放首元素地址 ok
{}
void test1(int arr[10])同理
{}
void test1(int* arr)以指针变量来存放首元素地址 ok
{}
void test2(int* arr[20])指针数组用来接收实参arr2 指针数组中首元素地址 ok
{}void test2(int** arr) 以二级指针来存储指针数组的地址 ok
{}
int main()
{int arr[10] = { 0 };int* arr2[20] = { 0 };test1(arr);test2(arr2);
}

二维数组传参

void test(int arr[3][5])数组int arr[3][5]本质上是数组指针 用来存储首元素地址 ok
{}
void test(int arr[][])错误 二维数组可以省略行 列不能省略
{}
void test(int arr[][5])同理 ok
{}void test(int *arr)错误 二维数组只能用数组指针接受
{}
void test(int* arr[5])错误 这是一个指针数组
{}
void test(int (*arr)[5])正确的 数组指针 用来存放数组的指针 可以通过(*arr)遍历来对应行的元素
{}
void test(int **arr)错误的 二维数组只能用数组指针接受
{}
int main()
{int arr[3][5] = {0};test(arr);
}

一级指针传参

#include <stdio.h>
void print(int* p, int sz)
{int i = 0;for (i = 0; i < sz; i++){printf("%d\n", *(p + i));}
}
int main()
{int arr[10] = { 1,2,3,4,5,6,7,8,9 };int* p = arr;int sz = sizeof(arr) / sizeof(arr[0]);print(p, sz);return 0;
}

p实参其实就是arr 用指针变量当形参接受(没有任何问题)。

函数参数为一级指针时,函数可以接受什么参数呢?

#include <stdio.h>
void test(char* p)
{}
int main()
{char arr[] = "abcd";char ch = '2';char* ptr = &ch;test(arr);test(&ch);test(ptr);return 0;
}

二级指针传参

#include <stdio.h>
void test(int** ptr)
{printf("num = %d\n", **ptr); 
}
int main()
{int n = 10;int*p = &n; //一级指针int **pp = &p;//二级指针test(pp);test(&p);return 0;
}

 当函数的参数为二级指针时,函数可以接受什么参数呢?

#include <stdio.h>
void test(char** p)
{}
int main()
{char* arr[5];char ch = 'a';char* p = &ch;char* pp = &p;test(arr);test(&p);test(pp);return 0;
}

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

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

相关文章

【Scala】——面向对象

1 Scala 包 1.1 包风格 Scala 有两种包的管理风格。 第一种 Java 的包管理风格相同&#xff0c;每个源文件一个包&#xff08;包 名和源文件所在路径不要求必须一致&#xff09;&#xff0c;包名用“.”进行分隔以表示包的层级关系&#xff0c;如 com.atguigu.scala。另一种风…

遥感单通道图像保存为彩色图像

系列文章目录 第一章PIL单通道图像处理 文章目录 系列文章目录前言一、代码实现二、问题记录在这里插入图片描述 总结 前言 将单通道图像以彩色图像的形式进行保存主要使用了PIL库 一、代码实现 palette_data [***]&#xff1a;可以进行自定义设置 代码如下&#xff1a; fr…

UVa12304 2D Geometry 110 in 1!

题目链接 UVa12304 2D Geometry 110 in 1! 题意 这是一个拥有6&#xff08;二进制是110&#xff09;个子问题的2D几何问题集。 1 CircumscribedCircle x1 y1 x2 y2 x3 y3&#xff1a;求三角形(x1,y1)-(x2,y2)-(x3,y3)的外接圆。这3点保证不共线。答案应格式化成(x,y,r…

服务器 配置git

参考了下面这篇文章&#xff0c;不对的地方做了改正 在服务器上git clone github项目的过程-CSDN博客 1. 下载解压 wget https://www.kernel.org/pub/software/scm/git/git-2.34.1.tar.gz tar -zxvf git-2.34.1.tar.gz 2. 安装 cd git-2.34.1/ ./configure make confi…

Geotools-PG空间库(Crud,属性查询,空间查询)

建立连接 经过测试&#xff0c;这套连接逻辑除了支持纯PG以外&#xff0c;也支持人大金仓&#xff0c;凡是套壳PG的都可以尝试一下。我这里的测试环境是Geosence创建的pg SDE&#xff0c;数据库选用的是人大金仓。 /*** 获取数据库连接资源** param connectConfig* return* {…

springboot私人健身与教练预约管理系统源码和论文

随着信息技术和网络技术的飞速发展&#xff0c;人类已进入全新信息化时代&#xff0c;传统管理技术已无法高效&#xff0c;便捷地管理信息。为了迎合时代需求&#xff0c;优化管理效率&#xff0c;各种各样的管理系统应运而生&#xff0c;各行各业相继进入信息管理时代&#xf…

【rk3568】01-环境搭建

文章目录 1.开发板介绍1.1相关资源&#xff1a;1.2接口布局1.3屏幕1.4核心板引脚可复用资源 2.环境搭建2.1安装依赖包2.2git配置2.3安装sdk2.4sdk介绍2.5sdk编译 3.镜像介绍 1.开发板介绍 开发板&#xff1a;atk-rk3568开发板 eMMC&#xff1a;64G LPDDR4&#xff1a;4G 显示屏…

螺旋数字矩阵 - 华为OD统一考试

OD统一考试(C卷) 分值: 100分 题解: Java / Python / C++ 题目描述 疫情期间,小明隔离在家,百无聊赖,在纸上写数字玩。他发明了一种写法: 给出数字个数n和行数m (0 < n <= 999,0 < m <= 999),从左上角的1开始,按照顺时针螺旋向内写方式,依次写出2,3……

创建ROS模型与小机器人地图规划

1、打开自己的VM系统 2、安装小机器人的安装包&#xff0c;输入如下命令&#xff0c;回车输入密码(自己设的)&#xff1a; sudo apt install ros-noetic-turtlebot3-simulations ros-noetic-turtlebot3-slam ros-noetic-turtlebot3-navigation 提示我之前安装过了 3、用rosla…

Java 常见缓存详解以及解决方案

一. 演示Mybatis 一级缓存 首先我们准备一个接口 两个实现的方法&#xff0c; 当我们调用这个queryAll&#xff08;&#xff09;方法时我们需要调用selectAll&#xff08;&#xff09;方法来查询数据 调用此接口实现效果 这个时候我们就可以发现了问题&#xff0c;我们调用方法…

18张AI电脑动漫超清壁纸免费分享

18张AI电脑动漫壁纸&#xff0c;紫色系和暗黑系&#xff0c;都很不错&#xff0c;喜欢的朋友可以拿去 CSDN免积分下载

【LV12 DAY12-13 GPIO C 语言与寄存器封装】

GPIO 通用型输入输出&#xff0c;GPIO可以控制连接在其引脚实现信号的输入和输出 芯片的引脚和外部设备相连从而实现与外部硬件的通讯&#xff0c;控制&#xff0c;信号采集的功能。 控制CHG_COK引脚输出为高电平&#xff0c;LED亮&#xff0c;输出为低电平&#xff0c;LED熄灭…

Android 10.0 TvSettings系统设置wifi连接密码框点击Enter键失去焦点

1.前言 在10.0的box产品开发中,在TvSettings中,在wifi连接的时候,在用遥控器输入wifi密码框的时候,会发现在按遥控器Enter键的时候, 发现EditText焦点失去了,导致输入法消失了,为了解决这个问题就需要拦截Enter键保证正常输入wifi密码,接下来就来实现这个功能 如图: 2.…

CSS 弹幕按钮动画

<template><view class="content"><button class="btn-23"><text class="text">弹幕按钮</text><text class="marquee">弹幕按钮</text></button></view></template><…

win7添加access的odbc数据源

从控制面板打开odbc数据源&#xff1b;如果像下面没有access的驱动程序&#xff0c; 根据资料&#xff0c;打开C盘-Windows-SysWow64-odbcad32.exe&#xff0c;看一下就有了&#xff1b; 然后添加用户DSN&#xff0c;选中access的驱动程序&#xff0c; 自己输入一个数据源名&am…

【浅尝C++】引用

&#x1f388;归属专栏&#xff1a;浅尝C &#x1f697;个人主页&#xff1a;Jammingpro &#x1f41f;记录一句&#xff1a;大半夜写博客的感觉就是不一样&#xff01;&#xff01; 文章前言&#xff1a;本篇文章简要介绍C中的引用&#xff0c;每个介绍的技术点&#xff0c;在…

Gitlab-ci:从零开始的前端自动化部署

一.概念介绍 1.1 gitlab-ci && 自动化部署工具的运行机制 以gitlab-ci为例&#xff1a; (1) 通过在项目根目录下配置.gitlab-ci.yml文件&#xff0c;可以控制ci流程的不同阶段&#xff0c;例如install/检查/编译/部署服务器。gitlab平台会扫描.gitlab-ci.yml文件&…

QML实现的图片浏览器

很久之前实现了一个QWidget版本的图片浏览器:基于Qt5的图片浏览器QHImageViewer 今天用QML也实现一个,功能差不多: ●悬浮工具栏 ●支持图片缩放、旋转、还原、旋转、拖动。 ●拖动图片时,释放鼠标图片会惯性滑动。 ●支持左右翻页查看文件夹中的图片。 ●支持保存图片至本…

【ONE·MySQL || 常见的基本函数】

总言 主要内容&#xff1a;介绍了MySQL中常用的基本函数。一些聚合函数、时间日期函数、字符串函数、数字函数等。       文章目录 总言1、聚合函数1.1、汇总1.2、COUNT()函数1.2.1、基本说明1.2.2、使用演示 1.3、SUM( )函数1.3.1、基本说明1.3.2、使用演示 1.4、AVG( )函…

java基础之Java8新特性-Optional

目录 1.简介 2.Optional类常用方法 3.示例代码 4.示例代码仓库地址 1.简介 Java 8引入了一个重要的新特性&#xff0c;即Optional类。Optional类是为了解决空指针异常而设计的。 在Java中&#xff0c;当我们尝试访问一个空对象的属性或调用其方法时&#xff0c;很容易抛出…