はじめてのC言語 - 演算子と演算子の間違いやすい点を詳しく解説

シリーズ記事ディレクトリ

 第 1 章 「C」Huzhuan - 初めて C 言語を知る (初心者向け!)

 第 2 章では、 分岐文とループ文、およびそれらのエラーが発生しやすい点について詳しく理解しています。 

 第3章 初心者C―機能を詳しく紹介

 第4章 はじめてのC言語——配列の内容と間違いやすい点を詳しく解説

 第5章 はじめてのC言語 - 演算子と演算子の間違いやすいポイントを詳しく解説


目次

シリーズ記事ディレクトリ

序文

1. 事業者の分類

2. 算術演算子 (2 つのオペランド)

3. シフト演算子 (2 つのオペランド)

3.1 左シフト演算子 <<

 3.2 右シフト演算子 >>

3.3 警告 

4、ビット演算子 (2 つのオペランド)

4.1 ビット単位の AND &

ビットごとの AND の具体的な用途:

 4.2 ビットごとの OR |

4.3 ビットごとの XOR^ 

ビットごとの XOR の具体的な用途:

4.4 バイナリビットの演習 

5. 代入演算子

 複合代入演算子

6、単項演算子 (1 つのオペランド)

6.1 単項演算子の概要

6.2 論理逆演算子 ! 

 6.3 アドレス演算子 & と逆参照演算子* 

6.4 オペランドの型長sizeofの計算

6.5 ++ および -- 演算子

7. 関係演算子 (2 つの演算子)

8、論理演算子 (2 つの演算子)

8.1 論理積演算子 &&

8.2 論理和演算子 || 

8.3 ロジックショート 

9、条件演算子(三項演算子)

10. カンマ式 

11. 添字参照、関数呼び出し、構造体メンバー 

11.1 [ ] 添字参照演算子 (2 つのオペランド)

11.2 ( ) 関数呼び出し演算子 (少なくとも 1 つのオペランド)

11.3 構造体のメンバーへのアクセス 

要約する


序文

       前の章では、エディターによって、1次元配列と 2 次元配列の作成と初期化1 次元配列と 2 次元配列をメモリに格納する方法範囲外の配列など、配列について詳しく学習することができました。 ,関数のパラメータとして配列を使用する場合はどうでしょうか。

       この章では、エディターが演算子の内容を学習します。目次を見ると、この章の内容が多く、すべての演算子が含まれていることは難しくありません。辛抱強く学習できることを願っています。この章を読んでください。


1. 事業者の分類

       なぜ演算子を学ぶのでしょうか? 式の評価用です。演算子を学ぶ前に、エディターに従って演算子の分類を学びましょう。

  • 算術演算子: +、-、*、/、%
  • シフト演算子: <<、>>
  • ビット演算子: &、|、^
  • 代入演算子: =、+=、-=、/=、*=、%=...
  • 単項演算子: ! 、-、+、sizeof、~、(型)
  • 関係演算子: >、<、>=、<=、!=、==
  • 論理演算子: &&、||
  • 条件演算子: exp1? 経験値2: 経験値3
  • カンマ式: exp1、exp2、exp3、...expN
  • 添字参照、関数呼び出し、構造体メンバー: [ ]、()、.、->

2. 算術演算子 (2 つのオペランド)

       この部分では編集者は特に話すことはないと思いますが、このブログは演算子を詳しく解説することを目的としているので、間違いやすいポイントを解説していきます!算術演算子は、その名前が示すように、+、-、*、/、% などの数学演算です。+、-、*の 3 つの演算子については特に説明する必要はありません。次に、エディターは: /、% の説明に重点を置きます。 

  1. % 演算子に加えて、他のいくつかの演算子が整数と浮動小数点数を処理します
  2. / 演算子の場合、両方のオペランドが整数の場合、整数の除算が実行されますそして、2 つの数値の中に浮動小数点数が含まれている限り、浮動小数点数の除算になります。
  3. % 演算子の 2 つのオペランドは整数である必要があり、除算後の剰余が返されます。

3. シフト演算子 (2 つのオペランド)

       それについて話す前に、シフト演算子はバイナリ ビットを移動することについて触れておきたいと思います。ここでは、まずC言語のバイナリについて説明します。

整数       の場合は4 バイト、つまり==32bitです。整数をバイナリ シーケンスとして書き込むと、その整数は 32 ビットになります。以下の図を見て理解してください。

       1)符号付き整数の場合、最上位ビットは符号ビットです。符号ビットは 1 で負の数を示し、符号ビットは 0 で正の数を示します。 

       2)符号なし整数の場合、符号ビットはなく、すべてのビットが有効ビットです。

