C++ Primer學習 《操作符與類型轉換》

操作符與類型轉換


運算符重載

當我們重載運算符時,操作數(operand)的類型和最終結果會被改變。但是!操作數的數量和優先級和組合順序(precedence and associativity)不會改變!


Lvalues和Rvalues(左值和右值)

C++中,lvalue和rvalue的概念是比較麻煩的。lvalue表達式,產生一個對象或函數(object or function);然而,一些lvalue,比如說const類型,不能在賦值語句(assignment)的左邊。另外,一些表達式也產生object,但是返回的是rvalue,而非lvalue。大體上,當我們把一個object用作rvalue時,我們使用的是該對象的值(its value,its content);而當我們把object當做lvalue時,我們使用對象本身(its identity,its location in memory)

另外重要的是:當需要rvalue時,我們可以用lvalue代替;但是,需要lvalue時,不能使用rvalue代替。

lvalue和rvalue很難完全弄明白的,可以根據下面對每個操作符的解讀,來深刻體會。


表達式求值的順序

一個表達式,例如:f()+g(),其實我們並不知道計算機是先計算f(),還是先計算g().對操作系統有一定了解的同學就會立刻意識到,f和g這兩個函數,不應該對同一個變量改變值,因爲可能會發生意想不到的結果。

下面這個例子:

int i=0;
cout<<i<<" "<<++i<<endl;
由於輸出操作符不確定i," ",++i這三個內容中會先執行哪一個;即計算機會先把這三個內容分別計算出來,然後再順序輸出,但是這三個內容哪個先計算哪個後計算是不確定的!因此可能輸出:0 1,也可能輸出:1 1,我們不得而知。

C++中,只有四個操作符,保證了運算的先後性:"&&","||","?:",和","。其他的操作符都不能保證順序,因此,我們不應該在那些操作符中改變同一個object。


算數操作符 Arithmetic Operator

算術操作符的結果都是rvalue。

在計算時,小的整形會被自動轉換成大的整形計算(如short轉成int),小的浮點數也被轉成大的浮點數(float轉成double)。

unary plus(單加符),用來返回原數的一個copy;unary minus(單減符),用來返回原數的負數。

int i =1024;
int k = -i;//k=-1024
bool b=true;
bool b2 = -b;//b2 = true!

取商和取模

在以前的C++版本中,對於負商,編譯器將其四捨五入;在C++ 11中,商永遠被向零化整(round toward zero,i.e. truncated)。

對於去模,如果m%n非零,那麼就和m的負號相同。

重要:除非-m溢出(這是你應該避免的),否則有:(-m)/n,m/(-n)總和-(m/n)結果相同;m%(-n)和m%n結果相同;(-m)%n和-(m%n)結果相同!

21/6;//3(3.5-->3)
-21/-8;//2(2.625-->2)
21/-5;//-4(-4.2-->-4)
-21%-8;//-5
21%-5;//1
終於不用再糾結取商和取模的結果了!!!贊!!!


邏輯和關係操作符(Logical and Relational Operators)

這些操作符的操作數都是rvalue,他們的結果也是rvalue。

指針也可以用來做與、或之類的操作。

int *p1 = new int, *p2 = new int;
if(p1 && p2)//使用指針的地址來判斷!永遠爲true
....
if(*p1 && *p2)//使用指針的值來判斷!
...


賦值操作符(Assignment Operator)

賦值操作符的左邊的操作數,必須是一個可修改的左值(modifiable lvalue)

Assignment經常被直接用在判斷條件中:

int i=get();
while(i != 42){
   i=get();
   ...
}
更好的方案:

int i;
while( (i=get()) != 42){
   ...
}


加加和減減操作符(increment and decrement operators)

這些操作符的操作數必須是lvalue。

前操作符(即加加和減減在前面,prefix operators)返回object本身,是一個lvalue。後操作符(postfix operators)返回object值的一個copy,是一個rvalue。

int i = 0;
++i = 1;//ok
i++ = 1;//error!
建議:只在必須時使用後操作符(postfix operators)。這是因爲,顯而易見,前操作符的效率更高。
重要建議:賦值語句的兩邊如果出現同一個object,不要對這個object使用加加和減減操作符!

while(beg != s.end() && !isspace(*beg))
    *beg = toupper(*beg++);//error! assignment is undefined!
上面例子中,等號兩邊的內容,需要分別被計算出來,然後再執行賦值操作,因此左邊和右邊哪個先執行哪個後執行,會影響程序的結果!


條件判斷操作符(Conditional Operator)

條件判斷操作符的結果,如果兩個表達式都是lvalue,或轉換成共同的lvalue type,那麼是lvalue;否則,是rvalue。

int *p1,*p2;
( (1>0)?*p1:*p2 ) = 100;//*p1的值爲100.注意這裏的外圈括號不能少!
int *p3;
( (1>0)? p1: p2 ) = p3;//p1指向p3
嵌套的條件判斷操作符

