第 2 章
2.1
int、long、long long 和 short 表示整型類型,C++語言規定它們表示數的范圍 short \(\leq\) int \(\leq\) long \(\leq\) long long。其中,數據類型 long long 是在 C++11 中新定義的;
除去布爾型和擴展的字符型之外,其它整型可以劃分為帶符號的(signed)和無符號的(unsigned)兩種。帶符號類型可以表示正數、負數和 0,無符號類型則僅能表示大于等于 0 的值。類型 int、short、long 和 long long 都是帶符號的,通過在這些類型名前添加 unsigned 就可以得到無符號類型,例如 unsigned long。類型 unsigned int 可以縮寫為 unsigned;
float 和 double 用于表示浮點數,其中,float 表示單精度浮點數,double 表示雙精度浮點數。執行浮點數運算選用 double,這是因為 float 通常精度不夠而且雙精度浮點數和單精度浮點數的計算代價相差無幾。事實上,對于某些機器來說,雙精度運算甚至比單精度還快。long double 提供的精度在一般情況下是沒有必要的,況且它帶來的運算時消耗也不容忽視。
附:單精度與雙精度是什么意思,有什么區別?
2.2
利率(rat):float,本金(principal):long long,付款(payment):long long。
利率一般是小數點后保留四位有效數字,float 合適;本金和付款使用最大的帶符號整型表示。
2.3
main.cpp
#include <iostream>int main() {std::cout << sizeof(unsigned) << std::endl;std::cout << sizeof(int) << std::endl;std::cout << "**************************" << std::endl;unsigned u = 10, u2 = 42;std::cout << u2 - u << std::endl;std::cout << u - u2 << std::endl;std::cout << "**************************" << std::endl;int i = 10, i2 = 42;std::cout << i2 - i << std::endl;std::cout << i - i2 << std::endl;std::cout << i - u << std::endl;std::cout << u - i << std::endl;return 0; } 4 4 ************************** 32 4294967264 ************************** 32 -32 0 0Process finished with exit code 0 unsigned int 占 4 字節 int 占 4 字節 ************************** u2 - u = 42 - 10 = 32 u - u2 = 10 - 42 = unsigned(-32) = 4294967264 分析一: 32 的二進制表示為 0000 0000 0000 0000 0000 0000 0010 0000 -32 的二進制補碼表示為 1111 1111 1111 1111 1111 1111 1110 0000 也就是說 -32 的十六進制表示為 FFFF FFE0 若將 FFFF FFE0 轉換為 unsigned int 類型,則高位的符號位全部成為了數值位,FFFF FFE0 按無符號數轉換成十進制數為 4294967264。 分析二: 把負數轉換成無符號數類似于直接給無符號數賦一個負值,結果等于這個負數加上無符號數的模。 unsigned int 占 4 字節,4 字節的無符號數模為 4294967296。 -32 + 4294967296 = 4294967264 ************************** i2 - i = 42 - 10 = 32 i - i2 = 10 - 42 = -32 當一個算術表達式中既有無符號數又有 int 值時,那個 int 值就會轉換成無符號數。 i - u = unsigned(10) - 10 = 0 u - i = 10 - unsigned(10) = 0 分析: 有符號數 10 轉換成無符號數 10,因為 10 是正數,符號位為 0,故轉換成無符號數時,符號位不會對對應無符號數產生副作用。Process finished with exit code 0 程序中 return 0 語句的副作用2.4
請看 2.3
2.5
(a) 'a', L'a', "a", L"a"
| 'a' | 字符字面值,類型是 char |
| L'a' | 寬字符字面值,類型是 wchar_t |
| "a" | 字符串字面值 |
| L"a" | 寬字符串字面值 |
(b) 10, 10u, 10L, 10uL, 012, 0xC
| 10 | 整型字面值,類型是 int |
| 10u | 無符號整型字面值,類型是 unsigned int |
| 10L | 整型字面值,類型是 long |
| 10uL | 無符號整型字面值,類型是 unsigned long |
| 012 | 八進制整型字面值,類型是 int |
| 0xC | 十六進制整型字面值,類型是 int |
(c) 3.14, 3.14f, 3.14L
| 3.14 | 浮點數字面值,類型是 double |
| 3.14f | 浮點數字面值,類型是 float |
| 3.14L | 浮點數字面值,類型是 long double |
(d) 10, 10u, 10., 10e-2
| 10 | 整型字面值,類型是 int |
| 10u | 無符號整型字面值,類型是 unsigned int |
| 10. | 浮點數字面值,類型是 double |
| 10e-2 | 浮點數字面值,類型是 double |
2.6
有區別
int month = 9, day = 7; // 9 和 7 是十進制數,正確的賦值 // 以 0 開始表示八進制,八進制各位范圍是 0 ~ 7,賦值不合法 int month = 09, day = 07;2.7
| "Who goes with F\145rgus?\012" | 字符串字面值,包含兩個八進制轉義序列 |
| 3.14e1L | 浮點數字面值,類型是 long double |
| 1024f | 浮點數字面值,類型是 float |
| 3.14L | 浮點數字面值,類型是 long double |
2.8
分別寫了八進制和十六進制的版本
ASCII
#include <iostream>int main() {std::cout << "-------- Oct --------" << std::endl;std::cout << "\62\115\12";std::cout << "---------------------" << std::endl;std::cout << "\62";std::cout << "\11";std::cout << "\115";std::cout << "\12";std::cout << "-------- Hx ---------" << std::endl;std::cout << "\x32\x4d\xa";std::cout << "---------------------" << std::endl;std::cout << "\x32";std::cout << "\x9";std::cout << "\x4d";std::cout << "\xa";return 0; } // 運行結果 -------- Oct -------- 2M --------------------- 2 M -------- Hx --------- 2M --------------------- 2 MProcess finished with exit code 02.9
// (a) // std::cin >> int input_value; // Error: variable must be defined before using for input int input_value; std::cin >> input_value;// (b) // int i = { 3.14 }; // Error: loss information in list initialization double d = { 3.14 }; // OK double d2 = { 3 }; // OK// (c) // double salary = wage = 9999.99; // Error double salary, wage; salary = wage = 9999.99;// (d) int i2 = 3.14; // OK, `i2` is 32.10
#include <iostream>std::string global_str; int global_int; int main () {int local_int;std::string local_str;std::cout << "global_str = " << global_str << std::endl;std::cout << "global_int = " << global_int << std::endl;std::cout << "local_str = " << local_str << std::endl;std::cout << "local_int = " << local_int << std::endl;return 0; } // 運行結果 global_str = global_int = 0 local_str = local_int = -406316616Process finished with exit code 02.11
extern int ix = 1024; // 定義 int iy; // 聲明并定義 iy extern int iz; // 聲明 iz 而非定義 iz2.12
int double = 3.14; // 錯誤,c++關鍵字 double 不能作為標識符 int _; // 正確 int catch-22; // 錯誤,標識符由數字、字母和下劃線組成 int 1_or_2 = 1; // 錯誤,標識符必須以字母或下劃線開頭 double Double = 3.14; // 正確,標識符長度沒有限制,大小寫敏感2.13
程序中 j 的值是 100
2.14
#include <iostream>int main () {int i = 100, sum = 0;for (int i = 0; i != 10; ++i)sum += i;std::cout << i << " " << sum << std::endl;return 0; } // 運行結果 100 45Process finished with exit code 02.15
#include <iostream>int main () {// 正確,隱式的將 1.01 轉換成 1int ival= 1.01;// 錯誤,除 2.4.1 節(第 55 頁)和 15.2.3 節(第 534 頁)將// 要介紹的兩種例外情況,引用的類型都要和與之綁定的對象嚴格匹// 配。而且,引用只能綁定在對象上,而不能與字面值或某個表達式的// 計算結果綁定在一起,相關原因將在 2.4.1 節詳述int &rval1 = 1.01;// 正確int &rval2 = ival;// 錯誤,引用必須被初始化,且初始值必須是一個對象int &rval3;return 0; }2.16
#include <iostream>int main () {int i = 0, &r1 = i;double d = 0, &r2 = d;// 正確r2 = 3.14159;std::cout << r2 << "\n";// 正確,發送自動類型轉換r2 = r1;std::cout << r2 << '\n';// 正確,發送自動類型轉換i = r2;std::cout << i << std::endl;// 正確,發送自動類型轉換r1 = d;std::cout << r1 << std::endl;return 0; }2.17
#include <iostream>int main () {int i, &ri = i;i = 5;ri = 10;std::cout << i << " " << ri << std::endl;return 0; } // 運行結果 10 10Process finished with exit code 02.18
#include <iostream>int main () {int ival = 23, *ip = &ival;std::cout << "ival = " << ival << " " << "*ip = " << *ip << std::endl;*ip = 24;std::cout << "ival = " << ival << " " << "*ip = " << *ip << std::endl;ival = 25;std::cout << "ival = " << ival << " " << "*ip = " << *ip << std::endl;return 0; } // 運行結果 ival = 23 *ip = 23 ival = 24 *ip = 24 ival = 25 *ip = 25Process finished with exit code 02.19
引用
引用(reference)為對象起了另外一個名字,引用類型引用(refers to)另外一種類型。
int ival = 1024; int &refVal = ival; // refVal 指向 ival(是 ival 的另一個名字) int &refVal2; // 報錯:引用必須被初始化 int &refVal4 = 10; // 錯誤:引用類型的初始值必須是一個對象 // 除特殊情況,其他所有引用的類型都要和與之綁定的對象嚴格匹配 double dval = 3.14; int &refVal5 = dval; // 錯誤:此處引用類型的初始值必須是 int 型對象指針
指針(pointer)是"指向(point to)"另外一種類型的復合類型。與引用類似,指針也實現了對其他對象的間接訪問。然而指針與引用相比又有很多不同點。
指針本身就是一個對象,允許對指針賦值和拷貝,而且在指針的生命周期內它可以先后指向幾個不同的對象;
指針無須在定義時賦初值;
因為引用不是對象,沒有實際地址,所以不能定義指向引用的指針;
除特殊情況,其他所有指針的類型都要和它所指向的對象嚴格匹配;
空指針的定義方法:
int *p1 = nullptr; // C++11 新標準引人的方法,推薦使用 int *p2 = 0; // 直接將 p2 初始化為字面值常量 0 // 需要首先 #include cstdlib int *p3 = NULL;三種定義空指針的方法等價,推薦使用第一種。
雖然指針無須在定義時賦初值,但是記得初始化所有指針。如果實在不清楚指針應該指向何處,就把它初始化為 nullptr 或者 0,這樣程序就能檢測并知道它沒有指向任何具體的對象了。使用未經初始化的指針是引發運行時錯誤的一大原因。
2.20
將 \(i\) 的值修改為 \(i^2\)
#include <iostream>int main () {int i = 42;int *p1 = &i;*p1 = *p1 * *p1;std::cout << "i = " << i << " " << "*p1 = " << *p1;return 0; } // 運行結果 i = 1764 *p1 = 1764 Process finished with exit code 02.21
int i = 0; double *dp = &i; // 錯誤,指針的類型都要和它所指向的對象嚴格匹配 int *ip = i; // 錯誤,忘記取地址符 & int *p = &i; // 正確2.22
if (p) means if the pointer p is not null.
if (*p) means if the object pointed by the pointer is not false (which means the object is not null or zero etc.).
#include <iostream>int main () {int i = 42;int *p = &i;std::cout << "p = " << p << " " << "*p = " << *p << std::endl;if (p)std::cout << "true" << std::endl;elsestd::cout << "false" << std::endl;if (*p)std::cout << "true" << std::endl;elsestd::cout << "false" << std::endl;i = 0;p = &i;std::cout << "p = " << p << " " << "*p = " << *p << std::endl;if (p)std::cout << "true" << std::endl;elsestd::cout << "false" << std::endl;if (*p)std::cout << "true" << std::endl;elsestd::cout << "false" << std::endl;return 0; } // 運行結果 p = 0x7ffee92f3818 *p = 42 true true p = 0x7ffee92f3818 *p = 0 true falseProcess finished with exit code 02.23
No, you can't. Because it would be expensive to maintain meta data about what constitutes a valid pointer and what doesn't, and in C++ you don't pay for what you don't want.
See answer here.
However, a smart pointer can be used to tell if it points to a valid object.
注:此題解答來自
2.24
int main() {int i = 42;void *p = &i; // OK, a `void *` pointer can point to any type//long *lp = &i; // Error, a `long *` pointer can not point to `int *`return 0; }2.25
#include <iostream>int main() {{int* ip, i, &r = i; // `ip` is `int *`, `i` is `int`, `r` is `int &`std::cout << "(a)" << std::endl;std::cout << "ip\t" << typeid(ip).name() << std::endl;std::cout << "i\t" << typeid(i).name() << std::endl;std::cout << "r\t" << typeid(r).name() << std::endl;// Note that `typeid` will lose the `const` qualifier and reference}{int i, *ip = 0; // `i` is `int`, `ip` is `int *`std::cout << "(b)" << std::endl;std::cout << "i\t" << typeid(i).name() << std::endl;std::cout << "ip\t" << typeid(ip).name() << std::endl;}{int* ip, ip2; // `ip` is `int *`, `ip2` is `int`std::cout << "(c)" << std::endl;std::cout << "ip\t" << typeid(ip).name() << std::endl;std::cout << "ip2\t" << typeid(ip2).name() << std::endl;}return 0; } // 運行結果 (a) ip Pi i i r i (b) i i ip Pi (c) ip Pi ip2 iProcess finished with exit code 0注:
Understanding the output of typeid().name()
std::type_info::name
2.26
本題所有語句應該被看作是順序執行的,即形如:
const int buf; int cnt = 0; const int sz = cnt; ++cnt; ++sz;(a)是非法的,const 對象一旦創建后其值就不能改變,所以 const 對象必須初始化。該句應修改為 const int buf = 10。
(b)和(c)是合法的。
(d)是非法的,sz 是一個 const 對象,其值不能被改變,當然不能執行自增操作。
2.27
(a)是非法的,非常量引用 r 不能引用字面值常量 0。修改方法:
int i = -1, &r = i; // 或 const int i = -1, &r = 0; // 或 const int i = -1, &r = i;(b)是合法的,p2 是一個常量指針,p2 的值永不改變,即 p2 永遠指向變量 i2。
(c)是合法的,i 是一個常量,r 是一個常量引用,此時 r 可以綁定到字面值常量 0。
(d)是合法的,p3 是一個常量指針,p3 的值永不改變,即 p3 永遠指向變量 i2;同時 p3 指向的是常量,即我們不能通過 p3 改變所指對象的值。
(e)是合法的,p1 指向一個常量,即我們不能通過 p1 改變所指對象的值。
(f)是非法的,引用本身不是對象,因此不能讓引用恒定不變。
(g)是合法的,i2 是一個常量,r 是一個常量引用。
2.28
(a)是非法的,cp 是一個常量指針,因其值(指針存儲的那個地址)不能被改變,所以必須被初始化。
(b)是非法的,cp2 是一個常量指針,因其值不能被改變,所以必須被初始化。
(c)是非法的,ic 是一個常量,因其值不能被改變,所以必須被初始化。
(d)是非法的,p3 是一個常量指針,因其值不能被改變,所以必須被初始化;同時 p3 所指向的是常量,即我們不能通過 p3 改變所指對象的值。
(e)是合法的,但是 p 沒有指向任何實際的對象。
2.29
(a)是合法的,常量 ic 的值賦給了非常量 i。
(b)是非法的,普通指針 p1 指向了一個常量,從語法上說,p1 的值可以隨意改變,顯然是不合理的。因為 p1 改變了,p1 所指對象因該也同時改變了,但是 p1 所指對象是一個常量,不能被修改。
(c)是非法的,普通指針 p1 指向了一個常量,錯誤情況與上一條類似。
(d)是非法的,p3 是一個常量指針,不能被賦值。
(e)是非法的,p2 是一個常量指針,不能被賦值。
(f)是非法的,ic 是一個常量,不能被賦值。
2.30
頂層 const 表示任意的對象是常量,而底層 const 與指針和引用等復合類型的基本類型部分有關。
解答:v2 和 p3 是頂層 const,分別表示一個整型常量和一個整型常量指針;p2 和 r2 是底層 const,分別表示它們所指(所引用)的對象是常量。
2.31
本題考查頂層 const 和底層 const 對于拷貝操作的影響。
解答:
在執行拷貝操作時,頂層 const 和底層 const 區別明顯。其中,頂層 const 不受影響,這是因為拷貝操作并不會改變被拷貝對象的值。底層 const 的限制則不容忽視,拷入和拷出的對象必須具有相同的底層 const 資格,或者兩個對象的數據類型必須能夠轉換。一般說來,非常量可以轉換成常量,反之則不行。
r1 = v2; 是合法的,r1 是一個非常量引用,v2 是一個常量(頂層 const),把 v2 的值拷貝給 r1 不會對 v2 有任何影響。
p1 = p2; 是非法的,p1 是普通指針,指向的對象可以是任意值,p2 是指向常量的指針(底層 const),令 p1 指向 p2 所指的內容,有可能錯誤地通過 p1 改變 p2 所指常量的值。
p2 = p1; 是合法的,與上一條語句相反,p2 可以指向一個非常量,只不過我們不會通過 p2 更改它所指的值。
p1 = p3; 是非法的,p3 包含底層 const 定義(p3 所指的對象是常量),不能把 p3 的值賦給普通指針。
p2 = p3; 是合法的,p2 和 p3 包含相同的底層 const,p3 的頂層 const 則可以忽略不計。
2.32
int null = 0, *p = &null; // 或 int null = 0, *p = nullptr;2.33
【出題思路】
本題旨在考察 auto 說明符與復合類型、常量混合使用時的各種情形。首先,使用引用其實是使用引用的對象,所以當引用被用作初始值時,真正參與初始化的其實是引用對象的值,編譯器以引用對象的類型作為 auto 的推斷類型。其次,auto 一般會忽略掉頂層 const,同時保留底層 const。
【解答】
前 3 條賦值語句是合法的,原因如下:
r 是 i 的別名,而 i 是一個整數,所以 a 的類型推斷結果是一個整數;ci 是一個整型常量,在類型推斷時頂層 const 被忽略掉了,所以 b 是一個整數;cr 是 ci 的別名,而 ci 是一個整型常量,所以 c 的類型推斷結果是一個整數。因為 a、b、c 都是整數,所以為其賦值 42 是合法的。
后 3 條賦值語句是非法的,原因如下:
i 是一個整數,&i 是 i 的地址,所以 d 的類型推斷結果是一個整型指針;ci 是一個整型常量,&ci 是一個整型常量的地址,所以 e 的類型推斷結果是一個指向整型常量的指針;ci 是一個整型常量,所以 g 的類型推斷結果是一個整型常量引用。因為 d 和 e 都是指針,所以不能直接用字面值常量為其賦值;g 綁定到了整型常量,所以不能修改它的值。
2.34
【出題思路】
本題旨在考察 auto 說明符與復合類型、常量混合使用時的各種情形。
【解答】
基于上一個練習中的變量和語句編寫的程序如下所示:
#include <iostream>using namespace std; int main(int argc, char *argv[]) {int i = 0, &r = i;auto a = r;const int ci = i, &cr = ci;auto b = ci;auto c = cr;auto d = &i;auto e = &ci;const auto f = ci;auto &g = ci;cout << a << " " << b << " " << c << " " << d << " " << e << " " << g << std::endl;a = 42;b = 42;c = 42;// d = 42;// e = 42;// g = 42;cout << a << " " << b << " " << c << " " << d << " " << e << " " << g << std::endl;return 0; } // 運行結果 0 0 0 0x7ffee3e7088c 0x7ffee3e70878 0 42 42 42 0x7ffee3e7088c 0x7ffee3e70878 0Process finished with exit code 02.35
【出題思路】
本題旨在考察 auto 說明符與復合類型、常量混合使用時的各種情形。
【解答】
由題意可知,i 是一個整型常量,j 的類型推斷結果是整數,k 的類型推斷結果是整型常量,p 的類型推斷結果指向整型常量的指針,j2 的類型推斷結果是整型常量,k2 的類型推斷結果是整型常量的引用。
- i is const int
- j is int
- k is const int &
- p is const int *
- j2 is const int
- k2 is const int &
用于驗證的程序是:
#include <iostream> #include <typeinfo>int main() {const int i = 42;auto j = i;const auto &k = i;auto *p = &i;const auto j2 = i, &k2 = i;std::cout << typeid(i).name() << std::endl; // istd::cout << typeid(j).name() << std::endl; // istd::cout << typeid(k).name() << std::endl; // istd::cout << typeid(p).name() << std::endl; // PKistd::cout << typeid(j2).name() << std::endl; // istd::cout << typeid(k2).name() << std::endl; // istd::cout << std::endl;std::cout << std::boolalpha; // 接下來的輸出把 bool 值顯示成 true/falsestd::cout << "i and j have same type? "<< std::is_same<decltype(i), decltype(j)>::value << std::endl;std::cout << "i and k have same type? "<< std::is_same<decltype(i), decltype(k)>::value << std::endl;std::cout << "i and j2 have same type? "<< std::is_same<decltype(i), decltype(j2)>::value << std::endl;std::cout << "j and j2 have same type? "<< std::is_same<decltype(j), decltype(j2)>::value << std::endl;std::cout << "k and k2 have same type? "<< std::is_same<decltype(k), decltype(k2)>::value << std::endl;return 0; } // 運行結果 // print i means int, and PKi means pointer to const int. i i i PKi i ii and j have same type? false i and k have same type? false i and j2 have same type? true j and j2 have same type? false k and k2 have same type? trueProcess finished with exit code 02.36
【出題思路】
本題旨在考察 decltype 與引用的關系。對于 decltype 所用的表達式來說,如果變量名加上一對括號,得到的類型與不加括號時會有不同。具體來說,如果 decltype 使用的是一個不加括號的變量,則得到的結果就是該變量的類型;如果給變量加上了一層或多層括號,編譯器就會把它當成一個表達式,從而推斷得到引用類型。
【解答】
在本題的程序中,初始情況下 a 的值是 3,b 的值是4。decltype(a) c = a; 使用的是一個不加括號的變量,因此 c 的類型就是 a 的類型,即該語句等同于 int c = a;,此時 c 是一個新整型變量,值為 3。decltype((b)) d = a; 使用的是一個加了括號的變量,因此 d 的類型是引用,即該語句等同于 int &d = a;,此時 d 是變量 a 的別名。
執行 ++c; ++d; 時,變量 c 的值自增為 4,因為 d 是 a 的別名,所以 d 自增 1 意味著 a 的值變成了 4。當程序結束時,a、b、c、d 的值都是 4。
#include <iostream> #include <typeinfo>int main() {int a = 3, b = 4;decltype(a) c = a;decltype((b)) d = a;++c;++d;std::cout << typeid(c).name() << std::endl; // intstd::cout << typeid(d).name() << std::endl; // int &std::cout << c << std::endl; // 4std::cout << d << std::endl; // 4return 0; }2.37
【出題思路】
decltype 的參數既可以是普通變量,也可以是一個表達式。當參數是普通變量時,推斷出的類型就是該變量的類型;當參數是表達式時,推斷出的類型是引用。
【解答】
根據 decltype 的上述性質可知,c 的類型是 int,值為 3;表達式 a = b 作為decltype 的參數,編譯器分析表達式并得到它的類型作為 d 的推斷類型,但是不實際計算該表達式,所以 a 的值不發生改變,仍然是 3;d 的類型是 int &,d 是 a 的別名,值是 3;b 的值一直沒有發生改變,為 4。
2.38
【出題思路】
auto 和 decltype 是兩種類型推斷的方式,本題旨在考察二者的區別和聯系。
【解答】
auto 和 decltype 的區別主要有三方面:
一個用以說明的示例如下所示:
#include <iostream> #include <typeinfo>int main() {int a = 3;auto c1 = a;decltype(a) c2 = a;decltype((a)) c3 = a;const int d = 5;auto f1 = d;decltype(d) f2 = d;std::cout << typeid(c1).name() << std::endl; // intstd::cout << typeid(c2).name() << std::endl; // intstd::cout << typeid(c3).name() << std::endl; // int &std::cout << typeid(f1).name() << std::endl; // intstd::cout << typeid(f2).name() << std::endl; // const intc1++;c2++;c3++;f1++;// f2++; // 錯誤:f2 是整型常量,不能執行自增操作std::cout << a << " " << c1 << " " << c2 << " " << c3 << " " << f1<< " " << f2 << std::endl;return 0; } // 運行結果 i i i i i 4 4 4 4 6 5Process finished with exit code 0對于第一組類型推斷來說,a 是一個非常量整數,c1 的推斷結果是整數,c2 的推斷結果也是整數,c3 的推斷結果由于變量 a 額外加了一對括號所以是整數引用。c1、c2、c3 依次執行自增操作,因為 c3 是變量的 a 的別名,所以 c3 自增等同于 a 自增,最終 a、c1、c2、c3 的值都變為 4。
對于第二組類型推斷來說,d 是一個常量整數,含有頂層 const,使用 auto 推斷類型自動忽略掉頂層 const,因此 f1 的推斷結果是整數;decltype 則保留頂層 const,所以 f2 的推斷結果是整數常量。f1 可以正常執行自增操作,而常量 f2 的值不能被改變,所以無法自增。
附:
- What is the difference between auto and decltype(auto) when returning from a function?
- decltype vs auto
2.39
#include <iostream>struct Foo { /* 此處為空 */} // 注意:沒有分號 int main() {return 0; } // 編譯無法通過 main.cpp:3:33: error: expected ';' after struct【出題思路】
本題旨在考查類定義的語法規范,尤其要注意類體結束之后的分號必不可少。
【解答】
該程序無法編譯通過,原因是缺少了一個分號。因為類體后面可以緊跟變量名以示對該類型對象的定義,所以在類體右側表示結束的花括號之后必須寫一個分號。
稍作修改,程序就可以編譯通過了。
#include <iostream>struct Foo { /* 此處為空 */}; int main() {return 0; } Process finished with exit code 02.40
【出題思路】
類的設計源于實際應用,設計 Sales_data 類的關鍵是理解在銷售過程中應該包含哪些數據元素,同時為每個元素設定合理的數據類型。
【解答】
原書中的程序包含 3 個數據成員,分別是 bookNo(書籍編號)、units_sold(銷售量)、revenue(銷售收入),新設計的 Sales_data 類細化了銷售收入的計算方式,在保留 bookNo 和 units_sold 的基礎上,新增了 sellingprice(零售價、原價)、saleprice(實售價、折扣價)、discount(折扣),其中 discount = saleprice / sellingprice。
#include <iostream>struct Sales_data {std::string bookNo; // 書籍編號unsigned units_sold = 0; // 銷售量double sellingprice = 0.0; // 零售價double saleprice = 0.0; // 實售價double discount = 0.0; // 折扣 }; int main() {return 0; }2.41
Sales_data.h
#ifndef SALESDATA_H // we're here only if SALESDATA_H has not yet been defined #define SALESDATA_H// Definition of Sales_data class and related functions goes here #include <iostream> #include <string>// 頭文件不應包含 using 聲明 // using namespace std;class Sales_data {// 友元函數friend std::istream &operator>>(std::istream &, Sales_data &);// 友元函數friend std::ostream &operator<<(std::ostream &, const Sales_data &);// 友元函數friend bool operator<(const Sales_data &, const Sales_data &);// 友元函數friend bool operator==(const Sales_data &, const Sales_data &);public: // 構造函數的 3 種形式Sales_data() = default;Sales_data(const std::string &book) : bookNo(book) {}Sales_data(std::istream &is) { is >> *this; }Sales_data &operator+=(const Sales_data &);std::string isbn() const { return bookNo; }private:std::string bookNo; // 書籍編號,隱式初始化為空串unsigned units_sold = 0; // 銷售量,顯式初始化為 0double sellingprice = 0.0; // 原始價格,顯式初始化為 0.0double saleprice = 0.0; // 實售價格,顯式初始化為 0.0double discount = 0.0; // 折扣,顯式初始化為 0.0 };inline bool compareIsbn(const Sales_data &lhs, const Sales_data &rhs) {return lhs.isbn() == rhs.isbn(); }Sales_data operator+(const Sales_data &, const Sales_data &);inline bool operator==(const Sales_data &lhs, const Sales_data &rhs) {return lhs.units_sold == rhs.units_sold &&lhs.sellingprice == rhs.sellingprice &&lhs.saleprice == rhs.saleprice &&lhs.isbn() == rhs.isbn(); }inline bool operator!=(const Sales_data &lhs, const Sales_data &rhs) {return !(lhs == rhs); // 基于運算符 == 給出 != 的定義 }Sales_data &Sales_data::operator+=(const Sales_data &rhs) {units_sold += rhs.units_sold;saleprice = (rhs.saleprice * rhs.units_sold + saleprice * units_sold)/ (rhs.units_sold + units_sold);if (sellingprice != 0)discount = saleprice / sellingprice;return *this; }Sales_data operator+(const Sales_data &lhs, const Sales_data &rhs) {Sales_data ret(lhs); // 把 lhs 的內容拷貝到臨時變量 ret 中,這種做法便于運算ret += rhs; // 把 rhs 的內容加入其中return ret; // 返回 ret }std::istream &operator>>(std::istream &in, Sales_data &s) {in >> s.bookNo >> s.units_sold >> s.sellingprice >> s.saleprice;if (in && s.sellingprice != 0)s.discount = s.saleprice / s.sellingprice;elses = Sales_data(); // 輸入錯誤,重置輸入的數據return in; }std::ostream &operator<<(std::ostream &out, const Sales_data &s) {out << s.isbn() << " " << s.units_sold << " "<< s.sellingprice << " " << s.saleprice << " " << s.discount;return out; }#endif重寫練習 1.20
main.cpp
#include <iostream> #include "Sales_data.h"using namespace std;int main() {Sales_data book;std::cout << "請輸入銷售記錄:" << std::endl;// 輸入格式:ISBN 售出本數 原始價格 實際售價while (std::cin >> book) {std::cout << "ISBN、售出本數、原始價格、實售價格、折扣為:" << book << std::endl;}return 0; } // 運行結果 請輸入銷售記錄: 0-201-78345-X 3 20.00 19.00 ISBN、售出本數、原始價格、實售價格、折扣為:0-201-78345-X 3 20 19 0.95 0-202-78345-X 4 30.00 29.00 ISBN、售出本數、原始價格、實售價格、折扣為:0-202-78345-X 4 30 29 0.966667重寫練習 1.21
main.cpp
#include <iostream> #include "Sales_data.h"using namespace std;int main() {Sales_data trans1, trans2;std::cout << "請輸入兩條 ISBN 相同的銷售記錄:" << std::endl;std::cin >> trans1 >> trans2;if (compareIsbn(trans1, trans2))std::cout << "匯總信息:ISBN、銷售本數、原始價格、實售價格、折扣為:"<< "\n" << trans1 + trans2 << std::endl;elsestd::cout << "兩條銷售記錄的 ISBN 不同" << std::endl;return 0; } // 運行結果 請輸入兩條 ISBN 相同的銷售記錄: 0-201-78345-X 3 20.00 19.00 0-201-78345-X 5 20.00 18.00 匯總信息:ISBN、銷售本數、原始價格、實售價格、折扣為: 0-201-78345-X 8 20 18.6154 0.930769Process finished with exit code 0重寫練習 1.22
main.cpp
#include <iostream> #include "Sales_data.h"using namespace std;int main() {Sales_data total, trans;std::cout << "請輸入幾條 ISBN 相同的銷售記錄:" << std::endl;// 每次輸入要求 ISBN 相同,售出本數隨便,原始價格相同,實售價格隨便if (std::cin >> total) {while (std::cin >> trans)if (compareIsbn(total, trans))total += trans;else { // ISBN 不同std::cout << "當前書籍 ISBN 不同" << std::endl;break;}std::cout << "有效匯總信息:ISBN、售出本數、原始價格、實售價格、折扣為:"<< "\n" << total << std::endl;} else {std::cout << "沒有數據" << std::endl;return -1;}return 0; } // 運行結果(實售價格為均值,具體可查看 Sales_item.h 運算符 += 重載的實現 請輸入幾條 ISBN 相同的銷售記錄: 0-201-78345-X 3 20.00 19.00 0-201-78345-X 5 20.00 29.00 0-202-78345-Y 2 200.00 199.00 當前書籍 ISBN 不同 有效匯總信息:ISBN、售出本數、原始價格、實售價格、折扣為: 0-201-78345-X 8 20 22.8462 1.14231Process finished with exit code 0重寫練習 1.23
#include <iostream> #include "Sales_data.h"using namespace std;int main() {Sales_data trans1, trans2;int num = 1; // 記錄當前書籍的銷售記錄總數std::cout << "請輸入若干銷售記錄:" << std::endl;if (std::cin >> trans1) {while (std::cin >> trans2) {if (compareIsbn(trans1, trans2)) // ISBN 相同num++;else { // ISBN 不同std::cout << trans1.isbn() << " 共有 "<< num << " 條銷售記錄" << std::endl;trans1 = trans2;num = 1;}std::cout << trans1.isbn() << " 共有 "<< num << " 條銷售記錄" << std::endl;}} else {std::cout << "沒有數據" << std::endl;return -1;}return 0; } // 運行結果 請輸入若干銷售記錄: 0-201-78345-X 3 20.00 19.00 0-201-78345-X 2 20.00 18.00 0-201-78345-X 共有 2 條銷售記錄 0-201-78345-X 5 20.00 19.00 0-201-78345-X 共有 3 條銷售記錄 0-202-78345-Y 3 220.00 129.00 0-201-78345-X 共有 3 條銷售記錄 0-202-78345-Y 共有 1 條銷售記錄 0-201-78345-X 7 20.00 19.99 0-202-78345-Y 共有 1 條銷售記錄 0-201-78345-X 共有 1 條銷售記錄注:1.6 節練習與重寫練習 1.23 相同
2.42
2.41 已實現該題功能
轉載于:https://www.cnblogs.com/kafffka/p/10963854.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
- 上一篇: 为什么JS是单线程?JS中的Event
- 下一篇: 网路编程 客户端