随笔 - 78  文章 - 1  trackbacks - 0
<2025年4月>
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

常用链接

留言簿(1)

随笔分类

随笔档案

搜索

  •  

最新评论

阅读排行榜

评论排行榜

     摘要:   阅读全文
posted @ 2011-05-22 20:34 Carrie 阅读(336) | 评论 (0)编辑 收藏

原创  Struct 结构体疑惑(c语言) 收藏

 

Struct 结构体疑惑(c语言)

 

 

(如有错,请务必指正)

 

本文主要介绍在struct 结构体中对数组和指针的操作细节。

 

我们需要明确两个概念:

a、  初始化:分配内存并写入值。

b、  赋值:  查找内存并写入值。

 

   在上一篇文章有提到数组和指针的初始化和赋值问题,对于数组,可以初始化,但不能对整个数组赋值,而指针,可以初始化也可以赋值。

 

   在结构体中,数组和指针最大的的区别为:数组将信息存在结构体所占有的内存中,而指针是将信息的首地址保存在结构体所占有的内存中,例如:

   typedef  struct name{

      char a[20];

      char *p;

}Name;

Name A={“Llilonglin”,Llilonglin”}

结构体对象A中的数组会将字符串常量“LilonglinCOPYA的存储内存中,而A中的指针只保存字符串常量在静态常量存储区中的首地址。

 

在结构体中对数组的初始化和赋值有以下需要注意:

一、赋值

例如 定义结构体对象

     Name A;

在使用时,编译器将会为该结构体分配内存空间,但为对结构对象A的变量初始化。 此时只能通过赋值操作来写入值。在对数组a[10]的赋值时应谨慎,例如:

A.              A.a[10]=”Lilonglin”, gcc将给出警告 :“赋值时将指针赋给整数,未作类型转换”,该警告主要由两个原因引起:1 .”结构体成员运算符的作用是访问结构体中个成员,该处操作的是结构体变量A中数组的第10个元素,虽然该下标已经越界;2、“Lilonglin”在此处编译器理解为一个常指针,指针是不能直接赋值给字符变量的。

B.               A.a=Lilonglin,gcc将会提示错误:赋值时,类型不兼容。 在定义申明完数组后数组名具有常量特性,表示数组存储空间的开始地址,而C语言中不能对常量赋值。

C.              根据数组的特性,如果需要得到对数组的正确赋值,则需要对数组的每个元素进行对应赋值。在此不再举例。

二、初始化

在结构体初始化时,用字符串对数组的初始化也需要有注意的地方,例如:

A、        Name A={.a=”Lilonglin”}  //OK

B、         1Name A={.a[10]=”Lilonglin”}  //error  gcc提示:初始化数组索引越界。

2Name A={.a[9]=”Lilonglin”}  //right  printf(“%s”,A.a) 得到“Lilonglin

3Name A={.a[1]=”Lilonglin”}  // right  printf(“%s”,A.a) 得到“Lilonglin

            为什么在1中,指定初始化用a10】时,gcc会提示越界呢。 在编译的过程中,在语法扫错阶段GCC的对数组边界检查时,发现了该错误,从而给出了提示,在VC下则不会,在此不得不佩服GCC的严谨。

23我们可以看到当使用不同的下标时对数组的初始化完全没有影响,

这不得不怀疑在结构体中,用字符串对数组的初始化时只与数组名有关,因为数组名具有指针属性,之后数组的各个元素被初始化,读者可以打印某个数组元素试试。

      此处只讨论了用字符串初始化的情况,对非字符串时指定初始化时,下标将起作用,大家可以试一试。

posted @ 2011-05-22 20:29 Carrie 阅读(1184) | 评论 (0)编辑 收藏

列举C++中的四种cast的转换分别是什么,有何作用,并举例说明,我虽然早知道C++有四种cast转换,但平常使用非常少也就没注意,打算对C++的这些类型转换方法进行小结。

下文中的“常规类型”指的是int、double、float、bool……这些非结构化类型,也就是不包括struct和class类型。“旧式转换”指的是C语言风格的“(NewType)(Val)”方式的转换。

1、指针=>常规类型

比如我们需要打印一个指针的值(它指向的地址)的时候,我们指针直接转换为整数,用printf输出个16进制的地址。我们可以用旧式转换,或者reinterpret_cast,其它转换都是不可以的。

    CIndepend oIndepend;
    CIndepend * pIndepend =   & oIndepend;
    unsigned char cTest = (unsigned char )pIndepend;
    unsigned short sTest = (unsigned short )pIndepend;
    unsigned int iTest = (unsigned int )pIndepend; 
