1. 程式人生 > >菜鳥的C++ 知識盲區(跌倒)到知識黑洞(放棄)---------2.1變數和基本型別

菜鳥的C++ 知識盲區(跌倒)到知識黑洞(放棄)---------2.1變數和基本型別

前言

說來話長,本人是一個不合格的程式設計師,最起碼我覺得我水平很菜。本科就讀於北方一個沒落的211,學的是機械設計製造及其自動化,基本上本科沒有接觸過什麼“高深”的關於程式設計的專案,不過稀裡糊塗計算機二級考過了,但是C語言並沒有學的很好,什麼指標啦只是大概知道。本科階段唯一讓我欣慰的就是踩了狗屎運保研到南京的一所985,然後選擇了機械電子專業,在這裡特別感謝該學校的tmq老師,然後從此讀研期間我便轉向了嵌入式。傳統的

就業不是很好工資待遇不是很高,但是我本科實在太菜。研究生三年我自我感覺還是學了點東西的,最起碼C語言熟悉了,只是熟悉,熟悉,熟悉。然後也試著學習C++,發現C++是一門從入門到放棄的語言。放棄瞭然後在入門,就這樣。白駒過隙,歲月如梭,我現在已經踏上了工作崗位,來到杭州一個公司,做嵌入式開發的相關工作。說實話,我現在很菜比,對,年齡也大了,9x年的,有時候很絕望,因為我是一個隨時被淘汰的程式猿。哎~!………………話說我在學習C++途中,遇到不會的經常csdn,看一些部落格,但是並沒有發現系統的一個深入淺出的對C++講解的(也許有,我未發現)。於是,我準備自己寫一下,一來自己鞏固知識,畢竟腦子不好,記憶力衰退。二來幫助那些和我遇到同樣困惑的學習C++的人。

關於C++學習,我會連載,時間不定,內容基本摘自C++ primer這本書(中文版,我英語菜),主要涵蓋對於我這個菜逼來說的比較混淆的容易忘記的知識點和概念。姑且把我的這系列部落格就叫  菜鳥的C++ 知識盲區到知識黑洞!(注意,本部落格只寫本菜鳥的知識盲區~~~)

言歸正傳~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

                                              2.變數和基本型別

2.1基本內建型別

C++中將內建型別分為算數型別和空型別。

bool b = 42; // b is true
int i = b; // i has value 1
i = 3.14; // i has value 3
double pi = i; // pi has value 3.0
unsigned char c = -1; // assuming 8-bit chars, c has value 255
signed char c2 = 256; // assuming 8-bit chars, the value of c2 is undefined
  1.  第一個布林值是非0,所以b是true的,但是它給int  i 賦值,結果卻是1 。這個是bool與int轉換。
  2. int 給double賦值,結果只保留浮點數之前的小數部分。

當一個算數表示式中既有無符號數又有int的時候,那個int值會轉變成無符號的數字。

unsigned u = 10;
int i = -42;
std::cout << i + i << std::endl; // prints -84
std::cout << u + i << std::endl; // if 32-bit ints, prints 4294967264

上述程式碼第二個表示式中寫的很清楚了,會先把-42轉換成無符號然後再運算,怪不得我司規範中強調不同型別的最好不要在防災一起運算。