整数のバイナリ表現には元のコード補数コード補数コードの3 つがあります。

元のコード:値の符号に従って、直接書き込まれたバイナリ シーケンスが元のコードです。

逆コード:元のコードの符号ビットは変更されず、他のビットはビットごとに反転されます。

補数コード:補数コードの 2 進数 + 1 が補数コードです。 

この例から、正の整数の場合、元のコード、逆コード、および補数コードは同じであり、計算は必要ありません。負の整数の場合、元のコード、逆コード、および補数コードを計算する必要があることが       わかります。

               整数はメモリ上では 2 の補数で格納され、計算の際も整数は 2 進数で計算されます。


3.1 左シフト演算子 <<

シフト ルール:左側を破棄し、右側に 0 を追加します。

       通常の数値には特別なケースはなく、一般に、事実上、1 ビットを左にシフトすることは、 2 を乗算することと同じです正の数または負の数の場合、いくつかの特に大きな数が異なることを除いて実際には同じであり、基本的に次のとおりです。左側の上位ビットが何であっても、符号ビットを破棄し、その後に加算される数サインビット。

以下に例を示します。

 


3.2 右シフト演算子 >>

       最初に言っておきますが、右シフト演算子はより難しいです。まず、右シフト演算は1. 論理右シフト2.算術右シフト の2 種類に分けられます。2 つの主な違いは、キーは左側に追加されたものに依存することです。

シフト規則: 1. 論理シフト: 左側は 0 で埋められ、右側は破棄されます; 2. 算術シフト: 左側は元の値の符号ビットで埋められ、右側は破棄されます。

       同様に、通常の数値には特別なケースはなく、一般に、1 ビット右にシフトすることは、実質的に 2 で除算することと同等です正の数の場合は右側の 1 ビットを破棄し、左側に直接 0 を追加します負の数については2 つのケースがありますので、一般的には、まずコンパイラがどちらの右シフト演算を使用しているかを理解してください。その方法は次のとおりです。以下に従う:算術右シフトの場合: 左側は元の符号ビットで埋められ、論理右シフトの場合: 左側は 0 で埋められます。

 以下に例を示します。

       正の整数の場合これら 2 つの右シフト演算は同じですが、負の整数の場合、これら2 つの右シフト演算は同じではないため、負の整数に注目してください。この違いに基づいて、使用しているコンパイラでどの右シフト演算がサポートされているかを確認するコードを作成できます。VS は算術右シフトを使用し、ほとんどのコンパイラは算術右シフトを使用します以下のコードを見てください。

#include <stdio.h>
int main()
{
	int a = -10 >> 1;
	if (a < 0)
		printf("该编译器用的是算术右移!\n");
	else
		printf("该编译器用的是逻辑右移!\n");
	return 0;
}

3.3 警告 

シフト演算子の場合、負のビットをシフトしないでください。これは標準では定義されていません。下の写真を見てください。


4、ビット演算子 (2 つのオペランド)

       初めて C 言語を学習したとき、おそらく先生はこの部分を知らなかったでしょう。少なくとも私たちの先生はそれについて話しませんでしたこの部分は非常に重要であり、いくつかのバイナリ問題で大きな役割を果たします。ただし、ビット演算子と論理演算子を混同しないでください。次は編集者が個別に勉強を指導します!

4.1 ビット単位の AND &

演算ルール:同時に1であれば1、0があれば0となります。

 この演算ルールを覚えて計算するコードを以下に説明します。

int main()
{
	int a = 7;
	int b = -10;
	int c = a & b;//按二进制的位与
	//00000000 00000000 00000000 00000111 ----- 7的补码
	//10000000 00000000 00000000 00001010
	//11111111 11111111 11111111 11110101
    //*************************************
	//*11111111 11111111 11111111 11110110*----- -10的补码
	//*00000000 00000000 00000000 00000111*----- 7的补码
    //*************************************
	//同1为1,有0则0
	//00000000 00000000 00000000 00000110 ----- 6
	printf("%d", c);
	return 0;
}

