🤖 作者:包瑞清(richie bao): lastmod: 2024-11-22T21:16:48+08:00

指针是 C 和 C++ 中的一种变量,其值是另一个变量的内存地址,即指针是存储内存地址的变量,用于直接操作内存,并间接操作数据。指针为类型相关,如int*指向int类型变量的指针。通过指针访问变量的值,也可以进行地址计算。

操作与用法 C C++

指针的基本操作

&:为获取地址操作符(取地址);

*:即表示指针(变量),如int* p;;又为解引用操作符,通过指针访问或修改其所指向的值,如*p

  #include <stdio.h>

int main() {
	int a = 10; // 定义一个整数变量 a
	int* p = &a; // 定义一个指针变量 p,并指向 a 的地址
	printf("Value of a: %d\n", *p); // 通过指针访问 a 的值

	return 0;
}
  
  🡮
Value of a: 10
  
  #include <stdio.h>

int main() {
	int x = 10;
	int* p; // 声明一个指向 int 类型的指针
	p = &x; // p 存储 x 的值

	*p = 20; // 修改 x 值为 20

	printf("Value of x: %d\n", *p);

	return 0;
}
  
  🡮
Value of x: 20
  
  #include <stdio.h>

int main() {
	int var = 42;
	int* ptr = &var;

	printf("Address of var: %p\n", (void*)&var); // 输出变量的地址
	printf("Address stored in ptr: %p\n", (void*)ptr); // 打印存储在指针中的地址
	printf("Value at the address stored in ptr: %d\n", *ptr); ///打印存储在指针地址处的值
	
	return 0;
}
  
  🡮
Address of var: 0000001BC06FF644
Address stored in ptr: 0000001BC06FF644
Value at the address stored in ptr: 42
  
  #include <iostream>
#include <format>

int main() {
    int a = 10; // 定义一个整数变量
    int* p = &a; // 定义一个指针变量,并指向 a 的地址
    std::cout << std::format("Value of a: {}\n", * p); // 通过指针访问 a 的值

    return 0;
}
  
  🡮
Value of a: 10
  
  #include <iostream>
#include <format>

int main() {
    int x = 10;
    int* p; // 声明一个指向 int 类型的指针

    p = &x; // p 存储 x 的值
    *p = 20; // 修改 x 值为 20

    std::cout << std::format("Value of x: {}\n", * p);

    return 0;
}
  
  🡮
Value of x: 20
  
  #include <iostream>
#include <format>

int main() {
    int var = 42;
    int* ptr = &var;

    std::cout << std::format("Address of var: {}\n", (void*)&var); // 输出变量的地址
    std::cout << std::format("Address stored in ptr: {}\n", (void*)ptr); // 输出变量的地址
    std::cout << std::format("Value at the address stored in ptr: {}\n", *ptr);  ///打印存储在指针地址处的值

    return 0;
}
  
  🡮
Address of var: 0x56a7b5f944
Address stored in ptr: 0x56a7b5f944
Value at the address stored in ptr: 42
  

指针与数组

  • 指针与一维数组

数组名实际上是一个常量指针,指向数组的第一个元素。

  • 指针与多维数组

一个多维数组可以看作是数组的数组,例如int a[3][4]表示一个包含3个元素的一维数组,而每个元素又是一个包含4个整数的数组。在多维数组中,a[i][j]的地址可以用&a[i][i]表示;a是一个指向a[0]的指针,a[0]是一个指向数组的指针(即指向int[4]的指针)。a的类型是int(*)[4],表示一个指向包含4个整数数组的指针;a+i表示第i行的地址,其类型仍然是int(*)[4]*(a+i)等价于a[i],是一个int[4]数组;*(*(a+i)+j)等价于a[i][j]

  • 用一维指针访问多维数组

可以通过将多维数组看作一块连续的内存,使用一维指针访问。

  • 指针与一维数组
  #include <stdio.h>

int main() {
	int arr[5] = { 1,4,9,16,25 };
	int* p = arr; // 等价于 int *p = &arr[0];

	printf("%d\n", *p);
	p += 1;

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

	return 0;
}
  
  🡮
