正如您所知道的,每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。请看下面的实例,它将输出定义的变量地址:

#include <iostream>

using namespace std;

int main ()
{
int var1;
char var2[10];

cout << "var1 变量的地址: ";
cout << &var1 << endl;

cout << "var2 变量的地址: ";
cout << &var2 << endl;

return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:
var1 变量的地址: 0xbfebd5c0
var2 变量的地址: 0xbfebd5b6

一.指针简单定义

==指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。==就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:

type *var-name;

在这里,type 是指针的基类型,它必须是一个有效的 C++ 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。

int *iPtr1,iPtr2; //表示定义了一个iPtr1指针变量和一个iPtr2整型变量

==指向整型数的指针是包含该整数地址的变量或者常量。==

用&操作符可以获取变量的地址,指针用于存放地址。

*放在可执行语句中的指针之前,为间接引用操作符;*放在指针定义中,为指针定义符。非指针是不能用间接引用操作符的,因为*只能作用于地址。

==指针是有类型的,给指针赋值,不但必须是一个地址,而且应该是一个与该指针类型相符的常量或者变量的地址。==

#include <iostream>

using namespace std;

int main ()
{
int var = 20; // 实际变量的声明
int *ip; // 指针变量的声明

ip = &var; // 在指针变量中存储 var 的地址

cout << "Value of var variable: ";
cout << var << endl;

// 输出在指针变量中存储的地址
cout << "Address stored in ip variable: ";
cout << ip << endl;

// 访问指针中地址的值
cout << "Value of *ip variable: ";
cout << *ip << endl;

return 0;
}

二.传递指针给函数

在 C++ 中,指针作为函数参数传递是一种常见操作,其核心作用是通过传递地址实现对数据的间接访问,从而达到修改原始数据、避免大型数据拷贝等目的。以下从传递方式、特性、用途和注意事项四个方面详细讲解:

1)传递指针的基本形式

指针作为函数参数时,本质是将指针变量(存储的地址)传递给函数,函数参数的类型需与指针类型匹配。

语法形式

// 函数定义:参数为指针类型(如 int*、char* 等)
返回类型 函数名(指针类型 参数名) {
// 函数体:通过指针访问或修改数据
}

// 调用函数:传递指针变量或地址(&变量)
函数名(指针变量); // 传递指针
函数名(&变量); // 传递变量的地址(等价于指向该变量的指针)

2)传递指针的核心特性

1.传递的是地址,而非数据本身
指针参数接收的是原始数据的地址,函数内部通过该地址可以直接访问或修改原始数据(不同于值传递的“拷贝副本”)。

示例:通过指针修改原始变量的值

#include <iostream>
using namespace std;

// 函数参数为 int* 指针
void modifyValue(int* ptr) {
*ptr = 100; // 通过解引用(*)修改指针指向的原始数据
}

int main() {
int num = 10;
modifyValue(&num); // 传递 num 的地址(&num 是指向 num 的指针)
cout << num; // 输出:100(原始数据被修改)
return 0;
}

2.指针本身是值传递
==指针参数的传递方式是“值传递”——函数会拷贝一份指针变量(存储的地址相同),但修改函数内部的指针变量(如指向新地址)不会影响外部的原始指针。==

示例:修改函数内的指针不影响外部

void changePointer(int* ptr) {
int x = 200;
ptr = &x; // 函数内的指针指向新地址(仅影响内部拷贝)
}

int main() {
int num = 10;
int* p = &num;
changePointer(p);
cout << *p; // 输出:10(外部指针仍指向 num)
return 0;
}

三.从函数中返回指针

在 C++ 中,函数可以返回指针(即指向向内存地址的变量),其核心作用是将函数内部创建的或处理的数据通过地址传递给外部使用。但返回指针需要特别注意内存生命周期和安全性,以下从基本用法、常见场景、风险及注意事项展开讲解:

1)返回指针的基本形式

函数返回指针时,返回类型需声明为“指针类型”(如 int*char* 等),函数体内返回一个合法的地址(指针变量)。

语法示例

// 函数返回 int 类型的指针
int* createInt() {
int* ptr = new int(10); // 动态分配一个 int,返回其地址
return ptr;
}

2)返回指针的风险与禁忌

