六.初阶指针

news2024/11/26 16:35:11

 前言:大家好哇!今天带大家认识下C语言中的指针,指针的用法等,希望对大家有所帮助!


目录

一.指针是什么

1.指针是什么?

2.如何理解指针变量

二.指针和指针的类型

1.指针类型

2.指针类型的意义

(1)指针类型决定了指针解引用时,能够访问的内存大小是多少

(2)指针加减整数

(3)指针修改数组元素

三.野指针

1.野指针的概念

2.野指针的成因

(1)指针未初始化

(2)指针越界访问

(3)指针指向的空间已经释放

 3.那该如何规避野指针问题?

四.指针运算

1.指针加整数

2.指针减整数

4.指针减指针

5.指针的关系运算

五.指针和数组

1.数组名

2.使用指针访问数组

六.二级指针

七.指针数组


一.指针是什么

1.指针是什么?

理解指针是什么首先要引入内存-------电脑上的一种存储设备,分4/8/16GB。

如上图所示:我们把整个内存划分为一个个小的内存单元,每个内存单元的大小是一个字节,然后将这些内存单元进行编号,未来我们想使用哪一个内存单元时只需要知道对应的编号就能找到,这个编号就叫做内存单元的地址,也就是C语言中的指针

那么这里还有一个问题,我们是如何给内存单元编号的呢?

其实对于32位机器,假设有32根地址线,那么假设每根地址线在寻址的时候产生高电平(1)和低电平(0),那么这32根地址线产生的地址就会是:

00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000001

.......

111111111 111111111 111111111 111111111

 总共有2^{32}个地址,每个地址对应一个内存空间,每个内存空间的大小为一个字节,那么我们就能对4G的空间进行编址寻址了。

2.如何理解指针变量

通过&(取地址操作符)取出变量的内存地址,把地址存放到一个变量中,这个变量就是指针变量。

所以指针变量是用来存放地址的变量,我们平时口语所说的指针指的就是指针变量。

上文提到,在32位机器上,地址是由32个0或1组成的二进制(bit)序列,那地址就得占4个字节,需要4个字节的空间来存储,所以一个指针变量的大小为4个字节

同理,64位机器上,一个指针变量的大小为8个字节

总结:

  1. 指针就是地址,地址就是指针;
  2. 指针变量就是变量,用来存放地址的变量(存放在之中的值都被当成地址处理);
  3. 一个小的内存单元大小为 1 个字节;
  4. 指针是用来存放地址的,地址是唯一标识一块内存空间的;
  5. 指针的大小在 32 位平台上是 4 个字节,在 64 位平台上是 8 个字节;

二.指针和指针的类型

1.指针类型

指针的本质是地址。

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

上面int 型指针pa和char型指针pc都可以存储a,他们的运行结果是一样的。

2.指针类型的意义

(1)指针类型决定了指针解引用时,能够访问的内存大小是多少

int  *p         //*p能够访问4个字节

char *p       //*p能够访问4个字节

double *p   //*p能够访问4个字节

.......

不同的指针类型,访问的大小不同:

int main()
{
    int a = 0x11223344;
    int* pa = &a; // 44 33 22 11 (至于为什么是倒着的,后面会讲。)
    *pa = 0;// 00 00 00 00
    
    char* pc = &a; // 44 33 22 11
    *pc = 0; // 00 33 22 11    // 在内存中仅仅改变了一个字节
 
    // 解引用操作时就不一样了:
    // 整型指针操作了4个字节,让四个字节变为0
    // 字符指针能把地址存到内存中,但是解引用操作时,只能动1个字节
 
    return 0;
}

(2)指针加减整数

公理:指针类型决定指针步长(指针走一步多远)

int *p;p+1              //跳过4个字节

char *p;p+1           //跳过1个字节

double *p;p+1       //跳过8个字节

int main()
{
    int a = 0x11223344;
    int* pa = &a;
    char* pc = &a;
 
    printf("%p\n", pa);   // 0095FB58
    printf("%p\n", pa+1); // 0095FB5C +4
 
    printf("%p\n", pc);   // 0095FB58
    printf("%p\n", pc+1); // 0095FB59 +1
    
    return 0;
}

