Skip to content
鼓励作者:欢迎打赏犒劳

指针

指针变量的定义和使用

text
#include <stdio.h>

int main() {

    // 定义一个int类型的变量,同时赋值为10
    int a = 10;
    // 打印变量的地址
    printf("&a = %p\n", &a);
    // 定义一个指针变量,int *保存int的地址
    // int *代表是一种数据类型,int *指针类型,p才是变量名
    int *p;
    // 指针指向谁,就把谁的地址赋值给这个指针变量
    p = &a;
    // 打印p, *p, p指向了a的地址,*p就是a的值
    printf("p = %p, *p = %d\n", p, *p);

    return 0;
}

通过指针间接修改变量的值

text
#include <stdio.h>

int main() {
    // 定义一个int类型变量a,同时赋值为0
    int a = 0;
    // 定义int *指针变量,同时赋值a的地址
    int *p = &a;
    // 通过指针间接修改a的值
    *p = 123;
    printf("a = %d\n", a);


    
    // 定义一个int类型变量b,同时赋值为5
    int b = 5;
    // p 保存 b的地址
    p = &b;
    // 通过指针间接修改b的值
    *p = 250;
    printf("b = %d\n", b);

    return 0;
}

结果

text
a = 123
b = 250

指针大小

  • 使用sizeof()测量指针的大小,得到的总是:4或8
  • sizeof()测的是指针变量指向存储地址的大小
    • 在32位平台,所有的指针(地址)都是32位(4字节)
    • 在64位平台,所有的指针(地址)都是64位(8字节)
text
#include <stdio.h>

int main() {
    int *p1;
    int **p2;
    char *p3;
    char **p4;

    // 所有 sizeof 的结果都应该是 size_t 类型,统一使用 %zu
    printf("sizeof(p1) = %zu\n", sizeof(p1));
    printf("sizeof(p2) = %zu\n", sizeof(p2));
    printf("sizeof(p3) = %zu\n", sizeof(p3));
    printf("sizeof(p4) = %zu\n", sizeof(p4));
    printf("sizeof(double *) = %zu\n", sizeof(double *));

    return 0;
}

指针步长

  • 指针步长指的是通过指针进行递增或递减操作时,指针所指向的内存地址相对于当前地址的偏移量。
  • 指针的步长取决于所指向的数据类型type。
    • 指针加n等于指针地址加上 n 个 sizeof(type) 的长度
    • 指针减n等于指针地址减去 n 个 sizeof(type) 的长度
text
#include <stdio.h>

int main() {
    char ch;
    char *p1 = &ch;
    printf("p1:%p, p1+1: %p\n", p1, p1 + 1); // 步长为1字节

    int a;
    int *p2 = &a;
    printf("p2:%p, p2+1: %p\n", p2, p2 + 1); // 步长为4字节

    double d;
    double *p3 = &d;
    printf("p3:%p, p3+1: %p\n", p3, p3 + 1); // 步长为8字节

    return 0;
}

指针步长的运用

指针步长机制是 C 语言高效处理连续内存数据的基础

数组遍历(最常用), 在 C 语言中,数组名本质上就是一个指向数组首元素的指针。当你使用 arr[i] 时,编译器底层将其转换为 *(arr + i)

  • 如果 arr 是 int 数组,arr + 1 会自动跳过 4 个字节,精准定位到第二个整数。
  • 如果没有步长机制,程序员必须手动计算 address + i * sizeof(int),这不仅繁琐而且容易出错
java
#include <stdio.h>

int main() {
    int nums[] = {10, 20, 30};
    int *p = nums; // p 指向数组第一个元素 (10)

    // 1. 打印指针 p 本身的地址
    printf("指针 p 的地址: %p\n", (void *)p);

    // 2. 打印指针 p 指向的值 (即 *p)
    printf("指针 p 指向的值: %d\n", *p);

    // 为了对比,我们可以再看看 p+1 的情况
    printf("\n--- 移动指针后 ---\n");
    printf("指针 p+1 的地址: %p\n", (void *)(p + 1));
    printf("指针 p+1 指向的值: %d\n", *(p + 1));

    return 0;
}

结果

text
指针 p 的地址: 000000821a9ffbbc
指针 p 指向的值: 10

--- 移动指针后 ---
指针 p+1 的地址: 000000821a9ffbc0
指针 p+1 指向的值: 20

野指针和空指针

c
#include <stdio.h>

int main() {
    int *p;
    p = 0x12345678; // 给指针变量p赋值,p为野指针, ok,不会有问题,但没有意义
    // *p = 1000;      // 操作野指针指向未知区域,内存出问题,err
    printf("111111111111111111\n");

    int *q = NULL;  // 空指针

    return 0;
}

多级指针

  • C语言允许有多级指针存在,在实际的程序中一级指针最常用,其次是二级指针。
  • 二级指针就是存储了【一级指针变量地址】的指针。
c
#include <stdio.h>

int main() {

    int a = 100;

    // 一级指针
    int* p1 = &a;
    printf("&a=%p\n", &a);
    printf("p1=%p\n", p1);
    printf("&p1=%p\n", &p1);

    // 二级指针,可以存储一级指针变量的地址
    int** p2 = &p1;
    printf("p2=%p\n", p2);
    printf("&p2=%p\n", &p2);

    // 三级指针,可以存储二级指针变量的地址
    int*** p3 = &p2;
    printf("p3=%p\n", p3);
    printf("&p3=%p\n", &p3);

    printf("---------------------\n");

    // 通过一级指针访问100,打印出来
    printf("*p1=%d\n", *p1);
    
    // 通过二级指针访问100,打印出来
    printf("**p2=%d\n", **p2);
    
    // 通过三级指针访问100,打印出来
    printf("***p3=%d\n", ***p3);
    
    return 0;
}

