====== 第八章 指针与引用 ======
指针和引用是C++中两个极其重要的概念,它们是实现动态内存管理、高效数据传递和复杂数据结构的基础。本章将深入探讨指针与引用的原理、用法以及它们之间的关系。
===== 8.1 指针基础 =====
==== 8.1.1 什么是指针 ====
指针是一个变量,其值为另一个变量的内存地址。通过指针,我们可以直接访问和操作内存中的数据。
**内存地址的概念**
在计算机中,每个变量都存储在内存的某个位置,这个位置有一个唯一的地址。就像每家每户都有一个门牌号一样,内存中的每个字节都有一个地址。
#include
using namespace std;
int main() {
int num = 100;
cout << "变量num的值: " << num << endl;
cout << "变量num的地址: " << &num << endl;
return 0;
}
**声明指针变量**
指针变量的声明格式为:
数据类型 *指针变量名;
例如:
int *pInt; // 指向int类型的指针
double *pDouble; // 指向double类型的指针
char *pChar; // 指向char类型的指针
void *pVoid; // 通用指针,可以指向任何类型
==== 8.1.2 指针的初始化与使用 ====
**获取变量的地址**
使用取地址运算符 **&** 可以获取变量的内存地址:
int num = 100;
int *p = # // p存储了num的地址
**解引用运算符**
使用解引用运算符 ***** 可以访问指针所指向的值:
#include
using namespace std;
int main() {
int num = 100;
int *p = #
cout << "num的值: " << num << endl;
cout << "p的值(地址): " << p << endl;
cout << "*p的值: " << *p << endl; // 通过指针访问num的值
*p = 200; // 通过指针修改num的值
cout << "修改后num的值: " << num << endl;
return 0;
}
**空指针与野指针**
int *p1 = nullptr; // C++11推荐的方式,空指针
int *p2 = NULL; // C风格,等价于0
int *p3 = 0; // 直接赋值为0
// int *p4; // 错误!野指针,未初始化
===== 8.2 指针与数组 =====
==== 8.2.1 数组名的本质 ====
在C++中,数组名本质上是一个指向数组首元素的常量指针。
#include
using namespace std;
int main() {
int arr[5] = {10, 20, 30, 40, 50};
cout << "arr的值(首地址): " << arr << endl;
cout << "&arr[0]: " << &arr[0] << endl;
cout << "*arr: " << *arr << endl; // 10
// 通过指针访问数组元素
int *p = arr;
for (int i = 0; i < 5; i++) {
cout << "*(p+" << i << ") = " << *(p + i) << endl;
// 等价于 p[i] 或 arr[i]
}
return 0;
}
==== 8.2.2 指针运算 ====
指针支持以下运算:
- **p + n**:指针向后移动n个元素的位置
- **p - n**:指针向前移动n个元素的位置
- **p++ / p--**:指针向后/向前移动一个元素
- **p - q**:两个指针之间的距离(元素个数)
#include
using namespace std;
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;
cout << "初始位置: *p = " << *p << endl; // 10
p++; // 移动到下一个元素
cout << "p++后: *p = " << *p << endl; // 20
p += 2; // 向前移动2个元素
cout << "p+=2后: *p = " << *p << endl; // 40
int *q = arr;
cout << "p - q = " << p - q << endl; // 3,相隔3个元素
return 0;
}
==== 8.2.3 指针与多维数组 ====
#include
using namespace std;
int main() {
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
// 使用指针访问二维数组
int (*p)[4] = matrix; // p指向包含4个int的数组
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++) {
cout << *(*(p + i) + j) << " "; // 等价于 matrix[i][j]
}
cout << endl;
}
return 0;
}
===== 8.3 指针与字符串 =====
==== 8.3.1 C风格字符串 ====
#include
#include
using namespace std;
int main() {
// 字符串常量
const char *str1 = "Hello, World!";
// 字符数组
char str2[] = "Hello";
cout << "str1: " << str1 << endl;
cout << "str2: " << str2 << endl;
cout << "strlen(str1): " << strlen(str1) << endl;
// 遍历字符串
const char *p = str1;
while (*p != '\0') {
cout << *p;
p++;
}
cout << endl;
return 0;
}
==== 8.3.2 字符串指针数组 ====
#include
using namespace std;
int main() {
const char *days[] = {
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
"Sunday"
};
for (int i = 0; i < 7; i++) {
cout << days[i] << endl;
}
return 0;
}
===== 8.4 指针与函数 =====
==== 8.4.1 指针作为函数参数 ====
通过指针参数,函数可以修改调用者的变量。
#include
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;
}
==== 8.4.2 数组作为函数参数 ====
#include
using namespace std;
// 计算数组元素之和
int sum(int *arr, int size) {
int total = 0;
for (int i = 0; i < size; i++) {
total += arr[i];
}
return total;
}
// 或者使用以下形式
int sum2(int arr[], int size) {
int total = 0;
for (int i = 0; i < size; i++) {
total += *(arr + i);
}
return total;
}
int main() {
int numbers[] = {1, 2, 3, 4, 5};
int size = sizeof(numbers) / sizeof(numbers[0]);
cout << "数组元素之和: " << sum(numbers, size) << endl;
return 0;
}
==== 8.4.3 函数指针 ====
函数指针是指向函数的指针,可以用来实现回调函数等高级功能。
#include
using namespace std;
// 定义函数类型
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int multiply(int a, int b) {
return a * b;
}
int main() {
// 声明函数指针
int (*operation)(int, int);
operation = add;
cout << "10 + 5 = " << operation(10, 5) << endl;
operation = subtract;
cout << "10 - 5 = " << operation(10, 5) << endl;
operation = multiply;
cout << "10 * 5 = " << operation(10, 5) << endl;
return 0;
}
===== 8.5 引用 =====
==== 8.5.1 引用的概念 ====
引用是变量的别名,它为已存在的变量提供另一个名字。引用必须在声明时初始化,且不能改变引用的对象。
#include
using namespace std;
int main() {
int num = 100;
int &ref = num; // ref是num的引用
cout << "num: " << num << endl;
cout << "ref: " << ref << endl;
ref = 200; // 通过引用修改值
cout << "修改后num: " << num << endl;
cout << "&num: " << &num << endl;
cout << "&ref: " << &ref << endl; // 地址相同
return 0;
}
==== 8.5.2 引用作为函数参数 ====
引用参数提供了一种更优雅的"按引用传递"方式。
#include
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;
}
**引用 vs 指针参数**
| 特性 | 指针 | 引用 |
|------|------|------|
| 语法 | 需要解引用(*) | 直接使用 |
| 空值 | 可以为nullptr | 不能为空 |
| 重新赋值 | 可以指向不同对象 | 始终绑定同一对象 |
| 安全性 | 需要检查空指针 | 更安全 |
==== 8.5.3 引用作为返回值 ====
#include
using namespace std;
int arr[] = {10, 20, 30, 40, 50};
// 返回数组元素的引用
int &getElement(int index) {
return arr[index];
}
int main() {
cout << "修改前: " << arr[2] << endl;
getElement(2) = 100; // 通过引用返回值修改数组元素
cout << "修改后: " << arr[2] << endl;
return 0;
}
==== 8.5.4 常量引用 ====
常量引用用于防止函数修改传入的参数,同时避免拷贝开销。
#include
#include
using namespace std;
// 使用const引用传递大型对象
void printString(const string &str) {
cout << str << endl;
// str = "new"; // 错误!不能修改const引用
}
int main() {
string text = "Hello, World!";
printString(text);
return 0;
}
===== 8.6 指针与引用的比较 =====
| 特性 | 指针 | 引用 |
|------|------|------|
| 定义 | 存储地址的变量 | 变量的别名 |
| 初始化 | 可以不初始化 | 必须立即初始化 |
| 空值 | 可以为空(nullptr) | 不能为空 |
| 修改绑定 | 可以指向不同对象 | 不能改变绑定的对象 |
| 内存占用 | 占用内存存储地址 | 不占用额外内存 |
| 使用语法 | 需要解引用(*) | 直接使用 |
| 算术运算 | 支持 | 不支持 |
| 多级间接 | 支持(如int**) | 不支持 |
===== 8.7 常见指针错误与避免 =====
==== 8.7.1 常见错误 ====
// 1. 野指针
int *p; // 未初始化
*p = 10; // 危险!
// 2. 空指针解引用
int *p2 = nullptr;
*p2 = 10; // 运行时错误!
// 3. 内存泄漏
void leak() {
int *p = new int[100];
// 没有delete,内存泄漏
}
// 4. 悬挂指针
int *dangling() {
int local = 10;
return &local; // 错误!返回局部变量的地址
}
// 5. 数组越界
int arr[5];
int *p = arr;
*(p + 10) = 100; // 越界访问
==== 8.7.2 最佳实践 ====
#include
using namespace std;
// 安全的指针使用示例
void safePointerUsage() {
// 1. 始终初始化指针
int *p1 = nullptr;
// 2. 使用前检查空指针
if (p1 != nullptr) {
*p1 = 10;
}
// 3. 使用智能指针(C++11以后推荐)
// unique_ptr smartPtr = make_unique(10);
// 4. 及时释放内存并置空
int *p2 = new int(100);
delete p2;
p2 = nullptr;
// 5. 避免返回局部变量的地址
// 如果需要,使用动态分配或返回副本
}
int main() {
safePointerUsage();
return 0;
}
===== 8.8 综合示例 =====
==== 示例:实现动态数组类 ====
#include
using namespace std;
class DynamicArray {
private:
int *data;
int size;
int capacity;
public:
DynamicArray(int cap = 10) {
capacity = cap;
size = 0;
data = new int[capacity];
}
~DynamicArray() {
delete[] data;
}
void push_back(int value) {
if (size >= capacity) {
// 扩容
capacity *= 2;
int *newData = new int[capacity];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
delete[] data;
data = newData;
}
data[size++] = value;
}
int &operator[](int index) {
if (index < 0 || index >= size) {
cerr << "Index out of bounds!" << endl;
exit(1);
}
return data[index];
}
int getSize() const {
return size;
}
void print() const {
for (int i = 0; i < size; i++) {
cout << data[i] << " ";
}
cout << endl;
}
};
int main() {
DynamicArray arr;
for (int i = 1; i <= 15; i++) {
arr.push_back(i * 10);
}
cout << "数组内容: ";
arr.print();
cout << "第5个元素: " << arr[4] << endl;
arr[4] = 999; // 通过引用修改
cout << "修改后: ";
arr.print();
return 0;
}
===== 8.9 习题 =====
==== 基础题 ====
1. **指针基础**
声明一个int变量a,值为100。声明一个指向a的指针p,并通过p输出a的值和地址。
2. **指针运算**
编写程序,使用指针遍历一个整型数组,计算所有元素的和。
3. **交换函数**
分别用指针和引用两种方式实现交换两个整数的函数,比较它们的差异。
==== 进阶题 ====
4. **字符串反转**
编写一个函数,使用指针实现C风格字符串的反转(不使用额外的数组)。
void reverse(char *str);
5. **数组去重**
编写函数,接收一个已排序的整型数组及其大小,使用指针操作去除重复元素,返回新的数组大小。
6. **函数指针数组**
创建一个计算器程序,使用函数指针数组实现加、减、乘、除四种运算。
==== 综合题 ====
7. **矩阵乘法**
使用指针实现两个矩阵的乘法。矩阵用动态分配的二维数组表示。
8. **链表实现**
使用指针实现一个简单的单向链表,包含以下功能:
- 在头部插入节点
- 在尾部插入节点
- 删除指定值的节点
- 打印链表
- 释放链表内存
9. **指针与引用的选择**
分析以下场景,说明应该使用指针还是引用,并解释原因:
- 函数需要返回多个值
- 函数参数可能是空值
- 实现多态
- 实现运算符重载
==== 思考题 ====
10. **深入理解**
- 解释为什么引用不能为NULL而指针可以
- 分析sizeof(指针)和sizeof(引用)的结果差异
- 讨论指针和引用在底层实现上的联系与区别
===== 8.10 小结 =====
本章深入学习了C++中指针和引用的概念:
- **指针**是存储内存地址的变量,通过解引用可以访问和修改目标数据
- **指针运算**允许我们以灵活的方式遍历数组和内存
- **引用**是变量的别名,提供了更安全的间接访问方式
- 引用作为函数参数时,语法更简洁且不易出错
- 指针和引用各有适用场景,需要根据实际情况选择
- 使用指针时要注意避免野指针、空指针解引用、内存泄漏等常见问题
掌握指针和引用是成为C++程序员的重要里程碑,它们是理解更高级概念(如动态内存、STL、多态)的基础。