• 拿捏指针(一)


    说到指针可能很多人既熟悉又陌生。那么什么是指针呢?

    目录

    1.0 定义和地址

     1.1 指针定义及

    1.2 &取地址操作符

    1.3 指针变量

    2.0 *解引用操作符

    2.1 指针解引用

    3.0 指针中的const

    3.1 const在*号左边

    3.2 const在*号右边

    4.0  void*指针

    5.0 指针的运算

    5.1 指针+-整数

    5.1.1 普通

    5.1.2 进阶

    5.2 指针-指针

    5.3 指针的关系运算 

    6.0 野指针

     6.1野指针的成因

    6.2 规避野指针

    7.0 assert

    8.0 传值调⽤和传址调⽤

    8.1 传值调用

     8.2 传址调⽤


    1.0 定义和地址

     1.1 指针定义及

    指针是一个变量,它存储了指向另一个变量的内存地址。它可以用来间接地访问和修改这个变量的值。每个变量在内存中都有一个唯一的地址,指针通过存储这个地址来引用其他变量。指针可以用于在程序中传递和操作内存地址,从而使程序能够更高效地访问和操作内存中的数据。

    这样说你可能,还是听不懂,那我们举个例子:

    这一天有八位客人在前台登记了入住,分别是a,b,c,d,e,f,g,h。他们一起住在酒店的一个楼层

    91c26ce6a9574f789c573cbe63075726.png

    这就类似于指针,通过地址能让我们准确的找到想找的人。

    我们回归到计算机中来,每次房间相当于一个字节,char刚刚好就是一个字符,但酒店也有大房间,有四个字节的int,八个字节的long.....而这些地址在C语言当中就被叫做指针。也可以理解为,

    房间编号=地址=指针。

    在32位下有32根总地址线,每根线只有两态,表⽰0,1【电脉冲有⽆】,那么 ⼀根线,就能表⽰2种含义,2根线就能表⽰4种含 义,依次类推。32根地址线,就能表⽰2^32种含 义,每⼀种含义都代表⼀个地址。同理在64位下,有64根总线,就能表示2^64种含义的地址。

    1.2 &取地址操作符

    取地址操作符用于获取一个变量的地址。

    每个字符都有属于自己的地址。

    1. #define _CRT_SECURE_NO_WARNINGS
    2. #include
    3. int main()
    4. {
    5. int a = 2;
    6. return 0;
    7. }
    8. //a的地址
    9. //0x004FFDF0
    10. //0x004FFDF1
    11. //0x004FFDF2
    12. //0x004FFDF3

     0801863a40d146fe95d569e2fd4e1e7e.png

    这就是整形(int)a向内存申请的四个字节,那么我们应该怎么将地址存起来 ? 

    这就需要&(取地址操作符)&a将a的地址取出来。

    1. int main()
    2. {
    3. int a = 2;
    4. &a;
    5. return 0;
    6. }

    1.3 指针变量

    我们将a的地址取出来,但是要将它存到哪里呢?

    这是时候就需要用到指针变量将这个地址给存起来。

    1. int main()
    2. {
    3. int a = 2;
    4. int* p1 = &a;
    5. return 0;
    6. }

    指针变量也是⼀种变量,这种变量就是⽤来存放地址的,存放在指针变量中的值都会理解为地址

    那么,int*和int应该怎么理解呢?

    c9a92ad24360428c955a88c2d3e56375.png

    p的左边是int*,*是在说明p是指针变量,而前面的int在说明a是整形,而指针变量p指向的是int(整形)a的内容。

    如果我们创建的变量是char  a ='we',那么储存变量的类型也要是char * 。

    那么指针变量的大小是多少?占多少个内存?

    8aea0e1c86d44a1f90954909457e61a0.png26709d4158e24bd0ad6281d765c24136.png

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

    注意指针变量的⼤⼩和类型是⽆关的,只要指针类型的变量,在相同的平台下,⼤⼩都是相同的。

    2.0 *解引用操作符

    我们要存放东西,拿东西就需要地址(指针),就可以通过地址(指针)找到地址(指针) 指向的对象,这⾥必须学习⼀个操作符叫解引⽤操作符(*)。

    定义:解引用操作符(*)用于访问指针所指向的内存地址中存储的值。当使用解引用操作符对指针进行解引用时,实际上是在访问指针所指向的内存单元。

    1. #include
    2. int main()
    3. {
    4. int a = 100;
    5. int* p = &a;
    6. *p = 10;
    7. return 0;
    8. }

    以上代码,*p=10;*p意思就是通过p中存放的地址,找到所指向的空间,*p其实就是a变量,所以*p = 10,就是把操作符a改成0。

    但为什么我们要大费周章的改,不直接对a就行更改呢?有没有意义?

    那肯定是有的,对a的修改,多了一种途径,写代码就会更加灵活。

    2.1 指针解引用

    1. //代码1
    2. int main()
    3. {
    4. int n = 0x11223344;
    5. int* p = &n;
    6. *p = 0;
    7. return 0;
    8. }
    9. //代码2
    10. int main()
    11. {
    12. int n = 0x11223344;
    13. char* p = (char* )&n;
    14. *p = 0;
    15. return 0;
    16. }

    我们调试时代码1时会发现,代码1会将n的四个字节全部改为0,而代码2只有一个字节会被改为0。

    这我们就能得出,指针变量初始化是根据变量的类型来决定的,类型决定了访问的字节大小。

    3.0 指针中的const

    将变量的地址传给指针变量,通过指针变量同样可以修改变量的值。但有时候我们不希望这个值或者地址被修改,我们用到什么呢?没错就是const。

    3.1 const在*号左边

     db7a3e8b37724545a3353db00e092817.png

    当const在*号左时,我们可以这样理解,*p指向指针的内容,内容锁死了,保证指针指向的内容不能通过指针来改变。 但是指针变量本⾝的内容可变。我们要修改它的值的时候,编译器就会报警告,无法修改。

    将*p注释后则会打印5。

    642a8ba35cd44f618cab2105649ce4c4.png

    3.2 const在*号右边

    c98d3493aac94d4689d0ad1b021e3bd9.png

    当const在*号左时,p修饰的是指针变量本身,p里面的地址就被锁死,保证了指针变量的内容不能修改,但是指针指 向的内容,可以通过指针改变。

    我们将p=&b注释掉,看看打印的值6a84ca33b31a4da6a4cb05cfa4ffd5bd.png

    4.0  void*指针

    void*指针是无类型的指针,可以接受任何类型的指针,但void*类型的指针不能直接进⾏指针的+-整数和解引⽤的运算。

    1. #include
    2. int main()
    3. {
    4. int a = 10;
    5. void* pa = &a;
    6. void* pc = &a;
    7. *pa = 10;
    8. *pc = 0;
    9. return 0;
    10. }

    vs编译器的结果

    207b43269cad42a4ad686248076b6af0.png

    又以上结果可以得知,void*可以接收任意类型的指针但是不能进行运算。 

    5.0 指针的运算

    5.1 指针+-整数

    5.1.1 普通

    1. #include
    2. int main()
    3. {
    4. int n = 10;
    5. char* pc = (char*)&n;
    6. int* pi = &n;
    7. printf("%p\n", &n);
    8. printf("%p\n", pc);
    9. printf("%p\n", pc + 1);
    10. printf("%p\n", pi);
    11. printf("%p\n", pi + 1);
    12. return 0;
    13. }

    打印结果:

    &n     =00EFF8A0
    pc     =00EFF8A0
    pc+1 =00EFF8A1
    pi      =00EFF8A0
    pi+1  =00EFF8A4 

    我们可以看出来,pi和pc跳过的大小是不一样的,pi的类型是(int*)所以跳过 四个字节;而pc的类型是(char*)跳过一个字节。

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

    5.1.2 进阶

    因为数组在内存中是连续存放的,所以只要知道了首元素地址,就能知道后边的元素的地址和值。

    数组12345678910
    下标012  3456789
    1. int main()
    2. {
    3. int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    4. int* p = &arr[0];
    5. int i = 0;
    6. int sz = sizeof(arr) / sizeof(arr[0]);
    7. for (i = 0; i < sz; i++)
    8. {
    9. printf("%d\n", *(p + i));//(p+i)就是指针+整数,同理-也是可行的
    10. }
    11. return 0;
    12. }

    5.2 指针-指针

    1. //指针-指针
    2. #include
    3. int my_strlen(char* s)
    4. {
    5. char* p = s;
    6. while (*p != '\0')
    7. p++;
    8. return p - s;
    9. }
    10. int main()
    11. {
    12. printf("%d\n", my_strlen("abc"));
    13. return 0;
    14. }

    或许有人会感觉指针-指针很奇怪,难于解决。那么我们画图来看看就明白了

    9accf18977194ec5988f1d1e2b1d68ae.png

    5.3 指针的关系运算 

    1. #include
    2. int main()
    3. {
    4. int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    5. int* p = &arr[0];
    6. int i = 0;
    7. int sz = sizeof(arr) / sizeof(arr[0]);
    8. while (p < arr + sz) //指针的⼤⼩⽐较
    9. {
    10. printf("%d ", *p);
    11. p++;
    12. }
    13. printf("%d ", *p);
    14. p++;
    15. return 0;
    16. }

    6.0 野指针

    野指针概念:野指针(或称为悬空指针)是指一个指针变量指向了无效的内存地址或者已被释放的内存,而访问该指针所指向的内存会导致不可预测的行为或程序崩溃。

     6.1野指针的成因

    (1)指针未初始化

    1. #include
    2. int main()
    3. {
    4. int* p;
    5. //局部变量指针未初始化,默认为随机值
    6. * p = 20;
    7. return 0;
    8. }

    (2) 指针越界访问

    1. #include
    2. int main()
    3. {
    4. int arr[10] = { 0 };
    5. int* p = &arr[0];
    6. int i = 0;
    7. for (i = 0; i <= 11; i++)
    8. {
    9. //当指针指向的范围超出数组arr的范围时,p就是野指针
    10. printf("%d\n", arr[i]);
    11. * (p++) = i;
    12. }
    13. return 0;
    14. }

    992c6af42a764b60bac4cec6da798271.png

    最后圈起来的这两个值就是随机值,也就是野指针。

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

    1. #include
    2. int* test()
    3. {
    4. int n = 100;
    5. return &n;
    6. }
    7. int main()
    8. {
    9. int* p = test();
    10. printf("%d\n", *p);
    11. return 0;
    12. }

    6.2 规避野指针

    (1)指针初始化

    如果明确知道指针指向哪⾥就直接赋值地址,如果不知道指针应该指向哪⾥,可以给指针赋值NULL. NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是⽆法使⽤的,读写该地址 会报错

    1. #ifdef __cplusplus
    2. #define NULL 0
    3. #else
    4. #define NULL ((void *)0)
    5. #endif

    初始化如下:

    1. #include
    2. int main()
    3. {
    4. int num = 10;
    5. int* p1 = #
    6. int* p2 = NULL;
    7. return 0;
    8. }

    (2)⼩⼼指针越界

    ⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是 越界访问。

    (3)指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性

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

    7.0 assert

    assert头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报 错终⽌运⾏。这个宏常常被称为“断⾔”。

    1. #include
    2. #include
    3. int main()
    4. {
    5. int a = 0;
    6. assert(a != 0);
    7. return 0;
    8. }

    宏接受⼀个表达式作为参数。上面该表达式为真(返回值⾮零), 任何作⽤,程序继续运⾏。如果该表达式为假(返回值为零), 流 st assert() 不会产⽣ assert() 就会报错,在标准错误 derr 中写⼊⼀条错误信息,显⽰没有通过的表达式,以及包含这个表达式的⽂件名和⾏号。

    8.0 传值调⽤和传址调⽤

    8.1 传值调用

    1. int Add(int x ,int b)
    2. {
    3. return x + b;
    4. }
    5. int main()
    6. {
    7. int a = 2;
    8. int b = 3;
    9. int ret = Add(a, b);
    10. printf("%d", ret);
    11. return 0;
    12. }

    上面代码就是将a和b的值,传到Add函数里,这就是简单的传值。

     8.2 传址调⽤

    写⼀个函数,交换两个整型变量的

    打印

     交换前:a=2 b=3
    交换前:a=2 b=3

    我们发现传值只是将数值拷贝了一份,但a和x,b和y的地址是不一样的。在函数中x和y进行了交换,返回的值还是Swap函数调⽤结束后回到main函数,a和b的没法交换。Swap1函数在使⽤ 的时候,是把变量本⾝直接传递给了函数,这种调⽤函数的⽅式我们之前在函数的时候就知道了,这 种叫传值调⽤。

    既然传值解决不了问题那只能用传地解决。

    1. void Swap(int* px, int* py)
    2. {
    3. int tmp = 0;
    4. tmp = *px;
    5. *px = *py;
    6. *py = tmp;
    7. }
    8. int main()
    9. {
    10. int a = 2;
    11. int b = 3;
    12. printf("交换前:a = % d b = % d\n", a, b);
    13. Swap(&a, &b);
    14. printf("交换后:a = % d b = % d\n", a, b);
    15. return 0;
    16. }

    打印结果

    交换前:a = 2 b = 3
    交换后:a = 3 b = 2 

    这⾥调⽤Swap函数的时候是将变量的地址传 递给了函数,这种函数调⽤⽅式叫:传址调⽤


    谢谢观众老爷们观看。 

  • 相关阅读:
    在大数据相关技术中,HBase是个分布的、面向列的开源数据库,是一个适合于非结构化数据存储的数据库。
    信息收集&WAF识别&蜘蛛头
    【附源码】计算机毕业设计JAVA“日进斗金”理财大师系统设计与实现
    前端基础入门
    【AI必备利器】GPU白嫖指南
    皕杰报表的web.xml配置
    Java扫码点餐小程序源码 智慧点餐系统源码 点餐APP SaaS模式
    Spark基础【RDD转换算子】
    新 Nsight Graph、Nsight Aftermath 版本中的性能提升和增强功能
    PAT乙级 1101 B是A的多少倍 C语言实现
  • 原文地址:https://blog.csdn.net/2302_80262940/article/details/136477758