日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

C語言-操作符(詳細)和表達式求值

作者:c鐵柱同學 更新時間: 2022-02-13 編程語言

本篇是對各種操作符和表達式的求值的介紹

1.算數操作符

2.移位操作符

3.位操作符

4.賦值操作符

5.單目操作符

6.關系操作符

7.邏輯操作符

8.條件操作符

9.逗號表達式

10.下標引用操作符與函數調用操作符

11.結構體成員操作符

12.表達式求值

13.操作符的屬性

1.算數操作符

+   加法
-   減法
*   乘法
/   取商
%   取余

算數操作符與數學類似,值得注意的是 / 和 % 。

float a,b;
a=10/3;      //值為3
b=10/3.0;    //3.3333333

/ 的兩個操作數如果都是整數,那么進行的就是整數除法,兩個操作數中至少有一個是小數時,才進行小數除法。而 % 的兩個操作數都必須是整形。

2.移位操作符

<<     左移
>>     右移

移位操作符移動的是二進制位

整數的二進制位有三種形式:

原碼

反碼

補碼

一個整形的大小是4個字節,所以占32個bit位,所以一個整形的二進制位一共有32位。第一位表示符號位,0表示整數,1表示負數。

整數在內存中都是以二進制的補碼儲存的

原碼就是把整數從十進制轉換成二進制數,然后補齊到32位得到

反碼就是原碼的符號位不變,其他的位全部按位取反得到的

補碼就是把反碼加一得到的

而正數的的原碼,反碼,補碼都是相同的

所以,我們要計算的只有負數的補碼

int a=0;
a<<1    //把a左移一位

我們知道,位移操作符是對整數的二進制位進行位移,而這個二進制位就是二進制的補碼。

對負數進行操作時,要先算出負數的補碼,在進行操作,然后再轉換成原碼就可以得到操作后的值

左移的規則

把補碼整體向左移,左邊的丟掉,右邊補零

右移的規則

算數右移:右邊丟棄,左邊補原符號位

邏輯右移: 右邊丟棄,左邊補零

?在右移時,到底進行算數右移還是邏輯右移是取決于編譯器的。(我當前的編譯器是算數右移)

?此外,還有一個未定義的規則:不要移動負數位

int a=0;
a>>-1;

3.位操作符

&    按位與
|    按位或
^    按位異或

與位移操作符一樣,也是對整數的二進制補碼進行操作,

a&b a與b的二進制補碼按位與(見零位零)。注意與取地址&區分,取地址&是單目操作符

a|b a與b的二進制補碼按位或(見一為一)。

a^b a與b的二進制補碼按位異或(相異為一,相同為零)。

?由上面的定義我們可以知道

a^a=0;
a^0=a;

當我們想交換a,b兩個數時,我們可以使用這樣一種方法(只能用于整數)

a=a^b;
b=a^b;
a=a^b;

?下面我們做一道例題:求出一個數在內存中存儲的1的個數

思考:假設我們要判斷一個數的在內存中存儲的補碼的最后一位是不是1,那我們只需要讓這個數按位與一個1,如果結果是0,說明最后一位是0,如果結果是1,說明最后一位是1,那么當我們想要驗證別的位時,我們只需要把1左移,然后把每一位都驗證一邊即可

int main()
{
    int n=0;
    scanf("%d",&n);
    int i,count;
    for(i=0;i<32;i++)
        if(1==(n<<i)&1)
            count++;
    printf("%d\n",count);
    return 0;
}

4.賦值操作符

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

這個比較好理解,就是賦值,需要注意的是

連續賦值:

a=x=y+1;

先把y+1賦給x,再把x賦給a

復合賦值:

a=a+2 <==> a+=2;
a=a>>1 <==> a>>=1;
a=a&4 <==> a&=4;

5.單目操作符

!      邏輯反
-       負值
+       正值
&       取地址
sizeof  計算類型創建的變量所占的內存大小,單位是字節
~       對一個數的二進制位按位取反(包括符號位)
--      前置,先減后用,后置,先用后減
++      前置,先加后用,后置,先用后加
*       解引用(間接訪問)  *&a <==> a
(類型)  強制類型轉換

邏輯反:C語言中,在c99之前,是沒有表示真假的類型的,我們用0表示假,非0表示真,在c99中引入了布爾類型頭文件是<stdbool.h>專門用來表示真假

bool flag1=ture;
bool flag2=false;

邏輯反就是把真變成假,假變成真。

取地址與解引用:取地址是取一個變量的地址,解引用是通過這個地址找到這個變量,所以他們經常成對使用

int a=10;
int* pa=&a;
*pa=5;
printf("%d",a);//輸出結果為5

sizeof:可能會有初學者覺得它是一個函數,因為確實使用的時候也很像是函數,但其實sizeof是一個操作符,我們可以通過下面的代碼驗證。

int main()
{
    int a=10;
    printf("%d\n",sizeof a);
    printf("%d\n",sizeof(int));
    return 0;    //輸出結果都是4
}

當我們省略a兩邊的括號時,代碼依然可以跑起來,這就說明了sizeof不是函數,因為函數的括號是絕對不能省略的(這種省略的寫法只是為了大家好區分sizeof是不是函數,并不支持大家這樣去寫,并且sizeof括號里如果是類型的話,也是不能省略括號的)

sizeof還有一個屬性

int a=10;
short s=0;
printf("%d\n",sizeof(s=a+2));//輸出2
printf("%d\n",s);            //輸出0

為什么第二個沒有輸出12呢,因為sizeof()括號中的表達式是不參與計算的,因為我們的電腦在執行我們寫的源文件時,會先編譯,再生成鏈接,最后生成.exe文件。而sizeof是在編譯期間處理的,表達式則是在程序編好后才運行的。所以上面的代碼中s的值沒有被改變。

~按位取反:當我們想把某一個數的某一個二進制位變為1時,我們可以讓這個數按位或一個1左移n位 a|(1<<n),但是當我們想變回來時,可以用 a&(~(1<<n))實現。

還有當我們在讀取多組數時,一般的代碼為

while(scanf("%d",&n)!=EOF);
{
    …………;
}

但是我們也可以用這種方法寫讀取多組數

while(~scanf("%d",&n))
{
    …………;
}

原理是因為scanf讀取失敗時會返回一個EOF,而EOF的值是(-1),(-1)的補碼全都是1,所以~(-1)的補碼就全為0,表示0,而0又表示假,所以上述代碼可以使循環停止。

6.關系操作符

>
>=
<
<=
==
!=

非常直觀,沒有什么難以理解的

需要注意的是:==不能用來判斷兩個字符串是否相等,"abcd"=="efg"比較的是這兩個字符串的首字符的地址,比較字符串大小有一個專門的函數---strcmp。strcmp比較的是兩個字符串對應位置上字符的大小,而不是長度,在比較時,如果字符串對應位置的字符相等則比較下一位,直到對應位置的字符不相等,則較大字符所在的字符串就是大的字符串。

7.邏輯操作符

&&   邏輯與  (見0為0)
||   邏輯或  (見1為1)

我們看下面的代碼

int i=0;a=0;b=2;c=3;d=4;
i=a++ && ++b && d++;
printf("%d %d %d %d\n",a,b,c,d);//輸出為1 2 3 4

上面的代碼為什么輸出是1 2 3 4而不是1 3 3 5呢?? 因為對&&來說,如果左邊的操作數為0,那么它的結果肯定是0,所以右邊的值就不會計算了,對 || 也是一樣,如果左邊的操作數為1,那么值肯定為1,右邊的操作數也不會計算了。

8.條件操作符

exp1?exp2:exp3

exp1為真,執行exp2,結果為exp2的結果

exp1為假,執行exp3,結果為exp3的結果

9.逗號表達式

exp1,exp2,exp3……,expN;

從左到右依次執行,整個表達式的結果為最后一個表達式的結果(前面的每個表達式都是要執行的)

10.下標引用操作符與函數調用操作符

[]    下標引用
()    函數調用

下標引用操作符就是我們在調用數組中的元素時用的[ ]。

arr[4] <==> *(arr+4) <==> *(4+arr) <==> 4[arr]

上面的代碼更加說明了【】是一個操作符,數組名和下標是它的兩個操作數。

函數調用操作符()的操作數為函數名和參數。

11.結構體成員操作符

.    結構體.成員名
->   結構體指針->成員名

我們來通過下面的代碼對這兩個操作符進行一個區分

