■この記事の概要
この記事は、C言語の否定演算子「!」の使い方を、基本的な例とド・モルガンの法則を用いた応用的な使い方とともに解説しています。
簡単なサンプルコードを通じて、否定演算子のトリッキーな使い方や、条件式のリファクタリングに役立つ技術を紹介しています。
又、この記事は、C言語における論理否定演算子!
の誤用によるエラー例を解説しています。
特にif(!x == 0)
のようなコードの誤解を避けるための適切な書き方や、演算子の結合順序による問題とその対策を示しています。
■論理否定演算子の基本的な使い方
//論理否定演算子ビックリマークの使い方
#include <stdio.h>
#include <ctype.h>
int main(void){
char s[] = "ABC123def456\n";
printf("もし、英字でけれなば %s ➡",s);
for(int i = 0 ;s[i] != '\0';i++){
if(!isalpha(s[i])){
putchar(s[i]);
}
}
printf("もし、数字でなければ %s ➡",s);
for(int i = 0 ;s[i] != '\0';i++){
if(!isdigit(s[i])){
putchar(s[i]);
}
}
}
./a.out
もし、英字でけれなば ABC123def456
➡123456
もし、数字でなければ ABC123def456
➡ABCdef
■否定演算子でド・モルガンの法則を検証する
#include <stdbool.h>
#include <stdio.h>
//ド・モルガンの法則
void DeMorgan(bool x,bool y)
{
bool d11 = (!x && !y);
bool d12 = !( x || y);
puts(d11 == d12 ? "同じ" : "違う");
bool d21 = (!x || !y);
bool d22 = !( x && y);
puts(d21 == d22 ? "同じ" : "違う");
}
int main(void)
{
DeMorgan(false,false);
DeMorgan(false,true);
DeMorgan(true,false);
DeMorgan(true,true);
}
./a.out
同じ
同じ
同じ
同じ
同じ
同じ
同じ
同じ
複雑な条件式をリファクタリング(動作を変えずにコードをわかりやすくする事)する際、
ド・モルガンの法則はよく使用されます。
■論理否定演算子の二重否定によるbool型変換
//論理否定演算子の二重否定によるbool型変換
#include <stdio.h>
int main(void){
int x;
x = 0; printf("x = 0 ➡ %d\n",!!x);
x = 1; printf("x = 1 ➡ %d\n",!!x);
x = 2; printf("x = 2 ➡ %d\n",!!x);
x = 0xFF; printf("x = 0xFF ➡ %d\n",!!x);
}
./a.out
x = 0 ➡ 0
x = 1 ➡ 1
x = 2 ➡ 1
x = 0xFF ➡ 1
任意の値を真偽値(0/1)に変換したい時の技です。
どんな値でも!!と2重否定すると
必ず0か1になります。
非ゼロの時⇒!非ゼロは0⇒!0⇒1
ゼロの時 ⇒!ゼロは1⇒!1⇒0
■if(!x == 0)は誤読しやすい
warning: logical not is only applied to the left hand side of this comparison
警告:!の結合力は==よりも強い
[-Wlogical-not-parentheses]
#include <stdio.h>
void f(int x){
//推奨しない書き方
if(!x == 0) puts("1");
if((!x)==0) puts("2");
if(!(x==0)) puts("3");
//推奨する書き方
if(x == 0) puts("4");
if(x != 0) puts("5");
}
int main(void){
f(0);
puts("★");
f(1);
}
if(!x == 0)のコードの意図が
if(x== 0)なのか
if(x != 0)なのか、よくわかりません。
if((!x)==0)や
if(!(x==0))のように丸括弧をつけても
分かりにくさは改善されません。
■論理演算とビット演算の混在はバグの臭い
warning: suggest parentheses around operand of ‘!’ or change ‘&’ to ‘&&’ or ‘!’ to ‘~’
警告:否定演算子!はビット演算子&より結合力が強い
[-Wparentheses]
#include <stdio.h>
void f(int x,int y) {
printf("★x=%d,y=%d\n",x,y);
//論理算子とビット演算の混在はバグの臭い
printf("%08x\n",!x & y);
//プログラマの意図がわからないので修正できない
printf("%08x\n",!x && y); //両方論理演算
printf("%08x\n",~x & y); //両方ビット演算
printf("%08x\n",(!x) & y); //結合力を明示
printf("%08x\n",!(x & y)); //結合力を明示
printf("%08x\n",(x==0) & y); //!の代わりに==0
printf("%08x\n",(x & y) == 0); //!の変わりに==0
}
int main(void){
f(0,0);
f(0,1);
f(1,0);
f(1,1);
}
論理演算とビット演算を混在させると、
プログラマが何をしたいか
分からなくなるので避けましょう。
原作
if(!x & y)
修正案
if(!x && y)//両方論理演算子
if(~x & y)//両方ビット演算子
if((!x) & y)//結合力を明示する
if(!(x & y))//結合力を明示する
if((x==0) & y)//!の代わりに==0
if((x & y) == 0)//!に代わりに==0
■if(!strcpy(p1,p2))はバグ
int f(char *p1,char *p2){
if(!strcpy(p1,p2))
return 1;
return 0;
}
多分strcmpの間違い
strcpyは第一引数をオーム返しするだけなので
第一引数にNULLはあり得ない。
■if(!strcmp(p1,p2) == 0) は多分バグ
int f(char *p1,char *p2){
if(!strcmp(p1,p2) == 0)
return 1;
return 0;
}
多分否定演算子!が不要
参考: