講解運算子
一、概念
1.1 含義
運算子用於執行程式程式碼運算,會針對一個及以上運算元來進行運算。
1.2 特點
-
優先順序和結合性:先考慮優先順序,再考慮結合性。同一優先順序的運算子結合性相同(用於消除歧義)。
-
一般而言:單目 > 雙目 > 三目(例外 ?: > 賦值運算子);算術 > 關係 > 邏輯 > 賦值。當我們提到幾目運算子,實際上談論的是運算元的個數。
-
優先順序被劃分為 15 個等級。
1.3 典型例題
優先順序1 初等運算子
例一:若有一下說明和語句:能正確引用陣列arr元素的選項是( D )
int arr[4][5]; //二維陣列。
int (*ptr)[5]; //指向陣列的指標。
ptr = arr;
-
A ptr+1 //代表 &arr[1]
-
B *(ptr+3) //代表 arr[3] 陣列名也是一個地址。
-
C *(ptr+1)+3 //代表 &arr[1][3]
-
D *(ptr[0]+2) //代表 *(&arr[0][2])
在C語言中,連續記憶體的表示形式是“起始地址+偏移量”。現有一陣列int arr[5] = {1, 2, 3, 4, 5};對一般的內建型別或者指標型別進行 & 是取得變數的首地址,但是如果對集合型別 陣列名取地址呢?&arr 型別是 int(*)[5],是不能直接賦值給 int **的。表面理解——型別不同,主要體現在+1的能力上。先展示我在VS2017下的執行結果吧:
008F F6E0 //arr首地址,列印輸出作為基準。
008F F6F4 //選項A
008F F71C //選項B
008F F700 //選項C
1973 //選項D 我把陣列元素全部填充成1973。
for(int i = 0; i < 20; ++i)
arr[i / 5][i % 5] = 1973;
1)記憶體就是記憶體,取決於編譯器如何看待;2)型別型別還是型別!地址是冰冷的一串數字,需要我們去揣摩如何解釋它。3)指標解引用之後就是取得所指之物的內容,多少級指標都是這樣的。
選項A ptr是指向陣列的指標,解引用之後獲得整個陣列!ptr+1的值會和陣列首地址相差20byte,14H,正好是20byte。
選項B ptr+3指向的是arr[3],解引用之後就獲得了arr[3],會和陣列首地址相差60byte,3CH,正好是60byte。
選項C ptr+1指向arr[1],解引用之後獲得了arr[1],陣列名+3,實際上得到的是&arr[1][3],會和陣列首地址相差32byte,
20H,正好是32byte。
選項D 出現了 [] 運算子,它等同於 *(基地址 + 偏移量)。ptr[0]效果等同於arr[0],ptr[0]+2等同於&arr[0][2],再進行解引用就取出了元素值。 ------出自(《組合語言第3版王爽》P23 )基地址+偏離量是一對很重要的概念。8086CPU採用段基址×16+偏移地址等於實體地址的手段,而且這種處理方式一直被延續下來。
例二 如下哪一段程式碼不能給地址 0xaae0275c 賦值為 1 ? ( )
A volatile int *p = (int *)0xaae0275c; *p = 1;
B (volatile int *)0xaae0275c[0] = 1;
C volatile int *p = (int *)0xaae0275c; p[0] = 1;
D *(volatile int *)0xaae0275c = 1;
B 選項, [ ] 優先順序最高,結合之後就會導致問題。應修改成 ((volatile int *)0xaae0275c)[0] = 1;
例三
struct str_t
{
long long m_len;
char m_data[32]; //字元陣列型別。
};
struct data1_t
{
long long m_len;
int m_data[2]; //整型陣列型別。
};
struct data2_t
{
long long m_len;
char *m_data[1]; //指標陣列型別。
};
struct data3_t
{
long long m_len;
void *m_data[]; //二級指標。 實際上會取用4位元組。
};
int main()
{
struct str_t str;
memset(&str, sizeof(struct str_t);
str.m_len = sizeof(struct str_t) - sizeof(long long);
sprtinf(str.m_data, "%s", "BruceeLee");
_____________________________________;
return 0;
}
問:下列程式碼不能正確輸出 BurceeLee(世界著名武術家)選項是( B )
-
A struct data3_t *pData = (struct data3_t*)&str; printf("%s\n", (char*)(&(pData->m_data[0]));
-
B struct data2_t *pData = (struct data2_t*)&str; printf("%s\n", (char*)(pData->m_data[0]));
-
C struct data1_t *pData = (struct data1_t*)&str; printf("%s\n", (char*)(pData->m_data));
-
D struct str_t *pData = (struct str_t*)&str; printf("%s\n", (char*)(pData->m_data));
解答:明確思路,只要我能拿到"BruceeLee"的首地址,不管是什麼型別的地址,進行強轉之後就能正確輸出。->和[]優先順序相同,結合性是從左至右。 四個結構體成員,只是m_data型別不同。
選項A m_data是指標陣列,pData->m_data實際上就是獲得指標陣列,緊接著pData->m_data[0]就訪問資料首元素,對陣列首元素取地址,地址是和字串"BruceeLee"起始地址重合的,進行強轉之後就能正確輸出。
選項B m_data是指標陣列,pData->m_data獲得該陣列,pData->m_data[0]就訪問到陣列首元素,實際上就是將'B' 'r' 'u' 'c'四個位元組的資料拼接成一個char*地址,從而形成野指標!
選項C m_data是一個整型陣列,pData->m_data實際上就是獲得了該陣列,將陣列名強轉成char*,實際上和字串起始地址是重合的,也能正確列印輸出。
選項D m_data是一個字元陣列,原理和選項C相同。
例四
宣告一個指向含有10個陣列元素的指標,其中每個元素是一個函式指標,該函式的返回值為int,引數是int*。下面選項正確的是( )
-
A(int *p[10])(int*)
-
B int [10]*p(int *)
-
C int (*(*p)[10])(int *)
-
D int ((int *)[10])*p
-
E 以上選項都不正確
宣告一個變數從識別符號入手,題中要求是指標型別的變數,則有(*)p;而後該變數指向一個陣列,則有(*p)[10];而後需要描述陣列元素型別——指標型別,則有(*(*p)[10]);最後需要描述具體是什麼型別的指標,int(*(*p)[10])(int*)。
優先順序2 ++ ~ *
例一
int i = 4;
int j = i++ +1; //執行貪心策略,吞下儘可能多的+。
解答:j = 5;因為後置自增是單目運算子,+是雙目運算子。先返回4再加1得到5然後i自增成5。
例二
*p++的效果是?
自增運算子和指標運算子同優先順序,右結合性(優先順序相同結合性一定得相同),等同於*(p++)。
例三
執行下列語句後的結果為()
int x = 3, y;
int *px = &x;
y = *px++;
優先順序相同,才考慮結合性,右結合性,等同於 y = *(px++)。先將解引用的值3賦給y,然後p前進一步(此時是沒有問題的),除非嘗試通過這個指標訪問指向的地址空間。如果題目改為 y = *++px;結果就是不可預知的。
例四
短路求值。
int main()
{
int i = 1, j = 1, k = 2;
if( (j++ || k++) && i++)
cout << i << " " << j << " " << k << endl;
return 0;
}
能想到這一點,就知道結果是 2 2 2 。
例五
下面程式的執行結果是( )
int main()
{
int a = 1, b = 10;
do{
b -= a;
a++;
}while(b-- < 0)
{
printf("a = %d, b = %d\n", a , b);
}
return 0;
}
do..while,do語句一定會執行1次。所以結果為 a = 2, b = 8。
例六
int x = 1;
int y = ~x;
問 y 的值是多少?
由補碼的性質可知:y = -2。
例七
int main()
{
char str[] = "ABCD";
char *p = str;
printf("%d\n", *(p+4));
return 0;
}
p指向陣列起始位置,p+4指向陣列結束標誌'\0',%d列印輸出就是0。此處容易犯的一個問題就是會誤以為本程式有編譯錯誤。
例八
下列程式的輸出結果是( )
int main()
{
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, *p = a + 3;
printf("%d\n", *++p);
return 0;
}
-
A 3
-
B 4
-
C a[4]的地址
-
D 非法
解答:“基地址+偏移量”模式似乎為C語言學習者所熱愛。
例九 函式pa和函式pb哪個執行更快?
#define NUMA 10000000
#define NUMB 1000
int a[NUMA], b[NUMB]
void pa()
{
int i, j;
for(i = 0; i < NUMB; ++i)
{
for(j = 0; j < NUMA; ++j)
++a[j];
}
}
void pb()
{
int i, j;
for(i = 0; i <NUMA; ++i)
{
for(j = 0; j < NUMB; ++j)
{
++b[j];
}
}
}
這是阿里巴巴的一道筆試題,乍一看之下,很無厘頭啊,但是細想,就能感受到出題者的心思縝密——主要考察程式的“區域性性原理”。 因為區域性性原理,所以記憶體利用效率更高,同時帶來另一個問題——缺頁(命中率)。因為陣列a比陣列b大很多,所以可能跨越更多的頁,缺頁率更高或者命中率更低。所以 函式pb 比 函式pa 快。
優先順序3 * / %
例一
若有定義: int a = 7; float x = 2.5, y = 4.7;則表示式 x + a % 3 * (int)(x + y) % 2 / 4的值是多少?
解答:乘、除、取餘三者優先順序相同,都是左結合性。從左到右耐心計算可以得到值為2.5。
例二
#include<iostream>
using namespace std;
int main()
{
int a = 2;
int b = ++a;
cout<< a / 6 << endl;
return 0;
}
解答: / 兩邊都是整型,所以運算之後的結果還是整型,故而輸出0。
優先順序4
優先順序5 移位運算子
例一 以下程式碼執行後,val的值是___?
unsigned long val = 0;
char a = 0x48;
char b = 0x52;
val = b << 8 | a;
表示式的值和變數的值,這是兩碼事。val = 0x5200 | 0x48 = 21064。
優先順序6
優先順序7
優先順序8 &
例一
返回偶數。
int fun(int x)
{
if(x % 2) //x是奇數。
{
return x - 1;
}
else
return x;
}
用表示式 x & -2;代替,以下說法不正確的是( C)
-
A 計算機的補碼錶示使得兩段程式碼等價
-
B 用第二段程式碼執行起來會更快一些
-
C 這段程式碼只適用於x為正數的情況
-
D 第一段程式碼更適合閱讀
不管輸入什麼,只需要把最低位抹成0,就實現了我們的目的,這就是位運算的魅力之所在。
優先順序9
優先順序10
優先順序11
優先順序12
優先順序13
優先順序14
優先順序15
二、擴充套件內容
2.1 運算子過載
三、參考文獻
【1】友元函式過載
相關文章
- 運算子 運算子
- php運算子 比較運算子 邏輯運算子 三元運算子PHP
- 運算子-賦值運算子賦值
- MySQL的四種運算子(算術運算子、比較運算子、邏輯運算子和位運算子)MySql
- 8.Golang中的運算子-算術運算子、關係運算子、邏輯運算子、賦值運算子Golang賦值
- c語言運算子詳解C語言
- 運算子的關係,什麼叫一元運算子,二元運算子,三元運算子,運算子優先順序,以及運算子的
- 運算子
- 算術運算子裡的特殊運算子
- javascript中&&運算子和||運算子的使用JavaScript
- Python 運算子優先順序 運算子Python
- Python學習-算術運算子,賦值運算子和複合運算子Python賦值
- 過載運算子、解構函式函式
- Java運算子>>與>>>區別詳解Java
- 你真的瞭解js運算子嗎JS
- js中的|與 && 運算子詳解JS
- C++運算子過載詳解C++
- java零基礎自學第一天②,運算子:表示式,算術運算子,+操作,賦值運算子,自增自減運算子,關係運算子,邏輯運算子,三元運算子Java賦值
- php運算子運用之型別運算子該如何使用PHP型別
- Kotlin 運算子詳解:算術、賦值、比較與邏輯運算子全解析Kotlin賦值
- C++ 迭代器運算子 箭頭運算子->C++
- 位運算子
- SHELL運算子
- mysql運算子MySql
- 身份運算子
- JavaScript運算子JavaScript
- oracle運算子Oracle
- JavaScript -= 運算子JavaScript
- JavaScript += 運算子JavaScript
- JavaScript *= 運算子JavaScript
- JavaScript %= 運算子JavaScript
- JavaScript |= 運算子JavaScript
- JavaScript <<= 運算子JavaScript
- JavaScript >>>= 運算子JavaScript
- JavaScript >>= 運算子JavaScript
- JavaScript &= 運算子JavaScript
- JavaScript ^= 運算子JavaScript
- JavaScript /= 運算子JavaScript