ビットごとの AND の具体的な用途:

       特定の数値の 2 の補数の特定のビットが 1 であるか 0 であるかを知りたい場合は、ビット単位の AND 演算を使用してそれを取得できます。なぜ?

       基本的な考え方は次のとおりです。ビットごとの AND の演算規則は次のとおりです。同じ 1 は 1、0 は 0 です特定の数値の 2 の補数の最後の桁を知りたい場合は、AND 1 (2^0) を実行するだけで、特定の数値の 2 の補数の最後の桁が次のようになります。ビット単位の AND が 1 に等しい場合、最後の数値は 1 になり、それが 0 の場合、最後の数値は 0 になります。同様に、特定の数値の 2 の補数の最後から 2 番目の桁を求める場合は、 2(2^1) に対してビット単位の AND を実行するだけで済みます

質問 1:ある数値の 2 の補数におけるある桁の数は何ですか?

************************************************* ************************************************* ****

方法:上記の基本的な考え方から、式をまとめることができます。 特定の数値の 2 の補数の最後の N 桁の数値を求めたい場合は、数値の上位 2 のビット単位の ANDを使用するだけで済みます特定の数 (N-1 ) の (2^(N-1)) 乗

************************************************* ************************************************* ****

コード:

int main()
{
	int num = 10;
	//如果想要求10的二进制补码中最后一位数
	int ans = num & 1;
	printf("ans = %d\n", ans);
	return 0;
}

トピック 2: 特定の数値の 2 の補数の 1 の最後の位置にある数値を見つけます。

************************************************* ************************************************* ****

方法:

************************************************* ************************************************* ****

コード:

 int onlyone = eor & (~eor + 1);

 4.2 ビットごとの OR |

動作規則:同時に0の場合のみ0となり、1になったら1になります。

このような操作は説明の必要はありませんが、操作ルールさえ覚えていればそのまま実行できますので、コードを以下に説明します。

int main()
{
	int a = 7;
	int b = -10;
	int c = a | b;//按二进制的位或
	//00000000 00000000 00000000 00000111 ----- 7的补码
	//10000000 00000000 00000000 00001010
	//11111111 11111111 11111111 11110101
	//*************************************
	//*11111111 11111111 11111111 11110110*----- -10的补码
	//*00000000 00000000 00000000 00000111*----- 7的补码
	//*************************************
	//同0为0,有1则1
	// 11111111 11111111 11111111 11110111 补码
	// 11111111 11111111 11111111 11110110 反码
	// 10000000 00000000 00000000 00001001 原码 --- -9
	printf("%d", c);
	return 0;
}

4.3 ビットごとの XOR^ 

演算規則:同じ場合は0、異なる場合は1。覚えておくべき 2 つの公式は次のとおりです: a ^ a = 0、a ^ 0 = a。

プロパティ: ビット単位の演算は、交換法則および結合法則に従います。

ビット単位の XOR の演算規則は比較的単純ですが、同じは 1、差は 0 というビット単位の XOR 演算規則を今後       学習する必要があります。したがって、2 つの操作を混同しやすいです。

では、ビット単位の XOR をどのように覚えればよいのでしょうか? ビットごとの XOR -つまり、根拠のない加算根拠のない加算をどのように理解すればよいでしょうか? 下の写真を見てください。

int main()
{
	int a = 7;
	int b = -10;
	int c = a ^ b;//按二进制的位异或
	//00000000 00000000 00000000 00000111 ----- 7的补码
	//10000000 00000000 00000000 00001010
	//11111111 11111111 11111111 11110101
	//*************************************
	//*11111111 11111111 11111111 11110110*----- -10的补码
	//*00000000 00000000 00000000 00000111*----- 7的补码
	//*************************************
	//相同为0,不同为1
	// 11111111 11111111 11111111 11110001 补码
	// 11111111 11111111 11111111 11110000 反码
	// 10000000 00000000 00000000 00001111 原码 --- -15
	printf("%d", c);
	return 0;
}

ビットごとの XOR の具体的な用途:

 非常に倒錯的なインタビューの質問: 

トピック 1: 2 つの数値を交換するために一時変数 (3 番目の変数) を作成することはできません。

************************************************* ************************************************* ****

方法 1:まず 2 つの数値 a と b を加算して a に代入し、次に a から b を減算して b に代入し、b に格納されるものが a になるようにします。次に、a から b を減算します (時点の b の値)。今回は a) を a に代入すると、a の値が b の値になります。

************************************************* ************************************************* ****

コード:

int main()
{
	int a = 10;
	int b = 90;
	printf("交换前:a = %d, b = %d\n", a, b);
	//方法一:
	a = a + b;
	b = a - b;
	a = a - b;
	printf("交换后:a = %d, b = %d\n", a, b);
	return 0;
}

