2008年8月6日
我們在學(xué)習(xí)C++的時候,有很多人不知怎樣學(xué)習(xí)它、怎樣學(xué)好它,也不知應(yīng)先從那里開始學(xué)起,關(guān)于C++的入門其實很簡單,你只要一步步按照你手中的那本C++教材來學(xué)就可以了,也許你把C++學(xué)的很爛,這時你千萬不要認為我好像很精通C++了。
我個人認為要想學(xué)習(xí)C++,最好直接學(xué)習(xí)它,不要先學(xué)習(xí)C語言,然后在學(xué)習(xí)C++,雖然C++是從C語言上發(fā)展過來的,但如果你對C語言了解的越
多,在你寫C++程序的時候,你很難擺脫C的風(fēng)格,既使你是一位很有經(jīng)驗的程序員,如果你對C很了解,在學(xué)習(xí)C++的時候,盡量使用C++的風(fēng)格,我這樣
并不是說C不好,關(guān)鍵我們現(xiàn)在要了解的是C++而不是C。
現(xiàn)在讓我們深入學(xué)習(xí)C++吧!C++的難學(xué),不僅在它那廣博的語法、語法背后的語義、語義背后的深層思維、深層思維背后的對像模型;C++的難
學(xué),還在與它提供了四種不同的編程思維模型。當我們找來一本C++教材時,當我們翻開第一頁時,這時我們已進入了C++的世界,我們現(xiàn)在開始探索,開始在
追求新技術(shù)的旅程中!
想學(xué)好C++,熟練掌握它的語法是不可少的,當你掌握了C++的語法時,那么我要恭喜你,你已正正進入了C++的世界,要想學(xué)好C++,你只有努
力的學(xué)習(xí),經(jīng)常的思考多多的實踐,這時你會問了,我應(yīng)該還要學(xué)習(xí)什么呢?
C++的語法我都已掌握了啊!我是不是可以學(xué)習(xí)Windows編程了呢?不要急,你是已掌握了C++的語法,但你能用它寫出高效率的程序嗎?你已對C++
所有運行機制都了解嗎?是的,單單了解C++語法是不夠的,接下來你的任務(wù)很多,要學(xué)習(xí)如何高效地使用C++語言。現(xiàn)在我就教你怎樣的學(xué)好它,怎樣的高效
使用它。
我們還是先從C++的語法開始說起吧!這里我只做一個簡單的概述,當我們學(xué)習(xí)C++的時候,你先要了解它的編程模式,其中包括面向?qū)ο窬幊獭⑼ㄓ?
編程和傳統(tǒng)的過程化編程。當你在學(xué)習(xí)一個C++語法時,如果你一時感到很難理解,不妨你先跳過這一段,繼續(xù)向后學(xué)習(xí),當你看完你所學(xué)習(xí)C++的那本教材
時,你在回過頭來學(xué)習(xí)C++,你會發(fā)現(xiàn)其實它就是那么回事,有很多人在學(xué)習(xí)C++時,剛學(xué)習(xí)到了一半,突然感到好像以前學(xué)習(xí)的語法忘了許多,他們會把書又
翻回去,找回那忘掉的語法,如果你在學(xué)習(xí)C++時也有這樣的情況,你大可不必那么擔心,你現(xiàn)在的任務(wù)是繼續(xù)你的學(xué)習(xí),不要去管那一時不記得的語法,如果你
現(xiàn)在去重新學(xué)習(xí)那一時忘掉的C++,恩,不錯,這看起來你好像對那語法已深深的牢記在心,當你的C++在學(xué)習(xí)到這里時,你能保證前面的語法不在遺忘嗎?這
時的你在學(xué)習(xí)新的C++語法時,但心會忘掉前面剛剛找回的C++,你說這時你能學(xué)好新的C++語法嗎?你會一邊學(xué)習(xí)新的,一邊重復(fù)舊的,這樣一來,那就糟
了,這時的你會很容易搞亂新舊C++語法,新的記不住,舊的又被新的語法搞亂了,這時的你不得不從頭再來(畢竟你是初學(xué)者)。
對于初學(xué)者來說,C++的廣博語法是件頭疼的事,學(xué)會了這個卻忘了那個,就像我上面提到的那樣,這時的你應(yīng)該繼續(xù)的學(xué)習(xí)C++新知識,等看完你手
中的那本C++教材時,你在來學(xué)習(xí)忘掉的語法,這時你會感覺好像C++很簡單,沒有我們開始說的那么難學(xué)啊!你會覺得我開始說C++難學(xué)是用來嚇唬人的。
我說C++難學(xué)當然不是用來嚇唬人的,這時的你對C++語法已非常熟悉了,這時你千萬不要認為對C++已很精通,就像我開頭所說的那樣,雖然現(xiàn)在你已擺脫
了初學(xué)著的稱呼,但你也不能算是位精通人士啊!你只掌握了C++的大概,接下來的你就要深入學(xué)習(xí)拉!
2008年3月14日
本文的寫作目的并不在于提供C/C++程序員求職面試指導(dǎo),而旨在從技術(shù)上分析面試題的內(nèi)涵。文中的大多數(shù)面試題來自各大論壇,部分試題解答也參考了網(wǎng)友的意見。
許多面試題看似簡單,卻需要深厚的基本功才能給出完美的解答。企業(yè)要求面試者寫一個最簡單的strcpy函數(shù)都可看出面試者在技術(shù)上究竟達到了怎樣的程
度,我們能真正寫好一個strcpy函數(shù)嗎?我們都覺得自己能,可是我們寫出的strcpy很可能只能拿到10分中的2分。讀者可從本文看到strcpy
函數(shù)從2分到10分解答的例子,看看自己屬于什么樣的層次。此外,還有一些面試題考查面試者敏捷的思維能力。
分析這些面試題,本身包含很強的趣味性;而作為一名研發(fā)人員,通過對這些面試題的深入剖析則可進一步增強自身的內(nèi)功。
2.找錯題
試題1:
void test1()
{
char string[10];
char* str1 = "0123456789";
strcpy( string, str1 );
}
試題2:
void test2()
{
char string[10], str1[10];
int i;
for(i=0; i<10; i++)
{
str1
= 'a';
}
strcpy( string, str1 );
}
試題3:
void test3(char* str1)
{
char string[10];
if( strlen( str1 ) <= 10 )
{
strcpy( string, str1 );
}
}
解答:
試題1字符串str1需要11個字節(jié)才能存放下(包括末尾的’\0’),而string只有10個字節(jié)的空間,strcpy會導(dǎo)致數(shù)組越界;
對試題2,如果面試者指出字符數(shù)組str1不能在數(shù)組內(nèi)結(jié)束可以給3分;如果面試者指出strcpy(string,
str1)調(diào)用使得從str1內(nèi)存起復(fù)制到string內(nèi)存起所復(fù)制的字節(jié)數(shù)具有不確定性可以給7分,在此基礎(chǔ)上指出庫函數(shù)strcpy工作方式的給10
分;
對試題3,if(strlen(str1) <= 10)應(yīng)改為if(strlen(str1) < 10),因為strlen的結(jié)果未統(tǒng)計’\0’所占用的1個字節(jié)。
剖析:
考查對基本功的掌握:
(1)字符串以’\0’結(jié)尾;
(2)對數(shù)組越界把握的敏感度;
(3)庫函數(shù)strcpy的工作方式,如果編寫一個標準strcpy函數(shù)的總分值為10,下面給出幾個不同得分的答案:
2分
void strcpy( char *strDest, char *strSrc )
{
while( (*strDest++ = * strSrc++) != ‘\0’ );
}
4分
void strcpy( char *strDest, const char *strSrc )
//將源字符串加const,表明其為輸入?yún)?shù),加2分
{
while( (*strDest++ = * strSrc++) != ‘\0’ );
}
7分
void strcpy(char *strDest, const char *strSrc)
{
//對源地址和目的地址加非0斷言,加3分
assert( (strDest != NULL) && (strSrc != NULL) );
while( (*strDest++ = * strSrc++) != ‘\0’ );
}
10分
//為了實現(xiàn)鏈式操作,將目的地址返回,加3分!
char * strcpy( char *strDest, const char *strSrc )
{
assert( (strDest != NULL) && (strSrc != NULL) );
char *address = strDest;
while( (*strDest++ = * strSrc++) != ‘\0’ );
return address;
}
從2分到10分的幾個答案我們可以清楚的看到,小小的strcpy竟然暗藏著這么多玄機,真不是蓋的!需要多么扎實的基本功才能寫一個完美的strcpy啊!
(4)對strlen的掌握,它沒有包括字符串末尾的'\0'。
讀者看了不同分值的strcpy版本,應(yīng)該也可以寫出一個10分的strlen函數(shù)了,完美的版本為: int strlen( const char *str ) //輸入?yún)?shù)const
{
assert( strt != NULL ); //斷言字符串地址非0
int len;
while( (*str++) != '\0' )
{
len++;
}
return len;
}
試題4:
void GetMemory( char *p )
{
p = (char *) malloc( 100 );
}
void Test( void )
{
char *str = NULL;
GetMemory( str );
strcpy( str, "hello world" );
printf( str );
}
試題5:
char *GetMemory( void )
{
char p[] = "hello world";
return p;
}
void Test( void )
{
char *str = NULL;
str = GetMemory();
printf( str );
}
試題6:
void GetMemory( char **p, int num )
{
*p = (char *) malloc( num );
}
void Test( void )
{
char *str = NULL;
GetMemory( &str, 100 );
strcpy( str, "hello" );
printf( str );
}
試題7:
void Test( void )
{
char *str = (char *) malloc( 100 );
strcpy( str, "hello" );
free( str );
... //省略的其它語句
}
解答:
試題4傳入中GetMemory( char *p )函數(shù)的形參為字符串指針,在函數(shù)內(nèi)部修改形參并不能真正的改變傳入形參的值,執(zhí)行完
char *str = NULL;
GetMemory( str );
后的str仍然為NULL;
試題5中
char p[] = "hello world";
return p;
的p[]數(shù)組為函數(shù)內(nèi)的局部自動變量,在函數(shù)返回后,內(nèi)存已經(jīng)被釋放。這是許多程序員常犯的錯誤,其根源在于不理解變量的生存期。
試題6的GetMemory避免了試題4的問題,傳入GetMemory的參數(shù)為字符串指針的指針,但是在GetMemory中執(zhí)行申請內(nèi)存及賦值語句 tiffany
bracelets
*p = (char *) malloc( num );
后未判斷內(nèi)存是否申請成功,應(yīng)加上:
if ( *p == NULL )
{
...//進行申請內(nèi)存失敗處理
}
試題7存在與試題6同樣的問題,在執(zhí)行
char *str = (char *) malloc(100);
后未進行內(nèi)存是否申請成功的判斷;另外,在free(str)后未置str為空,導(dǎo)致可能變成一個“野”指針,應(yīng)加上:
str = NULL;
試題6的Test函數(shù)中也未對malloc的內(nèi)存進行釋放。
剖析:
試題4~7考查面試者對內(nèi)存操作的理解程度,基本功扎實的面試者一般都能正確的回答其中50~60的錯誤。但是要完全解答正確,卻也絕非易事。
對內(nèi)存操作的考查主要集中在:
(1)指針的理解;
(2)變量的生存期及作用范圍;
(3)良好的動態(tài)內(nèi)存申請和釋放習(xí)慣。
再看看下面的一段程序有什么錯誤:
swap( int* p1,int* p2 )
{
int *p;
*p = *p1;
*p1 = *p2;
*p2 = *p;
}
在swap函數(shù)中,p是一個“野”指針,有可能指向系統(tǒng)區(qū),導(dǎo)致程序運行的崩潰。在VC++中DEBUG運行時提示錯誤“Access Violation”。該程序應(yīng)該改為:
swap( int* p1,int* p2 )
{
int p;
p = *p1;
*p1 = *p2;
*p2 = p;
}
2008年1月11日
我們現(xiàn)在編寫一個程序越來越容易了。利用一些軟件開發(fā)工具,往往只要通過鼠標的拖拖點點,計算機就會自動幫你生成許多代碼。但在很多時候,計算機的這種能力被濫用了,我們往往只考慮把這個程序搭起來,而不去考慮程序的性能如何,程序是否足夠的健壯。而此節(jié)課的目的主要是介紹一些編碼的經(jīng)驗,讓大家編寫的程序更加健壯和高性能。
在C++編程中應(yīng)該盡量使用const和inline來代替#define,盡量做到能不用#define就不用。#define常見的用途有“定義常量”以及“定義宏”,但其中存在諸多的弊病。
第一,查錯不直觀,不利于調(diào)試。Define的定義是由預(yù)處理程序處理的,作的是完全的文本替換,不做任何的類型檢查。在編譯器處理階段,define定義的東西已經(jīng)被完全替換了,這樣在debug的時候就看不到任何的相關(guān)信息,即跟蹤時不能step into宏。例如,把ASPECT_RATIO用define定義成1.653,編譯器就看不到ASPECT_RATIO這個名字了。如果編譯器報1.653錯,那么就無從知道此1.653來自于何處。在真正編碼的時候應(yīng)該使用如下的語句來定義:
static const double ASPECT_RATIO = 1.653;
第二,沒有任何類型信息,不是type safe。因為它是文本級別的替換,這樣不利于程序的維護。
第三,define的使用很容易造成污染。比如,如果有兩個頭文件都定義了ASPECT_RATIO, 而一個CPP文件又同時包含了這兩個頭文件,那么就會造成沖突。更難查的是另外一種錯誤,比如有如下的代碼:
// in header file def.h
#define Apple 1
#define Orange 2
#define Pineapple 3
…
// in some cpp file that includes the def.h
enum Colors {White, Black, Purple, Orange};
在.h文件中Orange被定義成水果的一種,而在.cpp文件中Orange又成為了一種顏色,那么編譯器就會把此處的Orange替換成2,編譯可能仍然可以通過,程序也能夠運行,但是這就成了一個bug,表現(xiàn)出古怪的錯誤,且很難查錯。再比如定義了一個求a與b哪個數(shù)大的宏,#define max(a,b) ((a) > (b) ? (a) : (b))
int a = 5, b = 0;
max(++ a, b);
max(++ a, b + 10);
190-823 117-202 在上面的操作中,max(++ a, b); 語句中a被++了兩次,而max(++ a, b + 10); 語句中a只加了一次,這樣在程序處理中就很有可能成為一個bug,且此bug也非常的難找。在實際編碼時可以使用如下的語句來做:
template<class T>
inline const T&
max(const T& a, const T& b) { return a > b ? a : b; }
2、Prefer C++-style casts 在程序中經(jīng)常會需要把一種類型轉(zhuǎn)換成另外一種類型,在C++中應(yīng)該使用static_cast、const_cast、dynamic_cast、reinterpret_cast關(guān)鍵字來做類型轉(zhuǎn)換。因為這有以下好處,一是其本身就是一種注釋,在代碼中看到上面這些關(guān)鍵字就可馬上知道此處是進行類型轉(zhuǎn)換。二是C語言中類型轉(zhuǎn)換通常是很難進行搜索的,而通過關(guān)鍵字cast則可以很容易的找到程序中出現(xiàn)類型轉(zhuǎn)換的地方了。
3、Distinguish between prefix and postfix forms of increment and decrement operators 通常對于操作系統(tǒng)或編譯器自身支持的類型,prefix(前綴,如++i)與postfix(后綴,如i++)的效果是一樣的。因為現(xiàn)在的編譯器都很聰明,它會自動做優(yōu)化,這兩者的匯編代碼是一樣的,性能不會有差別。但有時候也會有不同的,如一些重載了操作符的類型。下面是模擬prefix與postfix的操作過程,可以發(fā)現(xiàn)在postfix操作中會生成一個臨時變量,而這一臨時變量是會占用額外的時間和開銷的。
// prefix form: increment and fetch
UPInt& UPInt::operator++()
{
*this += 1; // increment
return *this; // fetch
}
// postfix form: fetch and increment
const UPInt UPInt::operator++(int)
{
UPInt oldValue = *this; // fetch
++(*this); // increment
return oldValue; // return what was fetched
}
一般情況下不需要區(qū)分是先++,還是后++,但是我們在編寫程序的時候最好能習(xí)慣性的將其寫成++i的形式,如在使用STL中的iterator時,prefix與postfix會有相當大的性能差異。請不要小看這些細節(jié),實際在編寫程序的時候,若不注意具體細節(jié),你會發(fā)現(xiàn)程序的性能會非常的低。但要注意,雖然在大多數(shù)情況下可以用prefix來代替postfix,但有一種情況例外,那就是有[]操作符時,比如gzArray [++index] 是不等于 gzArray[index++]的。
4、Minimizing Compile-time Dependencies
有些人在編寫程序時,往往喜歡將一個.h文件包含到另一個.h文件,而實踐證明在做大型軟件時這是一個非常不好的習(xí)慣,因這樣會造成很多依賴的問題,包含較多的.h文件,別人又使用了這個class,而在他的那個工程中可能并不存在這些.h文件,這樣很可能就編譯不能通過。而且這樣做,還可能造成很難去更新一個模塊的情況。因為一個.h文件被很多模塊包含的話,如果修改了此.h文件,在編譯系統(tǒng)的時候,編譯器會去尋找哪些模塊依賴于某個被修改過的.h文件,那么就導(dǎo)致了所有包含入此.h文件的模塊全都要進行重新編譯。在項目比較小的時候,大家可能還感覺不到差別,但是如果說是在大型的軟件系統(tǒng)里,你可能編譯一遍源碼需要七、八個小時。如果你這個.h文件被很多模塊包含的話,就算在.h文件中加了一行注釋,在編譯時編譯器檢查哪些文件被改動,那么所有包含入此.h文件的模塊都會被重新編譯,造成巨大的時間和精力負擔。對于此問題,解決的方法就是讓.h文件自包含,也就是說讓它包含盡量少的東西。所謂盡量少是指如刪掉任何一個它包含進來的.h文件,都將無法正常進行工作。其實在很多情況下,并不需要一個.h文件去包含另一個.h文件,完全可以通過class聲明來解決依賴關(guān)系的這種問題。再來看下面這個例子:1Z0-043
#include "a.h" // class A
#include "b.h" // class B
#include "c.h" // class C
#include "d.h" // class D
#include "e.h" // class E
class X : public A, private B
{
public:
E SomeFunctionCall(E someParameter);
private:
D m_dInstance;
};
當類X從類A和類B中派生時,需要知道X在內(nèi)存中都有哪些data,通常在內(nèi)存中前面是基類的data,后面緊跟的是此派生類自身定義的data,因此就必須知道類A與類B的內(nèi)部細節(jié),要不然編譯器就無法來安排內(nèi)存了。但是在處理參數(shù)以及參數(shù)返回值的時候,實際上并不需要知道這些信息,在此處定義的SomeFunctionCall()只需知道E是個class就足夠了,并不需要知道類E中的data如長度等的具體細節(jié)。上面的代碼應(yīng)該改寫成如下的形式,以減少依賴關(guān)系:
#include "a.h" // class A
#include "b.h" // class B
#include "c.h" // class C
#include "d.h" // class D
class E;
class X : public A, private B
{
public:
E SomeFunctionCall(E someParameter);
private:
D m_dInstance;
};
5、Never treat arrays polymorphically
不要把數(shù)組和多態(tài)一起使用,請看下面的例子。
class BST { ... };
class BalancedBST: public BST { ... };
void printBSTArray(ostream& s, const BST array[], int numElements)
{
for (int i = 0; i < numElements; ++i)
{
s << array[i];
// this assumes an operator<< is defined for BST
}
}
BalancedBST bBSTArray[10];
printBSTArray(cout, bBSTArray, 10);
數(shù)組在內(nèi)存中是一個連續(xù)的內(nèi)存空間,而在數(shù)組中應(yīng)該如何來定位一個元素呢?過程是這樣的,編譯器可以知道每個數(shù)據(jù)類型的長度大小,如果數(shù)組的index是0,則會自動去取第一個元素;如果是指定了某個index,編譯器則會根據(jù)此index與該數(shù)據(jù)類型的長度自動去算出該元素的位置。
在printBSTArray()函數(shù)中,盡管傳入的參數(shù)是BalancedBST類型,但由于其本來定義的類型是BST,那么它依然會根據(jù)BST來計算類型的長度。而通常派生類實例所占的內(nèi)存要比基類實例所占的內(nèi)存大一些,因此該程序在編譯時會報錯。請記住,永遠不要把數(shù)組和C++的多態(tài)性放在一起使用。
6、Prevent exceptions from leaving destructors
析構(gòu)函數(shù)中一定不要拋出異常。通常有兩種情況會導(dǎo)致析構(gòu)函數(shù)的調(diào)用,一種是當該類的對象離開了它的域,或delete表達式中一個該類對象的指針,另一種是由于異常而引起析構(gòu)函數(shù)的調(diào)用。
如果析構(gòu)函數(shù)被調(diào)用是由于exception引起,而此時在析構(gòu)函數(shù)中又拋出了異常,程序會立即被系統(tǒng)終止,甚至都來不及進行內(nèi)存釋放。因此如果在析構(gòu)函數(shù)中拋出異常的話,就很容易混淆引起異常的原因,且這樣的軟件也會讓用戶非常惱火。由于析構(gòu)函數(shù)中很可能會調(diào)用其它的一些函數(shù),所以在寫析構(gòu)函數(shù)的時候一定要注意,對這些函數(shù)是否會拋出異常要非常清楚,如果會的話,就一定要小心了。比如下面這段代碼:
Session::~Session()
{
logDestruction(this);
}
比如logDestruction()函數(shù)可能會拋出異常,那么我們就應(yīng)該采用下面這種代碼的形式:
Session::~Session()
{
try
{
logDestruction(this);
}
catch (...)
{
}
}
這樣程序出錯的時候不會被立即關(guān)掉,可以給用戶一些其它的選擇,至少先讓他把目前在做的工作保存下來。
7、Optimization:Remember the 80-20 rule
在軟件界有一個20-80法則,其實這是一個很有趣的現(xiàn)象,比如一個程序中20%的代碼使用了該程序所占資源的80%;一個程序中20%的代碼占用了總運行時間的80%;一個程序中20%的代碼使用了該程序所占內(nèi)存的80%;在20%的代碼上面需要花費80%的維護力量,等等。這個規(guī)律還可以被繼續(xù)推廣下去,不過這個規(guī)律無法被證明,它是人們在實踐中觀察得出的結(jié)果。從這個規(guī)律出發(fā),我們在做程序優(yōu)化的時候,就有了針對性。比如想提高代碼的運行速度,根據(jù)這個規(guī)律可以知道其中20%的代碼占用了80%的運行時間,因此我們只要找到這20%的代碼,并進行相應(yīng)的優(yōu)化,那么我們程序的運行速度就可以有較大的提高。再如有一個函數(shù),占用了程序80%的運行時間,如果把這個函數(shù)的執(zhí)行速度提高10倍,那么對程序整體性能的提高,影響是非常巨大的。如果有一個函數(shù)運行時間只占總時間的1%,那就算把這個函數(shù)的運行速度提高1000倍,對程序整體性能的提高也是影響不大的。所以我們的基本思想就是找到占用運行時間最大的那個函數(shù),然后去優(yōu)化它,哪怕只是改進了一點點,程序的整體性能也可以被提高很多。
要想找出那20%的代碼,我們的方法就是使用Profiler,它實際上是一些公司所開發(fā)的工具,可以檢查程序中各個模塊所分配內(nèi)存的使用情況,以及每個函數(shù)所運行的時間等。常見的Profiler有Intel公司開發(fā)的VTune,微軟公司開發(fā)的Visual Studio profiler,DevPartner from Compuware等
首先說指導(dǎo)思想。這是一個價值觀問題,我們在此提出三條標準:簡單,高性能,可移植。
我們在開篇就對簡單性目標作了敘述,這里再稍微展開討論一下。我們提出的簡單標準,首先是外部接口簡單,其次是內(nèi)部結(jié)構(gòu)簡單。我們知道,類庫是提供給上層應(yīng)用程序使用的,也就是按照一定的接口規(guī)范,向上層提供一定的功能服務(wù)。接口設(shè)計得越簡單,對上層用戶來說就越方便,就越不容易產(chǎn)生Bug。我們可以注意到,流行的成功類庫都是擁有簡單接口的。為了使接口簡單,常常不得不把有關(guān)具體實現(xiàn)的復(fù)雜性封裝于類庫內(nèi)部,也就是說,關(guān)于簡單性的設(shè)計原則,外部接口簡單優(yōu)先于內(nèi)部實現(xiàn)簡單。
高性能是C++語言優(yōu)于其它OO語言的一個特性。C++的高性能應(yīng)該首先歸于它運行模式,和大多數(shù)OO語言不同,C++程序編譯后直接產(chǎn)生本地平臺代碼(Native Code),理論上具備了可能的最大執(zhí)行性能。另外的一個原因是主流的C++編譯器都被設(shè)計得非常精巧,具有優(yōu)越的代碼優(yōu)化能力。對于C++類庫設(shè)計者來說,保持C++的高性能是一個重要目標。程序的高性能可以從兩方面來評價,一是時間性能,以盡量短的時間來解決盡量多的業(yè)務(wù);二是資源性能,以盡量少的資源消耗,包括CPU使用、內(nèi)存占用、網(wǎng)絡(luò)流量、磁盤空間等等,來維持正常的程序功能。提高性能的主要手段是數(shù)據(jù)結(jié)構(gòu)、算法和程序體現(xiàn)結(jié)構(gòu)的優(yōu)化設(shè)計000-861 117-102 。
再說可移植性。C++的編譯后輸出代碼是本地平臺代碼,因此C++本身不具有目標代碼可移植性,C++的可移植性只能是源代碼可移植性。源代碼的可移植性是指,同一軟件產(chǎn)品的全部或者部分源代碼可以在不同的編譯環(huán)境中進行編譯(不需要編譯的除外),并且其結(jié)果具有相同的品質(zhì)特性(依優(yōu)先順序包括功能性、可靠性、可用性、性能性、可維護性等)。編譯環(huán)境可以大致分為三個層次,最底層的是操作系統(tǒng),也就是平臺(Platform),其次是對源代碼直接進行處理的編譯器,然后是其它在編譯過程中必需的中間件物品,如庫文件等。我們知道C++雖然在語言規(guī)范上獲得了統(tǒng)一(ISO/IEC),其編譯器卻是群雄割據(jù)的局面,具有代表性的有Borland C++系列(已經(jīng)淡出市場),Microsoft的Visual Studio系列的C++編譯器和GNU陣營的壓軸產(chǎn)品gc中的g++。源代碼經(jīng)編譯環(huán)境處理后產(chǎn)生的可執(zhí)行代碼的執(zhí)行平臺稱為目標平臺,不同的編譯器的目標平臺也不同,有的支持多平臺,如g++,有的是單一平臺,如Visual C++。對于類庫設(shè)計者來說,想要獲得完全的可移植性是非常困難的(除非是象STL這樣被納入語言規(guī)范的類庫,因為不支持STL就是不支持標準的C++。即便如此不同的編譯環(huán)境還是存在不同的STL實現(xiàn)版本,造成“一個類庫多個實現(xiàn)”的局面),我們只能有選擇地支持一部分環(huán)境。我們在開篇就已經(jīng)說明,我們選擇g++和Visual C++編譯器,選擇Linux和Windows 32位目標平臺。
接下來我們來討論C++類庫設(shè)計的方法論。
首先,我們采用僅用頭文件的類庫設(shè)計方式(Header-only,STL的大多數(shù)實現(xiàn)版本都是采用Header-only的方式),也就是在頭文件(.h)中聲明和定義類,將其成員函數(shù)全部定義為內(nèi)聯(lián)函數(shù),而不使用源程序文件(.cpp)。
我們知道在C語言的開發(fā)環(huán)境中,所謂庫文件包含兩個部分,頭文件部分和二進制文件部分。根據(jù)二進制文件和用戶目標文件結(jié)合方式的不同,又可分為靜態(tài)鏈接文件和動態(tài)鏈接文件。這種庫的構(gòu)成模式已成為事實上的C語言開發(fā)環(huán)境的標準,絕大多數(shù)平臺、絕大多數(shù)編譯器都使用這種模式 117-301 190-721 。
然而C++語言開發(fā)環(huán)境,這種庫構(gòu)成模式遭遇到一個重大問題,就是符號命名問題。舉例來說,C++允許多個函數(shù)可以被重載(Overload),可以具有相同的名稱,而通過參數(shù)列表不同被予以區(qū)別。這樣就帶來一個問題,編譯完成的目標代碼中怎樣來區(qū)別這些在源代碼中具有相同名稱的函數(shù)?常見的做法是在編譯器輸出的函數(shù)的符號名稱中加入描述類型信息的字符串,這種方法通常被稱為名稱裝飾(Name decoration)或者名稱糟化(Name mangling,這個術(shù)語真不好翻譯,筆者的感覺是發(fā)明這個詞的人覺得編譯器把本來簡單干凈的符號給搞亂了)。比如說,g++3.4.4對于函數(shù)void func(int),其編譯輸出符號名稱為_Z1funci,對于函數(shù)void func(int, int),其輸出符號名稱為_Z1funcii,等等。但是,這種名稱裝飾規(guī)則沒有統(tǒng)一規(guī)范,也就是說不同的編譯器有各自不同的名稱裝飾規(guī)則,這樣就導(dǎo)致不同的C++編譯器只能識別自己的輸出文件,而沒有辦法處理其他編譯器的輸出文件。因此,如果將C++程序制作成二進制的庫文件,則其能夠支持的開發(fā)環(huán)境只能限于原始的開發(fā)環(huán)境,基本上不具有多種開發(fā)環(huán)境間的通用性。
一個解決辦法是將庫文件保持在源代碼形態(tài)(包括頭文件和源文件),而不編譯成二進制文件。比如STL的許多實現(xiàn)版本都是以頭文件形式存在。這樣雖然解決了名稱裝飾所帶來的不可移植問題,但同時又會帶來代碼編譯時間增長,源代碼完全公開等問題。在C++的名稱裝飾規(guī)則未被統(tǒng)一之前,看起來這個問題是很難兩全其美地解決了。
在本系列中,我們也仿照g++的STL實現(xiàn)方式,完全以頭文件形式來編寫類庫。為什么不把代碼放到源文件中去呢?主要原因是,頭文件只需要用戶使用包含指令(#include)就可以處理了,而源文件則需要配置到用戶工程的編譯目標列表中,和用戶的源程序形成共同編譯的形式,破壞了用戶工程的編譯目標的封閉性,比較麻煩而且不符合軟件開發(fā)的一般習(xí)慣。
其次我們來討論如何支持多平臺。我們已經(jīng)說過在本系列中我們的線程庫支持Linux平臺的Posix線程和Windows 32位平臺的線程模式。我們可以參考C++的Pimpl“慣語”(Pimpl idiom,在Herb Sutter的《Exceptional C++》中有介紹),采用2層類構(gòu)造方式。上次類亦即接口類,為用戶提供統(tǒng)一的類接口,在用戶看來具有唯一的類行為定義;下層類亦即實現(xiàn)類,將接口類的行為定義轉(zhuǎn)化為某個平臺的具體實現(xiàn)。
2008年1月3日
1.引言
C++語言的創(chuàng)建初衷是“a better
C”,但是這并不意味著C++中類似C語言的全局變量和函數(shù)所采用的編譯和連接方式與C語言完全相同。作為一種欲與C兼容的語言,C++保留了一部分過程式語言的特點(被世人稱為“不徹底地面向?qū)ο?#8221;),因而它可以定義不屬于任何類的全局變量和函數(shù)。但是,C++畢竟是一種面向?qū)ο蟮某绦蛟O(shè)計語言,為了支持函數(shù)的重載,C++對全局函數(shù)的處理方式與C有明顯的不同。
2.從標準頭文件說起
某企業(yè)曾經(jīng)給出如下的一道面試題:
面試題
為什么標準頭文件都有類似以下的結(jié)構(gòu)?
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef
__cplusplus
extern "C" {
#endif
/*...*/
#ifdef
__cplusplus
}
#endif
#endif /* __INCvxWorksh
*/
分析 顯然,頭文件中的編譯宏“#ifndef __INCvxWorksh、#define
__INCvxWorksh、#endif” 的作用是防止該頭文件被重復(fù)引用。
那么
#ifdef __cplusplus
extern "C" {
#endif
#ifdef
__cplusplus
}
#endif
的作用又是什么呢?我們將在下文一一道來。
3.深層揭密extern
"C"
extern "C"
包含雙重含義,從字面上即可得到:首先,被它修飾的目標是“extern”的;其次,被它修飾的目標是“C”的。讓我們來詳細解讀這兩重含義。
被extern
"C"限定的函數(shù)或變量是extern類型的;
extern是C/C++語言中表明函數(shù)和全局變量作用范圍(可見性)的關(guān)鍵字,該關(guān)鍵字告訴編譯器,其聲明的函數(shù)和變量可以在本模塊或其它模塊中使用。記住,下列語句:
extern int
a;
僅僅是一個變量的聲明,其并不是在定義變量a,并未為a分配內(nèi)存空間。變量a在所有模塊中作為一種全局變量只能被定義一次,否則會出現(xiàn)連接錯誤。
通常,在模塊的頭文件中對本模塊提供給其它模塊引用的函數(shù)和全局變量以關(guān)鍵字extern聲明。例如,如果模塊B欲引用該模塊A中定義的全局變量和函數(shù)時只需包含模塊A的頭文件即可。這樣,模塊B中調(diào)用模塊A中的函數(shù)時,在編譯階段,模塊B雖然找不到該函數(shù),但是并不會報錯;它會在連接階段中從模塊A編譯生成的目標代碼中找到此函數(shù)。
與extern對應(yīng)的關(guān)鍵字是static,被它修飾的全局變量和函數(shù)只能在本模塊中使用。因此,一個函數(shù)或變量只可能被本模塊使用時,其不可能被extern
“C”修飾。
被extern "C"修飾的變量和函數(shù)是按照C語言方式編譯和連接的;
未加extern
“C”聲明時的編譯方式 首先看看C++中對類似C的函數(shù)是怎樣編譯的。
作為一種面向?qū)ο蟮恼Z言,C++支持函數(shù)重載,而過程式語言C則不支持。函數(shù)被C++編譯后在符號庫中的名字與C語言的不同。例如,假設(shè)某個函數(shù)的原型為:
void foo( int x, int y
);
該函數(shù)被C編譯器編譯后在符號庫中的名字為_foo,而C++編譯器則會產(chǎn)生像_foo_int_int之類的名字(不同的編譯器可能生成的名字不同,但是都采用了相同的機制,生成的新名字稱為“mangled
name”)。
_foo_int_int這樣的名字包含了函數(shù)名、函數(shù)參數(shù)數(shù)量及類型信息,C++就是靠這種機制來實現(xiàn)函數(shù)重載的。例如,在C++中,函數(shù)void
foo( int x, int y )與void foo( int x, float y
)編譯生成的符號是不相同的,后者為_foo_int_float。
同樣地,C++中的變量除支持局部變量外,還支持類成員變量和全局變量。用戶所編寫程序的類成員變量可能與全局變量同名,我們以"."來區(qū)分。而本質(zhì)上,編譯器在進行編譯時,與函數(shù)的處理相似,也為類中的變量取了一個獨一無二的名字,這個名字與用戶程序中同名的全局變量名字不同。
未加extern
"C"聲明時的連接方式 假設(shè)在C++中,模塊A的頭文件如下:
// 模塊A頭文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
int foo(
int x, int y );
#endif
在模塊B中引用該函數(shù):
// 模塊B實現(xiàn)文件 moduleB.cpp
#include "moduleA.h"
foo(2,3);
實際上,在連接階段,連接器會從模塊A生成的目標文件moduleA.obj中尋找_foo_int_int這樣的符號!
加extern
"C"聲明后的編譯和連接方式
加extern "C"聲明后,模塊A的頭文件變?yōu)椋?br>
// 模塊A頭文件 moduleA.h
#ifndef MODULE_A_H
#define MODULE_A_H
extern "C"
int foo( int x, int y );
#endif
在模塊B的實現(xiàn)文件中仍然調(diào)用foo( 2,3
),其結(jié)果是:
(1)模塊A編譯生成foo的目標代碼時,沒有對其名字進行特殊處理,采用了C語言的方式;
(2)連接器在為模塊B的目標代碼尋找foo(2,3)調(diào)用時,尋找的是未經(jīng)修改的符號名_foo。
如果在模塊A中函數(shù)聲明了foo為extern
"C"類型,而模塊B中包含的是extern int foo( int x, int y )
,則模塊B找不到模塊A中的函數(shù);反之亦然。
所以,可以用一句話概括extern
“C”這個聲明的真實目的(任何語言中的任何語法特性的誕生都不是隨意而為的,來源于真實世界的需求驅(qū)動。我們在思考問題時,不能只停留在這個語言是怎么做的,還要問一問它為什么要這么做,動機是什么,這樣我們可以更深入地理解許多問題):
實現(xiàn)C++與C及其它語言的混合編程。
明白了C++中extern
"C"的設(shè)立動機,我們下面來具體分析extern "C"通常的使用技巧。
4.extern
"C"的慣用法
(1)在C++中引用C語言中的函數(shù)和變量,在包含C語言頭文件(假設(shè)為cExample.h)時,需進行下列處理:
extern "C"
{
#include "cExample.h"
}
而在C語言的頭文件中,對其外部函數(shù)只能指定為extern類型,C語言中不支持extern "C"聲明,在.c文件中包含了extern
"C"時會出現(xiàn)編譯語法錯誤。
筆者編寫的C++引用C函數(shù)例子工程中包含的三個文件的源代碼如下:
/* c語言頭文件:cExample.h */
#ifndef C_EXAMPLE_H
#define
C_EXAMPLE_H
extern int add(int x,int y);
#endif
/* c語言實現(xiàn)文件:cExample.c
*/
#include "cExample.h"
int add( int x, int y )
{
return x +
y;
}
// c++實現(xiàn)文件,調(diào)用add:cppFile.cpp
extern "C"
{
#include
"cExample.h"
}
int main(int argc, char* argv[])
{
add(2,3);
return 0;
}
如果C++調(diào)用一個C語言編寫的.DLL時,當包括.DLL的頭文件或聲明接口函數(shù)時,應(yīng)加extern "C"
{ }。
(2)在C中引用C++語言中的函數(shù)和變量時,C++的頭文件需添加extern "C",但是在C語言中不能直接引用聲明了extern
"C"的該頭文件,應(yīng)該僅將C文件中將C++中定義的extern
"C"函數(shù)聲明為extern類型。
筆者編寫的C引用C++函數(shù)例子工程中包含的三個文件的源代碼如下:70-210 1Y0-327
//C++頭文件 cppExample.h
#ifndef CPP_EXAMPLE_H
#define
CPP_EXAMPLE_H
extern "C" int add( int x, int y );
#endif
//C++實現(xiàn)文件
cppExample.cpp
#include "cppExample.h"
int add( int x, int y
)
{
return x + y;
}
/* C實現(xiàn)文件 cFile.c
/* 這樣會編譯出錯:#include
"cExample.h" */
extern int add( int x, int y );
int main( int argc, char*
argv[] )
{
add( 2, 3 );
return 0;
}
如果深入理解了第3節(jié)中所闡述的extern
"C"在編譯和連接階段發(fā)揮的作用,就能真正理解本節(jié)所闡述的從C++引用C函數(shù)和C引用C++函數(shù)的慣用法。對第4節(jié)給出的示例代碼,需要特別留意各個細節(jié)。
1.把C++當成一門新的語言學(xué)習(xí)(和C沒啥關(guān)系!真的。);
2.看《Thinking In
C++》,不要看《C++變成死相》;
3.看《The C++ Programming Language》和《Inside The C++
Object Model》,不要因為他們很難而我們自己是初學(xué)者所以就不看;
4.不要被VC、BCB、BC、MC、TC等詞匯所迷惑——他們都是集成開發(fā)環(huán)境,而我們要學(xué)的是一門語言;
5.不要放過任何一個看上去很簡單的小編程問題——他們往往并不那么簡單,或者可以引伸出很多知識點;
6.會用Visual
C++,并不說明你會C++;
7.學(xué)class并不難,template、STL、generic
programming也不過如此——難的是長期堅持實踐和不遺余力的博覽群書;
8.如果不是天才的話,想學(xué)編程就不要想玩游戲——你以為你做到了,其實你的C++水平并沒有和你通關(guān)的能力一起變高——其實可以時刻記住:學(xué)C++是為了編游戲的;
9.看Visual C++的書,是學(xué)不了C++語言的;
10.浮躁的人容易說:XX語言不行了,應(yīng)該學(xué)YY;——是你自己不行了吧!?
11.浮躁的人容易問:我到底該學(xué)什么;——別問,學(xué)就對了;
12.浮躁的人容易問:XX有錢途嗎;——建議你去搶銀行;
13.浮躁的人容易說:我要中文版!我英文不行!——不行?學(xué)呀!
14.浮躁的人容易問:XX和YY哪個好;——告訴你吧,都好——只要你學(xué)就行;
15.浮躁的人分兩種:a)只觀望而不學(xué)的人;b)只學(xué)而不堅持的人;
16.把時髦的技術(shù)掛在嘴邊,還不如把過時的技術(shù)記在心里;
17.C++不僅僅是支持面向?qū)ο蟮某绦蛟O(shè)計語言;
18.學(xué)習(xí)編程最好的方法之一就是閱讀源代碼;
19.在任何時刻都不要認為自己手中的書已經(jīng)足夠了;
20.請閱讀《The Standard C++
Bible》(中文版:標準C++寶典),掌握C++標準;
21.看得懂的書,請仔細看;看不懂的書,請硬著頭皮看;
22.別指望看第一遍書就能記住和掌握什么——請看第二遍、第三遍;
23.請看《Effective C++》和《More
Effective C++》以及《Exceptional C++》;
24.不要停留在集成開發(fā)環(huán)境的搖籃上,要學(xué)會控制集成開發(fā)環(huán)境,還要學(xué)會用命令行方式處理程序;
25.和別人一起討論有意義的C++知識點,而不是爭吵XX行不行或者YY與ZZ哪個好;
26.請看《程序設(shè)計實踐》,并嚴格的按照其要求去做;
27.不要因為C和C++中有一些語法和關(guān)鍵字看上去相同,就認為它們的意義和作用完全一樣;
28.C++絕不是所謂的C的“擴充”——如果C++一開始就起名叫Z語言,你一定不會把C和Z語言聯(lián)系得那么緊密 117-102 117-301 ;
29.請不要認為學(xué)過XX語言再改學(xué)C++會有什么問題——你只不過又在學(xué)一門全新的語言而已;
30.讀完了《Inside The C++
Object Model》以后再來認定自己是不是已經(jīng)學(xué)會了C++;
31.學(xué)習(xí)編程的秘訣是:編程,編程,再編程;
32.請留意下列書籍:《C++面向?qū)ο蟾咝Ь幊蹋–++ Effective Object-Oriented Software
Construction)》《面向?qū)ο筌浖?gòu)造(Object-Oriented Software Construction)》《設(shè)計模式(Design
Patterns)》《The Art of Computer Programming》;
33.記住:面向?qū)ο蠹夹g(shù)不只是C++專有的;
34.請把書上的程序例子親手輸入到電腦上實踐,即使配套光盤中有源代碼;
35.把在書中看到的有意義的例子擴充;
36.請重視C++中的異常處理技術(shù),并將其切實的運用到自己的程序中;
37.經(jīng)常回顧自己以前寫過的程序,并嘗試重寫,把自己學(xué)到的新知識運用進去;
38.不要漏掉書中任何一個練習(xí)題——請全部做完并記錄下解題思路;
39.C++語言和C++的集成開發(fā)環(huán)境要同時學(xué)習(xí)和掌握;
40.既然決定了學(xué)C++,就請堅持學(xué)下去,因為學(xué)習(xí)程序設(shè)計語言的目的是掌握程序設(shè)計技術(shù),而程序設(shè)計技術(shù)是跨語言的;
41.就讓C++語言的各種平臺和開發(fā)環(huán)境去激烈的競爭吧,我們要以學(xué)習(xí)C++語言本身為主;
42.當你寫C++程序?qū)懙揭话雲(yún)s發(fā)現(xiàn)自己用的方法很拙劣時,請不要馬上停手;請盡快將余下的部分粗略的完成以保證這個設(shè)計的完整性,然后分析自己的錯誤并重新設(shè)計和編寫(參見43);
43.別心急,設(shè)計C++的class確實不容易;自己程序中的class和自己的class設(shè)計水平是在不斷的編程實踐中完善和發(fā)展的;
44.決不要因為程序“很小”就不遵循某些你不熟練的規(guī)則——好習(xí)慣是培養(yǎng)出來的,而不是一次記住的;
45.每學(xué)到一個C++難點的時候,嘗試著對別人講解這個知識點并讓他理解——你能講清楚才說明你真的理解了;
46.記錄下在和別人交流時發(fā)現(xiàn)的自己忽視或不理解的知識點;
47.請不斷的對自己寫的程序提出更高的要求,哪怕你的程序版本號會變成Version 100.XX;
48.保存好你寫過的所有的程序——那是你最好的積累之一;
49.請不要做浮躁的人;
50.請熱愛C++!
一.C++語言的基礎(chǔ)
說起入門慢,第一個原因莫過于語言基礎(chǔ)了.高中時期學(xué)校組織的微機興趣小組學(xué)習(xí)的是PASCAL語言(我也不知道為什么要講這個語言,如果說是為了應(yīng)付比賽,當時也有C語言組呀),所以在大學(xué)轉(zhuǎn)向Windows編程的時候,我首先選擇了Delphi.大三的時候?qū)W校要求考國家二級,二級沒有 Delphi,于是又轉(zhuǎn)向了VB,原因是VB做起來與Delphi很像。后來發(fā)現(xiàn)VB的IDE做的比Delphi好用,而且BASIC語言寫起來簡單,于是便棄Delphi而去(明眼人恐怕又要罵我了,若不是太懶,怎么會喜歡VB的IDE呢?的確是這樣,后文會提到,懶不僅僅是學(xué)習(xí)VC的大敵,而且懶人是什么都學(xué)不好的)。長年與VB打交道,讓我對C/C++語言很不習(xí)慣——我不喜歡C++寫一個句語要打一個分號,我不喜歡大小寫字母要嚴格區(qū)分,我不喜歡比較的時候要寫兩個等號,我不喜歡……總之,對C++很沒好感,沒好感也就沒興趣學(xué)了(后面提到興趣是相當重要的)。當然如果你現(xiàn)在再問我應(yīng)該學(xué)習(xí)什么語言,我會毫不猶豫地向你推薦C++,因為就常用語言而言,C++語言中包含的知識是相當全面的——從面向過程,到基于對象/面向?qū)ο螅俚侥0搴头缎停梢哉f是應(yīng)有盡有,不夸張地說,別的語言在某種程度上而言是C++語言的子集或者說是在模仿C++、向C++靠攏。
在數(shù)次失敗中,給我很明顯的感覺就是,不學(xué)好C++語言就學(xué)習(xí)Visual
C++純粹是一種自虐。這次入門之前,我花了3個多月的時間系統(tǒng)地學(xué)習(xí)了C++語言,夠意思吧。然后我信心實足地敲響Visual
C++的家門,呵呵,這次她終于肯給面子了。舉個例子吧,在看Dll的調(diào)用時,用到“函數(shù)指針”,順理成章就看下去了,想一想如果沒有C++語言的基礎(chǔ),基本是不可能的。所以說,沒學(xué)會中文之前,別看《紅樓夢》,那不是《看圖識字》。奉勸想從VB轉(zhuǎn)向VC學(xué)習(xí)的朋友,如果你指望能像學(xué)習(xí)VB一樣邊學(xué)習(xí)VC 邊學(xué)習(xí)C++語言,那你可就錯了。
順便提醒C++語言入門的朋友一點,應(yīng)該關(guān)注ANSI/ISO
C++,也就是標準C++了,市面上C++的書良莠不齊,很多書是“舊書換新皮”,講的仍然是非標準C++,一定要選好。計算機書很貴,大家不妨找電子版的來看,網(wǎng)上有很多,甚至《C++
Primer》或者《C++沉思錄》這樣的好書也有熱心朋友放到了網(wǎng)上。不過,我最喜歡的是《C++編程金典》這本書,不愧是教育大師寫的書,用來學(xué)習(xí)很合適。至于編譯器的選擇,如果條件允許就安裝VS.NET2003吧,據(jù)說Visual
C++7.1的編譯器是目前對標準C++支持的最好的編譯器了。
二.VC學(xué)習(xí)資料的選擇
VC入門難有很多原因,其中不容忽視的一個就是優(yōu)秀的VC學(xué)習(xí)資相對較少。C++語言較深,Visual
C++用起來復(fù)雜,再加上資料少——難上加難。資料少,并不意味著沒有,怎樣選擇或者說挖掘就是關(guān)鍵。暫把資料分為光盤、書籍(包括電子書)和文檔(包括網(wǎng)上的)三類。
在選擇資料方面,大家一定要擯棄中國人思想中的兩大劣根性:<1>不勞而獲<2>一夜暴富。
“不勞而獲”的思想會導(dǎo)致趨向于選擇“講課”類的資料,比如多媒體光盤。結(jié)果是光盤容量往往很少但又要求內(nèi)容面面俱到(不然怎么賣出去呀),這就造成了知識的連貫性差而且講的又飛快,任你一遍一遍地聽,不見成效又打擊信心,最后只能放棄。期待早日有內(nèi)容豐富,講解精彩的光盤面市。
“一夜暴富”的思想會讓你趨向于選擇“速成”類教材。那樣的教材大多是騙人的——能寫個彈出窗口Hello一下World,這就能算是會Visual
C++了?我們還是不要自欺欺人的好。至少也要能連數(shù)據(jù)庫、能使用Socket吧……而這些知識怎么可能“速成”呢?
我有很多Visual
C++的學(xué)習(xí)資料,但沒有一本我是抱著一啃到底的,因為沒有哪本書十全十美,我是交替著使用些資料,這樣做的好處在于:
<1>知識的連貫性好,跳躍性小,進階坡度較小,讀起來舒服。都說Visual
C++的學(xué)習(xí)坡度比較陡,那個陡坡是出現(xiàn)在由單純的C++語言學(xué)習(xí)轉(zhuǎn)向Windows編程的時候,C++語言本身的學(xué)習(xí)并沒有那么困難。
<2>有積累效應(yīng),這本書講的不精不透,另一本書會幫你補上,這本書你沒留心,下本書總該長個心眼。還有就是一些小例子程序,把MFC的類或者函數(shù)拆開來給你看,目的非常明確,效果也不錯。每天學(xué)一點,不圖快,圖扎實。呵呵,跟VC搞“面向?qū)ο?#8221;,當然要一天一點戀愛了。
<3>舉一返三,動手實踐。如果多本書中都把它列為重點,那就一定要熟記在心而且上機操作,書上的例子一定要分析透徹,不能有“差不多”的思想——差多少算多呢?程序這東東,錯一個字母都不行呀。光看會了還差遠著呢,自己要能寫,而且能對例子進行擴展才行。
<4>內(nèi)容詳實豐富,這一點上,首推MSDN啦,還有就是在網(wǎng)上能找到的微軟出的Visual
C++的叢書,希望譯的電子版,是wdl格式的。雖然MSDN是英文版,但其中的英文并不難——您盡可以相信我,因為在下的英文水平是奇爛無比的。 MSDN有兩種用法,一種是當字典用,因為內(nèi)容全;一種是當消遣,沒事了看一個類,敲幾行代碼,看到那個MFC的繼承圖了嗎,挺好玩兒的,感覺像逛街—— 而且東西不要錢,help
yourself。
互聯(lián)網(wǎng)上的資源是非常非常豐富的,千萬不要錯過!好網(wǎng)站和下載站BB皆是。還有論壇、新聞組、在線QQ群……你問我有哪些?呵呵,遠在天邊近在眼前呀:)
三.內(nèi)因與外因:“三心二意”和“高手朋友”你有嗎?
啊哦,我不是在開玩笑。“三心”是指決心,信心和耐心。決心來源于動機,說來好笑,我最初動機很簡單,大學(xué)時有個朋友,計算機系的,我總認為我比他聰明(我的天~~~~),他會VC我不會,我就想超過他,現(xiàn)在都畢業(yè)兩年了,最初的動機早已經(jīng)不在了,而學(xué)習(xí)卻VC已經(jīng)成了我的心愿——最關(guān)鍵的一點是我的愿望是寫自己的輸入法,而寫輸入法只能用Visual
C++去實現(xiàn),所以我會有決心學(xué)好Visual
C++。至于信心,有兩次失敗完全是信心不足造成的,促成這次成功的信心說起來還挺傳奇:我去北京玩兒,回家的火車上一姓趙位老師看見我別著一個MCP的領(lǐng)章就過來跟我聊天,得知他是一位有著十多年VC開發(fā)經(jīng)驗的程序員,敬意油然而生。聊天的過程中,趙老師給了我極大的鼓勵和支持——我問他像我這種 Wood
Head能不能在半年內(nèi)入門VC,他告訴我,一定能,于是我就堅定了自己的信心,現(xiàn)在剛好是4個月,如果趙老師有機會看到這篇文章——我在這里謝謝您啦! (花絮:下車,兩個小時后我與女友分手了,是被甩呀同志們!隨后的一段日子里,一直與VC相伴……)
還要說說耐心:如果您已經(jīng)看到這里了,說明您很有耐心(竟然能看到這里還沒有拂袖而去),耐心與個人的風(fēng)格有關(guān),沒耐心的人多半是懶人,懶人什么都做不成,學(xué)習(xí)VC就是不能懶,書懶得看,問題懶得問,英語懶得譯……或者是有點挫折就放棄,學(xué)好VC是沒指望了。我不知道別人怎樣,反正我是沒少受挫,其實有兩次離入門就那么一點點了,我放棄了……學(xué)VC要越挫越勇,學(xué)VC要肯定執(zhí)著,Gogogo!
“二意”是指第一你要感覺學(xué)習(xí)VC有“意思”,二是你要感覺學(xué)習(xí)VC有“意義”。有意思,就是說你喜歡寫程序,“三心”的源動力來源于你對程序設(shè)計的熱愛,不喜歡編程的人可能能學(xué)好VB但絕學(xué)不好VC。有意義,就是說你要給自己一個理由:自己都不能給自己一個交待的事情是做不長久的。前面說過,我是為了寫自己的輸入法,解放中國人的雙手,這個理由夠純潔夠崇高,還有一個理由就是通過學(xué)習(xí)VC來礪練自己,成為一個真正的程序員。你可以有自己的理由,比如提高薪水或者取得認證云云,一定要有!這就像是給自己的“報酬”,沒有報酬只憑激情做事是任何事都做不長久的。
我小小的成功,有嚴重的原因是因為我有位“高手朋友”——楊W,他是個VC高手,大家會好奇地問:他教你寫什么呢?是MFC還是ATL或者是COM?呵呵,都不是,他從來沒教我寫過一行代碼,但他對我的每一次幫助都彌足珍貴,當我不知道從哪里查找類庫資源的時候,他告訴我:MSDN;當我不知道從哪里找到類的成員函數(shù)時,他告訴我:在頁面的左下角有一個class
member鏈接,當我問他能不能完成XXXX時,他說:別白費力氣了……在他的幫助下,我少走了很多彎路,這也正是高手朋友的可貴之處。在此,我要衷心地說一聲:謝謝!
并不是每位學(xué)習(xí)VC的朋友都有我這么好的運氣,如果你身邊沒有這樣的朋友也不用著急嗎,我這位好朋友可是經(jīng)常出沒于CSDN的壇壇里,明白了?不過,提醒與我一樣的初學(xué)者:一定要做一個會問問題的人哦!怎么做一個會問題的人呢?概括一下就是:目的明確,言簡意賅,核心代碼,客氣謙虛。
四.VC入門隨筆
本人寫東西向來思緒凌亂、顛三倒四。剩下好多東西不知道寫到哪里,沒辦法了,只好叫“隨筆”咯。
……學(xué)習(xí)VC編程,首先要豎立一個“系統(tǒng)/全局觀”。無論是VB、C#、Delphi,寫程序的時候只需要考慮程序本身就行了,換句話說就是你不用考慮消息是如何映射和傳遞的。而VC寫程序就要多多少少考慮到這些東西。打個比方:以前用VB寫程序,就好像是在一座山上建一個亭子,山是山,亭子是亭子,我只管造亭子就是了;而用VC寫程序,還是這個亭子,那么你應(yīng)該意識到,亭子是山的亭子,是山的一部分而不是一個孤立的建筑。“亭子”就是程序,“山”就是Windows系統(tǒng),亭子的地基是山留給建筑的“接口”,也就是API了……
……VC相對VB入門難,一上來不是像VB那樣給個窗體從頭做起,而且AppWizard要分好幾步,每一步里還有一大堆不知所云的選項,不等生成一個程序就已經(jīng)暈頭轉(zhuǎn)向了。怎么辦呢?一句話,從對話框程序入手,因為它最簡單,生成的類最少,而且相對是與VB編程最“像”的。在對話框程序里,你可以充分練習(xí)添加類和成員變量或者成員函數(shù)。……不過我有一點始終搞不明白,由易到難是對話框程序、單文檔程序、多文檔程序,在AppWizard里微軟為什么不按這個順序排列,非要倒著來呢?成心跟我們這些初學(xué)的做對!(國罵省去)……
……又是沒大寫……又是少分號……又是少一個等號……提醒VB轉(zhuǎn)過來的程序員,別總像我這么沒記性哦!
……還是提醒那些學(xué)習(xí)了VB或者是VB.NET/C#的DDMM,MFC的類雖然是面向?qū)ο蟮模鼪]有“屬性”這個概念地!不要指望有Me.TextBox1.Text="Hello
World!"這樣的語法,C/C++是函數(shù)型的語言,類已經(jīng)把“屬性”封裝成了成員變量,那些私有的成員變量你看不到,只能通過函數(shù)來更改——this ->myTextBox.SetWindowText("Hello
World!");……190-823 117-202 1Z0-043 1z0-042
……暈,原來Win32程序和MFC程序不是一回事呀(看看,這就是一本爛書帶給我的,讓我一直以為Win32程序就是MFC程序,直到拜讀《深入淺出MFC》時才恍然大悟)……
……VC好還是VB好?(拜托,別再問這種無聊的問題了)……
……VC的確能做底層,但不是最底層;VC的確功能強大,但不是萬能的——拿手術(shù)刀切西瓜或者用菜刀動手術(shù)都不對……
……VC高手都是用記事本寫程序的:笑不笑由你……190-802 000-834 000-861
……VC程序員比VB程序員強:呵呵,毛主席說過,武器不是戰(zhàn)爭勝利的決定因素……
……在快速開發(fā)工具(RAD)中,控件與后臺代碼是捆綁在一起的,而MFC的“控件類”不一樣,它的“資源”(或者說是皮)與“類”(或者說是瓤)是分開的,要通過ClassWizard把它們“粘”起來……
……如果說C++是一種程序設(shè)計語言,那么Visual
C++中的C++語言不如叫“Windows語言”更合適——Visual
C++就是在編程Windows,用到的宏或者Windows數(shù)據(jù)類型和Windows結(jié)構(gòu)數(shù)不勝數(shù),做好心理準備哦!……
……我的天,那么長的函數(shù)或者結(jié)構(gòu)都要一個字母一個字母寫呀!呵呵,按一下Ctrl+J看看發(fā)生了什么?我就奇怪了,幾乎沒看到有書上提醒我們的初學(xué)者要這樣去做。這可是著實嚇跑了不少初學(xué)者呢!(至少我就被嚇跑過)。器利工善,我們要把IDE用熟哦,微軟送的好禮物可不能浪費……
……很多書在添加完對新話框類之后都寫著要在主對話框類里手動添加對這個新類頭文件的引用,何必呢?用添加成員變量的方法添加這個新對話框類的實例,頭文件自動引用,一舉兩得。一句話:盡量多用Class
Wizard,能不手寫的地方就不手寫……
五.virtual BOOL LongWayToGo(void)
{
//頭一次寫文章,其中Bug肯定少不了,大家一起來DeBug。
//由于是入門級文章,如果有錯誤,很可能影響初學(xué)者學(xué)習(xí),恐誤人子弟,有錯必糾!
//希望大家多提寶貴意見,幫助我前進,謝謝先!
//這是虛函數(shù),留待有所得時續(xù)以后文。我還有很長的路……
return TRUE;
}
摘要: 很多人甚至市面上的一些書籍,都使用了void main( ) ,其實這是錯誤的。C/C++ 中從來沒有定義過void main( ) 。C++ 之父 Bjarne Stroustrup 在他的主頁上的 FAQ 中明確地寫著 The definition void main( ) { /* ... */ } is not and never has been C++, nor has it even been C.( void main( ) 從來就不存在于 C++ 或者 C )。下面我分別說一下 C 和 C++ 標準中對 main 函數(shù)的定義
閱讀全文
2008年1月2日
對于崇尚中庸之道的朋友,就不必理會這篇文章了。簡單說明一下目前網(wǎng)絡(luò)安裝的簡單過程:
安裝向?qū)builder2007trialsetup.exe檢測是否有.net
2.0環(huán)境,這個好辦,如果沒有安裝環(huán)境,在網(wǎng)上可以下載到并安裝。但是它的本體,全部安裝文件400多M,通過安裝向?qū)У膯尉€程進行下載,臨近每個文件結(jié)尾的時候還留下非常充裕的時間給你上wc,你需要上這么多次嗎?所以我強烈推薦我們的快車最新版,開8個線程真是牛啊。好了,廣告時間已過,開始我們的旅程。
7zip文件管理器,用于解壓7zip格式的文件:
http://www.skyuc.com/bbs/showthread.php?t=62&goto=nextoldest
.net 2.0環(huán)境所需要安裝的3個文件:
1)http://installers.codegear.com/prereq/radstudio/5.0/microsoft%20.net%20framework%202.0.7zip
2)http://installers.codegear.com.edgesuite.net/prereq/radstudio/5.0/microsoft%20jsharp%20runtime%202.0.7zip
3)http://installers.codegear.com.edgesuite.net/prereq/radstudio/5.0/microsoft%20.net%202.0%20english%20framework%20sdk%20x86.7zip
或
http://www.microsoft.com/downloads/info.aspx?na=90&p=&SrcDisplayLang=zh-cn&SrcCategoryId=&SrcFamilyId=fe6f2099-b7b4-4f47-a244-c96d69c35dec&u=http%3a%2f%2fdownload.microsoft.com%2fdownload%2fb%2fe%2fa%2fbea35549-7804-4e28-beef-a7d9d1675f4c%2fsetup.exe
在www.codegear.com上注冊,拿到你的安裝序列號,下載安裝向?qū)builder2007trialsetup.exe:
C++Builder 2007 Enterprise Trial
Serial Number: xxxxx
CDN Login
Name: xxx
First Name: xxx
Last Name: xxx
重要提示:注意下文的編號,如1)和(1)是不同的,并不是我寫錯了
需要特別注意的4個目錄:
1)2007安裝目錄,自定義:我的為J:\CodeGear\RAD Studio\5.0
2)示例文件目錄,自定義:J:\CodeGear\RAD Studio\5.0\Demos
3)安裝向?qū)Т鎯?007安裝文件用到的臨時目錄,自定義:H:\download\bcb2007
4)安裝向?qū)builder2007trialsetup.exe解壓3)的安裝文件用到的臨時目錄,不可自定義,所以一般要保證c盤剩余空間在1.2g以上(安裝了.net
2.0之后,系統(tǒng)仍提示需要1.7g,實際上不要這么多,可以忽視它的提示)對于所有用戶使用方式安裝的為:C:\Documents and Settings\All
Users\ApplicationData\{2EB4C530-C94F-4893-ABDC-C1E05A89956E}
安裝的4個步驟:
(1)安裝.net 2.0環(huán)境的3個文件,網(wǎng)上遍地都是。如果是7zip格式的文件,你要先安裝7zip的解壓軟件
(2)運行cbuilder2007trialsetup.exe,設(shè)置各種目錄,直到它開始向3)的目錄下載并自動解壓到4)中,這是一個環(huán)繞地球80天的等待步驟,就讓它自動運行好了
(3)在(2)的同時,我們用快車之類的軟件先下載3)用到的所有文件約423M并放到3)中的目錄,下面的鏈接你可以手工一條條地輸入快車,也可以做成一個html文件批量導(dǎo)入,方法我就不說了,留給大家發(fā)揮:
http://installers.codegear.com/release/radstudio/11.0.2709.7128/vcldotnetruntimes.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/vclwin32runtimes.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/bcbwin32.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/bcbwin32%20english.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/dunit.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/core.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/core%20english.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/corex.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/corecx.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/transm
// 本文轉(zhuǎn)自 C++Builder 研究 -
http://www.ccrun.com/article.asp?i=1024&d=lmp27sogrifierc.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/corewin32.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/corewin32%20english.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/xmlmapper.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/coredelphibcb.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/database.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/databasew32.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/dbexplr.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/ibxw32.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/ibxbcbwin32.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/ibxbcbwin32%20english.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/dbgow32.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/sqlbuilder.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/sqlbuilder%20english.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/dbxcomponentsw32.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/dbexpress.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/dbexpress%20english.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/dbexpressx.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/unittesting.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/unittesting_bcb.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/htmldesigner.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/htmldesigner%20english.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/ite.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/etm.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/itewin32.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/indywin32.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/indybcbwin32.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/intraweb.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/iwbcbwin32.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/sampleprograms.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/samplesbcbwin32.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/helpwin32.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/helpwin32%20english.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/helpcommon.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/helpcommon%20english.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/codevisualization.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/codevisualizationx.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/codevisualizationx%20english.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/codevisualization_bcbw32.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/internetcontrols.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/internetctrlswin32.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/internetctrlswin32x.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/soapwin32.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/webappdebugger.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/msofficecontrolscore.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/msofficebcbwin32.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/corba.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/codeguard.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/bde.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/bdecommon.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/bdecommon%20english.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/bde_pro.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/bdecommon_pro.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/bdecommon_pro%20english.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/databasedesktop.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/databasedesktop%20english.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/imagefiles.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/sampledatafiles.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/rave%20reports%20installer.7zip
http://installers.codegear.com/release/radstudio/11.0.2709.7128/borland%20database%20engine%20professional%20english.7zip
(4)完成了(3)之后,停止(2)的運行,再重新運行(2),這時候它就不再下載,只會自動解壓3)中的所有文件并放到4)去。當然,(3)中的文件下漏幾個也無所謂,反正它會補下。它仍需要通過網(wǎng)絡(luò)驗證這些文件的有效性,所以這也是一個環(huán)游地球一圈的漫長時間。最后,提示你注冊,如果你沒有那個可愛的破解文件bds.exe(版本11.0.2709.7128),估計你會進不去。如果你今天才來,很不幸,http://www.ccrun.com/forum/forum_posts.asp?TID=6071提到的破解文件已經(jīng)被刪除了,但是我成為了幸運者,所以我才能夠完成這篇文章。替換bds.exe,運行,啟動速度比起我無法忍受的turbo
c++ 2006 professional(和bds 2006是兄弟)快了很多。com支持已經(jīng)存在了,非常好。
TTreeView是VCL中提供的樹列表控件,樹的每個節(jié)點是一個TTreeNode類,TTreeNode組件的屬性和方法可以參考Borland提供的幫助(雖然不如MSDN全面,但有總比沒有強)。實際應(yīng)用中我們可能需要禁用某個節(jié)點(界面上反應(yīng)的效果是:節(jié)點字體呈灰色顯示,節(jié)點無法選中等)。但是VCL沒有提供Node->Disable();或Node->Enable = false;這樣的功能,我們只好自己動手實現(xiàn)了。首先我們需要為每個節(jié)點設(shè)定一個標志,用來標識此節(jié)點是否可用,標識方法有很多,比如判斷節(jié)點的文本(Text),節(jié)點的絕對索引值(AbsoluteIndex),節(jié)點的索引(Index)加縮進(Indent)等,在本例中我們用節(jié)點的Data屬性作標識(一個void
*型數(shù)據(jù),其實可以存放N多東西)。如果在你的應(yīng)用中恰好用了Data屬性,就另外想個用來作標志的方法吧。:)
我們寫一個自定義函數(shù),用來啟用/禁用一個節(jié)點:
void __fastcall CrnEnableTreeNode(
bool bEnable, TTreeNode *pNode)
{
pNode->Data = bEnable? NULL: (
void *)0xFFFF;
// 本文轉(zhuǎn)自 C++Builder 研究 -
http://www.ccrun.com/article.asp?i=1015&d=r2tf61
pNode->TreeView->Invalidate();
}
然后考慮如何達到禁用節(jié)點的效果,前面說了,我們只需實現(xiàn)這兩個效果:
1. 節(jié)點字體呈灰色顯示
2. 節(jié)點無法選中
節(jié)點字體呈灰色顯示可以通過TreeView的OnCustomDrawItem事件中的自繪實現(xiàn),在設(shè)計時狀態(tài),選中TreeView,Events選項卡雙擊OnCustomDrawItem事件,添加以下代碼:
void __fastcall TForm1::TreeView1CustomDrawItem(TCustomTreeView *Sender,
TTreeNode *Node, TCustomDrawState State,
bool &DefaultDraw)
{
if(
int(Node->Data) == 0xFFFF)
{
Sender->Canvas->Font->Color = clGray;
}
}
節(jié)點無法選中則可以通過TreeView的Changing事件來處理,在設(shè)計時狀態(tài),選中TreeView,Events選項卡雙擊OnChanging事件,添加以下代碼:
void __fastcall TForm1::TreeView1Changing(TObject *Sender, TTreeNode *Node,
bool &AllowChange)
{
AllowChange = (
int(Node->Data) != 0xFFFF);
}
有以上的實現(xiàn),效果基本就出來了:

測試代碼:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
if(TreeView1->Selected)
CrnEnableTreeNode(
true, TreeView1->Selected);
}
void __fastcall TForm1::Button2Click(TObject *Sender)
{
CrnEnableTreeNode(
true, TreeView1->Items->Item[1]);
}
為看到比較好的效果,可在測試時展開所有節(jié)點為:
TreeView1->FullExpand();