C語言——操做符 (超詳細概括!!!)

@toc 算法

1.算數操做符

圖片.png

+  -  *  /   %
// 加 減 乘 除 取餘

1.除了 % 操做符以外,其餘的幾個操做符能夠做用於整數和浮點數express

2.對於 / 操做符若是兩個操做數都爲整數,執行整數除法(求商舍餘)。而只要有浮點數,執行的就是浮點數除法。windows

3.% (取模)操做符的兩個操做數必須爲整數,若其中一個不爲整數則報錯,返回的是整除以後的餘數。數組

#include<stdio.h>
int main()
{
    int a = 5;
    int b = 2;
    int c = 0;
    printf("a+b = %d\n", a + b);//相加
    printf("a-b = %d\n", a - b);//相減
    printf("a*b = %d\n", a * b);//相乘
    printf("a/b = %d\n", a / b);//求商
    printf("a%%b = %d\n", a % b);//取模
    return 0;
}

圖片.png

#include <stdio.h>
int main()
{
    /*double a = 5.0 / 2;*/
    double a = 5 % 2.0;//報錯
    printf("a = %lf", a);
    return 0;
}

2.移位操做符

圖片.png

>>      <<  
//右移      左移

移位操做符可分爲 算術移位 和 邏輯移位
算術移位: 先移位,而後補足符號位 (一般是這個)
邏輯移位: 直接移位,0補齊markdown

總結:ide

1.左移移動一位正數至關於直接乘以2,負數也同樣,移動n位,乘以2 ^ n。函數

2.左位移一位 *2,右位移一位 /2(-1右位移仍是-1)測試

#include <stdio.h>
#include <windows.h>
int main()
{
    int a = 16;
    int b = a << 1;
    int c = a >> 2;
    //>>整數移位,移動的是二進制
    //0000 0000 0000 0000 0000 0000 0001 0000 
    int d = -1;
    int e = d >> 1;
    //在計算機中,數據是用補碼存儲的
    //移位運算符操做的是補碼
    //-1的補碼:
    //1111 1111 1111 1111 1111 1111 1111 1111
    //移位後-1的二進制碼不變
    //1111 1111 1111 1111 1111 1111 1111 1111 1
    //算數位移補符號位

    printf("%d\n", b);//輸出結果爲32
    printf("%d\n", c);//輸出結果爲4
    printf("%d\n", e);//輸出結果仍爲-1,移位後-1的二進制碼不變
    system("Pause");
    return 0;
}

圖片.png

右移是以算術移位 的方式來進行右移的設計


例如:3d

咱們先按照算術移位的方式去推導最終結果,而後再修改變量大小成 - 16進行驗證。
圖片.png

警告:

1.移位操做符只能做用於整數,不能用於浮點數,由於浮點數的存儲方式跟整數徹底不一樣

2.對於移位運算符,不要移動負數位,這個是標準未定義的。

eg: int num = 10;
num >> -1;//error


3.位操做符

C語言運算符表示 含義 示例
& 位與 x & y
| 位或 x | y
^ 異或 x ^ y
~ 按位取反 x ~ y

& (按位與) 二進制補碼 兩真才真(1) 一假則假(0)
| (按位或) 二進制補碼 一真則真(1) 兩假才假(0)
^ (按位異或) 二進制補碼 相異爲真(1) 相同爲假 (0)

#include<stdio.h>
int main()
{
    int a = 3;        //00000000 00000000 00000000 00000011
    int b = 5;        //00000000 00000000 00000000 00000101

    int c1 = a & b;   //00000000 00000000 00000000 00000001  =1
    int c2 = a | b;   //00000000 00000000 00000000 00000111  =7
    int c3 = a ^ b;   //00000000 00000000 00000000 00000110  =6

    printf("c1 = %d\n", c1);
    printf("c2 = %d\n", c2);
    printf("c3 = %d\n", c3);
    return 0;
}

圖片.png


練習1:不用臨時變量,交換a,b的值

不用臨時變量,交換a,b的值

假設咱們給定兩個數,a = 3, b = 5;

