You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C++中用最多1字节存储歌曲流派及组合?

问题描述

我希望在C++中用最多1字节存储歌曲流派(仅用于学术研究,非生产环境)。可选流派包括rock、pop、hip-hop、electronic music和jaz共5种,且支持组合(如同时属于pop和rock)。我最初想到用char类型(占1字节),利用字节的8个位来表示:激活第1位代表rock,第2位代表pop,依此类推,例如11100000表示歌曲属于rock、pop和hip-hop。但我不确定这是否是最优方案,目前遇到的问题:

  • 当输入如"rp"(流派首字母)时,需要设置char变量的对应位,担心不同机器的大小端存储方式会带来问题;
  • 已实现将位序列转换为字符串的功能(如11100000转为Rock&Pop&Hip-hop),但实现不够简洁;
  • 需要实现从歌曲集合中查找符合特定流派组合(如"pr"代表pop或rock)的歌曲的功能。

相关实现代码如下:

struct Song
{
    char Genre;
};

void readSongGenre(Song& s, const char* genre)
{
    int i = 0;
    while (genre[i] != '\0')
    {
        if (genre[i] == 'r')
        {
            toggleKthBit(s.Genre, 1);
        }
        else if (genre[i] == 'p')
        {
            toggleKthBit(s.Genre, 2);
        }
        else if (genre[i] == 'h')
        {
            toggleKthBit(s.Genre, 3);
        }
        else if (genre[i] == 'e')
        {
            toggleKthBit(s.Genre, 4);
        }
        else if (genre[i] == 'j')
        {
            toggleKthBit(s.Genre, 5);
        }

        i++;
    }
}
void printGenre(char genre)
{
int countOfSetBits = 0;

for (int i = 1; i <= 5; i++)
{
    if (isKthBitSet(genre, i))
    {
        countOfSetBits++;
    }
}

int printed = 0;

if (isKthBitSet(genre, 1))
{
    printed++;
    if (countOfSetBits != printed)
    {
        std::cout << "Rock&";
    }
    else
    {
        std::cout << "Rock";
    }
}

if (isKthBitSet(genre, 2))
{
    printed++;
    if (countOfSetBits != printed)
    {
        std::cout << "Pop&";
    }
    else
    {
        std::cout << "Pop";
    }
}

if (isKthBitSet(genre, 3))
{
    printed++;
    if (countOfSetBits != printed)
    {
        std::cout << "Hip-Hop&";
    }
    else
    {
        std::cout << "Hip-Hop";
    }
}

if (isKthBitSet(genre, 4))
{
    printed++;
    if (countOfSetBits != printed)
    {
        std::cout << "Electronic&";
    }
    else
    {
        std::cout << "Electronic";
    }
}

if (isKthBitSet(genre, 5))
{
    printed++;
    if (countOfSetBits != printed)
    {
        std::cout << "Jaz&";
    }
    else
    {
        std::cout << "Jaz";
    }
}
}

解决方案

位操作方案的可行性

你的方案完全可行,甚至是这类场景的最优选择之一:

  • char类型占1字节(8位),足够存储5种流派的组合(总共有2^5=32种组合,远小于8位的容量);
  • 位操作的内存开销极小,查询和修改效率极高,非常适合学术研究中的数据存储与检索场景。

解决大小端问题

首先明确:大小端是多字节数据的存储顺序,对单个char的位操作没有影响。但你提到的“第1位”“第2位”存在歧义——C++标准并未规定单个字节内部的位序(是高位在前还是低位在前),这才是潜在的问题,而非大小端。

解决方法是显式定义位掩码,直接用二进制值绑定流派,避免依赖“第几位”的模糊定义:

// 定义每个流派对应的位掩码,用高5位存储(低3位留空,不影响)
constexpr char GENRE_ROCK = 1 << 7;    // 二进制 10000000
constexpr char GENRE_POP = 1 << 6;     // 二进制 01000000
constexpr char GENRE_HIPHOP = 1 << 5;  // 二进制 00100000
constexpr char GENRE_ELECTRONIC = 1 << 4; // 二进制 00010000
constexpr char GENRE_JAZ = 1 << 3;     // 二进制 00001000