finalgrade = (grade > 90) ? "high pass"
                          :(grade < 60> ? "fail" : "pass";
條件判斷操作符是右結合的(right associative)!即從右往左,依次計算內容。


位操作符(Bitwise Operator)

位操作符只對整形(integral type)進行操作。

如果integral是signed並且是負數,那麼位操作的結果是不確定的,因此,儘量使用unsigned或確保integral是正數!


變量大小操作符(Sizeof Operator)

sizeof對一個表達式,或一個類型名,返回該表達式結果,或該類型的大小,以佔用了幾個字節(bytes)來表示。

變量大小操作符是右結合的,其返回值是一個constant expression of type size_t。

有兩種用法:

sizeof (type)
sizeof expression
幾個例子:

Sales_data data,*p;
sizeof(Sales_data);
sizeof data;//和上一個等價
sizeof p;//size of a pointer
sizeof *p;//和第一個,第二個等價
sizeof data.revenue;
sizeof Sales_data::revenue;//和上一個等價
一些值得注意的內容:

sizeof 一個reference type,返回該reference指向的object的size大小

sizeof一個pointer,返回一個pointer自身的大小

sizeof一個dereferenced pointer,返回pointer指向的object的大小,而pointer need not be valid!!

sizeof一個array,返回整個array的大小!sizeof不會把array轉換成pointer。

sizeof一個string或vector只返回這個類型本身的大小(即相當於sizeof一個class),其結果是固定的,和string或vector包含的元素無關。sizeof string = 28,sizeof vector = 16。


逗號操作符(Comma Operator)

逗號操作符從左向右,依次計算內容的值。

逗號操作符左邊的表達式在計算完後被丟棄,其返回的是右邊表達式的計算結果。如果右邊表達式是lvalue,那麼返回lvalue;否則返回rvalue。

逗號操作符可以有任意多個項,除了最右邊的是右邊表示,其他都是左邊表達式。


類型轉換(Type Conversion)

默認轉換最常出現的情況(Implicit Conversion)

1.比int小的整形會被轉換成較大的整形。

2.條件判斷中,非bool類型會被轉換成bool。

3.初始化時,用來初始化的變量會被轉換成接收變量的類型;賦值時,右邊表達式被轉換成左邊表達式的類型。

4.算數和關係運算,所有變量被轉換成統一類型。

5.函數調用時。

默認轉換中的算數轉換

宗旨是將操作數轉換成widest type。比如有一個操作數是long double,那麼所有其他的操作數都將被轉換成long double。如果有浮點數和整形同時出現,那麼整形會被轉換成適當的浮點數。

默認轉換中的unsigned type

其實,轉換的規則很複雜,但是大多無關緊要。最最重要的就是:unsigned和signed混合使用時的轉換,需要深刻記住!

如果一個unsigned和signed相遇,那麼:

1.unsigned的size比signed的size大或相等(the same as or larger than),那麼signed就被轉換成unsigned!比如unsigned int和int,那麼int就被轉成unsigned int。

2.unsigned的size比signed的size小。那麼結果是不定的。這時候:

2.1unsigned的所有可能的值都包含在signed中,那麼unsigned被轉成signed,例如long和unsigned int,如果long的bit數更多,那麼unsigned int轉成long

2.2不滿足2.1條件,那麼signed被轉成unsigned,例如long和unsigned int,如果long和unsigned int的bit數一樣多,那麼long轉成unsigned int

總的來說,就是保證unsigned的精度!

默認轉換中的array to pointer

array一般會被轉換成pointer。除了:decltype,address of(&),sizeof,typeid。

強制類型轉換(Explicit Conversions)

我們使用cast語句來顯式轉換兩個類型。注意:儘量減少類型轉換的出現。

強制類型轉換共有四種用法:static_cast,dynamic_cast,const_cast,reinterpret_cast.

static_cast

不涉及low-level const的被編譯器預先定義好的類型轉換,都可以使用static_cast完成。

int j = 0;
double p = static_cast<double>(j);

並不是說static_cast的操作符絕對不能是const

const char* p = "aaa";
string s = static_cast<string>(p);//ok!
甚至可以有:

const char* p = "aaa";
const string s = static_cast<const string>(p);//ok!top-level const

char *pc = "aaa";
	const char* ss = static_cast<const char*>(pc);//ok,low-level const

意思是說,static_cast可以添加const,但是它不能去掉const。
const_cast

只改變操作數的low-level const,且只改變const性質。即只能添加或去除const,但不能改變變量類型。另外,const_cast的操作數和轉換類型必須是pointer或reference(這樣纔能有low-level const)。

const_cast的主要能力是去掉const,如:

const char* s = "aaa";
char* ss = const_cast<char*>(s);
但是如果你試圖改變ss,那麼結果將是危險的!!

const_cast在函數重載時非常有用。

reinterpret_cast

這個類型轉換的作用是:重新解釋變量的比特類型(bit pattern),但不會改變其二進制的內容。

它能把int*轉成double*,甚至能把double*轉成int,但是這些變量的值並未改變。

reinterpret_cast需要在深刻理解編譯器執行類型轉換的實現後,才建議使用!我完全沒弄懂這貨有啥用!?

dynamic_cast

將在以後介紹。

老版本的強制類型轉換

老版本C++中,有兩種強制類型轉換方法:

type (expr);
(type) expr;
建議:

儘量不要使用強制類型轉換!

const_cast在函數重載時會有用。static_cast和dynamic_cast應該儘量少使用。而reinterpret_cast則非常危險!

如果無法避免,應該儘量限制這個類型轉換的作用域(limiting the scope in which the cast value is used),並且標註可能的轉換類型(documenting all assumptions about the types involved)。


操作符優先表(Operator Precedence Table)

這張表指明瞭每個操作符的組合順序,和優先級,非常實用!



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章