(3)指针修改数组元素

int main()
{
    int arr[10] = {0};
    int brr[10] = {0};
    int* pa = arr; //数组名 - 首元素地址
    char* pc= brr;
 
    /* 修改 */
    int i = 0;
    for(i=0; i<10; i++) {
        *(pa+i) = 1; //成功,arr里的元素都变为了1
    }
    for(i=0; i<10; i++) {
        *(pc+i) = 1; //不成功,只能一个字节一个字节的改,只改动了10个字节。
    }
    /* 打印 */
    for(i=0; i<10; i++) {
        printf("%d ", arr[i]);
        printf("%d ", brr[i]);
    }
 
    return 0;
}

这是因为:char*的指针解引用只能访问一个字节 

总结:

 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节);

 譬如,char* 的指针解引用只能访问1个字节,而 int* 的指针解引用就能够访问4个字节

三.野指针

1.野指针的概念

概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

2.野指针的成因

  1.  指针未初始化;
  2. 指针越界访问;
  3. 指针指向的空间已释放;

(1)指针未初始化

int main()
{
    int *p; //初始化随机值
    *p = 20; //*p未初始化就使用,虽然有指向的对象,但其存放的内存是随机的
 
    return 0;
}

(2)指针越界访问

常出现在数中

int main()
{
    int arr[10] = {0};
    int *p = arr;
    int i = 0;
    for(i=0; i<12; i++)
    {
        //当指针越出arr管理的范围时,p就称为野指针
        p++;
    }
 
    return 0;
}

(3)指针指向的空间已经释放

int* test()
{
    int a = 10;
 
    return &a;
}
 
int main()
{
    int *pa = test();
    *pa = 20;
 
    return 0;
}

 这是因为:

  • 一进入test 函数内时就创建一个临时变量 a,这个a是局部变量,进入函数时创建,一旦出去就销毁,销毁就意味着这个内存空间还给了操作系统,这块空间就不再是 a 的了;
  • 进入这个函数时创建了 a,有了地址,ruturn &a 把地址返回去了,但是这个函数一结束,这块空间就不属于自己了,pa调用使用时,这块空间已经释放了,指针指向的空间被释放了,这种情况就会导致野指针的问题;
  • 只要是返回临时变量的地址,都会存在问题(除非这个变量出了这个范围不销毁);

 3.那该如何规避野指针问题?

答:乖乖初始化,指针指向空间释放及时置空,指针使用前检查有效性。

//初始化
int main()
{
    int a = 10;
    int* pa = &a;  // 初始化
    int* p = NULL; // 当你不知道给什么值的时候用NULL
     
    return 0;
}
//不想用了就置空
int main()
{
    int a = 10;
    int *pa = &a;
    *pa = 20;
 
    //假设已经把a操作好了,pa指针已经不打算用它了
    pa = NULL; //置成空指针
    
    return 0;
}
//指针使用前检查有效性
int main()
{
    int a = 10;
    int *pa = &a;
    *pa = 20;
 
    pa = NULL; 
    //*pa = 10; 崩溃,访问发生错误,指针为空时不能访问
    if(pa != NULL) { // 检查 如果指针不是空指针
        *pa = 10; // 检查通过才执行
    }  
        
    return 0;
}

四.指针运算

1.指针加整数

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int i = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);
    int* p = arr; // 指向数组的首元素 - arr[0] - 1
 
    for(i=0; i<sz; i++) {
        printf("%d ", *p);
        p = p + 1; //p++   第一次循环+1之后指向2
    }
 
    return 0;
}

 打印结果为 1 2 3 4 5 6 7 8 9 10

2.指针减整数

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int i = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);
    int* p = &arr[9]; // 取出数组最后一个元素的地址
    
    for(i=0; i<sz/2; i++) {
        printf("%d ", *p);
        p = p - 2;  //指针每次向前移动2个步长
    }
    return 0;
}

 打印结果为 10 8 6 4 2

