目錄
一、算數操作符
?二、移位操作符
1.左移操作符
2.右移操作符
(1) 邏輯右移
(2) 算術右移
?(3)小總結
三、位操作符
四、賦值操作符
五、單目操作符
六、關系操作符
七、邏輯操作符
八、 條件操作符
九、逗號表達式
十、下標引用、函數調用和結構成員
1. [?]下標引用操作符
?2.? ( )函數調用操作符
3. 訪問一個結構的成員
十一、表達式求值
1.隱式類型轉換
2.算術轉換?
3.操作符的屬性
十二、操作符優先級
一、算數操作符
+? ? -? ? *? ?/? ?%
- 除了 % 操作符之外,其他的幾個操作符可以作用于整數和浮點數
- 對于 / 操作符如果兩個操作數都為整數,執行整數除法。但是,只要有浮點數參與運算就是浮點數除法
- % 要求兩個操作數必須為整數,得到的是余數
?二、移位操作符
>>????????右移操作符
<<? ? ? ? 左移操作符
注意:移位操作符的操作數只能是整數
1.左移操作符
規則巧記:左邊拋棄,右邊補0
這里假設存的是 int n =15;的二進制 進行左移 n<<1;?如果n?在沒有被左移賦值的情況下,n自身的值是不會發生變化。(這里與自增自減不一樣)
在深層次看一下,一個數 n<<1,? 發現相當于該數 n * 2^1。n << 2;? ?等于 n?*?2^2?
2.右移操作符
(1) 邏輯右移
移位規則:右邊舍棄,左邊補0
假設內存 存放的是 -1 補碼的二進制? ?
(2) 算術右移
移位規則:右邊舍棄,左邊用原值的符號位進行填充
?
?(3)小總結
如果 算術右移與 邏輯右移 總是分不清。巧記:(算術右移,可以想象成 算數,既然算數肯定會有正負,進而想到 左邊填充的是原符號位)
注意:對于位操作符,不存在移動負數位,C語言標準并未規定
int num = 10;
num>> -1;? //error(不要多次一舉)
num? << 1;? // ok? 右移? -1? 這不相當于 左移 1 嘛。
三、位操作符
&? ? ? ? //按位與? 巧記:有0則0,其中? 一個數 a&1 可求 該數的每一個二進制位
|? ? ? ? ?//按位或? ?巧記:有1則1
^? ? ? ? //按位異或 巧記:相同為0,相異為1
注:操作數必須是整數
【例】1
#include<stdio.h>int main() {int a = 1;//0001int b = 2;//0010/** a& b;* 0001* 0010* 0000* * a|b* 0001* 0010* 0011* * a^b* 0001* 0010* 1100*/return 0; }
接下來看一道面試題
【例】2?不能創建臨時變量(第三變量),實現兩個數的交換
解法一:
兩個數進行來回加減來進行兩個數的交換,通過調試的監視的窗口我們可以看到兩個數的交換?
?解法二:
#include<stdio.h>
int main()
{int a = 10;int b = 20;a = a ^ b;b = a ^ b;a = a ^ b;printf("%d %d",a,b);return 0;
}
從打印結果可以看到a 與 b的值進行了交換
【解析】^?按位異或 相同為0,相異為1(可以理解為 相同假,相異為真)
前面,提到 可以用a&1 該數的每一個二進制位,看下方例題
【例】編寫代碼,求一個整數存儲在內存中的二進制中1的個數
#include<stdio.h>
int main()
{int n = 10;int count = 0;// 0000 1010// 0000 0001int i = 0;for (i = 0; i < 32;i++){if (n & 1 == 1){count++;} n = n>>1;}printf("%d",count);return 0;
}
?這里用到了 >> 和 ^
解法二:
#include<stdio.h>
int main()
{int n = 10;int count = 0;while (n) {if (n%2 == 1){count++;}n /= 2;}return 0;
}
這里的思路是 因為計算機存儲是二進制 0 和1 ,%2取余判斷是否為1,/2進行下一位?
?解法三:
#include<stdio.h>
int main()
{int n = -1;int i = 0;int count = 0;while (n){count++;n = n & (n - 1);//1111 1111 1111 1111 1111 1111 1111 1111//1111 1111 1111 1111 1111 1111 1111 1110//1111 1111 1111 1111 1111 1111 1111 1110}printf("%d ", count);return 0;
}
這里的優化 是借助 兩個數差1 進行按位與,一個一個位找1
四、賦值操作符
?=? 這是賦值符,不是等于!!!
賦值操作符? 給一個變量進行賦值 注意賦值操作符的優先級比較低(包括復合賦值符)
int age = 18;age = 20; //對變量進行賦值
賦值操作符支持連續使用;
int a = 0;
int b = 10;
int c = 1;
a = b = c+1; //這里是連續賦值
雖然可以連續賦值,但是代碼的可讀會下降
b = c+1;
a = b;
//這樣子是不是看著更用以理解
代碼的可讀性也是很重要的
接下來看復合賦值符都有哪些
+= 、-=、*= 、/= 、%= 、>>=? 、<<= 、&= 、 |= 、^=
int a = 10;
a = a+10;
//可以寫成這樣
a += 10;
其他復合賦值符同上方用法一致
五、單目操作符
單目操作符就是操作數只有一個
?sizeof ()括號里面不是類型時可以省略(),說到sizeof ,其中 size_t 是一種類型,是一種無符號整型的,size_t 就是為sizeof專門設計的一種類型,打印時使用%zd,但是size_t 在不同平臺下可能是 unsigned int? ?也有可能是? unsigned long long int?
sizeof(數組名) ,計算的是整個數組的大小
++前置,先對該數+1,然后再去使用這個數
++后置,相當于 先使用該數,然后在對該數進行 +1
-- 與上方的++同理
六、關系操作符
> 、?>= 、 < 、 <=? 、!= 、==
注意:== 這個是 等于
? ? ? ? ? ? =? ?這個是 賦值
七、邏輯操作符
&&? ? ? ? 邏輯與
||? ? ? ? ? ?邏輯或
要區分
& 與 &&? ?;? ?| 與 ||
1&2? ?--->? 0??
1&&2? --->? 1? ?(左邊為假,右邊無需計算,直接為假)
1? |? 2 --->? 3
1? ||? 2 ---> 1?(左邊為真,右邊無需計算,直接為真)
【例】1筆試題,求結果輸出的值
#include<stdio.h>
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\nd = %d\n", a, b, c, d);return 0;
}
? 【結果】a = 1 b = 2 c = 3 d = 4
解析? ??首先 a = 0 ,a++ ,是先使用后 +1,又有&&?(左邊為假,右邊無需計算,直接為假)
打印的是 a? = 1,其他值正常打印
【例】2?對這個題進行改編 a = 1
#include<stdio.h>
int main()
{int i = 0, a = 1, b = 2, c = 3, d = 4;i = a++ && ++b && d++;printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);return 0;
}
【結果】 a = 2 b = 3 c = 3 d = 5
解析? a = 1時,左邊為真,后面表達式繼續進行計算,進而 a = 2, b = 3,c =3 ,d= 5
【例】3?對這個題進行改編 && 改為 ||? , 并求出i 的值
#include<stdio.h>
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\nd = %d\n", a, b, c, d);return 0;
}
【結果】 a = 1 b = 3 c = 3 d = 4? i = 1
解析 因為時邏輯或?(左邊為真,右邊無需計算,直接為真)雖然先使用 a 為假,但接下來的操作數 ++b , b = 3為真 進而后面無需計算。而 i 表達式里面有真 則 i = 1, 為真
八、 條件操作符
表達式1 ?表達式2 :表達式3;
唯一 一個三目操作符
在這里 看一個 求最大值的代碼
?a>b?a:b; //中文解釋 a>b 嗎?大于就是a, 不大于就是b
【例】求三個數的最大值?
#include<stdio.h>
int main()
{int a = 1;int b = 2;int c = 3;int max = 0;max = a > b ? a : b;max = max > c ? max : c;printf("%d",max);return 0;
}
?這里是使用兩次條件操作符進行計算三個數中的最大值
九、逗號表達式
表達式1 , 表達式2 ,... , 表達式n
逗號表達式,從左向右依次執行。整個表達式的結果是最后一個表達式的結果。
//代碼1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗號表達式
c 的結果是 13
逗號表達式可以對代碼進行優化
a = get_val();
count_val(a);
while (a > 0)
{//業務處理a = get_val();count_val(a);
}//使用逗號表達式可以改寫為
while (a = get_val(), count_val(a), a > 0)
{//業務處理
}
十、下標引用、函數調用和結構成員
1. [?]下標引用操作符
注意 操作數為 一個數組名 + 一個索引值
int arr[20]; //創建數組
// [ ] 的操作數 是 arr 和 20
?2.? ( )函數調用操作符
接受一個或者多個操作數:第一個操作數是函數名,剩余的操作數就是傳遞給函數的參數
#include <stdio.h>
void test1()
{printf("hehe\n");
}
void test2(const char* str)
{printf("%s\n", str);
}
int main()
{test1(); ??????//()作為函數調用操作符。test2("hello bit.");//()作為函數調用操作符。return 0;
}
3. 訪問一個結構的成員
.? ? ? ? 結構體.成員名
->? ? ? 結構體指針 -> 成員名
#include <stdio.h>
struct Stu
{char name[10];int age;char sex[5];double score;
};
void set_age1(struct Stu stu)
{stu.age = 18;
}
void set_age2(struct Stu* pStu)
{pStu->age = 18;//結構成員訪問
}
int main()
{struct Stu stu;struct Stu* pStu = &stu;//結構成員訪問stu.age = 20;//結構成員訪問set_age1(stu);pStu->age = 20;//結構成員訪問set_age2(pStu);return 0;
}
十一、表達式求值
表達式求值的順序一部分是由操作符的 優先級 和 結合性 決定。
同樣,有些表達式的操作數在求值的過程中可能需要轉換為其他類型。
1.隱式類型轉換
整型算術運算總是至少以缺省整型類型的精度來進行的。
為了獲得這個精度,表達式中的字符和短整型操作數在使用之前被轉換為普通整型,這種轉換稱為整型提升。
整型提升的意義
表達式的整型運算要在CPU的相應運算器件內執行,CPU內整型運算器(ALU)的操作數的字節長度
一般就是int的字節長度,同時也是CPU的通用寄存器的長度。因此,即使兩個char類型的相加,在CPU執行時實際上也要先轉換為CPU內整型操作數的標準長度。
通用CPU(general-purpose CPU)是難以直接實現兩個(8bit)字節直接相加運算(雖然機器指令中可能有這種字節相加指令)。所以,表達式中各種長度可能小于int長度的整型值,都必須先轉
換為int或unsigned int,然后才能送入CPU去執行運算。
//例
char a,b,c;
a = b + c;
在上述代碼中,b和c的值被提升為普通整型(int),然后再執行加法運算,加法運算完成之后,結果將被截斷,然后再存儲于a中
整型提升
整形提升是按照變量的數據類型的符號位來提升的
char c1 = -1;
變量c1的二進制位(補碼)中只有8個比特位:
1111111
因為 char 為有符號的 char
所以整形提升的時候,高位補充符號位,即為1
提升之后的結果是:
11111111111111111111111111111111
//正數的整形提升
char c2 = 1;
變量c2的二進制位(補碼)中只有8個比特位:
00000001
因為 char 為有符號的 char
所以整形提升的時候,高位補充符號位,即為0
提升之后的結果是:
00000000000000000000000000000001
無符號整形提升,高位補0
【例】1
int main()
{char a = 0xb6;short b = 0xb600;int c = 0xb6000000;if (a == 0xb6)printf("a");if (b == 0xb600)printf("b");if (c == 0xb6000000)printf("c");return 0;
}
【結果】c
實例的a,b要進行整型提升,但是c不需要整形提升a,b整形提升之后,變成了負數,所以表達式 a==0xb6 , b==0xb600 的結果是假,但是c不發生整形提升,則表達式 c==0xb6000000 的結果是真.
【a】1101 0110? 整型提升1111 1111 1111 1111 1111 1111 1101 0110
【b】1101 0110 0000 0000 整型提升1111 1111 1111 1111 1101 0110 0000 0000
【例】2
int main()
{char c = 1;printf("%u\n", sizeof(c));printf("%u\n", sizeof(+c));printf("%u\n", sizeof(-c));return 0;
}
【結果】1 4 4
實例2中的,c只要參與表達式運算,就會發生整型提升,表達式 +c ,就會發生提升,所以 sizeof(+c) 是4個字節。表達式 -c 也會發生整形提升,所以 sizeof(-c) 是4個字節,但是 sizeof(c) ,就是1個字節
2.算術轉換?
如果某個操作符的 各個操作數的類型不一樣,那么除非其中一個操作數的轉換為另一個操作數的類型,否則操作就無法進行。下面 尋常算術轉換
long double
double
unsigned long int?
long int
unsigned int
int
向上轉換??如果某個操作數的類型在上面這個列表中排名較低,那么首先要轉換為另外一個操作數的類型后執行運算。
注意: 算數轉換要合理,否則可能會出現精度丟失
?
float pi = 3.14;???????int num = f;//num = 3 精度丟失
3.操作符的屬性
復雜表達式的求值有三個影響的因素。
- 操作符的優先級
- 操作符的結合性
- 是否控制求值順序
兩個相鄰的操作符先執行哪個?取決于他們的優先級。如果兩者的優先級相同,取決于他們的結合性。
十二、操作符優先級
點擊下方鏈接查看???????
鏈接:操作符優先級
注意:我們寫出的表達式如果不能通過操作符的屬性確定唯一的計算路徑,那這個表達式就是存在問題的。