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

关键点总结:

  • 指针函数返回指针,需要注意内存管理。
  • 函数指针可以存储函数的地址,实现间接调用和函数作为参数传递。
相关推荐
earthzhang20213 小时前
第3讲:Go垃圾回收机制与性能优化
开发语言·jvm·数据结构·后端·性能优化·golang
thinktik5 小时前
AWS EKS 集成Load Balancer Controller 对外暴露互联网可访问API [AWS 中国宁夏区]
后端·kubernetes·aws
追逐时光者5 小时前
将 EasySQLite 解决方案文件格式从 .sln 升级为更简洁的 .slnx
后端·.net
驰羽5 小时前
[GO]GORM 常用 Tag 速查手册
开发语言·后端·golang
AntBlack6 小时前
虽迟但到 :盘一盘 SpringAI 现在发展得怎么样了?
后端·spring·openai
ss2737 小时前
手写Spring第4弹: Spring框架进化论:15年技术变迁:从XML配置到响应式编程的演进之路
xml·java·开发语言·后端·spring
舒一笑8 小时前
🚀 PandaCoder 2.0.0 - ES DSL Monitor & SQL Monitor 震撼发布!
后端·ai编程·intellij idea
Java中文社群8 小时前
服务器被攻击!原因竟然是他?真没想到...
java·后端
helloworddm9 小时前
Orleans 流系统握手机制时序图
后端·c#
开心-开心急了10 小时前
Flask入门教程——李辉 第三章 关键知识梳理
后端·python·flask