跳到主要内容

C++ 指针基础

什么是指针?

指针是C++中一种特殊的变量,它存储的是内存地址而不是实际的数据值。通过指针,我们可以间接地访问和操作存储在特定内存位置的数据。指针的这种特性使它成为C++中最强大但也最容易出错的特性之一。

在现代编程中,理解指针对于内存管理、数据结构实现以及高效操作系统级程序编写都至关重要。

内存地址与指针的关系

在计算机中,每一个变量都被存储在内存的某个位置,这个位置有一个唯一的地址(类似于街道上房子的门牌号)。

指针就是专门用来存储这些内存地址的变量。

指针的声明和初始化

在C++中,使用星号*来声明指针变量:

cpp
数据类型 *指针变量名;

例如,声明一个整型指针:

cpp
int *ptr;  // 声明一个指向整数的指针

初始化指针需要获取某个变量的地址,使用取地址运算符&

cpp
int number = 10;  // 普通整型变量
int *ptr = &number; // 初始化指针,使其指向number的地址

完整示例:

cpp
#include <iostream>
using namespace std;

int main() {
int number = 42; // 普通整型变量
int *ptr = &number; // 指针变量,存储number的地址

cout << "number的值: " << number << endl;
cout << "number的地址: " << &number << endl;
cout << "ptr存储的地址: " << ptr << endl;
cout << "ptr指向的值: " << *ptr << endl;

return 0;
}

输出:

number的值: 42
number的地址: 0x7ffee7dbc8c8 (地址值会因系统而异)
ptr存储的地址: 0x7ffee7dbc8c8 (与number地址相同)
ptr指向的值: 42

指针操作基础

1. 解引用操作符 (*)

解引用操作符*用于访问指针所指向的值:

cpp
int x = 10;
int *p = &x;

cout << *p; // 输出10,即x的值
*p = 20; // 修改x的值为20
cout << x; // 输出20

2. 指针与数组

在C++中,数组名本质上是指向数组第一个元素的指针:

cpp
int arr[5] = {10, 20, 30, 40, 50};
int *ptr = arr; // ptr指向arr[0]

cout << *ptr; // 输出10 (arr[0])
cout << *(ptr+1); // 输出20 (arr[1])
cout << *(ptr+2); // 输出30 (arr[2])

3. 指针算术

指针支持加减运算,但操作结果取决于指针类型:

cpp
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr;

p++; // p现在指向arr[1]
cout << *p; // 输出20

p = p + 2; // p现在指向arr[3]
cout << *p; // 输出40
警告

指针算术操作需谨慎,超出数组边界的操作会导致未定义行为!

4. 空指针

空指针不指向任何有效的内存位置:

cpp
int *ptr = nullptr;  // C++11及以后的空指针写法
// 旧版C++可以使用: int *ptr = NULL; 或 int *ptr = 0;

if (ptr == nullptr) {
cout << "这是一个空指针" << endl;
}
提示

始终在使用指针前检查它是否为空指针,以避免程序崩溃。

常见的指针类型

1. 整型指针

cpp
int number = 10;
int *ptr = &number;

2. 字符指针

cpp
char letter = 'A';
char *charPtr = &letter;

// 字符串与字符指针
char str[] = "Hello";
char *strPtr = str;

3. 指向结构体的指针

cpp
struct Person {
string name;
int age;
};

Person person1 = {"小明", 25};
Person *ptrPerson = &person1;

// 使用箭头操作符访问成员
cout << ptrPerson->name << endl; // 输出:小明
cout << ptrPerson->age << endl; // 输出:25

// 等价的写法
cout << (*ptrPerson).name << endl; // 输出:小明

指针与动态内存分配

C++中,我们可以使用newdelete操作符进行动态内存分配和释放:

动态分配单个变量

cpp
// 分配内存
int *ptr = new int;
*ptr = 10;

// 使用完毕后释放内存
delete ptr;
ptr = nullptr; // 避免悬空指针

动态分配数组

cpp
// 分配内存
int *arr = new int[5];
for(int i = 0; i < 5; i++) {
arr[i] = i * 10;
}

// 使用完毕后释放内存
delete[] arr; // 注意使用delete[]来释放数组
arr = nullptr;
注意

每次使用new分配内存后,必须相应地使用deletedelete[]释放内存,否则会导致内存泄漏。

实际应用案例

案例1:实现简单链表

链表是指针最典型的应用场景之一。以下是一个简单链表节点的实现:

cpp
#include <iostream>
using namespace std;

// 定义链表节点
struct Node {
int data;
Node* next;
};

int main() {
// 创建链表头节点
Node* head = new Node{10, nullptr};

// 添加第二个节点
head->next = new Node{20, nullptr};

// 添加第三个节点
head->next->next = new Node{30, nullptr};

// 遍历链表
Node* current = head;
while (current != nullptr) {
cout << current->data << " ";
current = current->next;
}
cout << endl;

// 释放内存
current = head;
while (current != nullptr) {
Node* temp = current;
current = current->next;
delete temp;
}

return 0;
}

输出:

10 20 30

案例2:函数参数中的指针应用

使用指针可以实现函数内部修改外部变量的值:

cpp
#include <iostream>
using namespace std;

// 通过指针交换两个数
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}

int main() {
int x = 5, y = 10;
cout << "交换前: x = " << x << ", y = " << y << endl;

swap(&x, &y);

cout << "交换后: x = " << x << ", y = " << y << endl;
return 0;
}

输出:

交换前: x = 5, y = 10
交换后: x = 10, y = 5

指针的常见错误与注意事项

  1. 未初始化的指针:使用前必须初始化,否则指向未知内存区域。

  2. 内存泄漏:动态分配的内存在不再需要时必须释放。

  3. 悬空指针:指向已释放的内存。

    cpp
    int* p = new int(10);
    delete p; // 内存已释放
    // p现在是悬空指针
    *p = 20; // 危险操作!可能导致程序崩溃
  4. 越界访问:不要访问数组边界外的元素。

指针与引用的区别

尽管指针和引用都可以间接访问变量,但它们有几个关键区别:

  • 引用必须在创建时初始化,指针可以稍后赋值
  • 引用不可重新绑定到其他对象,指针可以指向不同对象
  • 引用不能为空,指针可以是nullptr
  • 引用没有多级引用,指针可以有多级指针(指针的指针)
cpp
int x = 10, y = 20;
int& ref = x; // 引用
int* ptr = &x; // 指针

ref = 30; // 修改x的值为30
ptr = &y; // 将指针重新指向y
*ptr = 40; // 修改y的值为40

总结

指针是C++中强大而复杂的特性,它们允许程序员直接操作内存,实现高效灵活的代码。本文介绍了:

  • 指针的基本概念和内存模型
  • 如何声明和初始化指针
  • 指针操作(解引用、指针算术等)
  • 动态内存分配
  • 指针的实际应用

理解指针需要时间和实践,建议从简单例子开始,逐步掌握更复杂的用法。

练习题

  1. 编写一个函数,使用指针交换两个字符串的内容。
  2. 实现一个动态整数数组,支持添加、删除和打印元素的功能。
  3. 创建一个简单的链表,支持在链表头部和尾部添加元素。
  4. 编写一个函数,使用指针反转整数数组。

进一步学习资源

  • C++ Primer(第5版)- 经典的C++学习书籍,详细介绍了指针概念
  • 《Effective C++》- Scott Meyers著,包含许多关于指针安全使用的建议
  • cppreference.com - 在线C++参考资料库,包含详细的指针语法和用法

通过持续练习和阅读,你将能够熟练掌握C++指针,为更高级的编程打下坚实基础。