1
4
9
  
  • 指针与多维数组
  #include <stdio.h>

int main() {
	int a[3][4] = {
		{1,2,3,4},
		{5,6,7,8},
		{9,10,11,12}
	};

    // 用指针访问
	int (*p)[4] = a; // 指向 int[4]的指针

	printf("a[1][2] = %d\n", *(*(p + 1) + 2));

    // 遍历多维数组
	for (int i = 0;i < 3;i++) {
		for (int j = 0; j < 4; j++) {
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}

	return 0;
}
  
  🡮
a[1][2] = 7
1 2 3 4
5 6 7 8
9 10 11 12
  

🤖 代码解释

      int (*p)[4] = a; 
  

声明指针并指向二维数组。定义一个指针p,指向int[4](一维数组,长度为4);即将p指向二维数组a的起始地址。二维地址的首地址实际上是第一个一维数组的地址。

      printf("a[1][2] = %d\n", *(*(p + 1) + 2)); 
  

声明指针并指向二维数组。表达式分解:p + 1p指向二维数组的第0行,加1后指向第1行({5,6,7,8});*(p+1)为解引用,的到第1行(一个一维数组的地址,等同于a[1]);*(p + 1)+2则将第1行的地址向后偏移2个元素,指向a[1][2](值为7);*(*(p + 1) + 2)为解引用,取值,结果为7。

      for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            printf("%d ", *(*(p + i) + j));
        }
        printf("\n");
    }
  

遍历二维数组。外层循环for (int i = 0; i < 3; i++)为遍历二维数组的每一行;内层循环for (int j = 0; j < 4; j++)为遍历当前行的每一列;*(*(p + i) + j)中,p + i为指针p指向第i行(一个一维数组的地址);*(p + i)解引用,得到第i行数组;*(*(p + i) + j)是将第i行的起始地址偏移j个元素;*(*(p + i) + j)解引用,取第i行第j列的值。

  • 用一维指针访问多维数组
  #include <stdio.h>

int main() {
	int a[3][4] = {
		{1,2,3,4},
		{5,6,7,8},
		{9,10,11,12}
	};

    // 将二维数组当作一维数组处理
	int *p = &a[0][0];

    // 按一维方式访问元素
	for (int i = 0; i < 12; i++) {
		printf("%d ", *(p + i));
	}
	printf("\n");

	return 0;
}
  
  🡮
1 2 3 4 5 6 7 8 9 10 11 12
  
  • 指针与一维数组
  #include <iostream>

int main() {
	int arr[5] = { 1, 4, 9, 16, 25 };
	int* p = arr; // 数组名即为指向第一个元素的指针

	std::cout << *p << std::endl;

	p += 1;
	std::cout << *p << std::endl;
	std::cout << *(p + 1) << std::endl;

	return 0;
}
  
  🡮
1
4
9
  
  • 指针与多维数组
  #include <iostream>

int main() {
    int a[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
    };

    // 使用指针访问
    int (*p)[4] = a; // 指向包含 4 个整数数组的指针
    std::cout << "a[1][2] = " << *(*(p + 1) + 2) << std::endl; 

    // 遍历多维数组
    for (int i = 0;i < 3;++i) {
        for (int j = 0;j < 4;++j) {
            std::cout << *(*(p + i) + j) << " ";
        }
        std::cout << std::endl;
    }

	return 0;
}
  
  🡮
a[1][2] = 7
1 2 3 4
5 6 7 8
9 10 11 12
  

🤖 代码解释

      int (*p)[4] = a;
  

声明指针并指向二维数组。定义一个指针p,指向一个包含 4 个整数的数组(int[4]);即p指向二维数组a的起始地址(二维数组的首地址等同于第0行的起始地址)。

      std::cout << "a[1][2] = " << *(*(p + 1) + 2) << std::endl;
  

