LoginSignup
6
2

More than 1 year has passed since last update.

C言語で英文字の小文字を大文字に変換してみる

Last updated at Posted at 2023-03-18

はじめに

C言語で英文字の小文字を大文字に変換してみるやつです。

けっこう見掛けそうな実装

int lower2upper(int ch)
{
    if ('a' <= ch && ch <= 'z') {
        ch += -'a' + 'A';
    }
    return ch;
}

けっこう見掛けそうな実装です。

  • 'a'~'z'の文字コードが連続している
  • 対応する小文字と大文字の文字コードが等間隔である

以上の条件を前提としていますが、C言語の規格に合っていないため良い方法とは言えません。

標準ライブラリを使用する

#include <stdio.h>
#include <ctype.h>

int main(void)
{
    static const char s[] = "the quick brown fox jumps over the lazy dog.";
    for (int i = 0; s[i] != '\0'; i++) {
        putchar(toupper(s[i]));
    }
}

C言語の標準ライブラリにtoupper()という関数があり、これを使用する方法です。
ロケールの影響を受けるため英文字以外の文字に働いてしまう可能性があり、用途次第では使い辛いと思います。

#include <string.h>

int lower2upper(int ch)
{
    static const char l[] = "abcdefghijklmnopqrstuvwxyz";
    static const char u[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    const char* p = strchr(l, ch);
    if (p) {
        ch = u[p - l];
    } 
    return ch;
}

strchr()もロケールの影響を受ける可能性があるそうで、自分がロケールについて理解が浅いことも問題なのですが「XXXの設定では'~'は'x'の異体字として扱われる」等のルールがもしあるか、あるいは将来的に追加される可能性があるとしたらおっかない気がしています。

switch ~ case を使用する

int lower2upper(int ch)
{
    switch (ch) {
    case 'a':
        ch = 'A';
        break;
    case 'b':
        ch = 'B';
        break;
    case 'c':
        ch = 'C';
        break;
    case 'd':
        ch = 'D';
        break;
    case 'e':
        ch = 'E';
        break;
    case 'f':
        ch = 'F';
        break;
    case 'g':
        ch = 'G';
        break;
    case 'h':
        ch = 'H';
        break;
    case 'i':
        ch = 'I';
        break;
    case 'j':
        ch = 'J';
        break;
    case 'k':
        ch = 'K';
        break;
    case 'l':
        ch = 'L';
        break;
    case 'm':
        ch = 'M';
        break;
    case 'n':
        ch = 'N';
        break;
    case 'o':
        ch = 'O';
        break;
    case 'p':
        ch = 'P';
        break;
    case 'q':
        ch = 'Q';
        break;
    case 'r':
        ch = 'R';
        break;
    case 's':
        ch = 'S';
        break;
    case 't':
        ch = 'T';
        break;
    case 'u':
        ch = 'U';
        break;
    case 'v':
        ch = 'V';
        break;
    case 'w':
        ch = 'W';
        break;
    case 'x':
        ch = 'X';
        break;
    case 'y':
        ch = 'Y';
        break;
    case 'z':
        ch = 'Z';
        break;
    }
    return ch;
}

数年前に個人のブログで批判されてた方法です。
https://web.archive.org/web/20170516155255/https://www.tawashix.com/entry/program

はてなで様々なコメントが付けられていました。
https://b.hatena.ne.jp/entry/www.tawashix.com/entry/program

個人的にはそれほど悪いとは思わない方法です。
ちょっと冗長な見た目ですが、現代の優秀なコンパイラでは驚くほどコンパクトなコードを吐くのでコードサイズ的な問題はないと思います。

x86-64版gcc12.2に最適化指示-O2を指定してコンパイル
lower2upper:
        leal    -97(%rdi), %ecx
        movl    %edi, %eax
        leal    -32(%rdi), %edx
        cmpl    $26, %ecx
        cmovb   %edx, %eax
        ret
x86-64版clang15.0.0に最適化指示-O2を指定してコンパイル
lower2upper:                            # @lower2upper
        leal    -97(%rdi), %ecx
        leal    -32(%rdi), %eax
        cmpl    $26, %ecx
        cmovael %edi, %eax
        retq

※ ASCII以外の文字コードではもっと複雑なコードを吐かれる可能性は普通に考えられます。

関数の長さがコーディングルールに引っかかる等の事情があれば

int lower2upper(int ch)
{
    switch (ch) {
    case 'a': return 'A';
    case 'b': return 'B';
    case 'c': return 'C';
    case 'd': return 'D';
    case 'e': return 'E';
    case 'f': return 'F';
    case 'g': return 'G';
    case 'h': return 'H';
    case 'i': return 'I';
    case 'j': return 'J';
    case 'k': return 'K';
    case 'l': return 'L';
    case 'm': return 'M';
    case 'n': return 'N';
    case 'o': return 'O';
    case 'p': return 'P';
    case 'q': return 'Q';
    case 'r': return 'R';
    case 's': return 'S';
    case 't': return 'T';
    case 'u': return 'U';
    case 'v': return 'V';
    case 'w': return 'W';
    case 'x': return 'X';
    case 'y': return 'Y';
    case 'z': return 'Z';
    }
    return ch;
}

上のような詰めた書き方も有効かもしれません。或いはインデントルールや複数のreturnを認めないルールに引っかかるかもしれません。その辺はケースバイケースですね。

if ~ else を使用する

int lower2upper(int ch)
{
    if (ch == 'a') {
        ch = 'A';
    } else if (ch == 'b') {
        ch = 'B';
    } else if (ch == 'c') {
        ch = 'C';
    } else if (ch == 'd') {
        ch = 'D';
    } else if (ch == 'e') {
        ch = 'E';
    } else if (ch == 'f') {
        ch = 'F';
    } else if (ch == 'g') {
        ch = 'G';
    } else if (ch == 'h') {
        ch = 'H';
    } else if (ch == 'i') {
        ch = 'I';
    } else if (ch == 'j') {
        ch = 'J';
    } else if (ch == 'k') {
        ch = 'K';
    } else if (ch == 'l') {
        ch = 'L';
    } else if (ch == 'm') {
        ch = 'M';
    } else if (ch == 'n') {
        ch = 'N';
    } else if (ch == 'o') {
        ch = 'O';
    } else if (ch == 'p') {
        ch = 'P';
    } else if (ch == 'q') {
        ch = 'Q';
    } else if (ch == 'r') {
        ch = 'R';
    } else if (ch == 's') {
        ch = 'S';
    } else if (ch == 't') {
        ch = 'T';
    } else if (ch == 'u') {
        ch = 'U';
    } else if (ch == 'v') {
        ch = 'V';
    } else if (ch == 'w') {
        ch = 'W';
    } else if (ch == 'x') {
        ch = 'X';
    } else if (ch == 'y') {
        ch = 'Y';
    } else if (ch == 'z') {
        ch = 'Z';
    }
    return ch;
}

ifelseで書いても同じことですが個人的にはswitchcaseの方が好印象です。

条件演算子を使用する

int lower2upper(int ch)
{
    return ch == 'a' ? 'A' :
           ch == 'b' ? 'B' :
           ch == 'c' ? 'C' :
           ch == 'd' ? 'D' :
           ch == 'e' ? 'E' :
           ch == 'f' ? 'F' :
           ch == 'g' ? 'G' :
           ch == 'h' ? 'H' :
           ch == 'i' ? 'I' :
           ch == 'j' ? 'J' :
           ch == 'k' ? 'K' :
           ch == 'l' ? 'L' :
           ch == 'm' ? 'M' :
           ch == 'n' ? 'N' :
           ch == 'o' ? 'O' :
           ch == 'p' ? 'P' :
           ch == 'q' ? 'Q' :
           ch == 'r' ? 'R' :
           ch == 's' ? 'S' :
           ch == 't' ? 'T' :
           ch == 'u' ? 'U' :
           ch == 'v' ? 'V' :
           ch == 'w' ? 'W' :
           ch == 'x' ? 'X' :
           ch == 'y' ? 'Y' :
           ch == 'z' ? 'Z' : ch;
}

これも個人的にはそれほど悪くない気がするのですが、静的解析ツールに「式が長い」等文句を言われる可能性はありそうです。

変換テーブルを使用する

#include <limits.h>

int lower2upper(int ch)
{
    if (CHAR_MIN <= ch && ch <= CHAR_MAX) {
        static const char t[CHAR_MAX - CHAR_MIN + 1] = {
            ['a' - CHAR_MIN] = 'A',
            ['b' - CHAR_MIN] = 'B',
            ['c' - CHAR_MIN] = 'C',
            ['d' - CHAR_MIN] = 'D',
            ['e' - CHAR_MIN] = 'E',
            ['f' - CHAR_MIN] = 'F',
            ['g' - CHAR_MIN] = 'G',
            ['h' - CHAR_MIN] = 'H',
            ['i' - CHAR_MIN] = 'I',
            ['j' - CHAR_MIN] = 'J',
            ['k' - CHAR_MIN] = 'K',
            ['l' - CHAR_MIN] = 'L',
            ['m' - CHAR_MIN] = 'M',
            ['n' - CHAR_MIN] = 'N',
            ['o' - CHAR_MIN] = 'O',
            ['p' - CHAR_MIN] = 'P',
            ['q' - CHAR_MIN] = 'Q',
            ['r' - CHAR_MIN] = 'R',
            ['s' - CHAR_MIN] = 'S',
            ['t' - CHAR_MIN] = 'T',
            ['u' - CHAR_MIN] = 'U',
            ['v' - CHAR_MIN] = 'V',
            ['w' - CHAR_MIN] = 'W',
            ['x' - CHAR_MIN] = 'X',
            ['y' - CHAR_MIN] = 'Y',
            ['z' - CHAR_MIN] = 'Z',
        };
        if (t[ch - CHAR_MIN]) {
            ch = t[ch - CHAR_MIN];
        }
    }
    return ch;
}

判定したいのは英文字の小文字26字だけなのですが英文字の小文字の文字コードの範囲を知る方法がちょっと思いつかんかった為CHAR_MAX - CHAR_MIN + 1バイトのテーブルを用意して変換しています。あまり良い方法とは思いません。

おわりに

おわりです。

6
2
9

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
2