4.指针减指针

公理:指针减指针的绝对值得到的是元素之间的元素个数(必须指向用一块内存空间)

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    printf("%d\n", &arr[9] - &arr[0]); // 得到指针和指针之间元素的个数9
 
    return 0;
}
//错位示范:
int ch[5] = {0};
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%d\n", &arr[9] - &ch[0]); // 没有意义,结果是不可预知的

手写streln:

//用指针方法实现;
int my_strlen(char* str)
{
    char* start = str;
    char* end = str;
    while(*end != '\0') {
        end++;
    }
    return end - start; //return

}

//用指针间做差化简:
int my_strlen(const char* str)
{
    const char* end = str;
    while(*end++);
    return (end - str - 1);
}
 
int main()
{
    //strlen - 求字符串长度
    //递归 - 模拟实现了strlen - 计数器方式1, 递归的方式2
 
    char arr[] = "abcdef";
    int len = my_strlen(arr); //arr是首元素的地址
    printf("%d\n", len);
 
    return 0;
}

5.指针的关系运算

//vp从最大位置的后一个内存位置的指针开始比较
for(vp = &values[N_VALUES]; vp > &values[0];) 
{
    *--vp = 0;
}

对于这样一段代码,我们可以做这样的化简,使vp从数组末尾开始比较,一直比较到数组首元素的前一个元素

//最后一次比较是vp与数组首元素的前一个内存位置的指针进行比较
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)  
{
    *vp = 0;
}

虽然第二种写法更容易理解,但我们应避免这么写,因为标准并不保证它可以执行:

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

 

 

五.指针和数组

1.数组名

一般情况下,数组名是首元素的地址,但是有两个特例:

  • sizeof(数组名):计算的是整个数组的大小,单位是字节。
  • &数组名:取出的是整个数组的地址,此时的数组名表示的是整个数组。

2.使用指针访问数组

p+i访问的是数组arr下标为i的地址:

int main()
{
    int arr[10] = {0};
    int* p = arr;  // 用指针访问数组, arr为数组首元素的地址
    int i = 0;
 
 
    for(i=0; i<10; i++) {
        printf("%d ", arr[i]);  
    }
 
    for(i=0; i<10; i++) {
        *(p+i) = i;   //用指针对数组进行赋值操作
    }
 
   
    for(i=0; i<10; i++) {
        printf("%d ", arr[i]);
     
    }
 
    return 0;
}

打印结果为:0 0 0 0 0 0 0 0 0 0

                      0 1 2 3 4 5 6 7 8 9

所以通过指针是可以对数组进行访问的,一般情况下,数组名是数组首元素的地址。

六.二级指针

什么是二级指针? 如下图所示:

变量a的地址存放在pa中,变量pa的地址存放在ppa中,pa是一级指针,ppa是二级指针。

 总结:指针变量也是变量,是变量就有地址,一级指针的地址就存放在二级指针中。

 二级指针的应用:

int main()
{
    int a = 10;
    int* pa = &a;
    int** ppa = &pa; // ppa是二级指针
    int*** pppa = &ppa; // pppa是三级指针
    ...
    
    **ppa = 20;
    printf("%d\n", **ppa); // 20
    printf("%d\n", a);  // 20
 
    return 0;
}

 *ppa通过对ppa中的地址解引用,找到了pa,*ppa访问的是pa。

**ppa先通过*ppa找到pa,然后*pa再找到a,所以**ppa访问的是a。

七.指针数组

指针数组的概念:

指针数组本质上数组,但数组元素类型是指针

int a = 10;
int b = 20;
int c = 30;
int arr1[10];//arr1是一个存放整型的数组
int* arr2[3] = { &a,&b,&c };//arr2是一个存放整型指针的数组

注意数组arr2里存放的是变量a,b,c的地址,要访问a,b,c还要进行解引用操作。

//打印a,b,c的值
for(int i=0;i<3;i++)
{
    printf("%d",*arr2[i]);
}

打印结果为:10 20 30


本篇到此结束,谢谢大家的观看!

