chapter2 變量和基本類型
文章目錄
筆記
2.1基本內置類型
2.1.1算術類型
- 算術類型:整型(包括字符和bool),浮點型。
- 空類型:不對應具體值,僅用於某些特殊場合。如函數不返回任何值時使用空類型作爲返回類型。
- 無符號類型中的所有比特都用來存儲值。
- C++標準中約定,有符號數在表示範圍內正值和負值的量應該平衡。
2.1.2類別轉換
- 非布爾型賦值給布爾型:非0爲Ture,0爲False。
- 布爾賦型值給非布爾型:Ture爲1,False爲0。
- 浮點型賦值整型:近似處理,結果值僅保留浮點數的整數部分。
- 整型轉浮點型:小數部分取0。
- 當賦一個超出其範圍的值給無符號類型時,結果爲初始值對無符號類型表示數字總數取模後的餘數。
- 當給帶符號類型賦超過其表示範圍的數值時,其結果是未定義的,可能導致程序崩潰。
- 切勿混用帶符號類型和無符號類型。當表達式中既有帶符號類型又有無符號類型時,帶符號數會自動轉換成無符號數,程序會正確運行,但不會得到我們期望的結果。
2.1.3字面值常量
- 整型字面值
- 形式
- 八進制:以0開頭
- 十進制:正常表示
- 十六進制:以0x開頭
- 形式
- 浮點數字面值(默認double)
- 形式
- 小數:
3.14159
、0.
、.001
- 科學計數法:
3.14159E0
、0e0
指數部分用E或e標識。
- 小數:
- 形式
- 字符和字符串字面值
- 形式
- 字符字面值:用單引號括起來,‘a’
- 字符串字面值:用雙引號括起來,“abc”
- 注意:字符串字面值實際上是由常量字符構成的數組,在每個字符串結尾處添加空字符’\0’,字符串字面值的實際長度比它的內容多1。
- 形式
- 轉義序列
- 有兩類字符無法直接使用
- 不可打印的字符
- 在C++中有特殊含義的字符(引號雙引號等)
- 這些情況下需要用到轉義序列(
\t
、\n
等)
- 泛化轉義類型
\x
後緊跟一個或多個十六進制數字\
後緊跟1個、2個、3個八進制數字。數字部分表示字符對應的數值。
- 有兩類字符無法直接使用
- 指定字面值的類型
- 通過添加前綴和後綴,我們可以改變字面值的默認類型。
- 整型字面值:u或U表示unsigned,l或L表示有long,ll或LL表示longlong。
- 浮點數字面值:f或F表示float,l或L表示double。
- 字符和字符串:u表示Unicode16,U表示Unicode32,L表示寬字符,u8表示UTF-8(僅用於字符串字面常量)
- 通過添加前綴和後綴,我們可以改變字面值的默認類型。
- 布爾字面值和指針字面值
- Ture和False是布爾類型的字面值。
- nullptr是指針字面值。
2.2變量
變量提供一個具名的,可供程序操作的存儲空間。
2.2.1變量定義
- 定義形式:類型說明符後接一個或多個以逗號分割的變量名組成的列表,最後以分號結束。定義時可以爲一個或多個變量賦值。
- 例子:
int a;
int a,b,c=5;
- 例子:
- 初始值
- 對象在創建時獲得了一個值,我們稱其爲初始化。
- 定義完成的變量立刻就可以使用,因此在同一條定義語句中,可以用先定義的變量值去初始化後定義的其它變量。
- 例子:
double price = 109.99, discount = price * 0.16;
- 例子:
- 初始化與賦值:初始化不是賦值,初始化的含義是創建變量時賦予其一個初始值,而賦值的含義是把對象的當前值擦除,而以一個新值來替代。
- 列表初始化
- 以花括號來來初始化變量的形式被稱爲列表初始化。
- 例子:
int units_sold = {0};
以及int units_sold{0};
- 默認初始化
- 在定義變量時沒有指定初值,變量被默認初始化。**若內置類型的變量未被顯示初始化,則它的值是由定義的位置決定的。定義於任何函數體之外的變量初始化爲0;在函數體內部的變量不被初始化(即值未定義),在拷貝或訪問此類值時將引發錯誤。**因此,建議初始化每一個內置類型的變量。
2.2.2變量聲明和定義的關係
- 聲明:使名字爲程序所知,一個文件如果想使用別處定義的名字,則必須包含對那個名字的聲明。
- 定義:負責創建與名字關聯的實體。
- 變量的聲明和定義都規定了變量的類型和名字,但是定義還可以申請存儲空間,也可能爲變量賦初值。
- 聲明:
extern int i;
如果想聲明而非定義一個變量,就在變量名前添加關鍵字extern
,而不要顯式地初始化變量。 - 顯式化的聲明即爲定義:
extern double pi = 3.14159;
- 變量能且只能定義一次,但是可以被多次聲明。
- 在變量第一次被使用的附近定義它。
2.2.3標識符
- 由字母、數字和下劃線組成,必須以字母或下劃線開頭。對長度沒有限制,對大小寫敏感。
- 自定義標識符不能有連續兩個下劃線,不能以下劃線加大些字母開頭。定義在函數體外的標識符不能以下劃線開頭。
- 變量命名規範(用於提高可讀性):
- 標識符要能體現實際含義。
- 變量名一般用小寫字母。
- 自定義類名一般以大些字母開頭。
- 多個單詞組成應採用下劃線分隔,或採用駝峯法。
- 不能使用C++已有的關鍵字。
2.2.4名字的作用域
- 同一個名字在不同的作用域可能指向不同的實體。作用域通常以花括號{}分隔。
- 外層作用域的變量內層可見,全局作用域的變量全局可見。
- 如果函數有可能用到某全局變量或外層作用域變量,則不宜再定義一個同名的局部變量。
2.3複合類型
2.3.1引用
- 引用爲對象起了另外一個名字,定義引用時,程序把引用和它的初始值綁定在一起。一旦初始化完成,引用和它的初始化對象一直綁定在一起,因此引用必須初始化。
- 例子:
int i = 1024; int &r = i;
。一般以&d
的形式進行,d
爲被引用的變量名。 - 引用類型要與之綁定的對象嚴格匹配。
2.3.2指針
- 指針是指向另一種類型的符合類型。指針內存放着某個對象的地址。
- 例子:
int *ip1, *ip2
- 例子:
- 指針和引用的區別(補充)
- 指針本身是一個對象,引用只是對象的別名
- 指針在其生命週期內可以先後指向不同的對象,引用在初始化完成後,即和它的初始化對象綁定在一起。
- 指針無需在定義時賦初值,引用必須初始化。
- 獲取對象的地址
- 獲取指針內存放着的某個對象的地址需要使用取址符:
&
。 - 例子:
int ival = 42; int *p2 = &ival;
- 指針類型要與之指向的對象嚴格匹配。
- 獲取指針內存放着的某個對象的地址需要使用取址符:
- 指針值有四種情況:
- 1.指向一個對象。
- 2.指向緊鄰對象所佔空間的下一個位置。
- 3.空指針,意味着指針沒有指向任何對象。
- 4.無效指針,上述情況之外的其他值。
- 訪問無效指針將印發錯誤,使用第二第三種指針也是不被允許的。
- 利用指針訪問對象
- 用解引用符
*
可以訪問指針所指向的對象。 - 例子:
int ival = 42; int *p = &ival; cout << *p;
- 用解引用符
- 空指針
- 空指針不指向任何對象。
- 初始化空指針的方式:
int *p1 = nullptr; int *p2 = 0;
- 建議初始化所有指針。
2.3.3理解複合類型的聲明
- 定義多個變量
int* p1, p2;
應理解爲int(* p1), p2;
p1是指針,p2是int。*
僅僅修飾了p1
而已。
- 指向指針的引用
int *p; int *&r= p;
r爲對指針p的引用
- 面對複雜的指針或聲明語句,從右向左閱讀是比較好弄清楚的方式。
2.4 const限定符
- const的特點是const對象的值無法被改變,因此const對象也必須被初始化。
- 任何對const對象賦值的行爲都會報錯。只要不改變const對象的內容,其餘操作基本都能夠被進行。
- 例子:
int const i = 42; //編譯時初始化
int const j = get_size(); //運行時初始化
2.4.1 const的引用
- 對const的引用簡稱常量引用,在初始化常量引用時允許用任意表達式作爲初始值,允許爲一個常量引用綁定非常量的對象、字面值、一般表達式。
- 常量引用不允許通過引用修改其綁定對象的值,但可以通過其他途徑修改其綁定對象的值,如果被綁定的對象不是const對象的話。(直接修改綁定對象的值,通過其他非常量引用修改等)。
2.4.2 指針和const
- 指向常量的指針不能用於改變其所指對象的值,但可以指向一個非常量對象。
- 可以這樣理解,常量引用和指向常量的指針,不過是自己認爲自己指向了一個常量,所以無法更改其值而已。
- const指針
- 常量指針必須被初始化,且初始化完成後其值就不能被改變。
2.4.3 頂層const
- 定義
- 頂層const(top-level const):指針本身是個常量
- 底層const(low-level const):指針所指的對象是個常量
- 執行對象的拷貝操作時:
- 1)頂層const不受影響;
- 2)對於底層const而言:
- 拷入和拷出的對象必須具有相同的底層const資格
- 兩個對象的數據類型必須能夠轉換。一般來說,非常量可以轉換爲常量,反之則不行。
- 用於聲明引用的const都是底層const
2.4.4 constexpr 和常量表達式
- 常量表達式:值不會改變並且在編譯過程就能得到計算結果的表達式。
- constexpr變量:將變量聲明爲constexpr類型, 由編譯器驗證其值是否爲常量表達式。
- constexpr定義的指針爲頂層const。
2.5 處理類型
2.5.1 類型別名
- 類型別名是一個名字,用於指代某種類型
- 定義:
typedef: typedef double wages;
wages等同於double - 別名聲明(alias declaration):
using SI = Sales_item;
等同於Sales_item
- 定義:
- 在複合類型或常量中,在定義了別名後不要試圖替換爲本來的樣子進行理解會造成錯誤。
2.5.2 auto類型說明符
- auto:讓編譯器代替分析表達式所屬的類型。通過初始值推算變量類型
- auto會忽略引用和頂層const,保留底層const
- 在一條語句內定義多個變量時,初始值必須爲同一類型
2.5.3 decltype類型指示符
- decltype:在不用表達式的值初始化變量的情況下,從表達式的類型推斷出要定義的變量的類型。編譯器分析表達式並得到他的類型,卻不實際計算表達式的值
- 如果表達式的內容是解引用操作,的decltype將得到引用類型。
- decltype的表達式如果有雙層括號,則一定是個引用。
練習
2.1.1節練習
練習2.1
- 類型int、long、long long和short的區別是什麼?無符號類型和帶符號類型的區別是什麼?float和double的區別是什麼?
1)int佔2個字節。long佔4個字節。long long佔8個字節。short佔1個字節。其表示範圍相應地有所不同。
2)無符號類型表示值大於等於0。帶符號類型能表示負數。
3)float佔32字節。double佔64字節。double表示範圍比float大。
練習2.2
- 計算按揭貸款時,對於利率、本金和付款分別應該選擇何種數據類型?說明你的理由。
利率float。本金和付款爲double,因爲可能需要更多有效位數。付款方式char。
2.1.2節練習
練習2.3
-
讀程序寫結果。
unsigned u = 10, u2 = 42; std::cout << u2 - u << std::endl; // 輸出32 std::cout << u - u2 << std::endl; //輸出-32對int佔位數得值的模 int i = 10, i2 = 42; std::cout << i2 - i << std::endl; //輸出32 std::cout << i - i2 << std::endl; //輸出-32 std::cout << i - u << std::endl; //輸出0 std::cout << u - i << std::endl; //輸出0
練習2.4
- 編寫程序檢查你的估計是否正確,如果不正確,請仔細研讀本節知道弄明白問題所在。
2.1.3節練習
練習2.5
- 指出下述字面值的數據類型並說明每一組內幾種字面值的區別。
(a)'a', L'a', "a", L"a"
(b)10, 10u, 10L, 10uL, 012, 0xC
(c)3.14, 3.14f, 3.14L
(d)10, 10u, 10., 10e-2
(a)'a' //char字符型,字面值爲a
L'a' //long char 寬字符型,字面值爲a
"a" //string字符串型,字面值爲a\0
L"a" //long string寬字符串型,字面值爲a\0
(b)10 //int整型,10
10u //unsigned int無符號整型,10
10L //long長整型,10
10uL //unsigned long無符號長整型,10
012 //八進制int,字面值爲八進制的12,十進制的10
0xC //十六進制int,字面值爲十六進制的C,十進制的10
(c)3.14 //double浮點數,3.14
3.14f //float浮點數,3.14
3.14L //long double拓展精度浮點數,3.14
(d)10 //int整型,10
10u //unsigned int無符號整型,10
10. //double浮點數,10.
10e-2 //double浮點數,科學計數法表示,10e-2(0.01)
練習2.6
- 下面兩組定義是否有區別,如果有,請敘述之:
int month = 9, day = 7;
int month = 09, day = 07;
//有區別,前一組定義的是day是十進制,後一組day定義的是八進制。
練習2.7
- 下述字面值表達何種含義?它們各自的數據類型是什麼?
(a)"Who goes with F\145rgus?\012"
(b)3.14e1L (c)1024f (d)3.14L
(a)"Who goes with Fergus?"加一個換行 string字符串 //\145爲八進制下的e,\12爲換行符
(b)31.4 long double 拓展精度浮點數
(c)1024 float 浮點數
(d)3.14 long double 拓展精度浮點數
練習2.8
- 請利用轉義序列編寫一段程序,要求先輸出2M,然後轉到新一行。修改程序使其先輸出2,然後輸出製表符,再輸出M,最後轉到新一行。
int main()
{
std::cout << "2\115\n" << std::endl;
std::cout << "2\t\115\n" << std::endl;
return 0;
}
2.2.1節練習
練習2.9
- 解釋下列定義的含義。對於非法的定義,請說明在何處並將其改正。
(a) std::cin >> int input_value; (b) int i = { 3.14 };
(c) double salary = wage = 9999.99; (d) int i = 3.14;
(a)獲取int輸入。非法。變量需先定義,後使用。 改爲:
int input_value{0};
std::cin >> input_value;
(b)令整型數i初始化爲 3.14 。非法。使用float進行列表初始化int會丟失數據,因此會被編譯器拒絕。改爲:
int i = 3.14;
(c)初始化salary wage 爲9999.99。非法。這段代碼應解釋爲,將9999.99賦值給wage,並將wage的值作爲初值,對salary進行初始化。而wage並未被定義,未被初始化。改爲:
double wage = 0;
double salary = wage = 9999.99;
(d)令整型數i初始化爲 3.14 。合法。但會自動轉換,丟棄小數點後的信息。
練習2.10
- 下列變量的初值分別是什麼?
std::string global_str;
int global_int;
int main()
{
int local_int;
std::string local_str;
}
global_str的初值爲空串。global_int的初值爲0。local_int和local_str在main函數內,則不被初始化,初值爲未定義。
2.2.2節練習
練習2.11
- 指出下面的語句是聲明還是定義:
(a) extern int ix = 1024;
(b) int iy;
(c) extern int iz;
(a)顯示的聲明即爲定義,賦初始值的行爲抵消了extern的聲明作用。
(b)聲明並定義。
(c)聲明。
2.2.3節練習
練習2.12
- 請指出下面的名字中哪些是非法的?
(a) int double = 3.14; //非法。使用了C++已有的關鍵字
(b) int _; //合法
(c) int catch-22; //非法。標識符只能由數字,字母,下劃線組成
(d) int 1_or_2 = 1; //非法。標識符必須以下劃線或字母開頭
(e) double Double = 3.14; //合法
2.2.4節練習
練習2.13
- 下面程序中
j
的值是多少?
int i = 42;
int main()
{
int i = 100;
int j = i;
}
100。局部變量i
覆蓋了全局變量i
。
練習2.14
- 下面的程序合法嗎?如果合法,它將輸出什麼?
int i = 100, sum = 0;
for (int i = 0; i != 10; i++)
sum += i;
std::cout << i << " " << sum << std::endl;
合法,輸出100 45
。
2.3.1節練習
練習2.15
- 下面的哪個定義是不合法的?爲什麼?
(a) int ival = 1.01; //合法,但小數點部分會被捨棄
(b) int &rvall = 1.01; //不合法,引用類型的初始值必須是一個對象
(c) int &rval2 = ival; //合法
(d) int &rval3; //不合法,引用類型必須有初始值
練習2.16
- 考查下面的所有賦值然後回答:哪些賦值是不合法的?爲什麼?哪些賦值是合法的?它們執行了什麼樣的操做?
int i = 0, &r1 = i;
double d = 0, &r2 = d;
(a) r2 = 3.14159; //合法,給r2(d)賦值爲3.14159
(b) r2 =r1; //合法,給r2(d)賦值r1(i)的值0
(c) i =r2; //合法,給i賦值r2(d)的值0
(d) r1 = d; //合法,給r1(i)賦值d的值0
練習2.17
- 執行下面的代碼段將輸出什麼結果?
int i, &ri = i;
i = 5; ri = 10;
std::cout << i << " " << ri << std::endl;
10 10
2.3.2節練習
練習2.18
- 編寫代碼分別更改指針的值以及指針所指向對象的值。
int main()
{
int i = 100, j = 200, *p = &i;
p = &j; //更改指針的值
*p = 100; //更改指針所指向對象的值
return 0;
}
練習2.19
- 說明指針和引用的主要區別。
- 指針本身是一個對象,引用只是對象的別名
- 指針在其生命週期內可以先後指向不同的對象,引用在初始化完成後,即和它的初始化對象綁定在一起。
- 指針無需在定義時賦初值,引用必須初始化。
練習2.20
- 請敘述下面這段代碼的作用。
int i = 42;
int *p1 = &i; // 初始化指針p1指向i
*p1 = *p1 * *p1; // 將指針p1指向的對象值變爲它的平方,即p1現在所指的對象值爲1764。
練習2.21
- 請解釋下述定義。在這些定義中有非法的嗎?如果有,爲什麼?
int i = 0;
(a) double* dp = &i; //非法。類型不匹配
(b) int *ip = i; //非法。指針指向的是變量的地址,不是變量本身。需用取址符&取其地址。
(c) int *p = &i; //合法
練習2.22
- 假設p是一個int型指針,請說明下述代碼的含義。
if (p) // 判斷指針是否爲空指針
if (*p) // 判斷指針指向的對象的值是否爲0
練習2.23
- 給定指針p,你能知道它是否指向了一個合法的對象嗎?如果能,敘述判斷的思路;如果不能,也請說明原因。
不能知道。因爲指針可以任意指向一個地址,即使有時候可能看似正確,但依然不能保證它是無害的,指針的意義就是它的值是內存地址,它將按它指向的類型的信息來解釋那片空間。而那片空間是否合法,是由程序員保證,而不是指針本身。
練習2.24
- 在下面這段代碼中爲什麼p合法而lp不合法?
int i = 42; void *p = &i; long *lp = &i;
void*
指針可以指向任何值,因此p
合法。long
爲long int
類型,與i
的int
類型不匹配。故非法。
2.3.3練習
練習2.25
- 說明下列變量的類型和值。
(a) int* ip,i, &r = i; //ip,int指針,值不確定。i,int指針,值不確定。r,int引用,值不確定。
(b) int i, *ip = 0; //i int,值不確定。 ip,int指針,空指針。
(c) int* ip, ip2; //ip int指針,值不確定。ip2,int,值不確定。
2.4節練習
練習2.26
- 下面哪些句子是合法的?如果有不合法的句子,請說明爲什麼?
(a) const int buf; //不合法,未初始化
(b) int cnt = 0; //合法
(c) const int sz = cnt; //合法
(d) ++cnt; ++sz; //後一個不合法,const不允許被賦值(++內含賦值)
2.4.2節練習
練習2.27
- 下面的哪些初始化是合法的?請說明原因。
(a) int i = -1, &r =0; //不合法,引用必須綁定對象
(b) int *const p2 = &i2; //如果i2是int則合法
(c) const int i = -1, &r =0; //合法。常量引用允許綁定任何值
(d) const int *const p3 = &i2; //如果i2是const int則合法
(e) const int *p1 = &i2; //如果i2是int則合法
(f) const int &const r2; //不合法,引用必須先初始化
(g) const int i2 = i, &r = i; //如果i是int則合法
練習2.28
- 說明下面的這些定義是什麼意思,挑出其中不合法的。
(a) int i, *const cp; //定義int i,後半句不合法,常量必須被初始化
(b) int *p1, *const p2; //定義int指針 p1,後半句不合法,常量必須被初始化
(c) const int ic, &r = ic; //不合法,常量必須被初始化。後半句合法,對int常量ic的引用
(d) const int *const p3; //不合法,常量必須被初始化
(e) const int *p; //合法,定義指向int常量的指針p
練習2.29
- 假設已有上一個練習中定義的那些變量,則下面的哪些語句是合法的?請說明原因。
(a) i = ic; //合法,將int常量ic的值賦值給int i
(b) p1 = p3; //不合法,指針類型不同
(c) p1 = ⁣ //不合法,普通指針p1無法指向常量
(d) p3 = ⁣ //不合法,p3是常量指針,無法改變其值
(e) p2 = p1; //不合法,p2是常量指針,無法改變其值
(f) ic = *p3; //不非法,ic是常量int,無法改變其值
2.4.3節練習
練習2.30
- 對於下面的這些語句,請說明對象被申明成了頂層const還是底層const?
const int v2 = 0; int v1 = v2; //v2頂層const
int *p1 = &v1, &r1 = v1;
const int *p2 = &v2, *const p3 = &i, &r2 = v2; //p2底層const,p3兩者都有,r2底層const
練習2.31
- 假設已有上一個練習中所做的那些聲明,則下面的哪些語句是合法的?請說明頂層const和底層const在每個例子中有何體現。
r1 = v2; //合法,拷貝時頂層const無影響
p1 = p2; //不合法,底層const只能拷貝給擁有同樣底層const
p2 = p1; //合法,int*可以轉換成const int*
p1 = p3; //不合法,底層const只能拷貝給擁有同樣底層const
p2 =p3; //合法,擁有同樣底層const
2.4.4節練習
練習2.32
- 下面的代碼是否合法?如果非法,請設法將其修改正確。
int null = 0, *p = null;
不合法,null是個整型,p是個int 指針,二者雖然字面值是一樣的,均爲0,但類型不同,不可混用。改爲:
int null = 0, *p = nullptr;
2.5.2節練習
練習2.33
- 利用本節定義的變量,判斷下列語句的運行結果。
a = 42; b = 42; c = 42; //成功賦值
d = 42; e = 42; g = 42; //賦值失敗
練習2.34
- 基於上一個練習中的變量和語句編寫一段程序,輸出賦值前後變量的內容,你剛纔的推斷正確嗎?如果不對,請反覆研讀本節的示例知道你明白錯在何處爲止。
int i = 0, &r = i;
auto a = r;
cout << a <<endl;
// r是i的別名,i是一個整數,故a爲一個整數
a = 42; //accept
cout << a << endl;
const int ci = i, &cr = ci;
auto b = ci; //ci爲一個常量,但是auto會忽略const,故b爲一個int變量
cout << b << endl;
b = 42;
cout << b << endl;
auto c = cr; //cr是ci的別名,ci爲常量,auto忽略const,故b爲一個int變量
cout << c << endl;
c = 42;
cout << c << endl;
auto d = &i; //&i爲i的地址,故d爲一個指向int變量的指針
cout << d << endl;
//d = 42; //賦值失敗,d內存儲的爲一個int變量的地址
auto e = &ci; //&ci爲常量ci的地址,故e本來應該爲指向常量的指針,但是auto忽略頂層const,故e爲一個指向int變量的指針
cout << e << endl;
auto &g = ci; //ci爲一個常量, 故g爲一個常量引用
cout << g << endl;
// 等式右邊爲常量引用,左邊也必須定義爲常量形式
練習2.35
- 判斷下列定義推斷出的類型是什麼,然後編寫程序進行驗證。
const int i = 42; //const int整型常量
auto j = i; //int整型
const auto &k = i; //int整型的常量引用
auto *p = &i; //指向常量整型的指針
const auto j2 = i, &k2 = i; //j2 int整型常量,k2 int整型的常量引用
Code:
const int i = 42;
auto j = i;//i爲常量,auto判斷會忽略頂層const,故j爲int變量
j = 0; //賦值成功,j爲int型
const auto &k = i; //
// k = 0; //賦值失敗,因爲k爲const int&
auto *p = &i;
// *p = 0; //賦值失敗,*p不可修改,因爲p爲const int *
const auto j2 = i, &k2 = i;
// j2 = 0; //賦值失敗,j2爲const int,j2值不可修改
// k2 = 0; //賦值失敗,k2爲const int&, k2值不可修改
2.5.3節練習
練習2.36
- 關於下面的代碼,請指出每一個變量的類型以及程序結束時它們各自的值。
int a =3, b =4;
decltype(a) c = a;
decltype((b)) d =a;
++c;
++d; //d是對a的引用,自增使得a和d均加1
//a,4,int b,4,int c,4,int, d,4,int&
練習2.37
- 賦值時會產生引用的一類典型表達式,引用的類型就是左值的類型。也就是說,如果
i
是int
,這表達式i=x
的類型是int&
。根據這一特點,請指出下面的代碼中的每一個變量的類型和值。
int a = 3, b = 4;
decltype(a) c = a;
decltype(a = b) d =a; //decltype只推斷類型,沒有進行賦值操作
//a,int,3 b,int,4 c,int,3 d,int&,3
練習2.38
- 說明由decltype指定類型和由auto指定類型那個有何區別。請舉出一個例子,decltype指定的類型和auto指定的類型一樣;再舉一個例子,decltype指定的類型與auto指定的類型不一樣。
//auto必須初始化,decltype不用。auto的結果與表達形式無關,decltype結果類型與表達式形式有關
//decltype指定的類型與auto指定的類型一樣:auto (x)=a; decltype(x)=a;
//decltype指定的類型與auto指定的類型不一樣:auto (x)=a; decltype((x))=a;
int i = 0, &r = i;
// same
auto a = i;
decltype(i) b = i;
// different
auto c = r;
decltype(r) d = r;
2.6.1節練習
練習2.39
- 編譯下面的程序觀察其運行結果,注意,如果忘記寫類定義體後面的分號會發生什麼情況?記錄下相關信息,以後可能會用。
struct Foo { /* 此處爲空 */} //注意:沒有分號
int main()
{
return 0;
}
//error C2628: “Foo”後面接“int”是非法的(是否忘記了“;”?)
練習2.40
- 根據自己的理解寫出Sales_data類,最好與書中的例子區別。
struct Sale_data {
std::string bookno;
std::string bookname;
double price = 0.0;
int num = 1;
};
int main() {
return 0;
}
2.6.2節練習
練習2.41
- 使用你自己的Sales_item類重寫1.5.1節、1.5.2節和1.6節的練習。眼下先把Sales_data類的定義和main函數放在同一個文件裏。
1.5.1
#include <iostream>
#include <string>
struct Sale_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
int main()
{
Sale_data book;
double price;
std::cin >> book.bookNo >> book.units_sold >> price;
book.revenue = book.units_sold * price;
std::cout << book.bookNo << " " << book.units_sold << " " << book.revenue << " " << price;
return 0;
}
1.5.2
#include <iostream>
#include <string>
struct Sale_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
int main()
{
Sale_data book1, book2;
double price1, price2;
std::cin >> book1.bookNo >> book1.units_sold >> price1;
std::cin >> book2.bookNo >> book2.units_sold >> price2;
book1.revenue = book1.units_sold * price1;
book2.revenue = book2.units_sold * price2;
if (book1.bookNo == book2.bookNo) {
unsigned totalCnt = book1.units_sold + book2.units_sold;
double totalRevenue = book1.revenue + book2.revenue;
std::cout << book1.bookNo << " " << totalCnt << " " << totalRevenue << " ";
if (totalCnt != 0)
std::cout << totalRevenue / totalCnt << std::endl;
else
std::cout << "(no sales)" << std::endl;
return 0;
}
else {
std::cerr << "Data must refer to same ISBN" << std::endl;
return -1; // indicate failure
}
}
1.6
#include <iostream>
#include <string>
struct Sale_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
int main()
{
Sale_data total;
double totalPrice;
if (std::cin >> total.bookNo >> total.units_sold >> totalPrice) {
total.revenue = total.units_sold * totalPrice;
Sale_data trans;
double transPrice;
while (std::cin >> trans.bookNo >> trans.units_sold >> transPrice) {
trans.revenue = trans.units_sold * transPrice;
if (total.bookNo == trans.bookNo) {
total.units_sold += trans.units_sold;
total.revenue += trans.revenue;
}
else {
std::cout << total.bookNo << " " << total.units_sold << " " << total.revenue << " ";
if (total.units_sold != 0)
std::cout << total.revenue / total.units_sold << std::endl;
else
std::cout << "(no sales)" << std::endl;
total.bookNo = trans.bookNo;
total.units_sold = trans.units_sold;
total.revenue = trans.revenue;
}
}
std::cout << total.bookNo << " " << total.units_sold << " " << total.revenue << " ";
if (total.units_sold != 0)
std::cout << total.revenue / total.units_sold << std::endl;
else
std::cout << "(no sales)" << std::endl;
return 0;
}
else {
std::cerr << "No data?!" << std::endl;
return -1; // indicate failure
}
}
2.6.3節練習
練習2.42
- 根據你自己的理解重寫一個Sales_data.h頭文件,並以此爲基礎重做2.6.2節練習。
Sales_data.h
#include <iostream>
#include <string>
// own Sales_data
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
void CalcRevenue(double price);
double CalcAveragePrice();
void SetData(Sales_data data);
void AddData(Sales_data data);
void Print();
};
void Sales_data::CalcRevenue(double price)
{
revenue = units_sold * price;
}
void Sales_data::SetData(Sales_data data)
{
bookNo = data.bookNo;
units_sold = data.units_sold;
revenue = data.revenue;
}
void Sales_data::AddData(Sales_data data)
{
if (bookNo != data.bookNo) return;
units_sold += data.units_sold;
revenue += data.revenue;
}
double Sales_data::CalcAveragePrice()
{
if (units_sold != 0)
return revenue / units_sold;
else
return 0.0;
}
void Sales_data::Print()
{
std::cout << bookNo << " " << units_sold << " " << revenue << " ";
double averagePrice = CalcAveragePrice();
if (averagePrice != 0.0)
std::cout << averagePrice << std::endl;
else
std::cout << "(no sales)" << std::endl;
}
1.5.1
#include <iostream>
#include "2.42-Sales_data.h"
int main()
{
Sales_data book;
double price;
std::cin >> book.bookNo >> book.units_sold >> price;
book.CalcRevenue(price);
book.Print();
return 0;
}
1.5.2
#include <iostream>
#include "2.42-Sales_data.h"
int main()
{
Sales_data total;
double totalPrice;
if (std::cin >> total.bookNo >> total.units_sold >> totalPrice) {
total.CalcRevenue(totalPrice);
Sales_data trans;
double transPrice;
while (std::cin >> trans.bookNo >> trans.units_sold >> transPrice) {
trans.CalcRevenue(transPrice);
if (total.bookNo == trans.bookNo) {
total.AddData(trans);
}
else {
total.Print();
total.SetData(trans);
}
}
total.Print();
return 0;
}
else {
std::cerr << "No data?!" << std::endl;
return -1; // indicate failure
}
}
1.6
#include <iostream>
#include "2.42-Sales_data.h"
int main()
{
Sales_data total;
double totalPrice;
if (std::cin >> total.bookNo >> total.units_sold >> totalPrice) {
total.CalcRevenue(totalPrice);
Sales_data trans;
double transPrice;
while (std::cin >> trans.bookNo >> trans.units_sold >> transPrice) {
trans.CalcRevenue(transPrice);
if (total.bookNo == trans.bookNo) {
total.AddData(trans);
}
else {
total.Print();
total.SetData(trans);
}
}
total.Print();
return 0;
}
else {
std::cerr << "No data?!" << std::endl;
return -1; // indicate failure
}
}