代码钢琴家 阅读(120) 评论(0)

 

C++遵循先定义,后使用的原则。就拿函数的使用来举例吧。

我看过有些人喜欢这样写函数。

#include<iostream>
using namespace std;

int add(int x, int y)    //A
{
    return x + y;
}

int main()
{
    
    int re = add(1, 2);   //B

    system("pause");
    return 0;
}

 

但我更偏向下面这种。

#include<iostream>
using namespace std;

int add(int x, int y);    //A

int main()
{
    
    int re = add(1, 2);    //B

    system("pause");
    return 0;
}

int add(int x, int y)      //C
{
    return x + y;
}

 

 

C++的编译是以文件为单位,在某一个特定源文件中,则是从上至下,逐行解析的。

第一种风格中,A处的代码既是函数的定义(函数的实现),也充当了函数的声明。函数的定义是函数正真的实体,和逻辑实现。而声明则是告知编译器:我这个函数存在,我这个函数外观是什么样的。

当编译器分析到B处代码时,编译器已经知道了函数存在,则允许出现这个函数的调用,知道了函数的外观,编译器还会分析函数调用时是否正确使用了,如参数个数,参数类型等。

这个过程中拆解开来就是:定义 , 声明 , 使用。显然第二种风格更好的阐述了这三部分。

 

那么问题来了?当项目过大后,实体(函数定义,类,结构体,变量定义等)会放在不同的文件中。但是编译器又是以文件为单位处理的,它在处理main.cpp时,完全不知道lib.cpp的任何信息。

那么,如果要在main.cpp中调用lib.cpp中定义的函数,就必须手动将lib.cpp中的函数头拷贝声明到main.cpp中。那如果在100个源文件中调用了lib.cpp中的函数,岂不是要拷贝100份?

这样累不说,又容易出错,还不利于后期维护。

于是预处理器说:“你只需将lib.cpp中的需要共享,在其他源文件中使用的实体单独声明到一个头文件lib.h中吧,别的源文件需要使用你的lib.cpp中的实体,只需要在他们的源文件中加上一行预处理指令:#include"lib.h"    就OK了,剩下事交给我”

于是一切变成了这样:

 

/*lib.cpp*/
#include"lib.h"

int add(int x,int y)
{
    return x+y;
}

 

/*lib.h*/
#ifndef _LIB_H__
#define _LIB_H__
int add(int x, int y);
#endif

 

/*main.cpp*/
#include<iostream> #include"lib.h" using namespace std; int main() { int re = add(1, 2); cout << re << endl; system("pause"); return 0; }

 

那么问题又来了:预处理器它到底是怎么帮你的呢,它做了什么手脚?

下面我们就来用VS2013看看预处理的结果。如何查看预处理结果--->点我

/*lib.i文件*/
int add(int x, int y);

int add(int x,int y)
{
    return x+y;
}

 

/*main.i
前面省略87260行,都是iostream头文件里面的东西,真多!
*/
int add(int x, int y);
using namespace std;


int main()
{
    
    int re = add(1, 2);    
    cout << re << endl;
    system("pause");
    return 0;
}

 

 

总结:

 

1、预处理器在.cpp中遇到#include<> 或者 #include "  ",  都会将#include<> 或者 #include "  "指令替换为他们包含的头文件中的内容,形成 .i文件。

这就是预处理器对头文件的处理结果。当然还要考虑到预处理器也是有逻辑的,比如防止重复包含#ifndef   .......#define .......#endif

2、头文件只在预处理期起作用,预处理过后生成  .i 文件,此后头文件就没有作用了。


如果你不理解C/C++的编译过程,请点 击我

 

以下通过几个特例说明需要注意事项.

 

防止头文件的重复包含

当项目大了后,编译单元之间的关系变得复杂,就很可能出现头文件重复包含的情况。虽然大多是情况下,重复包含头文件是没有问题的,但是这会使.i文件膨胀 。

C/C++遵循单定义,多声明的规则。声明多次没问题,但是不必要的声明除了在利于代码阅读外的目的下使用外,其他的要尽量避免。

一般采用以下方法。

#ifndef _XXX_H__
#define _XXX_H__

//被包含内容放中间

#endif

 

C++提供了#pragma once 预处理指令来达到上述效果,但是很多人习惯了C中的写法,也为了获得更大的兼容性。

 

 

 

 

普通全局变量

有时为了让一个源文件中的全局变量在多个源文件中的共享。

普通全局变量是有外部链接性(多个源文件共享一个实体),也就是它可以在所有的.cpp文件中共享使用。因为它将在所有的源文件中共享,所以必须保证不会在其他源文件的任何地方出现相同的链接性且相同名称的全局变量,正所谓一山不容二虎。

 

 

 

下面是错误的写法,编译不通过,提示错误:error : “int sum”: 重定义

相信如果你明白了头文件和预处理的机制,你就应该知道为什么是错误的了。

 

/*share.cpp*/
#include"share.h"

int sum = 100;
/*share.h*/
#ifndef _SHARE_H__
#define _SHARE_H__
int sum;
#endif
/*main.cpp*/
#include<iostream> #include"share.h" using namespace std; int main() { cout << sum << endl; system("pause"); return 0; }

 

 

贴出代码说良心话。下面就是预处理后的结果。很明显的:全局变量sum的确重复定义了。在share.i 中 重复定义了2次,在main.i中又定义了一次,一共定义了3次!

/* share.i */
int sum;


