易混淆的c++知识点
初始化和賦值的區別
在定義一個變量或常量時為它指定初值叫做初始化,而在定義一個變量或常量以后使用戚值運算符修改它的值叫做賦值,勿將初始化與賦值混淆。
類的組合和友元函數
類組合的情況,類B 中內嵌了類A 的對象,但是B 的成員函數卻元法直接訪問A 的私有成員x 。
友元關系提供了不同類或對象的成員函數之間、類的成員函數與一般函數之間進行數據共享的機制。,通過友元關系,一個普通函數或者類的成員函數可以訪問封裝于另外一個類中的數據。友元函數是在類中用關鍵字friend 修飾的非成員函數。
若A 類為B 類的友元類,則A 類的所有成員函數都是B 類的友元函數, 都可以訪問B類的私有和保護成員。
組合類構造函數定義的一般形式為:
Circle::Circle(float r): radius? {}
常對象
如果將一個對象說明為常對象,則通過該常對象只能調用它的常成員函數,而不能調用其他成員函數(這就是c++ 從語法機制上對常對象的保護,也是常對象唯一的對外接口方式)。
此常成員函數不能更新目的對象的數據成員,也不能針對目的對象調用該類中沒有用const 修飾的成員函數(這就保證了在常成員函數中不會更改目的對象的數據成員的值)。
const 關鍵字可以用于對重載函數的區分。例如,如果在類中這樣聲明:
void print ();
void print () const;
習慣:在適當的地方使用const 關鍵字,是能夠提高程序質量的一個好習慣。對于無須改變對象狀態的成員函數,都應當使用const
常引用
如果在聲明引用時用const 修飾,被聲明的引用就是常引用。常引用所引用的對象不能被更新。如果用常引用作形參,便不會意外地發生對實的更改。
指針與引用
引用與指針的一個顯著區別是,普通指針可以多次被賦
值,也就是說可以多次更改它所指向的對象,而引用只能在初始化時指定被引用的對象,其后就不能更改。因此,引用的功能,與一個指針常量差不多。
需要指出的是,雖然在"讀取v 的地址"這一用途上p 和&r 是等價的,但p 和&r 卻具有不同的含義.p 可以再被取地址,而&r 則不行。也就是說,引用本身(而非被引用的對象)的地址是不可以獲得的,引用經定義后,對它的全部行為,全是針對被引用對象的,而引用本身所占用的空間則被完全隱藏起來了。因此,引用的功能還是要比指針常量略差一點。
只有常引用,而沒有引用常量,也就是說,不能用T &. const 作為引用類型。
這是因為引用只能在初始化時指定它所引用的對象,其后則不能再更改,這使得引用本身(而非被引用的對象)已經具有常量性質了。
對于數據參數傳遞、減少大對象的參數傳遞開銷這兩個用途來說,引用可以很好地代替指針,使用引用比指針更加簡潔、安全。其中,如果僅僅是為了后一個目的,一般來說應當使用常引用作為參數。
如果在聲明引用時用const 修飾,被聲明的引用就是常引用。常引用所引用的對象不能被更新。
const 類型說明符&引用名;
非const 的引用只能綁定到普通的對象,而不能綁定到常對象,但常引用可以綁定到常對象。一個常引用,無論是綁定到一個普通的對象,還是常對象,通過該引用訪問該對象時,都只能把該對象當作常對象
(1)可以聲明指向常量的指針,此時不能通過指針來改變所指對象的值,但指針本身可以改變,可以指向另外的對象
int a; const int *p1= &a; int b; p1= &b;//正確, p1 本身的值可以改變 *pl= 1;//編譯時出錯,不能通過p1 改變所指的對象(2) 可以聲明指針類型的常量,這時指針本身的值不能被改變。例如:
int *const p2= &a;
p2= &b; //錯誤, p2 是指針常量,值不能改變
指針的運算
指針算術運算的用途,一定要限制在通過指向鼓組中某個元素的指針,得到指向同一個敢組中另一個元素的指針。指針算術運算的其他用法,都會得到不確定的結果。
整型的2 和雙精度浮點型的2. 在內存中是由不同的二進制序列表示的。不過,在執
行static_cast (i) 這一操作時,編譯器會生成目標代碼,將i 的整型的二進制表示,轉換成浮點型的二進制表示,這種轉換叫做基于內容的轉換。但是有指針參加的轉換,情況就不大一樣了,例如:
int i= 2;
float*p= reinterpret cast< float *> (&i);
reinterpret_cast 是和static_ cast 并列的一種類型轉換操作符,它可以將一種類型的指針轉換為另一種類型的指針,這里把int* 類型的&i 轉換為float *類型。
reinterpret_cast 不僅可以在不同類型對象的指針之間轉換,還可以在不同類型函數的指針之間、不同類數據成員的指針之間、不同類函數成員的指針之間、不同類型的引用之間相互轉換。reinterpret_ cast 的轉換過程,在c+ 十標準中未明確規定,會因編譯環境而異。c++ 標準只保證用reinterpret_cast 操作符將A 類型的p 轉換為B 類型q ,
再用reinterpret_cast 操作符將B 類型的q 轉換為A 類型的r 后電應當有(p= =r) 成立。
二維數組:
int line1[]={l , 0, 0};
int line2[]={0, 1, 0};
int line3[]= {0, 0, 1};
int pLine[3]={line1 , line2 , line3};
pLine[i] [j] 與(pLine[i]+j) 等價,即先把指針數組pLine 所存儲的第i 個指針讀出,然后讀取它所指向的地址后方的第j 個數。它在表示形式上與訪問二維數組的元素非常相似,但在具體的訪問過程上卻不大一樣。
二維數組在內存中是以行優先的方式按照一維順序關系存放的。因此對于二維數組,可以將其理解為一維數組的一維數組,數組名是它的首地址,這個數組的元素個數就是行數,每個元素是一個一維數組。
在程序運行時,不僅數據要占據內存空間,執行程序的代碼也被調入內存并占據一定的空間。每一個函數都有函數名,實際上這個函數名就表示函數的代碼在內存中的起始地址。由此看來,調用函數的通常形式"函數名(參數表)“的實質就是"函數代碼首地址(參數表)”。
函數指針
聲明一個函數指針時,也需要說明函數的返回值、形式參數列表,其一般語法如下:
數據類型(頭函數指針名) (形參表)
數據類型說明函數指針所指函數的返回值類型;第一個阿括號中的內容指明一個的數指針的名稱;形參表則列出了該指針所指函數的形參類型和個數。
typedef int (*DoubleInt Fu nction) (double);
這聲明了DoublelntFunction 為"有一個double 形參、返回類型為int 的函數的指針"
類型的別名。下面,需要聲明這一類型的變量時,可以直接使用:
DoubleIntFunction funcPtr;
作用域
作用域可見性的一般規則如下。
.標識符要聲明在前,引用在后。
·在同一作用域中,不能聲明同名的標識符。
·在沒有互相包含關系的不同的作用域中聲明的同名標識符,互不影響。
·如果在兩個或多個具有包含關系的作用域中聲明了同名標識符,則外層標識符在內層不可見。
如果對象的生存期與程序的運行期相同,則稱它具有靜態生存期。在命名空間作用域中聲明的對象都是具有靜態生存期的。如果要在函數內部的局部作用域中聲明具有靜態生存期的對象,則要使用關鍵字statìc 。
局部作用域中靜態變量的特點是,它并不會隨著每次函數調用而產生一個副本,也不會隨著函數返回而失效。也就是說,當一個函數返回后,下一次再調用時,該變量還會保持上一回的值,即使發生了遞歸調用,也不會為該變量建立新的副本,該變量會在每次調用間共享。
在定義靜態變量的同時也可以為它賦初值,例如:
static int i= 5;
這表示i 會被賦予5 初始化,而非每次執行函數時都將i 賦值為5 。
定義時未指定初值的基本類型靜態生存期交量,會被賦予0 值初始化,而對于動態生存期變量,不指定初值意味著初值不確定。
局部生存期對象誕生于聲明點,結束于聲明所在的塊執行完畢之時。
外部變量
如果一個變量除了在定義它的源文件中可以使用外,還能被其他文件使用,那么就稱
這個變量是外部變量。命名空間作用域中定義的變量,默認情況下都是外部變量,但在其他文件中如果需要使用這一變量,需要用extern 關鍵字加以聲明。
。外部變量是可以為多個游、文件所共享的全局變量。
在包含多個源文件的工程中,匿名命名空間常常被用來屏蔽不希望暴露給其他源文件的標識符,這是因為每個源文件的匿名命名空間是彼此不同的,在一個源文件中沒有辦法訪
問其他源文件的匿名命名空間。
-
在同一作用域中,不能聲明同名的標識符。
-
在沒有互相包含關系的不同的作用域中聲明的同名標識符,互不影響。
-
如果在兩個或多個具有包含關系的作用域中聲明了同名標識符,則外層標識符在內層不可見。
常數據成員只能通過初始化列表來獲得初值
class A (
public:
A(int i);
void print () ;
private:
const int a;
static const int b;}
細節類成員中的靜態變量和常量都應當在類定義之外加以定義,但c++ 標準規定了一個例外:類的靜態常量如采具有整數類型或枚舉類型,那么可以直接在類定義中為它指定常量值,例如,可以直接在類定義中寫:
static const int b=10;
undef 的作用是刪除由# define 定義的宏,使之不再起作用。
一個新的關鍵字一mutable 。對于某類數據成員,可以使用
mutable 關鍵字加以修飾,這樣,即使在常成員函數中,也可以修改它們的值。被mutable 修飾的成員對象在任何時候都不會被視為常對象,這是mutable 更一般的含義。
程序是存儲在磁盤上的,在執行前,操作系統需要首先將它載入到內存中,并為它分配足夠大的內存空間來容納代碼段和數據段,然后把文件中存放的代碼段和初始化的數據段的內容載入其中一部分靜態生存期對象的初始化就是通過這種方式完成的,
內存空間的訪問方式
在c++ 程序中如何利用內存單元存取數據呢?一是通過變量名,二是通過地址。程序中聲明的變量要占據一定的內存空間,例如,對于一些常見的32 位系統, short 型占2 字節.long 型占4 字節。具有靜態生存期的變量在程序開始運行之前就已經被分配了內存空間。具有動態生存期的變量,是在程序運行時遇到變量聲明語句時被分配內存空間的。變量獲得內存空間的同時,變量名也就成了相應內存空間的名稱,在變量的整個生存期內都可以用這個名字訪問該內存空間,表現在程序語句中就是通過變量名存取變量內容。但是,有時使用變量名不夠方便或者根本沒有變量名可用,**這時就需要直接用地址來訪問內存單元。**例如,在不同的函數之間傳送大量數據時,如果不傳遞變量的值,只傳遞變量的地址,就會減少系統開銷,提高效率。如果是動態分配的內存單元(將在6.3 節介紹) .則根本就沒有名稱,這時只能通過地址訪問。
point= new int (2) ;
動態分配了用于存放int 類型數據的內存空間,并將初值2 存入該空間中,然后將首地址
賦給指針point 。
如采保留括號,但括號中不寫任何數值,則表示用。對該對象初始化,例如:
int *point= new int () ;
assert
assert 只在調試模式下生效,而在發行模式下不執行任何操作,這樣兼顧了調試模式的調試需求和發行模式的效率需求。
提示由于assert 只在調試模式下生效,一般用assert 只是檢查程序本身的邏輯錯誤,而用戶的不當輸入造成的錯誤,則應當用其他方式加以處理。
以指針形式訪問數組元素:
int main() { f1oat (*cp) [9] [8]=new f1oat[8] [9] [8]; for (int i=O; i<8; i++) for (int j=O; j< 9; j+ 十) for (int k=O; k<8; k++) //以指針形式訪問數組元素 *(*(*(甲+.i. )+j)+k)=static cast< float> (i *100+ j *10+ k) ;string
由于string 類具有接收const char 頭類型的構造函數,因此字符串常量和用字符數紐表示的字符串變量都可以隱含地轉換為stnng 對象。例如,可以直接使用字符串常量對stnng 對象初始化2
string str= “Hello world!”;
stnng 類的操作符:
s +t 將串s 和t 連接成一個新串
s!= t 判斷s 與t 是否不等
string append (const char *s) ;將字符串s 添加在本串尾
string assign (const char *s) ;賦值,將s 所指向的字符串賦值給本對象
int compare (const string &str) const;
比較本串與str 中串的大小,當本串<str 串時,返回負數J 當本串>str 串時,返回正數J
兩串相等時,返回0。
void swap (string& str) ;
//將本串與str 中的字符串進行交換
unsigned int find(const basic_string &str) const;
//查找并返回str 在本串中第一次出現的位置
基類與派生類
類型兼容規則是指在需要基類對象的任何地方,都可以使用公有派生類的對象來替代。通過公有繼承,派生類得到了基類中除構造函數、析構函數之外的所有成員。這樣,公有派生類實際就具備了基類的所有功能,凡是基類能解決的問題,公有振生類都可以解決。類型兼容規則中所指的替代包括以下的情況。
·派生類的對象可以隱含轉換為基類對象。
.派生類的對象可以初始化基類的引用。
·派生類的指針可以隱含轉換為基類的指針。
在替代之后,派生類對象就可以作為基類的對象使用,但只能使用從基類繼承的成員。
如果B 類為基類, D 為B 類的公有派生類,則D 類中包含了基類B 中除構造、析構函數之外的所有成員。這時,根據類型兼容規則,在基類B 的對象可以出現的任何地方,都可以用派生類D 的對象來替代。
class B {…}
class D: public B {…}
B b1 ,*pb1;
D dl;
派生類的對象也可以初始化基類對象的引用:
B &rb=d1;
,可以將公有派生類對象的地址賦值給基類類型的指針,
定義了派生類之后,要使用派生類就需要聲明該類的對象。對象在使用之前必須初始化。派生類的成員對象是由所有基類的成員對象與派生類新增的成員對象共同組成。
因此構造派生類的對象時,就要對基類的成員對象和新增成員對象進行初始化基類的
構造函數并沒有繼承下來,要完成這些工作,就必須給派生類添加新的構造函數。派生類對于基類的很多成員對象是不能直接訪問的,因此要完成對基類成員對象的初始化工作,需要通過調用基類的構造函數。派生類的構造函數需要以合適的初值作為參數,其中一些參數要傳遞給基類的構造函數,用于初始化相應的成員,另一些參數要用于對派生類新增的成員對象進行初始化。**在構造派生類的對象時,會首先調用基類的構造函數,來初始化它們的數據成員,**然后按照構造函數初始化列表中指定的方式初始化派生類新增的成員對象,最后才執行派生類構造函數的函數體。
基類構造函數的調用順序是按照派生類定義時的順序,因此應該是先Base2. 再Base1. 最后Base3;
而內嵌對象的構造函數調用順序應該是按照成員在類中聲明的順序
虛基類
。在派生類的對象中,這些同名數據成員在內存中同時擁有多個副本,同一個函數名會有多個映射。可以使用作用域分辨符來唯一標識并分別訪問它們,也可以將共同基類設置為虛基類,這時從不同的路徑繼承過來的同名數據成員在內存中就只有一個副本,同一個函數名也只有一個映射。這樣就解決了同名成員的唯一標識問題。
class 派生類名:virtual 繼承方式基類名 class BaseO { public: int varO;void funO () {cout<< "Member of BaseO"<< endl; } } class Basel: virtuaJ. public BaseO { public: int varl; }構造一個類的對象的一般順序是:
(1)如果該類有直接或間接的虛基類,則先執行虛基類的構造函數。
(2) 如采該類有其他,基類,則按照它們在繼承聲明列表中出現的次序,分別執行它們的構造函數,但構造過程中,不再執行它們的虛基類的構造函數。
(3) 按照在類定義中出現的順序,對派生類中新增的成員對象進行初始化。對于類類型的成員對象,如果出現在構造函數初始化列表中,則以其中指定的參數執行構造函數,如未出現,則執行默認構造函數;對于基本數據類型的成員對象,如果出現在構造函數的初始化列表中,則使用其中指定的值為其賦初值,否則什么也不做。
(4) 執行構造函數的函數體。
派生類指針可以隱含轉換為基類指針,之所以允許這種轉換隱含發生,是因為它是安全的轉換。而派生類指針要想轉換為基類指針,則轉換一定要顯式地進行。例如:
Base*pb= new Derived () ; / /將Derived 指針隱含轉換為Base 指針
Derived *pd= static cast< Derived *> (pd); / /將Base 指針顯式轉換為Derived 指針
虛函數
虛函數是動態綁定的基礎。虛函數必須是非靜態的成員函數。虛函數經過派生之后,在類族中就可以實現運行過程中的多態。
如果用基類類型的指針指向派生類對象,就可以通過這個指針來訪問該對象,問題是訪問到的只是從基類繼承來的同名成員。解決這一問題的辦法是:如果需要通過基類的指針指向派生類的對象,并訪問某個與基類同名的成員,那么首先在基類中將這個同名函數說明為虛函數。這樣,通過基類類型的指針,就可以便屬于不同派生類的不同對象產不同的行為,從而實現運行過程的多態。
虛函數聲明只能出現在類定義中的函數原型聲明中,而不能在成員函數實現的時候。而基類中聲明的非虛函數,通常代表那些不希望被派生類改變的功能,也是不能實現多態的。一般不要重寫繼承而來的非虛函數(雖然語法對此沒有強行限制) ,因為那會導致通過基類指針和派生類的指針或對象調用同名函數時,產生不同的結果,從而引起混亂。
,通過基類指針刪除派牛類對象時調用的是基類的析構函數,派生類的析構函數沒有被執行,因此派生類對象中動態分配的內存空間沒有得到釋放,造成了內存泄漏。
也就是說派生類對象成員p 所指向的內存空間,在對象消失后既不能被本程序繼續使用也沒有被擇放。對于內存需求量較大、長期連續運行的程序來說,如果持續發生這樣的錯誤是很危險的,最終將導致因內存不足而引起程序終止。
避免上述錯誤的有效方法就是將析構函數聲明為虛函數:
class Base (
public:
virtual - Base () ;}
純虛函數的聲明格式為2
virtual 函數類型函數名(參數表)=0
實際上,它與一般虛函數成員的原型在書寫格式上的不同就在于后面加了"= 0" 。
聲明為純虛函數之后,基類中就可以不再給出函數的實現部分。純虛函數的函數體由派生類給出。
基類指針指向子類對象時的類型轉化
基類的指針可以指向派生類的對象,通過這樣的指針雖然可以利用多態性來執行派生類提供的功能,但這僅限于調用基類中聲明的虛函數。如果希望對于一部分派生類的對象,調用派生類中引人的新函數,則無法通過基類指針進行。
dynamic_cast 是與static_cast. const_cast , reinterpret_cast 并列的4 種類型轉換操作符之→。它可以將基類的指針顯式轉換為派生類的指針,或將基類的引用顯式轉換為派生類的引用。{!3.與static_cast 不同的是,它執行的不是無條件的轉換,它在轉換前會檢查指針(或引用)所指向對象的實際類型是否與轉換的日的類型兼容,如果兼容轉換才會發生,才能得到派生類的指針(或引用) .再則:
·如果執行的是指針類型的轉換,會得到空指針。
·如果執行的是引用類型的轉換,會拋出異常
示例:
由于fun1 函數是基類Base 中定義的函數,通過Base 類的指針b 可以直接調用fun1()函數。fun2函數是派生類Derived1 中引人的新函數,只能對Derived1類的對象調用。
用typeid 獲取運行時類型信息
typeid 是c++ 的一個關鍵字,用它可以獲得一個類型的相關信息。
總結
以上是生活随笔為你收集整理的易混淆的c++知识点的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于宽字符(C++将中文文本文件的内容输
- 下一篇: 关于MVC框架和spring