跳到主要内容

C++ 指针与数组

在C++编程中,指针和数组是两个密切相关的概念。理解它们之间的关系对于掌握C++语言至关重要。本文将深入探讨C++中指针与数组的关系,从基础概念到实际应用,帮助初学者全面理解这两个重要概念。

数组与指针的基本关系

在C++中,数组名通常可以被视为指向数组第一个元素的指针。这是理解数组和指针关系的核心概念。

数组名作为指针

当我们声明一个数组时,数组名实际上代表了数组第一个元素的内存地址。

cpp
int numbers[5] = {10, 20, 30, 40, 50};

// 下面两种表达式是等价的
cout << numbers[0] << endl; // 输出: 10
cout << *numbers << endl; // 输出: 10

// 数组名是第一个元素的地址
cout << "数组首地址: " << numbers << endl;
cout << "首元素地址: " << &numbers[0] << endl;

输出:

10
10
数组首地址: 0x7ffd123456a0
首元素地址: 0x7ffd123456a0
备注

虽然数组名可以被视为指针,但它是一个"常量指针",不能被修改指向其他位置。例如,numbers = &someVariable 是非法的。

指针访问数组元素

我们既可以使用数组下标语法,也可以使用指针算术来访问数组元素。

cpp
int numbers[5] = {10, 20, 30, 40, 50};
int* ptr = numbers; // 指针指向数组第一个元素

// 使用数组下标语法
for (int i = 0; i < 5; i++) {
cout << "numbers[" << i << "] = " << numbers[i] << endl;
}

cout << "-----------------------" << endl;

// 使用指针算术
for (int i = 0; i < 5; i++) {
cout << "*(ptr + " << i << ") = " << *(ptr + i) << endl;
}

输出:

numbers[0] = 10
numbers[1] = 20
numbers[2] = 30
numbers[3] = 40
numbers[4] = 50
-----------------------
*(ptr + 0) = 10
*(ptr + 1) = 20
*(ptr + 2) = 30
*(ptr + 3) = 40
*(ptr + 4) = 50

指针算术运算

指针算术运算是指针与数组交互的重要特性。当对指针进行加减运算时,实际上是根据指针类型进行内存单元偏移。

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

cout << "ptr 指向的值: " << *ptr << endl; // 10
cout << "ptr+1 指向的值: " << *(ptr+1) << endl; // 20
cout << "ptr+2 指向的值: " << *(ptr+2) << endl; // 30

ptr++; // 指针向后移动一个整型大小
cout << "ptr++ 后指向的值: " << *ptr << endl; // 20

输出:

ptr 指向的值: 10
ptr+1 指向的值: 20
ptr+2 指向的值: 30
ptr++ 后指向的值: 20
警告

指针算术必须谨慎,超出数组边界的指针操作会导致未定义行为,可能引起程序崩溃或不可预期的结果。

数组与指针的差异

虽然数组名可以作为指针使用,但数组与指针并不完全相同:

  1. 数组是一种数据结构,指针是一种变量类型
  2. 数组名代表固定内存块的起始地址,而指针可以指向任何地方
  3. sizeof操作符处理它们有明显不同
cpp
int numbers[5] = {10, 20, 30, 40, 50};
int* ptr = numbers;

cout << "sizeof(numbers): " << sizeof(numbers) << " 字节" << endl; // 5 * 4 = 20字节
cout << "sizeof(ptr): " << sizeof(ptr) << " 字节" << endl; // 4或8字节,取决于系统

输出(在64位系统上):

sizeof(numbers): 20 字节
sizeof(ptr): 8 字节

指针数组和数组指针

指针数组

指针数组是一个数组,其元素是指针。

cpp
int a = 10, b = 20, c = 30;
int* ptr_array[3]; // 指针数组

ptr_array[0] = &a;
ptr_array[1] = &b;
ptr_array[2] = &c;

for (int i = 0; i < 3; i++) {
cout << "ptr_array[" << i << "] 指向的值: " << *ptr_array[i] << endl;
}

输出:

ptr_array[0] 指向的值: 10
ptr_array[1] 指向的值: 20
ptr_array[2] 指向的值: 30

数组指针

数组指针是一个指针,指向一个数组。

cpp
int numbers[5] = {10, 20, 30, 40, 50};
int (*ptr_to_array)[5] = &numbers; // 指针指向整个数组

cout << "通过数组指针访问第一个元素: " << (*ptr_to_array)[0] << endl;
cout << "通过数组指针访问第三个元素: " << (*ptr_to_array)[2] << endl;

输出:

通过数组指针访问第一个元素: 10
通过数组指针访问第三个元素: 30

多维数组与指针

在C++中,多维数组与指针的关系更加复杂,特别是在处理二维或更高维度的数组时。

二维数组与指针

cpp
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};

// matrix 是指向包含4个整数的数组的指针
int (*ptr)[4] = matrix;

cout << "matrix[1][2] = " << matrix[1][2] << endl;
cout << "ptr[1][2] = " << ptr[1][2] << endl;
cout << "*(*(matrix+1)+2) = " << *(*(matrix+1)+2) << endl;

输出:

matrix[1][2] = 7
ptr[1][2] = 7
*(*(matrix+1)+2) = 7

指针与动态数组

C++允许使用指针和动态内存分配来创建运行时大小的数组。

cpp
int size;
cout << "请输入数组大小: ";
cin >> size;

// 动态分配数组
int* dynamic_array = new int[size];

// 初始化数组
for (int i = 0; i < size; i++) {
dynamic_array[i] = i * 10;
}

