扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
比如说有文件 test.h
创新互联建站专业为企业提供陇川网站建设、陇川做网站、陇川网站设计、陇川网站制作等企业网站建设、网页设计与制作、陇川企业网站模板建站服务,十载陇川做网站经验,不只是建网站,更提供有价值的思路和整体网络服务。
里面包含有一个函数声明void fun(int n);
在另一个文件test.c里面加上#include " test.h"
并在test.c里面实现函数
void fun (int n)
{
printf("%d\n", n);
}
假设主函数main()。其他函数void fun(int a,int *b)。
关于函数定义和声明:
在代码中fun函数,有完整函数体的代码就是函数定义部分,比如void fun(int a,int *b){。。。};。
如果没有具体实现的函数体代码(也就是大括号内容),那么就是函数声明。比如void fun(int ,int *);
关于函数声明和调用:
如果fun函数定义在main代码上面。比如:
void fun(int a,int *b){。。。};
int main(){
int a=0,int b=0;
......
fun(a,b);//这里fun定义代码在main上面,所以可以不需要声明。反之需要先写声明语句
return 0;
}
关于函数局部变量及全局变量:
简单区分:
定义在函数之外的就是全局变量,这个变量所有函数都可以直接使用,并且共用同一个地址。任意函数修改了变量值,其他函数调用也会变。
定义在函数之内的就是局部变量,局部变量只属于该函数,其他函数即使定义了同样名字的变量,这两个变量也不同地址,互不相干。
比如:
int a;//这就是全局变量,作用域下所有函数共用
int main()
{
int b;//这就是局部变量,只在本函数有效,如果想在调用fun函数时让fun也使用该变量,需要把这个变量的地址作为参数传递过去。
printf("%d",b);
return 0;
}
注意:c语言中允许局部变量和全局变量同名,但是同名的局部变量会屏蔽全局变量,实际代码避免同名。
关于函数传值和传址:
如函数void fun(int a,int *b){..........};这里形参a 和*b,分别表示一个值和一个地址
所以在调用该函数时,比如:
int main()
{
int c,d;
fun(c,d);//这里调用就对应上面形参类型,第一个参数传递了c的值(传值),第二个参数传递了d的地址(传址),这里c和d,叫做实参。当fun函数运行时改变了a和b指向地址的值,对应main函数中c的值不变,d的值改变。
return 0;
}
顺带一说:局部变量,在函数运行结束后会自动释放,所以想把局部变量地址作为返回值,需要用malloc函数申请。(这里看不懂可以暂时忽视)
比如,a.c与文件夹b放在同一目录,c.h放在文件夹b里,那么a.c可以这么包含c.h:
#include"b\c.h"
反过来,如果和a.c在同一目录下还有d.h,c.h想要引用d.h
#include"..\d.h"
C++编程入门系列之二十四(C++程序设计必知:多文件结构和编译预处理命令)分类标签: C++ 编程入门 -
鸡啄米上一讲给大家讲了常引用、常对象和对象的常成员,今天给大家讲下编程入门知识--多文件结构和编译预处理命令。
一.C++程序的多文件结构
之前鸡啄米给大家看了很多比较完整的C++程序的例子,大家可能发现了,它们的结构基本上可以分为三个部分:类的声明、类的成员函数的实现和主函数。因为代码比较少,所以可以把它们写在一个文件中,但是我们实际进行软件开发时,程序会比较复杂,代码量比较大,
一个程序按结构至少可以划分为三个文件:类的声明文件(*.h文件)、类的实现文件(*.cpp文件)和主函数文件(使用到类的文件),如果程序更复杂,我们会为每个类单独建一个声明文件和一个实现文件。这样我们要修改某个类时就直接找到它的文件修改即可,不需要其他的文件改动。
鸡啄米在第十九讲中讲生存期时有个时钟类的例子,现在鸡啄米给大家看下将那个程序按照上面说的结构分到三个文件里:
// 文件1:Clock类的声明,可以起名为Clock.h
#include iostream
using namespace std;
class Clock //时钟类声明
{
public: //外部接口
Clock();
void SetTime(int NewH, int NewM, int NewS); //三个形参均具有函数原型作用域
void ShowTime();
~Clock(){}
private: //私有数据成员
int Hour,Minute,Second;
};
// 文件2:Clock类的实现,可以起名为Clock.cpp
#include "Clock.h"
//时钟类成员函数实现
Clock::Clock() //构造函数
{
Hour=0;
Minute=0;
Second=0;
}
void Clock::SetTime(int NewH,int NewM,int NewS)
{
Hour=NewH;
Minute=NewM;
Second=NewS;
}
void Clock::ShowTime()
{
coutHour":"Minute":"Secondendl;
}
// 文件3:主函数,可以起名为main.cpp
#include "Clock.h"
//声明全局对象g_Clock,具有文件作用域,静态生存期
Clock g_Clock;
int main() //主函数
{
cout"文件作用域的时钟类对象:"endl;
//引用具有文件作用域的对象:
g_Clock.ShowTime();
g_Clock.SetTime(10,20,30);
Clock myClock(g_Clock); //声明具有块作用域的对象myClock,并通过默认拷贝构造函数用g_Clock初始化myClock
cout"块作用域的时钟类对象:"endl;
myClock.ShowTime(); //引用具有块作用域的对象
}
在vs2010中如何生成这三个文件呢?我们可以点菜单中Project-Add Class,在弹出的对话框中选择c++ class,然后由弹出个对话框,在class name处填上类名点finish就可以了,这样.h文件和.cpp文件会自动生成,我们也可以点Project-Add New Item,在弹出的对话框中选择Header File(.h)或C++ File(.cpp)来生成.h文件或.cpp文件。
Clock.cpp和main.cpp都使用#include "Clock.h"把类Clock的头文件Clock.h包含进来。#include指令的作用就是将#include后面的文件嵌入到当前源文件该点处,被嵌入的文件可以是.h文件也可以是.cpp文件。如果不包含Clock.h,Clock.cpp和main.cpp就不知道Clock类的声明形式,就无法使用此类,所以所有使用此类的文件都应该包含声明它的头文件。关于#include指令下面鸡啄米会讲。
上面的程序在编译时,由Clock.cpp和Clock.h编译生成Clock.obj,由main.cpp和Clock.h编译生成main.obj,然后就是链接过程,Clock.obj和main.obj链接生成main.exe可执行文件。如果我们只修改了类的实现文件,那么只需重新编译Clock.cpp并链接就可以,别的文件不用管,这样就提高了效率。在Windows系统中的C++程序用工程来管理多文件结构,而Unix系统一般用make工具管理,如果大家从事Unix系统软件开发,就需要自己写make文件。
二.编译预处理程序
编译器在编译源程序以前,要由预处理程序对源程序文件进行预处理。预处理程序提供了一些编译预处理指令和预处理操作符。预处理指令都要由“#”开头,每个预处理指令必须单独占一行,而且不能用分号结束,可以出现在程序文件中的任何位置。
1.#include指令
#include指令也叫文件包含指令,用来将另一个源文件的内容嵌入到当前源文件该点处。其实我们一般就用此指令来包含头文件。#include指令有两种写法:
#include 文件名
使用这种写法时,会在C++安装目录的include子目录下寻找中标明的文件,通常叫做按标准方式搜索。
#include "文件名"
使用这种写法时,会先在当前目录也就是当前工程的目录中寻找""中标明的文件,若没有找到,则按标准方式搜索。
2.#define和#undef指令
如果你学过C语言,就会知道用#define可以定义符号常量,比如,#define PI 3.14 这条指令定义了一个符号常量PI,它的值是3.14。C++也可以这样定义符号常量,但一般更常用的是在声明时用const关键字修饰。C语言还用#define定义参数宏,来实现简单的函数运算,比如,#define add(x,y) (x+y) 这条指令说明如果我们用到add(1,2)则预处理后就会用(1+2)代替,C++中一般用内联函数来实现。
#undef用来删除由#define定义的宏,使其不再起作用。
3.条件编译指令
用条件编译指令可以实现某些代码在满足一定条件时才会参与编译,这样我们可以利用条件编译指令将同一个程序在不同的编译条件下生成不同的目标代码。例如,我们可以在调试程序时加入一些调试语句,用条件编译指令控制只有在debug模式下这些调试语句才参与编译,而在release模式下不参与编译。
条件编译指令有5中形式:
a.第一种形式:
#if 常量表达式
程序正文 //当“ 常量表达式”非零时本程序段参与编译
#endif
b.第二种形式:
#if 常量表达式
程序正文1 //当“ 常量表达式”非零时本程序段参与编译
#else
程序正文2 //当“ 常量表达式”为零时本程序段参与编译
#endif
c.第三种形式:
#if 常量表达式1
程序正文1 //当“ 常量表达式1”非零时本程序段参与编译
elif 常量表达式2
程序正文2 //当“常量表达式1”为零、“ 常量表达式2”非零时本程序段参与编译
...
elif 常量表达式n
程序正文n //当“常量表达式1”、...、“常量表达式n-1”均为零、“ 常量表达式n”非零时本程序段参与编译
#else
程序正文n+1 //其他情况下本程序段参与编译
#endif
d.第四种形式:
#ifdef 标识符
程序段1
#else
程序段2
#endif
如果“标识符”经#defined定义过,且未经undef删除,则编译程序段1,否则编译程序段2。
e.第五种形式:
#ifndef 标识符
程序段1
#else
程序段2
#endif
如果“标识符”未被定义过,则编译程序段1,否则编译程序段2。
4.define操作符
define是预处理操作符,不是指令,所以不能用#开头。使用形式为:define(标识符)。如果括号里的标识符用#define定义过,并且没有用#undef删除,则define(标识符)为非0,否则为0。可以这样使用:
#if !define(HEAD_H)
#define HEAD_H
我们在包含头文件时,有时多次重复包含同一个头文件,比如下面这种情况:
// main.cpp文件
#include "file1.h"
#include "file2.h"
int main()
{
…
}
// file1.h文件
#include "head.h"
…
// file2.h文件
#include "head.h"
…
// head.h文件
...
class A
{
...
}
...
main.cpp包含了file1.h文件,file1.h又包含了head.h文件,main.cpp还包含了file2.h文件,file2.h也包含了head.h文件,那么main.cpp就包含了两次head.h文件,在编译时就会报错,说head.h中的类A重复定义了。这时我们可以在被重复包含的文件head.h中使用条件编译指令,用一个唯一的标识符来标识head.h文件是否已经编译过,如果已经编译过则不会重复编译了。鸡啄米给大家改写下上面的head.h文件:
// head.h文件
#ifndef HEAD_H
#define HEAD_H
...
class A
{
...
}
...
#endif
在这个改好的head.h文件中,上来会先判断HEAD_H是否被定义过,如果没有被定义过,则head.h文件还没参与过编译,就编译此文件中的源代码,同时定义HEAD_H,标记head.h文件已经参与过编译。如果HEAD_H已经被定义过,则说明此文件已经参与过编译,编译器会跳过本文件左右内容编译其他部分,类A也不会有重复定义的错误了。
在调用的函数前定义函数,此时可以不需要声明。
2.在调用的函数前声明。
3.在调用的函数里面也可以声明。
4.在其他文件的头文件*.h文件里面声明,然后*.c文件直接调用头文件也可以。
把子函数放主函数MAIN函数外并且是之前定义,无需声明。
2.如果你把子函数放到MAIN函数外,则必须在定义的时候先声明。
3.如果把 子函数放在MAIN函数中就不需要提前声明了。
4.声明一般告诉编译器有这个对象。可这个'有'字就有2层意思,第一层意思是这个对象在其他地方已经定义过,比如用extern来声明就有这个意思,因为这个对象已经有了这个时候不会对它分配内存。第二层含义就是这个对象以前没有是我现在定义的(现在有了以前没有)这个时候声明又可以称为定义他们是重叠的,这个时候给这个对象分配内存。所以可以看出在有定义的地方一定有声明,有声明的地方不一定就有定义。定义也是声明,这是很多人都不了解的地方
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流