在 C 中反转字符串 [英] Reversing a string in C

查看:27
本文介绍了在 C 中反转字符串的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我开发了一个反向字符串程序.我想知道是否有更好的方法来做到这一点,以及我的代码是否有任何潜在问题.我希望练习 C 的一些高级功能.

char* reverse_string(char *str){炭温度;size_t len = strlen(str) - 1;size_t i;size_t k = len;for(i = 0; i 

解决方案

如果你想练习 C 的高级功能,指针怎么样?我们也可以为了好玩而加入宏和异或交换!

#include //对于 strlen()//原地反转给定的以空字符结尾的字符串void inplace_reverse(char * str){如果(字符串){char * end = str + strlen(str) - 1;//交换两个给定变量中的值//XXX: 当 a 和 b 引用相同的内存位置时失败# 定义 XOR_SWAP(a,b) do{^= b; ^= a;^= b;} 而 (0)//从字符串的两端向内走,//交换直到我们到达中间while (str < end){XOR_SWAP(*str, *end);str++;结尾 - ;}# undef XOR_SWAP}}

一个指针(例如char *,从右到左读取作为指向char) 是 C 中使用的数据类型引用另一个值在内存中的位置.在这种情况下,char 的存储位置.我们可以取消引用通过在指针前面加上 * 前缀,它给了我们值存储在该位置.所以存储在 str 的值是 *str.

我们可以用指针做简单的算术运算.当我们增加(或减少)一个指针,我们只需移动它以引用下一个(或上一个)该类型值的内存位置.递增指针不同类型的指针可能会移动不同数量的指针字节,因为不同的值在 C 中具有不同的字节大小.

这里,我们使用一个指针来指向第一个未处理的char 字符串 (str) 和另一个引用最后一个 (end).我们交换它们的值(*str*end),并移动指针向内到弦的中间.一旦 str >= end,要么它们都指向同一个 char,这意味着我们的原始字符串有一个奇数长度(中间 char 不需要反转),或我们已经处理了一切.

为了进行交换,我定义了一个.宏是文本替换由 C 预处理器完成.它们与功能非常不同,了解其中的区别很重要.当你调用一个函数时,该函数对您提供的值的副本进行操作.你打电话的时候一个宏,它只是进行文本替换 - 所以你给出的参数直接使用.

因为我只使用了一次 XOR_SWAP 宏,所以定义它可能有点矫枉过正,但它更清楚地说明了我在做什么.C预处理器展开宏后,while 循环如下所示:

 while (str 

请注意,宏参数每次在宏定义.这可能非常有用 - 但也可能破坏您的代码如果使用不当.例如,如果我压缩了增量/减量指令和宏调用成一行,如

 XOR_SWAP(*str++, *end--);

然后这将扩展为

 do { *str++ ^= *end--;*结束-- ^= *str++;*str++ ^= *end--;} 而(0);

具有三重递增/递减操作,实际上并没有做它应该做的交换.

在我们讨论这个主题时,您应该知道 xor (^) 是什么意思.这是一个基本的算术运算 - 像加法、减法、乘法、除法,除了它通常在小学不教.它一点一点地组合两个整数- 喜欢加法,但我们不关心结转.1^1 = 0, 1^0 = 1,0^1 = 1, 0^0 = 0.

一个众所周知的技巧是使用异或来交换两个值.这是因为三个基本的xor 的属性:x ^ 0 = xx ^ x = 0x ^ y = y ^ x 对于所有值 xy.所以说我们有两个变量 ab 最初存储两个值vavb.

<前>//原来://a == va//b == vb^= b;//现在:a == va ^ vbb ^= a;//现在: b == vb ^ (va ^ vb)//== va ^ (vb ^ vb)//== va ^ 0//== va^= b;//现在:a == (va ^ vb) ^ va//== (va ^ va) ^ vb//== 0 ^ vb//== vb

所以这些值被交换了.这确实有一个错误 - 当 ab 是同一个变量时:

<前>//原来://a == va一个 ^= 一个;//现在:a == va ^ va//== 0一个 ^= 一个;//现在:a == 0 ^ 0//== 0一个 ^= 一个;//现在:a == 0 ^ 0//== 0

因为我们 str <,这在上面的代码中从未发生过,所以我们没问题.

虽然我们关心正确性,但我们应该检查我们的边缘情况.if (str) 行应该确保我们没有为字符串提供 NULL 指针.空字符串 "" 怎么样?那么strlen("") == 0,所以我们将end初始化为str - 1,这意味着while(str < end) 条件永远不会为真,所以我们什么都不做.哪个是正确的.

有很多 C 语言需要探索.玩得开心!

更新: mmw 提出了一个很好的观点,那就是你必须调用它的方式要稍微小心一点,因为它确实就地运行.

 char stack_string[] = "这个字符串被复制到堆栈中.";inplace_reverse(stack_string);

这很好用,因为 stack_string 是一个数组,其内容被初始化为给定的字符串常量.不过

 char * string_literal = "这个字符串是可执行文件的一部分.";inplace_reverse(string_literal);

将导致您的代码在运行时燃烧并死亡.这是因为 string_literal 仅指向作为可执行文件的一部分存储的字符串 - 通常是操作系统不允许您编辑的内存.在一个更快乐的世界中,您的编译器会知道这一点,并在您尝试编译时发出一个错误,告诉您 string_literal 需要是 char const * 类型,因为您可以'不修改内容.然而,这不是我的编译器生活的世界.

您可以尝试一些技巧来确保某些内存在堆栈上或堆中(因此是可编辑的),但它们不一定是可移植的,而且可能非常难看.但是,我很乐意将这方面的责任交给函数调用者.我已经告诉他们这个函数会进行内存操作,他们有责任给我一个允许这样做的参数.

I have developed a reverse-string program. I am wondering if there is a better way to do this, and if my code has any potential problems. I am looking to practice some advanced features of C.

char* reverse_string(char *str)
{
    char temp;
    size_t len = strlen(str) - 1;
    size_t i;
    size_t k = len;

    for(i = 0; i < len; i++)
    {
        temp = str[k];
        str[k] = str[i];
        str[i] = temp;
        k--;

        /* As 2 characters are changing place for each cycle of the loop
           only traverse half the array of characters */
        if(k == (len / 2))
        {
            break;
        }
    }
}

解决方案

If you want to practice advanced features of C, how about pointers? We can toss in macros and xor-swap for fun too!

#include <string.h> // for strlen()

// reverse the given null-terminated string in place
void inplace_reverse(char * str)
{
  if (str)
  {
    char * end = str + strlen(str) - 1;

    // swap the values in the two given variables
    // XXX: fails when a and b refer to same memory location
#   define XOR_SWAP(a,b) do
    {
      a ^= b;
      b ^= a;
      a ^= b;
    } while (0)

    // walk inwards from both ends of the string, 
    // swapping until we get to the middle
    while (str < end)
    {
      XOR_SWAP(*str, *end);
      str++;
      end--;
    }
#   undef XOR_SWAP
  }
}

A pointer (e.g. char *, read from right-to-left as a pointer to a char) is a data type in C that is used to refer to location in memory of another value. In this case, the location where a char is stored. We can dereference pointers by prefixing them with an *, which gives us the value stored at that location. So the value stored at str is *str.

We can do simple arithmetic with pointers. When we increment (or decrement) a pointer, we simply move it to refer to the next (or previous) memory location for that type of value. Incrementing pointers of different types may move the pointer by a different number of bytes because different values have different byte sizes in C.

Here, we use one pointer to refer to the first unprocessed char of the string (str) and another to refer to the last (end). We swap their values (*str and *end), and move the pointers inwards to the middle of the string. Once str >= end, either they both point to the same char, which means our original string had an odd length (and the middle char doesn't need to be reversed), or we've processed everything.

To do the swapping, I've defined a macro. Macros are text substitution done by the C preprocessor. They are very different from functions, and it's important to know the difference. When you call a function, the function operates on a copy of the values you give it. When you call a macro, it simply does a textual substitution - so the arguments you give it are used directly.

Since I only used the XOR_SWAP macro once, it was probably overkill to define it, but it made more clear what I was doing. After the C preprocessor expands the macro, the while loop looks like this:

    while (str < end)
    {
      do { *str ^= *end; *end ^= *str; *str ^= *end; } while (0);
      str++;
      end--;
    }

Note that the macro arguments show up once for each time they're used in the macro definition. This can be very useful - but can also break your code if used incorrectly. For example, if I had compressed the increment/decrement instructions and the macro call into a single line, like

      XOR_SWAP(*str++, *end--);

Then this would expand to

      do { *str++ ^= *end--; *end-- ^= *str++; *str++ ^= *end--; } while (0);

Which has triple the increment/decrement operations, and doesn't actually do the swap it's supposed to do.

While we're on the subject, you should know what xor (^) means. It's a basic arithmetic operation - like addition, subtraction, multiplication, division, except it's not usually taught in elementary school. It combines two integers bit by bit - like addition, but we don't care about the carry-overs. 1^1 = 0, 1^0 = 1, 0^1 = 1, 0^0 = 0.

A well known trick is to use xor to swap two values. This works because of three basic properties of xor: x ^ 0 = x, x ^ x = 0 and x ^ y = y ^ x for all values x and y. So say we have two variables a and b that are initially storing two values va and vb.

  // initially:
  // a == va
  // b == vb
  a ^= b;
  // now: a == va ^ vb
  b ^= a;
  // now: b == vb ^ (va ^ vb)
  //        == va ^ (vb ^ vb)
  //        == va ^ 0
  //        == va
  a ^= b;
  // now: a == (va ^ vb) ^ va
  //        == (va ^ va) ^ vb
  //        == 0 ^ vb
  //        == vb

So the values are swapped. This does have one bug - when a and b are the same variable:

  // initially:
  // a == va
  a ^= a;
  // now: a == va ^ va
  //        == 0
  a ^= a;
  // now: a == 0 ^ 0
  //        == 0
  a ^= a;
  // now: a == 0 ^ 0
  //        == 0

Since we str < end, this never happens in the above code, so we're okay.

While we're concerned about correctness we should check our edge cases. The if (str) line should make sure we weren't given a NULL pointer for string. What about the empty string ""? Well strlen("") == 0, so we'll initialize end as str - 1, which means that the while (str < end) condition is never true, so we don't do anything. Which is correct.

There's a bunch of C to explore. Have fun with it!

Update: mmw brings up a good point, which is you do have to be slightly careful how you invoke this, as it does operate in-place.

 char stack_string[] = "This string is copied onto the stack.";
 inplace_reverse(stack_string);

This works fine, since stack_string is an array, whose contents are initialized to the given string constant. However

 char * string_literal = "This string is part of the executable.";
 inplace_reverse(string_literal);

Will cause your code to flame and die at runtime. That's because string_literal merely points to the string that is stored as part of your executable - which is normally memory that you are not allowed to edit by the OS. In a happier world, your compiler would know this, and cough an error when you tried to compile, telling you that string_literal needs to be of type char const * since you can't modify the contents. However, this is not the world my compiler lives in.

There are some hacks you could try to make sure that some memory is on the stack or in the heap (and is therefore editable), but they're not necessarily portable, and it could be pretty ugly. However, I'm more than happy to throw responsibility for this to the function invoker. I've told them that this function does in place memory manipulation, it's their responsibility to give me an argument that allows that.

这篇关于在 C 中反转字符串的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