忘了说我爱你绘本图片图片了

1844人阅读
VC++ 基本概念与应用(101)
VC++(161)
C++学了这么多年你知道为什么定义类时,类的定义放在.h文件中,而类的实现放在cpp文件中。它们为什么能够关联到一起呢?你知道什么东西可以放在.h文件中,什么不能。什么东西又可以放在cpp文件中。如果你忘记了或是压根就不明白,那么读过此文你会清晰无比!!
首先谈下声明与定义的区别。
&&&&&&& 声明是将一个名称引入程序。定义提供了一个实体在程序中的唯一描述。声明和定义有时是同时存在的。
&&&&& extern int b=1;
&&& 只有当中不存在初始化式是才是声明。其他情况既是定义也是声明。
&&&& 但是在下列情况下,声明仅仅是声明:
&&&&&& 1:仅仅提供函数原型。如
&&&&&& 3:;
&&&&&& 4:声明
&&&&&& 5:在类中定义的静态数据成员的声明
&span style=&font-size: 18&&class A&{& && public:& &&& static int&};&/span&&
&span style=&font-size:18&&class A
&& 下列情况下 ,定义仅仅是定义:
&&&&& 1:在类定义之外,定义并初始化一个静态数据成员。如
&&&&& 2:在类外定义非内联成员函数。
&&&& 声明仅仅是将一个符号引入到一个作用域。而定义提供了一个实体在程序中的唯一描述。在一个给定的定义域中重复声明一个符号是可以的但是却不能重复定义否则将会引起编译错误。但是在类中的成员函数和静态数据成员却是例外,虽然在类内它们都是声明,但是也不能有多个。
&span style=&font-size: 18&&class A&{& &&& public:& &&&&& static int&&&&&& static int&&&&& void func(int ,int);&&&&& void func(int ,int);&};&/span&&
&span style=&font-size:18&&class A
void func(int ,int);
void func(int ,int);
&&&&&&& 明白了声明与定义的区别,还需要明白 内部链接、外部链接。只有明白了它们你才会知道开头提出的问题。
&&&&&& 在编译时,编译器只检测程序语法和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成目标文件。而在链接程序时,链接器会在所有的目标文件中找寻函数的实现。如果找不到,那到就会报链接错误码()。在下,这种错误一般是:错误,意思说是说,链接器未能找到函数的实现。
&&&&& 链接把不同编译单元产生的符号联系起来。有两种链接方式:内部链接和外部链接。
&&&&& 如果一个符号名对于它的编译单元来说是局部的,并且在链接时不可能与其他编译单元中的同样的名称相冲突,那个这个符号就是内部链接。内部链接意味着对此符号的访问仅限于当前的编译单元中,对其他编译单元都是不可见的。
&&&&&& static关键字作用在全局变量时,表示静态全局变量。但是作用域仅仅在当前文件作用域内。其他文件中即使使用声明也是无法使用的。也类似。
&&&&&& 带有、关键字和枚举类型的连接是内部的。
&&&&&& 具有内部链接的符号无法作用于当前文件外部,要让其影响程序的其他部分,可以将其放在文件中。此时在所有包含此文件的源文件都有自己的定义且互不影响。
&&&&&& 类的定义具有内部链接,由于它是定义,因此在同一编译单元中不能重复出现。如果需要在其他编译单元使用,类必须被定义在头文件且被其他文件包含。仅仅在其他文件中使用声明是不行的,原因就是类的定义是内部链接,不会在目标文件导出符号。也就不会被其他单元解析它们的未定义符号。理解这一点很重要。
&&&& 内联函数也具有内部链接。
&&&&& 在一个多文件的程序中,如果一个符号在链接时可以和其他编译单元交互,那么这个名称就有外部链接。外部链接意味着该定义不仅仅局限在单个编译单元中。它可以在文件中产生外部符号。可以被其他编译单元访问用来解析它们未定义的符号。因此它们在整个程序中必须是唯一的,否则将会导致重复定义。
&&&&&& 非内联成员函数、非内联函数、非静态自由函数都具有外部链接。
&&&&&& 内联函数之所有具有内部链接,因为编译器在可能的时候,会将所有 对函数的调用替换为函数体,不将任何符号写入文件。
&&&&&& 判断一个符号是内部链接还是外部链接的一个很好的方法就是看该符号是否被写入文件。
&&&&&& 前面说的是定义对链接方式的影响,接下来说下声明对链接方式的影响。
&&&&&& 由于声明只对当前编译单元有用,因此声明并不将任何东西写入文件。
&&&&&& int func();
&&&&&& 这些声明本身不会影响到文件的内容。每一个都只是命名一个外部符号,使当前的编译单元在需要的时候可以访问相应的全局定义。
&&&& 函数调用会导致一个未定义的符号被写入到文件。如果在该文件中没有被使用,那么没有被写入到文件。而函数有对此函数的调用。也就会将此符号写入目标文件。此后此文件与定义此符号的文件被连接在一起,前面未定义的符号被解析。
&&&& 上述声明有可能导致该符号被写入目标文件中。但是以下声明并不会导致该符号写入到目标文件中。
&span style=&font-size: 18&&typedefint Int;Class A;&
struct& union&/span&&
&span style=&font-size:18&&typedef int Int;Class A;
&&&& 它们的链接也是内部的。
&&&& 类声明和类定义都是内部链接。只是为当前编译单元所用。
&&&& 静态的类数据成员的定义具有外部链接。如
&span style=&font-size: 18&&class A&{& static& int&};&/span&&
&span style=&font-size:18&&class A
//声明。具有内部链接。
};&/span&
&&&&& 静态数据成员仅仅是一个声明,但是它的定义却具有外部链接。
&&&& C++对类和枚举类型的处理方式是不一样的。比如:在不定义类时可以声明一个类。但是不能未经定义就声明一个枚举类型。
&&&& 基于以上的分析,我们可以知道:将具有外部链接的定义放在头文件中几乎都是编程错误。因为如果该头文件中被多个源文件包含,那么就会存在多个定义,链接时就会出错。
&&&& 在头文件中放置内部链接的定义却是合法的,但不推荐使用的。因为头文件被包含到多个源文件中时,不仅仅会污染全局命名空间,而且会在每个编译单元中有自己的实体存在。大量消耗内存空间,还会影响机器性能。
&&&& const和修饰的全局变量仅仅在当前文件作用域内有效。它们具有内部链接属性。
&&& 下面列出一些应该或是不应该写入头文件的定义:
&span style=&font-size: 18&&&#ifndef TEST_H& #define TEST_H&
int&&&& &extern int b=10;&const int c=2;&static int d=3;&static void func(){}&
void func2(){} &void func3();&class A& {& && public:& &&&& static int&&&&& int f;&&&&& void func4();&};& A::e=10;&void A:func4()&{& & & }& #endif&/span&&
&span style=&font-size:18&&//test.h
#ifndef TEST_H
#define TEST_H
//a有外部链接,不能在头文件中定义。
extern int b=10;//同上。
const int c=2;//c具有内部链接,可以定在头文件中但应该避免。
static int d=3;//同上。
static void func(){} //同上。
void func2(){} //同a。
void func3();//可以。仅仅是声明。并不会导致符号名被写入目标文件。
//可以,具有内部链接。
int f;//可以,同上。
void func4();//声明,内部链接。同上。
A::e=10;//不可以在头文件中包含具有外部链接的定义。符号名别写入目标文件。
void A:func4()//不可以,类成员函数。外部连接。
#endif&/span&
&&&&& 相信大家现在明白为什么只在类型声明成员函数,而不实现它是合法的了。也可以回答为什么类的定义可以放在文件中。而类的实现可以放在同名的文件中。老师以前的介绍是说编译器会自动寻找同名的文件。其实是因为由于文件中存储的是成员函数的实现,而成员函数具有外部链接特性,会在目标文件产生符号。在此文件中此符号是定义过的。其他调用此成员函数的目标文件也会产生一个未定的符号。两目标文件连接后此符号就被解析。注意数据成员应该放在文件中。而不能放在文件。
&&&&& 有内部链接的定义可以定义在文件中,并不会影响全局的符号空间 。但是在文件作用域中要避免定义(并不禁止)没有声明为静态的数据和函数,因为它们具有外部链接。
&span style=&font-size: 18&&int&
void func()& {&&& && ......& }&/span&&
&span style=&font-size:18&&
void func()
&&&&& 上述定义具有外部链接可能会与全局命名空间的其他符号名称存在潜在冲突。如果确实需要使用全局的变量或函数。可以为它们加上static关键字。使其作用域局限在当前文件内,具有内部链接也就不会对全局命名空间产生影响。因为内联函数和静态自由函数、枚举以及类型的数据都具有内部链接,所以它们可以定义在文件中,而不会影响全局命名空间。
&&&&& typedef和宏定义不会将符号引入文件,它们也可以出现在文件中,不会影响全局命名空间。
&&&&& typedef 为一个已存在的类型创建一个别名。而不是创建一个新的类型。它不提供类型安全。如
&span style=&font-size: 18&&typedefint IntA;&
typedef int InB;&/span&&
&span style=&font-size:18&&typedef int IntA;
typedef int InB;&/span&
&&&&&& 在需要的地方使用是不会报错的。它们可以互相替换。因为此我们称它不提供类型安全。但是在定义函数类型时经常使用,可以使定义更清晰。
&&&&& 标准库提供一个宏,用以保证给定的表达式值非零。否则便会输出错误信息并终止程序执行。只有在程序中没有定义时,才会工作。一旦定义,语句将会被忽略
。注意与中的相区别。是提供的。当被定义时才会起作用。
在的模式下会被定义。而在模式下会被定义。
&&& 好了,相信大家都会明白开头提出的问题了。如果有不明白的,请务必留言哦。如有错误,也请不吝指正!!
&&& 以上内容参考自《Large Scale C++ software design》。
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:629574次
积分:9178
积分:9178
排名:第1560名
原创:208篇
转载:586篇
评论:58条vb的操作题(有答案)_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
vb的操作题(有答案)
上传于||暂无简介
阅读已结束,如果下载本文需要使用1下载券
想免费下载本文?
定制HR最喜欢的简历
下载文档到电脑,查找使用更方便
还剩46页未读,继续阅读
定制HR最喜欢的简历
你可能喜欢下次自动登录
现在的位置:
& 综合 & 正文
C++中模板的声明和实现应该放在同一个文件中
在C++中,使用模板类和模板函数,在.h文件中写模板类的声明,在.cpp文件中写模板类中成员函数的实现,在编译时无错误。但在main()函数中实例化模板类的对象时候产生编译错误。
在rdDBMS.h中定义如下模板类:
template &class obj_type,
int rd_type&
class rdDBMS
list&obj_type&
rdDBMS(); //get data stored in file "reader.dat" into reader list
~rdDBMS();//write data in list reader into file reader.dat
void clrRdAll();//clear all reader records
int rdAdd(int,
char*);//add a new reader record
obj_type *rdQuery(int);//query a reader record
void rdDispAll();
void rdMng();//manage all reader records
其模板函数在rdDBMS.cpp中实现,然后再main()函数中实例化对象:
rdDBMS&Teacher, 0& rd_
此时产生如下编译错误:(见下面的图片)
解决办法:
这是由于编译器在对模板类的声明和实现分离时候,会产生这种错误。
解决办法:
把模板类的声明和实现放在同一个.h文件中,然后再其他文件中包含该.h文件即可;
在.h中声明,在另外一个.cpp文件中写其实现,然后在需要使用的其他文件中包含该.cpp文件即可
&&&&推荐文章:
【上篇】【下篇】原文:/ider/archive//what_is_in_cpp_header_and_implementation_file.html
在C++编程过程中,随着项目的越来越大,代码也会越来越多,并且难以管理和分析。于是,在C++中就要分出了头(.h)文件和实现(.cpp)文件,并且也有了Package的概念。
对于以C起步,C#作为&母语&的我刚开始跟着学习C++对这方面还是感到很模糊。虽然我可以以C的知识面对C++的语法规范,用C#的思想领悟C++中类的使用。但是C#中定义和实现是都在一个文件中(其实都是在类里面),而使用C的时候也只是编程的刚刚起步,所写的程序也只要一个文件就够了。因此对于C++的Package理解以及.h文件和.cpp文件的总是心存纠结。
幸好有详细的让我了解,一次对于Package的认识就明白多了。简单讲,一个Package就是由同名的.h和.cpp文件组成。当然可以少其中任意一个文件:只有.h文件的Package可以是接口或模板(template)的定义;只有.cpp文件的Package可以是一个程序的入口。
当然更具体详细的讲解,欢迎下载导师的教学来了解更多。
不过我在这里想讲的还是关于.h文件和.cpp文件
知道Package只是相对比较宏观的理解:我们在项目中以Package为编辑对象来扩展和修正我们的程序。编写代码时具体到应该把什么放到.h文件,又该什么放在.cpp文件中,我又迷惑了。
虽然Google给了我很多的链接,但是大部分的解释都太笼统了:申明写在.h文件,定义实现写在.cpp文件。这个解释没有差错,但是真正下手起来,又会发现不知道该把代码往哪里打。
于是我又把这个问题抛给了,他很耐心地给我详详细细地表述了如何在C++中进行代码分离。很可惜,第一次我听下了,但是没有听太懂,而且本来对C++就了解不深,所以也没有深刻的印象。
经过几个项目的试炼和体验之后,我又拿出这个问题问,他又一次耐心地给我讲解了一遍(我发誓他绝对不是忘记了我曾经问过同样的问题),这次我把它记录了下来。
为了不再忘记,我将它们总结在这里。
全局变量申明(带extern限定符)
全局函数的申明
带的全局函数的定义&
带的全局模板函数的申明和定义
类函数成员和数据成员的申明(在类内部)
类定义内的函数定义(相当于inline)
带的数据成员在类内部的初始化
带的类定义外的函数定义
模板类的定义
模板类成员的申明和定义(定义可以放在类内或者类外,类外不需要写inline)
实现文件(.cpp)
全局变量的定义(及初始化)
全局函数的定义&
& & & & & & & & & & & & & (无)&
类函数成员的定义
类带static限定符的数据成员的初始化
*申明:declaration*定义:definition
头文件的所有内容,都必须包含在
#ifndef&{Filename}&#define&{Filename}&//{Content of head file}&#endif
这样才能保证头文件被多个其他文件引用(include)时,内部的数据不会被多次定义而造成错误
inline限定符
在头文件中,可以对函数用inline限定符来告知编译器,这段函数非常的简单,可以直接嵌入到调用定义之处。
当然inline的函数并不一定会被编译器作为inline来实现,如果函数过于复杂,编译器也会拒绝inline。
因此简单说来,代码最好短到只有3-5行的才作为inline。有循环,分支,递归的函数都不要用做inline。
对于在类定义内定义实现的函数,编译器自动当做有inline请求(也是不一定inline的)。因此在下边,我把带有inline限定符的函数成员和写在类定义体内的函数成员统称为&要inline的函数成员&
非模板类型
就像前面笼统的话讲的:申明写在.h文件。
对于函数来讲,没有实现体的函数,就相当于是申明;而对于数据类型(包括基本类型和自定义类型)来说,其申明就需要用extern来修饰。
然后在.cpp文件里定义、实现或初始化这些全局函数和全局变量。
不过导师一直反复强调:不许使用全局函数和全局变量。用了之后造成的后果,目前就是交上去的作业项目会扣分。当然不能用自有不能用的理由以及解决方案,不过不在目前的讨论范围内。
自定义类型
对于自定义类型,包括类(class)和结构体(struct),它们的定义都是放在.h文件中。其成员的申明和定义就比较复杂了,不过看上边的表格,还是比较清晰的。
函数成员无论是否带有static限定符,其申明都放在.h文件的类定义内部。
对于要inline的函数成员其定义放在.h文件;其他函数的实现都放在.cpp文件中。
数据成员的申明与定义都是放在.h文件的类定义内部。对于数据类型,关键问题是其初始化要放在什么地方进行。
对于只含有static限定符的数据成员,它的初始化要放在.cpp文件中。因为它是所有类对象共有的,因此必须对它做合适的初始化。
对于只含有const限定符的数据成员,它的初始化只能在构造函数的初始化列表中完成。因为它是一经初始化就不能重新赋值,因此它也必须进行合适的初始化。
对于既含有static限定符,又含有const限定符的数据成员,它的初始化和定义同时进行。它也是必须进行合适的初始化
对于既没有static限定符,又没有const限定符的数据成员,它的值只针对本对象可以随意修改,因此我们并不在意它的初始化什么时候进行。
C++中,模板是一把开发利器,它与C#,Java的泛型很相似,却又不尽相同。以前,我一直只觉得像泛型,模板这种东西我可能一辈子也不可能需要使用到。但是在导师的强制逼迫使用下,我才真正体会到模板的强大,也真正知道要如何去使用模板,更进一步是如何去设计模板。不过这不是三言两语可以讲完的,就不多说了。
对于模板,最重要的一点,就是在定义它的时候,编译器并不会对它进行编译,因为它没有一个实体可用。
只有模板被具体化(specialization)之后(用在特定的类型上),编译器才会根据具体的类型对模板进行编译。
所以才定义模板的时候,会发现编译器基本不会报错(我当时还很开心的:我写代码尽然会没有错误,一气呵成),也做不出智能提示。但是当它被具体用在一个类上之后,错误就会大片大片的出现,却往往无法准确定位。
因此设计模板就有设计模板的一套思路和方式,但是这跟本文的主题也有偏。
因为模板的这种特殊性,它并没有自己的准确定义,因此我们不能把它放在.cpp文件中,而要把他们全部放在.h文件中进行书写。这也是为了在模板具体化的时候,能够让编译器可以找到模板的所有定义在哪里,以便真正的定义方法。
至于模板类函数成员的定义放在哪里,导师的意见是放在类定义之外,因为这样当你看类的时候,一目了然地知道有那些方法和数据;我在用Visual Studio的时候查看到其标准库的实现,都是放在类内部的。
可能是我习惯了C#的风格,我比较喜欢把它们都写在类内部,也因为在开发过程中,所使用的编辑器都有一个强大的功能:代码折叠。
当然还有其他原因就是写在类外部,对于每一个函数成员的实现都需要把模板类型作为限定符写一遍,把类名限定符也要写一遍。
=========================================================================================================
谢 邀,这个问题让我想起我在实习的时候犯的一个错误,就是把模版类的定义和实现分开写了,结果编译出错,查了两天才查出问题。C++中每一个对象所占用的空间大小,是在编译的时候就确定的,在模板类没有真正的被使用之前,编译器是无法知道,模板类中使用模板类型的对象的所占用的空间的大小的。只有模板被真正使用的时候,编译器才知道,模板套用的是什么类型,应该分配多少空间。这也就是模板类为什么只是称之为模板,而不是泛型的缘故。既然是在编译的时候,根据套用的不同类型进行编译,那么,套用不同类型的模板类实际上就是两个不同的类型,也就是说,stack&int&和stack&char&是两个不同的数据类型,他们共同的成员函数也不是同一个函数,只不过具有相似的功能罢了。如上图所示,很简短的六行代码,用的是STL里面的stack,stack&int&和stack&char&的默认构造函数和push函数的入口地址是不一样的,而不同的stack&int&对象相同的函数入口地址是一样的,这个也反映了模板类在套用不同类型以后,会被编译出不同代码的现象。所以模板类的实现,脱离具体的使用,是无法单独的编译的;把声明和实现分开的做法也是不可取的,必须把实现全部写在头文件里面。为了清晰,实现可以不写在class后面的花括号里面,可以写在class的外面。
================================
还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用,你都要加上这个。一般格式是这样的:
#ifndef &标识&&#define &标识&
......&......
&标识&在理论上来说可以是自由命名的,但每个头文件的这个&标识&都应该是唯一的。标识的命名规则一般是头文件名全大写,前后加下划线,并把文件名中的&.&也变成下划线,如:stdio.h
#ifndef _STDIO_H_&#define _STDIO_H_
2.在#ifndef中定义变量出现的问题(一般不定义在#ifndef中)。
#ifndef AAA#define AAA...&&&& ...#endif里面有一个变量定义在vc中链接时就出现了i重复定义的错误,而在c中成功编译。
(1).当你第一个使用这个头的.cpp文件生成.obj的时候,int i 在里面定义了当另外一个使用这个的.cpp再次[单独]生成.obj的时候,int i 又被定义然后两个obj被另外一个.cpp也include 这个头的,连接在一起,就会出现重复定义.
(2).把源程序文件扩展名改成.c后,VC按照C语言的语法对源程序进行编译,而不是C++。在C语言中,若是遇到多个int i,则自动认为其中一个是定义,其他的是声明。
(3).C语言和C++语言连接结果不同,可能(猜测)时在进行编译的时候,C++语言将全局变量默认为强符号,所以连接出错。C语言则依照是否初始化进行强弱的判断的。(参考)
解决方法:
(1).把源程序文件扩展名改成.c。
(2).推荐解决方案:.h中只声明在.cpp中定义
&x.h&#ifndef __X_H__#define __X_H__#endif //__X_H__&x.c&
阅读(...) 评论()酷勤网 C 程序员的那点事!
当前位置: >
浏览次数:次
调试程序的时候,突然想到这个问题,百度一下发现有不少这方面的问答,粗略总结一下:
属性写在.h文件中和在.m文件中有什么区别?
objective-c中有分类和类扩展的概念,而实现文件中的类声明实际上就是类扩展
@interface部分为类扩展(extension)。
其被设计出来就是为了解决两个问题的,其一,定义类私有方法的地方,也就是下面说到的区别一。其二,实现public readonly,private readwrite的property(意思是在h头文件中定义一个属性对外是readonly的,但在类的内部希望是可读写的,所以可以在m源文件中的@interface部分重新定义此属性为readwrite,此时此属性对外是只读的,对内是读写的)。
此外,也可在此部分申明变量和属性,但申明的变量,属性和方法均为私有的,只能够被当前类访问,相当于private。
属性在.h文件中和在.m中声明是有区别的。区别就是,在.h文件中声明的属性,外部类可以通过&类实例.属性&来调用,但在.m中声明的则不可以,获取和设置的方法,只能是通过setValue:forKey和valueForKey来实现。
成员变量,有三种权限,就是大家都知道的@private、@protected、,写在.m文件中时,相当于是@private权限,子类无法访问,验证了一下,做权限修改也无效。而写在.h文件中,默认是@protected权限,子类可以访问,可以做权限修改。因为访问权限指针对.h文件。.h文件中成员变量,外部类对其的调用,跟C++一样,用-&来调用。
这样可以提高编译效率,避免重复编译。
因为分开的话只需要编译一次生成对应的.obj文件后,再次应用该类的地方,这个类就不会被再次编译,从而大大提高了效率。这样可以提高编译效率,避免重复编译。
怎么去解释呢。。。其实这是一个面向对象的思想,所谓&提高&的比较对象,应该是直接将方法写到具体函数里的实现方式
h为编译器提供一个索引、声明,连接obj对象和主程序
编译器在编译的时候,如果需要,则去查找h,找到了h,再找对应的obj,就可以找到类的方法了
但是如果直接写入到同一个文件(例如hpp),主程序没有索引,也不清楚具体的类实现了没有,只能一次次重复的编译相同的代码,这样实际上没有把这个类该有的东西抽象出来
对于函数声明在头文件中,在实现文件中实现,也是避免重复编译,函数可以多次声明,但只能实现一次。
头文件相对于实现文件的作用在于:
1.头文件可以预先告诉编译器一些必要的声明,让编译器顺利进行下去,在连接实现以前.未必出现实际的定义.
头文件的意义在
a.使得程序简明,清晰.
b.避免了重复编写相同的声明代码.
2.**.c和**.h文件没有必然的联系.
关于头文件和实现文件的编译连接的过程
其实要理解C文件与头文件有什么不同之处,首先需要弄明白编译器的工作过程,一般说来编译器会做以下几个过程:
1.预处理阶段
2.词法与语法分析阶段
3.编译阶段,首先编译成纯汇编语句,再将之汇编成跟CPU相关的二进制码,生成各个目标文件
4.连接阶段,将各个目标文件中的各段代码进行绝对地址定位,生成跟特定平台相关的可执行文件,当然,最后还可以用objcopy生成纯二进制
码,也就是去掉了文件格式信息.
编译器在编译时是以C文件为单位进行的,也就是说如果你的项目中一个C文件都没有,那么你的项目将无法编译,连接器是以目标文件为单位
,它将一个或多个目标文件进行函数与变量的重定位,生成最终的可执行文件,在PC上的程序开发,一般都有一个main函数,这是各个编译器
的约定,当然,你如果自己写连接器脚本的话,可以不用main函数作为程序入口!!!!
有了这些基础知识,再言归正传,为了生成一个最终的可执行文件,就需要一些目标文件,也就是需要C文件,而这些C文件中又需要一个main
函数作为可执行程序的入口,那么我们就从一个C文件入手,假定这个C文件内容如下:
#include &stdio.h&
#include &mytest.h&
int main(int argc,char **argv)
test = 25;
printf(&test.................%dn&,test);
头文件内容如下:
现在以这个例子来讲解编译器的工作:
1.预处理阶段:编译器以C文件作为一个单元,首先读这个C文件,发现第一句与第二句是包含一个头文件,就会在所有搜索路径中寻找这两个
文件,找到之后,就会将相应头文件中再去处理宏,变量,函数声明,嵌套的头文件包含等,检测依赖关系,进行宏替换,看是否有重复定义
与声明的情况发生,最后将那些文件中所有的东东全部扫描进这个当前的C文件中,形成一个中间&C文件&
2.编译阶段,在上一步中相当于将那个头文件中的test变量扫描进了一个中间C文件,那么test变量就变成了这个文件中的一个全局变量,此时
就将所有这个中间C文件的所有变量,函数分配空间,将各个函数编译成二进制码,按照特定目标文件格式生成目标文件,在这种格式的目标文
件中进行各个全局变量,函数的符号描述,将这些二进制码按照一定的标准组织成一个目标文件
3.连接阶段,将上一步成生的各个目标文件,根据一些参数,连接生成最终的可执行文件,主要的工作就是重定位各个目标文件的函数,变量
等,相当于将个目标文件中的二进制码按一定的规范合到一个文件中
再回到C文件与头文件各写什么内容的话题上:
理论上来说C文件与头文件里的内容,只要是C语言所支持的,无论写什么都可以的,比如你在头文件中写函数体,只要在任何一个C文件包含此
头文件就可以将这个函数编译成目标文件的一部分(编译是以C文件为单位的,如果不在任何C文件中包含此头文件的话,这段代码就形同虚设
),你可以在C文件中进行函数声明,变量声明,结构体声明,这也不成问题!!!那为何一定要分成头文件与C文件呢?又为何一般都在头件
中进行函数,变量声明,宏声明,结构体声明呢?而在C文件中去进行变量定义,函数实现呢??原因如下:
1.如果在头文件中实现一个函数体,那么如果在多个C文件中引用它,而且又同时编译多个C文件,将其生成的目标文件连接成一个可执行文件
,在每个引用此头文件的C文件所生成的目标文件中,都有一份这个函数的代码,如果这段函数又没有定义成局部函数,那么在连接时,就会发
现多个相同的函数,就会报错
2.如果在头文件中定义全局变量,并且将此全局变量赋初值,那么在多个引用此头文件的C文件中同样存在相同变量名的拷贝,关键是此变量被
赋了初值,所以编译器就会将此变量放入DATA段,最终在连接阶段,会在DATA段中存在多个相同的变量,它无法将这些变量统一成一个变量,也就是仅为此变量分配一个空间,而不是多份空间,假定这个变量在头文件没有赋初值,编译器就会将之放入BSS段,连接器会对BSS段的多个
同名变量仅分配一个存储空间
3.如果在C文件中声明宏,结构体,函数等,那么我要在另一个C文件中引用相应的宏,结构体,就必须再做一次重复的工作,如果我改了一个C
文件中的一个声明,那么又忘了改其它C文件中的声明,这不就出了大问题了,程序的逻辑就变成了你不可想象的了,如果把这些公共的东东放
在一个头文件中,想用它的C文件就只需要引用一个就OK了!!!这样岂不方便,要改某个声明的时候,只需要动一下头文件就行了
4.在头文件中声明结构体,函数等,当你需要将你的代码封装成一个库,让别人来用你的代码,你又不想公布源码,那么人家如何利用你的库
呢?也就是如何利用你的库中的各个函数呢??一种方法是公布源码,别人想怎么用就怎么用,另一种是提供头文件,别人从头文件中看你的
函数原型,这样人家才知道如何调用你写的函数,就如同你调用printf函数一样,里面的参数是怎样的??你是怎么知道的??还不是看人家
的头文件中的相关声明啊!!!当然这些东东都成了C标准,就算不看人家的头文件,你一样可以知道怎么使用
& 相关主题:

我要回帖

更多关于 忘不了图片 的文章

 

随机推荐