1.多文件结构和编译预处理命令
C++程序的一般组织结构:
一个工程可以划分为多个源文件,例如:
类声明文件(.h文件);类实现文件(.cpp文件);类的使用文件(main()所在的cpp文件)
利用工程来组合各个文件。
系统提供的类库头文件一般不带h的,我们自定义的通常带后缀h的。
系统提供的头文件我们引用使用<>括起来,我们自己写的头文件引用使用""括起来。
用<>括起来的这样的头文件会直接去安装时的默认目录下寻找,用""括起来的头文件会首先在当前工作目录下寻找,如果找不到,才会到系统默认的目录下去找。
举例,之前的一段代码如下:
#include <iostream>
#include <cmath>
using namespace std;
class Point
{
public://外部接口
Point(int x=0,int y=0):x(x),y(y){}
int getX() {return x;}
int getY() {return y;}
friend float dist(const Point &p1,const Point &p2);
private:
int x,y;//私有数据成员
};
float dist(const Point &p1,const Point &p2)
{
double x=p1.x-p2.x;
double y=p1.y-p2.y;
return static_cast<float>(sqrt(x*x+y*y));
}
int main()
{
const Point myp1(1,1),myp2(4,5);
cout<<"The distance is:";
cout<<dist(myp1,myp2)<<endl;
return 0;
}
将其分为三部分:Point.h、Point.cpp、5_10.cpp
Point.h:
class Point
{
public://外部接口
Point(int x=0,int y=0):x(x),y(y){}
int getX() {return x;}
int getY() {return y;}
friend float dist(const Point &p1,const Point &p2);
private:
int x,y;//私有数据成员
};
Point.cpp:
#include <iostream>
#include <cmath>
#include "Point.h"
using namespace std;
float dist(const Point &p1,const Point &p2)
{
double x=p1.x-p2.x;
double y=p1.y-p2.y;
return static_cast<float>(sqrt(x*x+y*y));
}
5_10.cpp:
#include <iostream>
#include <cmath>
#include "Point.h"
using namespace std;
int main()
{
const Point myp1(1,1),myp2(4,5);
cout<<"The distance is:";
cout<<dist(myp1,myp2)<<endl;
return 0;
}
编译连接过程:
Point.cpp 和5_10.cpp 都包含了Point.h,这两个cpp文件分别进行编译,编译以后各自形成后缀为obj的的文件,这是编译过程。编译完后不能执行,不是可执行程序,要生成可执行程序需要经过连接过程,这个连接过程是将多个obj文件连接在一起。除此之外我们还调用了系统库中的功能,我们还需要将系统库中的相关内容也连接在一起。最后形成一个可执行文件。
在如今的可视化编程过程中看不到这些过程,只需要直接build。
2.外部变量:不在本程序文件中定义的变量。
除了在定义它的源文件中可以使用外,还能被其他文件使用。
文件作用域中定义的变量,默认情况下可以作为外部变量。
在其他文件中如果需要使用一个在别处定义的、在同一个工程中定义的变量,需要用extern关键字声明。
3.外部函数:
在所有类之外声明的函数(也就是非成员函数),都是具有文件作用域的。
这样的函数都可以在不同的编程单元中被调用。
只要在调用前进行引用性声明(即声明函数原型)即可。
4.将变量和函数限制在编译单元内:
在匿名命名空间中定义的变量和函数,都不会暴露给其他的编译单元。
namespace
{
int n;
void f()
{ n++; }
}
5.标准程序库:
标准C++类库是一个极为灵活并可扩展的可重用软件模块的集合。标准C++类与组件在逻辑上分为6种类型:
输入/输出类
容器类与抽象数据类型
存储管理类
算法
错误处理
运行环境支持
6.编译预处理:
#include包含指令:将一个源文件嵌入到当前源文件中该点处。
#include<文件名>
按标准方式搜索,文件位于C++系统目录的include子目录下。
#include"文件名"
首先在当前目录中搜索,若没有,再按标准方式搜索。
#define宏定义指令:
定义符号常量,很多情况下已被const定义语句取代。
定义带参数宏,已被内联函数取代。
#undef:
删除由#define定义的宏,使之不再起作用。
7.条件编译指令
条件编译指令——#if和#endif
#if 常量表达式
//当“常量表达式”非零时编译
程序正文
#endif
...
条件编译指令——#else
#if 常量表达式
//当“常量表达式”非零时编译
程序正文1
#else
//当“常量表达式”为零时编译
程序正文2
#endif
...
条件编译指令——#elif
#if 常量表达式
//当“常量表达式”非零时编译
程序正文1
#elif
//当“常量表达式”非零时编译
程序正文2
#else
//其他情况下编译
程序正文3
#endif
...
条件编译指令——#ifdef
#ifdef 标识符
程序段1
#else
程序段2
#endif
如果“标识符”经#define定义过,且未经#undef删除,则编译程序段1;否则编译程序段2
所谓被定义过,其实就是通过#define命令去定义过标识符,但是我们并不指定定义值是什么。只要标识符在#define中定义过,就说明我们竖起了一个标签,一个记号,按这个记号所规定的意义,我们应该去编译程序段1,否则编译程序段2
条件编译指令——#ifndef
#ifndef 标识符
程序段1
#else
程序段2
#endif
如果“标识符”未被定义过,则编译程序段1;否则编译程序段2
我们用这样的条件编译,可以在头文件中设置一个标识符,如果没有被定义过这个标识符,则编译某一段程序,同时马上把这个标识符定义一下,如果这个程序被多次包含的话,当第二次包含的时候,他就发现标识符已经被定义过了,那么重复的程序段就不会被编译了,这样就避免多次重复include同一个头文件的时候,造成一些类、函数、常量被重复定义的问题。
实验五:
客户机类:
client.h:
#ifndef ClIENT_H_
#define ClIENT_H_
class Client
{
static char ServerName;
static int ClientNum;
public:
static void ChangeServerName(char name);
static int getClientNum();
};
#endif // CLIENT_H_
client.cpp:
#include "client.h"
void Client::ChangeServerName(char name)
{
Client::ServerName=name;//静态数据成员的引用注意加
//“类名::”来修饰
Client::ClientNum++;
}
int Client::getClientNum()
{
return Client::ClientNum;
}
main.cpp:
#include <iostream>
#include "client.h"
using namespace std;
int Client::ClientNum=0;//静态数据成员需要在类外进行单独的初始化
char Client::ServerName='a';
int main()
{
Client c1;
c1.ChangeServerName('a');
cout<<c1.getClientNum()<<endl;
Client c2;
c2.ChangeServerName('b');
cout<<c2.getClientNum()<<endl;
return 0;
}