要實現兩個數的交換,咱們會想到醋和醬油交換的生活案例,咱們會先找一個空瓶子,先將醋或者醬油倒到空瓶子中,(假設將醋先倒入空瓶子中)而後咱們會將醬油倒入原醋瓶中,將原空瓶中的醋倒入醬油瓶中。
方法根據這種方式,咱們會先建立一個臨時變量來用於二者的數值交換

#include <stdio.h>
int main()
{
    int a = 3;
    int b = 5;
    int temp = 0;
    printf("before:a = %d,b = %d\n", a, b);
    temp = a;
    a = b;
    b = temp;
    printf("after:a = %d,b = %d", a, b);
    return 0;
}

可是咱們題目中明確要求不能建立變量,因此這種方式明顯不合適。

這時候咱們考慮另一種方式,從數學的角度出發:若是我先將 a與b之和放到a到當中 而後再用二者之和減去b不就獲得a了嗎,並把這個值賦給b,實現了a的值轉移給b 。同理,用二者之和減去剛剛賦予a值到b中的b,就能獲得原b的值(聽起來有點繞是不,簡單來講:就是 值:a + b - a = b = > 變量a

//方法一:加減法
#include<stdio.h>
//不建立臨時變量實現兩個數的交換
int main()
{
    int a = 3;
    int b = 5;
    printf("before:a = %d,b = %d\n", a, b);
    a = a + b;
    b = a - b;//將a的值給b
    a = a - b;//將b的值給a
    printf("after:a = %d,b = %d", a, b);
    return 0;
}

圖片.png

雖然上面說的這種方式實現了不用建立臨時變量就交換兩個數值,可是仍然存在必定的缺陷,好比咱們的變量a, b並不等於3和5,而是一個很大或者離int邊界閾值很接近的值,若是咱們用二者相加的方法,a + b頗有可能就超出int類型的邊界閾值,獲得一個並不是咱們想要的結果,再拿着這個結果去減去a或者b中一個數,並不能獲得本來的另外一個數的正確值。也就是存在「可能溢出」的缺陷。

既然這種加減法由於閾值的問題存在缺陷,咱們就要去考慮一種既可以成功交換兩個數的值,還不會存在閾值的缺陷,更深層次的思考,數值的存儲方式-- - 二進制,二進制能實現二者交換……a thousand years later—>異或操做符 ^ 這種位操做符不會產生進位的狀況,因此不存在溢出的可能。

//異或辦法
#include<stdio.h>
//不建立臨時變量實現兩個數的交換
int main()
{
    int a = 3;
    int b = 5;
    printf("before:a = %d,b = %d\n", a, b);
    a = a ^ b;
    b = a ^ b;
    a = a ^ b;
    printf("after:a = %d,b = %d", a, b);
    return 0;
}

image-20210802151211902


練習2:求一個整數存儲在內存中的二進制中1的個數

編寫代碼實現:求一個整數存儲在內存中的二進制中1的個數

方法①:看到這個問題,咱們能夠想到利用整數求其二進制原碼,只要在求的過程當中判斷餘數是否爲1,是就計數 + 1,根據這種想法,編寫個人代碼能夠獲得:

#include<stdio.h>
int main()
{
    int a = 0;
    int count = 0;
    scanf("%d", &a);
    while (a)
    {
        if ((a % 2) == 1)
            count++;
            a = a / 2;
    }
    printf("count = %d\n", count);
    return 0;
}

圖片.png

這種方式看似正確,可是當咱們輸入負數的時候,好比 - 1, - 1補碼爲11111111 11111111 11111111 11111111 正確答案應該爲:32 ,而咱們程序運行卻獲得0。
(實際上這種方式還有補救方法,只要將 負數看做無符號位的整型,將a定義從 int a = 0; 成更改爲unsigned int a - 1就會被看成一個超級大的數字11111111 11111111 11111111 11111111 來處理)

圖片.png

方法②:既然這種方法不可行,咱們就應該換一種思路,尋找新的方法,好比我可否獲得輸入數字存儲的二進制補碼,而後統計該補碼中1的個數?答案是:能夠,咱們應該要想到任何一個數若是和 1進行按位與操做,若其最後一位數爲1,則結果爲1,若爲0,結果爲0。再借助移位操做符右移即可以統計1的個數(int類型的大小 爲32位bit),所以能夠編寫代碼爲:

#include<stdio.h>
int main()
{
    int a = 0;
    int count = 0;
    int i = 0;
    scanf("%d", &a);
    for (i = 0; i < 32; i++)
    {
        if (1 == ((a >> i) & 1))
        {
            count++;
        }
    }
    printf("count = %d\n", count);
    return 0;
}

這裏咱們的判斷條件是:
if(1 == ((a >> i) & 1))

也就是將上面的1進行左移操做,而後將二者進行按位與操做,再比較1左移操做的結果 與按位與以後的結果是否相等,若相等,表明該二進制位置 爲 1

方法③:經過對輸入數字的二進制操做,從最後一位1開始,每一步減小一個1

#include<stdio.h>
int main()
{
    int a = 0;
    int count = 0;
    int i = 0;
    scanf("%d", &a);
    while (a)
    {
        count++;
        a &= a - 1;
        //a = a & (a - 1);
    }
    printf("count = %d\n", count);
    return 0;
}

a - 1可以讓a的最後一位變成0 這種奇妙的感受就像是三體小說中描寫的「高維世界像低維世界塌陷」
有這樣的一個公式 n & (n - 1)
假設n = 13 其後四位二進制補碼爲 1101

1101 ---- n
1100 ---- n - 1
1100 ---- n = n & (n - 1)
1011 ---- n - 1
1000 ---- n = n & (n - 1)
0111 ---- n - 1
0000 ---- n = n & (n - 1)

咱們能夠觀察n的變化,發現每一次進行n = n & (n - 1)後,其最後一位的1(最右邊的1)都會變成0(每執行一次,最右邊的1都會消失,直到變成0,中止執行),那麼在n變成0以前,n = n & (n - 1)能執行多少次,就表明最初的n中二進制補碼就有多少個1。
這種算法的執行次數不用每次都執行32次,有多少個1,就執行多少次,因此效率很高!

注意:位操做符在進行運算的時候,不須要考慮符號位的影響,全部位都當作須要參與運算的二進制位,
好比 - 1 ^ -1 = 0, 即符號位也正常參與位操做符運算。


4.複合賦值符

圖片.png

①簡單賦值操做符:
= 賦值操做符(注意:一個等號 = 表示賦值,兩個等號 == 表示條件判斷)
經過賦值操做符,咱們能夠給初始變量賦值,變動調整變量的值(也就是從新給變量賦值)
例如:

int weight = 80;//單位kg 
weight = 60;//對80不滿意,從新賦值成60
double salary = 10000.0;
salary = 30000.0;//對salary只有10000.0不滿意,變成30000.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;

#include<stdio.h>
int main()
{
    int a = 1;
    printf("%d\n", a += 1);
    printf("%d\n", a -= 1);
    printf("%d\n", a *= 2);
    printf("%d\n", a /= 2);
    printf("%d\n", a %= 2);
    printf("%d\n", a <<= 1);
    printf("%d\n", a >>= 1);
    printf("%d\n", a &= -1);
    printf("%d\n", a |= 1);
    printf("%d\n", a ^= 1);
    return 0;
}

圖片.png


5.單目操做符

邏輯反操做
負值
正值
取地址
sizeof 操做數的類型長度(以字節爲單位)
~ 對一個數的二進制按位取反
-- 前置、後置- -
++ 前置、後置++
& 間接訪問操做符(解引用操做符)
(類型) 強制類型轉換比

sizeof 詳細講解:

舉例一:

#include<stdio.h>
int main()
{
    int a = 1;//一個整形爲4的字節
    char b = 'A';
    int arr[20] = { 0 };
    printf("%d\n", sizeof(a));
    printf("%d\n", sizeof a);
    printf("%d\n", sizeof(int));

    printf("%d\n", sizeof(b));
    printf("%d\n", sizeof(char));

    printf("%d\n", sizeof(arr));
    printf("%d\n", sizeof(int[20]));
    return 0;
}

圖片.png

舉例二:

#include<stdio.h>
int main()
{
    short i = 0;
    int j = 10;
    printf("%d\n", sizeof(i = j + 5));//sizeof計算所佔空間的大小
    //無論這裏的j是什麼類型  j+5的結果都是放到變量 i當中  而i又是short類型
    //sizeof(i=j+5)等於 sizeof(i)等於sizeof(short)等於2
    printf("%d\n", i);
    //sizeof裏面放的表達式並不會真實進行運算,只是一個擺設
    //也就是說 i=j+5 這一步操做並未真實執行,因此i仍是原來的值:0
    return 0;
}

圖片.png

注意:sizeof括號中的表達式不參與實際運算!


~ 按位取反詳解:
按二進制補碼取反
例如 0:00000000 00000000 00000000 00000000
~0:11111111 11111111 11111111 11111111 補碼 等於 - 1

#include<stdio.h>
int main()
{
    int a = 0;
    printf("%d\n", ~a);
    return 0;
}

圖片.png

舉例1:

#include<stdio.h>
int main()
{
    int i = 11;
    //00000000 00000000 00000000 00001011 讓倒數第三位變成1,其他不變
    //00000000 00000000 00000000 00000100  按位或  就能獲得
    //1 << 2 1左移2位獲得 00000000 00000000 00000000 00000100

    //int j = i | 4;
    int j = i | (1 << 2);
    printf("%d\n", j);
    //將j變成原來的i  只須要按位與操做
    //00000000 00000000 00000000 00001111
    //11111111 11111111 11111111 11111011
    //00000000 00000000 00000000 00001011
//而  11111111 11111111 11111111 11111011 能夠由按位取反獲得
//    00000000 00000000 00000000 00000100  這個數又能夠經過1左移2位獲得
    j = j & (~(1 << 2));
    printf("%d\n", j);
    //int a = 0;
    //printf("%d\n", ~a);
    return 0;
}

圖片.png


減減-- 、 加加 ++ 詳解:

#include<stdio.h>
int main()
{
    int a = 10;
    //printf("%d\n", ++a);//前置++,先++,後打印
    //printf("%d\n", a++);//後置++,先打印,後++
    //printf("%d\n", --a);//前置--,先--,後打印
    printf("%d\n", a--);後置--,先打印,後--
    printf("%d\n", a);
    return 0;
}

圖片.png


(類型)強制類型轉換詳解:

#include<stdio.h>
int main()
{
    int a = (int)3.14;
    printf("%d\n", a);
    return 0;
}

圖片.png


練習:

#include<stdio.h>
void test1(int arr[])
{
    printf("%d\n", sizeof(arr));
}
void test2(char ch[])
{
    printf("%d\n", sizeof(ch));
}
int main()
{
    int arr[10] = { 0 };
    char ch[10] = { 0 };
    printf("%d\n", sizeof(arr));
    printf("%d\n", sizeof(ch));
    test1(arr);
    test2(ch);
    return 0;
}

圖片.png


6.關係操做符

圖片.png

關係運算符釋義 C語言表示 數學表示
大於 > >
大於等於 >=
等於 == =
不等於 !=
小於 < <
小於等於 <=

關係運算符的應用

  • 關係運算符的運算結果只有 0 或 1。當條件成立時結果爲 1,條件不成立結果爲 0。
  • 咱們來看一段代碼,以下:
#include <stdio.h>
int main() {
    printf("%d\n", 1 > 2);
    printf("%d\n", 1 < 2);
    return 0;
}

獲得的輸出結果爲:

0
1
  • 緣由就是1 &gt; 2在數學上是不成立的,因此結果爲0;而1 &lt; 2在數學上是不成立的,因此結果爲1

7.邏輯操做符

1.&& 邏輯與 判斷規則:兩真則真,一假就假
2.|| 邏輯或 判斷規則:一真則真,兩假才假

詳解:
位操做符中的 & 和 | 操做的是數的二進制補碼 邏輯操做符中的 && 和 || 操做的是數自己 (數爲0爲假,數爲非0爲真)
使用邏輯操做符以後,若表達式總體判斷爲真,則值爲1(1表示真,固定值) 若表達式總體判斷爲假,則值爲0(0表示假,固定值)

舉例一:

#include<stdio.h>
    int main()
{
    int i = 0, a = 0, b = 2, c = 3, d = 4;
    i = a++ && ++b && d++;
    //a++ 先使用a的值0,再++, 判斷a=0後,後面的 ++b && d++均不執行
    printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
    return 0;
}

圖片.png

總結:
邏輯與操做&& : 左邊判斷爲假後,右邊的表達式再也不執行,直接中止,返回值0(假)
邏輯或操做 || 左邊判斷爲真後,右邊的表達式再也不執行,直接中止,返回值1(真)


8.條件操做符

exp1 ? exp2 : exp3

解釋:表達式1進行判斷,若結果爲真,則 exp1 ? exp2 : exp3 總體的結果爲 表達式 exp2 的結果
若結果爲假,則exp1 ? exp2 : exp3 總體的結果爲 表達式 exp3 的結果
exp是expression的縮寫,中文意思爲「表達」

舉例:

#include<stdio.h>
int main()
{
    int a = 10;
    int b = 20;
    int max;
    //if (a > 5)
    //  b = 3;
    //else
    //  b = -3;
    //b = a > 5 ? 3 : -3;
    max = a > b ? a : b;
    printf("a = %d\nb = %d\nmax = %d\n", a, b, max);
    return 0;
}

圖片.png


9.逗號表達式

exp1, exp2, exp3, …….expN

逗號表達式:就是用逗號隔開的多個表達式。
逗號表達式,從左向右依次執行,整個表達式的結果是最後一個表達式的結果。

舉例1:

#include<stdio.h>
int main()
{
    int a = 1;
    int b = 2;
    int c = (a > b, a = b + 10, a, b = a + 1);
    printf("%d\n", c);
    return 0;
}

講解:這裏咱們要輸出c的值,那麼c的值是什麼呢?

int c = (a > b, a = b + 10, a, b = a + 1);
這裏初始化c的時候應用了逗號表達式,逗號表達式從左向右依次執行,整個表達式結果爲最後一個表達式的結果,咱們按照這個原則去推導,a > b 執行可是沒有結果 a = b + 10 至關於a = 2 + 10 = 12, a執行但不產生結果,b = a + 1 = 12 + 1 = 13,整個表達式的值也等於b 即c = 13。

對於這個結果咱們能夠運行程序驗證一下。
圖片.png


拓展:那這裏a, b的值分別爲多少呢?

圖片.png

a = 12, b = 13 再也不是本來的1和2,說明int c = (a > b, a = b + 10, a, b = a + 1); 中運算的結果有效接保留了下來。


10.下標引用、函數調用和結構成員

1.下標引用操做符 [ ]
操做數: 一個數組名 + 一個索引值

#include<stdio.h>
int main()
{
    int a[10] = { 0 };//1.定義一個數組
    //2.若是咱們想要訪問第5個數組元素,並給其賦值
    a[4] = 10;//3.用數組變量名+[]+下標索引數字
    //4.[ ] 對應的兩個操做數一個是變量名a  另一個就是下標/索引值 4
    printf("%d\n", a[4]);
    return 0;
}

圖片.png


2.函數調用操做符()
接收一個或者多個操做數,第一個操做數是函數名,剩餘的操做數就是傳遞給函數的參數。

#include<stdio.h>
//2.這個地方的()不是函數調用操做符,是函數定義的語法規則
int get_max(int x, int y)
{
    return x > y ? x : y;
}
int main()
{
    int a = 10;
    int b = 20;
    //1.調用函數的時候使用的() 就是函數調用操做符
    int max = get_max(a, b);
    //3.這裏的函數調用操做符()的操做數爲 函數名get_max,函數參數a,函數參數b  總共三個操做數
    //4.對於函數調用操做符()而言,其操做數至少要有一個(函數名),能夠有多個
    printf("max = %d\n", max);
    return 0;
}

image-20210802163207093


3.訪問一個結構的成員 . ->
思考:如何建立結構體類型和結構體類型變量呢?

#include<stdio.h>
//1.現實中爲了描述複雜的對象,構建結構體類型
//2.好比學生,有姓名,年齡,學號等信息
//建立一個結構體類型
struct Stu  //3.struct Stu是一個結構體類型,表示學生類型
            //類型是用來建立變量的
{
    char name[20];//姓名
    int age;//年齡
    char id[20];//學號
};
int main()
{
    int a = 10;
    //使用struct Stu這個類型建立了一個學生對象s1,並初始化
    struct Stu s1 = { "張三",20,"20210403" };
    return 0;
}

結構體類型和結構體類型變量詳解:

結構體類型和結構體類型變量的關係相似於圖紙和房子的關係
在蓋房子以前,須要有房屋蓋建的設計圖紙 。有了圖紙,才能根據圖紙蓋出好房子。
在設計圖紙環節,咱們並不會真正去蓋房子,因此就不會佔用土地。
一樣的道理,在建立結構體類型的時候,咱們並不會真正去存儲什麼信息,只有在利用結構體類型建立變量的時候,纔會去存儲相應的信息,向內存申請存儲空間。

建立好結構體變量後,咱們想要打印相關的信息來看一下,這時候就會用到訪問一個結構的成員.或 ->

1.結構體變量.成員名.操做符的操做數一個是結構體變量,另外一個是成員名

image-20210802163310971

2.將結構的地址取出來放入指針,經過指針進行訪問

圖片.png

第2中方式有點太囉嗦了,先要對指針解引用,如何經過.操做符訪問結構體變量的成員。
實際上經過指針訪問的方式能夠更加精簡一些

3.結構體指針->成員名 經過指針的方式直接訪問到變量的成員。

圖片.png


完整代碼:

#include<stdio.h>
//1.現實中爲了描述複雜的對象,構建結構體類型
//2.好比學生,有姓名,年齡,學號等信息
//建立一個結構體類型
struct Stu  //3.struct Stu是一個結構體類型,表示學生類型
            //類型是用來建立變量的
{
    char name[20];//姓名
    int age;//年齡
    char id[20];//學號
};
int main()
{
    int a = 10;
    //使用struct Stu這個類型建立了一個學生對象s1,並初始化
    struct Stu s1 = { "張三",20,"20210403" };
    struct Stu* ps = &s1;
    //結構體指針->成員名
    printf("%s\n", ps->name);
    printf("%d\n", ps->age);
    printf("%s\n", ps->id);

    //printf("%s\n", (*ps).name);
    //printf("%d\n", (*ps).age);
    //printf("%s\n", (*ps).id);

    //printf("%s\n", s1.name);
    //printf("%d\n", s1.age);
    //printf("%s\n", s1.id);
    //結構體變量.成員名  .操做符的操做數一個是結構體變量,另外一個是成員名
    return 0;
}

圖片.png


表達式求值

表達式求值的順序一部分是由操做符的優先級和結合性決定。
一樣,有些表達式的操做數在求值的過程當中可能須要轉換爲其餘類型。

隱式類型轉換(悄悄的進行類型轉換)

①C的整型算術運算老是至少以缺省整型類型的精度來進行的。
爲了得到這個精度,表達式中的字符和短整型操做數在使用以前被轉換爲普通整型,這種轉換稱爲整型提高。
是否是看的不太懂?這裏咱們舉例說明:

#include<stdio.h>
int main()
{
    char a = 3;
    //00000000 00000000 00000000 00000011  整數3
    //00000011  ->a   a是char類型,只有一個字節,這時候就會發生截斷
    //截斷的規則:挑最小最低位的字節內容
    char b = 127;
    //00000000 00000000 00000000 01111111  整數127
    //01111111  ->b
    //a+b  a和b如何相加 按照變量數據類型的符號位來提高的
    //00000011 ->00000000 00000000 00000000 00000011
    //01111111 ->00000000 00000000 00000000 01111111
    //           00000000 00000000 00000000 10000010
    char c = a + b;//這時候a,b被提高爲普通整型
    //10000010 ->c
    //後面咱們要以%d的形式打印,須要進行整型提高:按照變量數據類型的符號位來提高的 
    //11111111 11111111 11111111 10000010  ---補碼
    //11111111 11111111 11111111 10000001  ---反碼
    //10000000 00000000 00000000 01111110  ---原碼 -126
    printf("%d\n", c);
    printf("%c\n", c);
    return 0;
}

圖片.png

至於爲何% c打印出來的是 ? 須要咱們後面進行思考。

整型提高的意義∶
表達式的整型運算要在CPU的相應運算器件內執行,CPU內整型運算器(ALU)的操做數的字節長度通常就是int的字節長度,同時也是CPU的通用寄存器的長度。
所以,即便兩個char類型的相加,在CPU執行時實際上也要先轉換爲CPU內整型操做數的標準長度。 通用CPU(general - purpose CPU)是難以直接實現兩個8比特字節直接相加運算(雖然機器指令中可能有這種字節相加指令)。
因此,表達式中各類長度可能小於int長度的整型值,都必須先轉換爲int或unsigned int,而後才能送入CPU去執行運算。

如何進行整型提高呢 ?
整型提高是按照變量的數據類型的符號位來提高的

//負數的整型提高
char c1 = -1;
變量c1的二進制位(補碼)中只有8個比特位:1111111
由於char爲有符號的char
因此整型提高的時候,高位補充符號位,即爲1 提高以後的結果是 :
11111111111111111111111111111111
//正數的整型提高
char c2 = 1;
變量c2的二進制位(補碼)中只有8個比特位:00000001
由於char 爲有符號的char
因此整型提高的時候,高位補充符號位,即爲0 提高以後的結果是 :
00000000000000000000000000000001
//無符號整型提高,高位補0

整型提高舉例:

圖片.png

解析:
0xb6 b:10 / 1011 6 / 0110 b6 10110110 整型提高後 高位補符號位1 變成了
11111111 11111111 11111111 10110110
負數 0xb600 10110110 00000000
整型提高後 高位補符號位1
變成了 11111111 11111111 10110110 00000000 負數


實例1中if語句裏面進行了比較,比較也是一種運算,其中的a, b要進行整型提高, 可是c不須要整型提高a, b整型提高以後, 變成了負數, 因此表達式a == Oxb6, b == Oxb600的結果是假, 可是c不發生整型提高, 則表達式 c == Oxb6000000的結果是真, 所程序輸出的結果是 : c


圖片.png

解析:
實例2中的, c只要參與表達式運算, 就會發生整型提高
表達式 + c, 就會發生提高, 因此sizeof(+c)是4個字節.
表達式c,表達式(!c)中c實際參與了運算,會發生整型提高,在vs編譯器中顯示的這個結果其實是有問題的,在Linux,gcc編譯器下結果爲4,實際結果也應該爲4。

總結:何時會參與整型提高?
總要字節長度小於int,參與到實際運算後就會進行整型提高。

②算術轉換
若是某個操做符的各個操做數屬於不一樣的類型,那麼除非其中一個操做數的轉換爲另外一個操做數的類型,不然操做就沒法進行。下面的層次體系稱爲尋常算術轉換。
long doub1e
double
float
unsigned long int
long int
unsigned int
int
若是某個操做數的類型在上面這個列表中排名較低,那麼首先要轉換爲另一個操做數的類型後執行運算。(好比說int類型跟float類型參與運算,先要將int轉換爲float類型,而後float類型與float類型運算)
int a = 1;
float b = 2.0
b = a + b;
(會先將a轉換爲float類型 a = 1.00000)

警告 : 可是算術轉換要合理,要否則會有一些潛在的問題。
例如:排名高的類型向低位轉換的時候會出現精度丟失。
float f = 3.14;int num = f;//隱式轉換,會有精度丟失

③操做符的屬性
複雜表達式的求值有三個影響的因素。
1.操做符的優先級
2.操做符的結合性
3.是否控制求值順序。
兩個相鄰的操做符先執行哪一個 ? 取決於他們的優先級。若是二者的優先級相同,取決於他們的結合性。


C語言操做符優先級、結合性參考表:

圖片.png

常見問題解答:
一、如何記住運算符的15種優先級和結合性?
C語言中運算符種類比較繁多,優先級有15種,結合性有兩種。
二、如何記憶兩種結合性和15種優先級?下面講述一種記憶方法。
結合性有兩種,一種是自左至右,另外一種是自右至左,大部分運算符的結合性是自左至右,只有單目運算符、三目運算符的賦值運算符的結合性自右至左。
優先級有15種。

記憶方法:

記住一個最高的:構造類型的元素或成員以及小括號。
記住一個最低的:逗號運算符。
剩餘的是1、2、3、賦值。
意思是單目、雙目、三目和賦值運算符。
在諸多運算符中,又分爲:
算術、關係、邏輯。
兩種位操做運算符中,移位運算符在算術運算符後邊,邏輯位運算符在邏輯運算符的前面。再細分以下:
算術運算符分 * , / , % 高於 + , - 。
關係運算符中,〉,〉 = , < , <= 高於 == ,! = 。
邏輯運算符中,除了邏輯求反(!)是單目外,邏輯與( && )高於邏輯或( || )。
邏輯位運算符中,除了邏輯按位求反(~)外,按位與(&)高於按位半加(^),高於按位或( | )。
這樣就將15種優先級都記住了,再將記憶方法總結以下:
去掉一個最高的,去掉一個最低的,剩下的是1、2、3、賦值。雙目運算符中,順序爲算術、關係和邏輯,移位和邏輯位插入其中。


問題表達式

下面咱們來看一些問題表達式:

//表達式的求值部分由操做符的優先級決定。

表達式1

a * b + c * d + e * f;

註釋∶代碼1在計算的時候,因爲比 + 的優先級高,只能保證,的計算是比 + 早,可是優先級並不能決定第三個 比第一個 + 早執行。
因此表達式的計算機順序就多是︰
a
bc d
a
b + c de f
a b + c d + e f
或者 :
a
bc de f
a b + c d
a b + c d + e * f


表達式2

c + --c;

註釋︰同上,操做符的優先級只能決定自減–的運算在 + 的運算的前面,可是咱們並無辦法得知, + 操做符的左操做數的獲取在右操做數以前仍是以後求值,因此結果是不可預測的,是有歧義的。


代碼3
非法表達式

#include<stdio.h>
int main()
    {
    int i = 10;
    i = i-- - --i * (i = -3) * i++ + ++i; printf("i = %d\n",i);
    return 0;
    }

表達式3在不一樣編譯器中測試結果不一樣,例如:

image-20210802171121897


代碼4

#include<stdio.h>
int fun()
{
   static int count = 1;
   return ++count;
}
int main()
 {
   int answer;
   answer = fun(-fun() * fun());
   printf("%d\n",answer);//輸出多少?
   return 0;
  }

image-20210802171217722

這個代碼有沒有實際的問題 ?
有問題!
雖然在大多數的編譯器上求得結果都是相同的。
可是上述代碼answer = fun() - fun() * fun(); 中咱們只能經過操做符的優先級得知︰先算乘法,再算減法。函數的調用前後順序沒法經過操做符的優先級肯定。


代碼5

#include <stdio.h>
int main()
 {
     int i = 1;
     int ret = (++i) +(++i) +(++i);
     printf("%d\n", ret);
     printf("%d\n",i);
     return 0;
 }

嘗試在Linux環境gcc編譯器,vS2019環境下都執行,看結果。
Linux運行結果:10 4
VS2019運行結果:12 4

看看一樣的代碼產生了不一樣的結果,這是爲何 ?
簡單看一下彙編代碼.就能夠分析清楚.
這段代碼中的第一個 + 在執行的時候,第三個++是否執行,這個是不肯定的,由於依靠操做符的優先級和結合性是沒法決定第一個+和第三個前置++的前後順序。

總結∶咱們寫出的表達式若是不能經過操做符的屬性肯定惟一的計算路徑,那這個表達式就是存在問題的。

相關文章
相關標籤/搜索