//a.cpp
#include "a.h"
int a::i=8;//类内声明,类外定义
以下内容有的地方比较深,仅从我个人理解的角度进行分析。信息量比较庞大,请谨慎阅读。
一、前置知识
1.编译与链接的理解
c++编译过程_CSDN_Violin的博客-CSDN博客_c++编译
1)编译
编译的定义:C++程序的编译主要是按照文件单独进行编译(即以编译单元分块编译),生成目标代码文件。编译过程的输出是一系列的目标文件。所以,在编译其中一个文件中,编译器并不知道其余文件中的内容。
编译单元:一个编译单元(Translation Unit)是指一个.cpp文件以及这所include的所有.h文件,.h文件里面的代码将会被扩展到包含它的.cpp文件里,然后编译器编译该.cpp文件为一个.obj文件
2)链接
链接的定义:链接程序的主要工作就是将有关的目标文件(库文件、.obj文件)彼此相连接,也即将在一个文件中引用的符号同该符号在另外一个文件中的定义连接起来,使得所有的这些目标文件成为一个能够被操作系统装入执行的统一整体
3)内部链接与外部链接
参考:内部链接与外部链接_yuanweihuayan的博客-CSDN博客_内部链接和外部链接
【一天一个C++小知识】004.C++中内部链接和外部链接 - 知乎
内部链接:如果一个名称对于它的编译单元来说是局部的,并且在连接时不会与其它编译单元中的同样的名称相冲突,那么这个名称有内部连接(注:有时也将声明看作是看成是内部连接的)。
常见的内部链接类型有:static变量或函数、const的变量或函数、inline函数、enum类型、自定义的类等。
外部链接:如果一个名称对编译单元来说不是局部的,而在链接的时候其他的编译单元可以访问它,也就是说它可以和别的编译单元交互。
常见的外部链接类型有:普通的全局变量、普通的函数、类的static数据成员、类的非inline函数(static、普通成员函数)
举个最简单的经验判别:不是在类里面的类型的话,可内部链接的类型可以直接在.h文件中声明并定义,而外部链接的类型只能在.h文件中声明而必须在.cpp文件中定义。如果把内部链接类型放在.cpp文件里了,除非其他的文件用#include "xxx.cpp“这种不讲武德的方式进行数据交互,否则将是交互不了的,故此称其为内部链接。
而是在类里面的类型的话,则有点相反,内部链接的类型只能在类里面定义,而外部链接的类型的话既可以在类内部声明并定义也可以在类外部定义(但是无法在.h文件的类外部定义)。
不过不管是内部链接还是外部链接,在其他文件中加上了include最终都能使用到,因为根据上面1.1的说法,编译的时候会把include的东西跟这个cpp文件一块打包编译了。
2.补充:动态链接与静态链接
直接参考:什么是动态链接与静态链接?
上面的参考链接讲的例子太棒了。静态链接就是在编译链接的时候直接将库文件塞到程序里面去了,程序在运行的时候可以不用依赖静态库。而动态链接库在程序运行时是需要依赖于动态链接库文件的。
3.补充:头文件原理
参考:C++头文件的作用以及用法_陌千浔的博客-CSDN博客_c++头文件
简而言之,头文件的作用主要是为了:避免想相同内容的重复书写,提高编程效率和代码安全性。
头文件原理就是类似一个可以定位的跳板(即完成链接的这个步骤),在.h文件中找到声明然后到.cpp文件中找到真正的定义主体。
二、extern关键字
在有了前面知识的介绍下再理解extern就好理解多了。
1.引入
在我们下载项目然后修改的时候可能会见到:链接器工具错误 LNK2005。
这个错误便是没有理解清楚extern的用法。
关于接下来的部分参考了:
链接器工具错误 LNK2005 | Microsoft Docs
2.extern的作用
因为现代编译技术的规格,须要一个关键字来告诉编译器,在这个地方须要连接外部引用或者函数,在编译中,须要创建连接引用,在连接阶段须要解析引用。该关键字即是extern。
总之,extern主要就是为了拿来声明用的,extern本身是内部链接,但是其修饰的函数或变量是外部链接。
3.extern运用举例
1)修饰变量
// a.h
extern int i;//普通全局变量是外部链接方式,不加extern将报错。在此只是声明一个变量i
// a.cpp
int i;//真正的定义位置
第一种调用方式:不加include,在main函数中使用extern声明i,可使i在链接时直接链接到a.cpp中i的定义处。这种使用方式不用a.h文件也能运行。
//b.cpp
#include <stdio.h>
int mian(){
extern int i;
printf("%d\n",i);
}
第二种调用方式:用include,在b.cpp编译时会将a.h的内容一块编译了,然后main函数中的i在链接时先链接到a.h中i的声明再链接到a.cpp中i的定义处。
//b.cpp
#include <stdio.h>
#include "a.h"
int mian(){
printf("%d\n",i);
}
2)修饰函数
与使用变量的方式大同小异。
// a.h
void fun();
//与修饰普通变量不一样的是,函数默认自己加了extern,所以写的时候可不加extern关键字
//extern void fun();//这样写也可以
// a.cpp
#include <stdio.h>
void fun(){
printf("fun");
}//真正的定义位置
第一种调用方式:不加include
//b.cpp
#include <stdio.h>
int mian(){
void fun();//同样可以不加extern
fun();
}
第二种调用方式:用include
//b.cpp
#include <stdio.h>
#include "a.h"
int mian(){
fun();
}
3)extern "C"
直接参考上面的参考链接即可
三、static关键字
static关键字感觉使用起来会比较复杂一些,因为static关键字还可以修饰类成员。
接下来的部分参考了:C++中static关键字详解 - Cucucu - 博客园
C++Primer学习笔记(6)_qq_42987967的博客-CSDN博客
1.static修饰全局变量
在全局变量前加上一个static关键字,普通全局变量就变成一个静态全局变量。而这个静态全局变量只能用include去使用到它而无法被声明。加上static关键字后,该变量属于内部链接方式。
举例说明:
// a.h
static int si1=7;//在.h文件中可直接定义静态全局变量
// b.cpp
static int si2=7;//在.cpp文件中也可直接定义静态全局变量
//c.cpp
#include <stdio.h>
#include "a.h"
#include "b.cpp"//只有用include的方式才能在其他文件中使用到静态全局变量
int mian(){
printf("%d\n",si1);
printf("%d\n",si2);
}
2.static修
饰局部变量
静态局部变量作用域仍然为局部作用域。当定义它的函数或者语句块结束的时候,作用域结束。但是当局部静态变量离开作用域后,没有销毁,而是留在内存块中,只是我们不能再对其进行访问。直到函数再次被调用,并且静态局部变量的值是不变的。
个人认为,static修饰局部变量用的相对少。
举例说明:
//c.cpp
#include <stdio.h>
void test() {
static int i = 0;
printf("%d\n", ++i);
}//只有test函数才能访问到静态局部变量i,且test调用结束后i也不会被销毁
int mian(){
for (int j = 0; j != 3; ++j)test();//将打印1 2 3
}
3.static修饰类的数据成员
1)类的静态成员既可以被作用域访问又可被类对象访问
//a.h
class a {
static int i;
}//类内声明
//a.cpp
#include "a.h"
int a::i = 9;
//类外定义
//b.cpp
#include <stdio.h>
#include "a.h"
int main() {
a aa();
printf("%d\n", aa.i);//类对象访问
printf("%d\n", a::i);//类作用域访问
}
2)类的静态成员既不可在类内声明又可在类内定义(应该是其外部链接的性质),可类内声明外部定义。
//a.h
class a {
static int i1 = 0;//类内声明并定义报错
static int i2;//类内声明,类外定义
}
//a.cpp
#include "a.h"
int a::i2=1;//类外定义类的静态成员
以上举的是类的数据成员的例子,类的静态成员函数同样可以,类的普通函数也可以,因为他们都是外部链接方式。
3)静态成员函数中只能使用类的静态成员,而普通函数可使用类的静态成员和普通成员
//a.h
class a {
static int i1;//类内声明类外定义
int i2 = 2;
static int fun1() {
return i1;//return i2;将报错
}//类的静态成员函数只能使用类的静态成员
int fun2() {
return fun1() + i2 + i1;
}//类的普通函数可使用静态成员和普通成员
}
4)static成员无法在.h文件的类外部被定义(外部链接性质)
//a.h
class a {
static int i;//类内声明
}
static int a::i=0;//.h文件的类外部定义将报链接器工具错误 LNK2005
四、总结
本文从编译和链接的角度去理解extern和static关键字,能比较系统性地将知识串起来,但是目前的学识有限,可能还有理解出错的地方,欢迎指正。