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;
}

关键点总结:

  • 指针函数返回指针,需要注意内存管理。
  • 函数指针可以存储函数的地址,实现间接调用和函数作为参数传递。
相关推荐
weixin_456904275 小时前
Spring Boot 用户管理系统
java·spring boot·后端
cyforkk6 小时前
Spring 异常处理器:从混乱到有序,优雅处理所有异常
java·后端·spring·mvc
程序员爱钓鱼7 小时前
Go语言实战案例-开发一个Markdown转HTML工具
前端·后端·go
桦说编程7 小时前
爆赞!完全认同!《软件设计的哲学》这本书深得我心
后端
thinktik7 小时前
还在手把手教AI写代码么? 让你的AWS Kiro AI IDE直接读飞书需求文档给你打工吧!
后端·serverless·aws
老青蛙9 小时前
权限系统设计-用户设计
后端
echoyu.10 小时前
消息队列-初识kafka
java·分布式·后端·spring cloud·中间件·架构·kafka
yuluo_YX10 小时前
Go Style 代码风格规范
开发语言·后端·golang
David爱编程10 小时前
从 JVM 到内核:synchronized 与操作系统互斥量的深度联系
java·后端