C++编程基础(七):指针
文章目录
- 一、指针概述
- 1. 什么是指针?
- 2. 指针的定义与使用
- 二、特殊指针类型
- 1. 空指针 (`nullptr`)
- 2. void 指针 (万能指针)
- 三、 指针与 `const` (关键)
- 1. 指向 `const` 的指针 (常量指针)
- 2. `const` 指针 (指针常量)
- 3. 指向 `const` 的 `const` 指针
- 四、 指针的应用
- 1. 指针与数组
- 2. 指针与C风格字符串
- 3. 指针与函数
- 3.1. 指针作为函数参数(传地址)
- 3.2. 指针作为函数返回值
- 4. 多级指针(指针的指针)
- 五、 指针与引用的区别
一、指针概述
1. 什么是指针?
在计算机中,所有数据都存储在内存中。内存被划分为一个个的字节,每个字节都有一个唯一的编号,这个编号就称为地址(Address)或指针(Pointer)。
所谓指针变量,就是一个用来保存内存地址的变量。
2. 指针的定义与使用
C++中使用 * 来声明指针变量,使用 & (取地址运算符) 来获取一个变量的内存地址。
// 语法:定义与初始化
数据类型 *指针变量名 = &某个变量;
&varName:取地址。获取变量varName在内存中的地址。*ptrName:解引用。访问指针ptrName所指向的地址上存储的值。
- 示例
#include <iostream>int main() {int num = 100; // 定义一个整型变量 num// 定义一个整型指针 p,并将其初始化为 num 的地址int *p = #// 打印 num 的值和地址std::cout << "变量 num 的值: " << num << std::endl;std::cout << "变量 num 的地址: " << &num << std::endl;// 打印指针 p 中存储的地址,以及该地址上std::cout << "指针 p 存储的地址: " << p << std::endl;std::cout << "指针 p 指向的值: " << *p << std::endl; // 解引用// 通过指针修改变量的值*p = 200; // *p 就等价于 numstd::cout << "通过指针修改后,num 的值: " << num << std::endl;return 0;
}
输出:
变量 num 的值: 100
变量 num 的地址: 0x... (某个内存地址)
指针 p 存储的地址: 0x... (与上一行相同)
指针 p 指向的值: 100
通过指针修改后,num 的值: 200
二、特殊指针类型
1. 空指针 (nullptr)
一个指针不指向任何数据,称之为空指针。
在过去(C语言或旧的C++),空指针用 NULL 或 0 来表示。
// 旧的方式 (不推荐)
int *p1 = NULL;
int *p2 = 0;
NULL 本质上是一个宏,通常被定义为 0。这会带来歧义,例如在函数重载时:
void func(int);
void func(char*);func(NULL); // 编译器会调用 func(int),而不是 func(char*)!
为了解决这个问题,C++11 引入了 nullptr 关键字,它是一个类型安全的空指针常量,只能被转换为空指针。
在现代 C++ 中,应始终使用 nullptr 初始化空指针。
// 推荐的方式
int *p = nullptr;
注意: 未经初始化的指针(如
int *p;)是一个野指针,它指向一个未知的内存地址。对其解引用是极其危险的,会导致未定义行为。
2. void 指针 (万能指针)
void* 是一种特殊的指针类型,被称为“无类型指针”或“通用指针”。它可以指向任何类型的数据,无需进行强制类型转换,但有以下限制:
void指针可以接收任何类型变量的地址。- 不能直接解引用
void指针,因为它不包含类型信息(编译器不知道该读取几个字节)。 - 在使用前,必须将其强制类型转换为具体的数据类型指针。
int num = 100;
void *p = # // void* 可以持有 int*std::cout << "地址: " << p << std::endl;
// std::cout << *p << std::endl; // 编译错误!不能直接解引用// 必须强制转换回原始类型
std::cout << "值: " << *((int*)p) << std::endl;
三、 指针与 const (关键)
const 和 * 的相对位置决定了到底什么是“常量”。
一个简单的区分方法: 从右向左阅读声明。
1. 指向 const 的指针 (常量指针)
- 声明:
const int *p;(或者int const *p;) - 解读:
p是一个指针,它指向一个const int(常量整数)。 - 含义:
- 不能通过
*p修改所指向的值。 - 可以改变
p自己的指向。
- 不能通过
int num = 100, num2 = 200;
const int *p = #// *p = 111; // 错误!不能通过 p 修改值
p = &num2; // 正确!p 的指向可以改变
2. const 指针 (指针常量)
- 声明:
int * const p = # - 解读:
p是一个const(常量) 指针,它指向一个int(整数)。 - 含义:
- 可以通过
*p修改所指向的值。 - 不能改变
p自己的指向(p必须在定义时初始化)。
- 可以通过
int num = 100, num2 = 200;
int * const p = # // 必须在定义时初始化*p = 111; // 正确!可以修改值
// p = &num2; // 错误!p 的指向不能改变
3. 指向 const 的 const 指针
- 声明:
const int * const p = # - 解读:
p是一个const(常量) 指针,它指向一个const int(常量整数)。 - 含义:
- 不能通过
*p修改所指向的值。 - 不能改变
p自己的指向。
- 不能通过
int num = 100, num2 = 200;
const int * const p = #// *p = 222; // 错误!
// p = &num2; // 错误!
四、 指针的应用
1. 指针与数组
数组名在C++中可以被看作一个指向数组首元素(第0个元素)的常量指针。
int arr[3] = {1, 2, 3};
int *p = arr; // 将指针 p 指向数组首地址 (等价于 &arr[0])
注意: 数组名
arr和指针p并不完全等价。arr是一个常量,不能被修改(arr++是非法的),而p是一个变量,可以移动。
使用指针遍历数组(指针算术):
对指针进行加减运算(如 p+1),指针会移动 sizeof(数据类型) 个字节,从而精确指向下一个元素。
int arr[3] = {1, 2, 3};
int *p = arr;// 方法一:使用 *(p + i)
std::cout << "方法一:" << std::endl;
for (int i = 0; i < 3; i++) {std::cout << "arr[" << i << "] = " << *(p + i) << std::endl;
}// 方法二:移动指针 p (p++ 会修改 p 的值)
std::cout << "方法二:" << std::endl;
for (int i = 0; i < 3; i++) {std::cout << "arr[" << i << "] = " << *p << std::endl;p++; // 将 p 指向下一个元素
}
// 此时 p 不再指向 arr[0]
2. 指针与C风格字符串
C风格字符串本质上是 char 数组。
// 方式一:字符数组 (在栈上,内容可修改)
char str1[] = "hello world";
str1[0] = 'H'; // 正确
char *p1 = str1;
*(p1 + 1) = 'E'; // 正确// 方式二:指向字符串字面量 (在常量区,内容不可修改)
char *str2 = "hello world";
// *str2 = 'H'; // 编译通过,但运行时会崩溃!(未定义行为)//
// 现代C++的正确写法:
const char *str3 = "hello world";
// *str3 = 'H'; // 编译错误!(const 阻止了修改)
注意:
char *varName = "字符串";是一种已废弃且极其危险的写法。它试图用一个可修改的指针指向一块不可修改的内存(字符串字面量存储在常量区)。应始终使用const char*来指向字符串字面量。
3. 指针与函数
3.1. 指针作为函数参数(传地址)
使用指针作为函数参数,可以在函数内部修改函数外部的变量(类似于引用)。
案例: 交换两个变量的值。
// 传入指针,函数内部通过解引用 *p 来修改实参
void change(int* p1, int* p2) {int tmp = *p1;*p1 = *p2; // 修正:这里是 *p2 (取值),而不是 p2 (取地址)*p2 = tmp;
}int main() {int num1 = 100, num2 = 200;std::cout << "交换前: num1=" << num1 << ", num2=" << num2 << std::endl;// 传入变量的地址change(&num1, &num2);std::cout << "交换后: num1=" << num1 << ", num2=" << num2 << std::endl;return 0;
}
3.2. 指针作为函数返回值
函数可以返回一个指针。
注意:
绝不可以返回函数内部局部变量的地址。函数结束后,局部变量被销毁(其内存被释放),返回的指针将成为悬垂指针(Dangling Pointer),指向一块不再有效的内存。
// 错误示例:返回局部变量的地址
int* func_bad() {int num = 100;return # // 危险!num 在函数返回时被销毁
}// 正确示例:返回静态变量的地址
int* func_good() {// static 变量的生命周期是整个程序static int num = 100;return #
}int main() {int *p1 = func_bad();// cout << *p1 << endl; // 极度危险,可能崩溃,或输出垃圾值int *p2 = func_good();std::cout << *p2 << std::endl; // 正确,输出 100*p2 = 200;std::cout << *p2 << std::endl; // 正确,输出 200return 0;
}
(注:返回堆内存 (new) 也是一种方法,但需要调用者 delete,暂不讨论。)
4. 多级指针(指针的指针)
多级指针就是指向指针的指针。二级指针存储的是一级指针的地址。
int num = 100;// 一级指针
int *pnum = #// 二级指针
int **ppn = &pnum; // ppn 存储了 pnum 的地址// 访问
std::cout << "num = " << num << std::endl;
std::cout << "*pnum = " << *pnum << std::endl;
std::cout << "**ppn = " << **ppn << std::endl; // 解引用两次
理论上,指针的级数没有限制(int ******pa6;),但实际应用中,超过二级指针就很少见了。
五、 指针与引用的区别
引用(Reference) 是 C++ 中的一个重要概念,它是一个变量的别名。
| 特性 | 指针 (Pointer) | 引用 (Reference) |
|---|---|---|
| 本质 | 存储变量的地址。 | 变量的别名。 |
| 初始化 | 可以不初始化(不推荐,会成为野指针)。 | 必须在定义时初始化。 |
| 空值 | 可以为 nullptr。 | 不能有 “空引用”。 |
| 可变性 | 可以改变指向(指向另一个变量)。 | 一旦绑定,不能再更改为其他变量的别名。 |
| 操作 | 需要 * (解引用) 来访问值。 | 像普通变量一样直接使用。 |
| 内存 | 指针变量本身占用内存空间。 | 不(或说“概念上不”)占用额外内存。 |
| 多级 | 可以有二级、三级…指针 (int **p)。 | 只能有一级 (int &&r 有不同含义)。 |
简单总结:
- 指针更强大、更灵活,但也更危险(空指针、野指针、内存泄漏)。
- 引用更安全、更易用(没有空引用、自动解引用)。
关于常量引用 (
const int&):在函数传参时,如果不想修改参数,又想避免复制(特别是对于大对象),使用常量引用是 C++ 中最高效、最安全的做法。
void printObject(const MyObject& obj);
