笨鳥先飛學編程系列-C++的基礎特性
Posted on 2010-02-08 06:31 besterChen 閱讀(961) 評論(2) 編輯 收藏 引用 所屬分類: C/C++/STL/boost本來這一講是打算講指針的,可是考慮到C++中指針的更多操作,我不想講一個問題分成兩部分,因此,我就先講C++的部分,等需要用到指針的時候,我們專門寫一個專題講述指針部分。 好進入正題。
大家都知道,C++是在C的基礎上擴展了好多東西,其中好多是思想上的轉變,因此,很多C++中的東西,我們都可以用C語言來模擬出來,比如構造、析構等等。 但是也有很多是C++編譯器擴展的東西,我們沒有辦法用C去模擬,因此,我寫了這個小節,重在理解……
C++中,零散的知識點比較多,因此,我每個專題盡量減少其內容,而增加更新頻率,希望大家能有助于大家的理解。
一、宏定義的擴展——const、inline。
#define 宏定義,是C的知識范疇,由于全都都語法范疇且又僅僅是簡單的查找替換因此我沒有為它單獨的列一個專題,大家可以自己學習一下。我想如果要講這個內容,也得等我們課程進行到MFC或者WTL的時候再捎帶提一下。
學習過#define 以后,我們知道,它一般用來定義常量,由于它可以帶參數,且因沒有函數調用時傳參的復雜過程而速度快的優點,因此它經常被當作“函數”使用。
但是#define也是有缺點的,比如它定義的常量沒有類型信息,它定義的“函數”(帶參宏)不安全等原因,C++對這個功能進行了擴充,分別用const和inline兩個關鍵字來分別代替無參宏定義常量,帶參宏定義函數。下面分別看一下它們的用法及原理。
1. const 的用法。
const用于定義常量,基礎語法如下:
const 類型 常量名
類型 const 常量名
關于const的基礎語法:const 只是對除類型外,緊靠其右邊的元素。
比如:
// 這時const右面除了類型就是nCountNum變量,所以,nCountNum的內容不可以改變。
const int nCountNum = 5;
// const右邊是*,在指針中*表示內容,所以pnCountNum指針指向的變量內容不可以被改變。
const int *pnCountNum = nCountNum;
// const右邊還是*,所以同上
int const *pnCountNum = nCountNum;
// const右邊是指針變量名,指針變量代表地址,所以pnCountNum中的內容不可以被改變。
int * const pnCountNum = nCountNum;
OK,知道了以上的語法知識,我們需要了解,這種常量跟我們用宏定義的常量是有區別的,因為這個是編譯器級別的常量,我們可以通過指針來修改它的內容,當然,若在代碼中使用const變量,則直接在代碼中使用其常量值,如下面的代碼:
const int nCount = 50;
int tmpNum = 0;
scanf("%d", &tmpNum); // 防止編譯器自動優化代碼
printf("%d", tmpNum + nCount);
// 26: printf("%d", tmpNum + nCount);
// 0040D427 mov ecx,dword ptr [ebp-8]
// 0040D42A add ecx,32h // 直接當作常量使用
// 0040D42D push ecx
// 0040D42E push offset string "%d" (0042201c)
// 0040D433 call printf (0040f900)
通過上面的代碼,我們可以知道,nCount這個常量有自己的棧地址,只要有地址,我們肯定是可以用指針來修改它內容的。
但是,通過
// 0040D42A add ecx,32h // 直接當作常量使用
這一句,我們清楚,這個代碼是直接將nCount 當做常量使用,所以即使我們更改了nCount 中的內容,這里的值也不會改變了。
2. inline 的用法。
是的,雖然帶參宏的使用提高了編碼的效率,從一定程度上提高了程序的運行效率(因為它少了函數調用的壓棧出棧等操作)而被MFC,WTL等廣泛的應用,但是不可否認用帶參宏的不安全性,在C++中引入了inline函數的概念,它用inline函數來代替帶參宏的功能。
到這里,我們就不難理解,inline函數的特點了:在函數被調用的地方將代碼展開。比如下面的代碼:
/************************************************************************/
/* 在C++中用內聯函數來代替有參宏,跟有參宏一樣,它在調用的地方原地展開
/* 因此,內聯函數也同有參宏一樣,在同一個代碼中存在多份拷貝。
/* 所以,內聯函數一般聲明在頭文件中就可以了。
/* 說 明:
/* 內聯函數中不能包含switch、while等復雜結構,如果邏輯復雜了編譯器
/* 就將它當做普通函數處理。
/************************************************************************/
inline int add(int a, int b, int c)
{
return a+b+c;
}
int main(int argc, char* argv[])
{
int a,b,c;
scanf("%d %d %d", &a, &b, &c);
printf("%d", add(a,b,c));
return 0;
}
由于DEBUG方式編譯的程序不做任何優化,所以,我們release方式編譯此代碼,得到如下信息:
/*DEBUG 模式下,內聯就是普通函數,release模式下才真正的內聯。*/
// 00401000 >/$ 83EC 0C sub esp, 0xC ; _main
// 00401003 |. 8D4424 08 lea eax, dword ptr [esp+0x8]
// 00401007 |. 8D4C24 04 lea ecx, dword ptr [esp+0x4]
// 0040100B |. 8D5424 00 lea edx, dword ptr [esp]
// 0040100F |. 50 push eax
// 00401010 |. 51 push ecx
// 00401011 |. 52 push edx
// 00401012 |. 68 34804000 push offset <??_C@_08NNKG@?$CFd?5?$CF>; ASCII "%d %d %d"
// 00401017 |. E8 55000000 call <_scanf>
// 0040101C |. 8B4424 10 mov eax, dword ptr [esp+0x10]
// 00401020 |. 8B4C24 14 mov ecx, dword ptr [esp+0x14]
// 00401024 |. 03C1 add eax, ecx
// 00401026 |. 8B4C24 18 mov ecx, dword ptr [esp+0x18]
// 0040102A |. 03C1 add eax, ecx
// 0040102C |. 50 push eax
// 0040102D |. 68 30804000 push offset <??_C@_02MECO@?$CFd?$AA@> ; ASCII "%d"
// 00401032 |. E8 09000000 call <_printf>
// 00401037 |. 33C0 xor eax, eax
// 00401039 |. 83C4 24 add esp, 0x24
// 0040103C \. C3 retn
Inline函數的代碼被展開貼到main函數中的,是吧……
當然,并不是所有的代碼都可以被寫程序inline函數的,它有如下幾點要求:
1、 inline函數中的代碼邏輯不可過于復雜
2、 不能包含如循環,switch等復雜的語句
否則,編譯器會將inline函數當做一個普通函數處理。
二、 指針與引用。
我想,雖然我沒有系統的講過指針,但是根據我們第一課中的內容的提示,我相信,大家一定能夠理解指針的概念。所以我這一節不詳細講述指針的概念,
引用,是C++提出來的一個新的概念,不多廢話,看代碼:
/************************************************************************/
/* 引用是C++新增加的運算符。
/* 基本用法如下:
/************************************************************************/
void BaseUse()
{
printf("/-----------基礎用法---------------/\r\n");
int nBuf = 0;
scanf("%d", &nBuf); // 防止編譯器自動優化
int &a1 = nBuf; // 引用的用法
int *pa1 = &nBuf; // 指針是變量的地址
printf("%d %d %d\r\n", nBuf, a1, *pa1);
}
上面代碼我們給出了引用的最基本的用法,為了我們能夠將它與指針加以區別,我跟一指針一起使用,然后我們通過分析它的反匯編代碼,我們給出他們的區別。
由于Release模式下開了O2選項,對代碼進行了優化,所以我們看DEBUG模式的代碼
0040D77F |. 8D45 FC lea eax, dword ptr [ebp-0x4]
0040D782 |. 50 push eax
0040D783 |. 68 1C204200 push 0042201C ; /format = "%d"
0040D788 |. E8 D3210000 call scanf ; \scanf
0040D78D |. 83C4 08 add esp, 0x8
0040D790 |. 8D4D FC lea ecx, dword ptr [ebp-0x4] ; 給引用賦值
0040D793 |. 894D F8 mov dword ptr [ebp-0x8], ecx
0040D796 |. 8D55 FC lea edx, dword ptr [ebp-0x4] ; 指針的用法
0040D799 |. 8955 F4 mov dword ptr [ebp-0xC], edx
0040D79C |. 8B45 F4 mov eax, dword ptr [ebp-0xC] ; 取內容
由此,我們知道,引用時當作指針使用的,他們的傳遞方式一摸一樣,只是,引用在操作的時候,多了一個取內容的操作。
我們得出結論如下:
引用就是指針取內容。
指針就是引用取地址。
當然,如果我的這節課程到這里就結束了,似乎有點對不住各位同學,因為我這里似乎只講述了語法的東西,下面呢,我由引用的話題,講一下引用的一些高級用法。
// 定義一個學生信息的結構體
typedef struct _DATA_STUDENT_INFO
{
int nID;
char *szName;
char chsex;
}DATA_STUDENT_INFO, *PDATA_STUDENT_INFO;
// 聲明一個全局變量
DATA_STUDENT_INFO g_DSI[2] = {0};
// 返回一個結構體的引用
DATA_STUDENT_INFO& GetStudentObj(int nIndex)
{
return g_DSI[nIndex];
}
int ExtendUse()
{
printf("/-----------擴展用法---------------/\r\n");
GetStudentObj(0).chsex = 1;
GetStudentObj(0).szName = "besterChen";
GetStudentObj(0).nID = 1;
printf("I D: %d\r\n", GetStudentObj(0).nID);
printf("Name: %s\r\n", GetStudentObj(0).szName);
printf(" Sex: %d\r\n", GetStudentObj(0).chsex);
return 0;
}
由于引用時指針取內容,所以,GetStudentObj返回的就是一個對象,因此可以直接對它的成員進行操作。
三、 學習小結
我臨時決定講C++的,所以指針的專題等后期再講,因為一來我不想讓指針的話題分成兩部分討論,二來我還沒有準備好講指針將的透徹(其實主要就是沒有信心)。
另外,我覺得,我們現在打下的那些基礎足夠我們學C++了,雖然好多朋友都說:沒有必要學習C語言,直接學C++即可,但是我始終相信學習C語言是有必要的,因為C語言著重內存結構(好讓我們把握住程序的本質),C++著重于設計思想(也有好多的語法知識要學)。
直到上個專題內存操作,我們幾乎把C語言都講完了(當然僅僅是讓人迷糊的關鍵部分),本專題是C++課程的開始,我也著重于內存,因為我始終相信,掌握了內存結構就掌握了編程的本質。
所以,C++部分我會盡快的更新,因為大家都有基礎了。
最后,祝大家成功。