基本內置類型#
不同的編譯器和平台可能會有差異,下表為在 Windows_X64 環境下使用 MinGw GCC 9.2.0 32_bit 為例:
類型 | 含義 | 最小尺寸 | 範圍 |
---|---|---|---|
bool | 布爾類型 | 8bits | true 或 false |
char | 字符型 | 8bits | -2^7 ~ 2^7-1 |
wchar_t | 寬字符 | 16bits | 0 ~ 2^16 - 1 |
char16_t | Unicode 字符 | 16bits | 0 ~ 2^16 - 1 |
char32_t | Unicode 字符 | 32bits | 0 ~ 2^32 - 1 |
short | 短整型 | 16bits | -2^15 ~ 2^15-1 |
int | 整型 | 32bits | -2^31 ~ 2^31-1 |
long | 長整型 | 32bits | -2^31 ~ 2^31-1 |
long long | 長整型 | 64bits | -2^63 ~ 2^63-1 |
float | 單精度浮點型 | 32bits | 大約 7 位有效數字 |
double | 雙精度浮點型 | 64bits | 大約 15 位有效數字 |
long double | 擴展精度浮點型 | 96bits | 更高的有效數字 |
可以在 IDE 中編譯運行以下代碼查看本機算數類型的尺寸
// Windows_X64環境下MinGw GCC 9.2.0 32_bit為例
// 1Byte = 8bit
#include<iostream>
using namespace std;
int main(){
cout<< "int * "<<sizeof(int*)*8<<endl; // 32 bits
cout<< "bool "<<sizeof(bool)*8<<endl; // 8 bits
cout<< "char "<<sizeof(char)*8<<endl; // 8bits
cout<< "wchar_t "<<sizeof(wchar_t)*8<<endl; // 16bits
cout<< "char16_t "<<sizeof(char16_t )*8<<endl; // 16bits
cout<< "char32_t "<<sizeof(char32_t )*8<<endl; // 32bits
cout<< "short "<<sizeof(short )*8<<endl; // 16bits
cout<< "int "<<sizeof(int )*8<<endl; // 32bits
cout<< "long "<<sizeof(long )*8<<endl; // 32bits
cout<< "long long"<<sizeof(long long)*8<<endl; // 64bits
cout<< "float "<<sizeof(float)*8<<endl; // 32bits
cout<< "double "<<sizeof(double)*8<<endl; // 64bits
cout<< "long double "<<sizeof(long double)*8<<endl; //96bits
return 0;
}
內置類型的機器實現#
字節 (Byte) 是可尋址的最小內存塊,1 byte = 8 bit (比特)
字 (Word) 是存儲的基本單元,1 Word = 4 字節或 8 字節
內存中每個字節與一個數字(被稱為地址 address)關聯,我們能夠使用某個地址來表示從這個地址開始的大小不同的比特串,如地址為 736424 的那個字或者地址 736424 的那個字節。但是必須知道存儲在某地址的數據類型,才能賦予內存該地址明確含義,類型決定數據所占比特數,以及如何解釋這些比特的內容。
帶符號型和無符號型#
其它整型
-
除去布爾型和擴展字符型外,其它整型可劃分為帶符號的和無符號的
-
int, short, long, long long 帶符號
-
類型名前添加
unsigned
得到無符號類型 -
如
unsigned long
-
unsigned int 可簡寫為 unsigned)
字符型
-
分三種 char, signed char, unsigned char
-
char 表現為帶符號還是無符號的,由編譯器決定
如何選擇類型#
- 數值不可能是負數時,比如年齡,長度等等,選用
無符號類型
; - 整數計算式使用
int
類型,一般long
的大小和int
一樣,而short
顯得太小。如果處理較大的整數,應該選擇long long
; - 算數表達式中不要使用
bool
或char
,符號容易出問題; - 浮點數據運算使用
double
;
類型轉換#
類型所能表示值的範圍決定轉換過程:
- 非布爾型賦給布爾型,初始值為 0 則結果為 false, 否則為 true。
#include<iostream>
using namespace std;
int main() {
int i = 42;
if (i) {
i = 0;
}
cout << "i = " << i << endl; // i=0
return 0;
}
- 布爾型賦給非布爾型,初始值為 false 結果為 0,初始值為 true 結果為 1。
- 浮點數賦給整型,近似處理,保留小數點前的部分。
- 整型賦給浮點型,小數部分記 0,若整型超過浮點型容量則精度可能有損失。
- 超出範圍值賦給無符號數,結果是初始值對無符號類型表示的最大值取模余數。
- 超出範圍值賦給有符號數,結果未定義。
//test01.cpp
#include<iostream>
using namespace std;
int main() {
int i = 42;
if (i) {
i = 0;
}
cout << "i = " << i << endl; // i=0
return 0;
}
//test02.cpp
#include<iostream>
int main() {
unsigned u = 10;
int i = -42;
std::cout << i + i << std::endl; //-84
std::cout << u + i << std::endl; //4294967264
return 0;
}
//test03.cpp
#include<iostream>
int main() {
for (int i = 10; i >= 0; --i) {
std::cout << i << std::endl;
}
return 0;
} //正常輸出
//test04.cpp
#include<iostream>
int main() {
for (unsigned u = 10; u >= 0; --u) { //無符號數不會小於0
std::cout << u << std::endl; //死循環,ctrl+c退出
}
return 0;
}
//test05.cpp
#include<iostream>
int main() {
unsigned u = 10, u2 = 42;
std::cout << u2 - u << std::endl; //32
std::cout << u - u2 << std::endl; //4294967264
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
return 0;
}
字面值常量 (Literal Constants)#
- 整型字面值常量:
- 十進制 :42
- 八進制: 052 (以 0 開頭)
- 十六進制: 0x2A 或 0X2A (以 0x 或 0X 開頭)
- 二進制:
0b101010
或0B101010
(以 0b 或 0B 開頭)
錯誤示範:
#include<iostream>
int main() {
int month = 9, day = 7;
int month1 = 09, day1 = 07; //以0開頭會當成8進制賦值
// [Error] invalid digit "9" in octal constant
return 0;
}
- 浮點型字面值常量:
- 十進制:
3.14
或2.0
- 科學計數法:
3.0e8
(表示 3.0 乘以 10 的 8 次方)
- 字符型字面值常量:
- 字符:
'A'
或'x'
- 轉義字符:
'\n'
(表示換行符)或'\\'
(表示反斜杠)
轉義序列:
換行'\n' 橫向制表符'\t' 響鈴'\a'
縱向制表符'\v' 退格符'\b' 雙引號'\"'
反斜線'\\' 問號'\?' 單引號'\'' 回車'\r'
泛化轉義序列:'\x'跟1個多個16進制數,或'\'後跟1、2、3個8進制數
'\7'響鈴 '\12'換行 '\40'空格
'\0'空字符 '\115'字符M '\x4d'字符M
//test05.cpp
#include<iostream>
int main() {
std::cout << '\n'; //換行
std::cout << "\tHi!\n"; //制表符,Hi!,換行
std::cout << "\v\?\abc\b\n"; //縱向制表符,?,響鈴,bc,退格,換行
std::cout << "Hi \x4dO\115!\n"; //Hi MOM!,換行
std::cout << '\115' << '\n'; //M,換行
return 0;
}
- 字符串字面值常量:字符串字面值常量表示一串字符,使用雙引號括起來。例如:
- 字符串:
"Hello, World!"
字符串實際類型是字符數組,結尾添加 '\0' 字符 字符串型實際上是常量字符構成的數組,結尾處以 '\0' 結束,所以字符串類型實際上長度比內容多 1。
-
布爾字面值常量:布爾字面值常量表示真或假,可以是
true
或false
。 -
指針字面值: nullptr
-
指定字面值類型:
字符和字符串字面值:
前綴 | 含義 | 類型 |
---|---|---|
u | Unicode16 字符 | char16_t |
U | Unicode32 字符 | char32_t |
L | 寬字符 | wchar_t |
u8 | UTF-8 (字符串字面常量) | char |
整型字面值:
後綴 | 最小匹配類型 |
---|---|
u or U | unsigned |
l or L | long |
ll or LL | long long |
浮點型字面值:
後綴 | 類型 |
---|---|
f or F | float |
l or L | long double |
變量#
變量提供一個具名的、可供程序操作的存儲空間。 C++ 中變量和對象一般可以互換使用
define#
-
定義形式:類型說明符(type specifier) + 一個或多個變量名組成的列表;
-
初始化(initialize):對象在創建時獲得了一個特定的值。
-
- 初始化不是賦值 ,初始化 = 創建變量 + 賦予初始值 ,賦值 = 擦除對象的當前值 + 用新值代替
-
列表初始化:使用花括號 {}
-
若列表初始化且初始值存在丟失信息的風險,則編譯器將報錯。
int a{3.14}; // error: narrowing conversion from ‘double’ to ‘int’
-
默認初始化:定義時沒有指定初始值會被默認初始化;
-
在函數體內部的內置類型變量將不會被初始化。
-
類的對象如果沒有顯示初始化,其值由類定義。
#include<iostream>
std::string global_str; //定義全局變量global_str,初值為空字符串
int global_int; //定義全局變量global_int,初值為0
int main() {
int local_int; //局部變量未初始化,初值未定義
std::string local_str;
//local_str 是 string類的對象,它的值由類確定,為空字符串。
return 0;
}
建議初始化每一個內置類型變量!
變量聲明與定義的聯系#
把程序拆分成多個邏輯部分來編寫 ,C++ 支持分離式編譯機制,程序分割為若干文件,每個文件可被獨立編譯,為了支持分離式編譯,C++ 將聲明和定義區分開。
聲明使得名字為程序所知,定義負責創建與名字關聯的實體。
聲明規定了變量類型和名字。定義申請存儲空間,也可能為變量賦初值。想聲明一個變量而非定義它,在變量名前加關鍵字extern
, 不要顯示初始化變量。
變量只能被定義一次,但是可以多次聲明,定義只出現在一個文件中,其他文件使用該變量時需要對其聲明。
extern int i; //聲明i而非定義
int j; //聲明並定義j
// 包含了顯示初始化的聲明,就變成了定義:
extern double pi = 3.14; //定義
// 函數體內部,試圖初始化一個extern關鍵字標記的變量,將引發錯誤。
extern int ix = 1024; //定義
int iy; //定義
extern int iz; //聲明
變量標識符與命名規範#
標識符:
- 由字母、數字、下劃線組成,必須以字母或下劃線開頭。
-
長度沒有限制。
-
大小寫寫敏感。
-
保留名字不能用作標識符。
-
用戶自定義標識符
- 不能連續出現兩個下劃線
- 不能以下劃線緊跟大寫字母開頭
- 定義在函數體外的標識符不能以下劃線開頭
- 使用小駝峰命名法
//c++關鍵字
alignas continue friend register true
alignof decltype goto reinterpret_cast try
asm default if return typedef
auto delete inline short typeid
bool do int signed typename
break double long sizeof union
case dynamic_cast mutable static unsigned
catch else namespace static_assert using
char enum new static_cast virtual
char16_t explicit noexcept struct void
char16_t export nullptr switch volatile
class extern operator template wchar_t
const false private this while
constexpr float protected thread_local
const_cast for public throw
//c++操作符替代名
and bitand compl not_eq or_eq xor_eq
and_eq bitor not or xor
命名規範:
- 體現實際含義
- 變量名和函數名使用小駝峰命名法,如
myVariable
- 自定義類名大寫字母開頭,如
Sales_item
- 若標識符有多個單詞組成,單詞之間要有明顯區分,如
student_loan
,studentLoan
, 不要用studentloan
int double = 3.14; //error
int _;
int catch-22; //error
int 1_or_2 = 1; //error
double Double = 3.14;
名字的作用域(namescope),以 {} 分隔
- 全局作用域
- 塊作用域
第一次使用變量時再定義它,更容易找到,賦予比較合理的初始值。
//scope.cpp
#include <iostream>
// Program for illustration purposes only: It is bad style for a function
// to use a global variable and also define a local variable with the same
int reused = 42; // reused has global scope
int main() {
int unique = 0; // unique has block scope
// output #1: uses global reused; prints 42 0
std::cout << reused << " " << unique << std::endl;
int reused = 0; // new, local object named reused hides global reused
// output #2: uses local reused; prints 0 0
std::cout << reused << " " << unique << std::endl;
//此時全局變量被同名的局部變量覆蓋
// output #3: explicitly requests the global reused; prints 42 0
std::cout << ::reused << " " << unique << std::endl;
//使用全局變量
return 0;
}
嵌套的作用域:
同時存在全局和局部變量時,已定義局部變量的作用域中可用::
顯式訪問全局變量。用到全局變量時,盡量不使用重名的局部變量。
#include <iostream>
int i = 42;
int main() {
int i = 100;
int j = i;
//j的值是100,局部變量i覆蓋了全局變量i
std::cout << j << std::endl; //100
return 0;
}
#include <iostream>
int main() {
//下面的程序是否合法?如果合法,它將輸出什麼?
int i = 100, sum = 0;
for (int i = 0; i != 10; ++i)
sum += i; //此時的變量i僅在for循環內生效
std::cout << i << " " << sum << std::endl;
//合法輸出:100 45
return 0;
}
左值和右值#
-
左值(l-value)可以出現在賦值語句的左邊或者右邊,比如變量;
-
右值(r-value)只能出現在賦值語句的右邊,比如常量。
引用#
一般說的引用是指的左值引用
引用:引用是為對象起了另外一個名字,引用類型:引用(refer to)另外一種類型。
int &refVal = val;
- 引用必須初始化:
int &refVal2; //報錯,引用必須被初始化
- 引用和它的初始值是綁定 bind在一起的,而不是拷貝。一旦定義就不能更改綁定為其他的對象
- 引用類型要與綁定對象匹配
- 引用只能綁定在對象上,不能與字面值或表達式計算結果綁定
更詳細的解釋可參考:C++ 中的引用
指針#
是一種 "指向(point to)" 另外一種類型的復合類型。 本身就是一個對象,無需定義時賦值
定義指針類型#
int *ip1,*ip2;
//ip1和ip2都是int型對象指針
double dp,*dp2
//dp2 是指向double型對象的指針,dp是double類型
獲取對象的地址#
指針存放某個對象的地址。 獲取對象的地址:
int i=42;
int *p = &i;
&
是取地址符。 指針的類型與所指向的對象類型必須一致(均為同一類型 int、double 等)
int ival = 42;
int *p = &ival; //p存放ival的地址,p是指向val的指針
double dval;
double *pd = &dval;//正確
double *pd2 = pd;//正確
int *pi = pd;//錯誤,類型不匹配
pi = &dval;//錯誤,試圖把double對象的地址賦給int指針
指針的值 (即地址) 的四種狀態:
- 指向一個對象;
- 指向緊鄰對象的下一個位置;
- 空指針;
- 無效指針。
對無效指針的操作均會引發錯誤;
第二種和第三種雖為有效的,訪問指針對象的行為後果無法預計。
指針訪問對象#
#include <iostream>
int main() {
//如果指針指向一個對象,使用解引用符(操作符'*')來訪問對象
int ival = 42;
int *p = &ival;
std::cout << *p;// 輸出p指針所指對象的數據,
*p = 0;
std::cout << ival; // 0
return 0;
}
解引用操作僅適用於確實指向某個對象的有效指針。
空指針#
int *p1=nullptr; //使用空指針。
int *p2 = 0;//p2 初始化為字面常量0
// include<cstdlib>
int *p3 = NULL;//int *p3=0 NULL預處理變量
int zero = 0;
p1 = zero; //錯誤,不能把int變量直接賦給指針
建議初始化所有指針!
指針與引用的區別#
- 引用本身並非一個對象,引用定義後就不能綁定到其他的對象上;
- 指針並沒有此限制
//說明指針和引用的主要區別
//引用是另一個對象的別名,而指針本身就是一個對象。
//引用必須初始化,並且一旦定義了引用就無法再綁定到其他對象。
//而指針無須在定義時賦初值,也可以重新賦值讓其指向其他對象。
int i = 42;
int *pi = 0;//pi 初始化,沒有指向任何對象
int *pi2 = &i;//pi2指向i
int *pi3;//如果pi3定於於塊內,pi3值無法確定
pi3 = pi2;//pi3與pi2指向同一對象i
pi2 = 0;//pi2 不指向任何對象
//賦值語句永遠改變的是左側的對象。
pi = &ival;//pi指向了ival
*pi = 0;//ival值改變,pi沒改變
//請解釋下述定義。在這些定義中有非法的吗?如果有,為什麼?
int i = 0;
(a) double* dp = &i;
(b) int *ip = i;
(c) int *p = &i;
(a): 非法。不能將一個指向 double 的指針指向 int 。
(b): 非法。不能將 int 變量賦給指針。
(c): 合法。
其他指針操作#
int ival = 1024;
int *p1=0;
int *p2=&ival;
//指針值0,條件取false
if(p1)
//...
//p2指針非0,條件值是true
if(p2)
//...
//指針比較
if(p1==p2)
//...
非法指針會引發不可預計的後果!
//一條語句定義不同類型變量
int i=1024,
*p=&i,&r=i;
int* p;//合法但容易產生誤導
int* p1,p2;//p1是指針,p2是int
int *p1,*p2;//p1,p2都是指針
或
int *p1;
int *p2;
指向指針的指針#
#include <iostream>
using namespace std;
int main() {
int ival = 1024;
int *pi = &ival; //pi指向int型
int **ppi = π //ppi指向int型指針
// 解引用
cout << "The value of ival\n"
<< "direct value: " << ival << "\n"
<< "indirect value: " << *pi << "\n"
<< "doubly indirect value: " << **ppi
<< endl;
return 0;
//The value of ival
//direct value: 1024
//indirect value: 1024
//doubly indirect value: 1024
}
指向指針的引用#
從右向左閱讀 r 的定義
int i=42;
int *p;
int *&r = p; //r是對指針p的引用
r = &i; //p指向i
*r=0; //i=0
說明下列變量的類型和值。
(a) int* ip, i, &r = i;
(b) int i,*ip = 0;
(c) int* ip, ip2;
(a): ip 是一個指向 int 的指針, i 是一個 int, r 是 i 的引用。
(b): i 是 int , ip 是一個空指針。
(c): ip 是一個指向 int 的指針, ip2 是一個 int。
void 指針#
void
指針可以存放任意對象的地址。因無類型,僅操作內存空間,不能直接操作void *
指針所指的對象。
double obj = 3.14,
*pd = &obj;
void *pv = &obj; //obj 可以是任意類型的對象
pv = pd;//void* 可存任何類型,使用時再進行強制類型轉換即可
CONST 限定符#
const:定義一些不能被改變值的變量, const 對象一旦創建值不再改變,所以必須初始化,且不能被改變。
const int bufSize = 512;
bufSize = 512;//error,試圖寫值到const對象
const int i= getSize();
const int j=42;
const int k;//error,k未初始化常量
int i=42;
const int ci=i;
int j=ci;
CONST 對象僅在文件內有效:
-
當多個文件出現同名的 const 變量,等同於在不同文件分別定義獨立變量。
-
要想在多個文件中使用 const 變量共享,定義和聲明都加 extern 關鍵字即可。
//file_1.cc定義初始化常量,能被其它文件訪問
extern const int bufSize = fcn();
//file_1.h 頭文件
extern const int bufSize ;//與file_1.cc中定義的bufSize是同一個
const int buf; // 不合法, const 對象必須初始化
int cnt = 0; // 合法
const int sz = cnt; // 合法
++cnt; ++sz; // 不合法, const 對象不能被改變
const 的引用#
- 把引用綁定到 const 對象上,稱之為對常量的引用。
- 與普通引用不同,對常量的引用不能被用作修改它所綁定的對象
const int ci=1024;
const int &r1=ci;
r1=42; //error r1是對常量的引用
int &r2 = ci; //error,試圖讓非常量引用指向一個常量對象
初始化和對 const 的引用#
引用類型必須與其所用對象類型一致
- 例外:初始化常量引用時,允許用任意表達式做初始值,只要表達式的結果能轉換成引用的類型。
- 允許為一個常量引用綁定非常量的對象,字面值,一般表達式
int i=42;
const int &r1=i; //允許將const int&綁定到int對象
const int &r2=42;//r2 是個常量引用
const int &r3=r1*2;//r3是個常量引用
int &r4=r1*2;//error,r4是一个普通的非常量引用。
//當一個常量引用被綁定到另外一種類型發生了什麼?
double dval = 3.14;
const int &ri=dval;
//編譯器將上述代碼改為如下形式
const int temp=dval;//生成臨時整型變量
const int &ri=temp;//讓ri綁定這個臨時變量
- 臨時量 (temporary) 對象:當編譯器需要一個空間來暫存表達式結果的值時,臨時創建的一個未命名的變量
- 對臨時量的引用是錯誤行為:
double dval = 3.14;
int &ri = dval; //error
對 const 的引用很可能引用一個非 const 對象
- 常量引用僅對引用可參與的操作做出限定,對引用的對象本身是不是常量未做限定
int i = 42;
int &r1 = i; //r1綁定i
const int &r2 = i; //r2綁定i,不允許通過r2修改i
r1 = 0; //ok,r1非常量
r2 = 0; //error,r2是一個常量引用
和常量引用一樣,指向常量的指針同理也沒有規定所指對象必須是常量。僅要求不能通過指針改變對象值。
const 指針#
常量指針必須初始化,允許把指針定義成常量:
int errNumb=0;
int *const curErr=&errNumb;// curErr是常量,一直指向errNumb(地址不能變)
const double pi=3.1415;
const double *const pip= π//pip是常量,*pip也是常量,
//pip 是指向常量對象的常量指針 (值與地址均不能更改)
*pip = 2.72;//error *pip 是常量
if(*curErr){
errorHandler();
*curErr=0;//ok *curErr不是常量
}
//下面的哪些初始化是合法的?請說明原因。
int i = -1, &r = 0; // 不合法, r 必須引用一個對象
int *const p2 = &i2; // 合法,常量指針
const int i = -1, &r = 0(常量引用); // 合法
const int *const p3 = &i2; // 合法
const int *p1 = &i2; // 合法
const int &const r2; // 不合法, r2 是引用,沒有頂層const
const int i2 = i, &r = i; // 合法
//說明下面的這些定義是什么意思,挑出其中不合法的。
int i,*const cp; // 不合法, const 指針必須初始化
int *p1,*const p2; // 不合法, const 指針必須初始化
const int ic, &r = ic; // 不合法, const int 必須初始化
const int *const p3; // 不合法, const 指針必須初始化
const int *p; // 合法. 一個指針,指向 const int
//假設已有上一個練習中定義的那些變量,則下面的哪些語句是合法的?請說明原因。
i = ic; // 合法, 常量賦值給普通變量
p1 = p3; // 不合法, p3 是const指針,不能賦值給普通指針
p1 = ⁣ // 不合法, 普通指針不能指向常量
const int * const p3 = ⁣ // 合法, p3 是常量指針且指向常量
p3 = ⁣ //此時不合法 p3 常量
int * const p2 = p1; // 合法, 可以將普通指針賦值給常量指針
p2 = p1;//此時不合法 p2是常量
const int ic = *p3; // 合法, 對 p3 取值後是一個 int 然後賦值給 ic
ic = *p3;//此時不合法,ic是常量
頂層 const#
- 頂層 const:指針本身是個常量。
- 底層 const:指針指向的對象是個常量。拷貝時嚴格要求相同的底層 const 資格。
int i=0;
int *const p1=&i;//p1常量,頂層const
const int ci=42;//ci常量,頂層const
const int *p2=&ci;//*p2常量,底層const
const int *const p3=p2;//靠右頂層,靠左底層const
const int &r =ci;//用於聲明引用的const都是底層const
//拷貝時,頂層const不受影響
i=ci;//拷貝ci的值,ci頂層const,無影響
p2=p3;//p2 p3所指對象類型相同 p3頂層const部分不影響
//拷貝時嚴格要求相同的底層const資格,數據類型能夠轉換。
int *p =p3;//error p3包含底層const定義,p沒有
p2=p3;//ok p2 p3都是底層const
p2=&i; //ok int* 轉const int*
int &r=ci;//error 普通int& 不能綁定到int 常量
const int &r2=i;//ok const int& 可以綁定到int
//對於下面的這些語句,請說明對象被聲明成了頂層const還是底層const?
const int v2 = 0; int v1 = v2;
int *p1 = &v1, &r1 = v1;
const int *p2 = &v2,
*const p3 = &i, &r2 = v2;
//v2 是頂層const,p2 是底層const,p3 既是頂層const又是底層const,r2 是底層const。
//假設已有上一個練習中所做的那些聲明,則下面的哪些語句是合法的?
//請說明頂層const和底層const在每個例子中有何體現。
r1 = v2; // 合法, 頂層const在拷貝時不受影響
p1 = p2; // 不合法, p2 是底層const,如果要拷貝必須要求 p1 也是底層const
p2 = p1; // 合法, int* 可以轉換成const int*
p1 = p3; // 不合法, p3 是一個底層const,p1 不是
p2 = p3; // 合法, p2 和 p3 都是底層const,拷貝時忽略掉頂層const
constexpr 和常量表達式#
- 常量表達式:指值不會改變,且在編譯過程中就能得到計算結果的表達式。
//一個對象(表達式)是不是常量表達式由數據類型和初始值共同決定
const int max_file=30; //max_file是常量表達式
const int limit = max_file+1; //limit是常量表達式
int staff_size = 7; //staff_size不是常量表達式
const int sz = get_size(); //運行時獲取,sz是常量表達式。
- C++11 新標準規定,允許將變量聲明為
constexpr
類型以便由編譯器來驗證變量的值是否是一個常量的表達式。
constexpr int mf = 20; //20是常量表達式
constexpr int limit = mf+1; //mf+1是常量表達式
constexpr int sz = get_size(); //取決於get_size()是否是constexpr函數
字面值類型#
常量表達式的值編譯時就得到計算,類型簡單,值容易得到,稱為為 “字面值類型”(literal type)
- 算數類型
- 引用,指針,nullptr,0 或固定位置
指針和 constexpr#
- constexpr 把所定義的對象置為頂層
const int *p=nullptr;//p是一個指向常量的指針
constexpr int *q=nullptr;//q是常量指針
//constexpr把所定義的對象置為頂層
constexpr int *nq=nullptr;//nq是常量指針
int j=0;
constexpr int i=42;//i是常量
//i,j定義在函數外
constexpr const int *p=&i;//p是常量指針,指向整型常量i
constexpr int *p1=&j;//p1是常量指針,指向整數j
處理類型#
- 程序越來越複雜,類型越來越複雜。類型難於拼寫,明確目的含義,搞不清需要什麼類型。
類型別名#
-
傳統別名:使用
typedef
來定義類型的同義詞。typedef double wages; wages hour,weekly; //double hour,weekly;
-
新標準別名:別名聲明(alias declaration):
using SI = Sales_item; //(C++11) SI items;//Sales_item items;
指針,常量和類型別名#
// 對於復合類型(指針等)不能代回原式來進行理解
typedef char *pstring; // pstring是char*的別名
const pstring cstr = 0; // cstr是指向char的常量指針
const pstring *ps;//ps是一個指針,對象是指向char的常量指針
//基本數據類型是指針而不是const char。
AUTO 類型說明符 (C++ 11)#
清楚的知道表達式類型並不容易,因此引入auto
。
- auto 類型說明符:讓編譯器自行推斷類型。顯然 auto 定義變量必須要有初始值。
- 一條生命一句只能有一種基本數據類型:
auto sz = 0, pi =3.14;//錯誤,類型不一致
復合類型,常量和 auto#
int i = 0, &r = i;
auto a = r; //推斷a類型是int
//會忽略頂層const
const int ci = i;
auto b = ci;//推斷int,忽略頂層const
const int ci = 1; const auto f = ci;
//推斷類型是int,如果希望是頂層const需要自己加const
DECLTYPE 類型提示符 (C++ 11)#
decltype
是一個 C++11 中引入的關鍵字,用於獲取表達式的類型。它的語法如下:
decltype(expression)
其中,expression
是一個表達式,可以是變量、函數調用、類型轉換等。
decltype
的作用是根據表達式的類型推斷出一個變量或表達式的類型,並且返回該類型。例如:
int x = 42;
decltype(x) y; // y 的類型為 int
double square(double value) {
return value * value;
}
decltype(square(3.14)) result; // result 的類型為 double
struct Point {
int x;
int y;
};
Point p;
decltype(p.x) a; // a 的類型為 int
在上面的示例中,decltype(x)
返回 int
類型,因為表達式 x
的類型是 int
。decltype(square(3.14))
返回 double
類型,因為表達式 square(3.14)
的返回類型是 double
。decltype(p.x)
返回 int
類型,因為 p.x
是 Point
結構體的一個成員變量。
decltype
在編寫泛型代碼時非常有用,可以根據參數的類型來推斷出函數的返回類型,避免手動指定返回類型。例如:
template<typename T, typename U>
auto multiply(T a, U b) -> decltype(a * b) {
return a * b;
}
int main() {
int x = 5;
double y = 3.14;
auto result = multiply(x, y); // result 的類型為 double
return 0;
}
在上面的示例中,multiply
函數使用 decltype(a * b)
來推斷返回類型,根據傳入的參數類型,返回類型會自動推斷為 double
。
decltype
是一個非常有用的關鍵字,可以讓編譯器根據表達式的類型來推斷出變量或函數的類型,提高代碼的靈活性和可讀性。
-
從表達式的類型推斷出要定義的變量的類型。
-
decltype:選擇並返回操作數的數據類型,不計算表達式的值。
decltype(f()) sum = x; //推斷sum的類型是函數f的返回類型。
- 不會忽略頂層 const。
- 如果對變量加括號,編譯器會將其認為是一個表達式,
decltype((i))
得到結果為引用。 - 賦值是會產生引用的一類典型表達式,引用的類型就是左值的類型。也就是說,如果 i 是 int,則表達式 i=x 的類型是 int&。
自定義數據結構 STRUCT#
儘量不要把類定義和對象定義放在一起。如 struct Student {} xiaoming,xiaofang;
- 類可以以關鍵字 struct 開始,緊跟類名和類體。
- 類數據成員:類體定義類的成員。
- C++11:可以為類數據成員提供一個類內初始值 (in-class initializer)。
下面舉例:
- 定義 Sales_data 類:
//Sales_data.h
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
struct Sales_data {
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
#endif
- 使用 sales_data 類
//Sales_data.cpp
#include <iostream>
#include <string>
#include "Sales_data.h"
int main() {
Sales_data data1, data2;
// code to read into data1 and data2
double price = 0; // price per book, used to calculate total revenue
// read the first transactions: ISBN, number of books sold, price per b
std::cin >> data1.bookNo >> data1.units_sold >> price;
// calculate total revenue from price and units_sold
data1.revenue = data1.units_sold * price;
// read the second transaction
std::cin >> data2.bookNo >> data2.units_sold >> price;
data2.revenue = data2.units_sold * price;
// code to check whether data1 and data2 have the same ISBN
// and if so print the sum of data1 and data2
if (data1.bookNo == data2.bookNo) {
unsigned totalCnt = data1.units_sold + data2.units_sold;
double totalRevenue = data1.revenue + data2.revenue;
// print: ISBN, total sold, total revenue, average price per book
std::cout << data1.bookNo << " " << totalCnt
<< " " << totalRevenue << " ";
if (totalCnt != 0)
std::cout << totalRevenue / totalCnt << std::endl;
else
std::cout << "(no sales)" << std::endl;
return 0; // indicate success
} else { // transactions weren't for the same ISBN
std::cerr << "Data must refer to the same ISBN"
<< std::endl;
return -1; // indicate failure
}
}
編寫自己的頭文件#
- 頭文件通常包含一些只能被定義一次的實體:類、 const 和 constexpr 變量。
預處理器概述:
- 預處理器(preprocessor):確保頭文件多次包含仍能安全工作。
- 當預處理器看到 #include 標記時,會用指定的頭文件內容代替 #include
- 頭文件保護符(header guard):頭文件保護符依賴於預處理變量的狀態:已定義和未定義。
- #indef 已定義時為真
- #inndef 未定義時為真
- 頭文件保護符的名稱需要唯一,且保持全部大寫。養成良好習慣,不論是否該頭文件被包含,都要加保護符。
#ifndef SALES_DATA_H //SALES_DATA_H未定義時為真
#define SALES_DATA_H
strct Sale_data{
...
}
#endif