• 正文
  • 相关推荐
申请入驻 产业图谱

用简单的例子,来理解C指针

04/20 16:12
169
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

关注“码农爱学习

C/C++中,指针是一个常用的编程概念,它本身也是一个变量,只不过其存储的值是一个地址。

指针存在的意义,可以使程序直接访问内存、间接操作数据、高效传递数据等。

指针用的好,可以使编程更加高效灵活,因此,理解指针的本质,对于C/C++编程是十分必要的。如果理解的不够透彻,一来在编写代码时可能迷惑不清,二来如果使用不恰当,轻则代码的运行与预期不符,重则程序崩溃。

指针可以有多级,最常用的就是一级指针,简称指针,再者就是在某些场景下会用到二级指针,再复杂的三级及以上的指针,使用的极少。

本篇,就先来探究一下一级指针。

1 指针使用场景举例——两数相加

比如一个计算两个int相加的函数,注意这里只是举例,不需要纠结简单的加法为什么要写一个函数。

实现方式呢,可能有多种。

1.1 方法一:求和结果通过返回值(不需要使用指针)

将两个加数作为函数参数,结果通过函数的返回值返回,很好理解,如下示例代码。

test1.c

// gcc test1.c -o test1
#include <stdio.h>

int sum(int a, int b)
{
    int res = 0;
    res = a + b:
    return res;
}

int main()
{
    int m = 1;
    int n = 2;
    int calc_result = sum(m, n);
    printf("calc_result:%dn", calc_result);
    
    return0;
}

不涉及指针,代码一目了然,所见即所得,这里就不作过多解释了。

下面开始探究指针。

1.2 方法二:求和结果通过函数传参(需要使用指针)

如果计算结果也改为从参数传入,就需要用到指针了。为什么要使用指针,不使用指针会怎样?

1.2.1 如果不使用指针传参

test2.c

// gcc test2.c -o test2
#include <stdio.h>

void sum(int a, int b, int result)
{
    int res = 0;
    res = a + b;
    result = res;
    
    printf("[%s] a:%d(%p), b:%d(%p), result:%d(%p), res:%d(%p)n",
           __func__, a, &a, b, &b, result, &result, res, &res);
}

int main()
{
    int m = 1;
    int n = 2;
    int calc_result = 0;
    printf("[%s] m:%d(%p), m:%d(%p), calc_result:%d(%p)n", 
           __func__, m, &m, n, &n, calc_result, &calc_result);
    sum(m, n, calc_result);
    printf("[%s] calc_result:%dn", __func__, calc_result);
    
    return0;
}

在学习函数参数的时候,有一个知识点:形参与实参,函数定义时,函数后面括号里是形式参数,只是一个变量名,用来占位、接收数据,而在函数调用时,在函数的调用位置,这个时候是实际参数,而函数的调用中参数,是把实参的值复制给形参,形参在函数里面怎么改,都不会影响外面的实参

因为,上述对求和结果,通过参数的形式,不采用指针的方式,是无法将计算的结果(形参)传递给外面的实参的。

运行结果,可以看到求和结果无法传递出来:

根据打印的变量地址,画图来分析各变量的地址与数据传递关系:

可以看到,求和结果修改的只是函数内的形参。

1.2.2 使用指针传参

test3.c

// gcc test3.c -o test3
#include <stdio.h>

void sum(int a, int b, int *result)
{
    int res = 0;
    res = a + b;
    *result = res;
    
    printf("[%s] a:%d(%p), b:%d(%p), result:%p(%p)[*result:%d], res:%d(%p)n",
           __func__, a, &a, b, &b, result, &result, *result, res, &res);
}

int main()
{
    int m = 1;
    int n = 2;
    int calc_result = 0;
    printf("[%s] m:%d(%p), m:%d(%p), calc_result:%d(%p)n", 
           __func__, m, &m, n, &n, calc_result, &calc_result);
    sum(m, n, &calc_result);
    printf("calc_result:%dn", calc_result);
    
    return0;
}

运行结果,可以看到求和结果传递出来了:

分析各变量的地址与数据传递关系:

因为函数参数中result是指针,即告诉sum函数外部的求和变量的地址,因为sum函数可以通过直接修改外部变量的值,实现将内部的求和结果通过参数的方式传递值外部(其实是对数据进行一种间接访问的方式,通过解引用找到变量,再改值)。