结果:

text
&a=00000048c3bffaec
p1=00000048c3bffaec
&p1=00000048c3bffae0
p2=00000048c3bffae0
&p2=00000048c3bffad8
p3=00000048c3bffad8
&p3=00000048c3bffad0
---------------------
*p1=100
**p2=100
***p3=100

函数指针

值递值

传值是指将参数的值拷贝一份传递给函数,函数内部对该参数的修改不会影响到原来的变量

c
// 函数参数传值,函数内部交换2个变量的值,验证函数外部的变量有没有改变
#include <stdio.h>

// 函数定义
void func(int m, int n) {
    // 函数内部交换2个变量的值
    int temp = m;
    m = n;
    n = temp;
    printf("函数内部 m = %d, n = %d\n", m, n);
}

int main() {
    int a = 10;
    int b = 20;
    // 调用函数,值传递
    func(a, b);
    printf("函数外部 a = %d, b = %d\n", a, b);

    return 0;
}

结果

text
函数内部 m = 20, n = 10
函数外部 a = 10, b = 20

引用传递

传址是指将参数的地址传递给函数,函数内部可以通过该地址来访问原变量,并对其进行修改。

c
// 函数参数传地址,函数内部交换2个指针指向内存的值,验证函数外部的变量有没有改变
#include <stdio.h>

// 函数定义
void func(int *m, int *n) {
    // 函数内部交换2个指针指向内存的值
    int temp = *m;
    *m = *n;
    *n = temp;
    printf("函数内部 *m = %d, *n = %d\n", *m, *n);
}

int main() {
    int a = 10;
    int b = 20;
    // 调用函数,地址传递
    func(&a, &b);
    printf("函数外部 a = %d, b = %d\n", a, b);

    return 0;
}

结果

text
函数内部 *m = 20, *n = 10
函数外部 a = 20, b = 10

函数指针

函数名

一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址

c
#include <stdio.h>

void func(int a) {
    printf("func a = %d\n", a);
}


int main() {
    // 函数名字,就是代表函数的入口地址,函数地址
    printf("%p, %p, %p\n", func, &func, *func);

    // 3种调用方法都可以
    func(1); // 最简便,最常用
    (&func)(2);
    (*func)(3);

    return 0;
}

结果

text
00007ff662d61591, 00007ff662d61591, 00007ff662d61591
func a = 1
func a = 2
func a = 3

函数指针

函数指针:它是指针,指向函数的指针

语法格式:返回值 (*函数指针变量)(形参列表); : 函数指针变量的定义,其中返回值、形参列表需要和指向的函数匹配

c
#include <stdio.h>

void func(int a) {
    printf("a = %d\n", a);
}

int main() {
    // 函数指针变量的定义,同时初始化
    void (*p1)(int a) = func;
    // 通过函数指针变量调用函数
    p1(10);

    // 先定义函数指针变量,后面再赋值
    void (*p2)(int);
    p2 = func;
    // 通过函数指针变量调用函数
    p2(20);

    return 0;
}

函数指针用作函数参数

  • 参数是函数指针:等同于定义了一个新的指针,指向用户传递的参数
  • 该函数指针:要求要保证其 函数返回值,函数参数 和用户传递的函数一致
  • int (*p)(int, int) = add
  • int (*p)(int, int) = sub
c
#include <stdio.h>

// 定义函数,函数指针做形参
int calc(int a, int b, int (*p)(int, int)){
// int a = 1, int b = 2, int (*p)(int, int) = add;
    // 通过函数指针变量调用函数,获取返回值
    int res = p(a, b);
    
    return res;
}

// 定义加法函数
int add(int x, int y) {
    return x + y;
}

// 定义减法函数
int sub(int x, int y) {
    return x - y;
}

int main() {
    int result;

    // 回调加法函数
    result = calc(1, 2, add);
    printf("result = %d\n", result);

    // 回调减法函数
    result = calc(10, 5, sub);
    printf("result = %d\n", result);

    return 0;
}

回调函数

有一些函数里未确定的操作,可以由用户传递的函数指针执行。

  • 函数指针变量做函数参数,这个函数指针变量指向的函数就是回调函数
  • 回调函数可以增加函数的通用性:在不改变原函数的前提下,通过回调函数扩展功能
c
#include <stdio.h>
#include <stdint.h>

/**
 * @brief 定义函数,对两个数进行运算
 * 在模板流程中,加入一些自定义的流程
 * @param m 第一个数
 * @param n 第二个数
 * @param callback 对结果继续展示
 * 
 * @return 运算的结果
 * 不要求马上理解, 了解即可
 */
void calc(int m, int n, void (*callback)(int)) {
// int m = 3, int n = 2, void (*callback)(int) = result_play_audio;
	printf("--------------------------\n");
	printf("准备计算资源!\n");
	printf("开始运算,哔哔哔!\n");
	// 通过函数指针变量调用函数,得到返回值
	int rst = m + n;
    // 执行回调函数
	callback(rst); // 把结果调回去了	
	
	printf("运算完成,播放语音,马达震动!\n");
	printf("回收资源!\n");
}

/**
 * @brief 回调函数
 */
void result_play_audio(int rst){
	// 屏幕,声音,wifi发送
	printf("计算结果, 播放声音:%d\n", rst);
}

/**
 * @brief 回调函数
 */
void result_play_screen(int rst){
	// 屏幕,声音,wifi发送
	printf("计算结果, 播放画面:%d\n", rst);
}

int main() {
//	add(3,2);
//	sub(3,2);
	// 通过回调函数,可以将扩展已有代码的功能
	calc(3, 2, result_play_audio);
	calc(3, 2, result_play_screen);
	return 0;
}

如有转载或 CV 的请标注本站原文地址