JUST DO IT ~

我只想当个程序员

[杂记]非mfc项目 handle 替换 void *

 

一般来说,如果你不是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 代码。

posted on 2013-09-10 14:14 小高 阅读(524) 评论(0)  编辑  收藏 所属分类: C


只有注册用户登录后才能发表评论。


网站导航:
 

导航

<2013年9月>
25262728293031
1234567
891011121314
15161718192021
22232425262728
293012345

统计

常用链接

留言簿(3)

随笔分类(352)

收藏夹(19)

关注的blog

手册

搜索

积分与排名

最新评论

阅读排行榜

评论排行榜