修改readSongGenre函数,直接用掩码操作:

void readSongGenre(Song& s, const char* genre)
{
    int i = 0;
    while (genre[i] != '\0')
    {
        switch(genre[i])
        {
            case 'r': s.Genre |= GENRE_ROCK; break;
            case 'p': s.Genre |= GENRE_POP; break;
            case 'h': s.Genre |= GENRE_HIPHOP; break;
            case 'e': s.Genre |= GENRE_ELECTRONIC; break;
            case 'j': s.Genre |= GENRE_JAZ; break;
            // 可选:处理无效字符,比如忽略或抛出警告
        }
        i++;
    }
}

这样无论机器的位序如何,掩码的二进制值是固定的,操作逻辑完全一致,彻底消除平台差异。

优化字符串转换逻辑

原代码重复度极高,可通过流派信息数组统一管理掩码与名称,遍历数组完成转换,代码更简洁且易于维护:

#include <vector>
#include <string>
#include <iostream>

// 统一存储流派的掩码和名称
struct GenreMap {
    char mask;
    const char* name;
};

const GenreMap genre_mappings[] = {
    {GENRE_ROCK, "Rock"},
    {GENRE_POP, "Pop"},
    {GENRE_HIPHOP, "Hip-Hop"},
    {GENRE_ELECTRONIC, "Electronic"},
    {GENRE_JAZ, "Jaz"}
};
const size_t genre_count = sizeof(genre_mappings) / sizeof(genre_mappings[0]);

// 将流派掩码转换为字符串
std::string genre_to_string(char genre) {
    std::vector<std::string> active_genres;
    // 收集所有激活的流派名称
    for (size_t i = 0; i < genre_count; ++i) {
        if (genre & genre_mappings[i].mask) {
            active_genres.push_back(genre_mappings[i].name);
        }
    }
    // 拼接成带&的字符串
    std::string result;
    for (size_t i = 0; i < active_genres.size(); ++i) {
        if (i != 0) result += "&";
        result += active_genres[i];
    }
    return result;
}

// 打印流派的简化实现
void printGenre(char genre) {
    std::cout << genre_to_string(genre) << std::endl;
}

后续新增或修改流派,只需调整genre_mappings数组,无需修改转换逻辑。

实现流派组合查找功能

核心思路是将查询字符串转换为对应的掩码,再通过位运算判断歌曲是否符合条件:

  1. 查询字符串转掩码:将"pr"这类输入转为GENRE_POP | GENRE_ROCK;
  2. 匹配判断:
    • 若需求是包含任意指定流派(如"pr"代表pop或rock),则判断(song.Genre & query_mask) != 0;
    • 若需求是包含所有指定流派(如同时属于pop和rock),则判断(song.Genre & query_mask) == query_mask。

实现代码:

#include <vector>

// 将查询字符串转换为流派掩码
char query_to_mask(const char* query) {
    char mask = 0;
    int i = 0;
    while (query[i] != '\0') {
        switch(query[i]) {
            case 'r': mask |= GENRE_ROCK; break;
            case 'p': mask |= GENRE_POP; break;
            case 'h': mask |= GENRE_HIPHOP; break;
            case 'e': mask |= GENRE_ELECTRONIC; break;
            case 'j': mask |= GENRE_JAZ; break;
        }
        i++;
    }
    return mask;
}

// 查找符合流派组合的歌曲(示例:任意匹配)
std::vector<Song> find_songs_by_genre(const std::vector<Song>& songs, char query_mask) {
    std::vector<Song> matched;
    for (const auto& song : songs) {
        // 任意匹配:只要包含至少一个查询的流派
        if ((song.Genre & query_mask) != 0) {
            matched.push_back(song);
        }
        // 全匹配:必须包含所有查询的流派,替换上面的判断即可
        // if ((song.Genre & query_mask) == query_mask) {
        //     matched.push_back(song);
        // }
    }
    return matched;
}

内容的提问来源于stack exchange,提问作者SAQ

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.27 13:10:56