二进制逆序(字节反转)

二进制逆序原文
CRC16算法原文

1 理论分析

二进制逆序,顾名思义就是将低位和高位交换,例如0x23 = 0010 0011 B,逆序后就是1100 0100 B。字节反转在“小端”格式和“大端”格式之间的数据转换是一个必要的操作。
一种比较笨的办法就是将字节里的每一位提取出来,再重新组合,这种方法较为耗费时间,对低端单片机而言极为不利。实际上通过与运算(&)和移位运算,可以轻松的做到这一点。
主要原理:先交换每相邻两位上的数,以后把互相交换过的数看成一个整体,继续进行以2位为单位的交换操作,之后以4为单位,以此类推。 以211= 11010011 B为例:

状态
1101001111010011<—原数
1 11 00 01 111100011<—第一次运算后
1 0 1 11 1 0 010111100<—第二次运算后
1 1 0 0 1 0 1 111001011<—第三次运算后

2 程序实现

/*   ****************************************************************/  
/**  
** @brief CRC校验函数
** @details 需要进行8位字节大小端逆序和,16位字节大小端逆序
** @note  
*/  
/*   ***************************************************************/  
unsigned int _CRC16(unsigned char *puchMsg, unsigned int usDataLen)  
{  
    unsigned int wCRCin = 0xFFFF;  
    unsigned int wCPoly = 0x1021;  
    unsigned char wChar = 0x0000;  

    while (usDataLen--)     
    {  
        wChar = *(puchMsg++); 

        /* 8位交换大小端 */ 
        /* InvertUint8(&wChar,&wChar); */
        // 交换每两位  
        wChar = ((wChar >> 1) & 0x55) | ((wChar & 0x55) << 1);  
        // 交换每四位中的前两位和后两位  
        wChar = ((wChar >> 2) & 0x33) | ((wChar & 0x33) << 2);  
        // 交换每八位中的前四位和后四位  
        wChar = ((wChar >> 4) & 0x0F) | ((wChar & 0x0F) << 4);

        wCRCin ^= (wChar << 8);  
        for(int i = 0;i < 8;i++)  
        {  
          if(wCRCin & 0x8000)  
            wCRCin = (wCRCin << 1) ^ wCPoly;  
          else  
            wCRCin = wCRCin << 1;  
        }  
    }

    /* 16位交换大小端 */
    /* InvertUint16(&wCRCin,&wCRCin); */
    // 交换每两位  
    wCRCin = ((wCRCin >> 1) & 0x5555) | ((wCRCin & 0x5555) << 1);  
    // 交换每四位中的前两位和后两位  
    wCRCin = ((wCRCin >> 2) & 0x3333) | ((wCRCin & 0x3333) << 2);  
    // 交换每八位中的前四位和后四位  
    wCRCin = ((wCRCin >> 4) & 0x0F0F) | ((wCRCin & 0x0F0F) << 4);  
    // 交换相邻的两个字节  
    wCRCin = ((wCRCin >> 8) & 0x00FF) | ((wCRCin & 0x00FF) << 8);  

    return (wCRCin);  
}  

3,博主注

可以把整个过程看出一个递归过程理解起来会容易很多,比如:

00111101先分成0011-1101两部分调换1101-0011
1101先分成11-01两部分调换01-11(0011同理)
01分成0-1调换成1-0(11同理)

可以用递归函数完成这个过程,但32位以内都是没有必要的。