1.2.1 再进一步理解指针传参

通过上面的例子,应该对指针以及函数传参有更深的理解了。

那再来讨论一个问题,上面的代码中函数参数只是对求和结果使用指针的形式,那两个相加的数,使用可以使用指针,或是否有必要使用指针呢?

test4.c

// gcc test4.c -o test4
#include <stdio.h>

void sum(const int *a, const int *b, int *result)
{
    *result = *a + *b;
    
    printf("[%s] a:%p(%p)[*a:%d], b:%p(%p)[*b:%d], result:%p(%p)[*result:%d]n",
           __func__, a, &a, *a, b, &b, *b, result, &result, *result);
}

int main()
{
    int m = 1;
    int n = 2;
    int calc_result = 0;
    printf("[%s] m:%d(%p), m:%d(%p), calc_result:%d(%p)n", 
           __func__, m, &m, n, &n, calc_result, &calc_result);
    sum(&m, &n, &calc_result);
    printf("calc_result:%dn", calc_result);
    
    return0;
}

运行结果,依然的对的:

分析各变量的地址与数据传递关系:

两个相加的数,改为使用指针,对于内部求和逻辑来说,只是对求和的两个数据,从直接访问变为了间接访问(通过解引用获取到数据的值)而已,因此结果仍是正确的。

int值的加数改为指针,实际是没有必要的,函数参数都是要拷贝一份副本的,直接传int值,副本只需要4字节,改为传指针后,副本是指针类型,就需要8字节了。

1.3 一个可能出错的用法

继续来看,下面的代码是否有问题:

// gcc test5.c -o test5
#include <stdio.h>

void sum(int a, int b, int *result)
{
    int res = 0;
    res = a + b;
    printf("[%s] a:%d(%p), b:%d(%p), result:%p(%p), res:%d(%p)n",
           __func__, a, &a, b, &b, result, &result, res, &res);
    
    *result = res;
    
    printf("[%s] a:%d(%p), b:%d(%p), result:%p(%p)[*result:%d], res:%d(%p)n",
           __func__, a, &a, b, &b, result, &result, *result, res, &res);
}

int main()
{
    int m = 1;
    int n = 2;
    int *calc_result = NULL;
    printf("[%s] m:%d(%p), m:%d(%p), calc_result:%p(%p)n", 
           __func__, m, &m, n, &n, calc_result, &calc_result);
    sum(m, n, calc_result);
    printf("calc_result:%dn", *calc_result);
    
    return0;
}

虽然语法上没有什么问题,但这里就涉及到C指针容易遇到了一个坑——访问空地址!

运行结果,程序崩掉了:

仍然是画图,分析各变量的地址与数据传递关系:

可以看到,外部指针变量指向的空地址,并将这个空地址传递到了函数内部,函数内部通过解引用,操作空地址时,就会导致程序崩溃,因为不知道这个空地址指向哪里,在程序运行是就会表现为程序崩溃(Segmentation fault)。

2 关于取地址(&)与解引用(*)

上述示例代码,涉及到了取地址(&)与解引用(*)这两个概念。

字面意思上很好理解:

    取地址(&),就是取变量的地址,得到指针,也就是变量的存储位置解引用(*),就是根据指针,得到对应的值

可能容易迷惑的是,这两个操作,通过函数指针传参,在函数内部操作时,可能会迷惑,因为这实际涉及到了二级指针。

对于一个指针int *result

指针本身:是一个地址(指针变量是不带星号的,指针的类型是int *,即指向int类型数据的指针)

解引用:该地址指向的值

取地址:该地址在内存的位置,即指针变量本身的地址,或地址的地址,也就是二级指针

简单来说,对于指针作为函数参数,在函数内部操作时,通常情况只需要用到解引用(*),以及不带任何符号的指针本身。

而对指针在进行取地址(&),就是二级指针了,也就是指针本身这个8个字节(64位系统)数据的存储位置,通常情况是不需要关心这个地址的。

3 总结

本篇介绍了C指针的一些知识,通过实际的例子,来理解C指针的用法。

C语言将float拆分为4个hex传输与重组C语言打印数据的二进制格式-原理解析与编程实现C语言结构体对齐是怎么计算AES加密C代码实现C语言实现一个简易数据库

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录