梦想不会自己发光,真正闪耀的是那个为梦狂奔的你。献给知行的孩子们!(Eric.He著)
本教程将从 C++ 指针的核心概念、指针变量的定义与初始化,到指针的运算、高级应用(指针与数组、字符串、函数)等维度,全面拆解 C++ 指针的核心用法,帮助你掌握这一 C++ 编程的核心利器。
指针是 C++ 中最核心的概念之一,本质是存储内存地址的变量。
直观理解:
把计算机内存比作一排带编号的储物柜,变量是储物柜里的物品,内存地址是储物柜编号,指针是写有储物柜编号的纸条。通过纸条(指针)可以找到对应的储物柜(内存地址),进而操作里面的物品(变量值)。
指针变量的定义语法:
// 基础语法:数据类型* 指针变量名;
数据类型* 指针变量名;
// 示例:
// 定义指向int类型变量的指针p1
int* p1;
// 定义指向float类型变量的指针p2
float* p2;
// 定义指向char类型变量的指针p3
char* p3;
// 定义指向结构体的指针p4(假设已定义Student结构体)
struct Student* p4;
注意:
* 表示“指向...的指针”,int* 读作“指向int类型的指针”;* 可紧贴数据类型(int* p)或变量名(int *p),风格统一即可。指针变量定义后若未明确指向,会成为“野指针”(指向随机内存地址),极易导致程序崩溃。推荐先初始化为 NULL(C++11 后推荐 nullptr),表示指针暂时不指向任何有效内存。
#include <iostream>
using namespace std;
int main() {
// 初始化指针为NULL(空指针)
int* p = NULL;
// C++11及以上推荐使用nullptr
// int* p = nullptr;
// 空指针不可直接解引用(*p),否则程序崩溃
if (p == NULL) {
cout << "指针p是空指针,暂未指向有效内存" << endl;
}
return 0;
}
空指针特性:
NULL 本质是宏定义(#define NULL 0),表示地址 0(系统保留,不可访问);if (p == NULL))避免非法解引用;nullptr,专门用于表示空指针,避免与整数 0 混淆,优先使用。通过 &(取地址运算符)获取变量的内存地址,赋值给指针变量,完成初始化。
#include <iostream>
using namespace std;
int main() {
// 定义普通变量
int a = 10;
// 初始化指针p为变量a的地址(&a表示取a的内存地址)
int* p = &a;
cout << "变量a的值:" << a << endl;
// 输出a的内存地址(十六进制)
cout << "变量a的地址:" << &a << endl;
// 输出指针p的值(即a的地址)
cout << "指针p的值:" << p << endl;
return 0;
}
运行结果示例:
变量a的值:10
变量a的地址:0x0065FF18
指针p的值:0x0065FF18
new 是 C++ 动态内存分配关键字,用于在堆区分配内存,并返回该内存的地址,可直接初始化指针。
#include <iostream>
using namespace std;
int main() {
// 动态分配int类型的堆内存,返回地址赋值给p
int* p = new int;
// 给堆内存赋值
*p = 20;
cout << "堆内存中的值:" << *p << endl;
cout << "堆内存的地址:" << p << endl;
// 释放堆内存(避免内存泄漏)
delete p;
// 将指针置为空,避免野指针
p = nullptr;
return 0;
}
new初始化注意事项:
new 数据类型 会在堆区分配对应大小的内存,并返回首地址;delete 释放(数组用 delete[]);nullptr,否则会成为“野指针”。指针变量赋值分为两种场景:赋值为“内存地址”、指针间相互赋值。
#include <iostream>
using namespace std;
int main() {
// 场景1:赋值为变量地址
int a = 10;
int* p1 = nullptr;
// 赋值:p1指向a的地址
p1 = &a;
// 场景2:指针间相互赋值(类型需一致)
int* p2 = nullptr;
// p2指向p1所指向的地址(即a的地址)
p2 = p1;
cout << "*p1 = " << *p1 << endl; // 输出10
cout << "*p2 = " << *p2 << endl; // 输出10
// 场景3:赋值为堆内存地址
int* p3 = new int(30);
cout << "*p3 = " << *p3 << endl; // 输出30
delete p3;
p3 = nullptr;
return 0;
}
&(取地址运算符)用于获取变量/对象的内存地址,是指针初始化的核心运算符。
#include <iostream>
using namespace std;
int main() {
int num = 100;
double pi = 3.14;
char ch = 'A';
// 获取不同类型变量的地址
cout << "num的地址:" << &num << endl;
cout << "pi的地址:" << &pi << endl;
cout << "ch的地址:" << &ch << endl;
return 0;
}
注意:
& 只能作用于“左值”(可寻址的变量/对象),不能作用于常量(如 &10 非法);*(解引用运算符/间接存取运算符)用于通过指针访问其指向的内存中的数据。
#include <iostream>
using namespace std;
int main() {
int a = 50;
int* p = &a;
// 解引用:访问p指向的内存中的值(即a的值)
cout << "*p = " << *p << endl; // 输出50
// 解引用赋值:修改p指向的内存中的值(即修改a的值)
*p = 100;
cout << "a = " << a << endl; // 输出100
cout << "*p = " << *p << endl; // 输出100
return 0;
}
解引用注意事项:
指针的运算本质是“内存地址的偏移”,仅支持 +、- 算术运算和比较运算,运算单位是“指针指向类型的大小”。
指针 + n 表示:指针指向的地址向后偏移 n * 指针类型大小 个字节。
#include <iostream>
using namespace std;
int main() {
int arr[] = {10, 20, 30, 40, 50};
// p指向数组首元素(数组名arr本质是首元素地址)
int* p = arr;
cout << "p指向的地址:" << p << ",值:" << *p << endl; // 10
// p+1:偏移1个int大小(4字节),指向arr[1]
p = p + 1;
cout << "p+1指向的地址:" << p << ",值:" << *p << endl; // 20
// p+2:再偏移2个int大小(8字节),指向arr[3]
p = p + 2;
cout << "p+2指向的地址:" << p << ",值:" << *p << endl; // 40
return 0;
}
运行结果示例(32位系统):
p指向的地址:0x0065FF00,值:10
p+1指向的地址:0x0065FF04,值:20
p+2指向的地址:0x0065FF0C,值:40
指针 - n 表示:指针指向的地址向前偏移 n * 指针类型大小 个字节;两个同类型指针相减,表示地址之间的元素个数。
#include <iostream>
using namespace std;
int main() {
int arr[] = {10, 20, 30, 40, 50};
// p指向arr[3]
int* p = &arr[3];
cout << "p指向的地址:" << p << ",值:" << *p << endl; // 40
// p-2:向前偏移2个int大小(8字节),指向arr[1]
p = p - 2;
cout << "p-2指向的地址:" << p << ",值:" << *p << endl; // 20
// 两个指针相减:计算元素个数
int* p1 = arr; // 指向arr[0]
int* p2 = &arr[4]; // 指向arr[4]
// 结果为4(表示p2 - p1之间有4个int元素)
int diff = p2 - p1;
cout << "p2 - p1 = " << diff << endl;
return 0;
}
指针运算规则:
char* 偏移1字节,double* 偏移8字节);多重指针(又称指针的指针)是存储“指针地址”的指针,核心用于多级间接访问内存,常见的是二级指针(数据类型**)。
#include <iostream>
using namespace std;
int main() {
int a = 10;
// 一级指针:指向a的地址
int* p1 = &a;
// 二级指针:指向p1的地址
int** p2 = &p1;
cout << "a的值:" << a << endl;
cout << "a的地址:" << &a << endl;
cout << "p1的值(a的地址):" << p1 << ",*p1 = " << *p1 << endl;
cout << "p1的地址:" << &p1 << endl;
cout << "p2的值(p1的地址):" << p2 << ",*p2 = " << *p2 << endl;
// **p2:先解引用p2得到p1,再解引用p1得到a
cout << "**p2 = " << **p2 << endl;
// 修改a的值(通过二级指针)
**p2 = 200;
cout << "修改后a的值:" << a << endl;
return 0;
}
运行结果示例:
a的值:10
a的地址:0x0065FF18
p1的值(a的地址):0x0065FF18,*p1 = 10
p1的地址:0x0065FF10
p2的值(p1的地址):0x0065FF10,*p2 = 0x0065FF18
**p2 = 10
修改后a的值:200
多重指针的应用场景:
数组名本质是“指向数组首元素的常量指针”,通过指针可实现数组的高效遍历和操作。
#include <iostream>
using namespace std;
int main() {
int arr[] = {1, 2, 3, 4, 5};
int len = sizeof(arr) / sizeof(arr[0]); // 数组长度
// 数组名arr是指向首元素的常量指针
cout << "arr = " << arr << endl; // 首元素地址
cout << "*arr = " << *arr << endl; // 首元素值(arr[0])
cout << "arr[2] = " << *(arr + 2) << endl;// 等价于arr[2]
// 指针遍历数组
int* p = arr;
for (int i = 0; i < len; i++) {
// *(p+i) 等价于 arr[i]
cout << "arr[" << i << "] = " << *(p + i) << " ";
}
cout << endl;
// 修改数组元素(通过指针)
*(p + 3) = 40;
cout << "修改后arr[3] = " << arr[3] << endl;
return 0;
}
指针与数组的核心关系:
arr[i] 等价于 *(arr + i)(数组下标本质是指针偏移);arr++ 非法),但可将数组名赋值给普通指针后操作;C++ 中字符串有两种表示形式:字符数组、字符指针,字符指针是字符串操作的核心方式。
#include <iostream>
#include <cstring> // 字符串函数头文件
using namespace std;
int main() {
// 方式1:字符数组表示字符串(可修改)
char str1[] = "Hello C++";
str1[0] = 'h'; // 合法:修改第一个字符
cout << "str1 = " << str1 << endl;
// 方式2:字符指针指向字符串常量(不可修改)
char* str2 = "Hello Pointer";
// str2[0] = 'h'; // 非法:字符串常量存放在只读内存
// 字符串遍历(指针方式)
char* p = str1;
while (*p != '\0') { // '\0'是字符串结束符
cout << *p;
p++;
}
cout << endl;
// 字符串拷贝(指针应用)
char str3[20];
char* p3 = str3;
// strcpy:将str1拷贝到str3
strcpy(p3, str1);
cout << "str3 = " << p3 << endl;
return 0;
}
指针与字符串的注意事项:
char* p = "abc")存放在只读内存,不可修改;char str[] = "abc")存放在栈区,可修改;'\0' 是指针遍历字符串的终止条件,不可省略。指针在函数中的应用包括:指针作为函数参数、函数返回指针、函数指针(指向函数的指针)。
实现“传地址调用”,函数可直接修改实参的值,避免拷贝开销。
#include <iostream>
using namespace std;
// 指针参数:交换两个整数
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
cout << "交换前:x=" << x << ", y=" << y << endl;
// 传递地址
swap(&x, &y);
cout << "交换后:x=" << x << ", y=" << y << endl;
return 0;
}
函数可返回指针(如堆内存地址、静态变量地址),但禁止返回栈区局部变量的指针。
#include <iostream>
using namespace std;
// 返回堆内存指针
int* createInt(int value) {
// 堆内存不会随函数结束释放
int* p = new int(value);
return p;
}
int main() {
int* p = createInt(100);
cout << "*p = " << *p << endl; // 输出100
delete p; // 释放堆内存
p = nullptr;
return 0;
}
函数指针是指向函数的指针,存储函数的入口地址,用于回调函数、动态函数调用。
#include <iostream>
using namespace std;
// 加法函数
int add(int a, int b) {
return a + b;
}
// 减法函数
int sub(int a, int b) {
return a - b;
}
int main() {
// 定义函数指针:指向返回int、参数为两个int的函数
int (*funcPtr)(int, int);
// 指向add函数
funcPtr = add;
cout << "3 + 5 = " << funcPtr(3, 5) << endl;
// 指向sub函数
funcPtr = sub;
cout << "8 - 2 = " << funcPtr(8, 2) << endl;
return 0;
}
new 分配的内存必须用 delete 释放,数组用 delete[],避免内存泄漏;& 取地址、* 解引用实现内存的间接操作;本教程从指针的核心概念到高级应用,全面拆解了 C++ 指针的用法。掌握指针是突破 C++ 编程瓶颈的关键,也是学习数据结构与算法的核心基础。