はじめに
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
個人的にはそれほど悪いとは思わない方法です。
ちょっと冗長な見た目ですが、現代の優秀なコンパイラでは驚くほどコンパクトなコードを吐くのでコードサイズ的な問題はないと思います。
lower2upper:
leal -97(%rdi), %ecx
movl %edi, %eax
leal -32(%rdi), %edx
cmpl $26, %ecx
cmovb %edx, %eax
ret
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;
}
if
~else
で書いても同じことですが個人的にはswitch
~case
の方が好印象です。
条件演算子を使用する
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
バイトのテーブルを用意して変換しています。あまり良い方法とは思いません。
おわりに
おわりです。