码文不易,还请多多支持哦~

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

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

相关文章

http 跨域资源共享详解

http 跨域资源共享详解 由于浏览器同源策略限制&#xff0c;会导致出现跨域问题。而跨域资源共享&#xff08;CORS&#xff09;可以突破浏览的同源策略的限制&#xff0c;不过需要服务端配合设置相应的响应头&#xff0c;从而使跨源数据传输得以安全进行。 跨域资源共享新增了…

进销存软件对中小型企业管理有什么作用?

进销存软件对中小型企业管理有什么作用&#xff1f; 01 更加有序 库存不乱单据不乱价格不乱 使用进销存软件可以把这些都记录下来&#xff0c;有条不紊&#xff0c;出现什么问题也有据可查&#xff0c;不像纸质单据&#xff0c;会丢会坏&#xff0c;乱成一团。 02 能打印正式…

[Spring Cloud] Hystrix通过配置文件统一设置参数/与OpenFeign结合使用

✨✨个人主页:沫洺的主页 &#x1f4da;&#x1f4da;系列专栏: &#x1f4d6; JavaWeb专栏&#x1f4d6; JavaSE专栏 &#x1f4d6; Java基础专栏&#x1f4d6;vue3专栏 &#x1f4d6;MyBatis专栏&#x1f4d6;Spring专栏&#x1f4d6;SpringMVC专栏&#x1f4d6;SpringBoot专…

网站favion.ico图标

Favicon.ico一般用于作为缩略的网站标志&#xff0c;它显示在浏览器的地址栏或者标签上。 目前主要的浏览器都支持favicon.ico图标 一 制作favicon图标 1 把品优购图标切成png图片 2 把png图片转换为ico图标&#xff0c;这需要借助第三方转换网站&#xff0c;例如&#xff1…

刷题经验分享(一)

文章目录删除公共字符&#xff1a;组队竞赛&#xff1a;删除公共字符&#xff1a; 第一题&#xff1a;删除公共字符 方法一&#xff1a; 思路&#xff1a; 1.将第二个字符串的字符都映射到一个hashtable数组中&#xff0c;用来判断一个字符在这个字符串。 2. 判断一个字符在第…

140.深度学习分布式计算框架-3

140.1 Horovod Horovod是 Uber 开源的又一个深度学习工具&#xff0c;它的发展吸取了 Facebook「一小时训练 ImageNet 论文」与百度 Ring Allreduce 的优点&#xff0c;可为用户实现分布式训练提供帮助。Horovod 支持通过用于高性能并行计算的低层次接口 – 消息传递接口 (MPI…

Vue脚手架环境中简单使用MarkDown(只入门)

目录 入门 高级使用 入门 1 所在终端输入 npm install vue-meditor 2 复制以下代码 先新建一个组件 <template><div><MavonEditor v-model"myMarkDownData"/><button click"submit">提交</button></div> </te…

vue配置

首先安装node.js 在cmd node -v查看 然后 cmd命令行执行 : npm install -g vue/cli // 加-g是安装到全局 安装vue cli 安装vue cli 1 看一下 这是在cmd输入的内容,参考第一条链接 在vscode中怎么配置他? 文件-打开文件夹,选择一个文件夹 这里是firstvue 在下面新建文件夹…

可变长子网划分

目录 IP地址 子网划分 可变长子网划分 IP地址 在学习子网划分之前应该先清楚什么是IP地址和IP地址的类型 IP 地址的格式0网络地址主机地址10网络地址 主机地址 110网络地址主机地址1110组播地址11110保留 A 1.0.0.0~127.255.255.255 B 128.0.0.0~191.255.255…

C++编程进阶

目录 new运算符 new关键字的使用案例 C的引用 C中引用案例 引用的注意事项 引用做函数参数 引用做函数的返回值 前言&#xff1a; 具体案例 引用的本质 常量引用 常量引用原理 经典案例 函数的提高 函数的默认参数 注意&#xff1a; 具体案例 函数的占位参数…

已解决:树莓派外接硬盘 usb 或者sata 导致wifi无法链接 无线网卡无法使用问题

