一般來說,如果你不是MFC工程,需要引用HANDLE的話,
最好自己將其類型改為void*,比如,你本來變量的類型為HANDLE,
你把它改為void*就可以了。
--------------------------------------------------------------------------------
任何類型的指針,以及0,都可以隱式的轉換為void*,但是由于void*不包含任何類型信息(你想想void是什么意思:-) ),從void*到其它任何類型都沒有隱式的轉換。所以只有強制轉換。強制轉換(我指的是brute force那樣的)有兩種方式,一是如樓上所說的,舊式的C風格的強制類型轉換。由于強制類型轉換經常是很多隱蔽的錯誤的根源,所以在C++中應該盡量少的使用,使用的時候也應該用新的C++風格寫法,e.g.
int i = 3;;
void *p = &i; //隱式轉換,自動的
int *p = reinterpret_cast<int*>(p);
這樣寫的意義在于使得即便發生了因強制類型轉換而產生的錯誤,也更容易找到。B.Stroustrup之所以把這個操作符設計的這么丑陋,就是要你盡量少用,用了也容易查出來。
樓主說的是將void *指針賦給其他指針吧,因為空指針沒有指明類型,只有一個地址而已,如果賦給int 等類型的指針在VC中會出現“can't convert from 'void *' to 'int *'的編譯錯誤!
就像不同類型的普通變量之間賦值一樣!
所以要通過強制轉換指針類型!
下面是錢能教程中的一段代碼:
int a=20;
int *pr=&a;
void *p=pr; //ok:將整型指針值賦給空類型指針
pr=p; //error:不能將空類型指針賦給其他指針
pr=(int *)p; //ok: 顯式轉換被允許
發表于: 2004-03-31 22:56:29
<<C++Primer>>有一節介紹void*強制轉換的問題,看了很久都沒看懂,哪衛高手能詳細的講一下
轉)指針的引用(*&)與指針的指針(**) - [C,C++]
版權聲明:轉載時請以超鏈接形式標明文章原始出處和作者信息及本聲明
http://hiaurora.blogbus.com/logs/23641245.html
原文鏈接:http://www.programfan.com/article/2901.html
在下列函數聲明中,為什么要同時使用*和&符號?以及什么場合使用這種聲明方式?
void func1( MYCLASS *&pBuildingElement );
論壇中經常有人問到這樣的問題。本文試圖通過一些實際的指針使用經驗來解釋這個問題。仔細看一下這種聲明方式,確實有點讓人迷惑。在某種意義上,"*"和"&"是意思相對的兩個東西,把它們放在一起有什么意義呢?。
為了理解指針的這種做法,我們先復習一下C/C++編程中無所不在的指針概念。我們都知道MYCLASS*的意思:指向某個對象的指針,此對象的類型為MYCLASS。
Void func1(MYCLASS *pMyClass); // 例如:
MYCLASS* p = new MYCLASS;
func1(p);
上面這段代碼的這種處理方法想必誰都用過,創建一個MYCLASS對象,然后將它傳入func1函數。現在假設此函數要修改pMyClass:
void func1(MYCLASS *pMyClass) {
DoSomething(pMyClass);
pMyClass = // 其它對象的指針
}
第二條語句在函數過程中只修改了pMyClass的值。并沒有修改調用者的變量p的值。如果p指向某個位于地址0x008a00的對象,當func1返回時,它仍然指向這個特定的對象。(除非func1有bug將堆弄亂了,完全有這種可能。)
現在假設你想要在func1中修改p的值。這是你的權利。調用者傳入一個指針,然后函數給這個指針賦值。以往一般都是傳雙指針,即指針的指針,例如, CMyClass**。
MYCLASS* p = NULL;
func1(&p);
void func1(MYCLASS** pMyClass);
{ *pMyClass = new MYCLASS; …… }
調用func1之后,p指向新的對象。在COM編程中,你到處都會碰到這樣的用法--例如在查詢對象接口的QueryInterface函數中:
interface ISomeInterface {
HRESULT QueryInterface(IID &iid, void** ppvObj);
……
};
LPSOMEINTERFACE p=NULL;
pOb->QueryInterface(IID_SOMEINTERFACE, &p);
此處,p是SOMEINTERFACE類型的指針,所以&p便是指針的指針,在QueryInterface返回的時候,如果調用成功,則變量p包含一個指向新的接口的指針。
如果你理解指針的指針,那么你肯定就理解指針引用,因為它們完全是一回事。如果你象下面這樣聲明函數:
void func1(MYCLASS *&pMyClass) {
pMyClass = new MYCLASS;
……
}
其實,它和前面所講得指針的指針例子是一碼事,只是語法有所不同。傳遞的時候不用傳p的地址&p,而是直接傳p本身:
MYCLASS* p = NULL;
func1(p);
在調用之后,p指向一個新的對象。一般來講,引用的原理或多或少就象一個指針,從語法上看它就是一個普通變量。所以只要你碰到*&,就應該想到**。也就是說這個函數修改或可能修改調用者的指針,而調用者象普通變量一樣傳遞這個指針,不使用地址操作符&。
至于說什么場合要使用這種方法,我會說,極少。MFC在其集合類中用到了它--例如,CObList,它是一個Cobjects指針列表。
Class CObList : public Cobject {
…… // 獲取/修改指定位置的元素
Cobject*& GetAt(POSITION position);
Cobject* GetAt(POSITION position) const;
};
這里有兩個GetAt函數,功能都是獲取給定位置的元素。區別何在呢?
區別在于一個讓你修改列表中的對象,另一個則不行。所以如果你寫成下面這樣:
Cobject* pObj = mylist.GetAt(pos);
則pObj是列表中某個對象的指針,如果接著改變pObj的值: pObj = pSomeOtherObj;
這并改變不了在位置pos處的對象地址,而僅僅是改變了變量pObj。但是,如果你寫成下面這樣: Cobject*& rpObj = mylist.GetAt(pos);
現在,rpObj是引用一個列表中的對象的指針,所以當改變rpObj時,也會改變列表中位置pos處的對象地址--換句話說,替代了這個對象。這就是為什么CObList會有兩個GetAt函數的緣故。一個可以修改指針的值,另一個則不能。注意我在此說的是指針,不是對象本身。這兩個函數都可以修改對象,但只有*&版本可以替代對象。
在C/C++中引用是很重要的,同時也是高效的處理手段。所以要想成為C/C++高手,對引用的概念沒有透徹的理解和熟練的應用是不行的。 ################################################################ 注記:函數的參數為指針,如func(MyClass* p). 傳參時, MyClass* my; func(my); 相當于傳的指針的拷貝,是一個臨時值。因此,在函數體內部,你可以更改p指向的MyClass的屬性,但是你不能給p賦值,就向上面所說,p = new MyClass();或者*p = 。。。因為,即使你這樣做了,出了函數的作用的作用域,my仍然指向原來的地址。而你在函數題內部,改變的是臨時變量指針p的地址。所以,不能對p進行賦值等操作。 附圖說明:
歷史上的今天:
c++的類型轉換:void *無法被轉為struct node*,而在C下卻可以,又說C++兼容C ? 我定義個指向結構體的指針,
struct node*p;
void *fun();
p=fun();
C下完美通過,C++出錯
那個,現在的說法是:C是C,C++是C++。
要強制轉換。。。
不知道是不是vc,一般來說c++校驗比較嚴格,而c則給程序員較大的自由度(不過風險也相應存在)
p= (struct node*)fun();
你說對了,在C下面void*可以隱式轉換為左值指針類型,而C++必須加強制轉換
再說 c99 也沒完全兼容以前的c
又何必要求c++ 就得完全兼容 c
引用 5 樓 thefirstz 的回復:
你說對了,在C下面void*可以隱式轉換為左值指針類型,而C++必須加強制轉換
學習了~~~~~~~~~~~~~~~~~~
通常不涉及對象時,指針強轉,沒有副作用
*********************************************
p=static_cast<struct node*>(fun());
***********************************************
p=(TYPE*)fun();
再試試
*****************************************************
這點應該算是C++相對C的一個優點吧
較強的類型檢查
******************************************
應該是void* 無條件接受任何類型的指針吧,反之不行
C++中,為什么必須用造型來轉換*void
上一節 下一節 返回目錄編輯/糾錯/意見關注(146)更新:2012-12-31
分享到0
在C 語言中,你可以隱式地將*void 轉換為*T。這是不安全的。考慮一下:
#include<stdio.h>
int main(){
char i = 0;
char j = 0;
char* p = &i;
void* q = p;
int* pp = q; /* 不安全的,在C 中可以,C++不行 */
printf("%d %d\n",i,j);
*pp = -1; /* 覆蓋了從i 開始的內存 */
printf("%d %d\n",i,j);
}
使用一個并不指向T 類型的T*將是一場災難。因此,在C++中,如果從一個void*得到一個T*,你必須進行顯式轉換。舉例來說,要得到上列程序的這個令人別扭的效果,你可以這樣寫:
int* pp = (int*)q;
或者使用一個新的類型造型,以使這種沒有檢查的類型轉換操作變得更加清晰:
int* pp = static_cast<int*>(q);
造型被最好地避免了。
在C 語言中,這種不安全的轉換最常見的應用之一,是將malloc()的結果賦予一個合適的指針。例如:
int* p = malloc(sizeof(int));
在C++中,使用類型安全的new 操作符:
int* p = new int;
附帶地,new 操作符還提供了勝過malloc()的新特性:
new 不會偶然分配錯誤的內存數量;
new 會隱式地檢查內存耗盡情況,而且
new 提供了初始化。
舉例:
typedef std::complex<double> cmplx;
/* C 風格: */
cmplx* p = (cmplx*)malloc(sizeof(int)); /* 錯誤:類型不正確 */
/* 忘記測試p==0 */
if (*p == 7) { /* ... */ } /* 糟糕,忘記了初始化*p */
// C++風格:
cmplx* q = new cmplx(1,2); // 如果內存耗盡,將拋出一個bad_alloc 異常
if (*q == 7) { /* ... */ }
C語言位運算 - [C,C++]
版權聲明:轉載時請以超鏈接形式標明文章原始出處和作者信息及本聲明
http://hiaurora.blogbus.com/logs/29800492.html
前面介紹的各種運算都是以字節作為最基本位進行的。 但在很多系統程序中常要求在位(bit)一級進行運算或處理。C語言提供了位運算的功能, 這使得C語言也能像匯編語言一樣用來編寫系統程序。
一、位運算符C語言提供了六種位運算符:
& 按位與
| 按位或
^ 按位異或
~ 取反
<< 左移
>> 右移
1. 按位與運算 按位與運算符"&"是雙目運算符。其功能是參與運算的兩數各對應的二進位相與。只有對應的兩個二進位均為1時,結果位才為1 ,否則為0。參與運算的數以補碼方式出現。
例如:9&5可寫算式如下: 00001001 (9的二進制補碼)&00000101 (5的二進制補碼) 00000001 (1的二進制補碼)可見9&5=1。
按位與運算通常用來對某些位清0或保留某些位。例如把a 的高八位清 0 , 保留低八位, 可作 a&255 運算 ( 255 的二進制數為0000000011111111)。
main(){
int a=9,b=5,c;
c=a&b;
printf("a=%d\nb=%d\nc=%d\n",a,b,c);
}
2. 按位或運算 按位或運算符“|”是雙目運算符。其功能是參與運算的兩數各對應的二進位相或。只要對應的二個二進位有一個為1時,結果位就為1。參與運算的兩個數均以補碼出現。
例如:9|5可寫算式如下: 00001001|00000101
00001101 (十進制為13)可見9|5=13
main(){
int a=9,b=5,c;
c=a|b;
printf("a=%d\nb=%d\nc=%d\n",a,b,c);
}
3. 按位異或運算 按位異或運算符“^”是雙目運算符。其功能是參與運算的兩數各對應的二進位相異或,當兩對應的二進位相異時,結果為1。參與運算數仍以補碼出現,例如9^5可寫成算式如下: 00001001^00000101 00001100 (十進制為12)
main(){
int a=9;
a=a^15;
printf("a=%d\n",a);
}
4. 求反運算 求反運算符~為單目運算符,具有右結合性。 其功能是對參與運算的數的各二進位按位求反。例如~9的運算為: ~(0000000000001001)結果為:1111111111110110
5. 左移運算 左移運算符“<<”是雙目運算符。其功能把“<< ”左邊的運算數的各二進位全部左移若干位,由“<<”右邊的數指定移動的位數,
高位丟棄,低位補0。例如: a<<4 指把a的各二進位向左移動4位。如a=00000011(十進制3),左移4位后為00110000(十進制48)。6. 右移運算 右移運算符“>>”是雙目運算符。其功能是把“>> ”左邊的運算數的各二進位全部右移若干位,“>>”右邊的數指定移動的位數。
例如:設 a=15,a>>2 表示把000001111右移為00000011(十進制3)。 應該說明的是,對于有符號數,在右移時,符號位將隨同移動。當為正數時, 最高位補0,而為負數時,符號位為1,最高位是補0或是補1 取決于編譯系統的規定。Turbo C和很多系統規定為補1。
main(){
unsigned a,b;
printf("input a number: ");
scanf("%d",&a);
b=a>>5;
b=b&15;
printf("a=%d\tb=%d\n",a,b);
}
請再看一例!
main(){
char a='a',b='b';
int p,c,d;
p=a;
p=(p<<8)|b;
d=p&0xff;
c=(p&0xff00)>>8;
printf("a=%d\nb=%d\nc=%d\nd=%d\n",a,b,c,d);
}
位域
有些信息在存儲時,并不需要占用一個完整的字節, 而只需占幾個或一個二進制位。例如在存放一個開關量時,只有0和1 兩種狀態, 用一位二進位即可。為了節省存儲空間,并使處理簡便,C語言又提供了一種數據結構,稱為“位域”或“位段”。所謂“位域”是把一個字節中的二進位劃分為幾個不同的區域, 并說明每個區域的位數。每個域有一個域名,允許在程序中按域名進行操作。 這樣就可以把幾個不同的對象用一個字節的二進制位域來表示。一、位域的定義和位域變量的說明位域定義與結構定義相仿,其形式為:
struct 位域結構名
{ 位域列表 };
其中位域列表的形式為: 類型說明符 位域名:位域長度
例如:
struct bs
{
int a:8;
int b:2;
int c:6;
};
位域變量的說明與結構變量說明的方式相同。 可采用先定義后說明,同時定義說明或者直接說明這三種方式。例如:
struct bs
{
int a:8;
int b:2;
int c:6;
}data;
說明data為bs變量,共占兩個字節。其中位域a占8位,位域b占2位,位域c占6位。對于位域的定義尚有以下幾點說明:
1. 一個位域必須存儲在同一個字節中,不能跨兩個字節。如一個字節所剩空間不夠存放另一位域時,應從下一單元起存放該位域。也可以有意使某位域從下一單元開始。例如:
struct bs
{
unsigned a:4
unsigned :0 /*空域*/
unsigned b:4 /*從下一單元開始存放*/
unsigned c:4
}
在這個位域定義中,a占第一字節的4位,后4位填0表示不使用,b從第二字節開始,占用4位,c占用4位。
2. 由于位域不允許跨兩個字節,因此位域的長度不能大于一個字節的長度,也就是說不能超過8位二進位。
3. 位域可以無位域名,這時它只用來作填充或調整位置。無名的位域是不能使用的。例如:
struct k
{
int a:1
int :2 /*該2位不能使用*/
int b:3
int c:2
};
從以上分析可以看出,位域在本質上就是一種結構類型, 不過其成員是按二進位分配的。
二、位域的使用位域的使用和結構成員的使用相同,其一般形式為: 位域變量名·位域名 位域允許用各種格式輸出。
main(){
struct bs
{
unsigned a:1;
unsigned b:3;
unsigned c:4;
} bit,*pbit;
bit.a=1;
bit.b=7;
bit.c=15;
printf("%d,%d,%d\n",bit.a,bit.b,bit.c);
pbit=&bit;
pbit->a=0;
pbit->b&=3;
pbit->c|=1;
printf("%d,%d,%d\n",pbit->a,pbit->b,pbit->c);
}
上例程序中定義了位域結構bs,三個位域為a,b,c。說明了bs類型的變量bit和指向bs類型的指針變量pbit。這表示位域也是可以使用指針的。
程序的9、10、11三行分別給三個位域賦值。( 應注意賦值不能超過該位域的允許范圍)程序第12行以整型量格式輸出三個域的內容。第13行把位域變量bit的地址送給指針變量pbit。第14行用指針方式給位域a重新賦值,賦為0。第15行使用了復合的位運算符"&=", 該行相當于: pbit->b=pbit->b&3位域b中原有值為7,與3作按位與運算的結果為3(111&011=011,十進制值為3)。同樣,程序第16行中使用了復合位運算"|=", 相當于: pbit->c=pbit->c|1其結果為15。程序第17行用指針方式輸出了這三個域的值。
浮點數的存儲格式:
浮點數的存儲格式是符號+階碼(定點整數)+尾數(定點小數)
SEEEEEEEEMMMMMMMMMMMMMMMMMMMMMMM
即1位符號位(0為正,1為負),8位指數位,23位尾數位
浮點數存儲前先轉化成2的k次方形式,即:
f = A1*2^k + A2*2^(k-1) + ... + Ak +... +An*2^(-m) (Ai = {0, 1}, A1 = 1)
如5.5=2^2 + 2^0 + 2^(-1)
其中的k就是指數,加127后組成8位指數位
5.5的指數位就是2+127 = 129 = 10000001
A2A3.....An就是尾數位,不足23位后補0
所以5.5 = 01000000101000000000000000000000 = 40A00000
所以,對浮點數*2、/2只要對8位符號位+、- 即可,但不是左移、右移
關于unsigned int 和 int 的在位運算上的不同,下面有個CU上的例子描述的很清楚:
[問題]:這個函數有什么問題嗎?
/////////////////////////////////////////////////
/**
* 本函數將兩個16比特位的值連結成為一個32比特位的值。
* 參數:sHighBits 高16位
* sLowBits 低16位
* 返回:32位值
**/
long CatenateBits16(short sHighBits, short sLowBits)
{
long lResult = 0; /* 32位值的臨時變量*/
/* 將第一個16位值放入32位值的高16位 */
lResult = sHighBits;
lResult <<= 16;
/* 清除32位值的低16位 */
lResult &= 0xFFFF0000;
/* 將第二個16位值放入32位值的低16位 */
lResult |= (long)sLowBits;
return lResult;
}
/////////////////////////////////////////////////
[問題的發現]:
我們先看如下測試代碼:
/////////////////////////////////////////////////
int main()
{
short sHighBits1 = 0x7fff;
short sHighBits2 = 0x8f12;
unsigned short usHighBits3 = 0xff12;
short sLowBits1 = 0x7bcd;
long lResult = 0;
printf("[sHighBits1 + sLowBits1] ";
lResult = CatenateBits16(sHighBits1, sLowBits1);
printf("lResult = %08x ", lResult, lResult);
lResult = CatenateBits16(sHighBits2, sLowBits1);
printf("lResult = %08x ", lResult, lResult);
lResult = CatenateBits16(usHighBits3, sLowBits1);
printf("lResult = %08x ", lResult, lResult);
}
/////////////////////////////////////////////////
運行結果為:
[sHighBits1 + sLowBits1]
lResult = 7fff7bcd
lResult = 8f127bcd
lResult = ff127bcd
嗯,運行很正確嘛……于是我們就放心的在自己的程序中使用起這個函數來了。
可是忽然有一天,我們的一個程序無論如何結果都不對!經過n個小時的檢查和調試,最后終于追蹤到……CatenateBits16() !?它的返回值居然是錯的!!
“郁悶!”你說,“這個函數怎么會有問題呢!?”
可是,更郁悶的還在后頭呢,因為你把程序中的輸入量作為參數,在一個簡單的main()里面單步調試:
/////////////////////////////////////////////////
int main()
{
short sHighBits1 = 0x7FFF;
short sHighBits2 = 0x8F12;
unsigned short usHighBits3 = 0x8F12;
short sLowBits1 = 0x7BCD; //你實際使用的參數
short sLowBits2 = 0x8BCD; //你實際使用的參數
long lResult = 0;
printf("[sHighBits1 + sLowBits1] ";
lResult = CatenateBits16(sHighBits1, sLowBits1);
printf("lResult = %08x ", lResult, lResult);
lResult = CatenateBits16(sHighBits2, sLowBits1);
printf("lResult = %08x ", lResult, lResult);
lResult = CatenateBits16(usHighBits3, sLowBits1);
printf("lResult = %08x ", lResult, lResult);
printf(" [sHighBits1 + sLowBits2] ";
lResult = CatenateBits16(sHighBits1, sLowBits2);
printf("lResult = %08x ", lResult, lResult);
lResult = CatenateBits16(sHighBits2, sLowBits2);
printf("lResult = %08x ", lResult, lResult);
lResult = CatenateBits16(usHighBits3, sLowBits2);
printf("lResult = %08x ", lResult, lResult);
return 0;
}
/////////////////////////////////////////////////
發現結果竟然是:
[sHighBits1 + sLowBits1]
lResult = 7fff7bcd
lResult = 8f127bcd
lResult = 8f127bcd
[sHighBits1 + sLowBits2]
lResult = ffff8bcd //oops!
lResult = ffff8bcd //oops!
lResult = ffff8bcd //oops!
前一次還好好的,后一次就ffff了?X檔案?
[X檔案的真相]:
注意那兩個我們用來當作低16位值的sLowBits1和sLowBits2。
已知:
使用 sLowBits1 = 0x7bcd 時,函數返回正確的值;
使用 sLowBits2 = 0x8bcd 時,函數中發生X檔案。
那么,sLowBits1與sLowBits2有什么區別?
注意了,sLowBits1和sLowBits2都是short型(而不是unsigned short),所以在這里,sLowBits1代表一個正數值,而sLowBits2卻代表了一個負數值(因為8即是二進制1000,sLowBits2最高位是1)。
再看CatenateBits16()函數:
/////////////////////////////////////////////////
long CatenateBits16(short sHighBits, short sLowBits)
{
long lResult = 0; /* 32位值的臨時變量*/
/* 將第一個16位值放入32位值的高16位 */
lResult = sHighBits;
lResult <<= 16;
/* 清除32位值的低16位 */
lResult &= 0xFFFF0000;
/* 將第二個16位值放入32位值的低16位 */
lResult |= (long)sLowBits; //注意這一句!!!!
return lResult;
}
/////////////////////////////////////////////////
如果我們在函數中用
printf("sLowBits = %04x ", sLowBits);
打印傳入的sLowBits值,會發現
sLowBits = 0x7bcd 時,打印結果為
sLowBits = 7bcd
而sLowBits = 0x8bcd時,打印結果為
sLowBits = ffff8bcd
是的,即使用%04x也打印出8位十六進制。
因此,我們看出來了:
當sLowBits = 0x8bcd時,函數中 "lResult |= (long)sLowBits;" 這一句執行,會先將sLowBits轉換為
0xffff8bcd
再與lResult做或運算。由于現在lResult的值為 0xXXXX0000 (其中XXXX是任何值),所以顯然,無論sHighBits是什么值,最后結果都會是
0xffff8bcd
而當sLowBits = 0x7bcd時,函數中 "lResult |= (long)sLowBits;" 這一句執行,會先將sLowBits轉換為
0x00007bcd
再與lResult做或運算。這樣做或運算出來的結果當然就是對的。
也就是說,CatenateBits16()在sLowBits的最高位為0的時候表現正常,而在最高位為1的時候出現偏差。
[教訓:在某些情況下作位運算和位處理的時候,考慮使用無符號數值——因為這個時候往往不需要處理符號。即使你需要的有符號的數值,那么也應該考慮自行在調用CatenateBits16()前后做轉換——畢竟在位處理中,有符號數值相當詭異!]
下面這個CatenateBits16()版本應該會好一些:
/////////////////////////////////////////////////
unsigned long CatenateBits16(unsigned short sHighBits, unsigned short sLowBits)
{
long lResult = 0;
/* 將第一個16位值放入32位值的高16位 */
lResult = sHighBits;
lResult <<= 16;
/* 清除32位值的低16位 */
lResult &= 0xFFFF0000;
/* 將第二個16位值放入32位值的低16位 */
lResult |= (long)sLowBits & 0x0000FFFF;
return lResult;
}
/////////////////////////////////////////////////
注意其中的 "lResult |= (long)sLowBits & 0x0000FFFF;"。事實上,現在即使我們把CatenateBits16()函數的參數(特別是sLowBits)聲明為short,結果也會是對的。
如果有一天你把一只兔子扔給一只老虎,老虎把兔子吃了,第二天把一只老鼠扔給它,它又吃了,那么說明第一天你看錯了:它本來就是一只貓。
全文結束
C++類中可以有靜態構造函數嗎? [問題點數:50分] 收藏
hly_ysu211
hly_ysu211
等級:
結帖率:56%
樓主 發表于: 2009-06-23 13:35:51
C++類中可以有靜態構造函數嗎?
分享到:
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理 回復次數:40
lw1a2
lw1a2
等級:
#1 得分:0 回復于: 2009-06-23 13:37:35
不可以
SiteApp大賽火熱進行中!對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
chenzhp
chenzhp
等級:
#2 得分:0 回復于: 2009-06-23 13:40:16
不行
2013年7月微軟MVP當選名單揭曉!對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
neohope
neohope
等級:
3
#3 得分:0 回復于: 2009-06-23 13:45:04
呵呵,不可以的
參與Linux應用有獎調查,贏取MAC筆記本、HTC One手機!對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
lingyin55
lingyin55
等級:
#4 得分:0 回復于: 2009-06-23 13:45:33
c#就有,不要混淆了。
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
Loaden
老鄧
等級:
22
#5 得分:0 回復于: 2009-06-23 13:46:23
不可以的
靜態類不能生成實例的
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
Jalien
Jalien
等級:
#6 得分:0 回復于: 2009-06-23 13:52:02
沒有吧,不知道lz想干什么,如果要想不用實例化對象就可以用它的方法的話把它的方法都聲明為static就行,然后類名::方法名 就可以訪問它的方法。(方法中的變量都必須為static變量)
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
vangoals
vangoals
等級:
#7 得分:0 回復于: 2009-06-23 13:53:36
樓主想要C++ 版的 Singleton ?
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
hly_ysu211
hly_ysu211
等級:
#8 得分:0 回復于: 2009-06-23 13:56:30
那如果一個類中有一個類類型的成員變量,而這個類型又沒有默認構造函數,這個時候怎么辦呢???望解釋!!
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
jhony_lee
jhony_lee
等級:
#9 得分:0 回復于: 2009-06-23 13:58:49
不可以滴
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
goodname
goodname
等級:
32
#10 得分:0 回復于: 2009-06-23 13:59:52
可以在初始化列表里面調用一下這個類類型的構造函數,當然你需要給它合時的參數
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
lire1213
lire1213
等級:
#11 得分:0 回復于: 2009-06-23 14:01:29
不可以
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
zhangxun2007
zhangxun2007
等級:
#12 得分:0 回復于: 2009-06-23 14:02:20
引用 5 樓 Loaden 的回復:
不可以的 靜態類不能生成實例的
頂
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
amossavez
amossavez
等級:
#13 得分:0 回復于: 2009-06-23 14:02:26
是不可以的!!!
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
pathuang68
pathuang68
等級:
2
#14 得分:0 回復于: 2009-06-23 14:05:19
這個話題很有意思。
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
hikaliv
hikaliv
等級:
#15 得分:0 回復于: 2009-06-23 14:47:34
當然可以有。
你理解靜態構造函數么?
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
Jalien
Jalien
等級:
#16 得分:0 回復于: 2009-06-23 15:45:53
試一下不就知道了:
C/C++ code
?
1
2
3
4
5
6
7
8
9
class Test{
public:
static Test(){
}
};
int main(){
Test t;
}
vs2008報錯:error C2574: “Test::Test(void)”: 不能聲明為靜態的
gcc報錯: error: constructor cannot be static member function
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
Jalien
Jalien
等級:
#17 得分:0 回復于: 2009-06-23 15:48:29
Borland5.6.4也編譯不過:Error E2092 cons.cpp 3: Storage class 'static' is not allowed here
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
hikaliv
hikaliv
等級:
#18 得分:0 回復于: 2009-06-23 15:56:29
好像真的不行……
C++做不到的呵……
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
hikaliv
hikaliv
等級:
#19 得分:0 回復于: 2009-06-23 15:56:55
又說錯話了……
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
Kevin_Perkins
Kevin_Perkins
等級:
#20 得分:0 回復于: 2009-06-23 16:05:46
不可以有靜態的構造函數。
靜態成員函數和靜態成員變量是屬于類的,不是屬于某個類的實例的。從這個意義上講,精通構造函數毫無意義。
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
adventurelw
adventurelw
等級:
#21 得分:0 回復于: 2009-06-23 16:21:00
引用 8 樓 hly_ysu211 的回復:
那如果一個類中有一個類類型的成員變量,而這個類型又沒有默認構造函數,這個時候怎么辦呢???望解釋!!
除非你人為將默認構造函數聲明為私有或保護的[1],以及聲明了其他非默認構造函數而沒有聲明默認構造函數[2],
否則不會沒有默認構造函數。
第一種情況純粹是跟自己過不去
第二種情況可以顯式調用相關構造函數
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
Loaden
老鄧
等級:
22
#22 得分:0 回復于: 2009-06-23 16:50:56
引用 8 樓 hly_ysu211 的回復:
那如果一個類中有一個類類型的成員變量,而這個類型又沒有默認構造函數,這個時候怎么辦呢???望解釋!!
怎么會沒有默認構造函數呢?
編譯器會生成一個。
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
liujinxunhappy08110
liujinxunhappy08110
等級:
#23 得分:0 回復于: 2009-06-23 16:54:28
不能有
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
sszwbuaa
sszwbuaa
等級:
#24 得分:0 回復于: 2009-06-23 17:38:55
引用 16 樓 Jalien 的回復:
試一下不就知道了: C/C++ code class Test{ public: static Test(){ } }; int main(){ Test t; } vs2008報錯:error C2574: “Test::Test(void)”: 不能聲明為靜態的 gcc報錯: error: constructor cannot be static member function
實踐出真知!
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
M0521
M0521
等級:
#25 得分:0 回復于: 2009-06-23 18:24:00
C++類中可以有靜態構造函數嗎 ??
如果你私有化 構造函數 那么構造函數 必須從靜態函數 new 出來 ! 不知道你說的是不是這個意思
C/C++ code
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class A
{
explicit A(char * n_world){ strcpy (m_array,n_world);}
char m_array[32];
public:
static A * Create(char * world) {
return new A(world);
}
void show(){std::cout<<m_array<<endl;}
};
A a=* A::Create(" C++ !");
a.show ();
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
nwao7890
nwao7890
等級:
#26 得分:0 回復于: 2009-06-23 18:30:45
靜態成員函數不能訪問一般的數據成員,而只能訪問靜態數據成員,也只能調用其他的靜態成員函數。
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
pengzhixi
pengzhixi
等級:
243
#27 得分:0 回復于: 2009-06-23 18:38:24
C++中不可以將構造函數申明為靜態函數
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
pengzhixi
pengzhixi
等級:
243
#28 得分:0 回復于: 2009-06-23 19:08:54
構造函數如果為靜態的話,this指針怎么處理呢?
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
ztz0223
好孩子的阿佐
等級:
#29 得分:0 回復于: 2009-06-23 20:12:00
構造函數要給每一個對象一個this指針
如果可以是靜態的,它如何構造和訪問this指針?
明顯是不可以的!
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
leewon1988
leewon1988
等級:
#30 得分:0 回復于: 2009-06-23 20:20:23
顯然不可能,靜態的是所有類共有的,構造函數是什么?是構造一個對象的,靜態的怎么會屬于一個對象呢?
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
lconline
lconline
等級:
#31 得分:0 回復于: 2010-06-02 09:46:56
引用 22 樓 loaden 的回復:
引用 8 樓 hly_ysu211 的回復: 那如果一個類中有一個類類型的成員變量,而這個類型又沒有默認構造函數,這個時候怎么辦呢???望解釋!!怎么會沒有默認構造函數呢?
編譯器會生成一個。
如果沒有定義默認構造函數,也沒有定義其它的構造函數。對象不會被初始化,也不會調用任何構造函數。
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
patricxuqi
patricxuqi
等級:
#32 得分:0 回復于: 2010-06-02 11:23:32
引用 28 樓 pengzhixi 的回復:
構造函數如果為靜態的話,this指針怎么處理呢?
靜態函數是類屬于類的,不是屬于對象的。而C++正是通過this指針來區分對象。靜態函數與非靜態函數的區別就是不接受這個this。如果構造函數不接受this的話,又怎么能建立某個具體對象呢?
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
blpluto
blpluto
等級:
#33 得分:0 回復于: 2010-06-02 11:30:19
不可以。這是因為C++的特性原因……
但是這個過程卻可以模擬!!
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
waterx
waterx
等級:
#34 得分:0 回復于: 2010-06-02 11:34:27
引用 31 樓 lconline 的回復:
引用 22 樓 loaden 的回復: 引用 8 樓 hly_ysu211 的回復: 那如果一個類中有一個類類型的成員變量,而這個類型又沒有默認構造函數,這個時候怎么辦呢???望解釋!! 怎么會沒有默認構造函數呢? 編譯器會生成一個。 如果沒有定義默認構造函數,也沒有定義其它的構造函數。對象不會被初始化,也不會調用任何構造函數。
不對,別誤導,
如果類沒有默認構造函數,而又沒有調用別的構造函數,編譯會提示沒有合適的構造函數調用
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
ZiyData
ZiyData
等級:
#35 得分:0 回復于: 2010-06-02 11:47:54
有什么不好,給了程序員很大的靈活性,可以自己在代碼中控制各個類的初始化順序!
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
ww884203
ww884203
等級:
#36 得分:0 回復于: 2010-06-02 12:08:58
不要胡思亂想了
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
magic7004
magic7004
等級:
#37 得分:0 回復于: 2010-06-02 13:16:02
靜態構造函數就算能有,那也是沒有任何意義的啊。
構造函數的作用就是對成員變量和需要的資源進行初始化,如果構造函數是靜態的,那么它就不可以訪問成員變量,那么它就無法實現構造函數的功能....
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
cyblueboy83
cyblueboy83
等級:
#38 得分:0 回復于: 2010-06-02 13:56:11
如果要實現單體,自己提供一個靜態的getinstance函數吧
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
lthyxy
那個無知的少年
等級:
#39 得分:0 回復于: 2010-06-02 14:07:04
編譯器會自動生成
對我有用[0] 丟個板磚[0] 引用 | 舉報 | 管理
softman11
softman11
等級:
#40 得分:0 回復于: 2010-06-02 14:21:42
C++不提供這個支持
但是C#可以的哈。
C/C++語言void及void指針深層探索2006-08-05 06:00 來源:blog 作者:蔣濤 責任編輯:方舟·yesky 評論(16)
1.概述
許多初學者對C/C++語言中的void及void指針類型不甚理解,因此在使用上出現了一些錯誤。本文將對void關鍵字的深刻含義進行解說,并詳述void及void指針類型的使用方法與技巧。
2.void的含義
void的字面意思是“無類型”,void *則為“無類型指針”,void *可以指向任何類型的數據。
void幾乎只有“注釋”和限制程序的作用,因為從來沒有人會定義一個void變量,讓我們試著來定義:
void a;
這行語句編譯時會出錯,提示“illegal use of type 'void'”。不過,即使void a的編譯不會出錯,它也沒有任何實際意義。
void真正發揮的作用在于:
(1)對函數返回的限定;
(2)對函數參數的限定。
我們將在第三節對以上二點進行具體說明。
眾所周知,如果指針p1和p2的類型相同,那么我們可以直接在p1和p2間互相賦值;如果p1和p2指向不同的數據類型,則必須使用強制類型轉換運算符把賦值運算符右邊的指針類型轉換為左邊指針的類型。
例如:
float *p1;
int *p2;
p1 = p2;
其中p1 = p2語句會編譯出錯,提示“'=' : cannot convert from 'int *' to 'float *'”,必須改為:
p1 = (float *)p2;
而void *則不同,任何類型的指針都可以直接賦值給它,無需進行強制類型轉換:
void *p1;
int *p2;
p1 = p2;
但這并不意味著,void *也可以無需強制類型轉換地賦給其它類型的指針。因為“無類型”可以包容“有類型”,而“有類型”則不能包容“無類型”。道理很簡單,我們可以說“男人和女人都是人”,但不能說“人是男人”或者“人是女人”。下面的語句編譯出錯:
void *p1;
int *p2;
p2 = p1;
提示“'=' : cannot convert from 'void *' to 'int *'”。
3.void的使用
下面給出void關鍵字的使用規則:
規則一如果函數沒有返回值,那么應聲明為void類型
在C語言中,凡不加返回值類型限定的函數,就會被編譯器作為返回整型值處理。但是許多程序員卻誤以為其為void類型。例如:
add ( int a, int b )
{
return a + b;
}
int main(int argc, char* argv[])
{
printf ( "2 + 3 = %d", add ( 2, 3) );
}
程序運行的結果為輸出:
2 + 3 = 5
這說明不加返回值說明的函數的確為int函數。
林銳博士《高質量C/C++編程》中提到:“C++語言有很嚴格的類型安全檢查,不允許上述情況(指函數不加類型聲明)發生”。可是編譯器并不一定這么認定,譬如在Visual C++6.0中上述add函數的編譯無錯也無警告且運行正確,所以不能寄希望于編譯器會做嚴格的類型檢查。
因此,為了避免混亂,我們在編寫C/C++程序時,對于任何函數都必須一個不漏地指定其類型。如果函數沒有返回值,一定要聲明為void類型。這既是程序良好可讀性的需要,也是編程規范性的要求。另外,加上void類型聲明后,也可以發揮代碼的“自注釋”作用。代碼的“自注釋”即代碼能自己注釋自己。
規則二如果函數無參數,那么應聲明其參數為void
在C++語言中聲明一個這樣的函數:
int function(void)
{
return 1;
}
則進行下面的調用是不合法的:
function(2);
因為在C++中,函數參數為void的意思是這個函數不接受任何參數。
我們在Turbo C 2.0中編譯:
#include "stdio.h"
fun()
{
return 1;
}
main()
{
printf("%d",fun(2));
getchar();
}
編譯正確且輸出1,這說明,在C語言中,可以給無參數的函數傳送任意類型的參數,但是在C++編譯器中編譯同樣的代碼則會出錯。在C++中,不能向無參數的函數傳送任何參數,出錯提示“'fun' : function does not take 1 parameters”。
所以,無論在C還是C++中,若函數不接受任何參數,一定要指明參數為void。
規則三小心使用void指針類型
按照ANSI(American National Standards Institute)標準,不能對void指針進行算法操作,即下列操作都是不合法的:
void * pvoid;
pvoid++; //ANSI:錯誤
pvoid += 1; //ANSI:錯誤
//ANSI標準之所以這樣認定,是因為它堅持:進行算法操作的指針必須是確定知道其指向數據類型大小的。
//例如:
int *pint;
pint++; //ANSI:正確
pint++的結果是使其增大sizeof(int)。
但是大名鼎鼎的GNU(GNU's Not Unix的縮寫)則不這么認定,它指定void *的算法操作與char *一致。
因此下列語句在GNU編譯器中皆正確:
pvoid++; //GNU:正確
pvoid += 1; //GNU:正確
pvoid++的執行結果是其增大了1。
在實際的程序設計中,為迎合ANSI標準,并提高程序的可移植性,我們可以這樣編寫實現同樣功能的代碼:
void * pvoid;
(char *)pvoid++; //ANSI:正確;GNU:正確
(char *)pvoid += 1; //ANSI:錯誤;GNU:正確
GNU和ANSI還有一些區別,總體而言,GNU較ANSI更“開放”,提供了對更多語法的支持。但是我們在真實設計時,還是應該盡可能地迎合ANSI標準。
規則四如果函數的參數可以是任意類型指針,那么應聲明其參數為void *
典型的如內存操作函數memcpy和memset的函數原型分別為:
void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );
這樣,任何類型的指針都可以傳入memcpy和memset中,這也真實地體現了內存操作函數的意義,因為它操作的對象僅僅是一片內存,而不論這片內存是什么類型。如果memcpy和memset的參數類型不是void *,而是char *,那才叫真的奇怪了!這樣的memcpy和memset明顯不是一個“純粹的,脫離低級趣味的”函數!
下面的代碼執行正確:
//示例:memset接受任意類型指針
int intarray[100];
memset ( intarray, 0, 100*sizeof(int) ); //將intarray清0
//示例:memcpy接受任意類型指針
int intarray1[100], intarray2[100];
memcpy ( intarray1, intarray2, 100*sizeof(int) ); //將intarray2拷貝給intarray1
有趣的是,memcpy和memset函數返回的也是void *類型,標準庫函數的編寫者是多么地富有學問啊!
規則五 void不能代表一個真實的變量
下面代碼都企圖讓void代表一個真實的變量,因此都是錯誤的代碼:
void a; //錯誤
function(void a); //錯誤
void體現了一種抽象,這個世界上的變量都是“有類型”的,譬如一個人不是男人就是女人(還有人妖?)。
void的出現只是為了一種抽象的需要,如果你正確地理解了面向對象中“抽象基類”的概念,也很容易理解void數據類型。正如不能給抽象基類定義一個實例,我們也不能定義一個void(讓我們類比的稱void為“抽象數據類型”)變量。
4.總結
小小的void蘊藏著很豐富的設計哲學,作為一名程序設計人員,對問題進行深一個層次的思考必然使我們受益匪淺
解決C++ 無法從void 轉換為LRESULT的方法詳解
發布:jingxian 字體:[增加 減小] 類型:轉載
本篇文章是對C++中無法從void轉換為LRESULT的解決方法進行了詳細的分析介紹,需要的朋友參考下
這個應該是一個MFC程序,ON_MESSAGE是添加消息響應函數,這句話就是添加熱鍵WM_HOTKEY的響應函數。當你注冊了熱鍵之后,當用戶按下熱鍵,會執行OnHotKey函數來處理這個消息。錯誤就應該是OnHotKey這個函數的聲明錯誤了,返回值應該是LRESULT. VS2008對消息的檢查更為嚴格,以前在VC6下完全正常運行的消息映射在VS2008下編譯不通過
ON_MESSAGE(WM_message,OnMyMessage);
OnMessage返回值必須為LRESULT,其形式為:afx_msg LRESULT OnMessage(WPARAM, LPARAM);
如果不符合,則有錯誤提示:error C2440: “static_cast”:無法從“void (__thiscall CMainFrame::* )(void)”轉換為“LRESULT (__thiscall CWnd::* )(WPARAM,LPARAM)”
解決方法如下:首先,把原來的消息函數返回值類型改為LRESULT,函數內可以隨便寫個return 0;然后消息函數的參數必須改寫成(WPARAM wParam,LPARAM lParam)而不論這兩個。
深入const int *p與int * const p的區別詳解(常量指針與指向常量的指針)
發布:jingxian 字體:[增加 減小] 類型:轉載
本篇文章是對const int *p與int * const p的區別進行了詳細的分析介紹,需要的朋友參考下
對于指針和常量,有以下三種形式都是正確的:
復制代碼 代碼如下:
const char * myPtr = &char_A;//指向常量的指針
char * const myPtr = &char_A;//常量的指針
const char * const myPtr = &char_A;//指向常量的常量指針
下面依次對這三種類型進行介紹。
因為*操作符是左操作符,左操作符的優先級是從右到左,對于
1.常量指針(Constant Pointers)
復制代碼 代碼如下:
int * const p
先看const再看* ,是p是一個常量類型的指針,不能修改這個指針的指向,但是這個指針所指向的地址上存儲的值可以修改。
實例1:
復制代碼 代碼如下:
#include<iostream>
#include<stdlib.h>
using namespace std;
void main()
{
int i1=30;
int i2=40;
int * const pi=&i1;//這里的pi指針式常量。
//pi=&i2; //注意這里,pi不能再這樣重新賦值了,即不能再指向另一個新地址。所以我已經注釋了它。
printf("%d\n", *pi ) ; //輸出是30
i1=80; //5.想想看:這里能用*pi=80;來代替嗎?可以,這里可以通過*pi修改i1的值。
printf("%d\n", *pi ) ; //輸出是80
system("pause");
}
實例2:
復制代碼 代碼如下:
char char_A = 'A';
char char_B = 'B';
char * const myPtr = &char_A;
myPtr = &char_B; // error - can't change address of myPtr
2.指向常量的指針(Pointers to Constants)
復制代碼 代碼如下:
const int *p
先看*再看const,定義一個指針指向一個常量,不能通過指針來修改這個指針指向的值。
實例3:
復制代碼 代碼如下:
#include<iostream>
#include<stdlib.h>
using namespace std;
void main()
{
int i1=30;
int i2=40;
const int * pi=&i1;
printf("%d\n", *pi ) ; //輸出是30
pi=&i2; //注意這里,pi可以在任意時候重新賦值一個新內存地址
i2=80; //想想看:這里能用*pi=80;來代替嗎?當然不能
printf("%d\n", *pi ) ; //輸出是80
system("pause");
}
實例4
復制代碼 代碼如下:
char char_A = 'A';
const char * myPtr = &char_A;
*myPtr = 'J'; // error - can't change value of *myPtr
所以指針p所指向的整型數是一個常量,其值不能被修改。
3.指向常量的常量指針
對于“指向常量的常量指針”,就必須同時滿足上述1和2中的內容,既不可以修改指針的值,也不可以修改指針指向的值。
4.引入字符數組和字符指針
字符數組和字符指針的定義如下:
復制代碼 代碼如下:
char a[] = "I Love You!"; //定義了一個字符數組
char *p = "I Love You!"; //定義了一個字符指針
可以將a理解為常量指針,而p為指向常量的指針,代碼實例如下:
復制代碼 代碼如下:
#include<iostream>
#include<stdlib.h>
using namespace std;
void main()
{
char a[] = "I Love You!"; //定義了一個字符數組,數組名a是一個常量指針,指向的位置不變,都是數組第一個元素的位置
char *p = "I Love You!"; //定義了一個字符指針,指針p指向一個字符串常量,這個字符串常量不能修改
//*(p+1)='a';//錯誤,不可以修改指針指向的值,所以這里注釋掉。
a[1]='a';//常量指針,不能修改指針的值,但是可以修改指針所指向的值。
//a=p;//錯誤,a是常量指針,不可修改其值。
cout<<a<<endl;
cout<<p<<endl;
cout<<a[1]<<endl;
cout<<*(p+2)<<endl;
system("pause");
}
輸出值為:
IaLove You!
I Love You!
a
L
C++
我們將從指針的語法和使用并結合例子來討論他們的區別。
Void 指針:
Cpp代碼
void * pointer_variable;
void這是是作為一個關鍵字來使用。
參考指針的定義和使用,我們知道所定義指針的數據類型同指針所指的數據類型是一致的。所分配給指針的地址也必須跟指針類型一樣。
例如:
Cpp代碼
int i;
float f;
int* exf;
float* test;
then
exf=&i;
int類型指針指向int變量的地址空間,所以是對的。
如果寫成:
Cpp代碼
exf=&f;
這條語句就會產生錯誤。因為int類型的指針指向的是一塊float變量的地址空間。同樣,如果我們試圖把float類型的指針指向一塊int類型的地址空間,也是錯誤的,例如:
Cpp代碼
test=&i;
上面一條語句將會報錯。
void類型指針是可以用來指向任何數據類型的特殊指針。
使用前面的例子,如果我們手動聲明一個void類型指針:
Cpp代碼
void* sample;
在前面的例子中,如果我們定義的一個void類型指針去指向一個float變量的地址空間是完全正確的。
Cpp代碼
sample=&f;
同樣,如果我們把這個void類型指針去指向一個int類型的地址空間也是正確的:
Cpp代碼
sample=&i;
void(類型)指針,是一種特殊的指針,它足夠靈巧的指向任何數據類型的地址空間。當然它也具有一定的局限:
在我們要取得指針所指地址空間的數據的時候使用的是 ‘*’操作符,程序員必須清楚了解到對于void指針不能使用這種方式來取得指針所指的內容。因為直接取內容是不允許的。而必須把void指針轉換成其他任何valid數據類型的指針,比如char,int,float等類型的指針,之后才能使用'*'取出指針的內容。這就是所謂的類型轉換的概念。
NULL pointer(空指針):
NULL指針的概念不同于前面所說的void指針。NULL指針是任何數據類型指針的一種,并且使用0作為初始值(譯者:這個好像要跟操作系統有關,有的系統把NULL 指針指向0地址,其他可能指向非零地址,對吧?有異議請留言)。當然這個不是強制性的。其表明NULL指針并未指向任何一塊合法的(valid)的地址空間。
舉例:
Cpp代碼
int* exforsys;
exforsys=0;
以上的聲明表示exforsys是一個int類型的指針,但其不指向任何有效的地址空間,這表明exforsys有一個空指針值(0)。
Void指針和NULL指針的區別:
Void指針是一個可以指向任何數據類型的特殊指針。NULL指針可是是任何數據類型的但其不指向任何有效的地址空間或者引用。區分空指針和指針未被初始化是很關鍵的,比如,假如程序員寫下:
Cpp代碼
#include <iostream.h>
int *exforsys;
void main()
{
*exforsys=100;
}
上面程序代碼的輸出如下:
NULL POINTER ASSIGNMENT
上面的程序拋出運行時的錯誤。表明指針變量exforsys還沒有被分配任何有效的地址空間,并且試圖存取0地址空間就產生了錯誤信息。
Author: UNKNOWN
Original Paper URL: http://www.exforsys.com/tutorials/c-plus-plus/c-plus-plus-void-pointer-and-null-pointer.html
更多關于NULL指針的AQ&A,參考:http://c-faq.com/null/
聲明:ITeye文章版權屬于作者,受法律保護。沒有作者書面許可不得轉載。
推薦鏈接
Java開發新方式:專注UI,快速開發!
返回頂樓
agurick
等級: 初級會員
性別:
文章: 59
積分: 10
來自: 陜西
發表時間:2009-04-25
NULL指針代表一個指針的值。
void 指針代表一個指針的類型,差的遠了去。
返回頂樓 回帖地址0 0 請登錄后投票
zhuqimeng
等級: 初級會員
性別:
文章: 28
積分: 30
來自: 徐州
發表時間:2009-05-20
agurick 寫道
NULL指針代表一個指針的值。
void 指針代表一個指針的類型,差的遠了去。
一針見血
C++中,為什么必須用造型來轉換*void
上一節 下一節 返回目錄編輯/糾錯/意見關注(146)更新:2012-12-31
分享到0
在C 語言中,你可以隱式地將*void 轉換為*T。這是不安全的。考慮一下:
#include<stdio.h>
int main(){
char i = 0;
char j = 0;
char* p = &i;
void* q = p;
int* pp = q; /* 不安全的,在C 中可以,C++不行 */
printf("%d %d\n",i,j);
*pp = -1; /* 覆蓋了從i 開始的內存 */
printf("%d %d\n",i,j);
}
使用一個并不指向T 類型的T*將是一場災難。因此,在C++中,如果從一個void*得到一個T*,你必須進行顯式轉換。舉例來說,要得到上列程序的這個令人別扭的效果,你可以這樣寫:
int* pp = (int*)q;
或者使用一個新的類型造型,以使這種沒有檢查的類型轉換操作變得更加清晰:
int* pp = static_cast<int*>(q);
造型被最好地避免了。
在C 語言中,這種不安全的轉換最常見的應用之一,是將malloc()的結果賦予一個合適的指針。例如:
int* p = malloc(sizeof(int));
在C++中,使用類型安全的new 操作符:
int* p = new int;
附帶地,new 操作符還提供了勝過malloc()的新特性:
new 不會偶然分配錯誤的內存數量;
new 會隱式地檢查內存耗盡情況,而且
new 提供了初始化。
舉例:
typedef std::complex<double> cmplx;
/* C 風格: */
cmplx* p = (cmplx*)malloc(sizeof(int)); /* 錯誤:類型不正確 */
/* 忘記測試p==0 */
if (*p == 7) { /* ... */ } /* 糟糕,忘記了初始化*p */
// C++風格:
cmplx* q = new cmplx(1,2); // 如果內存耗盡,將拋出一個bad_alloc 異常
if (*q == 7) { /* ... */ }
C++拷貝構造函數(深拷貝,淺拷貝)
對于普通類型的對象來說,它們之間的復制是很簡單的,例如:
int a=88;
int b=a;
而類對象與普通對象不同,類對象內部結構一般較為復雜,存在各種成員變量。下面看一個類對象拷貝的簡單例子。
#include <iostream>
using namespace std;
class CExample {
private:
int a;
public:
CExample(int b)
{ a=b;}
void Show ()
{
cout<<a<<endl;
}
};
int main()
{
CExample A(100);
CExample B=A;
B.Show ();
return 0;
}
運行程序,屏幕輸出100。從以上代碼的運行結果可以看出,系統為對象B分配了內存并完成了與對象A的復制過程。就類對象而言,相同類型的類對象是通過拷貝構造函數來完成整個復制過程的。下面舉例說明拷貝構造函數的工作過程。
#include <iostream>
using namespace std;
class CExample {
private:
int a;
public:
CExample(int b)
{ a=b;}
CExample(const CExample& C)
{
a=C.a;
}
void Show ()
{
cout<<a<<endl;
}
};
int main()
{
CExample A(100);
CExample B=A;
B.Show ();
return 0;
}
CExample(const CExample& C)就是我們自定義的拷貝構造函數。可見,拷貝構造函數是一種特殊的構造函數,函數的名稱必須和類名稱一致,它的唯一的一個參數是本類型的一個引用變量,該參數是const類型,不可變的。例如:類X的拷貝構造函數的形式為X(X& x)。
當用一個已初始化過了的自定義類類型對象去初始化另一個新構造的對象的時候,拷貝構造函數就會被自動調用。也就是說,當類的對象需要拷貝時,拷貝構造函數將會被調用。以下情況都會調用拷貝構造函數:
一個對象以值傳遞的方式傳入函數體
一個對象以值傳遞的方式從函數返回
一個對象需要通過另外一個對象進行初始化。
如果在類中沒有顯式地聲明一個拷貝構造函數,那么,編譯器將會自動生成一個默認的拷貝構造函數,該構造函數完成對象之間的位拷貝。位拷貝又稱淺拷貝,后面將進行說明。
自定義拷貝構造函數是一種良好的編程風格,它可以阻止編譯器形成默認的拷貝構造函數,提高源碼效率。
淺拷貝和深拷貝
在某些狀況下,類內成員變量需要動態開辟堆內存,如果實行位拷貝,也就是把對象里的值完全復制給另一個對象,如A=B。這時,如果B中有一個成員變量指針已經申請了內存,那A中的那個成員變量也指向同一塊內存。這就出現了問題:當B把內存釋放了(如:析構),這時A內的指針就是野指針了,出現運行錯誤。
深拷貝和淺拷貝可以簡單理解為:如果一個類擁有資源,當這個類的對象發生復制過程的時候,資源重新分配,這個過程就是深拷貝,反之,沒有重新分配資源,就是淺拷貝。下面舉個深拷貝的例子。
#include <iostream>
using namespace std;
class CA
{
public:
CA(int b,char* cstr)
{
a=b;
str=new char[b];
strcpy(str,cstr);
}
CA(const CA& C)
{
a=C.a;
str=new char[a]; //深拷貝
if(str!=0)
strcpy(str,C.str);
}
void Show()
{
cout<<str<<endl;
}
~CA()
{
delete str;
}
private:
int a;
char *str;
};
int main()
{
CA A(10,"Hello!");
CA B=A;
B.Show();
return 0;
}
深拷貝和淺拷貝的定義可以簡單理解成:如果一個類擁有資源(堆,或者是其它系統資源),當這個類的對象發生復制過程的時候,這個過程就可以叫做深拷貝,反之對象存在資源,但復制過程并未復制資源的情況視為淺拷貝。
淺拷貝資源后在釋放資源的時候會產生資源歸屬不清的情況導致程序運行出錯。
Test(Test &c_t)是自定義的拷貝構造函數,拷貝構造函數的名稱必須與類名稱一致,函數的形式參數是本類型的一個引用變量,且必須是引用。
當用一個已經初始化過了的自定義類類型對象去初始化另一個新構造的對象的時候,拷貝構造函數就會被自動調用,如果你沒有自定義拷貝構造函數的時候,系統將會提供給一個默認的拷貝構造函數來完成這個過程,上面代碼的復制核心語句就是通過Test(Test &c_t)拷貝構造函數內的p1=c_t.p1;語句完成的。
關于C語言的void main() ---轉帖
閱讀:293回復:0
樓主#
發表于 2012-8-10 22:55:46
很多人甚至市面上的一些書籍,都使用了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函數的定義。
一、 C語言中的main() 在C89中,main( )是可以接受的。Brian W. Kernighan和Dennis M. Ritchie的經典巨著The C programming Language 2e(《C 程序設計語言第二版》)用的就是main( )。不過在最新的C99標準中,只有以下兩種定義方式是正確的:
int main(void)
int main(int argc, char *argv[])
(參考資料:ISO/IEC 9899:1999 (E) Programming languages ? C 5.1.2.2.1 Program startup)
當然,我們也可以做一點小小的改動。例如:char *argv[]可以寫成char **argv;argv和argc可以改成別的變量名(如intval和charval),不過一定要符合變量的命名規則。
如果不需要從命令行中獲取參數,請用int main(void);否則請用int main(int argc, char *argv[])。
main函數的返回值類型必須是int,這樣返回值才能傳遞給程序的調用者(如操作系統)。
如果main函數的最后沒有寫return語句的話,C99規定編譯器要自動在生成的目標文件中(如exe文件)加入return 0;,表示程序正常退出。不過,我還是建議你最好在main函數的最后加上return語句,雖然沒有這個必要,但這是一個好的習慣。注意,vc6不會在目標文件中加入return 0;,大概是因為vc6是98年的產品,所以才不支持這個特性。現在明白我為什么建議你最好加上return語句了吧!不過,gcc3.2(Linux下的C編譯器)會在生成的目標文件中加入return 0;。
二、 C++中的main() C++98中定義了如下兩種main函數的定義方式:
int main( )
int main(int argc, char *argv[])
參考資料:ISO/IEC 14882(1998-9-01)Programming languages ? C++ 3.6 Start and termination
int main( )等同于C99中的int main(void);int main(int argc, char *argv[])的用法也和C99中定義的一樣。同樣,main函數的返回值類型也必須是int。如果main函數的末尾沒寫return語句,C++98規定編譯器要自動在生成的目標文件中加入return 0;。同樣,vc6也不支持這個特性,但是g++3.2(Linux下的C++編譯器)支持。
三、 關于void main() 在C和C++中,不接收任何參數也不返回任何信息的函數原型為“void foo(void);”。可能正是因為這個,所以很多人都誤認為如果不需要程序返回值時可以把main函數定義成void main(void)。然而這是錯誤的!main函數的返回值應該定義為int類型,C和C++標準中都是這樣規定的。雖然在一些編譯器中,void main可以通過編譯(如vc6),但并非所有編譯器都支持void main,因為標準中從來沒有定義過void main。g++3.2中如果main函數的返回值不是int類型,就根本通不過編譯。而gcc3.2則會發出警告。所以,如果你想你的程序擁有很好的可移植性,請一定要用int main。
不要用“我的老師告訴我這么做是對的”之類的話來為自己開脫;老師們總是習慣犯錯誤(teachers have a bad habit of being wrong)。寫安全的,合乎標準的代碼,大家就可以專注于你程序中其它的問題而不是在這種規范方面的東西上浪費時間。
應當指出:在某些系統中,若程序使用void main定義或沒有return值,則可能導致堆棧異常從而導致系統故障。(詳見后面英文部分)
四、返回值的作用 main函數的返回值用于說明程序的退出狀態。如果返回0,則代表程序正常退出;返回其它數字的含義則由系統決定。通常,返回非零代表程序異常退出。下面我們在winxp環境下做一個小實驗。首先編譯下面的程序:
int main(void)
{
return 0;
}
然后打開附件里的“命令提示符”,在命令行里運行剛才編譯好的可執行文件,然后輸入“echo %ERRORLEVEL%”,回車,就可以看到程序的返回值為0。假設剛才編譯好的文件是a.exe,如果輸入“a && dir”,則會列出當前目錄下的文件夾和文件。但是如果改成“return -1”,或者別的非0值,重新編譯后輸入“a && dir”,則dir不會執行。因為&&的含義是:如果&&前面的程序正常退出,則繼續執行&&后面的程序,否則不執行。也就是說,利用程序的返回值,我們可以控制要不要執行下一個程序。這就是int main的好處。如果你有興趣,也可以把main函數的返回值類型改成非int類型(如float),重新編譯后執行“a && dir”,看看會出現什么情況,想想為什么會出現那樣的情況。順便提一下,如果輸入a || dir的話,則表示如果a異常退出,則執行dir。
五、那么int main(int argc, char *argv[], char *envp[])呢? 這當然也不是標準C/C++里面定義的東西!char *envp[]是某些編譯器提供的擴展功能,用于獲取系統的環境變量。因為不是標準,所以并非所有編譯器都支持,故而移植性差,不推薦使用——除非你的程序是專門設計用于工作在特定的環境中而且需要獲取系統的環境變量。
----轉《C語言中文網》
關于C語言的void main() ---轉帖
閱讀:293回復:0
樓主#
發表于 2012-8-10 22:55:46
很多人甚至市面上的一些書籍,都使用了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函數的定義。
一、 C語言中的main() 在C89中,main( )是可以接受的。Brian W. Kernighan和Dennis M. Ritchie的經典巨著The C programming Language 2e(《C 程序設計語言第二版》)用的就是main( )。不過在最新的C99標準中,只有以下兩種定義方式是正確的:
int main(void)
int main(int argc, char *argv[])
(參考資料:ISO/IEC 9899:1999 (E) Programming languages ? C 5.1.2.2.1 Program startup)
當然,我們也可以做一點小小的改動。例如:char *argv[]可以寫成char **argv;argv和argc可以改成別的變量名(如intval和charval),不過一定要符合變量的命名規則。
如果不需要從命令行中獲取參數,請用int main(void);否則請用int main(int argc, char *argv[])。
main函數的返回值類型必須是int,這樣返回值才能傳遞給程序的調用者(如操作系統)。
如果main函數的最后沒有寫return語句的話,C99規定編譯器要自動在生成的目標文件中(如exe文件)加入return 0;,表示程序正常退出。不過,我還是建議你最好在main函數的最后加上return語句,雖然沒有這個必要,但這是一個好的習慣。注意,vc6不會在目標文件中加入return 0;,大概是因為vc6是98年的產品,所以才不支持這個特性。現在明白我為什么建議你最好加上return語句了吧!不過,gcc3.2(Linux下的C編譯器)會在生成的目標文件中加入return 0;。
二、 C++中的main() C++98中定義了如下兩種main函數的定義方式:
int main( )
int main(int argc, char *argv[])
參考資料:ISO/IEC 14882(1998-9-01)Programming languages ? C++ 3.6 Start and termination
int main( )等同于C99中的int main(void);int main(int argc, char *argv[])的用法也和C99中定義的一樣。同樣,main函數的返回值類型也必須是int。如果main函數的末尾沒寫return語句,C++98規定編譯器要自動在生成的目標文件中加入return 0;。同樣,vc6也不支持這個特性,但是g++3.2(Linux下的C++編譯器)支持。
三、 關于void main() 在C和C++中,不接收任何參數也不返回任何信息的函數原型為“void foo(void);”。可能正是因為這個,所以很多人都誤認為如果不需要程序返回值時可以把main函數定義成void main(void)。然而這是錯誤的!main函數的返回值應該定義為int類型,C和C++標準中都是這樣規定的。雖然在一些編譯器中,void main可以通過編譯(如vc6),但并非所有編譯器都支持void main,因為標準中從來沒有定義過void main。g++3.2中如果main函數的返回值不是int類型,就根本通不過編譯。而gcc3.2則會發出警告。所以,如果你想你的程序擁有很好的可移植性,請一定要用int main。
不要用“我的老師告訴我這么做是對的”之類的話來為自己開脫;老師們總是習慣犯錯誤(teachers have a bad habit of being wrong)。寫安全的,合乎標準的代碼,大家就可以專注于你程序中其它的問題而不是在這種規范方面的東西上浪費時間。
應當指出:在某些系統中,若程序使用void main定義或沒有return值,則可能導致堆棧異常從而導致系統故障。(詳見后面英文部分)
四、返回值的作用 main函數的返回值用于說明程序的退出狀態。如果返回0,則代表程序正常退出;返回其它數字的含義則由系統決定。通常,返回非零代表程序異常退出。下面我們在winxp環境下做一個小實驗。首先編譯下面的程序:
int main(void)
{
return 0;
}
然后打開附件里的“命令提示符”,在命令行里運行剛才編譯好的可執行文件,然后輸入“echo %ERRORLEVEL%”,回車,就可以看到程序的返回值為0。假設剛才編譯好的文件是a.exe,如果輸入“a && dir”,則會列出當前目錄下的文件夾和文件。但是如果改成“return -1”,或者別的非0值,重新編譯后輸入“a && dir”,則dir不會執行。因為&&的含義是:如果&&前面的程序正常退出,則繼續執行&&后面的程序,否則不執行。也就是說,利用程序的返回值,我們可以控制要不要執行下一個程序。這就是int main的好處。如果你有興趣,也可以把main函數的返回值類型改成非int類型(如float),重新編譯后執行“a && dir”,看看會出現什么情況,想想為什么會出現那樣的情況。順便提一下,如果輸入a || dir的話,則表示如果a異常退出,則執行dir。
五、那么int main(int argc, char *argv[], char *envp[])呢? 這當然也不是標準C/C++里面定義的東西!char *envp[]是某些編譯器提供的擴展功能,用于獲取系統的環境變量。因為不是標準,所以并非所有編譯器都支持,故而移植性差,不推薦使用——除非你的程序是專門設計用于工作在特定的環境中而且需要獲取系統的環境變量。
----轉《C語言中文網》
C/C++語言void及void指針深層探索(轉) 發布時間:2008-11-24 15:56:07
技術類別:軟件開發
1.概述
許多初學者對C/C++語言中的void及void指針類型不甚理解,因此在使用上出現了一些錯誤。本文將對void關鍵字的深刻含義進行解說,并詳述void及void指針類型的使用方法與技巧。
2.void的含義
void的字面意思是“無類型”,void *則為“無類型指針”,void *可以指向任何類型的數據。
void幾乎只有“注釋”和限制程序的作用,因為從來沒有人會定義一個void變量,讓我們試著來定義:
void a;
這行語句編譯時會出錯,提示“illegal use of type 'void'”。不過,即使void a的編譯不會出錯,它也沒有任何實際意義。
void真正發揮的作用在于:
(1) 對函數返回的限定;
(2) 對函數參數的限定。
我們將在第三節對以上二點進行具體說明。
眾所周知,如果指針p1和p2的類型相同,那么我們可以直接在p1和p2間互相賦值;如果p1和p2指向不同的數據類型,則必須使用強制類型轉換運算符把賦值運算符右邊的指針類型轉換為左邊指針的類型。
例如:
float *p1;
int *p2;
p1 = p2;
其中p1 = p2語句會編譯出錯,提示“'=' : cannot convert from 'int *' to 'float *'”,必須改為:
p1 = (float *)p2;
而void *則不同,任何類型的指針都可以直接賦值給它,無需進行強制類型轉換:
void *p1;
int *p2;
p1 = p2;
但這并不意味著,void *也可以無需強制類型轉換地賦給其它類型的指針。因為“無類型”可以包容“有類型”,而“有類型”則不能包容“無類型”。道理很簡單,我們可以說“男人和女人都是人”,但不能說“人是男人”或者“人是女人”。下面的語句編譯出錯:
void *p1;
int *p2;
p2 = p1;
提示“'=' : cannot convert from 'void *' to 'int *'”。
3.void的使用
下面給出void關鍵字的使用規則:
規則一 如果函數沒有返回值,那么應聲明為void類型
在C語言中,凡不加返回值類型限定的函數,就會被編譯器作為返回整型值處理。但是許多程序員卻誤以為其為void類型。例如:
add ( int a, int b )
{
return a + b;
}
int main(int argc, char* argv[])
{
printf ( "2 + 3 = %d", add ( 2, 3) );
}
程序運行的結果為輸出:
2 + 3 = 5
這說明不加返回值說明的函數的確為int函數。
林銳博士《高質量C/C++編程》中提到:“C++語言有很嚴格的類型安全檢查,不允許上述情況(指函數不加類型聲明)發生”。可是編譯器并不一定這么認定,譬如在Visual C++6.0中上述add函數的編譯無錯也無警告且運行正確,所以不能寄希望于編譯器會做嚴格的類型檢查。
因此,為了避免混亂,我們在編寫C/C++程序時,對于任何函數都必須一個不漏地指定其類型。如果函數沒有返回值,一定要聲明為void類型。這既是程序良好可讀性的需要,也是編程規范性的要求。另外,加上void類型聲明后,也可以發揮代碼的“自注釋”作用。代碼的“自注釋”即代碼能自己注釋自己。
規則二 如果函數無參數,那么應聲明其參數為void
在C++語言中聲明一個這樣的函數:
int function(void)
{
return 1;
}
則進行下面的調用是不合法的:
function(2);
因為在C++中,函數參數為void的意思是這個函數不接受任何參數。
我們在Turbo C 2.0中編譯:
#include "stdio.h"
fun()
{
return 1;
}
main()
{
printf("%d",fun(2));
getchar();
}
編譯正確且輸出1,這說明,在C語言中,可以給無參數的函數傳送任意類型的參數,但是在C++編譯器中編譯同樣的代碼則會出錯。在C++中,不能向無參數的函數傳送任何參數,出錯提示“'fun' : function does not take 1 parameters”。
所以,無論在C還是C++中,若函數不接受任何參數,一定要指明參數為void。
規則三 小心使用void指針類型
按照ANSI(American National Standards Institute)標準,不能對void指針進行算法操作,即下列操作都是不合法的:
void * pvoid;
pvoid++; //ANSI:錯誤
pvoid += 1; //ANSI:錯誤
//ANSI標準之所以這樣認定,是因為它堅持:進行算法操作的指針必須是確定知道其指向數據類型大小的。
//例如:
int *pint;
pint++; //ANSI:正確
pint++的結果是使其增大sizeof(int)。
但是大名鼎鼎的GNU(GNU's Not Unix的縮寫)則不這么認定,它指定void *的算法操作與char *一致。
因此下列語句在GNU編譯器中皆正確:
pvoid++; //GNU:正確
pvoid += 1; //GNU:正確
pvoid++的執行結果是其增大了1。
在實際的程序設計中,為迎合ANSI標準,并提高程序的可移植性,我們可以這樣編寫實現同樣功能的代碼:
void * pvoid;
(char *)pvoid++; //ANSI:正確;GNU:正確
(char *)pvoid += 1; //ANSI:錯誤;GNU:正確
GNU和ANSI還有一些區別,總體而言,GNU較ANSI更“開放”,提供了對更多語法的支持。但是我們在真實設計時,還是應該盡可能地迎合ANSI標準。
規則四 如果函數的參數可以是任意類型指針,那么應聲明其參數為void *
典型的如內存操作函數memcpy和memset的函數原型分別為:
void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );
這樣,任何類型的指針都可以傳入memcpy和memset中,這也真實地體現了內存操作函數的意義,因為它操作的對象僅僅是一片內存,而不論這片內存是什么類型。如果memcpy和memset的參數類型不是void *,而是char *,那才叫真的奇怪了!這樣的memcpy和memset明顯不是一個“純粹的,脫離低級趣味的”函數!
下面的代碼執行正確:
//示例:memset接受任意類型指針
int intarray[100];
memset ( intarray, 0, 100*sizeof(int) ); //將intarray清0
//示例:memcpy接受任意類型指針
int intarray1[100], intarray2[100];
memcpy ( intarray1, intarray2, 100*sizeof(int) ); //將intarray2拷貝給intarray1
有趣的是,memcpy和memset函數返回的也是void *類型,標準庫函數的編寫者是多么地富有學問啊!
規則五 void不能代表一個真實的變量
下面代碼都企圖讓void代表一個真實的變量,因此都是錯誤的代碼:
void a; //錯誤
function(void a); //錯誤
void體現了一種抽象,這個世界上的變量都是“有類型”的,譬如一個人不是男人就是女人(還有人妖?)。
void的出現只是為了一種抽象的需要,如果你正確地理解了面向對象中“抽象基類”的概念,也很容易理解void數據類型。正如不能給抽象基類定義一個實例,我們也不能定義一個void(讓我們類比的稱void為“抽象數據類型”)變量。
4.總結
小小的void蘊藏著很豐富的設計哲學,作為一名程序設計人員,對問題進行深一個層次的思考必然使我們受益匪淺。
C++拷貝構造函數(深拷貝,淺拷貝)
對于普通類型的對象來說,它們之間的復制是很簡單的,例如:
int a=88;
int b=a;
而類對象與普通對象不同,類對象內部結構一般較為復雜,存在各種成員變量。下面看一個類對象拷貝的簡單例子。
#include <iostream>
using namespace std;
class CExample {
private:
int a;
public:
CExample(int b)
{ a=b;}
void Show ()
{
cout<<a<<endl;
}
};
int main()
{
CExample A(100);
CExample B=A;
B.Show ();
return 0;
}
運行程序,屏幕輸出100。從以上代碼的運行結果可以看出,系統為對象B分配了內存并完成了與對象A的復制過程。就類對象而言,相同類型的類對象是通過拷貝構造函數來完成整個復制過程的。下面舉例說明拷貝構造函數的工作過程。
#include <iostream>
using namespace std;
class CExample {
private:
int a;
public:
CExample(int b)
{ a=b;}
CExample(const CExample& C)
{
a=C.a;
}
void Show ()
{
cout<<a<<endl;
}
};
int main()
{
CExample A(100);
CExample B=A;
B.Show ();
return 0;
}
CExample(const CExample& C)就是我們自定義的拷貝構造函數。可見,拷貝構造函數是一種特殊的構造函數,函數的名稱必須和類名稱一致,它的唯一的一個參數是本類型的一個引用變量,該參數是const類型,不可變的。例如:類X的拷貝構造函數的形式為X(X& x)。
當用一個已初始化過了的自定義類類型對象去初始化另一個新構造的對象的時候,拷貝構造函數就會被自動調用。也就是說,當類的對象需要拷貝時,拷貝構造函數將會被調用。以下情況都會調用拷貝構造函數:
一個對象以值傳遞的方式傳入函數體
一個對象以值傳遞的方式從函數返回
一個對象需要通過另外一個對象進行初始化。
如果在類中沒有顯式地聲明一個拷貝構造函數,那么,編譯器將會自動生成一個默認的拷貝構造函數,該構造函數完成對象之間的位拷貝。位拷貝又稱淺拷貝,后面將進行說明。
自定義拷貝構造函數是一種良好的編程風格,它可以阻止編譯器形成默認的拷貝構造函數,提高源碼效率。
淺拷貝和深拷貝
在某些狀況下,類內成員變量需要動態開辟堆內存,如果實行位拷貝,也就是把對象里的值完全復制給另一個對象,如A=B。這時,如果B中有一個成員變量指針已經申請了內存,那A中的那個成員變量也指向同一塊內存。這就出現了問題:當B把內存釋放了(如:析構),這時A內的指針就是野指針了,出現運行錯誤。
深拷貝和淺拷貝可以簡單理解為:如果一個類擁有資源,當這個類的對象發生復制過程的時候,資源重新分配,這個過程就是深拷貝,反之,沒有重新分配資源,就是淺拷貝。下面舉個深拷貝的例子。
#include <iostream>
using namespace std;
class CA
{
public:
CA(int b,char* cstr)
{
a=b;
str=new char[b];
strcpy(str,cstr);
}
CA(const CA& C)
{
a=C.a;
str=new char[a]; //深拷貝
if(str!=0)
strcpy(str,C.str);
}
void Show()
{
cout<<str<<endl;
}
~CA()
{
delete str;
}
private:
int a;
char *str;
};
int main()
{
CA A(10,"Hello!");
CA B=A;
B.Show();
return 0;
}
深拷貝和淺拷貝的定義可以簡單理解成:如果一個類擁有資源(堆,或者是其它系統資源),當這個類的對象發生復制過程的時候,這個過程就可以叫做深拷貝,反之對象存在資源,但復制過程并未復制資源的情況視為淺拷貝。
淺拷貝資源后在釋放資源的時候會產生資源歸屬不清的情況導致程序運行出錯。
Test(Test &c_t)是自定義的拷貝構造函數,拷貝構造函數的名稱必須與類名稱一致,函數的形式參數是本類型的一個引用變量,且必須是引用。
當用一個已經初始化過了的自定義類類型對象去初始化另一個新構造的對象的時候,拷貝構造函數就會被自動調用,如果你沒有自定義拷貝構造函數的時候,系統將會提供給一個默認的拷貝構造函數來完成這個過程,上面代碼的復制核心語句就是通過Test(Test &c_t)拷貝構造函數內的p1=c_t.p1;語句完成的。
分類: Basic C++
標簽: 拷貝構造函數, 深拷貝, 淺拷貝
typedef
百科名片
typedef struc
在計算機編程語言中用來為復雜的聲明定義簡單的別名,與宏定義有些差異。它本身是一種存儲類的關鍵字,與auto、extern、mutable、static、register等關鍵字不能出現在同一個表達式中。
目錄
定義
用法總結
語言用法
代碼簡化
平臺開發
編輯本段
定義
typedef聲明,簡稱typedef,為現有類型創建一個新的名字,或稱為類型別名,在結構體定義,還有一些數組等地方都大量的用到。
它有助于創建平臺無關類型,甚至能隱藏復雜和難以理解的語法 。使用typedef可編寫出更加美觀和可讀的代碼。所謂美觀,意指typedef能隱藏笨拙的語法構造以及平臺相關的數據類型,從而增強可移植性以及未來的可維護性。本文下面將竭盡全力來揭示typedef強大功能以及如何避免一些常見的使用陷阱。[1]
編輯本段
用法總結
如何創建平臺無關的數據類型,隱藏笨拙且難以理解的語法?
使用typedef為現有類型創建同義字,定義易于記憶的類型名
typedef使用最多的地方是創建易于記憶的類型名,用它來歸檔程序員的意圖。類型出現在所聲明的變量名字中,位于“typedef”關鍵字右邊。例如:
typedef int size;
此聲明定義了一個int的同義字,名字為size。注意typedef并不創建新的類型。它僅僅為現有類型添加一個同義字。你可以在任何需要int的上下文中使用size:
void measure(size * psz);
size array[4];
size len = file.getlength();
std::vector<size> vs;
typedef 還可以掩飾復合類型,如指針和數組。
例如,你不用像下面這樣重復定義有 81 個字符元素的數組:
char line[81];
char text[81];
定義一個 typedef,每當要用到相同類型和大小的數組時,可以這樣:
typedef char Line[81];
此時Line類型即代表了具有81個元素的字符數組,使用方法如下:
Line text, secondline;
getline(text);
同樣,可以像下面這樣隱藏指針語法:
typedef char * pstr;
int mystrcmp(pstr, pstr);
這里將帶我們到達第一個 typedef 陷阱。標準函數 strcmp()有兩個‘ const char *'類型的參數。因此,它可能會誤導人們像下面這樣聲明 mystrcmp():
int mystrcmp(const pstr, const pstr);
用GNU的gcc和g++編譯器,是會出現警告的,按照順序,‘const pstr'被解釋為‘char* const‘(一個指向char的指針常量),兩者表達的并非同一意思(詳見C++ Primer 第四版 P112)。
char * const cp : 定義一個指向字符的指針常數,即const指針,常指針。
const char* p : 定義一個指向字符常數的指針,即常量指針。
char const* p : 等同于const char* p[2]。
為了得到正確的類型,應當如下聲明:
typedef const char* pstr;
編輯本段
語言用法
基本解釋
typedef為C語言的關鍵字,作用是為一種數據類型定義一個新名字。這里的數據類型包括內部數據類型(int,char等)和自定義的數據類型(struct等)。
在編程中使用typedef目的一般有兩個,一個是給變量一個易記且意義明確的新名字,另一個是簡化一些比較復雜的類型聲明。
至于typedef有什么微妙之處,請你接著看下面對幾個問題的具體闡述。
2. typedef & 結構的問題
當用下面的代碼定義一個結構時,編譯器報了一個錯誤,為什么呢?莫非C語言不允許在結構中包含指向它自己的指針嗎?請你先猜想一下,然后看下文說明:
typedef struct tagNode
{
char *pItem;
pNode pNext;
} *pNode;
分析:
1、typedef的最簡單使用
typedef long byte_4;
給已知數據類型long起個新名字,叫byte_4。
2、 typedef與結構結合使用
typedef struct tagMyStruct
{
int iNum;
long lLength;
} MyStruct;
這語句實際上完成兩個操作:
1) 定義一個新的結構類型
struct tagMyStruct
{
int iNum;
long lLength;
};
分析:tagMyStruct稱為“tag”,即“標簽”,實際上是一個臨時名字,struct關鍵字和tagMyStruct一起,構成了這個結構類型,不論是否有typedef,這個結構都存在。
我們可以用struct tagMyStruct varName來定義變量,但要注意,使用tagMyStruct varName來定義變量是不對的,因為struct 和tagMyStruct合在一起才能表示一個結構類型。
2) typedef為這個新的結構起了一個名字,叫MyStruct。
typedef struct tagMyStruct MyStruct;
因此,MyStruct實際上相當于struct tagMyStruct,我們可以使用MyStruct varName來定義變量。
答案與分析
C語言當然允許在結構中包含指向它自己的指針,我們可以在建立鏈表等數據結構的實現上看到無數這樣的例子,上述代碼的根本問題在于typedef的應用。
根據我們上面的闡述可以知道:新結構建立的過程中遇到了pNext域的聲明,類型是pNode,要知道pNode表示的是類型的新名字,那么在類型本身還沒有建立完成的時候,這個類型的新名字也還不存在,也就是說這個時候編譯器根本不認識pNode。
解決這個問題的方法有多種:
1)、
typedef struct tagNode
{
char *pItem;
struct tagNode *pNext;
} *pNode;
2)、
typedef struct tagNode *pNode;
struct tagNode
{
char *pItem;
pNode pNext;
};
注意:在這個例子中,你用typedef給一個還未完全聲明的類型起新名字。C語言編譯器支持這種做法。
3)、規范做法:
struct tagNode
{
char *pItem;
struct tagNode *pNext;
};
typedef struct tagNode *pNode;
3. typedef & #define的問題
有下面兩種定義pStr數據類型的方法,兩者有什么不同?哪一種更好一點?
typedef char* pStr;
#define pStr char*;
答案與分析:
通常講,typedef要比#define要好,特別是在有指針的場合。請看例子:
typedef char* pStr1;
#define pStr2 char *
pStr1 s1, s2;
pStr2 s3, s4;
在上述的變量定義中,s1、s2、s3都被定義為char *,而s4則定義成了char,不是我們所預期的指針變量,根本原因就在于#define只是簡單的字符串替換而typedef則是為一個類型起新名字。
上例中define語句必須寫成 pStr2 s3, *s4; 這這樣才能正常執行。
#define用法例子:
#define f(x) x*x
main( )
{
int a=6,b=2,c;
c=f(a) / f(b);
printf("%d \\n",c);
}
以下程序的輸出結果是: 36。
因為如此原因,在許多C語言編程規范中提到使用#define定義時,如果定義中包含表達式,必須使用括號,則上述定義應該如下定義才對:
#define f(x) (x*x)
當然,如果你使用typedef就沒有這樣的問題。
4. typedef & #define的另一例
下面的代碼中編譯器會報一個錯誤,你知道是哪個語句錯了嗎?
typedef char * pStr;
char string[4] = "abc";
const char *p1 = string;
const pStr p2 = string;
p1++;
p2++;
答案與分析:
是p2++出錯了。這個問題再一次提醒我們:typedef和#define不同,它不是簡單的文本替換。上述代碼中const pStr p2并不等于const char * p2。const pStr p2和const long x本質上沒有區別,都是對變量進行只讀限制,只不過此處變量p2的數據類型是我們自己定義的而不是系統固有類型而已。因此,const pStr p2的含義是:限定數據類型為char *的變量p2為只讀,因此p2++錯誤。
#define與typedef引申談
1) #define宏定義有一個特別的長處:可以使用 #ifdef ,#ifndef等來進行邏輯判斷,還可以使用#undef來取消定義。
2) typedef也有一個特別的長處:它符合范圍規則,使用typedef定義的變量類型其作用范圍限制在所定義的函數或者文件內(取決于此變量定義的位置),而宏定義則沒有這種特性。
5. typedef & 復雜的變量聲明
在編程實踐中,尤其是看別人代碼的時候,常常會遇到比較復雜的變量聲明,使用typedef作簡化自有其價值,比如:
下面是三個變量的聲明,我想使用typdef分別給它們定義一個別名,請問該如何做?
>1:int *(*a[5])(int, char*);
>2:void (*b[10]) (void (*)());
>3. double(*(*pa)[9])();
答案與分析:
對復雜變量建立一個類型別名的方法很簡單,你只要在傳統的變量聲明表達式里用類型名替代變量名,然后把關鍵字typedef加在該語句的開頭就行了。
>1:int *(*a[5])(int, char*);
//pFun是我們建的一個類型別名
typedef int *(*pFun)(int, char*);
//使用定義的新類型來聲明對象,等價于int* (*a[5])(int, char*);
pFun a[5];
>2:void (*b[10]) (void (*)());
//首先為上面表達式藍色部分聲明一個新類型
typedef void (*pFunParam)();
//整體聲明一個新類型
typedef void (*pFun)(pFunParam);
//使用定義的新類型來聲明對象,等價于void (*b[10]) (void (*)());
pFun b[10];
>3. double(*(*pa)[9])();
//首先為整體聲明一個新類型
typedef double(*pFun)();
//再為上面表達式藍色部分聲明一個新類型
typedef pFun (*pFunParam)[9];
//使用定義的新類型來聲明對象,等價于double(*(*pa)[9])();
pFunParam pa;
編輯本段
代碼簡化
上面討論的 typedef 行為有點像 #define 宏,用其實際類型替代同義字。不同點是 typedef 在編譯時被解釋,因此讓編譯器來應付超越預處理器能力的文本替換。例如:
typedef int (*PF) (const char *, const char *);
這個聲明引入了 PF 類型作為函數指針的同義字,該函數有兩個 const char * 類型的參數以及一個 int 類型的返回值。如果要使用下列形式的函數聲明,那么上述這個 typedef 是不可或缺的:
PF Register(PF pf);
Register() 的參數是一個 PF 類型的回調函數,返回某個函數的地址,其署名與先前注冊的名字相同。做一次深呼吸。下面我展示一下如果不用 typedef,我們是如何實現這個聲明的:
int (*Register (int (*pf)(const char *, const char *)))
(const char *, const char *);
很少有程序員理解它是什么意思,更不用說這種費解的代碼所帶來的出錯風險了。顯然,這里使用 typedef 不是一種特權,而是一種必需。持懷疑態度的人可能會問:"OK,有人還會寫這樣的代碼嗎?",快速瀏覽一下揭示 signal()函數的頭文件 ,一個有同樣接口的函數。
typedef 和存儲類關鍵字(storage class specifier)
這種說法是不是有點令人驚訝,typedef 就像 auto,extern,mutable,static,和 register 一樣,是一個存儲類關鍵字。這并不是說 typedef 會真正影響對象的存儲特性;它只是說在語句構成上,typedef 聲明看起來象 static,extern 等類型的變量聲明。下面將帶到第二個陷阱:
typedef register int FAST_COUNTER; // 錯誤
編譯通不過。問題出在你不能在聲明中有多個存儲類關鍵字。因為符號 typedef 已經占據了存儲類關鍵字的位置,在 typedef 聲明中不能用 register(或任何其它存儲類關鍵字)。
編輯本段
平臺開發
typedef 有另外一個重要的用途,那就是定義機器無關的類型,例如,你可以定義一個叫 REAL 的浮點類型,在目標機器上它可以獲得最高的精度:
typedef long double REAL;
在不支持 long double 的機器上,該 typedef 看起來會是下面這樣:
typedef double REAL;
并且,在連 double 都不支持的機器上,該 typedef 看起來會是這樣:、
typedef float REAL;
你不用對源代碼做任何修改,便可以在每一種平臺上編譯這個使用 REAL 類型的應用程序。唯一要改的是 typedef 本身。在大多數情況下,甚至這個微小的變動完全都可以通過奇妙的條件編譯來自動實現。不是嗎? 標準庫廣泛地使用 typedef 來創建這樣的平臺無關類型:size_t,ptrdiff 和 fpos_t 就是其中的例子。此外,象 std::string 和 std::ofstream 這樣的 typedef 還隱藏了長長的,難以理解的模板特化語法,例如:basic_string,allocator> 和 basic_ofstream>。
參考資料
1. Bjarne Stroustrup.C++程序設計語言.中國:機械工業出版社,2010:76-77.
2. const char*, char const*, char*const的區別 .網易博客[引用日期2013-01-20].
const char*, char const*, char*const的區別
2009-06-05 00:28:16| 分類: VC |字號 訂閱
const char*, char const*, char*const的區別問題幾乎是C++面試中每次都會有的題目。 這個知識易混點之前是看過了,今天做Linux上寫GTK程序時又出現個Warning,發散一下又想到這個問題,于是翻起來重嚼一下。
事實上這個概念誰都有只是三種聲明方式非常相似:
Bjarne在他的The C++ Programming Language里面給出過一個助記的方法:
把一個聲明從右向左讀。
char * const cp; ( * 讀成 pointer to ) cp is a const pointer to char
const char * p; p is a pointer to const char;
char const * p; 同上因為C++里面沒有const*的運算符,所以const只能屬于前面的類型。
C++標準規定,const關鍵字放在類型或變量名之前等價的。
const int n=5; //same as below
int const m=10
結論:
char * const cp : 定義一個指向字符的指針常數,即const指針
const char* p : 定義一個指向字符常數的指針
char const* p : 等同于const char* p
const char **是一個指向指針的指針,那個指針又指向一個字符串常量。
char **也是一個指向指針的指針,那個指針又指向一個字符串變量。
3人 | 分享到: 閱讀(5060)|
void指針 指針有兩個屬性:指向變量/對象的地址和長度
但是指針只存儲地址,長度則取決于指針的類型
編譯器根據指針的類型從指針指向的地址向后尋址
指針類型不同則尋址范圍也不同,比如:
int*從指定地址向后尋找4字節作為變量的存儲單元
double*從指定地址向后尋找8字節作為變量的存儲單元
1.void指針是一種特別的指針
void *vp
//說它特別是因為它沒有類型
//或者說這個類型不能判斷出指向對象的長度
2.任何指針都可以賦值給void指針
type *p;
vp=p;
//不需轉換
//只獲得變量/對象地址而不獲得大小
3.void指針賦值給其他類型的指針時都要進行轉換
type *p=(type*)vp;
//轉換類型也就是獲得指向變量/對象大小
轉:http://icoding.spaces.live.com/blog/cns!209684E38D520BA6!130.entry
4.void指針不能復引用
*vp//錯誤
因為void指針只知道,指向變量/對象的起始地址
而不知道指向變量/對象的大小(占幾個字節)所以無法正確引用
5.void指針不能參與指針運算,除非進行轉換
(type*)vp++;
//vp==vp+sizeof(type)
#include<iostream>
#include<stdlib.h>
#include<string>
using namespace std;
typedef struct tag_st
{
char id[10];
float fa[2];
}ST;
//我在程序里面這樣使用的
int main()
{
ST * P=(ST *)malloc(sizeof(ST));
strcpy(P->id,"hello!");
P->fa[0]=1.1;
P->fa[1]=2.1;
ST * Q=(ST *)malloc(sizeof(ST));
strcpy(Q->id,"world!");
Q->fa[0]=3.1;
Q->fa[1]=4.1;
void ** plink=(void **)P;
*((ST *)(plink)) = * Q; //plink要先強制轉換一下,目的是為了讓它先知道要覆蓋的大小.
//P的內容竟然給Q的內容覆蓋掉了.
cout<<P->id<<" "<<P->fa[0]<<" "<<P->fa[1]<<endl;
return 0;
}
posted on 2008-09-02 20:17 Dragon 閱讀(10271) 評論(7) 編輯 收藏 引用 所屬分類: C++
評論:
# re: void指針 Joey Posted @ 2009-07-12 08:17
寫得不錯,總結得也還好, 回復 更多評論
# re: void指針 路過的 Posted @ 2009-09-17 17:07
為什么是 void ** plink=(void **)P;
void ** plink= P; 不行嗎?不是任何類型都可以直接付給void指針嗎?
回復 更多評論
# re: void指針 學習的 Posted @ 2009-09-17 17:16
void ** plink=(void **)P;
為什么要二級指針?
void * plink=(void *)P; 不行嗎? 回復 更多評論
# re: void指針 學習的 Posted @ 2009-09-17 17:18
如果要用二級指針的話,應該是
void ** plink=&p ; 回復 更多評論
# re: void指針 路過 Posted @ 2009-10-21 11:15
為什么要用二級指針啊?void * plink=(void *)P; 也是行的啊! 回復 更多評論
# re: void指針 yulai_li Posted @ 2009-11-10 15:59
void * plink=P; 就已經可以了 回復 更多評論
# re: void指針 aa Posted @ 2010-03-02 14:26
@yulai_li
不需要二級指針的。 回復 更多評論
C++ void指針和NULL指針
博客分類: C/C++
CC++C#F#
我們將從指針的語法和使用并結合例子來討論他們的區別。
Void 指針:
Cpp代碼
void * pointer_variable;
void * pointer_variable; void這是是作為一個關鍵字來使用。
參考指針的定義和使用,我們知道所定義指針的數據類型同指針所指的數據類型是一致的。所分配給指針的地址也必須跟指針類型一樣。
例如:
Cpp代碼
int i;
float f;
int* exf;
float* test;
then
exf=&i;
int i;
float f;
int* exf;
float* test;
then
exf=&i;
int類型指針指向int變量的地址空間,所以是對的。
如果寫成:
Cpp代碼
exf=&f;
exf=&f; 這條語句就會產生錯誤。因為int類型的指針指向的是一塊float變量的地址空間。同樣,如果我們試圖把float類型的指針指向一塊int類型的地址空間,也是錯誤的,例如:
Cpp代碼
test=&i;
test=&i; 上面一條語句將會報錯。
void類型指針是可以用來指向任何數據類型的特殊指針。
使用前面的例子,如果我們手動聲明一個void類型指針:
Cpp代碼
void* sample;
void* sample; 在前面的例子中,如果我們定義的一個void類型指針去指向一個float變量的地址空間是完全正確的。
Cpp代碼
sample=&f;
sample=&f; 同樣,如果我們把這個void類型指針去指向一個int類型的地址空間也是正確的:
Cpp代碼
sample=&i;
sample=&i; void(類型)指針,是一種特殊的指針,它足夠靈巧的指向任何數據類型的地址空間。當然它也具有一定的局限:
在我們要取得指針所指地址空間的數據的時候使用的是 ‘*’操作符,程序員必須清楚了解到對于void指針不能使用這種方式來取得指針所指的內容。因為直接取內容是不允許的。而必須把void指針轉換成其他任何valid數據類型的指針,比如char,int,float等類型的指針,之后才能使用'*'取出指針的內容。這就是所謂的類型轉換的概念。
NULL pointer(空指針):
NULL指針的概念不同于前面所說的void指針。NULL指針是任何數據類型指針的一種,并且使用0作為初始值(譯者:這個好像要跟操作系統有關,有的系統把NULL 指針指向0地址,其他可能指向非零地址,對吧?有異議請留言)。當然這個不是強制性的。其表明NULL指針并未指向任何一塊合法的(valid)的地址空間。
舉例:
Cpp代碼
int* exforsys;
exforsys=0;
int* exforsys;
exforsys=0; 以上的聲明表示exforsys是一個int類型的指針,但其不指向任何有效的地址空間,這表明exforsys有一個空指針值(0)。
Void指針和NULL指針的區別:
Void指針是一個可以指向任何數據類型的特殊指針。NULL指針可是是任何數據類型的但其不指向任何有效的地址空間或者引用。區分空指針和指針未被初始化是很關鍵的,比如,假如程序員寫下:
Cpp代碼
#include <iostream.h>
int *exforsys;
void main()
{
*exforsys=100;
}
#include <iostream.h>
int *exforsys;
void main()
{
*exforsys=100;
} 上面程序代碼的輸出如下:
NULL POINTER ASSIGNMENT
上面的程序拋出運行時的錯誤。表明指針變量exforsys還沒有被分配任何有效的地址空間,并且試圖存取0地址空間就產生了錯誤信息。
Author: UNKNOWN
C/C++語言void及void指針深層探索2006-08-05 06:00 來源:blog 作者:蔣濤 責任編輯:方舟·yesky 評論(16)
1.概述
許多初學者對C/C++語言中的void及void指針類型不甚理解,因此在使用上出現了一些錯誤。本文將對void關鍵字的深刻含義進行解說,并詳述void及void指針類型的使用方法與技巧。
2.void的含義
void的字面意思是“無類型”,void *則為“無類型指針”,void *可以指向任何類型的數據。
void幾乎只有“注釋”和限制程序的作用,因為從來沒有人會定義一個void變量,讓我們試著來定義:
void a;
這行語句編譯時會出錯,提示“illegal use of type 'void'”。不過,即使void a的編譯不會出錯,它也沒有任何實際意義。
void真正發揮的作用在于:
(1)對函數返回的限定;
(2)對函數參數的限定。
我們將在第三節對以上二點進行具體說明。
眾所周知,如果指針p1和p2的類型相同,那么我們可以直接在p1和p2間互相賦值;如果p1和p2指向不同的數據類型,則必須使用強制類型轉換運算符把賦值運算符右邊的指針類型轉換為左邊指針的類型。
例如:
float *p1;
int *p2;
p1 = p2;
其中p1 = p2語句會編譯出錯,提示“'=' : cannot convert from 'int *' to 'float *'”,必須改為:
p1 = (float *)p2;
而void *則不同,任何類型的指針都可以直接賦值給它,無需進行強制類型轉換:
void *p1;
int *p2;
p1 = p2;
但這并不意味著,void *也可以無需強制類型轉換地賦給其它類型的指針。因為“無類型”可以包容“有類型”,而“有類型”則不能包容“無類型”。道理很簡單,我們可以說“男人和女人都是人”,但不能說“人是男人”或者“人是女人”。下面的語句編譯出錯:
void *p1;
int *p2;
p2 = p1;
提示“'=' : cannot convert from 'void *' to 'int *'”。
3.void的使用
下面給出void關鍵字的使用規則:
規則一如果函數沒有返回值,那么應聲明為void類型
在C語言中,凡不加返回值類型限定的函數,就會被編譯器作為返回整型值處理。但是許多程序員卻誤以為其為void類型。例如:
add ( int a, int b )
{
return a + b;
}
int main(int argc, char* argv[])
{
printf ( "2 + 3 = %d", add ( 2, 3) );
}
程序運行的結果為輸出:
2 + 3 = 5
這說明不加返回值說明的函數的確為int函數。
林銳博士《高質量C/C++編程》中提到:“C++語言有很嚴格的類型安全檢查,不允許上述情況(指函數不加類型聲明)發生”。可是編譯器并不一定這么認定,譬如在Visual C++6.0中上述add函數的編譯無錯也無警告且運行正確,所以不能寄希望于編譯器會做嚴格的類型檢查。
因此,為了避免混亂,我們在編寫C/C++程序時,對于任何函數都必須一個不漏地指定其類型。如果函數沒有返回值,一定要聲明為void類型。這既是程序良好可讀性的需要,也是編程規范性的要求。另外,加上void類型聲明后,也可以發揮代碼的“自注釋”作用。代碼的“自注釋”即代碼能自己注釋自己。
規則二如果函數無參數,那么應聲明其參數為void
在C++語言中聲明一個這樣的函數:
int function(void)
{
return 1;
}
則進行下面的調用是不合法的:
function(2);
因為在C++中,函數參數為void的意思是這個函數不接受任何參數。
我們在Turbo C 2.0中編譯:
#include "stdio.h"
fun()
{
return 1;
}
main()
{
printf("%d",fun(2));
getchar();
}
編譯正確且輸出1,這說明,在C語言中,可以給無參數的函數傳送任意類型的參數,但是在C++編譯器中編譯同樣的代碼則會出錯。在C++中,不能向無參數的函數傳送任何參數,出錯提示“'fun' : function does not take 1 parameters”。
所以,無論在C還是C++中,若函數不接受任何參數,一定要指明參數為void。
規則三小心使用void指針類型
按照ANSI(American National Standards Institute)標準,不能對void指針進行算法操作,即下列操作都是不合法的:
void * pvoid;
pvoid++; //ANSI:錯誤
pvoid += 1; //ANSI:錯誤
//ANSI標準之所以這樣認定,是因為它堅持:進行算法操作的指針必須是確定知道其指向數據類型大小的。
//例如:
int *pint;
pint++; //ANSI:正確
pint++的結果是使其增大sizeof(int)。
但是大名鼎鼎的GNU(GNU's Not Unix的縮寫)則不這么認定,它指定void *的算法操作與char *一致。
因此下列語句在GNU編譯器中皆正確:
pvoid++; //GNU:正確
pvoid += 1; //GNU:正確
pvoid++的執行結果是其增大了1。
在實際的程序設計中,為迎合ANSI標準,并提高程序的可移植性,我們可以這樣編寫實現同樣功能的代碼:
void * pvoid;
(char *)pvoid++; //ANSI:正確;GNU:正確
(char *)pvoid += 1; //ANSI:錯誤;GNU:正確
GNU和ANSI還有一些區別,總體而言,GNU較ANSI更“開放”,提供了對更多語法的支持。但是我們在真實設計時,還是應該盡可能地迎合ANSI標準。
規則四如果函數的參數可以是任意類型指針,那么應聲明其參數為void *
典型的如內存操作函數memcpy和memset的函數原型分別為:
void * memcpy(void *dest, const void *src, size_t len);
void * memset ( void * buffer, int c, size_t num );
這樣,任何類型的指針都可以傳入memcpy和memset中,這也真實地體現了內存操作函數的意義,因為它操作的對象僅僅是一片內存,而不論這片內存是什么類型。如果memcpy和memset的參數類型不是void *,而是char *,那才叫真的奇怪了!這樣的memcpy和memset明顯不是一個“純粹的,脫離低級趣味的”函數!
下面的代碼執行正確:
//示例:memset接受任意類型指針
int intarray[100];
memset ( intarray, 0, 100*sizeof(int) ); //將intarray清0
//示例:memcpy接受任意類型指針
int intarray1[100], intarray2[100];
memcpy ( intarray1, intarray2, 100*sizeof(int) ); //將intarray2拷貝給intarray1
有趣的是,memcpy和memset函數返回的也是void *類型,標準庫函數的編寫者是多么地富有學問啊!
規則五 void不能代表一個真實的變量
下面代碼都企圖讓void代表一個真實的變量,因此都是錯誤的代碼:
void a; //錯誤
function(void a); //錯誤
void體現了一種抽象,這個世界上的變量都是“有類型”的,譬如一個人不是男人就是女人(還有人妖?)。
void的出現只是為了一種抽象的需要,如果你正確地理解了面向對象中“抽象基類”的概念,也很容易理解void數據類型。正如不能給抽象基類定義一個實例,我們也不能定義一個void(讓我們類比的稱void為“抽象數據類型”)變量。
4.總結
小小的void蘊藏著很豐富的設計哲學,作為一名程序設計人員,對問題進行深一個層次的思考必然使我們受益匪淺
void類型以及void指針的使用方法更新時間:2011-04-27 13:18:57 來源:愛程序網整理 點擊:5253-
-
一、void的含義
void的字面意思是“無類型”,void *則為“無類型指針”,void *可以指向任何類型的數據。
void幾乎只有“注釋”和限制程序的作用,因為從來沒有人會定義一個void變量,讓我們試著來定義:
void a;
這行語句編譯時會出錯,提示“illegal use of type 'void'”。不過,即使void a的編譯不會出錯,它也沒有任何實際意義。
void真正發揮的作用在于:
(1) 對函數返回的限定;
(2) 對函數參數的限定。
我們將在第三節對以上二點進行具體說明。
眾所周知,如果指針p1和p2的類型相同,那么我們可以直接在p1和p2間互相賦值;如果p1和p2指向不同的數據類型,則必須使用強制類型
轉換運算符把賦值運算符右邊的指針類型轉換為左邊指針的類型。
例如:
float *p1;
int *p2;
p1 = p2;
其中p1 = p2語句會編譯出錯,提示“'=' : cannot convert from 'int *' to 'float *'”,必須改為:
p1 = (float *)p2;
而void *則不同,任何類型的指針都可以直接賦值給它,無需進行強制類型轉換:
void *p1;
int *p2;
p1 = p2;
但這并不意味著,void *也可以無需強制類型轉換地賦給其它類型的指針。因為“無類型”可以包容“有類型”,而“有類型”則不能包
容“無類型”。道理很簡單,我們可以說“男人和女人都是人”,但不能說“人是男人”或者“人是女人”。下面的語句編譯出錯:
void *p1;
int *p2;
p2 = p1;
提示“'=' : cannot convert from 'void *' to 'int *'”。
二、void指針使用實例
int download_addr;
void abc(void)
{
download_addr = 0x0c400000;
void (*fun)(void);
fun = (void (*)(void))download_addr;
(*fun)();
}
注釋:
void (*fun)(void); 定義一個函數指針func 輸入參數為void返回類型為void
fun = (void (*)(void))download_addr;
這句話是將download_addr這個函數指針強制轉換為參數為void返回類型為void的函數指針,然后賦值給func
最后一句就是要執行這個函數
download_addr 是一個函數指針
來源:豆芽博客,地址:http://www.aichengxu.com/article/c%E8%AF%AD%E8%A8%80/149_11.html保留原文鏈接,是開源分享的開始.
C 語言中的泛型
2012 年 11 月 15 日 孫鶴 C, 2
很多高級語言有很強的泛型編程機制,例如: C++ 的模板;python 中的函數并不需要定義參數類型;這些都是泛型編程的機制。
但是 C 程序員依然渴望自己寫的一些東西可以得到復用,例如實現了一個鏈表,總是希望它可以應用在很多場景下。
那么 C 語言如何實現泛型編程呢?在Linux 下 C 語言大概提供了兩種機制實現泛型編程。
void * 指針
C 語言是操作內存最容易的語言,而 void * 指針代表的是指向任意的內容,所以泛型用它再適合不過了。但是 void * 指向的內容在使用的時候,必須顯示的轉換成你想要的類型,或者知道它的大小。
typeof 方式
這種方式并不是所有的編譯器都支持,但是在 Linux 下的各種編譯器完全沒問題。通常這種方式都需要跟宏配合使用,但是可以達到你意想不到的效果。
--------------------------------------------------------------------------------
下面來看個實際的簡單例子吧,例如我希望實現一個兩個數互換的方法。分別用 void * 和 typof 方式實現:
1234567891011121314151617181920212223242526272829303132333435363738 #include <stdio.h> #include <stdlib.h> #include <string.h> #define typeof_swap(v1, v2) \ do { \ typeof(v1) tmp; \ tmp = v1; \ v1 = v2; \ v2 = tmp; \ } \ while(0) voidvoidptr_swap(void *p1, void *p2, size_t size) { void *tmp; tmp = malloc(size); memcpy(tmp, p1, size); memcpy(p1, p2, size); memcpy(p2, tmp, size); free(tmp); } intmain(int argc, char *argv[]) { int a = 2; int b = 3; voidptr_swap(&a, &b, sizeof(int)); printf("voidptr_swap:\na: %d\nb: %d\n", a, b); typeof_swap(a, b); printf("typeof_swap:\na: %d\nb: %d\n", a, b); return 0; }
線性結構的思考 vim 中 taglist 插件的使用
2 thoughts on “C 語言中的泛型”
coder_L 說道:
2013 年 5 月 14 日 21:21
typeof 是C99的關鍵字? 我用gcc -std=c89 main.c和gcc -std=c99 main.c 都出現tmp未聲明的錯誤信息。但是默認gcc main.c就沒錯誤而且運行正確, 這是什么原因? 我印象中似乎c99沒增加這個關鍵字啊。額, 還有memcpy移動內存之前, 用不用檢查內存重疊?
回復 孫鶴 說道:
2013 年 5 月 24 日 09:05
typeof 屬于 gcc 都支持的,但是 windows 編譯器不支持,具體我不清楚是不是 C99 才支持的。memcpy 我記得是不檢查的,具體可以查下 glibc 代碼。