返回指针的最大风险是“指针指向的内存已释放”,导致野指针(访问会引发程序崩溃或未定义行为),以下是绝对禁止的行为:

1. 禁止返回局部变量的指针

局部变量(函数内定义,无 static 修饰)在函数执行结束后会被自动释放,返回其指针会导致野指针。

错误示例

int* badFunc() {
int num = 10; // 局部变量,函数结束后内存释放
return &num; // 错误:返回局部变量的指针(野指针)
}

int main() {
int* ptr = badFunc();
cout << *ptr; // 未定义行为(可能崩溃或输出随机值)
return 0;
}

正确示例:

#include <iostream>
using namespace std;

// 返回动态分配的 int 数组指针
int* createArray(int size) {
int* arr = new int[size]; // 堆内存分配,函数结束后不释放
for (int i = 0; i < size; i++) {
arr[i] = i * 5;
}
return arr; // 返回数组首地址
}

int main() {
int* arrPtr = createArray(3); // 接收指针
for (int i = 0; i < 3; i++) {
cout << arrPtr[i] << " "; // 输出:0 5 10(内存有效)
}
delete[] arrPtr; // 必须手动释放,否则内存泄漏
return 0;
}

2. 避免返回悬垂指针

如果指针指向的内存被提前释放(如手动 delete 后),返回该指针会成为悬垂指针(指向无效内存)。

错误示例

int* createAndDelete() {
int* ptr = new int(5);
delete ptr; // 提前释放内存
return ptr; // 错误:返回已释放内存的指针(悬垂指针)
}

四.指针与数组

1)核心关系:数组名即指针常量

在 C++ 中,数组名在大多数表达式中会被编译器解释为指向其首元素地址指针常量

这意味着:

  1. 数组名存储着数组第一个元素的内存地址。
  2. 你可以将数组名直接赋值给一个同类型的指针变量。

示例 1:数组名作为地址

#include <iostream>

int main() {
int arr[5] = {10, 20, 30, 40, 50};
int* ptr = arr; // 数组名 arr 隐式转换为 int* 类型指针,指向 arr[0]

// 验证:arr, &arr[0], ptr 的值都是相同的
std::cout << "arr 的地址 (数组首地址): " << arr << std::endl;
std::cout << "&arr[0] 的地址 (首元素地址): " << &arr[0] << std::endl;
std::cout << "ptr 的值 (存储的地址): " << ptr << std::endl;

return 0;
}

2)内存访问方式:指针运算和数组下标

由于数组名可以看作是指向首元素的指针,因此你可以通过两种等价的方式来访问数组中的元素:数组下标法指针算术法

示例 2:等价的访问方式

访问方式 语法 含义
下标法 (Array Subscript) arr[i] 访问数组中第 i 个元素的值。
指针算术 (Pointer Arithmetic) *(ptr + i) 指针 ptr 向后移动 i 个元素大小的字节数后,解引用(取其值)。
#include <iostream>

int main() {
int arr[] = {100, 200, 300};
int* ptr = arr; // ptr 指向 100

// 访问第三个元素 (索引 2)
int element_subscript = arr[2]; // 数组下标法
int element_pointer = *(ptr + 2); // 指针算术法

std::cout << "下标法访问 arr[2]: " << element_subscript << std::endl; // 300
std::cout << "指针法访问 *(ptr + 2): " << element_pointer << std::endl; // 300

// 还可以使用指针和下标混合访问(虽然不常见)
std::cout << "指针的下标法 ptr[1]: " << ptr[1] << std::endl; // 200

return 0;
}

3)深入理解:指针算术

当你对一个指针进行加减运算时,它移动的单位是其指向的类型的大小,而不是单个字节。

  • 如果 ptrint* 类型,ptr + 1 会将地址值增加 sizeof(int) 字节。
  • 如果 ptrdouble* 类型,ptr + 1 会将地址值增加 sizeof(double) 字节。

4)指针与数组的本质区别

虽然数组名在某些上下文中可以看作指针,但它们并非完全相同。它们在内存分配、大小和赋值操作上有本质区别。

1. 内存分配与存储位置

特性 数组 (e.g., int arr[5]) 指针 (e.g., int* ptr)
存储 存储整个数组的数据。 仅存储一个内存地址(通常是 4 或 8 字节)。
内存 局部数组在栈区,全局/静态数组在静态区 指针变量本身在栈区或静态区,它指向的内存可以在堆区栈区

