0


快过年了别着急玩耍,学会指针轻松一整年的学习

指针

大家常说,指针是C语言的灵魂,十分的难,确实,在你没有理解指针的时候,你会觉得很难,但我们尝试去通过指针的本质去理解指针

指针是什么,指针和内存的关系是什么

指针是什么,接下来我先套用百度来的解释,再和大家详细讲解.

在计算机科学中,指针是编译语言中的一个对象,利用地址,它的值直接指向存在电脑存储器中某一个地方的值。由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化呢成为指针,意思是通过它能找到以它为地址的内存单元.

C语言是一种和内存直接挂钩的计算机编程语言,我们定义变量,调用函数,都是在向内存中申请一块空间进行操作,之前说过,变量是一个盒子,常量是盒子里的内容,现在我们把盒子比喻成房子,住的人是常量,那么你住在一个小区里,你的房子一定有着对应的门牌号,这个就是内存中的地址,我们把内存形象的分为一个个内存单元,一个字节对应一个内存单元,按照需求向内存中申请不同数量的内存单元

指针变量

我们常提的指针,其实是指针变量,就是存储地址这一特殊常量的盒子,指针和这三个符号总是挂钩的 & [],*一定要牢记这一点,接下来我们慢慢讲指针变量

指针的基本操作

以int类型的指针为例,我们简单介绍一下指针的简单使用

#include<stdio.h>
int main() {
    int a = 5;
    int* p = &a;
    printf("a=%d\n", a);
    *p = 10;
    printf("a=%d\n", a);

}

&地址代表拿到变量的地址,*代表根据地址找到变量进行一次解引用

所以结果是

指针变量的大小

指针变量的大小取决于编译器环境的大小,32位环境下指针的大小是四个字节,地址的编号是八位的十六进制序列,64位环境的大小是八个字节,地址的编号是十六位的十六进制序列

指针变量的类型

指针变量和一般的变量不同,指针类型不同决定的不是指针变量的大小

结论一:指针类型决定指针允许访问的空间大小

#include<stdio.h>
int main() {
    int arr[5] = { 1,2,3,4,5 };
    char* p = (char*)arr;
    for (int i = 0; i < 5; i++)
    {
        *(p + i) = 0;
    }
    for (int i = 0; i < 5; i++)
    {
        printf("%d ", arr[i]);
    }
}

那么这个代码的结果是多少呢?

他只改变了第一个和第二个元素,并没有改变五个元素。

我们都知道一个char是一个字节,一个int是4个字节,如果按照上面的结论,指针变量类型决定指针访问的字节数就可以解释的通了,但至于2为什么改变了,这和数据存储结构有关

结论二:指针加减整数代表移动多少步,指针类型决定移动一步的大小

上面的代码中进行了p+i这里的i是整数,就是指针p移动了i步,+代表向高地址移动,-代表向低地址移动,接下来我们用一段代码来理解

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

那么当我们输入两个0的时候代码的运行结果是什么呢?

按照结论和代码执行的过程去进行分析,代码的结果应该是5和7被替换成0

按照结论来分析结果并没有什么错误,所以,指针的整数的运算代表着指针的移动

结论三:指针相减的结果是两端地址中的元素个数,指针的比较是高地址大于低地址

指针之间也是存在运算的,根据上一个结论我们得知,指针加整数代表向高地址移动,指针减整数代表指针往低地址移动,我们一起用这段代码证明一下

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

我们先想想这串代码的运行结果,然后再对比一下答案

我们不难发现结论的正确性,当然,**还有一点就是高地址的十六进制序列大,低地址的十六进制序列小 **

野指针

使用指针的时候一定要注意,一不小心可能就会野指针问题

什么是野指针

野指针就是一种指向的位置不可知的指针,这种不可知的位置可能是随机的,不正确的,或者是没有明确限制的。

野指针的三种产生原因

1.指针未进行初始化

用一段简单的代码来举例