// 使用数组
cout << "动态数组内容: ";
for (int i = 0; i < size; i++) {
cout << dynamic_array[i] << " ";
}
cout << endl;

// 释放内存
delete[] dynamic_array;

输出(假设用户输入5):

请输入数组大小: 5
动态数组内容: 0 10 20 30 40
注意

动态分配的内存必须在不再需要时通过delete[]释放,否则会导致内存泄漏。

字符数组与指针

C/C++中字符串有两种表示方式:字符数组和字符指针。它们的行为和特性有所不同。

cpp
// 字符数组
char str1[] = "Hello";
// 字符指针
const char* str2 = "World";

cout << "str1: " << str1 << endl;
cout << "str2: " << str2 << endl;

// 可以修改str1的内容
str1[0] = 'J';
cout << "修改后的str1: " << str1 << endl;

// str2指向的字符串是常量,不能直接修改其内容
// str2[0] = 'J'; // 这会导致未定义行为,可能造成程序崩溃

输出:

str1: Hello
str2: World
修改后的str1: Jello

实际应用案例

案例1:简单的图像处理

假设我们有一个简单的灰度图像,使用二维数组表示。我们可以使用指针进行高效的图像操作:

cpp
const int HEIGHT = 3;
const int WIDTH = 4;

// 模拟灰度图像
unsigned char image[HEIGHT][WIDTH] = {
{50, 60, 70, 80},
{90, 100, 110, 120},
{130, 140, 150, 160}
};

// 使用指针提高图像亮度
void increaseBrightness(unsigned char* img, int size, int delta) {
for (int i = 0; i < size; i++) {
// 确保值不超过255
int newValue = img[i] + delta;
img[i] = (newValue > 255) ? 255 : newValue;
}
}

// 打印图像
void printImage(unsigned char image[HEIGHT][WIDTH]) {
for (int i = 0; i < HEIGHT; i++) {
for (int j = 0; j < WIDTH; j++) {
cout << setw(4) << (int)image[i][j];
}
cout << endl;
}
}

cout << "原始图像:" << endl;
printImage(image);

// 增加亮度
increaseBrightness((unsigned char*)image, HEIGHT * WIDTH, 50);

cout << "\n增亮后图像:" << endl;
printImage(image);

输出:

原始图像:
50 60 70 80
90 100 110 120
130 140 150 160

增亮后图像:
100 110 120 130
140 150 160 170
180 190 200 210

案例2:实现一个简单的内存池

使用指针和数组实现一个简单的内存池,可以高效地分配和回收固定大小的内存块:

cpp
class SimpleMemoryPool {
private:
static const int POOL_SIZE = 10;
static const int BLOCK_SIZE = 32; // 每个内存块32字节

char memory[POOL_SIZE][BLOCK_SIZE];
bool used[POOL_SIZE]; // 记录哪些块已被使用

public:
SimpleMemoryPool() {
memset(used, 0, sizeof(used)); // 初始化所有块为未使用
}

void* allocate() {
for (int i = 0; i < POOL_SIZE; i++) {
if (!used[i]) {
used[i] = true;
cout << "分配内存块 " << i << endl;
return memory[i];
}
}
cout << "内存池已满,无法分配" << endl;
return nullptr;
}

void deallocate(void* ptr) {
ptrdiff_t start = (char*)memory - (char*)nullptr;
ptrdiff_t addr = (char*)ptr - (char*)nullptr;
ptrdiff_t offset = addr - start;

if (offset >= 0 && offset < POOL_SIZE * BLOCK_SIZE && offset % BLOCK_SIZE == 0) {
int index = offset / BLOCK_SIZE;
if (used[index]) {
used[index] = false;
cout << "释放内存块 " << index << endl;
}
}
}
};

SimpleMemoryPool pool;

// 分配三个内存块
void* block1 = pool.allocate();
void* block2 = pool.allocate();
void* block3 = pool.allocate();

// 释放第二个内存块
pool.deallocate(block2);

// 再分配一个,应该会得到刚才释放的块
void* block4 = pool.allocate();

输出:

分配内存块 0
分配内存块 1
分配内存块 2
释放内存块 1
分配内存块 1

总结

在C++中,指针和数组有着密切的关系:

  1. 数组名可以视为指向首元素的指针
  2. 指针可以通过指针算术运算访问数组元素
  3. 尽管相似,但数组和指针在本质上是不同的
  4. 多维数组的指针操作更加复杂
  5. 指针与数组结合使用可以实现动态内存管理
  6. 理解指针和数组的关系对于编写高效的C++代码至关重要

掌握指针与数组的关系不仅有助于理解C++的内存模型,还能帮助我们编写更高效、更灵活的代码。无论是进行底层系统编程,还是开发高性能应用,这些概念都是不可或缺的基础。

练习题

  1. 编写一个函数,使用指针而不是数组下标遍历数组并计算所有元素的总和。
  2. 实现一个函数,使用指针交换两个数组的内容。
  3. 创建一个程序,使用指针动态分配一个二维数组,然后安全地释放它。
  4. 编写一个函数,接受一个整型数组和一个整数值,返回指向数组中第一个等于该值的元素的指针。如果没有找到,返回nullptr。
  5. 实现一个简单的字符串复制函数,使用指针而不是标准库函数。

进一步学习资源

  • 《C++ Primer》中关于数组和指针的章节
  • C++标准库中的<array><memory>部分
  • 在线资源:cppreference.com上关于指针和数组的文档
  • 实践项目:尝试实现一个简单的矩阵计算库,充分利用指针和数组操作

通过持续实践和深入学习,你将能够熟练运用C++中的指针和数组,编写出高效且可靠的代码。