当前位置: 首页 > news >正文

六.初阶指针

 前言:大家好哇!今天带大家认识下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


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

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

相关文章:

  • 人工智能-4计算机视觉和图像处理01
  • R语言dplyr包select函数筛选dataframe数据中以指定字符串开头的数据列(变量)
  • 【SpringBoot】SpringBoot+SpringSecurity+CAS实现单点登录
  • 【仿真建模】AnyLogic入门基础教程 第一课
  • 《看漫画学Python》1、2版分享,python最佳入门教程,中学生用业余时间都能学会,北大教授看完都这样定义它
  • python一键去PDF水印,只需十行代码,超级简单...
  • 城市消费券,拒绝恶意爬取
  • C语言第十一课(下):优化扫雷游戏
  • MySQL之优化SELECT语句
  • IPv6与VoIP——ipv6接口标识与VoIP概述
  • 性能测试_JMeter_connection timed out :connect
  • SpringCloud 相关
  • 精华推荐 | 【深入浅出RocketMQ原理及实战】「底层原理挖掘系列」透彻剖析贯穿RocketMQ的存储系统的实现原理和持久化机制
  • 基于JAVA的会议管理系统参考【数据库设计、源码、开题报告】
  • 爬虫基础知识
  • php的引用
  • 【剑指offer】让抽象问题具体化
  • css属性的继承、初识值、计算值、当前值、应用值
  • HTML5新特性总结
  • JavaScript 基础知识 - 入门篇(一)
  • JS题目及答案整理
  • vue从入门到进阶:计算属性computed与侦听器watch(三)
  • 百度地图API标注+时间轴组件
  • 笨办法学C 练习34:动态数组
  • 从0实现一个tiny react(三)生命周期
  • 订阅Forge Viewer所有的事件
  • 前端性能优化--懒加载和预加载
  • 腾讯大梁:DevOps最后一棒,有效构建海量运营的持续反馈能力
  • 通过几道题目学习二叉搜索树
  • 为什么要用IPython/Jupyter?
  • 用Visual Studio开发以太坊智能合约
  • media数据库操作,可以进行增删改查,实现回收站,隐私照片功能 SharedPreferences存储地址:
  • 【运维趟坑回忆录】vpc迁移 - 吃螃蟹之路
  • ​LeetCode解法汇总2670. 找出不同元素数目差数组
  • # centos7下FFmpeg环境部署记录
  • #NOIP 2014# day.2 T2 寻找道路
  • #stm32驱动外设模块总结w5500模块
  • (java)关于Thread的挂起和恢复
  • (k8s中)docker netty OOM问题记录
  • (理论篇)httpmoudle和httphandler一览
  • (利用IDEA+Maven)定制属于自己的jar包
  • (三)uboot源码分析
  • (五)网络优化与超参数选择--九五小庞
  • (一)基于IDEA的JAVA基础12
  • (转)Linux下编译安装log4cxx
  • .Net mvc总结
  • .net MySql
  • .netcore 如何获取系统中所有session_ASP.NET Core如何解决分布式Session一致性问题
  • .NET设计模式(11):组合模式(Composite Pattern)
  • ::
  • @serverendpoint注解_SpringBoot 使用WebSocket打造在线聊天室(基于注解)
  • [ 数据结构 - C++] AVL树原理及实现
  • [1181]linux两台服务器之间传输文件和文件夹
  • [④ADRV902x]: Digital Filter Configuration(发射端)
  • [AS3]URLLoader+URLRequest+JPGEncoder实现BitmapData图片数据保存