跳转至

指针

指针变量, 简称指针, 用来存放其它变量的内存地址. 通过指针, 可以直接访问系统内存, 从而提高程序执行效率。

定义

类型标识符 * 指针变量名

  1. 类型标识符: 表示该指针可指向的对象的数据类型, 即该指针能存放哪类数据的地址.
  2. 星号和指针变量名之间可以没有空格

初始化

指针可以通过取地址运算符 & 获取变量的地址进行初始化:

int num = 10;
int *ptr = #       // 正确:指向已存在的变量
int *ptr2 = NULL;      // 安全初始化:空指针
// int *ptr3;         // 危险:野指针(随机地址)

通过解引用运算符 * 可以访问指针指向的内存中的值:

printf("%d", *ptr); // 输出变量 a 的值

指针运算

操作 说明 示例
指针加减 按数据类型大小移动地址(ptr + n → 地址增加 n*sizeof(类型) int arr[3]; int *p = arr; p++;
指针比较 比较两个指针的地址位置 if (p1 < p2) {...}
指针相减 计算两个指针之间的元素个数 int diff = p2 - p1;
  1. 指针加上或减去一个整型数值 k,等效于获得一个新指针,该指针指向原来的元素之后或之 前的第 k 个元素
  2. 指针加上或减去一个整型数值 k,实际移动距离(字节数)与其数据类型有关: 假定 px 是 int 型(占 4 个字节), 则 px+3 与 px 相差 12 个字节
int x[5] = {0,1,2,3,4};
int * px = &x[0]; // px 指向 x[0]
cout << *px << endl; // 输出 x[0] 的值
cout << *(px+1) << endl; // 输出的 x[1] 值
px = px + 2; // px 改为指向 x[2]

空指针

空指针(NULL)是一个特殊的指针值,表示不指向任何有效的内存地址:

int *ptr = NULL;

野指针

未初始化的指针称为野指针,访问野指针可能导致程序崩溃:

int *ptr; // 野指针,未初始化

悬挂指针

悬挂指针,也叫做悬空指针,指的是释放指针指向的动态内存后的指针。

int *p = malloc(sizeof(int));
free(p);
*p = 5; // ❌ 使用了悬空指针

void 类型的指针

void * 指针名

  • void 类型的指针可以存储任何类型的对象的地址;
  • 不允许直接使用 void 指针访问其目标对象;
  • 必须通过显式类型转换, 才可以访问 void 类型指针的目标对象
int x;
int * px;
void * pv;
pv = &x; // OK, void 型指针指向整型变量
px = (int *)pv; // OK, 使用 void 型指针时需要强制类型转换

指向常量的指针

const 类型标识符 * 指针名

  1. 指向常量的指针必须用 const 声明;
  2. 这里的 const 限定了指针所指对象的属性, 即目标对象是常量, 而不是指针是常量;
  3. 允许把非 const 对象的地址赋给指向常量的指针, 即指向常量的指针也可以指向普通变量;
  4. 不允许使用指向常量的指针来修改其目标对象的值, 即使它所指向的对象不是常量
const int a = 3;
int b = 5;
const int * cpa = &a; // OK
*cpa = 5; // ERROR
cpa = &b; // OK
*cpa = 9; // ERROR
b = 9; // OK

常量指针

常量指针, 简称常指针: 指针本身的值不能修改。

类型标识符 * const 指针名

int a = 3, b = 5;
int * const pa = &a; // OK
pa = &b; // ERROR

数组名即指针常量

^f2f980

数组名就是一个指向数组首元素的指针常量

  • 数组名称本身是一个常量指针,不能被修改。

  • 数组名称可以隐式转换为指向数组第一个元素的指针。

int arr[3] = {1, 2, 3};
int *p = arr;

printf("%d\n", *(p + 1));  // 输出 2

分析下面三种写法,有什么不同?

int b=500;
const int *a= &b;  //[1] const 在 *左边, 修饰指针指向的变量
int const* a= &b;  //[2] , *a=3 非法
int* const a= &b;  //[3] , const 在*右边,修饰指针本身,a++错误

这三个声明中的区别在于 const 关键字的位置不同,分别表示不同的含义:

  1. const int *a:这表示 a 是一个指向常量整数的指针,即指针 a 指向的内容不能通过 a 修改。但是,指针 a 本身是可以改变的,可以指向不同的地址。
  2. int const* a:与第一个声明相同,这也表示 a 是一个指向常量整数的指针。const 关键字可以位于 int 前面或后面,对声明的含义没有影响。
  3. int* const a:这表示 a 是一个指向整数的常量指针,即指针 a 本身是常量,它指向的地址不能通过 a 修改。但是,指针 a 指向的内容可以通过 a 修改。

综上所述,这三种声明的含义分别是:

  • const int *aint const* a:指向常量整数的指针。
  • int* const a:指向整数的常量指针。

指向常量的常指针

const 类型标识符 * const 指针名

指针本身的值不能修改, 也不能通过该指针修改其目标对象的值。

指针数组

指针数组指的是由指针变量组成的数组。

类型标识符 * 指针数组名[n]

char *strs[] = {"A", "B"};

数组指针

数组指针 是一种特殊的指针,用于指向数组的起始地址。数组指针 是一个变量,用于存储数组的起始地址。数组指针可以被修改,可以指向不同的数组或数组中的不同位置。注意它与[[指针#^f2f980|数组名称]]不是一个概念。

int arr[] = {1, 2, 3, 4, 5}; // 定义一个数组
int *ptr = arr;              // 定义一个数组指针,指向数组的第一个元素

// 修改数组指针
ptr += 2; // 指针指向数组的第三个元素
printf("Modified pointer: %p\n", (void *)ptr); // 输出修改后的指针值
printf("Value at ptr[0]: %d\n", ptr[0]); // 输出指针指向的第三个元素

int arr[5] = {1, 2, 3, 4, 5};
int (*ptr)[5] = &arr; // ptr 指向数组 arr

函数指针

在C语言中,函数指针 是一种特殊的指针,用于存储函数的内存地址。函数指针可以像一般函数一样,用于调用函数、传递参数。

返回类型 (*指针名)(参数类型列表);

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

int (*func_ptr)(int, int) = add; // func_ptr 指向 add 函数

int result = func_ptr(3, 5); // 通过函数指针调用函数,结果为 8

// 创建一个函数指针数组,用于存储多个函数的地址
int subtract(int a, int b) {
    return a - b;
}

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

// 声明一个函数指针数组
int (*func_ptrs[3])(int, int) = {add, subtract, multiply};

// 使用函数指针数组调用不同的函数
for (int i = 0; i < 3; i++) {
 printf("Result: %d\n", func_ptrs[i](10, 5));
}

另外一个示例:

typedef void(func_t)();   // 函数类型
typedef void(*func_ptr_t)();   // 函数指针类型

void test() { // 函数名就是一个函数指针
    printf("%s\n", __func__);
}

int main(int argc, char* argv[])
{
    func_t* func = test;        // 声明一个函数指针
    func_ptr_t func2 = test;    // 函数指针
    void (*func3)();            // 声明 包含函数原型的函数指针变量
    func3 = test;
    func();
    func2();
    func3();
    return EXIT_SUCCESS;
}