3 回答

TA貢獻2016條經驗 獲得超9個贊
如果您想練習C的高級功能,那么指針呢?我們也可以在宏和異或交換中投入樂趣!
#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
}
}
甲指針(例如char *,從右到左為讀指針char是用于指位置的另一值的存儲器在C語言的數據類型)。在這種情況下,a char的存儲位置。我們可以 通過給指針加上前綴來取消引用指針*,從而為我們提供存儲在該位置的值。因此,存儲在的值str是*str。
我們可以使用指針進行簡單的算術運算。當我們增加(或減少)指針時,我們只需將其移動以引用該類型值的下一個(或上一個)存儲位置。不同類型的遞增指針可能會將指針移動不同的字節數,因為不同的值在C中具有不同的字節大小。
在這里,我們使用一個指針來引用char字符串中的第一個未處理的指針(str),使用另一個指針來引用最后一個未處理的指針 (end)。我們交換它們的值(*str和*end),然后將指針向內移動到字符串的中間。一旦str >= end它們都指向相同的char,這意味著我們原始的字符串長度是奇數個(中間char不需要顛倒),或者我們已經處理了所有東西。
為了進行交換,我定義了一個macro。宏是由C預處理程序完成的文本替換。它們與功能有很大不同,因此必須知道它們之間的區別。當您調用一個函數時,該函數將對您提供的值進行操作。調用宏時,它只是執行文本替換-因此,您直接給它提供的參數會被使用。
由于我只使用過XOR_SWAP一次宏,因此定義它可能是過大的了,但是它使我在做什么更加清楚。在C預處理器擴展宏之后,while循環如下所示:
while (str < end)
{
do { *str ^= *end; *end ^= *str; *str ^= *end; } while (0);
str++;
end--;
}
請注意,每次在宏定義中使用宏參數時,它們都會顯示一次。這可能非常有用-但如果使用不正確,也會破壞您的代碼。例如,如果我已將增量/減量指令和宏調用壓縮為一行,例如
XOR_SWAP(*str++, *end--);
然后這將擴展為
do { *str++ ^= *end--; *end-- ^= *str++; *str++ ^= *end--; } while (0);
它具有三倍的增/減操作,并且實際上并沒有執行它應該執行的交換操作。
當我們討論這個主題時,您應該知道xor(^)的含義。這是一種基本的算術運算-像加法,減法,乘法,除法,但它通常不在小學里教。它一點一點地結合了兩個整數-像加法一樣,但是我們不在乎結轉。 1^1 = 0,1^0 = 1, 0^1 = 1,0^0 = 0。
一個眾所周知的技巧是使用xor交換兩個值。這工作XOR因為三個基本屬性:x ^ 0 = x,x ^ x = 0和x ^ y = y ^ x所有值x和y。所以說,我們有兩個變量a,并b與起初存儲兩個值 和。vavb
// 原來:
// a == v a
// b == v b
a ^ = b;
//現在:a == v a ^ v b
b ^ = a;
//現在:b == v b ^(v a ^ v b)
// == v a ^(v b ^ v b)
// == v a ^ 0
// == v a
a ^ = b;
//現在:a ==(v a ^ v b)^ v a
// ==(v a ^ v a)^ v b
// == 0 ^ v b
// == v b
因此,將交換值。這確實有一個錯誤-when a和b是相同的變量:
// 原來:
// a == v a
a ^ = a;
//現在:a == v a ^ v a
// == 0
a ^ = a;
//現在:a == 0 ^ 0
// == 0
a ^ = a;
//現在:a == 0 ^ 0
// == 0
由于我們str < end,在上面的代碼中永遠不會發生這種情況,所以我們可以。
當我們擔心正確性時,我們應該檢查邊緣情況。該if (str)行應確保沒有NULL為字符串提供指針。空字符串""呢?好了strlen("") == 0,所以我們將初始化end為str - 1,這意味著while (str < end)條件永遠不會成立,因此我們什么也不做。哪個是正確的。
有很多C需要探索。玩得開心!
更新: mmw帶來了一個好處,那就是您確實需要謹慎操作,因為它確實就地運行。
char stack_string[] = "This string is copied onto the stack.";
inplace_reverse(stack_string);
由于stack_string是一個數組,其內容初始化為給定的字符串常量,因此可以正常工作。然而
char * string_literal = "This string is part of the executable.";
inplace_reverse(string_literal);
將導致您的代碼在運行時啟動并死亡。這是因為string_literal僅指向存儲為可執行文件一部分的字符串-通常是操作系統不允許您編輯的內存。在一個更幸福的世界中,您的編譯器會知道這一點,并在嘗試編譯時出現錯誤,并告訴您該string_literal類型必須為您,char const *因為您無法修改其內容。但是,這不是我的編譯器所生活的世界。
您可以嘗試使用一些技巧來確保某些內存在堆棧或堆中(因此是可編輯的),但是它們不一定是可移植的,并且可能很丑陋。但是,我很樂意為此承擔責任給函數調用者。我已經告訴他們該函數可以進行適當的內存操作,他們有責任給我一個允許這樣做的參數。

TA貢獻1785條經驗 獲得超4個贊
只是重新布置,并進行安全檢查。我還刪除了您未使用的退貨類型。我認為這是安全和干凈的:
#include <stdio.h>
#include <string.h>
void reverse_string(char *str)
{
/* skip null */
if (str == 0)
{
return;
}
/* skip empty string */
if (*str == 0)
{
return;
}
/* get range */
char *start = str;
char *end = start + strlen(str) - 1; /* -1 for \0 */
char temp;
/* reverse */
while (end > start)
{
/* swap */
temp = *start;
*start = *end;
*end = temp;
/* move */
++start;
--end;
}
}
int main(void)
{
char s1[] = "Reverse me!";
char s2[] = "abc";
char s3[] = "ab";
char s4[] = "a";
char s5[] = "";
reverse_string(0);
reverse_string(s1);
reverse_string(s2);
reverse_string(s3);
reverse_string(s4);
reverse_string(s5);
printf("%s\n", s1);
printf("%s\n", s2);
printf("%s\n", s3);
printf("%s\n", s4);
printf("%s\n", s5);
return 0;
}
已進行編輯,以使當strlen為0時,結束點不會指向可能損壞的內存位置。
- 3 回答
- 0 關注
- 484 瀏覽
添加回答
舉報