在32位系统中,指针其实是个32位的无符号整型,要正常输出指针的值,正确做法是把它转换为一个无符号32位整形数输出(有符号的可能导致输出不正确),如果转换为一个16位的,或者8位的,那将丢失数据,当然了,前面这段代码不会出现任何 error和warning,你知道你在干什么即可。刚说了,我们除了旧式转换还可以用reinterpret_cast,于是上面的代码可以这样写:

    unsigned char cTest = reinterpret_cast < unsigned char   > (pIndepend);
    unsigned short sTest = reinterpret_cast < unsigned short > (pIndepend);
    unsigned int iTest = reinterpret_cast < unsigned int > (pIndepend); 
也是没有任何问题的,运行效果一样,那我们能不能把指针转换为浮点型呢?这样:

     float fTest = reinterpret_cast < float > (pIndepend);
     double dTest = reinterpret_cast < double > (pIndepend); 
不行,你试试看就知道了,你会得到这样的编译错误:

error C2440: 'reinterpret_cast' : cannot convert from 'class CIndepend *' to 'float'
        There is no context in which this conversion is possible
error C2440: 'reinterpret_cast' : cannot convert from 'class CIndepend *' to 'double'
        There is no context in which this conversion is possible

其实将指针转换为浮点数这种做法就很怪异嘛,你不觉得吗?这样有什么用啊?不过你一定要这样干的话也不是没有办法,看代码:

     float fTest = reinterpret_cast < float   &> (pIndepend);
     double dTest = reinterpret_cast < double   &> (pIndepend); 
加个小小的“&”符号就可以了,C++会不顾一切地把pIndepend这个变量当作一个float和double,把指针的值“理解”为float和double,当然,这样获得的浮点数的值是没什么实际意义的,因为指针的值是32位无符号整型,而浮点数有它自己特定的格式,这么一转换就牛头不对马嘴。你还可以这样写:

       float fTest = ( float   & )pIndepend;
       double dTest = ( double   & )pIndepend; 
效果一样,得到的也是无意义的值。

2、常规类型=>指针

就是反过来罗,可能不说大家都能猜到结果了。把常规类型转换为指针有什么用呢?可能有点用,比如我们在某些特殊的场合要求一个指针指向一个特别的地址,这时候我们可以直接给指针赋个值,当然了,这个值应该是整型:

    CIndepend * pIndepend;

     char cVal =   1 ;
     short sVal =   2 ;
     int iVal =   3 ;

    pIndepend = (CIndepend * )cVal;
    pIndepend = (CIndepend * )sVal;
    pIndepend = (CIndepend * )iVal; 
这样是没问题的,那浮点数呢?——又来了,浮点数转换为指针?这怎么算啊?显然这是不行的,如果真的需要“转换”,那就先把浮点数转换为整型,然后再赋值给指针吧。这个时候,dynamic_cast和static_cast都是不行的。

3、基本类型转换

比如int转换为float,char转变为short,很多时候我们都“默认”了这种转换,即使没有显式指定用旧式转换还是static_cast。在这种类型的转换中,旧式转换和static_cast的表现非常地类似:

     double dVal =   5.0 ;

     char cVal = static_cast < char > (dVal);
     short sVal = static_cast < short > (dVal);
     int iVal = static_cast < int > (dVal);

    cVal = ( char )(dVal);
    sVal = ( short )(dVal);
    iVal = ( int )(dVal); 
而dynamic_cast还是不可行,那……reinterpret_cast呢?不妨试试看:

     double dVal =   5.0 ;

     char cVal = reinterpret_cast < char > (dVal);
     short sVal = reinterpret_cast < short > (dVal);
     int iVal = reinterpret_cast < int > (dVal); 
一编译,就出下面的错误:

error C2440: 'reinterpret_cast' : cannot convert from 'double' to 'char'
        Conversion is a valid standard conversion, which can be performed implicitly or by use of static_cast, C-style cast or function-style cast
error C2440: 'reinterpret_cast' : cannot convert from 'double' to 'short'
        Conversion is a valid standard conversion, which can be performed implicitly or by use of static_cast, C-style cast or function-style cast
error C2440: 'reinterpret_cast' : cannot convert from 'double' to 'int'
        Conversion is a valid standard conversion, which can be performed implicitly or by use of static_cast, C-style cast or function-style cast

说这是个有效标准转换,请使用static_cast或者C风格的旧式转换。reinterpret_cast不是号称最宽松的转换么?怎么不行了?你一定要它行也是没问题的,和前面的那样,加个“&”符号:

     char cVal = reinterpret_cast < char   &> (dVal);
     short sVal = reinterpret_cast < short   &> (dVal);
     int iVal = reinterpret_cast < int   &> (dVal); 
但结果并不是你想要的结果,因为这样reinterpret_cast会不管三七二十一,直接把dVal的东西当作是一个char,short和int,很明显,double是有一定的格式的,将double直接“理解”为char,short或者int一定会有问题。

