ptr + n = (int)ptr + sizeof(type)*n //运算结果为同类指针的常量

数组名是数组第0个元素的地址常量

type ary[M] =

ptr = ary;

ptr[n] = (type 8const)((int)ptr + sizeof(type)*n)

ary[n] = (type 8const)((int)ary + sizeof(type)*n)

举例:

type *A = …;

type *B = …;

pA - pB = (int)pA - (int)pB / sizeof(type)

后续cpp有引用,类似于指针,但是更好用一点

void *pv

void * 在 C 和 C++ 语言中是一个非常重要的类型,它表示一个指向未知类型数据的指针。以下是一些 void * 使用的场景:

  1. 通用指针:由于 void * 可以指向任何类型的数据,它可以被用作一个通用指针,用于在不同数据类型指针之间进行转换。

  2. 内存操作:在执行内存分配(如使用 malloccalloc)和释放(如使用 free)时,返回的类型是 void *,这样就可以用来分配任何类型的内存。

  3. 函数参数:当函数需要处理不同类型的数据,但是又不希望为每种类型都定义一个函数时,可以使用 void * 作为函数参数,使得函数更加通用。

  4. 数据结构:在实现某些数据结构如通用链表或树时,节点可以包含 void * 指针,以便它们能够存储任何类型的数据。 尽管 void * 非常有用,但是使用它时需要注意以下几点:

  • 类型安全void * 不支持指针算术操作,因为它不指向一个具体的类型,所以在使用前通常需要将其转换回原始类型。

  • 转换:将 void * 转换为其他类型的指针时,需要确保转换是正确的,以避免潜在的错误和程序崩溃。

  • 性能考虑:在某些情况下,使用 void * 可能会导致性能损失,因为它可能会阻止编译器进行某些优化。 例如,以下是一个使用 void * 作为参数的函数,它简单地交换两个变量的值:

#include <stdio.h>
void swap(void *a, void *b, size_t size) {
    char *temp = malloc(size);
    if (temp == NULL) {
        perror("Failed to allocate memory");
        return;
    }
    memcpy(temp, a, size);
    memcpy(a, b, size);
    memcpy(b, temp, size);
    free(temp);
}
int main() {
    int x = 100, y = 200;
    printf("Before swap: x = %d, y = %d\n", x, y);
    swap(&x, &y, sizeof(int));
    printf("After swap: x = %d, y = %d\n", x, y);
    return 0;
}

在这个例子中,swap 函数能够交换任何类型的数据,只要传递正确的字节大小。这是 void * 通用性的一个体现。

#include <stdio.h>
#include <string.h>


char *foo(char szBuf[]) {
    printf("%d\r\n", strlen(szBuf));
    printf("%s\r\n", sizeof(szBuf));

    char szBuf2[20];
    strcpy(szBuf2, szBuf);
    printf("%d\r\n", strlen(szBuf2));
    printf("%s\r\n", sizeof(szBuf2));

    return szBuf2;

}
//猜猜每个printf输出什么。
int main() {
    char szBuf1[] = "Hello World";
    char *szBuf2 = "Hello World";

    printf("%d\n", strlen(szBuf1)); 
    printf("%d\n", strlen(szBuf2)); 
    printf("%d\n", sizeof(szBuf1));
    printf("%d\n", sizeof(szBuf2)); 
    printf("%d\n", sizeof("Hello world")); 
    char *psz = foo(szBuf1);

    puts(psz); //猜猜这个puts会输出什么?
    printf("%d\n", strlen(psz)); //guess
    printf("%d\n", sizeof(psz)); //guess
}

函数指针

函数指针是C/C++中的一种特殊指针,它指向函数,而不是变量。通过函数指针,你可以在程序运行时动态地调用不同的函数,这为代码带来了更多的灵活性。

1. 什么是函数指针?

函数指针就是保存某个函数地址的指针。因为函数在内存中也占有一块地址空间,所以我们可以将这个地址存储在一个指针变量中,并通过指针调用该函数。

2. 函数指针的定义

函数指针的声明格式有些特殊,形式如下:

return_type (*pointer_name)(parameter_list);

其中:

  • return_type 是函数的返回类型。

  • parameter_list 是函数的参数列表(类型和顺序要与指向的函数一致)。

  • pointer_name 是函数指针的名称。

例如,假设有一个返回 int 类型,并且接受两个 int 参数的函数,则它的函数指针可以这样定义:

int (*func_ptr)(int, int);

这个定义表示 func_ptr 是一个指向返回类型为 int 且参数类型为两个 int 的函数的指针。

3. 函数指针的使用

下面通过一个简单的例子说明如何定义和使用函数指针。

定义普通函数

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

定义函数指针并使用

#include <stdio.h>