欠点:変数の値が非常に大きい場合、データの切り捨てが発生します。

************************************************* ************************************************* ****

方法 2:ビット単位の XOR を使用し、 a ^ a = 0、 a ^ 0 = aに従って、それを取得できます。この処理ではキャリーは発生せず、データがオーバーフローすることはありません。

************************************************* ************************************************* ****

コード:

int main()
{
	int a = 10;
	int b = 90;
	printf("交换前:a = %d, b = %d\n", a, b);
	//方法二:
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	printf("交换后:a = %d, b = %d\n", a, b);
	return 0;
}

短所:このコードはあまり読みにくくあまり効率的でもありません

トピック 2:  2 つの数値のみが奇数回出現し、他の数値が偶数回出現する数値 arr が与えられた場合、2 つの数値を昇順で出力します。

************************************************* ************************************************* ****

方法:まず、これらすべての数値の XOR を計算します。奇数回出現する 2 つの数値の XOR 結果を取得し、それらを eor に代入します。これは、2 つの数値が等しくなく、eor のバイナリ内に次の位置が存在する必要があることを示します。 1、位置が 1 の数値を見つけます (4.1 ビットごとの AND の具体的な適用を参照)、配列内の数値を 2 つの部分に分割します。1 つの部分は位置が 1 の数値で、もう 1 つの部分は位置が 1 の数値です。は0です。次に、2 つの数値が分離され、XOR のデータのセットをランダムに選択して 1 つの数値を取得し、eor XOR を使用して別の数値を取得できます。

************************************************* ************************************************* ****

コード:

#include <stdio.h>
int main() 
{
    int n = 0;
    int eor = 0, eor1 = 0;
    int temp = 0, ans = 0;
    scanf("%d", &n);
    int arr[100000];
    for(int i = 0; i < n; i++)
    {
        scanf("%d", &arr[i]);
    }
    for(int i = 0; i < n; i++)
    {
        eor ^= arr[i];
    }
    int onlyone = eor & (~eor + 1);
    for(int i = 0; i < n; i++)
    {
        if((onlyone & arr[i]) != 0)
            eor1 ^= arr[i];
    }
    ans = eor ^ eor1;
    if(eor1 > ans)
    {
        temp = eor1;
        eor1 = ans;
        ans = temp;
    }
    printf("%d %d\n", eor1, ans);
    return 0;
}

4.4 バイナリビットの演習 

トピック: 達成するコードの作成: メモリに格納されているバイナリ内の 1 の数を整数で求めます。


5. 代入演算子

       代入演算子は優れた演算子です。前の値に満足できない場合は、この演算子を使用して自分自身を再代入できます。

例えば:

int weight = 120;//体重
weight = 80;//不满意就赋值
double salary = 100000.0;
salary = 200000.0; //使用赋值操作符赋值

代入演算子は連続して使用できますが、右から左に順番に実行されます。 

	int a = 10;
	int x = 0;
	int y = 20;
	a = x = y + 1; //连续赋值,可读性不高
	//改进
	x = y + 1;
	a = x;//这样写更加清晰爽朗而且便于调试

 複合代入演算子

+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=

これらの演算子は複合効果として記述できるため、コードがより整理されます

例えば:

	int x = 10;
	x = x + 10;
	x += 10; //这样写比较整洁

6、単項演算子 (1 つのオペランド)

6.1 単項演算子の概要

6.2 論理逆演算子 ! 

操作規則: true を false に、false を true に変更します

 使用シナリオを見てみましょう。

int main()
{
    int a = 0;
    if(!a)
        printf("hehe\n");
    return 0;
}

 6.3 アドレス演算子 & と逆参照演算子* 

       アドレス演算子は変数のアドレスを取り出して p に格納し、逆参照演算子はp に格納されているアドレスを通じて p が指すオブジェクトを検索します。

6.4 オペランドの型長sizeofの計算

int main()
{
	int a = 10;
	printf("%d\n", sizeof(a));
    printf("%d\n", sizeof a); //这种写法是正确的,证明了sizeof不是函数,函数后的括号省略不了
	printf("%d\n", sizeof(int));
	return 0;
}

       上記のコードからわかるように、sizeof は変数の長さと型の長さの両方を計算できます。このコードを実行すると、次の図が表示されるのはなぜですか。

sizeof 計算の結果は size_t 型であり       、size_t符号なし整数であるため、%zdを使用して size_t 型のデータを出力できます%zd をサポートしていない一部の古いコンパイラでは、%uも使用できます

