- enum 枚舉
enum spectrum{red, orange, yellow, green, blue, violet, indigo, ultraviolet};
spectrum band;
band = blue; - 指針
int * pt = new int;
delete pt;
new的用武之地:創建動態數組
int* psome = new int[10]
delete [] psome; 方括號告訴程序,應釋放整個數組,而不僅僅是指針指向的元素。
指向數組的指針:
不同的C++存儲方式是通過存儲持續性、作用域和鏈接性來描述的。
C++使用三種(C++11種是4種)不同的方案來存儲數據,這些方案的區別是數據保留在內存中的時間:
-
自動存儲持續性
存儲在棧中(后進先出LIFO) 執行代碼塊時,其中的變量依次加入到棧中,在離開代碼塊時,按相反的順序釋放這些變量。
在函數中定義聲明的變量(包括函數參數)的存儲持續性都是自動的,它們在程序開始執行其所屬的函數或代碼塊時被創建,在執行完函數或代碼塊時,它們使用的內存被釋放。 -
靜態存儲
整個程序執行期間都存在的存儲方式。兩種定義方式:在函數外面定義或者使用static。 -
動態存儲 自由存儲(free store)/堆(heap)
new delete
用new運算符分配的內存將一直存在,直到使用delete運算符將其釋放或程序結束為止。 -
線程存儲持續性
如果變量使用thread_local聲明,則讓其聲明周期與所屬的線程一樣長。
作用域和鏈接
作用域:描述名稱在文件(單元)的多大范圍可見
鏈接性:描述了名稱如何在不同單元中共享
鏈接性在外部的名稱可在文件間共享
鏈接性為內部的名稱只能由一個文件中的函數共享
自動變量沒有鏈接性,因為它們不能共享
- 默認情況下,在函數中聲明的函數參數和變量的存儲持續性為自動,作用于為局部,沒有鏈接性。
- 靜態存儲的鏈接性有3種:
外部鏈接性(可在其它文件中訪問)在外部聲明
int global = 1000;//static duration, external linkage
內部鏈接性(只能在當前文件中訪問)
在代碼塊外面聲明,并且使用static限定符。
無鏈接性(只能在當前函數或代碼塊中訪問)
在代碼塊內聲明,并且使用static限定符
存儲描述 | 持續性 | 作用域 | 鏈接性 | 如何聲明 |
---|---|---|---|---|
自動 | 自動 | 代碼塊 | 無鏈接性 | 在代碼塊中 |
存儲器 | 自動 | 代碼塊 | 無鏈接性 | 在代碼塊中,使用關鍵字register |
靜態,無鏈接性 | 靜態 | 代碼塊 | 無鏈接性 | 在代碼塊中,使用關鍵字static |
靜態,外部鏈接性 | 靜態 | 文件 | 外部鏈接性 | 不在任何函數內 |
靜態,內部鏈接性 | 靜態 | 文件 | 內部連接性 | 不在任何函數內,使用關鍵字static |
由于靜態變量的數目在程序運行期間是不變的,因此程序不需要使用特殊的裝置(如棧)來管理它們。編譯器將分配固定的內存塊來存儲所有的靜態變量。
靜態持續性,外部鏈接性
鏈接性為外部的變量通常簡稱為外部變量,外部變量也稱全局變量
單定義規則:
在每個使用外部變量的文件中,都必須聲明它,
定義聲明:defining declaration 簡稱定義
引用聲明,reference declaration 簡稱聲明
引用聲明使用關鍵字 extern
//file01.cpp
extern int cats = 20;
int dogs = 22;
int fleas;
//file02.cpp
extern int cats;
extern int dogs;
//file03.cpp
extern int cats;
extern int dogs;
extern int fleas;
作用域解釋符(::),放在變量名前面時,該運算符表示使用變量的全局版本。
可以使用外部變量在多文件程序的不同部分之間共享數據;可以使用鏈接性為內部的靜態變量在同一個文件中的多個函數之間共享數據(名稱空間提供了另外一種數據共享的方法)。如果將作用域為整個文件的變量變為靜態的,就不必擔心其名稱與其他文件中的作用域為整個文件的變量發生沖突
-
存儲說明符 CV限定符
存儲說明符:auto register static extern thread_local mutable
CV限定符 const(內存被初始化后,程序便不能對它再進行修改)
volatile(即使程序代碼沒有對內存單元進行修改,其值也可能發生變化。告訴編譯器,不要進行這種優化)
mutable:即使結構(或類)變量為const,其某個成員也可以被修改。
struct data
{
char name[30];
mutable int accesses;
}
const data veep={"Claybourne Clodde",0,...};
strcpy(veep.name,"Joye Joux");//not allowed
veep.accesses++;
在C++中,const對默認存儲類型稍有影響。在默認情況下全局變量的鏈接性為外部。但const全局變量的連接性為內部。也就是說,在C++看來,全局const定義就像使用了static說明符一樣
- 將一組常量放在頭文件中
函數和鏈接性
所用函數的存儲持續性都是靜態的,即在整個程序執行期間都一直存在。默認情況下,函數的鏈接性為外部,即可以在文件間共享,可以在函數原型中使用關鍵字extern來指出函數是在另一個文件中定義的(但這是可選的)
還可以使用關鍵字static將函數的鏈接性設置為內部的,使之只能在一個文件中使用,必須同時在原型和函數定義中使用關鍵字static。
語言鏈接性(c++ language linkage)
鏈接程序要求每個不同的函數都有不同的符號名,在C語言中,一個名稱只對應一個函數。但是C++中,一個名稱可能對應多一個函數。必須將這些函數翻譯為不同的符號名稱,因此C++編譯器執行名稱矯正或名稱修飾,為重載函數生成不同的符號名稱。鏈接程序在尋找于C++函數調用的匹配的函數時,使用的方法與C語言不同,但如果要在C++程序中使用C庫預編譯的函數,可以用函數原型來指出要使用的約定:
extern "C" void spiff(int); //use C protocal for name look-up
extern void spoff(int);//use C++ protocal for name look-up
extern "C++" void spaff(int);//use C++ protocal for name look-up
存儲方案和動態分配
前面的幾種內存分配方案不使用與C++運算符new(或C函數malloc)分配的內存,這種內存被稱為動態內存,動態內存由new和delete控制,而不是由作用域和鏈接性規則控制。與自動內存不同,動態內存不是LIFO,其分配和釋放順序要取決于new和delete在何時以何種方式被使用。
編譯器使用三塊獨立的內存:一塊用于靜態變量,一塊用于自動變量,一塊用于動態存儲。
存儲方案不適用于動態內存,但使用與用來跟蹤動態內存的自動和靜態指針變量。
使用new運算符初始化:
int *pi = new int (6);
double * pd = new double(99.99);
struct where{double x;double y;double z;};
where *one = new where{2.5,5.3,7.2};
int * ar = new int[4]{2,3,6,7};
placement new
#include <new>
p1 = new chaff;//new a structure in heap
p1= new (buffer) chaff//new a structrure in the buffer
名稱空間
聲明區域(declaration region)可以在其中進行聲明的區域
潛在作用域:從聲明點開始,到其聲明區域的結尾。
名稱空間可以是全局的,也可以位于另一個名稱空間中。但不能位于代碼塊中。在默認情況下,在名稱空間中聲明的名稱的鏈接性為外部的(除非它引用了常量)
using聲明 指定特定的標識符可用
using Jill::fetch;
main中的using聲明Jill::fetch將fetch添加到main()定義的聲明區域中。
在函數的外面使用using聲明時,將把名稱添加到全局名稱空間中。
using編譯指令 使整個名稱空間可用
using namespace xxx ;
類
- 訪問控制
private public protected 描述了對類成員的訪問控制
使用類對象的程序都可以直接訪問共有部分。但只能通過共有函數(或者友元函數)來訪問對象的私有成員。
防止程序直接訪問數據稱為數據隱藏
封裝:將實現細節放在一起,并且將它們與抽象分開
數據隱藏是一種封裝,將類函數定義和類聲明放在不同的文件中也是一種封裝 - 通常將數據放在私有部分,將類接口的成員函數放在共有部分。
類函數可以訪問類的private組件 - 內聯函數
其定義位于類聲明中的函數都將自動成為內聯函數
也可以在類聲明之外定義成員函數,并使其成為內聯函數。只需在類實現部分中定義函數時使用inline限定符即可 - 類的構造函數和析構函數
構造函數 沒有返回值
Stock(const string & co, long n = 0 , double pr = 0.0);
成員函數:
const Stock& topval(const Stock & s) const; - 對象數組
Stock mystuff[4];
···
const int STKS = 10;
Stock stocks[STKS] = {
Stock("NanoSmart",12.5,20);
Stock(),
Stock("Monolithic Obelisks",130,3.25);
}
···
初始化對象數組的方案:首先使用默認構造函數創建數組元素,然后花括號中的構造函數將創建臨時對象,然后將臨時對象的內容復制到相應的元素中。要創建類對象數組,則這個類必須有默認構造函數 - 運算符的重載:
Time operator+(cons Time & t) const;
total = coding.operator+(fixing);
total = coding + fixing; - 重載的限制:
1 重載后的運算符必須至少有一個操作數是用戶定義的類型
2 不能將減法運算符重載為計算兩個double值的和,而不是它們的差。
不能修改運算符的優先級
3 不能創建新的運算符
4 不能重載下面的運算符:
sizeof()
.
::
?:
typeid
const_cast dynamic_cast reinterpret_cast static_cast
5 表中大多數運算符都可以通過成員函數或非成員函數進行重載:但下面的運算符只能通過成員函數進行重載:
=
()
[]
->
Time Time::operator*(double mult) const
{
}
- 類的自動轉換與強制類型轉換
只有接收一個參數的構造函數才能作為轉換函數
Stonewt(double lbs);
Stonewt mycat;
mycat = 19.6;
程序將使用構造函數Stonewt(double)來創建一個臨時的Stonewt對象,并將19.6作為初始化值。隨后,采用逐成員賦值的方式將該臨時對象的內容復制到mycat中。這一過程稱為隱式轉換。
如果第二個參數提供默認值,它便可用于轉換int.
自動特性并非總是合乎需要的。因此,C++新增了關鍵字explicit,用于關閉這種自動特性:也就是說,可以這樣聲明構造函數:
explicit Stonewt(double lbs)
這樣將關閉上述示例中介紹的隱式轉換,但仍然允許顯式轉換,即顯示強制類型轉換:
Stonewt mycat;
mycat = 19.6;//not valid if Stonewt(double) is declared as explicit
mycat = Stonewt(19.6);//ok, an explicit conversion
- 使用特殊的C++運算符函數---轉換函數
轉換函數是用戶定義的強制類型轉換,可以像使用強制類型轉換那樣使用它們。如果定義了Stonewt到double的轉換,就可以使用下面的轉化:
Stonewt wolfe(285.7)
double host = wolfe;
//double host = double(wolfe)
operator typeName();
轉換函數必須是類方法,轉換函數不能指定返回類型,轉換函數不能有參數,但是有返回值
operator double();
可以將轉換運算符聲明為顯示的:
···
explicit operator int() const;
explicit operator double() const;
···
友元
-
友元函數
創建友元函數第一步是將其原型放在類聲明中,并在類聲明前面加上關鍵字friend
friend Time operator*(double m, const Time & t);
//在定義中不要使用friend
- 常用的友元:重載<<運算符 cout<<
ostream & operator<<(ostream & os, const Time & t)
{
os << t.hours << " hours" << t.minutes << "minutes";
return os;
}
cout << trip;
ofstream fout;
fout.open("savetime.txt");
Time trip(12,40);
fout << trip; //類的繼承屬性讓ostream引用能指向ostream和ofstream對象
友元類
友元成員函數
特殊成員函數
默認構造函數,如果沒有定義構造函數
如果定義了構造函數,C++將不會定義默認構造函數,如果希望在創建對象時不顯示地對它進行初始化,則必須顯示地定義默認構造函數,這種構造函數沒有任何參數
帶參數的構造函數也可以時默認構造函數,只要所有參數都有默認的值
默認析構函數,如果沒有定義
復制構造函數,如果沒有定義
新建一個對象并將它初始化為同類現有對象時,復制構造函數都將被調用
StringBad ditto(motto);
StringBad metto = motto;//
StringBad also = StringBad(motto);
//直接使用賦值構造函數生成一個臨時對象,然后將臨時對象的內容賦給metoo和also
StringBad* pStringBad = new StringBad(motto);
//初始化一個匿名對象,然后把新對象的地址賦值給pstring指針
定義一個顯式復制構造函數以解決問題:
進行深度復制(復制構造函數應當復制字符串并將副本的地址賦給str成員)
默認構造函數逐個復制非靜態成員(成員復制也稱為淺復制),復制的是成員的值。
賦值運算符,如果沒有定義
解決辦法:進行深度復制
String::String(const String & st)
{
num_strings++;
len = st.len;
str = new char[len+1];
std::strcpy(str,st.str);
}
String & String::operator=(const String & st)
{
if(this == &st)
return *this;
delete [] str;
len = st.len;
str = new char[len+1];
std::strcpy(str,st.str);
return *this;
}
地址運算符,如果沒有定義
(c++11移動構造函數
移動賦值運算符)
- 靜態類成員函數:
可以將成員函數聲明為靜態的(函數聲明包含關鍵字static)!!!
不能通過對象調用靜態成員函數,靜態成員函數甚至不能使用this指針。如果靜態成員函數是在公有部分聲明的,則可以使用類名和作用域解析運算符來調用它。
其次,由于靜態成員函數不與特定的對象相關聯。因此只能使用靜態成員數據。
可以重載賦值運算符,使之能接收對象的銀行和直接使用常規字符串。
成員初始化列表
繼承 --3種繼承關系(公有繼承,保護繼承,私有繼承)
公有繼承 is-a關系 (is-a-kind-of)
class RatedPlayer : public TableTennisPlayer
{
...
}
公有派生,派生類對象包含積累對象。
使用公有派生,基類的公有成員將稱為派生類的公有成員;基類的私有部分也將稱為派生類的一部分,但只能通過基類的公有和保護方法訪問。
派生類需要自己的構造函數
派生類可以根據需要添加額外的數據成員和成員函數。
派生類構造函數必須使用基類構造函數
RatedPlayer::RatedPlayer(unsigned int r,const string & fn,
const string & ln, book ht) : TableTennisPlayer(fn, ln, ht)
{
rating = r;
}
- 有關派生類構造函數的要點如下:
首先創建基類對象
派生類構造函數應通過成員初始化列表傳遞給基類構造函數
派生類構造函數應初始化派生類新增的數據成員 - 派生類和基類的關系:
派生類對象可以使用基類的方法
基類指針可以在不進行顯示類型轉換的情況下指向派生類對象。基類引用可以在不進行顯示類型轉換的情況下引用派生類對象。
多態公有繼承
希望同一個方法在派生類和基類中的行為是不同的。方法的行為取決于調用該方法的對象。這種較復雜的行為稱為多態--具有多種形態
有兩種機制可以實現多態的公有繼承:
在派生類中重新定義基類的方法
- 使用虛方法
//ViewAcct()不是虛函數
Brass dom("Dominic Banker",11224,4183.45);
BrassPlus dot("Dorothy Banker",12118,2592.00);
Brass & b1_ref = dom;
Brass & b2_ref = dot;
b1_ref.ViewAcct();//調用Brass的
b2_ref.ViewAcct();//調用Brass的
//使用虛函數
Brass dom("Dominic Banker",11224,4183.45);
BrassPlus dot("Dorothy Banker",12118,2592.00);
Brass & b1_ref = dom;
Brass & b2_ref = dot;
b1_ref.ViewAcct();//調用Brass的
b2_ref.ViewAcct();//調用BrassPlus
1 方法在基類聲明為虛后,它在派生類中將自動成為虛方法。然而,在派生類聲明中使用關鍵字virtual來指出哪些函數是虛函數也不失為一個好方法。
2 基類聲明一個虛析構函數,這樣做確保釋放派生類對象時,按照正確的順序調用析構函數。如果虛構函數不是虛的,則只調用對應于指針類型的析構函數。
Brass * p_clients[CLIENTS];
將派生類引用或指針轉換為基類引用或指針被稱為向上強制轉換,這使公有繼承不需要進行顯示類型轉換。
編譯器對非虛方法使用靜態聯編
對虛方法使用動態聯編
- 虛函數的工作原理
編譯器處理虛函數的方法:
給每個對象添加一個隱藏成員。隱藏成員中保存了一個指向函數地址數組的指針。這種數組稱為虛函數表(virtual function table, vtbl)。虛函數表中存儲了為 類對象進行聲明的虛函數地址。
調用虛函數時,程序將查看存儲在對象中的vtbl地址,然后轉向相應的函數地址表。如果使用類聲明中定義的第一個虛函數,則程序將使用數組中的第一個函數地址,并執行具有該地址的函數。
overload(重載)相同函數名 不同參數
overwrite子類重寫父類方法
在派生類中重新定義函數,將不是使用相同的函數特征標覆蓋基類聲明,而是隱藏同名的基類方法,不管參數特征標如何。
protected
派生類的成員可以直接訪問基類的保護成員,但不能直接訪問基類的私有成員
抽象基類
abstract base class, ABC
C++通過使用春虛函數提供未實現的函數。純虛函數聲明的結尾處為 =0
當類聲明中包含純虛函數時,則不能創建該類的對象。包含純虛函數的類只用作基類。純虛函數也可以有定義。
可以將ABC看作是一種必須實施的接口,ABC要求具體派生類覆蓋其純虛函數--迫使派生類遵循ABC設置的接口規則。
保護繼承
私有繼承
堆(heap)
- vector set
strlen返回字符串長度
指針
- 指針和const
第一種:讓指針指向一個常量對象,這樣可以防止使用該指針來修改所指向的值。
int age = 39;
const int * pt = &age; 指針指向const int
不能通過pt來修改age的值,但是可以通過age直接修改age的值,因為age不是const。
第二種:將指針本身聲明為常量,這樣可以防止改變指針指向的位置
int const * pt2 = &age; const指針
int * const pt2 = &age; - 函數指針
函數的地址:函數名
聲明函數指針:
double pam(int);//函數原型
double (*pf) (int);//指向函數的指針,1個int參數,返回double;
pf=pam; 讓函數指針指向函數
注意區分
double* pf (int) 和 double(pf)(int)
void estimate(int lines,double (pf) (int));//函數原型 第二個參數是一個函數指針
estimate(50,pam);
使用指針來調用函數
使用指針來調用被指向的函數。(pf)扮演的角色與函數名相同
double x = pam(4); //calling pam() using the function name
double y = (pf)(5);//calling pam() using the pointer pf
函數指針數組
const double* (*pa[3])(const double ,int) = {f1,f2,f3};
const double * px = (pa[0])(av,3);
double y = (pa[1])(av,3);
typedef const double (p_fun)(const double ,int);
p_fun p1 = f1; p1指向f1
p_fun pa[3] = {f1,f2,f3}; pa是一個array of 3 function pointers
p_fun (pd)[3] = &pa; pd points to an array of 3 function pointers.
- 浮點數的寫法 科學計數法 10的幾次方
10e-6 - 表達式中的轉換
當同一個表達式中包含兩種不同的算術類型時,C++將執行兩種轉換:
1 自動轉換
short chicken = 20;
short ducks = 35;
short fowl = chicken + ducks;
在執行第三句的時候,程序把short先轉化為int進行計算,然后將結果轉換為short.
2 整型提升
4種類型轉換
C++還引入了4個強制類型轉換運算符
- dynamic_cast
- const_cast
- static_cast 將值從一種數值類轉換為另外一種數值類型
int thorn;
static_cast<long> (thorn)
static_cast<typeName> (value) - reinterpret_cast
函數
- 內聯函數
在函數聲明前加上關鍵字inline
在函數定義前加上關鍵字inline - 引用變量
-
函數多態(函數重載)
同名函數,但是參數列表不同
多態:多種形式 -
函數模板:(通用編程)//這種是函數多態的升級 上面的函數多態對不同類型的參數還需要逐個寫出來。模板就不用了,直接替代
通用的函數描述
使用泛型來定義函數,其中的泛型可用具體的類型替換。
template <typename AnyType> 建立一個模板 typename可以用class代替,template和typename是關鍵字 T來代替AnyType也是可以的
void Swap(AnyType &a, AnyType &b) 模板不會創建任何函數。當需要交換int函數時,編譯器將按模板模式創建這樣的函數
{
AnyType temp;
temp = a;
a = b;
b = temp;
} - 重載的模板
被重載的函數特征標必須不同
template <typename T>
void Swap(T& a, T& b);
template <typename T>
void Swap(T *a, T *b,int n); - 為特定類型提供具體化的模板
顯示具體化(explicit specialization) 當編譯器中找到與函數調用匹配的具體化定義時,將使用該定義而不再尋找模板。
對于給第那個的函數名,可以有非模板函數,模板函數和顯示具體化模板函數以及它們的重載版本
//not template function prototype
void Swap(job &, job &);
//template prototype
template<typename T>
void Swap(job &, job &);
template <> void Swap<job>(job&,job&);
template <> void Swap(job&,job&);//不要使用Swap模板來生成函數定義,而應該使用專門為int類型顯式定義的函數定義
非模板版本>顯示具體化>模板生成的版本
函數調用Swap(i,j)導致編譯器生成Swap的一個實例,該實例使用int類型
隱式實例化(implicit instantiation),使用模板生成函數定義
顯示實例化(explicit instantiaion) 可以直接命令編譯器創建特定的實例
template void Swap<int>(int &,int &);//該聲明的意思是“使用Swap()模板生成int類型的函數定義”
隱式實例化,顯示實例化,顯示具體化統稱為具體化。相同之處:標識的都是具體類型的函數定義,而不是通用描述 - 編譯器使用哪個函數版本:
完全匹配(常規函數優先于模板)
提升轉換(char/short -> int, long->double )
標準轉換(int -> char, long ->double)
用戶定義的轉換,如類聲明中定義的轉換。 - 堆棧
中綴表達式
后綴表達式 求值策略:從左向右掃描,逐個處理運算數和運算符號
先放進去后拿出來 堆棧
堆棧(stack)具有一定操作約束的線性表。只在一端(棧頂,Top)做插入、刪除
插入數據:入棧(Push)
刪除數據:出棧(Pop)
后入先出:Last in First Out(LIFO)
放在桌子上的一疊碗
堆棧可以用來倒序輸出