<think>我们计划将字符串中的每个字符转换为其8位二进制表示(即每个字符对应一个字节二进制形式)。注意:字符串通常以空字符'\0'结尾,但转换时我们通常只处理到字符串长度,不包括结尾的空字符(除非特别要求)。 步骤: 1. 遍历字符串中的每个字符(直到遇到结尾的空字符,但通常我们根据字符串长度来确定)。 2. 对于每个字符,将其ASCII码值转换为8位二进制数(不足8位在前面补0)。 3. 将每个字符的二进制表示拼接起来,或者逐个字符转换后直接输出。 注意:由于一个字符的ASCII码范围是0-255,所以用8位二进制表示即可。 方法一:使用位操作逐位获取二进制位,并构建每个字符的二进制字符串。 方法二:使用标准库函数(如itoa)?但注意C标准库中没有直接提供将数字转换为二进制字符串的函数,所以需要自己实现。 我们将实现一个函数,将单个字符转换成一个8位的二进制字符串(动态分配内存),然后对输入字符串的每个字符调用这个函数,并将结果拼接起来。 但是注意:原问题中引用的代码(引用[2])有一个函数fun233,它实现了将一个字符转换为二进制字符串(但该函数存在一些问题,比如没有处理不足8位的情况,且函数中使用了固定大小的临时数组,返回动态分配的内存,但未考虑内存释放,而且其转换过程是逆序的,然后通过反向填充得到正确顺序)。我们参考其思路,但需要修正为固定8位。 修正思路: 对于每个字符(实际上是unsigned char,避免负数): 从高位(第7位)到低位(第0位)依次检查每一位是0还是1。 这样构建的字符串就是正确的顺序,不需要反转。 另一种方法:使用移位操作,从最高位开始,依次将每一位转换为'0'或'1',并存入字符串。 具体步骤(单个字符转换): char ch = 要转换的字符; 我们分配一个9字节的字符串(8个字符+1个结束符'\0')。 然后,对每一位(i从0到7): 将ch与一个掩码(0x80右移i位)进行与操作,判断该位是1还是0。 或者,我们可以将ch左移i位,然后与0x80比较?但更简单的是从最高位开始,用掩码0x80>>i。 实际上,我们可以这样: for (int i = 0; i < 8; i++) { if (ch & (0x80 >> i)) bin_str[i] = '1'; else bin_str[i] = '0'; } 然后bin_str[8]='\0'; 注意:0x80是10000000(二进制),右移i位后,就可以依次检查每一位。 另一种:从最高位开始,用掩码0x80(即128)右移0位(就是10000000)检查最高位,右移1位(01000000)检查次高位,直到右移7位(00000001)检查最低位。 但是,由于右移操作可能因符号位产生问题,所以最好将字符转换为无符号字符(unsigned char)再操作。 因此,我们定义函数: char *char_to_binary(unsigned char c) { char *bin_str = malloc(9); // 8位+1个结束符 if (bin_str == NULL) return NULL; for (int i = 0; i < 8; i++) { if (c & (0x80 >> i)) bin_str[i] = '1'; else bin_str[i] = '0'; } bin_str[8] = '\0'; return bin_str; } 然后,对于整个字符串,我们遍历每个字符,调用这个函数,将得到的二进制字符串拼接起来。 但是,注意:整个字符串的二进制表示将是每个字符的二进制字符串连接起来(没有空格或其他分隔符)。如果字符串很长,那么最终的二进制字符串会很长,需要分配足够的内存。 另一种做法:我们可以不动态分配每个字符的二进制字符串,而是直接构建整个字符串的二进制表示。这样可以减少多次分配和释放内存的麻烦,并且效率更高。 优化方案: 我们可以一次性分配足够的内存:如果输入字符串长度为n(不包括结尾的'\0'),那么我们需要8*n+1个字节(每个字符8位,最后加一个结束符)。 然后,遍历字符串的每个字符,对于每个字符,计算其8位二进制,并直接放入结果字符串的相应位置。 步骤: char *string_to_binary(const char *str) { if (str == NULL) return NULL; int len = strlen(str); // 分配内存:8*len + 1 char *bin_str = malloc(8*len + 1); if (bin_str == NULL) return NULL; bin_str[0] = '\0'; // 初始化为空字符串 for (int i = 0; i < len; i++) { unsigned char c = str[i]; // 转换为无符号,避免符号扩展 for (int j = 0; j < 8; j++) { // 当前位是1还是0? if (c & (0x80 >> j)) bin_str[i*8+j] = '1'; else bin_str[i*8+j] = '0'; } } bin_str[8*len] = '\0'; // 结束符 return bin_str; } 注意:这样我们就得到了一个连续的二进制字符串,其中每8位对应原字符串的一个字符。 然后,我们可以打印这个字符串。 但是,如果字符串很长,我们可能不想一次性分配大块内存,也可以逐个字符转换并打印(不保存整个结果),这样就不需要分配大内存了。 如果只需要打印,而不需要返回整个字符串,我们可以直接打印每个字符的二进制: void print_string_as_binary(const char *str) { if (str == NULL) return; for (int i = 0; str[i] != '\0'; i++) { unsigned char c = str[i]; for (int j = 0; j < 8; j++) { putchar(c & (0x80 >> j) ? '1' : '0'); } // 如果要在每个字符的二进制之间加空格,可以在这里加putchar(' '); } putchar('\n'); } 这样更简单,且不需要分配额外内存(除了栈上的少量变量)。 因此,我们推荐使用直接打印的方法。 示例代码: #include <stdio.h> #include <string.h> void print_binary(const char *str) { if (str == NULL) return; for (size_t i = 0; i < strlen(str); i++) { unsigned char c = str[i]; for (int j = 0; j < 8; j++) { putchar((c & (0x80 >> j)) ? '1' : '0'); } } printf("\n"); } 但是,注意:如果字符串很长,多次调用strlen效率低,可以改为: for (size_t i = 0; str[i] != '\0'; i++) 或者: while (*str) { unsigned char c = *str++; ... // 处理c } 修改后: void print_binary(const char *str) { if (str == NULL) return; unsigned char *p = (unsigned char *)str; while (*p) { unsigned char c = *p; for (int i = 0; i < 8; i++) { if (c & (0x80 >> i)) putchar('1'); else putchar('0'); } p++; } putchar('\n'); } 测试: 输入 "A" 的二进制应该是 "01000001"(因为'A'的ASCII码是65,二进制为01000001) 但是,注意:这个函数不会打印字符串结尾的空字符。如果需要包括结尾的空字符,可以去掉循环条件中的`*p`判断,并指定长度(例如,使用strlen,然后循环包括最后一个字符,即空字符)。但通常我们不转换空字符。 如果用户希望包括空字符,可以修改函数,传入字符串长度(包括空字符)?但通常字符串的二进制表示不包括结尾空字符,因为打印时我们按字符串长度(不包括结尾空字符)处理。 因此,我们按上面的函数,只转换到字符串结尾空字符之前。 使用示例: int main() { char input[100]; printf("Enter a string: "); fgets(input, sizeof(input), stdin); // 去掉fgets可能读入的换行符 input[strcspn(input, "\n")] = '\0'; printf("Binary representation: "); print_binary(input); return 0; } 注意:fgets会读入换行符,所以我们将其替换为结束符。 另外,如果输入字符串中包含多字节字符(如中文),则每个字节都会转换为8位二进制。因为中文字符通常用多个字节表示(如UTF-8编码),所以一个中文字符会转换为多个8位二进制串(连续打印出来)。 因此,这个函数是按字节处理的,适用于ASCII字符串和UTF-8编码的字符串(按字节处理)。 现在,我们按照上述思路编写代码。</think>在C语言中,将字符串转换为二进制格式输出需要逐个字符处理其ASCII码值,并将每个字节转换为8位二进制表示。以下是实现方法和代码示例: ### 实现步骤 1. **遍历字符串**:逐个处理每个字符(包括空终止符`\0`可选) 2. **字符转ASCII**:获取字符的整数值(0-255) 3. **字节二进制**:将字节值转换为8位二进制字符串 4. **输出结果**:按位打印或拼接成完整二进制串 ### 代码实现 ```c #include <stdio.h> #include <limits.h> // 打印单个字符的8位二进制表示 void print_char_binary(unsigned char c) { for (int i = CHAR_BIT - 1; i >= 0; i--) { // 从高位到低位 putchar((c & (1 << i)) ? '1' : '0'); } } // 打印整个字符串的二进制表示 void print_string_binary(const char* str) { if (!str) return; for (int i = 0; str[i] != '\0'; i++) { // 遍历到空终止符 print_char_binary(str[i]); putchar(' '); // 可选:字符间添加空格分隔 } // 可选:包含结尾的'\0' // print_char_binary(0); } int main() { char input[128]; printf("输入字符串: "); fgets(input, sizeof(input), stdin); printf("二进制表示: "); print_string_binary(input); putchar('\n'); return 0; } ``` ### 关键说明 1. **位操作原理** `(c & (1 << i))`检测字节中特定位的状态: - `1 << i`生成位掩码(如`10000000`) - 按位与操作`&`测试该位是否为1 - 从最高位(MSB)向最低位(LSB)处理 2. **字符处理** - `unsigned char`确保正确处理0-255范围 - `CHAR_BIT`常量(通常为8)保证平台兼容性 - 默认不包含结尾`\0`,取消注释可添加[^4] 3. **输出示例** ```text 输入字符串: Hi 二进制表示: 01001000 01101001 ``` - 'H' = ASCII 72 → 01001000 - 'i' = ASCII 105 → 01101001 ### 优化建议 1. **动态拼接**(需内存管理): ```c char* string_to_binary(const char* str) { int len = strlen(str); char* bin = malloc(len * 9 + 1); // 8位+空格+结束符 bin[0] = '\0'; for (int i = 0; i < len; i++) { char byte[9]; for (int j = 7; j >= 0; j--) { byte[7-j] = (str[i] & (1 << j)) ? '1' : '0'; } byte[8] = '\0'; strcat(bin, byte); strcat(bin, " "); // 分隔符 } return bin; } ``` 2. **处理UTF-8字符** 多字节字符需特殊处理: ```c void print_utf8_binary(const char* str) { while (*str) { // 检测多字节字符首字节 int bytes = (*str & 0x80) == 0 ? 1 : (*str & 0xE0) == 0xC0 ? 2 : (*str & 0xF0) == 0xE0 ? 3 : 4; for (int i = 0; i < bytes; i++) { print_char_binary(*str++); putchar(' '); } putchar('|'); // 字符分隔 } } ``` ### 注意事项 - **内存管理**:动态分配需手动`free()` - **字节序无关**:此方法独立于CPU字节序 - **性能考量**:直接打印比拼接字符串更高效 - **扩展ASCII**:`unsigned char`支持0-255完整范围 > 此方法通过位操作直接访问内存中的二进制表示,比十进制转换更高效[^1][^3]。对于长字符串建议直接打印避免内存分配。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值