// 定义两个函数
int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int main() {
    // 定义一个函数指针,指向具有两个int参数并返回int类型的函数
    int (*func_ptr)(int, int);

    // 让函数指针指向add函数
    func_ptr = add;
    printf("Result of add: %d\n", func_ptr(10, 5));  // 输出 15

    // 让函数指针指向subtract函数
    func_ptr = subtract;
    printf("Result of subtract: %d\n", func_ptr(10, 5));  // 输出 5

    return 0;
}

4. 函数指针作为参数

你可以将函数指针作为参数传递给另一个函数,这样可以让函数根据不同的逻辑调用不同的函数。例如:

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

// 操作函数,接收一个函数指针作为参数
int operation(int (*func)(int, int), int x, int y) {
    return func(x, y);
}

int main() {
    int result;

    result = operation(add, 10, 5);
    printf("Result of add: %d\n", result);  // 输出 15

    result = operation(subtract, 10, 5);
    printf("Result of subtract: %d\n", result);  // 输出 5

    return 0;
}

5. 函数指针数组

如果你有一组函数具有相同的参数和返回类型,你可以创建一个函数指针数组,将这些函数存储在数组中。这样你可以根据需要调用不同的函数。

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int multiply(int a, int b) {
    return a * b;
}

int main() {
    // 定义函数指针数组
    int (*func_ptrs[3])(int, int) = {add, subtract, multiply};

    // 调用数组中的不同函数
    printf("Add: %d\n", func_ptrs[0](10, 5));      // 输出 15
    printf("Subtract: %d\n", func_ptrs[1](10, 5)); // 输出 5
    printf("Multiply: %d\n", func_ptrs[2](10, 5)); // 输出 50

    return 0;
}

6. 回调函数与函数指针

函数指针常用在回调函数中。当你希望某个函数在某些事件发生时调用另一个函数,可以通过传递函数指针来实现。这种机制在事件驱动的编程和信号处理等场景下非常有用。

7. 注意事项

  • 函数指针指向的函数必须与指针的声明类型匹配,包括参数类型和返回值类型。

  • 函数指针的用途非常广泛,常用于回调机制、动态调用函数、多态实现等。

总结来说,函数指针赋予了程序更高的灵活性,尤其是在需要动态选择或改变函数的场景下,函数指针可以极大地简化代码结构。

数组与指针

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int main ()
{
  int ary[3][4] = {
    {1, 2, 3, 4},
    {10, 20, 30, 40},
    {100, 200, 300, 400}
  };

    //数组名是第0个元素的指针常量
    //二维数组的元素是一维数组
    //int ary[3][4]的元素是int[4]
    //ary是int[4]类型的指针常量

printf("ary = %p\r\n", ary);

//*ary得到int[4]的一维数组
//*ary得到int类型的指针常量
printf("*ary = %p\r\n", *ary);

//任何类型的变量取地址得到该类型的指针
//&ary取地址得到int[3][4]类型的指针
printf("&ary = %p\r\n", &ary);


printf("&ary[0] = %p\r\n", &ary[0]);
printf("ary[0] = %p\r\n", ary[0]);
printf("ary[0][0] = %p\r\n", ary[0][0]);


//ary是int[4]类型的指针常量
//ary + 1 ==> (int)ary + 1 * sizeof(int[4])
printf("ary + 1 =%p\r\n", ary + 1);
//*ary是int[4]类型的指针常量
//*ary + 1 ==> (int)*ary + 1 * sizeof(int)
printf("*ary + 1 = %p\r\n", *ary + 1);
//&ary取地址得到int[3][4]类型的指针常量
//&ary + 1 ==> (int)&ary + 1 * sizeof(int[3][4])
printf("&ary + 1 = %p\r\n", &ary + 1);

//二维数组的元素是一维数组
//ary[0]是int[4]数组
//数组名是第0个元素的指针常量
//int[4]是int类型的指针常量
printf("ary[0] + 1 = %p\r\n", ary[0] + 1); 

//指针加整形得到同类型的指针常量
//ary是 int[4]类型的指针常量
//ary + 1得到int[4]类型的指针常量
//*(ary + 1)得到一维数组int[4]
//数组名是第0个元素的指针常量
//*(ary + 1)是int类型的指针常量
//对int类型的指针常量做[1]运算得到int类型的指针常量
printf("(*(ary + 1))[1] = %p\r\n", (*(ary + 1))[1]);

}


int (*p)[4] = ary;
int (*pAry)[3][4] = &ary;

printf("%p\r\n", p);
printf("%p\r\n", *p);
printf("%p\r\n", pAry);
printf("%p\r\n", *pAry);
printf("%p\r\n", **pAry);

