C/C++ 每日一练:实现一个字符串(C 风格 / 中文)反转函数

字符串(C 风格)

题目要求

         编写一个函数,接受一个字符串作为输入,并返回该字符串的反转版本。例如,输入字符串 "hello" 应输出 "olleh"。

功能要求

  1. 函数应能够处理不同长度的字符串,包括空字符串。
  2. 输入字符串只包含字母、数字和常规字符(不包含多字节字符)。
  3. 不允许使用库函数,如 strrev 、std::reverse或类似的专门用于字符串反转的函数,要求自己实现。

思路解析

  1. 字符串的存储方式:在 C 语言中,字符串是以字符数组形式存储的,并且以 \0(空字符)作为结束符。因此,需要通过遍历数组来实现字符串的反转。

  2. 反转算法

    • 使用两个指针,一个指向字符串的开头,另一个指向字符串的末尾。
    • 交换这两个指针所指向的字符,并向中间推进,直到两个指针相遇或交错。
    • 这样,在原地反转字符串,避免额外的空间消耗。
  3. 边界情况:需要考虑如下特殊情况:

    • 空字符串:直接返回空字符串。
    • 单字符字符串:无需处理,直接返回原字符串。
    • 两个指针相遇或交错的判断条件,例如start < end。

过程解析

  1. 输入检查:首先检查输入字符串是否为空或长度为 1,如果是,直接返回。
  2. 指针操作:使用两个指针分别指向字符串的起始位置和结束位置,然后交换这两个位置的字符。
  3. 逐步推进指针:每次交换后,起始指针向后移动,结束指针向前移动,直到它们相遇或交错。
  4. 返回结果:反转完成后,返回修改后的字符串。

运用的知识点

  1. 指针操作:C/C++ 中处理字符串时,往往会用到指针进行遍历和操作,尤其是对字符数组的处理。
  2. 数组操作:了解数组的基本操作和边界检查。
  3. 字符交换:在反转过程中需要交换字符,通过一个中间变量完成。
  4. 条件判断和循环:用 while 循环来实现字符串的遍历,并通过条件判断来处理边界情况。

示例代码

