C++——字符串常量、二维数组、函数与指针的深度应用(补)

一、深入理解字符串常量

  • 字符与字符串的本质:

    • 在 C++ 中,单个字符用单引号 ' 包裹,例如 'A'
    • 字符串是由多个字符组成的序列,以空字符 \0 结尾。 \0 被称为尾零空终止符,它是一个特殊的非打印字符,用于标记字符串的结束位置。这是 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;
}

关键点总结:

  • 指针函数返回指针,需要注意内存管理。
  • 函数指针可以存储函数的地址,实现间接调用和函数作为参数传递。
相关推荐
二闹8 分钟前
三个注解,到底该用哪一个?别再傻傻分不清了!
后端
用户490558160812520 分钟前
当控制面更新一条 ACL 规则时,如何更新给数据面
后端
林太白21 分钟前
Nuxt.js搭建一个官网如何简单
前端·javascript·后端
码事漫谈23 分钟前
VS Code 终端完全指南
后端
该用户已不存在1 小时前
OpenJDK、Temurin、GraalVM...到底该装哪个?
java·后端
怀刃1 小时前
内存监控对应解决方案
后端
码事漫谈1 小时前
VS Code Copilot 内联聊天与提示词技巧指南
后端
Moonbit2 小时前
MoonBit Perals Vol.06: MoonBit 与 LLVM 共舞 (上):编译前端实现
后端·算法·编程语言
Moonbit2 小时前
MoonBit Perals Vol.06: MoonBit 与 LLVM 共舞(下):llvm IR 代码生成
后端·程序员·代码规范
Moonbit2 小时前
MoonBit Pearls Vol.05: 函数式里的依赖注入:Reader Monad
后端·rust·编程语言