# 1.文件包含

.h文件中主要是用来声明函数和变量,有时候也可以用来定义函数和变量。

.h中最常见的就是重复包含,看下面例子

文件add.h:

int c = 10;
int add(int &a, int &b);

文件add.cpp:

#include "add.h"
int add(int &a, int &b)
{
     return a + b;
}

文件addA.h:

#include "add.h"
int addA(int a, int b);

文件addA.cpp:

#include "addA.h"
int addA(int a, int b)
{
     return add(a, b);
}

文件addB.h:

#include "add.h"
int addB(int a, int b);

文件addA.cpp:

#include "addB.h"
int addB(int a, int b)
{
     return add(a, b);
}

文件main.cpp:

#include <iostream>

#include "addA.h"
#include "addB.h"
int main(int argc, char **argv)
{
     int a = 1, b = 2;
     std::cout << addA(a, b) << " " << addB(a, b) << std::endl;
     return 0;
}

学过C++的肯定都知道上面代码编译会报错,因为包含add.h两次,导致重复声明了c变量。

g++ main.cpp -o m.o

In file included from addB.h:3,
                 from main.cpp:4:
add.h:3:5: error: redefinition of ‘int c’
    3 | int c = 10;
      |     ^
In file included from addA.h:3,
                 from main.cpp:3:
add.h:3:5: note: ‘int c’ previously defined here
    3 | int c = 10;

这种情况下解决办法最常用的就是在每个头文件中添加宏:

#ifndef __ADD_H__
#define __ADD_H__
...
#endif // __ADD_H__

然后,再编译发现就会发现可以编译成功。

依次将上面四个源文件编译完成:

g++ main.cpp -o m.o
g++ add.cpp -o a.o
g++ addA.cpp -o A.o
g++ addB.cpp -o b.o

链接时会导致报错:

g++ a.o A.o B.o m.o -o m
/usr/bin/ld: A.o:(.data+0x0): multiple definition of `c'; a.o:(.data+0x0): first defined here
/usr/bin/ld: B.o:(.data+0x0): multiple definition of `c'; a.o:(.data+0x0): first defined here
/usr/bin/ld: /usr/lib/gcc/x86_64-linux-gnu/9/../../../x86_64-linux-gnu/Scrt1.o: in function `_start':
(.text+0x24): undefined reference to `main'
collect2: error: ld returned 1 exit status

为什么会出错呢? 按照条件编译,add.h并没有重复包含,可是还是提示变量a重复定义了。 对比第一次报错,不加条件编译在编译时就报错,加了条件编译,编译能通过,但链接时报错。

在这里注意的是,变量/函数/类/结构体的重复定义不仅会发生在源程序编译的时候,在目标程序链接的时候同样也有可能发生。

c/c++编译的基本单元是.c.cpp文件,各个基本单元的编译是相互独立的,#ifndef等条件编译只能保证在一个基本单元(单独的.c.cpp文件)中头文件不会被重复编译,但是无法保证两个或者更多基本单元中相同的头文件不会被重复编译。

头文件尽量只有声明,不要有定义。

解决上面问题的一种办法是,在add.h中定义int c = 10;时加static修饰符,如下:

add.h文件:

#ifndef __ADD_H__
#define __ADD_H__
static int c = 10;
int add(int &a, int &b);
#endif // __ADD_H__

然后,就能编译通过了,static关键字在这里修饰定义的变量时局部变量,只在当前文件中可见。在编译A.oB.o时会在两个编译文件中分别定义变量c,因此就可以编译通过了,同样static也能用来修饰函数,表示函数只能在当前文件中被调用。函数的不同点在于,如果static修饰的函数定义在.h文件中,那么其他包含.h的文件能调用static修饰的函数,每个文件使用的是一个单独的函数;如果,static修饰的函数声明在.h文件中,定义在.cpp文件中,那么这个函数对其他文件将不可见。

// case 1:
// add.h
#ifndef __ADD_H__
#define __ADD_H__
static int add(int &a, int &b)
{
     return a + b;
}
#endif // __ADD_H__

// case 2:
// add.h
#ifndef __ADD_H__
#define __ADD_H__
static int add(int &a, int &b);
#endif // __ADD_H__
// add.cpp
#include "add.h"
int add(int &a, int &b)
{
     return a + b;
}
// addA.h
#include "add.h"
int addA(int &a, int &b);
// addA.cpp
#include "addA.h"
int addA(int &a, int &b)
{
     return add(a, b); // error, can't invoke add function since it is a static function
}

# 2.static的作用

由上面的例子看到了static的一种应用是限制函数或变量的作用域在当前源文件。static另外一个作用是定义静态数据和类中定义属于类而不是对象的成员

// main.cpp
int accumulate(int a)
{
    static int cc = 0;
    cc += a;
    return cc;
}

int main(int argc, char** argv)
{
     int re = 0;
     for(auto i=0; i<3; i++)
     {
          re = accumulate(i);
     }
     std::cout << re << std::endl;
     return 0;
}

上面累加函数accumulate中声明的cc变量是静态数据,保存在静态存储区(全局变量存储区), 因此延长了cc的生命周期,但static并不改变变量的作用域,因此还是只能在accumulate函数中使用。

C++中,static变量如果初始化,那么初始化发生在任何代码执行之前,属于编译期初始化 。 全局变量、static全局变量、static局部变量,此三者的生命周期、初始化方法完全一致,只是可见范围不同。

此外就是static还可以用来修饰类中的数据成员或者成员函数,这样数据成员就保存在类的内存空间上而不是类实例对象的内存空间上。static修饰的成员函数中没有this指针,因此不能访问类中的非静态数据成员和方法。

类中的static数据成员,仍然受private等访问权限修饰符控制,若需通过类名::变量名来使用,需将其声明为public类型。

类中的static数据成员,需在类外初始化。

#include <iostream>

class Example {
    public:
        Example() : b(0) {};
    public:
        static int a;
    private:
        int b;
};

int Example::a = 10;
int main(int argc, char **argv)
{
     std::cout << Example::a << std::endl;
     return 0;
}
(adsbygoogle = window.adsbygoogle || []).push({});

# 参考资料