C 示例
#include <stdio.h>
#include <string.h>// 字符串反转函数
void reverseString(char *str) {if (str == NULL) {return;  // 如果输入为空,直接返回}int len = strlen(str);  // 获取字符串的长度(长度不包括末尾的 \0 字符)int start = 0;          // 起始指针,指向字符串开头int end = len - 1;      // 结束指针,指向字符串末尾// 当 start 小于 end 时,交换字符while (start < end) {// 交换 str[start] 和 str[end]char temp = str[start];str[start] = str[end];str[end] = temp;// 更新指针位置start++;end--;}
}int main() 
{char str[] = "hello";  // 输入字符串printf("原始字符串: %s\n", str);reverseString(str);  // 调用反转函数printf("反转后的字符串: %s\n", str);  // 输出结果return 0;
}
C++ 示例
#include <iostream>
#include <cstring>  // 包含 C 风格字符串的函数// 字符串反转函数
void reverseString(char *str) {if (str == nullptr) // nullptr是C++11引入的关键字,用于表示空指针,比C语言中的 NULL 更加类型安全。{return;  // 如果输入为空,直接返回}int len = strlen(str);  // 获取字符串长度int start = 0;          // 起始位置int end = len - 1;      // 结束位置// 交换字符while (start < end) {std::swap(str[start], str[end]);  // 使用 C++ 的 swap 函数进行交换start++;end--;}
}int main() {char str[] = "world";  // 输入字符串std::cout << "原始字符串: " << str << std::endl;reverseString(str);  // 调用反转函数std::cout << "反转后的字符串: " << str << std::endl;  // 输出结果return 0;
}

字符串(中文)

        中文字符串在内存中占用的是多个字节(通常是 UTF-8 编码下的 3 个字节),因此不能像处理英文(只占用 1 个字节)一样简单地逐个字符交换。如果直接逐字节交换,会破坏字符编码,导致输出乱码。因此,处理多字节字符需要更加小心,确保每次操作一个完整的字符。

       注意,这里使用的方式同样适用于中英文混合的情况。

思路解析

  1. UTF-8 编码:UTF-8 是一种变长的字符编码,对于英文字符来说是 1 个字节,而对于中文字符,通常需要 3 个字节。为了正确地处理中文字符,首先需要识别出每个字符的边界,然后对字符进行反转。

  2. 遍历和识别字符:需要逐字遍历字符串,判断当前字符占用的字节数(1 字节表示 ASCII 字符,3 字节表示中文字符)。每次读取一个完整字符并存储下来,最后再进行反转。

过程解析

  1. 逐字解析:逐个解析 UTF-8 字符,根据其前导字节(本文后面有解释)判断该字符是 1 字节还是多字节字符。
    • 对于 1 字节的 ASCII 字符,直接处理。
    • 对于多字节字符(如中文),根据 UTF-8 的编码规则解析其长度。
  2. 存储并反转:将每个完整的字符(包括中文)存储到一个数组中,然后再按字符顺序进行反转。

示例代码

C 示例
#include <stdio.h>  // 引入标准输入输出库  
#include <stdlib.h> // 引入标准库,用于动态内存分配等  
#include <string.h> // 引入字符串处理库  // 判断当前字符的字节数(UTF-8 编码)  
int getUTF8CharLength(unsigned char ch) {if ((ch & 0x80) == 0) {         // 如果最高位为0,则是单字节字符(ASCII)  return 1;}else if ((ch & 0xC0) == 0xC0) { // 如果最高两位为110,则是双字节字符  return 2;}else if ((ch & 0xE0) == 0xE0) { // 如果最高三位为1110,则是三字节字符(常用于中文字符)  return 3;}else if ((ch & 0xF8) == 0xF0) { // 如果最高四位为11110,则是四字节字符(特殊符号)  return 4;}return 1;  // 默认按 1 字节处理,防止错误(理论上这种情况不应该发生)  
}// 反转 UTF-8 编码的字符串  
void reverseUTF8String(const char* str, char* reversedStr) {int len = (int)strlen(str);  // 获取字符串长度,并转换为int类型  int i = 0;  // 用于遍历原始字符串的索引  int j = 0;  // 用于遍历字符长度数组的索引  // 动态分配内存  char* temp = (char*)malloc(len + 1);  // 为临时字符串分配内存,+1用于存储结束符'\0'  int* charLens = (int*)malloc(len * sizeof(int));  // 为字符长度数组分配内存  int index = 0;  // temp 和 charLens 的索引  // 逐字符解析  while (i < len) {int charLen = getUTF8CharLength((unsigned char)str[i]);  // 获取当前字符的字节数  charLens[index] = charLen;  // 记录该字符的长度  // 拷贝当前字符到临时字符串中  // 注意:strncpy_s 是 Microsoft 的安全函数,标准 C 中使用 strncpy,但 strncpy 不会自动添加 '\0'  // 这里假设 strncpy_s 的行为是拷贝指定长度的字符,并在目标缓冲区末尾添加 '\0'(如果缓冲区足够大)  // 但由于 strncpy_s 不是标准 C 函数,这里应使用 strncpy 并手动处理 '\0'  // strncpy_s(temp + index, len + 1 - index, str + i, charLen);  strncpy(temp + index, str + i, charLen);  // 使用标准 C 函数  temp[index + charLen] = '\0';  // 手动添加 '\0',确保字符串正确结束  index += charLen;i += charLen;}// 反转字符:从最后一个字符开始拷贝到输出字符串中  int pos = 0;  // 用于记录 reversedStr 的当前位置  for (i = index - charLens[j]; i >= 0; i -= charLens[j++]) {// 拷贝字符到 reversedStr 中,注意这里同样应该使用 strncpy 并手动处理 '\0'  // 但由于我们已经知道每个字符的确切长度,并且 reversedStr 足够大,可以直接拷贝  // strncpy_s(reversedStr + pos, len + 1 - pos, temp + i, charLens[j]);  strncpy(reversedStr + pos, temp + i, charLens[j]);reversedStr[pos + charLens[j]] = '\0';  // 实际上这一步是多余的,因为后面会覆盖  pos += charLens[j];}// 添加字符串结束符(理论上这一步是多余的,因为我们已经逐个字符地添加了结束符)  // 但为了代码的清晰性和防止意外情况,还是加上  reversedStr[pos] = '\0';// 释放内存  free(temp);  // 释放临时字符串的内存  free(charLens);  // 释放字符长度数组的内存  
}int main() 
{const char* str = "你好,世界!Hello, World!";  // 输入字符串(中英文混合)  char reversedStr[100];  // 预留足够的空间用于存储反转后的字符串  printf("原始字符串: %s\n", str);  // 输出原始字符串  reverseUTF8String(str, reversedStr);  // 调用反转函数  printf("反转后的字符串: %s\n", reversedStr);  // 输出反转后的字符串  return 0;  // 程序正常结束  
}
C++ 示例

         由于 UTF-8 编码的特性,我们可以使用 C++ 中的 std::string 和 STL 来处理中文字符。通过利用 std::string 的迭代器来解析字符,可以很方便地实现这一功能。

#include <iostream>  // 引入输入输出流库,用于输入输出操作  
#include <string>    // 引入字符串库,用于字符串操作  
#include <vector>    // 引入向量库,用于存储动态数组  // 判断当前字符的字节数(UTF-8 编码)  
int getUTF8CharLength(unsigned char ch) {  // 如果最高位为0,则是单字节字符(ASCII)  if ((ch & 0x80) == 0) {    return 1;  }   // 如果最高两位为110,则是双字节字符  else if ((ch & 0xC0) == 0xC0) {    return 2;  }   // 如果最高三位为1110,则是三字节字符(常用于中文字符)  else if ((ch & 0xE0) == 0xE0) {    return 3;  }   // 如果最高四位为11110,则是四字节字符(一些特殊符号)  else if ((ch & 0xF8) == 0xF0) {    return 4;  }  // 默认按 1 字节处理,防止错误(理论上这种情况不应该发生,因为UTF-8编码规则已覆盖所有情况)  return 1;    
}  // 反转 UTF-8 编码的字符串  
std::string reverseUTF8String(const std::string& str) {  std::vector<std::string> chars;  // 使用向量存储每个完整字符的字符串  // 遍历字符串,解析出每个完整字符  for (size_t i = 0; i < str.size(); ) {  // 使用size_t类型索引,因为它是专门用于表示大小和索引的无符号整数类型  int charLen = getUTF8CharLength(static_cast<unsigned char>(str[i]));  // 获取当前字符的字节数  chars.push_back(str.substr(i, charLen));  // 从当前位置开始,截取指定长度的子字符串,并添加到向量中  i += charLen;  // 更新索引,移动到下一个字符的起始位置  }  // 使用标准库算法反转字符向量  std::reverse(chars.begin(), chars.end());  // 将反转后的字符拼接成新的字符串  std::string reversedStr;  for (const auto& ch : chars) {  // 使用范围for循环遍历向量中的每个字符串  reversedStr += ch;  // 将每个字符串拼接到新的字符串中  }  return reversedStr;  // 返回反转后的字符串  
}  int main()   
{  std::string str = "你好,世界!Hello, World!";  // 定义一个包含中文和英文字符的字符串  std::cout << "原始字符串: " << str << std::endl;  // 输出原始字符串  std::string reversedStr = reverseUTF8String(str);  // 调用反转函数,获取反转后的字符串  std::cout << "反转后的字符串: " << reversedStr << std::endl;  // 输出反转后的字符串  return 0;  // 程序正常结束  
}

前导字节

        前导字节(Leading Byte)是用于标识一个多字节字符开始位置的字节,特别是在像 UTF-8 这样使用变长编码的字符集里。通过前导字节,我们可以判断该字符需要多少个字节进行存储。

         在 UTF-8 编码中,每个字符根据其 Unicode 码点的大小会使用 1 到 4 个字节来表示:

  • 对于 ASCII 字符(如英文字符),仅用 1 个字节,范围是 0x000x7F(0 到 127 的十进制数)。
  • 对于超出 ASCII 范围的字符(如中文、表情符号等),需要使用 2、3 或 4 个字节。

         通过每个字符的第一个字节(即前导字节),可以判断该字符使用了多少个字节。前导字节的高位比特数决定了字符的字节长度。

前导字节的格式

         UTF-8 的每个字符的前导字节可以按照以下规则进行识别:

字节数前导字节格式范围 (十六进制)范围 (二进制)描述
1 字节0xxxxxxx00 - 7F00000000 - 01111111适用于 ASCII 字符
2 字节110xxxxxC0 - DF11000000 - 11011111多字节字符的第一个字节
3 字节1110xxxxE0 - EF11100000 - 11101111多字节字符的第一个字节,通常是中文
4 字节11110xxxF0 - F711110000 - 11110111多字节字符的第一个字节,特殊符号等

         根据第一个字节(前导字节)的二进制格式,可以判断字符的总字节数:

  • 如果前导字节是 0xxxxxxx,那么这是一个 1 字节字符(ASCII 字符)。
  • 如果前导字节是 110xxxxx,这是一个 2 字节的字符。
  • 如果前导字节是 1110xxxx,这是一个 3 字节的字符(常见于中文)。
  • 如果前导字节是 11110xxx,这是一个 4 字节的字符。
后续字节

        每个多字节字符的后续字节都以 10xxxxxx 的形式开始,也就是高两位固定为 10。范围是 0x80 到 0xBF。

举例说明
  • ASCII 字符
    • 字符 'A' 的 UTF-8 编码是 0x41(十进制 65),属于 1 字节的字符,其二进制表示为 01000001,前导字节是 0xxxxxxx。
  • 中文字符
    • 字符 "你" 的 UTF-8 编码是 0xE4 0xBD 0xA0(3 字节),其第一个字节是 0xE4,二进制表示为 11100100,前导字节格式为 1110xxxx,表示这是一个 3 字节的字符。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/55574.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

「Python精品教程」Python快速入门,基础数据结构:数字

​***奕澄羽邦精品教程系列*** 编程环境&#xff1a; 1、Python 3.12.5 2、Visual Studio Code 1.92.1 在现实世界中&#xff0c;我们经常要面对各式各样的数字&#xff0c;通过简单或者复杂的数学运算&#xff0c;来帮助我们计算出想要的结果。程序开发过程中&#xff0c;数字…

Spring Boot + Vue 前后端分离项目总结:解决 CORS 和 404 问题

Spring Boot Vue 前后端分离项目总结&#xff1a;解决 CORS 和 404 问题 在进行前后端分离的项目开发中&#xff0c;我们遇到了几个关键问题&#xff1a;跨域问题 (CORS) 和 404 路由匹配错误。以下是这些问题的详细分析和最终的解决方案。 问题描述 跨域请求被阻止 (CORS) 当…

.net core 实现多线程方式有哪些

在 .NET Core 中&#xff0c;有多种方式可以实现多线程编程。这些方式包括使用 Thread 类、Task 和 Parallel 类库。每种方法都有其适用场景和优缺点。下面我将通过代码示例来展示这些不同的多线程实现方式。 1. 使用 Thread 类 Thread 类是 .NET 中最基本的多线程实现方式。…

自动化测试工具在API测试中的优势是什么?

在设计API接口时&#xff0c;确保数据获取的效率和准确性是至关重要的。以下是一些最佳实践和代码示例&#xff0c;帮助你提高API的数据获取效率和准确性。 1. 使用高效的数据访问模式 选择合适的数据库访问模式对于提高数据获取效率至关重要。例如&#xff0c;使用索引可以显…

【启明智显分享】ZX7981PM WIFI6 5G-CPE:2.5G WAN口,2.4G/5G双频段自动调速

昨天&#xff0c;我们向大家展现了ZX7981PG WIFI6 5G-CPE&#xff0c;它强大的性能也引起了一波关注&#xff0c;与此同时&#xff0c;我们了解到部分用户对更高容量与更高速网口的需求。没关系&#xff01;启明智显早就预料到了&#xff01;ZX7981PM满足你的需求&#xff01; …

Vue3 集成Monaco Editor编辑器

Vue3 集成Monaco Editor编辑器 1. 安装依赖2. 使用3. 效果 Monaco Editor &#xff08;官方链接 https://microsoft.github.io/monaco-editor/&#xff09;是一个由微软开发的功能强大的在线代码编辑器&#xff0c;被广泛应用于各种 Web 开发场景中。以下是对 Monaco Editor 的…

人大金仓 V8 数据库环境设置与 Spring Boot 集成配置指南

人大金仓 V8 数据库环境设置与 Spring Boot 集成配置指南 本文介绍了如何设置人大金仓 V8 数据库&#xff0c;并与 Spring Boot 应用程序进行集成。首先&#xff0c;讲解了如何通过 ksql 命令行工具创建用户、数据库&#xff0c;并授予权限。同时&#xff0c;文章展示了如何加…

【设计模式】深入理解Python中的抽象工厂设计模式

深入理解Python中的抽象工厂设计模式 设计模式是软件开发中解决常见问题的经典方案&#xff0c;而**抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;**是其中非常重要的一种创建型模式。抽象工厂模式的主要作用是提供一个接口&#xff0c;创建一系列相关或依赖…

HTML5教程(三)- 常用标签

1 文本标签-h 标题标签&#xff08;head&#xff09;&#xff1a; 自带加粗效果&#xff0c;从h1到h6字体大小逐级递减一个标题独占一行 语法 <h1>一级标题</h1><h2>二级标题</h2><h3>三级标题</h3><h4>四级标题</h4><h5…

vLLM 部署大模型问题记录

文章目录 部署前置工作下载 vLLM Docker 镜像下载模型 Qwen2.5-72B-Instruct-GPTQ-Int4启动指令接口文档地址&#xff1a;http://localhost:8001/docs问题记录 Llama-3.2-11B-Vision-Instruct启动指令接口文档地址&#xff1a;http://localhost:8001/docs问题记录 Qwen2-Audio-…

关于md5强比较和弱比较绕过的实验

在ctf比赛题中我们的md5强弱比较的绕过题型很多&#xff0c;大部分都是结合了PHP来进行一个考核。这一篇文章我将讲解一下最基础的绕过知识。 MD5弱比较 比较的步骤 在进行弱比较时&#xff0c;PHP会按照以下步骤执行&#xff1a; 确定数据类型&#xff1a;检查参与比较的两…

jmeter响应断言放进csv文件遇到的问题

用Jmeter的json 断言去测试http请求响应结果&#xff0c;发现遇到中文时出现乱码&#xff0c;导致无法正常进行响应断言&#xff0c;很影响工作。于是&#xff0c;察看了其他测试人员的解决方案&#xff0c;发现是jmeter本身对编码格式的设置导致了这一问题。解决方案是在jmete…

【文化课学习笔记】【化学】选必三:同分异构体的书写

【化学】选必三&#xff1a;同分异构体的书写 如果你是从 B 站一化儿笔记区来的&#xff0c;请先阅读我在第一篇有机化学笔记中的「读前须知」(点开头的黑色小三角展开)&#xff1a;链接 链状烃的取代和插空法 取代法 一取代物 甲烷、乙烷、丙烷、丁烷的种类 甲烷&#xff1a;只…

Java中集合类型的转换

在Java编程中&#xff0c;集合框架&#xff08;Collections Framework&#xff09;提供了一套用于存储和处理对象集合的接口和类。由于集合框架的灵活性和强大功能&#xff0c;我们经常需要在不同的集合类型之间进行转换。本文将介绍Java中常见的集合类型转换方法&#xff0c;包…

游戏逆向基础-找释放技能CALL

思路&#xff1a;通过send断点然后对send的data参数下写入断点找到游戏里面的技能或者攻击call 进入游戏先选好一个怪物&#xff08;之所以要先选好是因为选怪也会断&#xff0c;如果直接左键打怪的话就会断几次&#xff09; 断下来后对参数下硬件写入断点 硬件断点断下来后先…

如何用pyhton修改1000+图片的名字?

import os oldpath input("请输入文件路径&#xff08;在windows中复制那个图片文件夹的路径就可以):") #注意window系统中的路径用这个‘\分割&#xff0c;但是编程语言中一般都是正斜杠也就是’/‘ #这里写一个代码&#xff0c;将 \ > / path "" fo…

基于SpringBoot+Vue+uniapp的海产品加工销售一体化管理系统的详细设计和实现(源码+lw+部署文档+讲解等)

详细视频演示 请联系我获取更详细的视频演示 项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不…

6 机器学习之应用现状

在过去二十年中&#xff0c;人类收集、存储、传输、处理数据的能力取得了飞速提升&#xff0c;人类社会的各个角落都积累了大量数据&#xff0c;亟需能有效地对数据进行分析利用的计算机算法&#xff0c;而机器学习恰顺应了大时代的这个迫切需求&#xff0c;因此该学科领域很自…

基于FPGA的DDS信号发生器(图文并茂+深度原理解析)

篇幅有限,本文详细源文件已打包 至个人主页资源,需要自取...... 前言 DDS(直接数字合成)技术是先进的频率合成手段,在数字信号处理与硬件实现领域作用关键。它因低成本、低功耗、高分辨率以及快速转换时间等优点备受认可。 本文着重探究基于 FPGA 的简易 DDS 信号发生器设…

交叉熵损失 在PyTorch 中的计算过程

其实就是根据 真实值的结果&#xff0c;当成索引去取的值 import torch import torch.nn as nnaaaa torch.tensor([[2.0,1.0,3.0],[2.0,4.0,2.0]])l1 nn.LogSoftmax(dim-1) result l1(aaaa) print(result) import torch import torch.nn as nn# 定义交叉熵损失函数 criterio…