20 /* 十進位制*/ 024 /* 八進位制*/ 0x14 /* 十六進位制

預設情況下十進位制字面值常量是帶符號的,八進位制和十六進位制可能帶符號也坑內不帶符號。

指定字面值型別:

L'a' // wide character literal, type is   wchar_t
u8"hi!" // utf-8 string literal   (utf-8 encodes a Unicode character in 8 bits)
42ULL // unsigned integer literal, type is unsigned long long
1E-3F // single-precision floating-point literal, type is float
3.14159L // extended-precision floating-point literal, type is long double

 (原諒我不想畫表格)

上述表格可以指定指定字面值型別。(當使用一個場整型字面值的時候,請使用大寫字母L 小寫l和1太像了

2.2變數

變數的定義的基本單形式:

                                   型別說明符+變數名組成的列表; 記住型別說明符這個詞語

waring:初始化變數和賦值有本質區別。

C++11新標準的一部分,用花括號初始化變數得到了廣泛的應用。

int units_sold = 0;
int units_sold = {0};
int units_sold{0};
int units_sold(0);

但是用於內建型別的變數時,這種初始化形式有個重要的特點:如果我們使用列表初始化,而且初始值存在丟失資訊的風險,比如說值的精度等,這個時候編譯器會報錯的:

long  double ld = 3.1415926536;
int a{ld}, b = {ld}; // error: narrowing conversion required
int c(ld), d = ld; // ok: but value will be truncated

當然,定義變數的時候沒有指定初始值,則變數會被預設初始化。(此時我聯絡到預設建構函式)而預設值由變數的型別以及定義變數的位置決定。

宣告和定義是有本質區別的,變數只能被定義一次,但是可以被宣告很多次。

extern int i; // declares but does not define i
int j; // declares and defines j

C++標誌符必須以字母和下劃線開頭,我第一次就傻乎乎的以數字開頭哎~

接下來講講作用域:巢狀的作用域是指被包含的作用域被稱為內層作用域,包含著別的作用域被稱為外層作用域,注意:允許外層內層作用域重新定義外層作用域已經有的名字。這句話就是說區域性變數和全域性變數可以同名,但是呢區域性變數會覆蓋全域性變數。之前我覺得不可以同名,但是這個我真是盲區了。所以我司規範中強調過這個規範~~~~~~~~~~~~~~~~~~~~~~

2.3複合型別

複合型別很有意思,也是難點。特別是這個C中傳說的指標,有人說過C把指標學會了其他都學會了。在本章中著重介紹兩種複合型別,引用和指標。其中引用是C中沒有的。

複合型別的定義是由一個數據型別和緊隨其後的一個宣告符列表組成,每個宣告符命名了一個變數並指定該變數與基本資料型別的關係。一般情況下宣告符就是變數名,其實還有更復雜的宣告符。

先說引用:

int ival = 1024;
int &refVal = ival; // refVal refers to (is another name for) ival
int &refVal2; // error: a reference must be initialized

第三個為什麼會出錯呢,原因很簡單,就是引用在定義的時候必須初始化。通俗的講,引用就是建立的物件的別名。

指標和引用最大的不同就是指標無須在定義的時候初始化,如果指標不在定義的初始化,它的指向是不確定的。

我覺得上表應該讓像我一樣菜鳥好好看看,我之前一直是把  int* 結合一起寫的,其實是錯誤的,或者說誤導人的~就是因為沒有理解好符合型別的宣告:

int* p; // legal but might be misleading  注意*和int緊緊挨在一起 和p中間有一個空格。合法但是誤人子弟

注意*和int緊緊挨在一起 和p中間有一個空格。合法但是誤人子弟。很多人包括我之前以為基本資料型別是int* ,對是int*!!!!這樣是錯誤的想法!即使這樣定義指標,基本資料型別還是int而非int*,*只是修飾了p而已。接著看下面定義多個變數:

int* p1, p2; // p1 is a pointer to int; p2 is an int

p1是指向int的指標,p2是int。如果把int和* 看成一個整體的話,按照邏輯來講 p1和p2都是指向int的指標了!!!所以說,涉及到指標和引用的宣告,一般有兩種寫法:第一種是修飾符和變數名放在一起,這種著重強調變數具有符合型別

int *p1, *p2; // both p1 and p2 are pointers to int

第二種是把修飾符和變數名放在一起,著重強調本次申明定義了一種符合型別:

int* p1; // p1 is a pointer to int
int* p2; // p2 is a pointer to int

我們的神書是採用第一種寫法,我了不要誤人子弟~我強烈建議是用第一種寫法~~~~~~~~~~~~~

空指標不指向任何物件~

2.4const  限定符

2.4.1const的引用:

const  單詞翻譯過來的意思就是常量的意思。所以在C++ 中我們希望他修飾的變數值是不變的。

注意 const 物件被建立後,其值是不能改變的。更重要的是:const 物件必須初始化~!~~~

const int i = get_size(); // ok: initialized at run time
const int j = 42; // ok: initialized at compile time
const int k; // error: k is uninitialized const

上面第三個就是因為沒有初始化~~。

對常量的引用就是把引用繫結在const物件上,與普通引用不同的是,對常量的引用不能被修改它所繫結的物件~。這句話有點繞口,請看下面的程式程式碼的例子:

const int ci = 1024;
const int &r1 = ci; // ok: both reference and underlying object are co
r1 = 42; // error: r1 is a reference to const
int &r2 = ci; // error: non const reference to a const object

注意上面第四個:試圖用一個非常量的引用指向一個常量物件。假如可以,那麼這個非常量的引用改變值,那豈不是前後自相矛盾嗎?

對const的引用可能引用一個並非const的物件:

int i = 42;
int &r1 = i; // r1 bound to i
const int &r2 = i; // r2 also bound to i; but cannot be used to change i
r1 = 0; // r1 is not const; i is now 0
r2 = 0; // error: r2 is a reference to const

不允許通過r2修改i的值~。 但是,i的值可以通過其他方式修改~~~,好好理解該話。

2.4.2 const和指標:

指向常量的指標不能用於改變其所指的物件的值,要想存放常量物件的地址,只能使用指向常量的指標。這句話有點繞~請看下面的例子:

const double pi = 3.14; // pi is const; its value may not be changed
double *ptr = &pi; // error: ptr is a plain pointer
const double *cptr = &pi; // ok: cptr may point to a double that is const
*cptr = 42; // error: cannot assign to *cptr

注意:和常量引用一樣,指向常量的指標也沒有規定所指的物件必須是個常量。所謂指向常量的指標僅僅要求不能通過該指標改變物件的值。

記住指標是物件!!而引用不是物件,所以初始化的時候指標可以不用賦值,而引用必須賦值。另外,可以允許把指標本身定位常量。指標一旦定位常量,就必須初始化了:

int errNumb = 0;
int *const curErr = &errNumb; // curErr will always point to errNumb
const double pi = 3.14159;
const double *const pip = &pi; // pip is a const pointer to a const

要想弄清楚這些這些宣告的含義最簡單有效的辦法就是從右向左閱讀,離著curRrr最近的符號是const,所以它是一個常量物件,物件的型別由宣告符號其餘部分確定。宣告符是*,所以它是一個常量指標。

2.4.3 頂層const

關於這個頂層只不過是一個概念,有頂層所以肯定有底層。頂層表示指標本省是一個常量,底層表示指標所指的物件是一個常量。所以,頂層const可以表示任意的物件是常量,這一點對任何資料都適用,底層的const則 與指標和引用等符合型別的基本型別部分有關係。如下:

int i = 0;
int *const p1 = &i; // we can't change the value of p1; const is top-level
const int ci = 42; // we cannot change ci; const is top-level
const int *p2 = &ci; // we can change p2; const is low-level
const int *const p3 = p2; // right-most const is top-level, left-most is not
const int &r = ci; // const in reference types is always low-level

當執行物件的拷貝操作常量低頂層const和底層const有明顯區別,其中頂層不會受到什麼影響,底層const會受到一些限制。

注意下面這句話:拷入和拷出的物件必須具備相同的底層const資格,或者兩個物件的資料型別能夠相互轉換。一般來說,非常量可以轉換成常量,反之不行!

int *p = p3; // error: p3 has a low-level const but p doesn't
p2 = p3; // ok: p2 has the same low-level const qualification as p3
p2 = &i; // ok: we can convert int* to const int*
int &r = ci; // error: can't bind an ordinary int& to a const int object
const int &r2 = i; // ok: can bind const int& to plain int

2.4.4 constexpr和常量表達式

常量表達式是指值不會改變並且在編譯的過程中就能得到計算結果的表示式:

const int max_files = 20; // max_files is a constant expression
const int limit = max_files + 1; // limit is a constant expression
int staff_size = 27; // staff_size is not a constant expression
const int sz = get_size(); // sz is not a constant expression

C++ 11新標準規定,允許將變數宣告為constexpr型別以便編譯器來驗證變數的值是否一個常量表達式。

必須明確一點,在constexpr宣告中定義了一個指標,限定符constexper僅僅對指標有效,與指標所指的物件無關:

const int *p = nullptr; // p is a pointer to a const int
constexpr int *q = nullptr; // q is a const pointer to int

2.5處理型別

2.5.1類型別名

有兩種方法可以定義類型別名,傳統的方法是使用關鍵字 typedef :

typedef double wages; // wages is a synonym for double
typedef wages base, *p; // base is a synonym for double, p for double*

新標準規定了一種新的方法使用別名宣告來定義型別的別名:

using SI = Sales_item; // SI is a synonym for Sales_item

這種方法用關鍵字using 作為別名宣告的開始,其後緊跟著別名和等號。

如果某個類型別名指代的是複合型別或常量,那麼把它用到申明語句裡就會產生意想不到的後果。例如:

typedef char *pstring;
const pstring cstr = 0; // cstr is a constant pointer to char
const pstring *ps; // ps is a pointer to a constant pointer to char

上面的語句聲明瞭pstring ,它實際上是型別char*的別名。const是給定型別的修飾,pstring實際上是指向char的指標,因此,const pstring 就是指向char的常量指標,而非指向常量字元的指標。遇到一條使用了類型別名的宣告語句的時候,人們往往會錯誤的嘗試把類型別名替換成本來的樣子,以理解該含義:

const char *cstr = 0; // wrong interpretation of const pstring cstr

記住上面的理解是錯誤的~是對const pstring cstr的錯誤理解啊~~~~~~~~~~

2.5.2auto型別說明符

C++11 新標準引入了auto型別說明符,用它能讓編譯器代替我們去分析表示式所屬的型別。編譯器推斷出來的auto型別有時候和初始值型別不一樣,編譯器會適當地改變結果型別使其符合初始化的規則。auto一般會忽略掉頂層const,同時底層const會保留下來。例如:

const int ci = i, &cr = ci;
auto b = ci; // b is an int (top-level const in ci is dropped)
auto c = cr; // c is an int (cr is an alias for ci whose const is top-level)
auto d = &i; // d is an int*(& of an int object is int*)
auto e = &ci; // e is const int*(& of a const object is low-level const)

如果希望推斷出auto是頂層cosnt,需要明確指出:

const auto f =ci

2.5.3 decltyoe

C++11新標準引入第二種型別說明符 decltype ,它的作用是選擇並返回運算元的資料型別,編譯器會分析表示式並得到資料型別:

decltype(f()) sum = x; // sum has whatever type f returns

decltype 處理頂層const和引用的方式與auto並不相同。如果decltype使用的表示式是一個變數,則decltype返回改變數的型別

(包括頂層const和引用在內)

const int ci = 0, &cj = ci;
decltype(ci) x = 0; // x has type const int
decltype(cj) y = x; // y has type const int& and is bound to x
decltype(cj) z; // error: z is a reference and must be initialized

 decltype和引用

int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // ok: addition yields an int; b is an (uninitialized) int
decltype(*p) c; // error: c is int& and must be initialized

這個有點複雜,我覺的算黑洞的邊緣了,也不知道C++11怎麼搞得,慢慢來~

r是一個引用,因此decltype(r)的結果是引用型別。如果想讓結果型別r所指的型別,可以把r作為表示式的一部分,r+0,所以第二行就變長了int  。另一方面,解引用指標可以得到指標所指的物件,而且還能給這個物件賦值,因此,decltype(*p)的結果就是int& 而非int。(我想說我理解的是int*)。

另外decltype和auto的另一處重要區別是,decltype的結果型別和表示式形式密切相關。有一種情況特別注意:

對於decltype所用的表示式來說,如果變數名加上了一對括號,則得到的型別與不加括號時會有不同。如果decltype使用的是一個不加括號的變數,則得到的結果是該變數的型別。如果給變數多加了一層或多層括號,編譯器會把它認為是一個表示式。變數是一種可以作為賦值語句的左值的特殊的表示式,所有這樣的decltype就會得到一個引用型別:

decltype((i)) d; // error: d is int& and must be initialized
decltype(i) e; // ok: e is an (uninitialized) int

切記,decltype((variable))(注意是雙括號)的結果永遠是引用。

/**************************************************ending****************************************************************/

這一章的內容的知識盲點就到這裡~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

以後有的再新增吧~