C++ Primer讀書筆記

標題: C++ Primer讀書筆記


前些日子開始看《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語句後面不能緊接右花括號'}',如
代碼:
label8: }
辦法是在冒號後面加一個空語句(一個';'即可),如
代碼:
label7: ;}
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... }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章