第二章 變量和基本類型
數據類型是程序的基礎:它告訴我們數據的意義及我們能在數據上執行的操作
2.1 基本數據類型
C++定義了一套包括算術類型和空類型在內的基本數據類型
2.1.1 算術類型
算術類型:
- 整型
- 浮點型
布爾類型的取值是真或者假
帶符號類型和無符號類型:
除去布爾型和擴展的字符之外,其他整型可以劃分爲 帶符號的和無符號的。帶符號的可以表示正數、負數、0,無符號類型僅可以表示大於等於0的值。- 類型 int、short、long、和 long long都是帶符號的
- 類型 unsigned int、unsigned short、unsigned long和 unsigned long long都是無符號的
如何選擇類型
- 當明確知曉數值不可能爲負時,選用無符號類型
- 使用int執行整數運算。如果數值超過int的數值範圍,則選用long long
- 在算術表達式中不要使用char或bool,只有在存放字符或者布爾值時才使用他們
- 執行浮點數運算選用double
2.1.2 類型轉換
對象的類型定義了對象能包含的數據和能參與的運算,其中一種運算被大多數類型支持,就是將對象從一種類型轉換爲另一種類型。
類型所能表示的值的範圍決定了轉換的過程:
- 當我們把一個非布爾類型的算術值付給布爾類型時,初始值是0 則結果爲false,否則結果爲true
#include <iostream>
using namespace std;
int main()
{
int intVal=12;
bool boolVal=intVal;
cout<<"boolVal:"<<boolVal<<endl;
return 0;
}
結果:
boolVal:1
- 當我們把一個布爾值賦給非布爾類型時,初始值爲false 則結果爲0,初始值爲true 則結果爲1
#include <iostream>
using namespace std;
int main()
{
bool boolTrueVal=true;
bool boolFalseVal=false;
int intTrueVal=boolTrueVal;
int intFalseVal=boolFalseVal;
cout<<"intTrueVal:"<<intTrueVal<<endl;
cout<<"intFalseVal:"<<intFalseVal<<endl;
return 0;
}
結果:
intTrueVal:1
intFalseVal:0
- 當我們把一個浮點數賦給整數類型時,進行了近似處理。結果值將僅保留浮點數中小數點之前的部分。
#include <iostream>
using namespace std;
int main()
{
double doubleVal=3.1415926;
int intVal=doubleVal;
cout<<"intVal:"<<intVal<<endl;
return 0;
}
結果:
intVal:3
- 當我們把一個整數值賦值給浮點類型時,小數部分記爲0。如果該整數所佔的空間超過了浮點類型的容量,經度可能有損失。
#include <iostream>
using namespace std;
int main()
{
int intVal=120;
float doubleVal=intVal;
cout<<"doubleVal:"<<doubleVal<<endl;
return 0;
}
結果:
doubleVal:120
這地方在輸出結果的時候給格式化了
- 當我們賦給無符號類型一個超出它表示範圍的值時,結果是初始值對無符號類型表示數值總數取模後的餘數。
#include <iostream>
using namespacestd;
int main()
{
//unsigned int的最大值爲 4294967296
unsigned int intVal;
intVal = 9999999999999999999;
cout << "intVal:" << intVal << endl;
//取模
unsigned intMod = 9999999999999999999 % 4294967296;
cout << "intMod:" << intMod << endl;
return 0;
}
結果:
intVal:2313682943
intMod:2313682943
- 當我們賦給帶符號類型一個超出它表示範圍的值時,結果是錯誤值。(此處應注意)
#include <iostream>
using namespace std;
int main()
{
int intVal=9999999999999999999;
cout<<"intVal:"<<intVal<<endl;
return 0;
}
結果:
intVal:-1981284353
2.1.3 字面值常量
一個形如42的值被稱作字面值常量。
- 整型和浮點型字面值
20 //十進制
024 //八進制
0x14 //十六進制
- 字符和字符串字面值
'a' //字符字面值
"Hello world!" //字符串字面值
- 轉義序列
名稱 | 字符 | 名稱 | 字符 | 名稱 | 字符 |
---|---|---|---|---|---|
換行符 | \n | 橫向製表符 | \t | 報警(響鈴)符 | \a |
縱向製表符 | \v | 退格符 | \b | 雙引號 | \" |
反斜槓 | \ | 問號 | \? | 單引號 | \’ |
回車符 | \r | 進紙符 | \f |
- 指定字面值類型
L'a' //字符串類型字面值,類型是wchar_t
u8"hi" // utf-8字符串字面值
42ULL //無符號整型字面值,類型是unsigned long long
- 布爾字面值和指針字面值
//布爾字面值是 true 和 false
bool trueVal=true;
bool falseVal=false;
//nullptr是指針字面值(指針模塊)
int* pIntVal=nullptr;
2.2 變量
變量提供一個具名的,可供程序操作的存儲空間。
個人理解就是:有很多存放不同物品的倉庫,我把每個倉庫都取上名字。
2.2.1 變量的定義
變量定義的基本形式:類型說明符 由一個或多個變量名
int sum=0,value=0,units_sold=1; //sum value和units_sold都是int
Sales_item item; //item類型是Sales_item
- 初始值
//price先被定義並賦值,隨後被用於初始化discount
double price=109.99,discount=price*0.16;
//age 先定義 然後賦值
int age;
age=10;
- 列表初始化
int units_sold=0;
int units_sold={0};
- 默認初始值
如果定義變量時沒有指定初值,則變量被默認初始化,此時變量被賦予了默認值。
全局變量在聲明時會把初始化;局部變量在聲明時不會被初始化
2.2.2 變量聲明和定義的關係
- 聲明:使得名字爲程序所知。(例如:做飯的時候需要有湯勺,鍋,刀等等。只是有這些東西,用不用另說)
- 定義:負責創建於名字關聯的實體。(例如:做飯的時候使用到的湯勺,鍋等等)
2.2.3 標識符
C++的標誌符由 字母、數字、下劃線組成,其中必須以字母或下劃線開頭。
- 變量命名規範
- 標誌符要能體現實際含義。
- 變量名一般用小寫字母,如index,不要用Index或者INDEX。
- 用戶自定義類型一般以大寫字母開頭,如Sales_item。
- 如果標識符由多個單詞組成,則單詞間應有明顯區分,如student_loan或studentLoan。
對於命名規範來說,若能堅持,必將有效
命名規範不是c++語言硬性規定的,只是在寫代碼的時候便於識別和理解的約定,團隊可以根據情況自行規定風格。可以參考 Google、微軟等大公司的風格。
- c++關鍵字
- C++操作符替換名
2.2.4 名字的作用域
作用域是程序的一部分,在其中名字有其他特定的含義。C++語言中大多數作用域以花括號分隔。(說的不明不白的)
個人理解:作用域就是名字的使用範圍。例如:學校裏的班級,在這個學校裏可以直接說哪個班,但是在學校以外就要指定學校名稱+班級名稱。
- 嵌套作用域
作用域能彼此包含,被包含的作用域成爲內層作用域,包含着別的作用域的作用域稱外層作用域
2.3 符合類型
符合類型是指基於其他類型定義的類型。
2.3.1 引用
引用 爲對象起了另一個名字,引用類型引用另外一種類型。通過將聲明符寫成&d的形式定義引用類型,其中d是聲明的變量名。
int iVal=1024;
int &refVal=iVal; //refVal指向iVal
int &refVal2; //報錯:引用必須被初始化
- 引用即別名
引用並非對象,相反的,它只是爲一個已經存在的對象所取的另外一個名字。
對引用類型的所有操作都是在與之綁定的對象上進行的。
2.3.2 指針
- 指針是執行另外一種類型的符合類型。與引用類似,指針也實現了對其他對象的間接訪問。
- 指針本身就是一個對象,允許對指針賦值和拷貝。與引用不同。
- 指針無需在定義時賦值。
- 獲取指針的地址
&:取地址符
int iVal=20;
int *p=&iVal; //p 存放變量iVal的地址,或者說p指向變量iVal的指針
- 指針值
指針值應屬於下列4中狀態之一:
- 指向一個對象。
- 指向緊鄰對象所佔空間的下一個位置。
- 空指針,意味着指針沒有指向任何對象。
- 無效指針,依舊是上述情況之外的其他值。
- 利用指針訪問對象
*:解引用符(操作符*)
int iVal=40;
int *p=&iVal;//p存放着變量iVal的地址
cout<<"*p:"<<*p<<endl;//由符號*得到指針p所指向的對象
- 空指針
空指針:不指向任何對象的指針。
int *p1=nullptr;//等價與 int *p1=0;
int *p2=0;
//需要引入 cstdlib頭文件
int *p3=NULL;
//上方三者等價
- 賦值和指針
int i=42;
int *pi=0; //pi被初始化,但沒有指向任何對象
int *pi2=&i;//pi2被初始化,存有i的地址
int *pi3; // pi3被定義,值無法確定
pi3=pi2; //pi3和pi2指向同一個對象i
pi2=0; //pi2不指向任何對象
- void* 指針
void*是一種特殊的指針,可用於存放任意對象的地址。
double obj=3.14,*pd=&obj;
void *pv=&obj;//正確:void* 能存放任意類型對象的地址
pv=pd; //pv可以存放任意類型的指針
2.3.3 理解符合類型的聲明
- 定義多個變量
int* p1,p2; //p1是指向int的指針,p2是int 容易產生誤導
int *p1,p2; //推薦寫法
- 指向指針的指針
int iVal=1024;
int *pi=&iVal; //pi指向一個int類型的數
int **ppi=π //pi指向一個int型的指針
- 指向指針的引用
int i=1024;
int *p; //p是一個int型指針
int *&r=p; //r是一個對指針p的引用
r=&i; //r引用了一個指針,因此給r賦值&i就是令p指向i
*r=0; //解引用r得到i,也就是p指向的對象,將i的值改爲0
2.4 const限定符
const: 用const修飾的變量的值不能被改變。
- 初始化和const
int i=1024;
const int ci=i; //正確:i的值被拷貝給了ci
int j=ci; //正確:ci的值被拷貝給了j
2.4.1 const的引用
- 初始化和對const的引用
int i=1024; //定義i
const int &r1=i; //正確:允許將const int&綁定到一個普通int對象上
const int &r2=2048; //正確:r1是一個常量引用
const int &r3=r1*2; //正確:r3是一個常量引用
int &r4=r1*2; //錯誤:r4是一個普通的非常量引用
2.4.2 指針和const
- 指向常量的指針 即指針常量
const double pi=3.14;//pi是一個常量,它的值不能改變
double *ptr=π //錯誤:ptr是一個普通指針
const *cptr=π //正確:cptr可以指向一個雙精度常量
*cptr=1024; //錯誤:不能給*cptr賦值
也就是說 指針保存的地址是能改變的,保存的地址指向的對象是不可以變的
- const指針 即常量指針
指針是對象而引用不是,因此就像其他對象類型一樣,運行把指針本身定位常量。
常量指針必須初始化,而且一旦初始化完成,則它的值就不能再改變了。
int errNumb=0;
int *const curErr=&errNumb; //curErr將一直指向errNumb
const double pi=3.14159;
const double *const pip=π //pip是一個指向常量對象的常量指針
指針所保存的地址是不可以改變的,保存地址所指向的對象是可以改變的
2.4.3 頂層底層const
- 頂層const:指針本身是個常量
- 底層const:指針所指的對象是一個常量
2.4.4 constexpr和常量表達式
- 常量表達式:值不會改變並且編譯過程就能得到計算結果的表達式。
const int max_files=20; //max_files是常量表達式
const int limit=max_files+1; //limit是常量表達式
int staff_size=27; //staff_size不是常量表達式
const int sz=get_size(); //sz不是常量表達式
- constexpr變量
C++11新標準規定,運行將變量聲明爲constexpr類型以便由編譯器來驗證變量的值是否是一個常量表達式。
constexpr int mf=20; //20是常量表達式
constexpr int limit=mf+1; //mf+1是常量表達式
constexpr int sz=size(); //只有當size是 一個constexpr函數時,纔是一個正確的聲明語句
- 指針和constexpr
在constexpr生命中如果定義了一個指針,限定符constexpr僅對指針有效,與指針所指的對象無關。
const int *p = nullptr; //p是一個指向整型變量的指針
constexpr int *q = nullptr; //q是一個指向整數的常量指針
// p和q相差甚遠,p是一個指向常量的指針,而q是一個指針常量,
// 其中關鍵字constexpr把它所定義的對象置爲頂層const
2.5 處理類型
2.5.1 類型別名
類型別名 是一個名字,它是某種類型的同義詞。
- 定義類型別名
- typedef:
typedef double wages; //wages是double的同義詞 typedef wages base, *p; //base是double的同義詞,p是double*的同義詞
- using:
using SI=Sales_item; //SI是Sales_item的同義詞
- 指針、常量和類型別名
typedef char *pstring;
const pstring cstr=0; //cstr是指向char的常量指針
const pstring *ps; //ps是一個指針,它的對象是指向char的常量指針
2.5.2 auto類型說明符
C++11新標準引入了auto類型說明符,用它就能讓編譯器替我們去分析表達式所屬的類型。
int i=0,&r=i;
auto a=r; //a是一個整數(r是i的別名,而i是一個整數)
auto一般會忽略掉頂層const,同是底層const會保留下來
int i=0,&r=i;
const int ci=i,&cr=ci;
auto b=ci; //b是一個整數(ci的頂層const特性被忽略掉了)
auto c=cr; //c是一個整數(cr是ci的別名,ci本身是一個頂層const)
2.5.3 decltype 類型指示符
C++11新標準引入了第二種類型說明符declytype,它的作用是選擇並返回操作數的數據類型
decltype(f()) sum=x; //sum類型就是函數f的返回類型
const int ci=0,&cj=ci;
decltype(ci) x=0; //x 類型爲 const int
decltype(cj) y=x; //y的類型是 const int&, y綁定到變量x
decltype(cj) z; //錯誤:z的類型是一個引用,必須初始化
- decltype和引用
int i=1024,*p=&i,&r=i;
decltype(r+0) b; //正確:加法的結果是int,因此b是一個(未初始化的)int
decltype(*p) c; //錯誤:c是int&,必須初始化
2.6 自定義數據結構
2.6.1 定義Sales_item
struct Sales_item
{
string bookNo;
unsigned int units_sold=0;
double revenue=0.0;
}salesItem,*salesItem;
2.6.2 使用Sales_item類
#include <iostream>
#include <string>
using namespace std;
struct Sales_item
{
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
}saleItem,*p_SalesItem; //此處聲明一個實例,和一個指針引用
int main()
{
saleItem.bookNo = "C++從入門到精通";
saleItem.units_sold = 100;
saleItem.revenue = 89;
cout << "bookNo:" << saleItem.bookNo<<"\t unit_sold:"<<saleItem.units_sold<<"\t revenue:"<<saleItem.revenue << endl;
p_SalesItem = new Sales_item();
p_SalesItem->bookNo = "Effective C++";
p_SalesItem->units_sold = 200;
p_SalesItem->revenue = 99;
cout << "bookNo:" << p_SalesItem->bookNo << "\t unit_sold:" << p_SalesItem->units_sold << "\t revenue:" << p_SalesItem->revenue << endl;
delete(p_SalesItem);
return 0;
}
2.6.3 編寫自己的頭文件
#define: 把一個名字設定爲預處理變量
#ifdef : 當且僅當變量定義時爲真(以 #endif爲止)
#ifndef: 當且僅當變量未定義時爲真(以 #endif爲止)
#ifndef SALES_ITEM_H
#define SALES_ITEM_H
#include <string>
using namespace std;
struct Sales_item
{
string bookNo;
unsigned units_sold=0;
double revenue=0.0;
}saleItem,*p_saleItem;
#endif // SALES_ITEM_H
下面的頭文件和上面的等價
#pragma once //此文件只引用一次
#include <string>
using namespace std;
struct Sales_item
{
string bookNo;
unsigned units_sold=0;
double revenue=0.0;
}saleItem,*p_saleItem;