C++ 指针与数组
在C++编程中,指针和数组是两个密切相关的概念。理解它们之间的关系对于掌握C++语言至关重要。本文将深入探讨C++中指针与数组的关系,从基础概念到实际应用,帮助初学者全面理解这两个重要概念。
数组与指针的基本关系
在C++中,数组名通常可以被视为指向数组第一个元素的指针。这是理解数组和指针关系的核心概念。
数组名作为指针
当我们声明一个数组时,数组名实际上代表了数组第一个元素的内存地址。
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
是非法的。
指针访问数组元素
我们既可以使用数组下标语法,也可以使用指针算术来访问数组元素。
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
指针算术运算
指针算术运算是指针与数组交互的重要特性。当对指针进行加减运算时,实际上是根据指针类型进行内存单元偏移。
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
指针算术必须谨慎,超出数组边界的指针操作会导致未定义行为,可能引起程序崩溃或不可预期的结果。
数组与指针的差异
虽然数组名可以作为指针使用,但数组与指针并不完全相同:
- 数组是一种数据结构,指针是一种变量类型
- 数组名代表固定内存块的起始地址,而指针可以指向任何地方
- sizeof操作符处理它们有明显不同
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 字节
指针数组和数组指针
指针数组
指针数组是一个数组,其元素是指针。
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
数组指针
数组指针是一个指针,指向一个数组。
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++中,多维数组与指针的关系更加复杂,特别是在处理二维或更高维度的数组时。
二维数组与指针
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++允许使用指针和动态内存分配来创建运行时大小的数组。
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++中字符串有两种表示方式:字符数组和字符指针。它们的行为和特性有所不同。
// 字符数组
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:简单的图像处理
假设我们有一个简单的灰度图像,使用二维数组表示。我们可以使用指针进行高效的图像操作:
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:实现一个简单的内存池
使用指针和数组实现一个简单的内存池,可以高效地分配和回收固定大小的内存块:
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++中,指针和数组有着密切的关系:
- 数组名可以视为指向首元素的指针
- 指针可以通过指针算术运算访问数组元素
- 尽管相似,但数组和指针在本质上是不同的
- 多维数组的指针操作更加复杂
- 指针与数组结合使用可以实现动态内存管理
- 理解指针和数组的关系对于编写高效的C++代码至关重要
掌握指针与数组的关系不仅有助于理解C++的内存模型,还能帮助我们编写更高效、更灵活的代码。无论是进行底层系统编程,还是开发高性能应用,这些概念都是不可或缺的基础。
练习题
- 编写一个函数,使用指针而不是数组下标遍历数组并计算所有元素的总和。
- 实现一个函数,使用指针交换两个数组的内容。
- 创建一个程序,使用指针动态分配一个二维数组,然后安全地释放它。
- 编写一个函数,接受一个整型数组和一个整数值,返回指向数组中第一个等于该值的元素的指针。如果没有找到,返回nullptr。
- 实现一个简单的字符串复制函数,使用指针而不是标准库函数。
进一步学习资源
- 《C++ Primer》中关于数组和指针的章节
- C++标准库中的
<array>
和<memory>
部分 - 在线资源:cppreference.com上关于指针和数组的文档
- 实践项目:尝试实现一个简单的矩阵计算库,充分利用指针和数组操作
通过持续实践和深入学习,你将能够熟练运用C++中的指针和数组,编写出高效且可靠的代码。