6.5 ++ および -- 演算子

演算規則:前置 ++ (--): 最初に + (-) 1 を使用し、後置 ++ (--): 最初に使用し、その後 + (-) 1 を使用します。 


7. 関係演算子 (2 つの演算子)

        これらの演算子は比較的単純であり、説明することはありませんが、一部の演算子を使用する場合は落とし穴に注意する必要があります。

警告:プログラミングの過程で、== と = が誤って間違って書かれ、エラーが発生します。


8、論理演算子 (2 つの演算子)

8.1 論理積演算子 &&

演算規則:論理 AND は AND、すべての true は true、false は false です。

論理 andビット単位の andを区別する

1 & 2  -------->0
1 && 2 -------->1

8.2 論理和演算子 || 

演算規則:論理和は or、すべての false は false、true は true。

論理和ビット単位のOR を区別する

1 | 2 -------->3
1 || 2 ------->1

8.3 ロジックショート 

演算規則: && 左側のオペランドが false の場合、右側の式はカウントされません; || 右側のオペランドが true の場合、右側の式はカウントされません。

この演算子を学んだ後、クラスメートが私に尋ねた質問を突然思い出しました。

       今この質問を見たとき、選択肢 b と c の両方が間違っているように感じられたので混乱しましたが、そうではありません。c オプションには論理的短絡があるため、私が知らない知識が含まれているため C 言語では、計算を実行するときに、プログラムが過負荷な計算を実行しないようにコードを最適化する必要があります。プログラムでは、a||(b = c) がこの論理演算であるのに、なぜ論理的短絡が発生するのでしょうか? C言語では、プログラムの計算量を減らすために、この論理演算を行う際、元の論理演算は正しいまま、式の正確な値が決まった場合には後続の演算を計算しないで演算を簡略化する手法が採用されています。

       OR 演算では、a が true の場合、次の値が何であれ、計算する必要はありません。プログラムの結果はすでにわかっているため、論理短絡を実行し、b を計算しません。 = c なので、c は計算されません。 b に代入しても、b は元の値と等しくなります。a が false の場合、後続の結果が結果全体に影響を与える可能性があることを意味するため、論理短絡は実行できません。

筆記試験の問題は次のとおりです。

トピック:

int main()
{
	int i = 0, a = 0, b = 2, c = 3, d = 4;
	i = a++ && ++b && d++;
	printf(" a = %d\n b = %d\n c = %d\n d = %d\n", a, b, c, d);
	return 0;
}

答え:


9、条件演算子(三項演算子)

 この演算子はコードを簡素化するために使用されていると思います。詳細については、次のコードを参照してください。

if (a > 5)
	b = 3;
else
	b = -3;
//等价于
b = a > 5 ? 3 : -3;

応用: 

トピック:条件式を使用して 2 つの数値のうち大きい方の値を見つける

方法:条件式を使用する

コード:

int main()
{
	int a = 90, b = 78;
	int ans = a > b ? a : b;
	printf("%d\n", ans);
	return 0;
}

10. カンマ式 

計算規則:計算は左から右に順番に実行され、式全体の結果は最後の式の結果となり、前の計算は後続の結果に影響を与える可能性があります。 

 使用シナリオを見てみましょう。

	a = get_val();
	couny_val(a);
	while (a > 0)
	{
		//业务处理
		a = get_val();
		couny_val(a);
	}
	//如果使用逗号表达式,改写:
	while (a = get_val(), couny_val(a), a > 0)
	{
		//业务处理
	}

11. 添字参照、関数呼び出し、構造体メンバー 

11.1 [ ] 添字参照演算子 (2 つのオペランド)

オペランド:配列名 + インデックス値

int arr[10]; //创建数组
arr[9] = 10; //使用下标引用操作符
[ ]的两个操作数是arr和9。

11.2 ( ) 関数呼び出し演算子 (少なくとも 1 つのオペランド)

1 つ以上のオペランドを受け入れます。最初のオペランドは関数名で、残りのオペランドは関数に渡されるパラメーターです。

11.3 構造体のメンバーへのアクセス 

.struct.メンバー名

-> 構造体ポインタ -> メンバー名


要約する

       このパートでは、編集者がオペレーターの詳細な説明をブログに書きました。皆さんも読んだらコメントを残していただけると嬉しいです、ありがとうございます!

おすすめ

転載: blog.csdn.net/2301_77868664/article/details/132173595