一、深入理解字符串常量
-
字符与字符串的本质:
- 在 C++ 中,单个字符用单引号
'
包裹,例如'A'
。 - 字符串是由多个字符组成的序列,以空字符
\0
结尾。\0
被称为尾零 或空终止符,它是一个特殊的非打印字符,用于标记字符串的结束位置。这是 C 风格字符串的约定。
- 在 C++ 中,单个字符用单引号
-
'\0'
和' '
的区别:'\0'
(尾零) : 是一个 ASCII 码为 0 的字符。它不是 字符串内容的一部分,而是作为字符串结束的标记。当我们使用strlen
等函数计算字符串长度时,不会将\0
计算在内。' '
(空格字符) : 是一个普通的字符,其 ASCII 码为 32。空格是字符串内容的一部分,会被strlen
等函数计算在内。
-
空字符串
""
: 表示一个不包含任何字符的字符串,但它仍然包含一个尾零\0
。因此,空字符串的长度为 0。 -
空数组的意义: 虽然可以定义一个空的数组,例如
char empty_arr[0];
,但在实际应用中很少见,因为它无法存储任何元素。它可能在某些特定的底层编程或模板编程中会用到。 -
数组名与指针的紧密联系:
- 数组名作为常量指针 : 在大多数情况下,数组名可以隐式转换为指向数组第一个元素的常量指针。这意味着你不能修改数组名指向的地址,但可以通过数组名来访问和修改数组元素的值。
- 赋值给指针变量: 可以将数组名赋值给相同数据类型的指针变量,这样指针就指向了数组的起始位置。
示例代码及详细解释:
c
#include <iostream>
#include <cstring> // 包含 strlen 函数的头文件
int main() {
char str1[] = "Hello"; // 编译器会自动在 "Hello" 后面添加 '\0'
char str2[] = ""; // 空字符串,实际上是 {'\0'}
char str3[10]; // 定义一个可以存储 9 个字符的字符数组,留一个位置给 '\0'
char* p_str = str1; // 指针 p_str 指向 str1 的第一个元素 'H'
std::cout << "字符串 str1 的内容: " << str1 << std::endl;
std::cout << "字符串 str1 的长度 (不包含 '\0'): " << strlen(str1) << std::endl; // 输出 5
std::cout << "str1[0] 的地址: " << static_cast<void*>(&str1[0]) << std::endl; // 强制转换为 void* 以打印地址
std::cout << "p_str 指向的地址: " << static_cast<void*>(p_str) << std::endl; // 两者地址相同
// 注意:直接打印字符数组或字符指针,会输出整个字符串直到 '\0'
std::cout << "str2 的内容: " << str2 << std::endl;
std::cout << "字符串 str2 的长度: " << strlen(str2) << std::endl; // 输出 0
return 0;
}
关键点总结:
- 字符串常量以
\0
结尾。 - 空字符串
""
包含一个\0
。 - 数组名在很多情况下可以视为指向数组首元素的常量指针。
二、深入理解二维数组与行指针
二维数组可以看作是数组的数组,理解其内存布局和访问方式至关重要。
-
二维数组的内存模型:
- 在内存中,二维数组的元素是连续存储的,按行优先的方式排列。例如,
int matrix[2][3]
在内存中会依次存储matrix[0][0]
,matrix[0][1]
,matrix[0][2]
,matrix[1][0]
,matrix[1][1]
,matrix[1][2]
。
- 在内存中,二维数组的元素是连续存储的,按行优先的方式排列。例如,
-
行指针的概念:
- 二维数组的数组名 (
matrix
) 在表达式中使用时,会decay(退化)成指向其第一个行 (也就是第一个一维数组)的指针。这个指针的类型是"指向包含列数
个元素类型
的数组"的指针。在int matrix[2][3]
的例子中,matrix
的类型可以看作是指向int [3]
的指针。 - 这种指向一维数组的指针被称为行指针 或数组指针。
- 二维数组的数组名 (
-
指针数组 vs. 数组指针:
- 指针数组 : 是一个数组,其元素都是指针。例如:
int *ptr_arr[5];
表示一个包含 5 个int*
类型元素的数组,每个元素可以指向一个整数。 - 数组指针 : 是一个指针,它指向一个数组。例如:
int (*arr_ptr)[3];
表示一个指向包含 3 个整数的数组的指针。注意括号()
的必要性,它决定了运算的优先级。
- 指针数组 : 是一个数组,其元素都是指针。例如:
示例代码及详细解释:
c
#include <iostream>
int main() {
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
// matrix 是一个指向包含 3 个 int 的数组的指针 (行指针)
int (*p_row)[3] = matrix;
std::cout << "二维数组 matrix 的首地址: " << static_cast<void*>(matrix) << std::endl;
std::cout << "行指针 p_row 的值 (指向第一行): " << static_cast<void*>(p_row) << std::endl; // 与 matrix 地址相同
std::cout << "matrix[0] 的地址: " << static_cast<void*>(matrix[0]) << std::endl; // 第一行的首地址,与 matrix 相同
// p_row + 1 移动到下一行(移动了 3 个 int 的大小)
std::cout << "p_row + 1 的值 (指向第二行): " << static_cast<void*>(p_row + 1) << std::endl;
std::cout << "matrix[1] 的地址: " << static_cast<void*>(matrix[1]) << std::endl; // 第二行的首地址
// 使用行指针访问二维数组元素
// *(p_row + 1) 解引用行指针,得到第二行的首地址,类型是 int*
// *(p_row + 1) + 2 在第二行的首地址上偏移 2 个 int 的位置,指向 matrix[1][2]
// *(*(p_row + 1) + 2) 解引用,得到 matrix[1][2] 的值
std::cout << "matrix[1][2] 的值为: " << *(*(p_row + 1) + 2) << std::endl;
return 0;
}
关键点总结:
- 二维数组在内存中按行优先存储。
- 数组名可以退化为指向第一行的行指针。
- 理解指针数组和数组指针的区别。
三、函数与指针的深度应用
指针在函数中扮演着重要的角色,可以用于传递数据、返回结果,甚至传递函数本身。
-
指针函数:返回指针的函数
- 指针函数的返回值是一个指针类型。这使得函数可以返回动态分配的内存地址,或者指向在函数外部定义的变量的地址。
- 需要注意内存管理,避免返回指向局部变量的指针,因为局部变量在函数执行结束后会被销毁。
示例代码:
c
#include <iostream>
// 指针函数:返回动态分配的 int 数组
int* createArray(int size) {
int* arr = new int[size];
for (int i = 0; i < size; ++i) {
arr[i] = i * 2;
}
return arr; // 返回指向动态分配内存的指针
}
int main() {
int* myArray = createArray(5);
if (myArray != nullptr) {
std::cout << "动态数组的元素: ";
for (int i = 0; i < 5; ++i) {
std::cout << myArray[i] << " ";
}
std::cout << std::endl;
delete[] myArray; // 记得释放动态分配的内存
myArray = nullptr;
}
return 0;
}
-
函数指针:指向函数的指针
- 函数指针存储的是函数的入口地址。通过函数指针,我们可以间接地调用函数,可以将函数作为参数传递给其他函数,或者存储在一组函数列表中。
- 函数指针的声明需要指定函数的返回类型和参数列表。
示例代码及详细解释:
c
#include <iostream>
// 一个简单的加法函数
int add(int a, int b) {
return a + b;
}
// 一个使用函数指针作为参数的函数
void executeOperation(int a, int b, int (*operation)(int, int)) {
std::cout << "执行结果: " << operation(a, b) << std::endl;
}
int main() {
// 声明一个指向返回 int,接受两个 int 参数的函数的指针
int (*funcPtr)(int, int);
// 将 add 函数的地址赋值给 funcPtr
funcPtr = add;
// 通过函数指针调用 add 函数
int result = funcPtr(5, 3);
std::cout << "通过函数指针调用 add: " << result << std::endl;
// 将 add 函数作为参数传递给 executeOperation 函数
executeOperation(10, 5, add);
return 0;
}
关键点总结:
- 指针函数返回指针,需要注意内存管理。
- 函数指针可以存储函数的地址,实现间接调用和函数作为参数传递。