使用指针访问二维数组的元素。表达式分解:p + 1中指针p从第0行移动到第1行,指向{5,6,7,8}*(p + 1)为解引用,得到第1行的起始地址(等价于a[1]);*(p + 1) + 2为在第1行的基础上偏移2个元素,指向a[1][2](值为7);*(*(P + 1) + 2)再次解引用,取出指针指向的值,即7

      for (int i = 0; i < 3; ++i) {
        for (int j = 0; j < 4; ++j) {
            std::cout << *(*(p + i) + j) << " ";
        }
        std::cout << std::endl;
    }
  

遍历二维数组。外层循环for (int i = 0; i < 3; ++i)遍历二维数组的每一行(i表示行号);内层循环for (int j = 0; j < 4; ++j)遍历当前行的每一列(j表示列号)。通过指针访问二维数组,*(p + i)指向第i行(等价于a[i]);*(p + i) + j 在第i行上偏移j个元素,指向a[i][j]*(*(p + i) + j)解引用,取出值。

  • 用一维指针访问多维数组
  #include <iostream>

int main() {
    int a[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
    };

    // 将二维数组的首地址赋值给一维指针
    int* p = &a[0][0];

    // 按一维方式访问二维数组
    for (int i = 0;i < 12;++i) {
        std::cout << *(p + i) << " ";
    }
    std::cout << std::endl;

	return 0;
}
  
  🡮
1 2 3 4 5 6 7 8 9 10 11 12
  

指针与函数

  • 指针作为函数参数

使用指针可以实现函数参数的传址调用。

🤖 代码解释

  void swap(int *x, int *y) {
  

函数的定义。定义一个函数swap,用于交换两个整数变量的值。参数int *xint *y分别指向一个整数的指针,为其地址。void表示该函数没有返回值。

      int temp = *x;
  

临时变量保存值。定义一个临时变量temp,将x所指向的值存储到temp中;*x是解引用操作,获取指针x指向内存地址中存储的值。

      *x = *y;
  

y指针指向地址(变量)的值赋值给x指针指向地址(变量)的值。*y为解引用,获取其指向地址的值;*x通过解引用操作,访问x指针所指向的变量,并修改x指向内存地址(变量)所存储的值。

      *y = temp;
  

将临时变量的值赋值给x指针指向地址(变量)的值,完成两个整数变量值的交换。

  • 指针作为函数返回值

C/C++ 中,指针可以作为函数的返回值,允许函数返回一个内存地址,以便对该地址进行操作。

  • 指针作为函数参数
  #include <stdio.h>

void swap(int* x, int* y) {
	int temp = *x;
	*x = *y;
	*y = temp;
}

int main() {
	int a = 10, b = 20;
	swap(&a, &b); // 交换 a 和 b 的值
	printf("a = %d; b = %d", a, b);

	return 0;
}
  
  🡮
a = 20; b = 10
  
  • 指针作为函数返回值

在 C 中, 最常见的做法是返回指向局部静态变量或动态分配内存的指针。需要特别小心避免返回指向局部变量的指针,因为局部变量的生命周期在函数返回后结束。

返回指向静态变量的指针。示例中num是静态变量,在函数调用外仍然存在,因此返回的指针是有效的。

  #include <stdio.h>

int* getPointerToStaticVariable() {
	static int num = 10; // 静态变量,生命周期直到程序结束
	return &num;
}

int main() {
	int* ptr = getPointerToStaticVariable();
	printf("Value: %d\n", *ptr);

	return 0;
}
  
  🡮
Value: 10
  

返回指向动态分配内存的指针。

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

int* createArray(int size) {
    int* arr = (int*)malloc(size * sizeof(int));
    if (arr == NULL) {
        printf("Memory allocation failed!\n");
        return NULL;
    }
    for (int i = 0; i < size; i++) {
        arr[i] = i;
    }
    return arr;
}

int main() {
    int* arr = createArray(5);
    if (arr != NULL) {
        for (int i = 0; i < 5; i++) {
            printf("%d ", arr[i]);
        }
        printf("\n");
        free(arr);  // 释放动态内存
    }
    return 0;
}
  
  🡮
0 1 2 3 4
  

🤖 代码解释

  int* arr = (int*) malloc(size * sizeof(int));
  
  1. int* arrarr是一个指向整数的指针(int*),为用于存储一个整数数组的起始地址。动态内存分配后,arr会指向malloc返回的内存块起始地址。
  2. (int*)是强制类型转换。malloc会返回一个void*类型的指针(通用指针),可以指向任何类型的内存块。在 C 中,void*可以被隐式转换为其他类型的指针,因此(int*)强制类型转换是可选的。
  3. malloc(size * sizeof(int))为动态分配指定字节数的内存块,返回该内存块的起始地址。其中参数size * sizeof(int)表示要分配的内存大小(以字节为单位)。size是一个变量,表示需要的数组元素个数。sizeof(int)是一个运算符,用来计算int类型的大小(以字节为单位)。
  4. 如果分配成功,malloc返回分配的内存块的起始地址;如果分配失败(例如内存不足),malloc返回NULL
  • 指针作为函数参数
  #include <iostream>
#include <format>

void swap(int* x, int* y) {
	int temp = *x;
	*x = *y;
	*y = temp;
}

int main() {
	int a = 10, b = 20;
	swap(&a, &b); // 交换 a 和 b 的值
	std::cout << std::format("a = {}; b = {}\n", a, b);

	return 0;
}
  
  🡮
a = 20; b = 10
  
  • 指针作为函数返回值

C++ 允许与 C 相同的指针操作,同时提供更多的功能,如智能指针(std::unique_ptrstd::shared_ptr),可以帮助避免内存泄漏。

返回普通指针。在 C++ 中,new用于动态内存分配,delete[]用于释放内存。

  #include <iostream>
#include <format>

int* createArray(int size) {
	int* arr = new int[size]; // 动态分配内存
	for (int i = 0;i < size;i++) {
		arr[i] = i;
	}
	return arr;
}

int main() {
	int* arr = createArray(5);
	for (int i = 0;i < 5;i++) {
		std::cout << std::format("{} ", arr[i]);
	}
	std::cout << std::endl;
	delete[] arr; // 释放动态分配的内存

	return 0;
}
  
  🡮
0 1 2 3 4
  

返回智能指针。std::unique_ptrstd::shared_ptr会自动管理内存的释放,因此可以避免内存泄漏。

  #include <iostream>
#include <format>

std::unique_ptr<int[]> createArray(int size) {
	std::unique_ptr<int[]> arr = std::make_unique<int[]>(size);
	for (int i = 0;i < size;i++) {
		arr[i] = i;
	}
	return arr;
}

int main() {
	std::unique_ptr<int[]> arr = createArray(5);
	for (int i = 0;i < 5;i++) {
		std::cout << std::format("{} ", arr[i]);
	}
	std::cout << std::endl;

    // 不需要手动调用 delete, 智能指针会自动释放内存
	return 0;
}
  
  🡮
0 1 2 3 4
  

🤖 代码解释

  std::unique_ptr<int[]> createArray(int size)
  
  1. std::unique_ptr<int[]>为定义函数createArray的返回类型,表示一个管理动态分配的整数数组的智能指针。std::unique_ptr是 C++11 引入的智能指针,确保动态内存在使用完成后自动释放,防止内存泄漏。模板参数int[]指定这是一个动态分配的整数数组。
  2. 参数int size,指定数组的大小,即需要分配的整数个数。
  std::unique_ptr<int[]> arr = std::make_unique<int[]>(size);
  
  1. std::make_unique<int[]>(size)是一个用于创建智能指针的函数模板,其动态分配一个大小为size的整数数组,并返回一个std::unique_ptr<int[]>,指向这个数组的起始地址。使用std::make_unique比直接调用new更安全,能够避免内存泄漏。
  2. std::unique_ptr<int[]> arr中,arr是一个std::unique_ptr类型的变量,负责管理分配的整数数组。当arr离开作用域时,指针会自动释放所分配的内存,无需显示调用delete[]

指针与结构体

  • 通过指针访问结构体

定义结构体后,可以通过指针访问和操作其所指向的成员。

  #include <stdio.h>

// 定义结构体
struct Point {
    int x;
    int y;
};


int main() {
    struct Point p = { 10,20 }; // 定义和初始化结构体变量
    struct Point* ptr = &p; // 定义指向结构体的指针

    // 通过指针访问结构体成员
    printf("x: %d, y: %d\n", ptr->x, ptr->y);  // 使用`->`操作符,等价于(*ptr).x
    ptr->x = 30; // 修改结构体成员
    printf("Modified x: %d\n", ptr->x);

    return 0;
}
  
  🡮
x: 10, y: 20
Modified x: 30
  
  • 动态分配结构体内存

使用malloc动态分配内存。通过free释放动态分配的内存,防止内存泄漏。

  #include <stdio.h>
#include <stdlib.h> // 包含 malloc 和 free 函数

struct Point {
    int x;
    int y;
};


int main() {
    // 动态分配内存
    struct Point* p = (struct Point*)malloc(sizeof(struct Point));

    if (p == NULL) {
        printf("Memory allocation failed\n");
        return 1;
    }

    // 使用动态分配的结构体
    p->x = 10;
    p->y = 20;
    printf("x: %d, y: %d\n", p->x, p->y);

    free(p); // 释放内存

    return 0;
}
  
  🡮
x: 10, y: 20
  
  • 动态分配结构体内存

在 C++ 中,用newdelete替代 C 中的mallocfree

  #include <iostream>
#include <format>

struct Point {
	int x;
	int y;
};

int main() {
	Point* p = new Point; // 动态分配结构体
	p->x = 10;
	p->y = 20;
	std::cout << std::format("x: {}, y: {}", p->x, p->y);

	delete p; // 释放内存

	return 0;
}
  
  🡮
x: 10, y: 20
  
  • 使用智能指针

用智能指针(如std::uniuque_ptrstd::shared_ptr)自动管理内存,避免手动释放。

  #include <iostream>
#include <format>

struct Point {
	int x;
	int y;
};

int main() {
    // 使用 unique_ptr 管理结构体
	std::unique_ptr<Point> p = std::make_unique<Point>();
	p->x = 10;
	p->y = 20;
	std::cout << std::format("x: {}, y: {}\n", p->x, p->y);

    // 无需手动 delete, uniuqe_ptr 自动释放内存
	return 0;
}
  
  🡮
x: 10, y: 20
  

函数指针

函数指针是一种指针类型变量,其存储的是函数的地址。通过函数指针可以间接调用函数。其用途包括回调函数,允许将函数作为参数传递,实现动态行为;动态函数选择,在运行时选择调用不同的函数;策略模式,指针可以指向多种实现,动态切换逻辑;事件处理,在图形用户界面(graphical user interface, GUI) 等编程中,用于事件绑定和触发;动态加载,使用指针绑定动态链接库中的函数。声明函数指针的基本语法为ReturnType (*PointerVariableName)(ParemeterTypeList);,初始化函数指针的语法为PointerVariableName = FunctionName;

  • 基本用法
  #include <stdio.h>

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

int main() {
    // 声明一个函数指针,指向返回类型 int, 接收两个 int 类型的函数参数
    int (*func_ptr)(int, int);

    // 将函数地址赋值给指针(函数名即为函数地址)
    func_ptr = add;

    // 通过函数指针调用函数
    printf("Result: %d\n", func_ptr(5, 3));

    return 0;
}
  
  🡮
Result: 8
  
  • 回调函数
  #include <stdio.h>

void execute_callback(void (*callback)(void)) {
    // 调用回调函数
    callback();
}

void say_hello() {
    printf("Hello, World!\n");
}

int main() {
    execute_callback(say_hello); // 传递函数指针

    return 0;
}
  
  🡮
Hello, World!
  
  • 基本用法
  #include <iostream>
#include <format>

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

int main() {
    // 声明并初始化函数指针
	int (*func_ptr)(int, int) = multiply;

    // 通过函数指针调用函数
	std::cout << std::format("Result: {}\n", func_ptr(3, 5));

	return 0;
}
  
  🡮
Result: 15
  
  • 指向成员函数的指针

类成员函数的调用需要通过对象调用。

  #include <iostream>
#include <format>

class Calculator {
public:
	int add(int a, int b) { return a + b; }
	int multiply(int a, int b) { return a * b; }
};

int main() {
	Calculator calc;

    // 声明一个指向成员函数的指针
	int (Calculator:: * func_ptr)(int, int);

    // 指向 add 函数
	func_ptr = &Calculator::add;
	std::cout << std::format("Add: {}\n", (calc.*func_ptr)(3, 4));

    // 指向 multiply 函数
	func_ptr = &Calculator::multiply;
	std::cout << std::format("Multiply: {}\n", (calc.*func_ptr)(3, 4));

	return 0;
}
  
  🡮
Add: 7
Multiply: 12
  

指针数组

指针数组是一个数组,其中每一个元素都是一个指针,指向其他变量或对象的地址。常用应用包括字符串管理,用于管理字符串集合;动态数据结构,用于管理动态分配的内存块;多态性,存储不同派生类对象的指针;多维数组,通过灵活的指针操作模拟多维数组;回调机制,管理回调函数的指针。指针数组的基本语法为Type* arrayName[size],其中Type* 表示数组中的每个元素都是指向Type的指针;size为数组中指针的数量。可以用arrayName[index]获取特定索引的指针;通过*arrayName[index]解引用访问指针指向的值。

  • 存储变量地址的指针数组
  #include <stdio.h>

int main() {

    int a = 10, b = 20, c = 30;
    int* ptrArray[3]; // 声明一个包含 3 个 int 指针的数组

    // 将变量的地址存储到指针数组中
    ptrArray[0] = &a;
    ptrArray[1] = &b;
    ptrArray[2] = &c;

    // 通过指针数组访问变量的值
    for (int i = 0;i < 3;i++) {
        printf("Value at ptrArray[%d]: %d\n", i, *ptrArray[i]);
    }

    return 0;
}
  
  🡮
Value at ptrArray[0]: 10
Value at ptrArray[1]: 20
Value at ptrArray[2]: 30
  
  • 存储字符串的指针数组

在 C 中,字符串是字符数组,指针数组可以用来存储多个字符串。

  #include <stdio.h>

int main() {
    // 声明一个指针数组用于存储字符串
    const char* strArray[] = { "Hello","World","C programming" };

    // 访问并打印字符串
    for (int i = 0;i < 3;i++) {
        printf("String %d: %s\n", i + 1, strArray[i]);
    }

    return 0;
}
  
  🡮
String 1: Hello
String 2: World
String 3: C programming
  
  • 存储动态分配整数的指针数组

示例代码使用一个指针数组管理动态分配的内存;将动态分配的整数存储到数组中;打印动态分配的整数;最后释放内存,避免内存泄漏。

  #include <iostream>
#include <format>


int main() {
	int* ptrArray[3]; // 声明一个包含 3 个 int 指针的数组

    // 动态分配内存并存储到数组中
	for (int i = 0;i < 3;i++) {
		ptrArray[i] = new int(i + 1); // 分配内存并赋值
	}

    // 通过指针数组访问值
	for (int i = 0;i < 3;i++) {
		std::cout << std::format("Value at ptrArray[{}]: {}\n", i, *ptrArray[i]);
	}

    // 释放动态分配的内存
	for (int i = 0;i < 3;i++) {
		delete ptrArray[i];
	}

	return 0;
}
  
  🡮
Value at ptrArray[0]: 1
Value at ptrArray[1]: 2
Value at ptrArray[2]: 3
  
  • 存储对象指针的指针数组

在 C++ 中,指针数组可以存储对象的指针。

  #include <iostream>
#include <format>

class Animal {
public:
	virtual void speak() const {
		std::cout << "Animal speaks" << std::endl;
	}
};

class Dog :public Animal {
public:
	void speak()const override {
		std::cout << "Dog barks" << std::endl;
	}
};

class Cat : public Animal {
public:
	void speak()const override {
		std::cout << "Cat meows" << std::endl;
	}
};


int main() {
	Animal* animals[2]; // 声明一个指针数组用于存储 Animal 对象的指针

    // 存储派生类对象的指针
	animals[0] = new Dog();
	animals[1] = new Cat();

    // 通过数组访问对象
	for (int i = 0;i < 2;i++) {
		animals[i]->speak();
	}

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

	return 0;
}
  
  🡮
Dog barks
Cat meows
  

多级指针

在 C/C++ 中,多级指针(即指向指针的指针)是指一个指针指向另一个指针,这样可以在内存中间接引用数据。多级指针常用于处理二维数组、动态分配的多维数组及需要复杂内存管理的场景。

  • 一级指针(普通指针)

一级指针是指向某个数据类型的指针,例如, int* ptr是一个指向int类型变量的指针。

  int a = 10;
int* ptr = &a;
  
  • 二级指针(指向指针的指针)

二级指针是指向一级指针的指针,即一个指针保存另一个指针的地址。常见于处理指针数组或动态分配内存时。

  int a = 10;
int* ptr1 = &a; // 一级指针
int** ptr2 = &ptr1; // 二级指针

printf("%d\n", **ptr2); // 输出 10
  

上述代码中,ptr2是一个指向ptr1的指针,而ptr1是指向a的指针。**ptr2表示解引用ptr2两次,最终获得a的值。

  • 三级指针(指向二级指针的指针)

三级指针是指向二级指针的指针,可以用于更复杂的数据结构,如动态二维数组。

  int a = 10;
int* ptr1 = &a;
int** ptr2 = &ptr1;
int*** ptr3 = &ptr2;

printf("%d\n", ***ptr3); // 输出 10
  
  • 二维数组的多级指针

示例为使用二级指针int** arr来处理动态分配的二维数组。首先分配一个指向行的指针数组,然后为每一个行分配一个int类型的数组,最后使用数组并释放内存。

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

int main() {
	int rows = 3, cols = 4;
	int** arr = (int**)malloc(rows * sizeof(int*));

	for (int i = 0;i < rows;i++) {
		arr[i] = (int*)malloc(cols * sizeof(int));
	}

	for (int i = 0;i < rows;i++) {
		for (int j = 0;j < cols;j++) {
			arr[i][j] = i * cols + j;
		}
	}

	for (int i = 0;i < rows;i++) {
		for (int j = 0;j < cols;j++) {
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}

	for (int i = 0; i < rows;i++) {
		free(arr[i]);
	}
	free(arr);

	return 0;
}
  
  🡮
0 1 2 3
4 5 6 7
8 9 10 11
  

🤖 代码解释

      int** arr = (int**)malloc(rows * sizeof(int*));
  
  • 使用malloc动态分配一个指针数组arr, 其包含rowsint*(指向整型数组的指针)。sizeof(int*)表示每个指针的大小,rows * sizeof(int*)是分配的内存总大小。这里分配了一个包含 3 个指针的数组,每个指针将指向一行。
      for (int i = 0; i < rows; i++) {
        arr[i] = (int*)malloc(cols * sizeof(int));
    }
  
  • 遍历每一行(共 3 行)。arr[i] = (int*)malloc(cols * sizeof(int));为每一行动态分配存储cols个整数的内存空间。每个arr[i]是指向整型数组的指针,大小为cols * sizeof(int),即每行有4个整数。
  • 二维数组的多级指针

指针数组可以用于模拟多维数组,通过数组中的每个指针指向不同的数组。

  #include <iostream>

int main() {
	int rows = 2, cols = 3;

    // 创建一个指针数组用于存储行
	int** matrix = new int* [rows];

    // 为每一行分配内存
	for (int i = 0;i < rows;i++) {
		matrix[i] = new int[cols];
		for (int j = 0;j < cols;j++) {
			matrix[i][j] = i * cols + j + 1; // 赋值
		}
	}

    // 打印二维数组
	for (int i = 0;i < rows;i++) {
		for (int j = 0;j < cols;j++) {
			std::cout << matrix[i][j] << " ";
		}
		std::cout << std::endl;
	}

    // 释放内存
	for (int i = 0;i < rows;i++) {
		delete[] matrix[i];
	}
	delete[] matrix;


	return 0;
}
  
  🡮
1 2 3
4 5 6
  

🤖 代码解释

      int** matrix = new int* [rows];
  
  • 动态分配一个指针数组matrix,包含rowsint*(指向整型数组的指针),可以理解为分配了存储行指针的空间。
      for (int i = 0; i < rows; i++) {
        matrix[i] = new int[cols]; // 为每一行分配存储 cols 个整数的内存
        for (int j = 0; j < cols; j++) {
            matrix[i][j] = i * cols + j + 1; // 初始化每个元素
        }
    }
  
  1. 外层循环遍历每一行。matrix[i] = new int[cols];为每一个行分配存储cols个整数的连续内存。
  2. 内层循环遍历当前行的每一列。matrix[i][j] = i * cols + j + 1;为按顺序给矩阵的每个元素赋值。

空指针和悬空指针

在 C/C++ 中,空指针(Null Pointer)和悬空指针(Dangling Pointer)是两种不同的指针状态,对其处理直接关系到程序的安全性和稳定性。

  • 空指针(Null Pointer)

空指针是指指针没有指向任何有效的内存位置,通常用于初始化指针变量,表示该指针不指向任何对象。空指针的值通常为NULL,或者在 C++11 中可以使用nullptr(推荐写法)。

C 风格空指针 C++11 引入的空指针
int* ptr = NULL; int* ptr = nullptr;
在 C 中,NULL是一个宏,通常被定义为((void*)0),表示指针不指向任何有效的地址 nullptr为类型安全的空指针

检查空指针,可以使用[C]if (ptr == NULL){}和[C++]if (ptr == nullptr){}

空指针的主要用途是在程序中检测和初始化指针,以确保其不会指向不确定的内存区域,如用于指针初始化;用于表示函数返回的“无效”值;及在某些函数中检查指针是否有效等。

  • 悬空指针(Dangling Pointer)

悬空指针是指指向一个已被释放或不再有效的内存地址的指针。通常情况下,悬空指针是因为对象已经被销毁或内存已经被释放,但指针仍然指向旧地址。悬空指针的访问会导致未定义行为,可能会引起程序崩溃或错误的数据访问。悬空指针产生的原因有释放内存后仍然访问指向该内存的指针;指向局部变量的指针在函数返回后仍然有效(局部变量已销毁);指向已删除对象的指针。

  int* ptr = (int*)malloc(sizeof(int)); // 动态分配内存
*ptr = 42;
free(ptr);  // 释放内存

// 此时 ptr 是一个悬空指针,访问它是未定义的行为
*ptr = 10;  // 访问悬空指针,导致错误
  

上述示例,ptr指向的内存被free释放后,ptr变成了悬空指针。如果继续访问,就会发生未定义的行为。为了避免悬空指针,可以将指针设置为NULLnullptr

  free(ptr);
ptr = nullptr; // 使 ptr 不在悬空
  

在对象的析构函数中处理指针的释放和设为空。

  class MyClass {
private:
	int* ptr;
public:
	MyClass() { ptr = new int; }
	~MyClass() { delete ptr; ptr = nullptr; }

};
  

使用智能指针来自动管理内存,从而避免悬空指针问题。

  std::unique_ptr<int> ptr = std::make_unique<int>(42);
  

空指针和悬空指针区别如表,

特性 空指针(Null Pointer) 悬空指针(Dangling Pointer)
定义 不指向任何有效内存地址,通常为NULLnullptr 指向已释放或无效的内存地址
产生原因 初始化指针,指示指针未分配内存 在释放内存后指针没有被置为NULL,导致指向无效内存
使用后果 不访问空指针,避免崩溃 访问悬空指针会导致程序崩溃或未定义行为
解决办法 检查指针是否为nullptrNULL 释放内存后将指针设置为nullptr