int sum = 100;
/*main.i
省略iostream 中的代码
*/

int sum;

using namespace std;

int main()
{
    cout << sum << endl;    
    system("pause");
    return 0;
}

 

要保证其他源文件能使用普通全局变量,又不会重定义,就要使用extern关键字来声明。extern用来声明。

改:

/*share.cpp*/
#include"share.h"

int sum = 100;
//extern int sum = 100; 也是OK的。
//这里 的extern是可选的,加上extern 的唯一目的是,暗示这个变量会被其他文件使用
/*share.h*/
#ifndef _SHARE_H__
#define _SHARE_H__
extern int sum;
#endif
/*main.cpp*/
#include<iostream> #include"share.h" using namespace std; int main() { cout << sum << endl; system("pause"); return 0; }

 

 

 

全局static变量

static修饰全局变量的目的就是为了将外部链接性改为内部链接性(仅仅在定义它的文件中共享,对其他文件隐藏自己,定义的实体是源文件私有的)。

这样避免了对外部链接性空间的名称污染,其他源文件完全可以定义具有同名的外部链接的变量,当然也可以定义同名的内部链接变量。

 

 

 

static修饰的全局变量 和 全局函数 都不要在对应模块的头文件中声明,因为static是为了隐藏在一个源文件中,而放在头文件中则是为了方便其他源文件使用,这2者显然矛盾了。

下面我们尝试开发一个对int数组排序的库来说明问题。举一反三靠大家自己了。

 

/*sort.cpp*/

/*
我们使用了2个函数完成排序:1、swap用于交换2个值,bubble_sort则是排序的实现。显然我们只想对其他使用者提供 bubble_sort这一个函数接口,而对外隐藏swap函数。于是将swap修饰为static
*/

#include"sort.h"
static void swap(int &a, int&b)//static 函数仅仅在自己的源文件声明就够了,不要在头文件中声明。
                                       //为什么  需要在自己的源文件中声明呢?假如将下面的 swap函数和bubble_sort函数定义的位置交换下,那么
//编译器在从上往下解析sort.cpp时,会先看见swap在bubble_sort中的调用,而编译器事先不知道swap的任何声明和外观信息。

static void swap(int &a, int&b) { int t = a; a = b; b = t; } void bubble_sort(int arr[], int len) { for (int i = 0; i < len - 1; ++i) { for (int j = 0; j < len - 1 - i; ++j) { if (arr[j]>arr[j + 1]) swap(arr[j], arr[j + 1]); } } }
/*sort.h*/
#ifndef _SORT_H_
#define _SORT_H_
void bubble_sort(int arr[], int len);

#endif

 

 

#include"sort.h"
#include<iostream>

using namespace std;

int main()
{
    int arr[5] = { 12, 6, -12, 44, -90 };

    bubble_sort(arr, 5);

    for (size_t i = 0; i < 5; ++i)
    {
        cout << arr[i] << endl;
    }

    
    system("pause");
    return 0;
}

 

 

 

 

全局const常量

全局const默认是具有内部链接性,就像使用了static修饰后一样。

/*test.cpp*/

const int foo = 12;

//等价于   static const int foo = 12;

 

 

由于const全局常量是内部链接性的,所以我们可以将 const定义放在头文件中,这样所有包含了这个头文件的源文件都有了自己的一组const 定义,由于const为文件内部链接性,所以不会有重定义错误。

 

/*one.cpp*/
#include"one.h"

/*one.h*/
#ifndef _ONE_H__
#define _ONE_H__
const int x = 1;
const int y = 2;

#endif
#include"one.h"

int main()
{
    return 0;
}

 

预处理后

 

/*one.i */

const int x = 1;
const int y = 2;
/*main.i*/
const
int x = 1; const int y = 2; int main() { return 0; }

 

 

你会觉得这样很不经济,如果这个头文件被包含100次,那岂不是在这100个源文件都定义了这组全局常量?而且全局常量的存活期又很长。当然是有解决办法的。那就是使用extern

改:

/*one.cpp*/
#include"one.h"

extern const int x = 1;
extern const int y = 2;
/*one.h*/
#ifndef _ONE_H__
#define _ONE_H__
extern const int x;
extern const int y;

#endif
#include"one.h"

int main()
{
    
    return 0;
}

 

预处理后的文件

/*one.i*/
extern const int x;
extern const int y;


extern const int x = 1;
extern const int y = 2;
/*main.i*/

extern const int x;
extern const int y;

int main()
{
    
    return 0;
}

 

 

 

全局函数

C++类 calss 和 结构体struct  中的成员函数受 OOP封装规则限定。这里只谈全局函数:在自定义名称空间中的,以及在全局(::)名称空间中的函数。

全局函数默认都是有外部链接性的。因此,在一个源文件中定义的函数,其他源文件只要有声明,就可以使用。

也可以将全局函数使用static 修饰,使其在定义它的源文件中私有化。此时它和使用static 修饰了的全局变量一样,会隐藏外部链接中同名的全局函数,也不会引起重名错误。

 

 

 

头文件中放什么?

1、类的声明

2、结构的声明

3、内敛函数的定义 和声明。内敛函数整体都放在头文件中。这样包含它的每个源文件才知道这样展开。

4、模板

5、#define 宏 和  const 常量(C++不建议使用宏)

 

 

 

/***************************************************/

 欢迎转载,请注明出处:www.cnblogs.com/lulipro

 为了获得更好的阅读体验,请访问原博客地址。

 代码钢琴家

/***************************************************/