#include<stdio.h>
int main() {
    int* p;
    *p = 10;
    printf("%d", *p);

    return 0;
}

这里的指针p未进行初始化,属于野指针,运行代码会出错

**2.指针越界访问 **

我们都知道数组的下标是从0开始,一个长度为10的arr数组,arr[10]操作就属于越界操作,这种情况就属于越界访问

3.指针指向的空间释放

我们都知道函数调用会向栈区申请一块空间,函数调用结束之后这个空间会进行销毁,如果我们使用的指针指向的是函数中创建的临时变量,函数调用结束这段空间会进行释放,这时候的指针就属于野指针,会出现这种情况主要是三种情况

调用函数结束的空间释放,动态内存释放和关闭文件

后两种情况我们目前还不会接触,就以第一种情况来进行举例

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

在gcc环境下的编译器会报错,因为a申请的空间已经释放

正确避免野指针

野指针有时会杀人于无形之中,甚至有时会让程序崩溃,那既然野指针这么可怕,我们就一定要想办法避免野指针

1.使用之前进行初始化

对于一个指针,定义的时候我们一定要对其进行初始化

如果想让指针指向一个已经存在的变量就直接指向那个变量,如果不知道该让指针指向谁,就赋值空指针
比如
int a=10;
int*pa=&a;
int*pb;//我们不知道指向谁,但是pb是有用的,我们就先赋值为空
int*pb=NULL;

NULL就是空指针的意思

2.小心指针越界

指针越界是一个非常让人头疼的事,所以我们在使用指针顺序访问,或者随机访问时一定要判断指针是否越界

#include<stdio.h>
int main() {
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int *p = arr;
    int *p1 = arr + 5;
    int *p2 = arr + 8;
    int *p3 = arr + 15;
    for (int i = 0; i < 10; i++)
    {
        if (p + i < arr + 10)//arr+10就是数组的一个边界
            printf("%d ", *(p + i));
        if (p1 + i < arr + 10)
            printf("%d ", *(p1 + i));
        if (p2 + i < arr + 10)
            printf("%d ", *(p2 + i));
        if (p3+ i < arr + 10)
            printf("%d\n", *(p3 + i));
    }
    return 0;
}

我们用arr+10来进行判断边界,当然肯定会有小伙伴说用>arr-1也能判断边界,这里涉及了指针比较的一个规则

允许指向数组元素的指针与指向数组的最后一个元素后面的内存位置的指针进行比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较

我们再来看看这段代码的结果

结果没有什么问题,说明这样并不会越界,所以我们可以这样来检查指针越界

3.指针指向的空间释放后就把指针赋值为空指针

简单来说就是指针指向的位置不能使用之后,我们就用NULL来赋值这个指针,这个在动态内存和文件指针会详细介绍

4.指针使用之前确定其有效性

#include<stdio.h>
int main() {
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int *p = arr;
    int *p1 = arr + 5;
    int *p2 = arr + 8;
    int *p3 = arr + 15;
    for (int i = 0; i < 10; i++)
    {
        if (p != NULL && p + i < arr + 10)//arr+10就是数组的一个边界
            printf("%d ", *(p + i));
        if (p1 != NULL && p1 + i < arr + 10)
            printf("%d ", *(p1 + i));
        if (p2 != NULL && p2 + i < arr + 10)
            printf("%d ", *(p2 + i));
        if (p3 != NULL && p3 + i < arr + 10)
            printf("%d\n", *(p3 + i));
    }
    return 0;
}

我们对之前的代码进行改造,p!=NULL,就是一种非常有效的判断

好了今天的分享就到这里了,下一节深度剖析一下指针地址与数组,帮助大家更好的掌握指针


本文转载自: https://blog.csdn.net/weixin_62753802/article/details/122547704
版权归原作者 build小春宝 所有, 如有侵权,请联系我们删除。

“快过年了别着急玩耍,学会指针轻松一整年的学习”的评论:

还没有评论