前些日子開始看《C++ Primer》,順便做一些筆記,既有書上的,也有自己理解的。
因為剛學C++不久,筆下難免有謬誤之處,行文更是凌亂;
所幸不是用來顯配的東西,發在linuxsir只是為了方便自己閱讀記憶,以防只顧上網忘了正事。
書看了不到一半,所以大約才寫了一半,慢慢補充。
=========================================
==========================================
轉載務必注明原作者
neplusultra 2005.2.3
==========================================
const要注意的問題 1、下面是一個幾乎所有人剛開始都會搞錯的問題:
已知:typedef char *cstring;
在以下聲明中,cstr的類型是什么?
extern const cstring cstr;
錯誤答案:const char *cstr;
正確答案:char *const cstr;
錯誤在于將typedef當作宏擴展。const 修飾cstr的類型。cstr是一個指針,因此,這個定義聲明了cstr是一個指向字符的const指針。
2、指針是const還是data為const?
辨別方法很簡單,如下:
代碼:
char *p="hello"; //non-const pointer, non-const data;
const char *p="hello"; // non-const pointer, const data;
char * const p="hello"; // const pointer , non-const data;
const char * const p="hello"; // const pointer, const data;
要注意的是,"hello"的類型是const char * ,按C++standard規則,char *p="hello" 是非法的(右式的const char* 不能轉換為左式的char *),違反了常量性。但是這種行為在C中實在太頻繁,因此C++standard對于這種初始化動作給予豁免。盡管如此,還是盡量避免這種用法。
3、const初始化的一些問題
const 對象必須被初始化:
代碼:
const int *pi=new int; // 錯誤,沒有初始化
const int *pi=new int(100); //正確
const int *pci=new const int[100]; //編譯錯誤,無法初始化用new表達式創建的內置類型數組元素。
什么時候需要copy constructor,copy assignment operator,destructor 注意,若class需要三者之一,那么它往往需要三者。
當class的copy constructor內分配有一塊指向hcap的內存,需要由destructor釋放,那么它也往往需要三者。
為什么需要protected 訪問級別 有人認為,protected訪問級別允許派生類直接訪問基類成員,這破壞了封裝的概念,因此所有基類的實現細節都應該是private的;另外一些人認為,如果派生類不能直接訪問基類的成員,那么派生類的實現將無法有足夠的效率供用戶使用,如果沒有protected,類的設計者將被迫把基類成員設置為public。
事實上,protected正是在高純度的封裝與效率之間做出的一個良好折衷方案。
為什么需要virtual member function又不能濫用virtual 若基類設計者把本應設計成virtual的成員函數設計成非virtual,則繼承類將無法實現改寫(overridden),給繼承類的實現帶來不便;
另一方面,一旦成員函數被設計成virtual,則該類的對象將額外增加虛擬指針(vptr)和虛擬表格(vtbl),所以倘若出于方便繼承類overridden的目的而使所有成員函數都為virtual,可能會影響效率,因為每個virtual成員函數都需付出動態分派的成本。而且virtual成員函數不能內聯(inline),我們知道,內聯發生在編譯時刻,而虛擬函數在運行時刻才處理。對于那些小巧而被頻繁調用、與類型無關的函數,顯然不應該被設置成virtual。
關于引用的一些注意點 1、把函數參數聲明為數組的引用:當函數參數是一個數組類型的引用時,數組長度成為參數和實參類型的一部分,編譯器檢查數組實參的長度和與在函數參數類型中指定的長度是否匹配。
代碼:
//參數為10個int數組
void showarr(int (&arr)[10]);
void func()
{
int i,j[2],k[10];
showarr(i); //錯誤!實參必須是10個int的數組
showarr(j); //錯誤!實參必須是10個int的數組
showarr(k); //正確!
}
//更靈活的實現,借助函數模板。下面是一個顯示數組內容的函數。
template <typename Type , int size>
void printarr(const Type (& r_array)[size])
{
for(int i=0;i<size;i++) std::cout<< r_array[i] <<' ';
std::cout << std::endl;
}
void caller()
{
int ar[5]={1,2,5,3,4}; //數組可以任意大小。
printarr(ar); //正確!自動正確調用printarr()
}
2、
3、
goto語句的一些要注意的地方 1、label語句只能用作goto的目標,且label語句只能用冒號結束,且label語句后面不能緊接右花括號'}',如
辦法是在冒號后面加一個空語句(一個';'即可),如
2、goto語句不能向前跳過如下聲明語句:
代碼:
goto label6;
int x=1; //錯誤,不能跳過該聲明!
cout<<x<<endl; //使用x
label6:
//其他語句
但是,把int x=1; 改為int x; 則正確了。另外一種方法是:
代碼:
goto label6;
{
int x=1; //正確,使用了語句快
cout<<x<<endl;
}
label6:
//其他語句
3、goto語句可以向后(向程序開頭的方向)跳過聲明定義語句。
代碼:
begin:
int i=22;
cout<< i <<endl;
goto begin; //非常蹩腳,但它是正確的
變量作用域 1、花括號可以用來指明局部作用域。
2、在for、if、switch、while語句的條件/循環條件中可以聲明變量,該變量僅在相應語句塊內有效。
3、extern為聲明但不定義一個對象提供了一種方法;它類似于函數聲明,指明該對象會在其他地方被定義:或者在此文本的其他地方,或者在程序的其他文本文件中。例如extern int i; 表示在其他地方存在聲明 int i;
extern 聲明不會引起內存分配,他可以在同一個文件或同一個程序中出現多次。因此在全局作用域中,以下語句是正確的:
代碼:
extern int c;
int c=1; //沒錯
extern int c; //沒錯
但是,extern聲明若指定了一個顯式初始值的全局對象,將被視為對該對象的定義,編譯器將為其分配存儲區;對該對象的后續定義將出錯。如下:
代碼:
extern int i=1;
int i=2; //出錯!重復定義
auto_ptr若干注意點 1、auto_ptr的主要目的是支持普通指針類型相同的語法,并為auto_ptr所指對象的釋放提供自動管理,而且auto_ptr的安全性幾乎不會帶來額外的代價(因為其操作支持都是內聯的)。定義形式有三種:
代碼:
auto_ptr<type_pointed_to>identifier(ptr_allocated_by_new);
auto_ptr<type_pointed_to>identifier(auto_ptr_of_same_type);
auto_ptr<type_pointed_to>identifier;
2、所有權概念。auto_ptr_p1=auto_ptr_p2的后果是,auto_ptr_p2喪失了其原指向對象的所有權,并且auto_ptr_p2.get()==0。不要讓兩個auto_ptr對象擁有空閑存儲區內同一對象的所有權。注意以下兩種種初始化方式的區別:
代碼:
auto_ptr<string>auto_ptr_str1(auto_ptr_str2.get()); //注意!用str2指針初始化str1, 兩者同時擁有所有權,后果未定義。
auto_ptr<string>auto_ptr_str1(auto_ptr_str2.release());//OK!str2釋放了所有權。
3、不能用一個指向“內存不是通過應用new表達式分配的”指針來初始化或者賦值auto_ptr。如果這樣做了,delete表達式會被應用在不是動態分配的指針上,這將導致未定義的程序行為。
C風格字符串結尾空字符問題代碼:
char *str="hello world!"; //str末尾自動加上一個結尾空字符,但strlen不計該空字符。
char *str2=new char[strlen(str)+1] // +1用來存放結尾空字符。
定位new表達式 頭文件:<new>
形式:new (place_address) type-specifier
該語句可以允許程序員將對象創建在已經分配好的內存中,允許程序員預分配大量的內存供以后通過這種形式的new表達式創建對象。其中place_address必須是一個指針。例如:
代碼:
char *buf=new char[sizeof(myclass-type)*16];
myclass-type *pb=new (buf) myclass-type; //使用預分配空間來創建對象
// ...
delete [] buf; // 無須 delete pb。
名字空間namespace
1、namespace的定義可以是不連續的(即namespace的定義是可以積累的),即,同一個namespace可以在不同的文件中定義,分散在不同文件中的同一個namespace中的內容彼此可見。這對生成一個庫很有幫助,可以使我們更容易將庫的源代碼組織成接口和實現部分。如:在頭文件(.h文件)的名字空間部分定義庫接口;在實現文件(如.c或.cpp文件)的名字空間部分定義庫實現。名字空間定義可積累的特性是“向用戶隱藏實現細節”必需的,它允許把不同的實現文件(如.c或.cpp文件)編譯鏈接到一個程序中,而不會有編譯錯誤和鏈接錯誤。
2、全局名字空間成員,可以用“::member_name”的方式引用。當全局名字空間的成員被嵌套的局部域中聲明的名字隱藏時,就可以采用這種方法引用全局名字空間成員。
3、名字空間成員可以被定義在名字空間之外。但是,只有包圍該成員聲明的名字空間(也就是該成員聲明所在的名字空間及其
外圍名字空間)才可以包含它的定義。
尤其要注意的是#include語句的次序。假定名字空間成員mynamespace::member_i的聲明在文件dec.h中,且#include "dec.h"語句置于
全局名字空間,那么在include語句之后定義的其他名字空間內,mynamespace::member_i的聲明均可見。即,mynamespace::member_i可以在#include "dec.h"之后的任何地方任何名字空間內定義。
4、未命名的名字空間。我們可以用未命名的名字空間聲明一個局部于某一文件的實體。未命名的名字空間可以namespace開頭,其后不需名字,而用一對花括號包含名字空間聲明塊。如:
代碼:
// 其他代碼略
namespace
{
void mesg()
{
cout<<"**********\n";
}
}
int main()
{
mesg(); //正確
//...
return 0;
}
由于未命名名字空間的成員是程序實體,所以mesg()可以在程序整個執行期間被調用。但是,未命名名字空間成員只在特定的文件中可見,在構成程序的其他文件中是不可以見的。未命名名字空間的成員與被聲明為static的全局實體具有類似的特性。在C中,被聲明為static的全局實體在聲明它的文件之外是不可見的。
using關鍵字 1、using聲明與using指示符:前者是聲明某名字空間內的一個成員,后者是使用整個名字空間。例如:
代碼:
using cpp_primer::matrix; // ok,using聲明
using namespace cpp_primer; //ok,using指示符
2、 該using指示符語句可以加在程序文件的幾乎任何地方,包括文件開頭(#include語句之前)、函數內部。不過用using指定的名字空間作用域(生命周期)受using語句所在位置的生命周期約束。如,函數內部使用“using namespace myspacename;”則 myspacename僅在該函數內部可見。
3、可以用using語句指定多個名字空間,使得多個名字空間同時可見。但這增加了名字污染的可能性,而且只有在
使用各名字空間相同成員時由多個using指示符引起的二義性錯誤才能被檢測到,這將給程序的檢測、擴展、移植帶來很大的隱患。因此,因該盡量使用using聲明而不是濫用using指示符。
重載函數 1、如果兩個函數的參數表中參數的個數或者類型不同,則認為這兩個函數是重載的。
如果兩個函數的返回類型和參數表精確匹配,則第二個聲明被視為第一個的重復聲明,與參數名無關。如 void print(string& str)與void print(string&)是一樣的。
如果兩個函數的參數表相同,但是返回類型不同,則第二個聲明被視為第一個的錯誤重復聲明,會標記為編譯錯誤。
如果在兩個函數的參數表中,只有缺省實參不同,則第二個聲明被視為第一個的重復聲明。如int max(int *ia,int sz)與int max(int *, int=10)。
參數名類型如果是由typedef提供的,并不算作新類型,而應該當作typedef的原類型。
當參數類型是const或者volatile時,分兩種情況:對于實參按值傳遞時,const、volatile修飾符可以忽略;對于把const、volatile應用在指針或者引用參數指向的類型時,const、volatile修飾符對于重載函數的聲明是有作用的。例如:
代碼:
//OK,以下兩個聲明其實一樣
void func(int i);
void func(const int i);
//Error,無法通過編譯,因為func函數被定義了兩次。
void func(int i){}
void func(const int i){}
//OK,聲明了不同的函數
void func2(int *);
void func2(const int *);
//OK,聲明了不同的函數
void func3(int&);
void func3(const int&);
2、鏈接指示符extern "C"只能指定重載函數集中的一個函數。原因與內部名編碼有關,在大多數編譯器內部,每個函數明及其相關參數表都被作為一個惟一的內部名編碼,一般的做法是把參數的個數和類型都進行編碼,然后將其附在函數名后面。但是這種編碼不使用于用鏈接指示符extern "C"聲明的函數,這就是為什么在重載函數集合中只有一個函數可以被聲明為extern "C"的原因,具有不同的參數表的兩個extern "C"的函數會被鏈接編輯器視為同一函數。例如,包含以下兩個聲明的程序是非法的。
代碼:
//error:一個重載函數集中有兩個extern "C"函數
extern "C" void print(const char*);
extern "C" void print(int);
函數模板 1、定義函數模板:
代碼:
template <typename/class identifier, ...>
[inline/extern]
ReturnType FunctionName(FuncParameters...)
{
//definition of a funciton template...
}