printf("%p\r\n", p + 1);
printf("%p\r\n", *p + 1);
printf("%p\r\n", pAry + 1);
printf("%p\r\n", *pAry + 1);
printf("%p\r\n", **pAry + 1);

int n = 6;
int *pN = &n;
int **pPN = &pN;

printf("%p\r\n", pPN);
printf("%p\r\n", *pPN);
printf("%p\r\n", **pPN);

解引用

解引用(Dereference)是计算机编程中处理指针(或引用)时的一个重要概念,通常指从指针中获取其指向的目标值。解引用通过访问指针存储的地址并读取该地址处的实际数据,从而间接操作数据。解引用操作允许程序通过指针修改和访问变量的内容。

在不同的编程语言中,解引用的操作和语法可能有所不同。下面分别介绍一些常见语言中的解引用操作:

1. C/C++ 中的解引用

在 C 和 C++ 中,指针是存储变量地址的变量。解引用指针可以访问或修改指针指向的内容。使用 * 操作符来解引用一个指针。

#include <stdio.h>

int main() {
    int x = 10;
    int *p = &x; // p 是指向 x 的指针
    printf("x = %d\n", *p); // 解引用 p,访问 x 的值
    *p = 20; // 通过解引用修改 x 的值
    printf("x = %d\n", x); // x 现在为 20
    return 0;
}
  • &x 获取变量 x 的地址,存储在指针 p 中。

  • *p 解引用指针 p,从而访问或修改 x 的值。

2. C++ 中的引用和解引用

C++ 引入了引用的概念,引用是变量的别名,解引用操作变得更加直观,不需要使用 * 操作符。

#include <iostream>

int main() {
    int x = 10;
    int &ref = x; // ref 是 x 的引用,直接操作 x
    std::cout << "x = " << ref << std::endl; // 访问 x 的值
    ref = 20; // 修改 x 的值
    std::cout << "x = " << x << std::endl; // x 现在为 20
    return 0;
}
  • int &ref = x; 表示 refx 的引用,ref 可以直接当作 x 来使用。

3. Rust 中的解引用

Rust 也使用 * 操作符进行解引用。Rust 提供了非常安全的内存管理,确保指针不会产生空指针解引用等问题。

fn main() {
    let x = 10;
    let p = &x; // p 是 x 的引用(指针)
    println!("x = {}", *p); // 解引用 p 访问 x 的值
}

Rust 强调所有权与借用的概念,&x 是一个不可变引用(类似于指针),而 *p 解引用引用 p 来访问 x 的值。

4. Python 中的解引用

在 Python 中,变量是引用类型,虽然没有显式的指针,但可以理解所有变量实际上都持有对象的引用。虽然 Python 没有直接的指针操作,但通过类似方式可以实现对象的间接访问。

x = [10]
p = x  # p 是对列表 x 的引用
print(p[0])  # 访问 x 中的值
p[0] = 20  # 修改 x 中的值
print(x[0])  # x 现在为 20

这里,px 都指向同一个列表,通过 p 可以修改 x 的内容,类似于解引用。

5. Java 中的解引用

Java 中没有显式的指针操作,但所有非基本类型变量都可以视为对象引用。对引用变量的访问和操作也可以理解为一种隐式的解引用。

class Main {
    public static void main(String[] args) {
        int[] x = {10};
        int[] p = x; // p 是对 x 的引用
        System.out.println(p[0]); // 访问 x 中的值
        p[0] = 20; // 修改 x 中的值
        System.out.println(x[0]); // x 现在为 20
    }
}

Java 中的引用变量 p 实际上指向了对象的内存地址,因此 p[0]x[0] 都是对相同内存地址的数据的访问。

6. 解引用中的常见错误

  • 空指针解引用(Null Pointer Dereference):试图解引用一个 null 或未初始化的指针会导致程序崩溃或未定义行为。

  • 悬空指针(Dangling Pointer):指针指向一个已经释放的内存地址,解引用悬空指针可能会导致不可预知的错误。

例如,C/C++ 中最常见的空指针解引用:

int *p = NULL;
printf("%d", *p);  // 错误:空指针解引用

解引用是通过指针或引用间接访问数据的过程。在现代编程语言中,解引用操作常见于指针操作、引用变量等场景。不同语言的解引用语法可能不同,但其核心概念是一致的,即通过存储的地址访问实际的数据。

二维数组和二级指针补充

二维数组与二级指针在 C 语言中是常见的概念,它们都涉及到指针,但本质上是两种不同的结构。下面我们详细讲解它们的区别、联系和如何使用。

一、二维数组

1. 二维数组的定义

二维数组是一种存储二维数据的数组,在 C 语言中常用于表示表格或矩阵。一个二维数组可以看作是一个数组的数组,具体来说,每一行都是一个一维数组。

例如,定义一个 2x3 的二维数组:

int array[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};

这个数组的存储布局是连续的。它存储的是 2 行 3 列的 int 值。在内存中,array 的元素按照行优先顺序存储,即:

| 1 | 2 | 3 | 4 | 5 | 6 |

2. 访问二维数组

访问二维数组的方式是通过行和列的索引来访问具体的元素。例如:

  • array[0][1] 访问第一行第二列的元素,结果为 2

  • array[1][2] 访问第二行第三列的元素,结果为 6

3. 二维数组的指针表示

在 C 语言中,数组的名字可以视作指向数组第一个元素的指针。对于二维数组来说,array 可以看作是指向第一行的指针:

  • array 是一个指向 array[0] 的指针,array[0] 是指向第一个一维数组的指针,即 array[0] 本质上是 int *

通过指针访问二维数组:

  • *arrayarray[0],即指向第一行的指针(int *)。

  • *(array + 1)array[1],即指向第二行的指针(int *)。

例如:

printf("%d\n", *(*(array + 1) + 2));  // 访问 array[1][2],结果为 6

二、二级指针(int **

1. 二级指针的定义

二级指针是指指向指针的指针。简单来说,二级指针是一个指向一级指针(例如 int *)的指针。可以通过二级指针访问一个指针所指向的内存地址。

例如,定义一个二级指针:

int a = 10;
int *p = &a;     // p 是一级指针,指向 a
int **pp = &p;   // pp 是二级指针,指向 p

这里:

  • p 是一级指针,指向 int 类型的变量 a,它存储了 a 的地址。

  • pp 是二级指针,指向一级指针 p,它存储了指针 p 的地址。

通过 pp 可以间接访问 a 的值:

printf("%d\n", **pp);  // 输出 a 的值,结果为 10

2. 二级指针的用途

二级指针最常见的用途是在函数中传递指针并修改原始指针的值。因为 C 语言中函数参数是按值传递的,如果需要在函数中改变一个指针的值(例如动态分配内存),就需要传递二级指针。

例如,使用二级指针动态分配二维数组:

#include <stdio.h>
#include <stdlib.h>

void allocateMemory(int ***array, int rows, int cols) {
    *array = (int **)malloc(rows * sizeof(int *));  // 分配指向每一行的指针数组
    for (int i = 0; i < rows; i++) {
        (*array)[i] = (int *)malloc(cols * sizeof(int));  // 为每一行分配内存
    }
}

int main() {
    int **array;
    allocateMemory(&array, 2, 3);  // 传递二级指针进行内存分配

    // 初始化二维数组
    array[0][0] = 1; array[0][1] = 2; array[0][2] = 3;
    array[1][0] = 4; array[1][1] = 5; array[1][2] = 6;

    // 打印数组
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", array[i][j]);
        }
        printf("\n");
    }

    // 释放内存
    for (int i = 0; i < 2; i++) {
        free(array[i]);
    }
    free(array);

    return 0;
}

三、二维数组与二级指针的联系和区别

1. 联系:

  • 访问方式:二维数组和二级指针在访问时的方式类似。你可以使用双重解引用 ** 来访问二维数组中的元素,或者通过指针偏移来访问数组元素。

  • 存储结构:无论是二维数组还是通过二级指针动态分配的数组,它们的内存布局可以是连续的(在静态数组的情况下)或非连续的(在动态分配的情况下)。

2. 区别:

  • 内存分配:二维数组的内存是静态分配的,编译器为其分配连续的内存空间。而二级指针可以通过 malloc 动态分配内存,每一行的内存地址可以是不连续的。

  • 声明方式:二维数组的声明如 int array[2][3],而二级指针需要声明为 int **,并且可以动态分配内存。

  • 访问元素:二维数组的元素可以直接通过 array[i][j] 访问,而二级指针的元素访问需要通过双重解引用 *(*(array + i) + j) 或等价的 array[i][j]

四、例子:静态二维数组和动态二级指针分配的比较

  1. 静态二维数组

int array[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};
  1. 动态分配的二维数组(使用二级指针):

int **array = (int **)malloc(2 * sizeof(int *)); // 动态分配2行
for (int i = 0; i < 2; i++) {
    array[i] = (int *)malloc(3 * sizeof(int));   // 每行动态分配3列
}

// 给数组赋值
array[0][0] = 1; array[0][1] = 2; array[0][2] = 3;
array[1][0] = 4; array[1][1] = 5; array[1][2] = 6;

五、总结

  • 二维数组是存储二维数据的数组,内存分配是静态的,存储布局是连续的。

  • 二级指针是指向指针的指针,可以用于动态分配二维数组的内存,内存布局可以是连续或不连续的。

  • 它们都可以用于表示矩阵或二维表格,但是在内存分配和访问方式上有所区别。