头文件包含问题
- 格式:doc
- 大小:37.00 KB
- 文档页数:4
GCC常见错误解析一、错误类型第一类∶C语法错误错误信息∶文件source.c中第n行有语法错误(syntex errror)。
这种类型的错误,一般都是C语言的语法错误,应该仔细检查源代码文件中第n行及该行之前的程序,有时也需要对该文件所包含的头文件进行检查。
有些情况下,一个很简单的语法错误,gcc会给出一大堆错误,此时要保持清醒的头脑,不要被其吓倒,必要的时候再参考一下C语言的基本教材。
第二类∶头文件错误错误信息∶找不到头文件head.h(Can not find include file head.h)。
这类错误是源代码文件中的包含头文件有问题,可能的原因有头文件名错误、指定的头文件所在目录名错误等,也可能是错误地使用了双引号和尖括号。
第三类∶档案库错误错误信息∶连接程序找不到所需的函数库,例如∶ld: -lm: No such file or directory.这类错误是与目标文件相连接的函数库有错误,可能的原因是函数库名错误、指定的函数库所在目录名称错误等,检查的方法是使用find命令在可能的目录中寻找相应的函数库名,确定档案库及目录的名称并修改程序中及编译选项中的名称。
第四类∶未定义符号错误信息∶有未定义的符号(Undefined symbol)。
这类错误是在连接过程中出现的,可能有两种原因∶一是使用者自己定义的函数或者全局变量所在源代码文件,没有被编译、连接,或者干脆还没有定义,这需要使用者根据实际情况修改源程序,给出全局变量或者函数的定义体;二是未定义的符号是一个标准的库函数,在源程序中使用了该库函数,而连接过程中还没有给定相应的函数库的名称,或者是该档案库的目录名称有问题,这时需要使用档案库维护命令ar检查我们需要的库函数到底位于哪一个函数库中,确定之后,修改gcc 连接选项中的-l和-L项。
排除编译、连接过程中的错误,应该说这只是程序设计中最简单、最基本的一个步骤,可以说只是开了个头。
【转载】防止变量重复定义、头文件重复包含、嵌套包含【转自】 /zengzhaonong/blog/item/8a8871062d481f7f03 088106.html#include文件的一个不利之处在于一个头文件可能会被多次包含,为了说明这种错误,考虑下面的代码:#include "x.h"#include "x.h"显然,这里文件x.h被包含了两次,没有人会故意编写这样的代码。
但是下面的代码:#include "a.h"#include "b.h"看上去没什么问题。
如果a.h和b.h都包含了一个头文件x.h。
那么x.h在此也同样被包含了两次,只不过它的形式不是那么明显而已。
多重包含在绝大多数情况下出现在大型程序中,它往往需要使用很多头文件,因此要发现重复包含并不容易。
要解决这个问题,我们可以使用条件编译。
如果所有的头文件都像下面这样编写:#ifndef _HEADERNAME_H#define _HEADERNAME_H...//(头文件内容)#endif那么多重包含的危险就被消除了。
当头文件第一次被包含时,它被正常处理,符号_HEADERNAME_H被定义为1。
如果头文件被再次包含,通过条件编译,它的内容被忽略。
符号_HEADERNAME_H按照被包含头文件的文件名进行取名,以避免由于其他头文件使用相同的符号而引起的冲突。
但是,你必须记住预处理器仍将整个头文件读入,即使这个头文件所有内容将被忽略。
由于这种处理将托慢编译速度,所以如果可能,应该避免出现多重包含。
test-1.0使用#ifndef只是防止了头文件被重复包含(其实本例中只有一个头件,不会存在重复包含的问题),但是无法防止变量被重复定义。
# vi test.c-------------------------------#include <stdio.h>#include "test.h"extern i;extern void test1();extern void test2();int main(){test1();printf("ok\n");test2();printf("%d\n",i);return 0;}# vi test.h-------------------------------#ifndef _TEST_H_#define _TEST_H_char add1[] = "\n"; char add2[] = "\n"; int i = 10;void test1();void test2();#endif# vi test1.c-------------------------------#include <stdio.h>#include "test.h"extern char add1[];void test1(){printf(add1);}# vi test2.c-------------------------------#include <stdio.h>#include "test.h"extern char add2[]; extern i;void test2(){printf(add2);for (; i > 0; i--)printf("%d-", i);}# Makefile-------------------------------test: test.o test1.o test2.otest1.o: test1.ctest2.o: test2.cclean:rm test test.o test1.o test2.o错误:test-1.0编译后会出现"multiple definition of"错误。
[CC++]编程规范⼀:头⽂件篇⼀般来说,每⼀个.cc或者.cpp⽂件对应⼀个头⽂件(.h⽂件),当然,也有例外,例如⼀些测试单元或者main⽂件,头⽂件的⼀些规范可以令代码可读性、程序的性能等⼤为改观,所以还是要注意头⽂件的规范问题。
⼀、#define保护所有头⽂件为了防⽌⽂件被多重包含(multiple inclusion),⼀般就需要#define保护。
#define保护的格式如下:<PROJECT>_<PATH>_<FILE>_H_例如:#ifndef FOO_BAR_BARZ_H_#define FOO_BAR_BARZ_H_...#endif //FOO_BAR_BARZ_H_这个是⽐较⽼的⼀个做法了,所以很多编译器基本都是可以兼容的,但是还有另⼀个语句也是可以起到相同作⽤,但是⼀些太⽼的编译器据说⽤不了的(但是我好像没遇到过),VS下好像默认新建⼀个类的头⽂件就会⽤这个新的:#program once这⼀种相对于第⼀种的好处是,不通过#define⼀个变量来起到防⽌重复包含,所以,不存在重复变量的问题,#define后⾯的变量在同⼀个⼯程⾥是不能重复的,重复的话是只认⼀个的,就可能导致另⼀个⽂件编译的时候被排除。
⼆、头⽂件依赖在头⽂件中减少包含其他头⽂件,改⽤前置声明(forward declarations),理由是:头⽂件被包含的同时会引⼊⼀项新的依赖(dependency),只要头⽂件被修改,代码就要重新编译,如果你的头⽂件包含了其它头⽂件,这些头⽂件的任何改动都将导致那些包含了你的头⽂件的代码重新编译。
使⽤前置声明就是在头⽂件中添加:class Foo;然后在对应的源⽂件中包含对应的头⽂件#include <Foo.h>可以这么做的⼏种情况:1)、将数据声明为指针(Foo*)或者引⽤(Foo&);2)、参数、返回值为Foo的函数只声明不定义;3)、静态数据成员可以被声明为Foo,因为静态数据成员的定义在类定义之外;但是,如果你的类是Foo的⼦类或者包含类型为Foo的⾮静态成员数据,则必须包含头⽂件。
#include<stdio.h> //预处理(头文件包含)#include<string.h>#include <conio.h>#include<iostream.h>#define MaxiProgramNumber 888 //源程序最大字符的个数#define MaxiWordLength 18 //标示符和关键字最长字符的个数#define MaxiTokenNumer 118 //标识符能包含的最多的字符个数#define MaxiWordLength1 18#define MaxiTokenNumer1 118char Ecode[MaxiTokenNumer1][MaxiWordLength1];int Top=0;int nID=1; //初始化/*……………下面定义栈结构,栈中的元素是一个结构体……………*/ typedef struct{int add;int next;char str[MaxiWordLength1];}ADDL; //ADDL用于识别WHILE,DOADDL L[MaxiTokenNumer1]; //L为定义的结构体名int nL=0;int nadd=1; //初始化/*…………………输入串栈,栈中元素为结构体………………………*/ typedef struct{int ID;char b[MaxiWordLength];}link;link Token[MaxiTokenNumer];link Token2[MaxiTokenNumer];int nTokenNumer=1; //标志位置char a[MaxiProgramNumber]; //数组用于装入源程序int nLength=0; //用于指向数组中的元素/*………………函数声明(词法分析部分用到的函数)…………………*/ void GetProgram(); //把源程序装入数组achar GetChr(); //将数组a中的元素读出int Judge(char& chr); //用于判断'\0'int IsLetter(char c); //用于判断是否为字母void Input(char* p,char c,int& nx); //标识符关键字入指针p指向的数组第nx+1个元素void Save(char* word,int x); //将关键字或标志符或算符装入Token void Getcode();/*………………函数声明(语法分析部分用到的函数)…………………*/ void Pop(); //出栈void InputS();void InputE1();void InputE2();void InputE3();void InputA();int firstset();int panduanESA(); //识别非终结符int EStrcmp();/*…………………………词法分析部分…………………………………*/ int Wordanalyze(){int jieshu=0; //词法分析没有结束char chr;cout<<"**请输入一段源程序(以#为结束标志):*********"<<endl<<endl <<"********计算机0305班30号张古月************"<<endl<<endl;GetProgram(); //把源程序装入数组aint x;char word[MaxiWordLength]; //声明临时数组while((chr=GetChr())!='\0'){if(!(Judge(chr))){break;} //跳过空格和回车取元素x=0;word[x]='\0'; //清空if(IsLetter(chr)){jieshu=1;while(IsLetter(chr)){Input(word,chr,x); //是字符就将其装入数组wordchr=GetChr(); }if(chr=='>' || chr=='='||chr=='<')nLength=nLength-1; //指向算符word[x]='\0';Save(word,x); } //将关键字或标志符或算符装入Token else if(chr=='>') //将'>'装入Token{Input(word,chr,x);word[x]='\0';Save(word,x);}else if(chr=='=') //将'='装入Token{ Input(word,chr,x);word[x]='\0';Save(word,x);}else if(chr=='<') //将'<'装入Token{Input(word,chr,x);word[x]='\0';Save(word,x);}else{printf("输入出错!\n"); / /出错处理jieshu=0;return 0;}} //这个函数的作用是将输入符号放入Token if(jieshu==1) //词法分析结束{ Getcode();printf("单词符号表为[<种别编码,单词属性>]:\n");for(int i=1;i<nTokenNumer;i++) //输出词法分析结果{ printf("<");printf("%d",Token[i].ID);printf(",");printf("%s",Token[i].b);printf(">\n");strcpy(Token2[i].b,Token[i].b); }//将输入的符号拷贝在Token2中for(int d=1;d<nTokenNumer;d++) //将标识符号替换成id{ if(Token[d].ID==6){ strcpy(Token[d].b,"id");} }strcpy(Token[nTokenNumer].b,"#");Token[nTokenNumer].ID=7; //'#'的种别编码为7return 1; }getch(); }void GetProgram() //把源程序装入数组a{char ch;while((ch=getchar())!='#'){ if(nLength>=MaxiProgramNumber-2)break;elsea[nLength++]=ch; }a[nLength]='\0';nLength=0;}char GetChr() //将数组a中的元素读出{if(nLength>=MaxiProgramNumber-1)return '\0';elsereturn a[nLength++];}int Judge(char& chr) //用于判断'\0',返回值为0时为'\0' {while(chr==10 || chr==32) //当chr为空格和换行时{chr=GetChr();if(chr=='\0'){ return 0; } }return 1;}int IsLetter(char c) //用于判断是否为字母{ if(c>='a' && c<='z' || c>='A' && c<='Z' )return 1;else return 0;}void Input(char* p,char c,int& nx) //标识符或关键字进入指针p指向的数组第nx+1元素{if(nx<MaxiWordLength-1){ p[nx]=c;nx++; }}void Save(char* p,int x) //将关键字或标志符或算符装入Token { for(int i=0;i<=x;i++)Token[nTokenNumer].b[i]=p[i];nTokenNumer=nTokenNumer+1;}/*…………为不同的输入符号赋予不同的种别编码…………………*/ void Getcode(){for(int i=1;i<nTokenNumer;i++){ if(strcmp(Token[i].b,"while\0")==0)Token[i].ID=1;else if(strcmp(Token[i].b,"do\0")==0)Token[i].ID=2;else if(strcmp(Token[i].b,">\0")==0)Token[i].ID=3;else if(strcmp(Token[i].b,"=\0")==0)Token[i].ID=4;else if(strcmp(Token[i].b,"<\0")==0)Token[i].ID=5;elseToken[i].ID=6; }}/*…………………………语法分析过程…………………………………*/ int ExpressionAnalyse() //语法分析{printf("\n语法分析得出:\n");strcpy(Ecode[Top++],"#"); //将#压栈strcpy(Ecode[Top++],"S"); //将S压栈int FLAG=1; //语法分析未结束标志while(FLAG){int f1=0;f1= panduanESA ();if(f1==1){cout<<" S->while E do A"<<endl;InputS(); }else if(f1==2){ int f3=0;f3=firstset();if(f3==1){cout<<" E->id>id"<<endl;InputE1(); }else if(f3==2){cout<<" E->id=id\n"<<endl;InputE2(); }else if(f3==3){cout<<" E->id<id"<<endl;InputE3(); } }else if(f1==3){cout<<" A->id=id"<<endl;InputA();}else{int f2=0;f2=EStrcmp();if(f2==1) //识别出关键字{ Pop();nID=nID+1; }else if(f2==3) //识别出#,分析结束{ cout<<endl<<"语法正确!"<<endl;FLAG=0;return 1; }else{ cout<<endl<<"语法错误啦!"<<endl;FLAG=0;return 0;}}}getch();}void InputG() //压栈{Pop();strcpy(Ecode[Top++],"A");strcpy(Ecode[Top++],"do");strcpy(Ecode[Top++],"E");strcpy(Ecode[Top++],"while");}void InputE() //压栈{Pop();strcpy(Ecode[Top++],"id");strcpy(Ecode[Top++],">");strcpy(Ecode[Top++],"id");}void InputA() //压栈{Pop();strcpy(Ecode[Top++],"id");strcpy(Ecode[Top++],"=");strcpy(Ecode[Top++],"id");}void Pop() //出栈操作{Top=Top-1;}int firstset(){ if(strcmp(Token2[3].b,">")==0)return 1;if(strcmp(Token2[3].b,"=")==0)return 2;if(strcmp(Token2[3].b,"<")==0)return 3; }int NEStrcmp() //识别非终结符{ if(strcmp(Ecode[Top-1],"S")==0)return 1;else if(strcmp(Ecode[Top-1],"E")==0)return 2;else if(strcmp(Ecode[Top-1],"A")==0)return 3;elsereturn 4;}int EStrcmp() //识别while和do关键字{if(strcmp(Ecode[Top-1],"#")==0){ if(strcmp(Token[nID].b,"#")==0)return 3;}else if(strcmp(Ecode[Top-1],Token[nID].b)==0){ if(strcmp(Ecode[Top-1],"while")==0){ L[nL].add=nadd++;L[nL].next=nadd++;strcpy(L[nL].str ,"while");nL++; }if(strcmp(Ecode[Top-1],"do")==0){L[nL].add=nadd++;L[nL].next=L[nL-1].add;strcpy(L[nL].str ,"do");nL++; }return 1; }elsereturn 0;cout<<Token[2].b<<endl;}/*……………………………语义分析…………………………………*/ void main(){cout<<"*******编译原理课程设计:****************"<<endl<<endl;cout<<"***while语句的翻译分析,采用LL(1)法,输出三地址:*************"<<endl<<endl;cout<<"***程序所用的文法为:****************************"<<endl;cout<<" S->while E do A"<<endl;cout<<" E->id>id|E->id=id|E->id<id"<<endl;cout<<" A->id=id"<<endl;cout<<" (id代表标识符)"<<endl;if(Wordanalyze()) //词法分析成功{if(ExpressionAnalyse()) //语法分析也成功{int i;L[nL].add=nadd;for(i=0;i<nL;i++){if(strcmp(L[i].str,"while")==0){ cout<<endl;cout<<"正确的语义输出为:"<<endl;cout<<"L0: if "<<Token2[2].b<<" > "<<Token2[4].b<<" goto L2"<<endl;cout<<"L1: if not goto L4"<<endl; }else{cout<<"L2: "<<Token2[6].b<<":="<<Token2[8].b<<endl;cout<<"L3: "<<"goto L0"<<endl;cout<<"L4: "<<endl;}}}}}。
C++中头文件相互包含的几点问题一、类嵌套的疑问C++头文件重复包含实在是一个令人头痛的问题,前一段时间在做一个简单的数据结构演示程序的时候,不只一次的遇到这种问题。
假设我们有两个类A和B,分别定义在各自的有文件A.h和B.h 中,但是在A中要用到B,B中也要用到A,但是这样的写法当然是错误的:class B;class A{public:B b;};class B{public:A a;};因为在A对象中要开辟一块属于B的空间,而B中又有A的空间,是一个逻辑错误,无法实现的。
在这里我们只需要把其中的一个A类中的B类型成员改成指针形式就可以避免这个无限延伸的怪圈了。
为什么要更改A而不是B?因为就算你在B中做了类似的动作,也仍然会编译错误,表面上这仅仅上一个先后顺序的问题。
为什么会这样呢?因为C++编译器自上而下编译源文件的时候,对每一个数据的定义,总是需要知道定义的数据的类型的大小。
在预先声明语句class B;之后,编译器已经知道B是一个类,但是其中的数据却是未知的,因此B类型的大小也不知道。
这样就造成了编译失败,VC++6.0下会得到如下编译错误:error C2079: 'b' uses undefined class 'B'将A中的b更改为B指针类型之后,由于在特定的平台上,指针所占的空间是一定的(在Win32平台上是4字节),这样可以通过编译。
二、不同头文件中的类的嵌套在实际编程中,不同的类一般是放在不同的相互独立的头文件中的,这样两个类在相互引用时又会有不一样的问题。
重复编译是问题出现的根本原因。
为了保证头文件仅被编译一次,在C++中常用的办法是使用条件编译命令。
在头文件中我们常常会看到以下语句段(以VC++6.0自动生成的头文件为例):#if !defined(AFX_STACK_H__1F725F28_AF9E_4BEB_8560_67813900AE6B__INCLUDE D_)#define AFX_STACK_H__1F725F28_AF9E_4BEB_8560_67813900AE6B__INCLUDED_ //很多语句……#endif其中首句#if !defined也经常做#ifndef,作用相同。
VSCode .h 头文件的规则一、引言在使用VSCode进行C/C++开发时,头文件(.h文件)作为程序的重要组成部分,其规范和规则对于代码的结构和可读性至关重要。
本文将介绍VSCode .h头文件的规则,以帮助开发者编写清晰、规范的头文件。
二、文件命名规范1. 头文件应该以.h为扩展名,例如:example.h。
2. 头文件的命名应能清晰反映其所包含的功能或内容,应使用全小写字母,并使用下划线分隔单词,例如:math_functions.h。
三、头文件保护1. 头文件应该使用预编译指令来防止重复包含,以避免编译错误。
2. 可以使用 #ifndef、#define 和 #endif 三个预编译指令来包含整个头文件,例如:```c#ifndef EXAMPLE_H#define EXAMPLE_H// 头文件内容#endif // EXAMPLE_H```四、包含必要的头文件1. 头文件应该包含其所依赖的其他头文件,以确保程序的正确编译和运行。
2. 在包含其他头文件时,应该使用相对路径或绝对路径,以避免混乱和错误的引用。
五、避免定义全局变量和函数1. 头文件应该只包含类型定义、常量定义、函数声明或模板声明,不应该包含全局变量或函数定义。
2. 如果需要定义全局变量或函数,应该在对应的源文件中进行定义,而不是在头文件中。
六、使用 #pragma once1. 为了避免使用传统的宏方式保护头文件的方法,可以使用 #pragma once 指令作为替代。
2. #pragma once 可以确保同一个头文件内容不会被多次包含,避免了传统宏方式可能存在的问题。
七、头文件的内容1. 头文件应该包含适当的注释,以说明头文件的作用、包含的内容和使用方法。
2. 头文件中的类型定义、常量定义、函数声明等应该按照一定的结构组织,以增加可读性和易用性。
八、头文件的优化1. 尽量减少头文件的包含内容,只包含必要的内容。
2. 减少对其他头文件的依赖,可减少编译时间和提高代码的可移植性。
全局变量可不可以定义在可被多个.C文件包含的头文件中?为什么?可以,在不同的C文件中以static形式来声明同名全局变量。
头文件中不可以直接定义变量和函数,但是可以定义static变量,类。
extern 用法,全局变量与头文件(重复定义)用#include可以包含其他头文件中变量、函数的声明,为什么还要extern关键字,如果我想引用一个全局变量或函数a,我只要直接在源文件中包含#include<xxx.h> (xxx.h包含了a的声明)不就可以了么,为什么还要用extern 呢??这个问题一直也是似是而非的困扰着我许多年了,今天上网狠狠查了一下总算小有所获了:头文件首先说下头文件,其实头文件对计算机而言没什么作用,她只是在预编译时在#include的地方展开一下,没别的意义了,其实头文件主要是给别人看的。
我做过一个实验,将头文件的后缀改成xxx.txt,然后在引用该头文件的地方用#include"xxx.txt"编译,链接都很顺利的过去了,由此可知,头文件仅仅为阅读代码作用,没其他的作用了!不管是C还是C++,你把你的函数,变量或者结构体,类啥的放在你的.c或者.cpp 文件里。
然后编译成lib,dll,obj,.o等等,然后别人用的时候最基本的gcc hisfile.cpp yourfile.o|obj|dll|lib 等等。
但对于我们程序员而言,他们怎么知道你的lib,dll...里面到底有什么东西?要看你的头文件。
你的头文件就是对用户的说明。
函数,参数,各种各样的接口的说明。
那既然是说明,那么头文件里面放的自然就是关于函数,变量,类的“声明”了。
记着,是“声明”,不是“定义”。
那么,我假设大家知道声明和定义的区别。
所以,最好不要傻嘻嘻的在头文件里定义什么东西。
比如全局变量:#ifndef _XX_头文件.H#define _XX_头文件.Hint A;#endif那么,很糟糕的是,这里的int A是个全局变量的定义,所以如果这个头文件被多次引用的话,你的A会被重复定义显然语法上错了。
c语⾔头⽂件中定义全局变量的问题问题是这么开始的:最近在看⼀个PHP的扩展源码,编译的时候的遇到⼀个问题:ld: 1 duplicate symbol for architecture x86_64仔细看了⼀下源码,发现在头⽂件中出现了全局变量的定义。
简化⼀下后,可以这么理解:// t1.h#ifndef T1_H#define T1_Hint a = 0;#endif//------------------//t1.c#include "t1.h"#include "t2.h"int main(){return 0;}//-----------------//t2.h#include "t1.h"//empty//----------------//t2.c#include "t2.h"//empty//-------这两个c⽂件能否通过编译?想必有点经验的必会说不会,重定义了。
那么是否真的如此?并不这么简单。
第⼀个问题,#ifndef 的这个宏是否防⽌了重定义(redefinition)?答案:是。
但是是在单个translation unit中(wiki )。
#ifndef 的头⽂件宏称为 include guards的()我们知道,⼀个完整的编译的过程是经过one.c --> PREPROCESSOR -> tmp.c(temporary) -> COMPILER -> one.obj -> LINKER -> one.exe这三个过程的,⽽在预编译阶段,便会把include的⽂件展开,我们使⽤cc -E 命令来查看t1.c的预编译的结果:➜ t cc -E t1.c# 1 "t1.c"# 1 "<built-in>" 1# 1 "<built-in>" 3# 321 "<built-in>" 3# 1 "<command line>" 1# 1 "<built-in>" 2# 1 "t1.c" 2# 1 "./t1.h" 1int a = 0;# 3 "t1.c" 2# 1 "./t2.h" 1# 4 "t1.c" 2int main(void){return 0;}看到编译器把 t1.h 做了展开,我们看到了 a的定义。
c语言中头文件和源文件解析编译原理头文件和源文件是C语言中常见的两种文件类型,它们在编译原理中扮演着重要的角色。
本文将对头文件和源文件进行解析,从编译原理的角度探讨它们的作用和使用方法。
一、头文件的概念和作用头文件是一种特殊的文件,它通常以.h作为文件扩展名,用于存放函数声明、宏定义、结构体声明等内容。
头文件的作用主要有以下几个方面:1.1 提供接口声明头文件中包含了函数的声明,通过包含头文件可以让源文件知道这些函数的存在,并且能够正确地调用这些函数。
这种方式可以提高代码的可读性和可维护性,使得不同的源文件可以共享同一个函数的实现。
1.2 定义常量和宏头文件中可以定义常量和宏,这些常量和宏可以被多个源文件引用和使用。
通过在头文件中定义常量和宏,可以提高代码的可重用性和可维护性,避免了在多个源文件中重复定义常量和宏的问题。
1.3 声明结构体和类型头文件中可以声明结构体和类型,这些结构体和类型可以被多个源文件引用和使用。
通过在头文件中声明结构体和类型,可以提高代码的可读性和可维护性,避免了在多个源文件中重复声明结构体和类型的问题。
二、源文件的概念和作用源文件是C语言程序的主要组成部分,它通常以.c作为文件扩展名,包含了具体的函数实现和全局变量定义等内容。
源文件的作用主要有以下几个方面:2.1 实现函数的定义源文件中包含了函数的具体实现,通过编译和链接的过程,可以将函数的定义和函数的调用联系起来。
源文件中的函数实现可以直接访问和修改全局变量,同时也可以调用其他源文件中的函数。
2.2 定义全局变量源文件中可以定义全局变量,这些全局变量可以被多个函数访问和修改。
全局变量在程序的整个执行过程中都是存在的,它们的作用域不限于某个函数,可以在不同的函数之间共享数据。
2.3 包含头文件源文件可以通过包含头文件来使用头文件中定义的函数、常量、宏、结构体和类型等。
通过包含头文件,源文件可以获取到头文件中的声明信息,从而可以正确地使用其中定义的内容。
头文件包含问题
C++中基础类互相引用带来的问题
在一些大的工程中,可能会包含几十个基础类,免不了之间会互相引用( 不满足继承关系,而是组合关系) 。
也就是需要互相声明。
好了,这时候会带来一些混乱。
如果处理得不好,会搞得一团糟,根据我的经验,简单谈谈自已的处理办法:
编码时,我们一般会尽量避免include 头文件,而是采用声明class XXX 。
但有时候还是必须用Include 头文件,那么,两者的划分在于什么呢?
应该是很明确的,但书上好像都少有提及。
首先:
我们要明白为什么要用声明取代头文件包含:对了,是为了避免无必要的重编译( 在头文件发生变更时) 。
工程较大,低速机,或基础类经常变更( 不合理的设计吧) ,编译速度还是会在意的,另外,更为重要的是,采用声明可降低代码(class) 之间的藕合度,这也是面向对象设计的一大原则。
二:一般原则:
a. 头文件中尽量少include, 如果可以简单申明class clsOld; 解决,那最好。
减少没有必要的include;
b. 实现文件中也要尽量少include, 不要include 没有用到的头文件。
三:那什么时候可以只是简单声明class clsOld 呢?
简单的说:不需要知道clsOld 的内存布局的用法都可以( 静态成员除外) ,也就是讲如果是指针或引用方式的都行。
比如:
clsOld * m_pOld; // 指针占4 个字节长
clsOld & test(clsOld * pOld) {return *pOld};
一切OK 。
四:什么时候不能简单声明class clsOld ,必须include 呢?
不满足三的情况下:
比如:
clsOld m_Objold; // 不知道占据大小,必须要通过它的具体声明来计算
原因很简单,想想你要计算sizeof(classNew) ,但连clsOld 的size 都不知道,编译器显然会无能为力。
特殊情况:
int test() { return clsOld::m_sInt;}
静态成员调用,想来应该是不需要知道内存布局的,但因为需要知道m_sInt 是属于clsOld 命名空间的,如果只声明class xxx 显然是不足以说明的,所以必须包含头文件。
综上所述,我有以下几点建议:
1 :如果有共同相关依赖( 必须include) 的类,比如A,B 都依赖D 可以放在一起,然后直接Include "d" 类的使用者只需关心与本类暴露出的相关类型,内部用到的类型不用去管( 不用自已去include d) 。
这样给出的class ,调用者才更好用( 不用去看代码查找,是不是还需要包含其它头文件) 。
2 :如果A 类依赖D B 类不依赖D ,可以把它们分开两个头文件。
各自Incl ude 。
这样可避免当D 发生变化时,避免不必要重编译。
3 :类中尽量采用指针或引用方式调用其它类,这样就可以只声明class xxx 了。
并且这也符合资源最优利用,更利于使用多态。
/--------------------------------------------------///////////////////////// ////////////////////////////////////////////////////
对于VC++中的头文件包含值得注意的一点
今天用VC++编译我这昨天写的代码时发现总是通不过,看到大部分的错误都是与一个类的定义有关。
明明我已经在这个类中定义了一个成员变量,可编译器偏要说那个变量不是这个类的成员。
没办法,找了半天原因还是没有头绪。
再三地确认不是我C++的语法错误之后。
我开始怀疑是不是头文件的问题。
因为当我把一些#include "... "搬到#pragma once之前或者之后,错误报告就会发生变化。
有时就只是说我的一个类重复定义了。
以前用C写头文件的时候都是用宏定义来避免重复包含头文件。
C++里虽然也还可用这个方法,但VC++就是不用,它用的是#pragma once。
这一变化对我来说真是有点摸不着头脑了,我不清楚#pragma once的工作方式是如何的。
而我现在遇到的问题又与这个有关。
找找网上的内容看吧。
上google搜了半天,那些论坛里的回答基本上都是“防止重复包含头文件的,你不用管他。
”这样的。
可是不管他真的行吗?我刚学的VC++,我看的书上是没有讲过这个内容的。
怎么在网上也搜不到呢?经过我不懈的努力,终于让我找到了一篇http://www.ye /127/1736627_3.shtml。
作者Adding的这段话对我的帮助最大:“既然使用了包含文件,为什么还要在class CMainFrame前添加"class CViewerView;"等代码?如果用包含文件代替它,行不行?很多Visual C++书籍对这些问题避而不谈,但实际上这是一个重要的问题。
如果不能理解上述代码,我们很可能为无法通过编译而大伤脑筋。
这些问题的出现是基于这样的一些事实:在我们用标准C/C++设计程序时,有一个原则即两个代码文件不能相互包含,而且多次包含还会造成重复定义的错误。
为了解决这个难题, Visual C++使用#pragma once来通知编译器在生成时
只包含(打开)一次,也就是说,在第一次#include之后,编译器重新生成时不会再对这些包含文件进行包含(打开)和读取,因此我们看到在用向导创建的所有类的头文件中有#pragma once语句就不会觉得奇怪了。
然而正是由于这个语句而造成了在第二次#include后编译器无法正确识别所引用的类。
因此,我们在相互包含时还需要加入类似class CViewerView这样的语句来通知编译器这个类是一个实际的调用。
”
看来就是这个问题了。
把我的代码一加上那些类的声明以后果然通过了编译。
我感觉这个问题在VC++里面来说应该是值得注意的。
可能是由于我刚学VC ++,见识太少的原故吧。
无论如何先记下来,说不定也有人在为这个#pragma o nce伤脑筋呢。