4、class的转换

上一节说的是基本类型,那对于类呢?一个类直接转换为另一个类,这看起来确实有些荒谬,不过强大而灵活的C++却偏偏允许了这种行为。看代码:

class CBase
{
public :
    CBase(){};
     int m_iBase;
};

class CIndepend
{
public :
    CIndepend(){};
     int m_iIndepend;
};

int main( int argc, char * argv[])
{
    CBase oBase;
    CIndepend oIndepend = reinterpret_cast < CIndepend &> (oBase);
     return   0 ;
} 
居然编译过去了,运行貌似也没什么问题,当然转换过程和前面的差不多,就是把oBase理解为一个CIndepend对象,这个赋值运算执行“位拷贝”,这种方式的转换在实际中是碰不到的,起码我想不出有什么理由使用它。这种情况下,其它的转换方式都是不可行的。

5、class=>指针 or 指针=>class

这种行为更怪异,class直接理解为指针?这其实是不可行的,跟前面提到的浮点数转换为指针一样,如果实在需要,就把class转变为整型,然后整型转换为指针:

CIndepend * pIndepend = reinterpret_cast < CIndepend *> (reinterpret_cast < unsigned int   &> (oBase)); 
可这……这啥意思呢?哈哈,别问我。反过来,指针转换为class恐怕也令人费解:

    CDerived oDerived;
    CDerived * pDerived =   & oDerived;
    CIndepend oIndepend = reinterpret_cast < CIndepend &> (pDerived); 
能担当起这种怪异的工作的,唯reinterpret_cast是也,这样会产生什么后果呢?指针是个32位无符号整型,将它强制理解为一个CIndepend,然后作位拷贝,理所当然,oIndepend的值会被改变,而且还有访问越界的风险,导致内容混乱甚至程序崩溃。

6、指针之间的转换

前面一直没提起的一种转换就是dynamic_cast,因为它是最为严格的一种转换,它只能完成指针到指针的转换,而且还有限制。看这个:

    CDerived oDerived;
    CDerived * pDerived =   & oDerived;
     CIndepend * pIndepend = dynamic_cast < CIndepend *> (pDerived); 
编译出错了:

error C2683: dynamic_cast : 'CDerived' is not a polymorphic type
        D:\work\CastTest\CastTest.cpp(13) : see declaration of 'CDerived'

因为CDerived和CIndepend没有继承关系,把dynamic_cast换成static_cast还是不行的,会出另外一个错:

error C2440: 'static_cast' : cannot convert from 'class CDerived *' to 'class CIndepend *'
        Types pointed to are unrelated; conversion requires reinterpret_cast, C-style cast or function-style cast

编译器说这是没有关系的两个指针,应该用reinterpret_cast或者C风格的旧式转换,再看:

     // CDerived是CBase的子类 
    CBase oBase;
    CBase * pBase =   & oBase;
    CDerived * pDerived = dynamic_cast < CDerived *> (pBase); 
基类指针转换为子类指针,行不行呢?出错,错误跟刚才的一样,记住,dynamic_cast 仅仅可以把子类指针转换为基类指针,别的都不行!上面这段代码如果不用dynamic_cast,而是用static_cast,就能编译通过,static_cast的要求来得比较宽松。

OK,到这里为止,大家都知道什么时候用什么转换是可以的了,问题是C++为什么搞出怎么多转换出来呢?我想很大程度上是兼顾了安全性和灵活性,要想安全,class指针的转换就使用dynamic_cast;一般情况下我们认为,static_cast也是安全的;C风格的旧式转换则灵活一些,它允许任意类型指针之间的转换;而reinterpret_cast就更加了,什么乱七八糟都可以。那从功能强弱上排个序,我想从强到弱应该是:reinterpret_cast,旧式转换,static_cast,dynamic_cast。

Oh,还有一种转换,差点忘了,就是const_cast,不过这种转换比较特别,可以独立开来,它的功能就是去除一个变量的const属性,也就是说,允许修改常量的值,哈哈,修改常量的值?既然要修改常量的值,那为什么还要声明它为常量?——这也是C++灵活的一个体现。不过const_cast其实有个问题,有时候它并不能真正改变一个常量的值