2. sizeof 运算符的行为

sizeof 运算符是区分数组和指针的最直接方式。

#include <iostream>

int main() {
int arr[5] = {0}; // 5个 int
int* ptr = arr; // 指向 arr[0]

// 数组名:返回整个数组占用的总字节数
std::cout << "sizeof(arr) (数组大小): " << sizeof(arr) << " 字节" << std::endl;
// 输出: 20 字节 (假设 int 是 4 字节,5 * 4 = 20)

// 指针:返回指针变量本身占用的字节数(取决于系统架构,通常 4 或 8)
std::cout << "sizeof(ptr) (指针大小): " << sizeof(ptr) << " 字节" << std::endl;
// 输出: 8 字节 (在 64 位系统上)

return 0;
}

3. 可变性(赋值操作)

特性 数组名 指针变量
赋值 不可赋值。 数组名是地址的常量,不能指向其他地址。 可赋值。 指针变量是地址的变量,可以随时指向其他内存地址。
int arr1[5];
int arr2[5];
int* ptr;

// arr1 = arr2; // 错误!不能给数组名赋值

ptr = arr1; // 正确:ptr 指向 arr1 的首地址
ptr = arr2; // 正确:ptr 重新指向 arr2 的首地址

五.const指针

1)const 指针的核心:限制和不变性

const 关键字应用于指针声明时,它用于限制指针本身指针指向的数据的修改能力。

区分原则:看 const* 谁近

这是区分 const 指针的最关键原则

  1. const* 左边: 限制指针指向的数据
  2. const* 右边: 限制指针变量本身

2)指向常量的指针

  • 语法: const type* ptr;type const* ptr;
  • 别名: 常量指针(虽然容易混淆)、指向不可修改数据的指针。
  • 核心限制: 不能通过该指针修改它所指向的内存单元中的数据。
  • 指针本身: 指针变量本身是可变的,可以指向其他地址。

示例 1:指向常量的指针

int value_a = 10;
int value_b = 20;

// const 在 * 左边
const int* ptr_to_const = &value_a;

// 1. 限制数据修改:
// *ptr_to_const = 15; // 错误!不能通过 ptr_to_const 修改 value_a 的值

// 2. 指针可变:
ptr_to_const = &value_b; // 正确!可以改变指针的指向 (指向 value_b)

std::cout << *ptr_to_const << std::endl; // 输出 20

重要用途: 当你将一个指针作为函数参数传递时,使用 const 可以确保函数不会意外地修改传入的原始数据。

2)常量指针

  • 语法: type* const ptr;
  • 别名: 指针常量。
  • 核心限制: 不能修改指针变量本身存储的地址,即一旦初始化后,它必须永远指向同一个地方。
  • 指向的数据: 指针指向的数据是可变的,可以通过该指针修改数据。

示例 2:常量指针

int value_x = 100;
int value_y = 200;

// const 在 * 右边
int* const const_ptr = &value_x;

// 1. 限制指针本身:
// const_ptr = &value_y; // 错误!不能改变指针的指向(它必须指向 value_x)

// 2. 数据可变:
*const_ptr = 150; // 正确!可以通过 const_ptr 修改 value_x 的值

std::cout << value_x << std::endl; // 输出 150

重要用途: 用于固定访问某个对象或内存块,确保程序不会意外地将指针重置到其他地址。

3)指向常量的常量指针

你可以将两种限制结合起来,创建一个既不能修改指向地址,也不能通过它修改所指向数据的指针。

  • 语法: const type* const ptr;

示例 3:双重 const

int value_m = 500;

// 1. 第一个 const:不能修改数据
// 2. 第二个 const:不能修改指针指向
const int* const const_all = &value_m;

// *const_all = 600; // 错误!不能修改数据
// const_all = &another_var; // 错误!不能修改指针指向

4)总结表格与助记法

语法结构 限制项 可变项 助记法
const T* pT const* p 指向的数据 (*p) 指针本身 (p) const* 近,限制数据。
T* const p 指针本身 (p) 指向的数据 (*p) const 离变量名近,限制变量。
const T* const p 数据指针