我的环境是树莓派4b 买了一个有硬盘的盒子 看上图的连接方式&#xff0c;是占用了树莓派的一个usb3.1进行了sata的转接&#xff0c;实现挂载硬盘。 但是我发现&#xff0c;安装系统开机之后&#xff0c;可以看到有硬盘接入&#xff0c;但是无法连wifi&#xff0c;如果拔掉硬盘…

G. SlavicG‘s Favorite Problem(树的遍历DFS,BFS均可)

Problem - G - Codeforces 给你一棵有n个顶点的加权树。回顾一下&#xff0c;树是一个没有任何循环的连接图。加权树是一棵树&#xff0c;其中每条边都有一定的权重。这棵树是无定向的&#xff0c;它没有根。 由于树让你感到厌烦&#xff0c;你决定挑战自己&#xff0c;在给定…

java--Lambda (3)变量的访问与修改

文章目录0 写在前面1 可以直接在 Lambda 表达式中访问外层的局部变量2 在 Lambda 表达式当中被引用的变量的值不可以被更改3 在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量4 写在最后0 写在前面 学习使用在Lambda表达式&#xff0c;有些地方访问一些变…

VM系列振弦读数模块采集测量数据的一般步骤

VM 模块是通用型单振弦式传感器测量模块&#xff0c;主要功能是测量频率的传感器内置的温度传感器&#xff0c;使用默认工作参数即可自动测读绝大多数振弦传感器。 但由于传感器类型、结构、厂家、钢弦材料、线圈等影响因素不同&#xff0c;导致使用 VM 模块测量某些传感器时需…

【408专项篇】C语言笔记-第六章(指针)

文章目录第一节&#xff1a;指针的本质1. 指针的定义2. 取地址操作符与取值操作符第二节&#xff1a;指针的传递1. 指针的传递第三节&#xff1a;指针的偏移1. 指针的偏移2. 指针与一维数组第四节&#xff1a;动态指针与内存申请1. 指针与动态内存申请2. 堆空间和栈空间的差异第…

电脑无线网卡连接的无线信号不好的,经常丢包,掉线断网如何优化解决

环境&#xff1a; 电脑&#xff1a;HP480G7 系统:Win10 专业版 无线网卡&#xff1a;水星ud6s 、TP-LINK等 AP&#xff1a;锐捷RAP2200(E) 组网模式&#xff1a;AP代管AP模式 问题描述&#xff1a; 电脑无线网卡连接的无线信号不好的&#xff0c;经常断网&#xff0c;因…

docker logs命令详解

docker 命令官档如下&#xff1a; https://docs.docker.com/engine/reference/commandline/docker/ docker logs官档如下 https://docs.docker.com/engine/reference/commandline/logs/#retrieve-logs-until-a-specific-point-in-time 一、常用命令 1. 可以查看命令用法 doc…

英国访问学者|签证申请详细步骤简述

英国访问学者签证流程是怎么样的&#xff1f;下面就随知识人网老师一起来看一看英国访问学者签证申请详细步骤简述。 第1步&#xff1a;英国签证申请中心只接受在线填写的申请表&#xff0c;不接受手写表格。请访问官网完整填写申请表。(必须使用英文大写填写) 第2步&#xff…

数据库系统 整体结构化 的理解

数据库系统实现整体数据的结构化&#xff0c;这是数据库的主要特征之一&#xff0c;也是数据库系统与文件系统的本质区别。 在文件系统中&#xff0c;文件中的记录内部具有结构&#xff0c;但是记录的结构和记录之间的联系被固化在程序中&#xff0c;需要由程序员加以维…

jenkins配置maven+git自动构建jar包

进入页面 安装maven插件 需要使用maven插件构建项目,所以安装maven插件 步骤如下图&#xff1a; 点击安装后跳转至安装页面&#xff0c;等待在线安装完成即可 安装完成配置当前服务器中安装的maven 下滑到最下面点击新增maven 配置完成点击应用完成 配置当前服务器中安装的gi…