posted @ 2011-05-22 14:28 Carrie 阅读(1624) | 评论 (0)编辑 收藏
学习VC++时经常会遇到链接错误LNK2001,该错误非常讨厌,因为对于   
  编程者来说,最好改的错误莫过于编译错误,而一般说来发生连接错误时,   
  编译都已通过。产生连接错误的原因非常多,尤其LNK2001错误,常常使人不   
  明其所以然。如果不深入地学习和理解VC++,要想改正连接错误LNK2001非   
  常困难。   
    初学者在学习VC++的学习过程中,遇到的LNK2001错误的错误消息主要为:   
    unresolved   external   symbol   “symbol”(不确定的外部“符号”)。   
    如果连接程序不能在所有的库和目标文件内找到所引用的函数、变量或   
  标签,将产生此错误的消息,一般来说,发生错误的原因有两个:一是所引用   
  的函数、变量不存在、拼写不正确或者使用错误;其次可能使用了不同版本   
  的连接库。   
    以下是可能产生LNK2001错误的原因:   
    一.由于编码错误导致的LNK2001。   
    1.不相匹配的
程序代码或模块定义(.DEF)文件能导致LNK2001。例如,     
  如果在C++   源文件内声明了一变量“var1”,却试图在另一文件内以变量   
  “VAR1”访问该变量,将发生该错误。   
    2.如果使用的内联函数是在.CPP文件内定义的,而不是在头文件内定   
  义将导致LNK2001错误。   
    3.调用函数时如果所用的参数类型同函数声明时的类型不符将会产生   
  LNK2001。   
    4.试图从基类的构造函数或析构函数中调用虚拟函数时将会导致LNK2001。   
    5.要注意函数和变量的可公用性,只有全局变量、函数是可公用的。   
    静态函数和静态变量具有相同的使用范围限制。当试图从文件外部访问   
  任何没有在该文件内声明的静态变量时将导致编译错误或LNK2001。   
    函数内声明的变量(局部变量)   只能在该函数的范围内使用。   
    C++   的全局常量只有静态连接性能。这不同于C,如果试图在C++的   
  多个文件内使用全局变量也会产生LNK2001错误。一种解决的方法是需要时在   
  头文件中加入该常量的初始化代码,并在.CPP文件中包含该头文件;另一种   
  方法是使用时给该变量赋以常数。   
    二.由于编译和链接的设置而造成的LNK2001   
    1.如果编译时使用的是/NOD(/NODEFAULTLIB)选项,程序所需要的运行   
  库和MFC库在连接时由编译器写入
目标文件模块,   但除非在文件中明确包含   
  这些库名,否则这些库不会被链接进工程文件。在这种情况下使用/NOD将导   
  致错误LNK2001。   
    2.如果没有为wWinMainCRTStartup设定程序入口,在使用Unicode和MFC   
  时将得到“unresolved   external   on   _WinMain@16”的LNK2001错误信息。   
    3.使用/MD选项编译时,既然所有的运行库都被保留在动态链接库之内,   
  源文件中对“func”的引用,在目标文件里即对“__imp__func”   的引用。   
  如果试图使用静态库LIBC.LIB或LIBCMT.LIB进行连接,将在__imp__func上发   
  生LNK2001;如果不使用/MD选项编译,在使用MSVCxx.LIB连接时也会发生LNK2001。   
    4.使用/ML选项编译时,如用LIBCMT.LIB链接会在_errno上发生LNK2001。   
    5.当编译调试版的应用程序时,如果采用发行版模态库进行连接也会产   
  生LNK2001;同样,使用调试版模态库连接发行版应用程序时也会产生相同的   
  问题。   
    6.不同版本的库和编译器的混合使用也能产生问题,因为新版的库里可   
  能包含早先的版本没有的符号和
说明。   
    7.在不同的模块使用内联和非内联的编译选项能够导致LNK2001。如果   
  创建C++库时打开了函数内联(/Ob1或/Ob2),但是在描述该函数的相应头   
  文件里却关闭了函数内联(没有inline关键字),这时将得到该错误信息。   
  为避免该问题的发生,应该在相应的头文件中用inline关键字标志内联函数。   
    8.不正确的/SUBSYSTEM或/ENTRY设置也能导致LNK2001。   
    其实,产生LNK2001的原因还有很多,以上的原因只是一部分而已,对初   
  学者来说这些就够理解一阵子了。但是,
分析错误原因的目的是为了避免错   
  误的发生。LNK2001错误虽然比较困难,但是只要注意到了上述问题,还是能   
  够避免和予以解决的。  
   
posted @ 2011-05-22 14:06 Carrie 阅读(429) | 评论 (0)编辑 收藏
 

我的程序是如下形状:一个头文件.h,一个主函数.cpp,一个操作函数.cpp。操作函数包含头文件,然后主函数包含操作函数.cpp。运行时出现错误:

错误症状:

Linking...
xz.obj : error LNK2005: "public: __thiscall XZ::XZ(void)" (
??0XZ@@QAE@XZ) already defined in xzmain.obj
xz.obj : error LNK2005: "public: void __thiscall XZ::pdc(void)" (
?pdc@XZ@@QAEXXZ) already defined in xzmain.obj
xz.obj : error LNK2005: "public: void __thiscall XZ::pcd(void)" (
?pcd@XZ@@QAEXXZ) already defined in xzmain.obj
xz.obj : error LNK2005: "public: void __thiscall XZ::pzf(void)" (
?pzf@XZ@@QAEXXZ) already defined in xzmain.obj
xz.obj : error LNK2005: "public: __thiscall XZ::~XZ(void)" (
??1XZ@@QAE@XZ) already defined in xzmain.obj
Debug/xzmain.exe : fatal error LNK1169: one or more multiply defined symbols found
执行 link.exe 时出错.

解决方案一:

把主函数所包含的操作函数.cpp,改为头文件.h,然后就可以解决如上错误。

解决方案二:

重新打开编译器,重新打开刚才的文件,先打开主函数.cpp,然后编译运行,打开Fileview,其中有一个external dependencies,把头文件.h,和操作函数.cpp添加到这里,这样再编译运行主函数.cpp,程序运行成功!

posted @ 2011-05-22 13:57 Carrie 阅读(715) | 评论 (0)编辑 收藏
造成LNK2005错误主要有以下几种情况:
1.重复定义全局变量。可能存在两种情况:
A、对于一些初学编程的程序员,有时候会以为需要使用全局变量的地方就可以使用定义申明一下。其实这是错误的,全局变量是针对整个工程的。正确的应该是在一个CPP文件中定义如下:int g_Test;那么在使用的CPP文件中就应该使用:extern int g_Test即可,如果还是使用int g_Test,那么就会产生LNK2005错误,一般错误错误信息类似:AAA.obj error LNK2005 int book c?
book@@3HA already defined in BBB.obj。切记的就是不能给变量赋值否则还是会有LNK2005错误。
             这里需要的是“声明”,不是“定义”!根据C++标准的规定,一个变量是声明,必须同时满足两个条件,否则就是定义:
(1)声明必须使用extern关键字;(2)不能给变量赋初值
所以,下面的是声明:
extern int a;
下面的是定义
int a; int a = 0; extern int a =0;
B、对于那么编程不是那么严谨的程序员,总是在需要使用变量的文件中随意定义一个全局变量,并且对于变量名也不予考虑,这也往往容易造成变量名重复,而造成LNK2005错误。
2.头文件的包含重复。往往需要包含的头文件中含有变量、函数、类的定义,在其它使用的地方又不得不多次包含之,如果头文件中没有相关的宏等防止重复链接的措施,那么就会产生LNK2005错误。解决办法是在需要包含的头文件中做类似的处理:#ifndef MY_H_FILE      //如果没有定义这个宏
#define MY_H_FILE      //定义这个宏
…….      //头文件主体内容
…….
#endif
上面是使用宏来做的,也可以使用预编译来做,在头文件中加入:
#pragma once
//头文件主体
3.使用第三方的库造成的。这种情况主要是C运行期函数库和MFC的库冲突造成的。具体的办法就是将那个提示出错的库放到另外一个库的前面。另外选择不同的C函数库,可能会引起这个错误。微软和C有两种C运行期函数库,一种是普通的函数库:LIBC.LIB,不支持多线程。另外一种是支持多线程的:msvcrt.lib。如果一个工程里,这两种函数库混合使用,可能会引起这个错误,一般情况下它需要MFC的库先于C运行期函数库被链接,因此建议使用支持多线程的msvcrt.lib。所以在使用第三方的库之前首先要知道它链接的是什么库,否则就可能造成LNK2005错误。如果不得不使用第三方的库,可以尝试按下面所说的方法修改,但不能保证一定能解决问题,前两种方法是微软提供的:
A、选择VC菜单Project->Settings->Link->Catagory选择Input,再在Ignore libraries 的Edit栏中填入你需要忽略的库,如:Nafxcwd.lib;Libcmtd.lib。然后在Object/library Modules的Edit栏中填入正确的库的顺序,这里需要你能确定什么是正确的顺序,呵呵,God bless you!
B、选择VC菜单Project->Settings->Link页,然后在Project Options的Edit栏中输入/verbose:lib,这样就可以在编译链接程序过程中在输出窗口看到链接的顺序了。
C、选择VC菜单Project->Settings->C/C++页,Catagory选择Code Generation后再在User Runtime libraray中选择MultiThread DLL等其他库,逐一尝试。
关于编译器的相关处理过程,参考:
http://www.donews.net/xzwenlan/archive/2004/12/23/211668.aspx
这就是我所遇到过的LNK2005错误的几种情况,肯定还有其他的情况也可能造成这种错误,所以我不希望你在看完这篇文章以后,再遇到LNK2005错误时候,不动脑筋的想对号入座的排除错误。编程的过程就是一个思考的过程,所以还是多多开动你的头脑,那样收获会更多!