struct Stu
{
    char name[20];
    int age;
    float score;
};
void print1(struct Stu ss)
{
    printf("%s %d %f\n",ss.name,ss.age,ss.score);
}
void print2(struct Stu* ps)
{
    printf("%s %d %f\n",(*ps).name,(*ps).age,(*ps).score);
    printf("%s %d %f\n",ps->name,ps->age,ps->score);
int main()
{
    struct Stu s={"李四",20,95.5};
    print1(s);
    print2(&s);
    return 0;
}

?上面的代碼會打印出三組數據,他們都是一樣的,說明我們傳的是結構體時,要用 結構體.成員名來訪問,當我們傳的是結構體指針時,可以用 結構體指針->成員名 來訪問,也可以解引用指針然后用(*結構體指針).成員名 來訪問

12.表達式求值

隱式類型轉換

C的整型算術運算總是至少以缺省整型類型的精度來進行的。 為了獲得這個精度,表達式中的字符和短整型操作數在使用之前被轉換為普通整型,這種轉換稱為整型提升。

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

?簡單來說就是CPU中最小的計算單位是一個整形的大小,所以當小于一個整形大小的變量要進行運算時,計算機要先把它變成整型進行運算,然后再變回去。

那么計算機是如何進行整形提升的呢?我們看下面的代碼

int main()
{
    char c1=3;
    char c2=127;
    char c3=c1+c2;
    printf("%d\n",c3);
    return 0;
}

c1,c2都是字符型,而3,127都是整型,char中存放不下,所以會發生截斷,下面我們來分析一下計算機的計算過程

char c1=3;
//00000000 00000000 00000000 00000011--3的補碼
//00000011--c1
char c2=127;
//00000000 00000000 00000000 01111111--127的補碼
//01111111--c2
//那么我們如何進行整型提升呢?
//負數:高位補1
//正數:高位補0
//無符號位:高位補0
char c3=c1+c2;
//00000000 00000000 00000000 10000010--130的補碼
//10000010--c3
//符號位為1,表示負數,所以高位補1
//11111111 11111111 11111111 10000010--c3整型提升后的補碼
//11111111 11111111 11111111 10000001--c3整形提升后的反碼
//10000000 00000000 00000000 01111110--c3整形提升后的原碼--(-126)
printf(%d\n",c3);//值為-126

上面我們就分析清楚了計算機到底是怎么進行整型提升的。

char c=1;
printf("%u\n",sizeof(c));  //1
printf("%u\n",sizeof(+c)); //4
printf("%u\n",sizeof(-c)); //4

上面這個代碼的輸出結果也說明了當c要進入表達式進行計算時,就會進行整型提升。

算數轉換

當操作符對大于等于整型的不同類型的數進行操作時,會把其中一個數的類型轉換成另一個數的類型,否則就無法進行操作,下面的層次體系稱為尋常算術轉換。

long double
double
float
ubsigned long int
long int
ubsigned int
int

上面的類型的轉換優先級是從上到下排列的,即當一個long double類型與int類型的數據運算時,會把int轉換成long double類型。

13.操作符的屬性

(1)操作符的優先級

(2)操作符的結合性

(3)是否控制求值序列

?操作符的優先級有一個明確的表格,大家有興趣可以去查找一下

只有相鄰的兩個操作符猜討論優先級,當相鄰的兩個操作符優先級相同時,結合性起作用

結合性就像下面的代碼:

a+b+c

兩個操作符都是+,優先級相同,那么就通過他們的結合性判斷先后順序,+操作符是從左到右結合的,所以該式的計算順序是a+b,再加c。

大多數操作符都是不控制求值順序的

只有以下四個

邏輯與

邏輯或

條件操作符

逗號表達式

?就像上面所說,以邏輯與為例,當邏輯與的左邊的操作數為0時,表達式必為0,所以不計算右邊的表達式,這就是控制求值順序。

下面有幾個典型的錯誤表達式

a*b+c*d+e*f

這個表達式是有多種執行順序的,如果abcdef都是變量,那么無論哪種路徑都不會影響最終的值,但是當abcdef都是表達式的時候,并且如果他們當中還使用了一下相同的變量時,這時候不同的計算路徑就很可能產生不同的結果,所以這是一個錯誤的表達式。

c=++c

這個代碼的問題是我們不知道左邊的c是在自加之前準備好還是自加之后,所以也有問題。

int i=10;
i=i-- - --i*(i=-3)*i++ + ++i;
printf("i=%d\n",i);

這個代碼在不同的編譯器里有不同的結果,所以也是存在問題。

int fun()
{
    static int count=1;
    return ++count;
}
int main()
{
    int answer;
    answer=fun()-fun()*fun();
    printf("%d\n",answer);
}

上述代碼的問題在于每次調用fun函數時返回的值都不同,但是我們無法確定到底應該先調用那個fun函數。

綜上所述,我們在寫表達式的時候一定要注意,要根據操作符的屬性,寫出能夠確定唯一執行路徑的表達式,否則就是錯誤的表達式。

原文鏈接:https://blog.csdn.net/qq_45967533/article/details/122801791

欄目分類
最近更新