附录:

编译器处理相关
一.预处理器-编译器-汇编器-链接器
预处理器会处理相关的预处理指令,一般是以"#"开头的指令。如:#include "xx.h" #define等。
编译器把对应的*.cpp翻译成*.s文件(汇编语言)。
汇编器则处理*.s生成对应的*.o文件(obj目标文件)
最后链接器把所有的*.o文件链接成一个可执行文件(?.exe)

1.部件:
首先要知道部件(可以暂且狭义地理解为一个类)一般分为头文件(我喜欢称为接口,如:*.h)及实现文件(如:*.cpp)。
一般头文件会是放一些用来作声明的东东作为接口而存在的。
而实现文件主要是实现的具体代码。

2.编译单个文件:
记住IDE在bulid文件时只编译实现文件(如*.cpp)来产生obj,在vc下你可以对某个?.cpp按下ctrl+f7单独编译它
生成对应一个?.obj文件。在编译?.cpp时IDE会在?.cpp中按顺序处理用#include包括进来的头文件
(如果该头文件中又#include有文件,同样会按顺序跟进处理各个头文件,如此递归。。)

3.内部链接与外部链接:
内、外链接是比较基础的东东,但是也是新手最容易错的地方,所以这里有必要祥细讨论一下。
内部链接产生的符号只在本地?.obj中可见,而外部链接的符号是所有*.obj之间可见的。
如:用inline的是内部链接,在文件头中直接声明的变量、不带inline的全局函数都是外部链接。
在文件头中类的内部声明的函数(不带函数体)是外部链接,而带函数体一般会是内部链接(因为IDE会尽量把它作为内联函数)
认识内部链接与外部链接有什么作用呢?下面用vc6举个例子:
// 文件main.cpp内容:
void main(){}
// 文件t1.cpp内容:
#include "a.h"
void Test1(){ Foo(); }
// 文件t2.cpp内容:
#include "a.h"
void Test2(){ Foo(); }
// 文件a.h内容:
void Foo( ){ }
好,用vc生成一个空的console程序(File - new - projects - win32 console application),并关掉预编译选项开关
(project - setting - Cagegoryrecompiled Headers - Not using precompiled headers)
现在你打开t1.cpp按ctrl+f7编译生成t1.obj通过
打开t2.cpp按ctrl+f7编译生成t2.obj通过
而当你链接时会发现:
Linking...
t2.obj : error LNK2005: "void __cdecl Foo(void)" (
?Foo@@YAXXZ) already defined in t1.obj
这是因为:
1. 编译t1.cpp在处理到#include "a.h"中的Foo时看到的Foo函数原型定义是外部链接的,所以在t1.obj中记录Foo符号是外部的。
2. 编译t2.cpp在处理到#include "a.h"中的Foo时看到的Foo函数原型定义是外部链接的,所以在t2.obj中记录Foo符号是外部的。
3. 最后在链接 t1.obj 及 t2.obj 时, vc发现有两处地方(t1.obj和t2.obj中)定义了相同的外部符号(注意:是定义,外部符号可以
多处声明但不可多处定义,因为外部符号是全局可见的,假设这时有t3.cpp声明用到了这个符号就不知道应该调用t1.obj
中的还是t2.obj中的了),所以会报错。
解决的办法有几种:
a.将a.h中的定义改写为声明,而用另一个文件a.cpp来存放函数体。(提示:把上述程序改来试试)
(函数体放在其它任何一个cpp中如t1.cpp也可以,不过良好的习惯是用对应cpp文件来存放)。
这时包括a.h的文件除了a.obj中有函数体代码外,
其它包括a.h的cpp生成的obj文件都只有对应的符号而没有函数体,如t1.obj、t2.obj就只有符号,当最后链接时IDE会把
a.obj的Foo()函数体链接进exe文件中,并把t1.obj、t2.obj中的Foo符号转换成对应在函数体exe文件中的地址。
另外:当变量放在a.h中会变成全局变量的定义,如何让它变为声明呢?
例如: 我们在a.h中加入:class CFoo{};CFoo* obj;
这时按f7进行build时出现:
Linking...
t2.obj : error LNK2005: "class CFoo * obj" (
?obj@@3PAVCFoo@@A) already defined in t1.obj
一个好办法就是在a.cpp中定义此变量( CFoo* obj,然后拷贝此定义到a.h文件中并在前面加上extern(extern CFoo* obj
如此就可通过了。当然extern也可以在任何调用此变量的位置之前声明,不过强烈建议不要这么作,因为到处作用extern,会
导致接口不统一。良好的习惯是接口一般就放到对应的头文件。

b. 将a.h中的定义修改成内部链接,即加上inline关键字,这时每个t1.obj和t2.obj都存放有一份Foo函数体,但它们不是外部
符号,所以不会被别的obj文件引用到,故不存在冲突。(提示:把上述程序改来试试)
另外我作了个实验来验证”vc是把是否是外部符号的标志记录在obj文件中的“(有点绕口)。可以看看,如下:
(1)文件内容:
// 文件main.cpp内容:
void main(){}
// 文件t1.cpp内容:
#include "a.h"
void Test1(){ Foo(); }
// 文件t2.cpp内容:
#include "a.h"
void Test2(){ Foo(); }
// 文件a.h内容:
inline void Foo( ){ }
(2) 选t1.cpp按ctrl+f7单独编译,并把编译后的t1.obj修改成t1.obj_inline
(3) 选t2.cpp按ctrl+f7单独编译,并把编译后的t2.obj修改成t2.obj_inline
(4) 把除了t1.obj_inline及t2.obj_inline外的其它编译生成的文件删除。
(5) 修改a.h内容为:void Foo( ){ },使之变为非内联函数作测试
(6) rebuild all所有文件。这时提示:
Linking...
t2.obj : error LNK2005: "void __cdecl Foo(void)" (
?Foo@@YAXXZ) already defined in t1.obj
Debug/cle.exe : fatal error LNK1169: one or more multiply defined symbols found
(7) 好,看看工程目录下的debug目录中会看到新生成的obj文件。
下面我们来手工链接看看,
打开菜单中的project - setting - Link,拷贝Project options下的所有内容,如下:
kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /incremental:yes /pdb:"Debug/cle.pdb" /debug /machine:I386 /out:"Debug/cle.exe" /pdbtype:sept
把它修改成:
Link.exe kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /incremental:yes /pdb:"Debug/cle.pdb" /debug /machine:I386 /out:"Debug/cle.exe" /pdbtype:sept Debug/t1.obj Debug/t2.obj Debug/main.obj
pause
注意前面多了Link.exe,后面多了Debug/t1.obj Debug/t2.obj Debug/main.obj以及
最后一个pause批处理命令,然后把它另存到工程目录(此目录下会看到debug目录)下起名为link.bat
运行它,就会看到:
t2.obj : error LNK2005: "void __cdecl Foo(void)" (
?Foo@@YAXXZ) already defined i
n t1.obj
Debug/cle.exe : fatal error LNK1169: one or more multiply defined symbols found
很好,我们链接原来的obj文件得到的效果跟在vc中用rebuild all出来的效果一样。那么现在如果
我们把备份出来的t1.obj_inline覆盖t1.obj而t2.obj_inline覆盖t2.obj再手动链接应该会是
不会出错的,因为原t1.obj_inline及t2.obj_inline中存放的是内部链接符号。好运行Link.bat,果然
不出所料,链接成功了,看看debug目录下多出了一个exe文件。这就说明了内或外符号在obj有标志标识!
(提示:上述为什么不用vc的f7build链接呢,因为文件时间改变了,build会重新生成新的obj,
所以我们用手动链接保证obj不变)[注bj信息可用dumpbin.exe查看]


4.#include规则:
有很多人不知道#include 文件该放在何处?

1). 增强部件自身的完整性:
为了保证部件完整,部件的cpp实现文件(如test.cpp)中第一个#include的应当是它自身对应的头文件(如test.h)。
(除非你用预编译头文件, 预编译头必须放在第一个)。这样就保证了该部件头文件(test.h)所必须依赖的其它接口(如a.h等)要放到它对应的文件头中(test.h),而不是在cpp中(test.cpp)把所依赖的其它头文件(a.h等)移到其自身对应的头文件(test.h等)之前(因为这样强迫其它包括此部件的头文件(test.h)的文件(b.cpp)也必须再写一遍include(即b.cpp若要#include "test.h"也必须#include "a.h")”。另外我们一般会尽量减少文件头之间的依赖关系,看下面:

2). 减少部件之间的依赖性:
在1的基础上尽量把#include到的文件放在cpp中包括。
这就要求我们一般不要在头文件中直接引用其它变量的实现,而是把此引用搬到实现文件中。
例如:
// 文件foo.h:
class CFoo{
void Foo(){}
};
// 文件test.h:
#include "foo.h"
class CTest{
CFoo* m_pFoo;
public:
CTest() : m_pFoo(NULL){}
void Test(){ if(m_pFoo){ m_pFoo->Foo();}}
...........
};
// 文件test.cpp:
#include "test.h"
.....

如上文件test.h中我们其实可以#include "foo.h"移到test.cpp文件中。因为CFoo* m_pFoo我们只想在部件CTest中用到,
而将来想用到CTest部件而包括test.h的其它部件没有必要见到foo.h接口,所以我们用前向声明修改原文件如下:
// 文件foo.h:
class CFoo{
public:
void Foo(){}
};
// 文件test.h:
class CFoo;
class CTest{
CFoo* m_pFoo;
public:
CTest();
void Test();
//........
};
// 文件test.cpp:
#include "test.h" // 这里第一个放该部件自身对应的接口头文件
#include "foo.h" // 该部件用到了foo.h
CTest::CTest() : m_pFoo(0){
m_pFoo = new CFoo;
}
void CTest::Test(){
if(m_pFoo){
m_pFoo->Foo();
}
}
//.....
// 再加上main.cpp来测试:
#include "test.h" // 这里我们就不用见到#include "foo.h"了
CTest test;
void main(){
test.Test();
}

3). 双重包含卫哨:
在文件头中包括其它头文件时(如:#include "xx.h")建议也加上包含卫哨:
// test.h文件内容:
#ifndef __XX1_H_
#include "xx1.h"
#endif
#ifndef __XX2_H_
#include "xx2.h"
#endif
......

虽然我们已经在xx.h文件中开头已经加过,但是因为编译器在打开#include文件也
是需要时间的,如果在外部加上包含卫哨,对于很大的工程可以节省更多的编译时间。

posted @ 2011-05-22 13:47 Carrie 阅读(633) | 评论 (0)编辑 收藏

好久没这种认真的感觉了
为了搞清一个问题,可以专注一个小时以上不开小差。
只有初中的时候才有这种精神,现在想找回来,怎么也专注不起来。
想当初俺也是考全年级第一的!

posted @ 2011-05-21 23:20 Carrie 阅读(92) | 评论 (0)编辑 收藏

最近在看代码,写代码的人很喜欢用回调函数和函数指针。一直觉得回调函数和函数指针挺神秘的,所以查了一些资料,来与大家一起来分享。

什么是回调函数

简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用为调用它所指向的函数时,我们就说这是回调函数。

为什么要使用回调函数

   因为使用回调函数可以把调用者和被调用者分开,调用者不关心谁是被调用者,所有它需知道的,只是存在一个具有某种特定原型、某些限制条件(如返回值为int)的被调用函数。回调函数就好像是一个中断处理函数,系统在符合你设定的条件时自动调用。

如何使用回调函数

 使用回调函数,我们需要做三件事:

  • 声明
  • 定义
  • 设置触发条件:在你的函数种把你的回调函数名称转化为地址作为一个参数,以便于系统调用。

声明和定义时应注意,回调函数由系统调用,所以可以认为它属于windows系统,不要把它当作你的某个类的成员函数。

回调函数是一个程序员不能显示调用的函数,通过将回调函数的地址传给调用者从而实现调用。回调函数是十分必要的,在我们想通过一个统一接口实现不同的内容,这时回调函数非常合适。

函数指针的声明

对回调函数有了一个初步的了解,下面我们来说一下函数指针。因为要实现回调,必须首先定义函数指针。

void (*) ()

左边圆括弧中的星号是函数指针声明的关键。另外两个元素是函数的返回类型(void)和右边圆括弧中的入口参数

为函数指针声明类型定义:

Typedef void(* pfv)()

pfv 是一个函数指针,它指向的函数没有输入参数,返回类型为voie。使用这个类型定义名称可以隐藏负责的函数指针语法。

void (*p)();

void func()

{

……

}

p = func;

p的赋值可以不同,但一定要是函数的指针,并且参数和返回类型相同。

例如:

现学现卖的一个小例子

#include <iostream>
using namespace std;

typedef 
void (*PF)();
void func()
{
  cout 
<< "func" << endl;
}


void caller( PF pf)
{
  pf();
}


int main()
{
  PF p 
= func;
  caller(p);

  system(
"pause");

  
return 0;
}


调用约定

visual c++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示调用规范(默认为_cdecl)。调用规范影响编译器产生的给定函数名,参数传递的顺序,堆栈清理责任以及参数传递机制。

不过,在win32的程序中,我见得比较多的是CALLBACK,这个宏定义在windef.h中,

#define CALLBACK    __stdcall

它约定了函数在它们返回到调用者之前,都会从堆栈中移除掉参数。

posted @ 2011-03-28 21:11 Carrie 阅读(376) | 评论 (0)编辑 收藏
2010实习找工作
笔试面试讨论QQ群欢迎加入
posted @ 2009-04-03 19:38 Carrie 阅读(85) | 评论 (0)编辑 收藏
v
posted @ 2009-01-06 15:48 Carrie 阅读(66) | 评论 (0)编辑 收藏
仅列出标题
共8页: 1 2 3 4 5 6 7 8