今天把explicit和extern记混了,所以特此记录一下两个关键字的用法。extern是用于声明变量或函数的,而explicit用于类的单参构造函数,是禁止隐式类型转换的。
1. extern用法
首先C++中声明和定义是分开的,定义就要分配内存空间。因此,声明可以有多个,而定义只能有一个。对于变量,不带extern都是定义,比如int a;
和int a=1;
都是定义;对于函数,带{}是定义,不带的就是声明。extern的作用就是告诉编译器,我这只是个声明,你去其他地方找定义,可以起到在文件间共享数据的作用。
C++中链接属性有三种:none(无)、external(外部)和 internal(内部)。
external,外部链接属性。非常量全局变量和自由函数(除成员函数以外的函数)均默认为外部链接的,它们具有全局可见性,在全局范围不允许重名。
internal,内部链接属性。具有该属性的类型有,const对象,constexpr对象,命令空间内的静态对象
none,在类中、函数体和代码块中声明的变量默认是具有none链接属性。它和internal一样只在当前作用域可见。
下面看extern的用法
1.1 extern用于变量
首先看用于变量,代码如下:
// main.cpp
int a;
int b = 200;
//test.cpp
int a;
int b = 100;
明显可以看到,报错a和b重定义。如果修改为:
// main.cpp
int a;
const int b = 200;
//test.cpp
int a;
const int b = 100; // 或者int b=100;
可以看到,只报错a重定义。这就说明常量全局变量默认是内部链接的,所以想要在文件间传递常量全局变量需要在定义时指明extern。
而如下代码是可以运行的:
// main.cpp
#include<iostream>
int main()
{
extern int a;
a = 100; // 或者直接写 int a=100;
std::cout << a << std::endl; // 结果输出100
return 0;
}
// test.cpp
int a=10;
说明局部变量不影响。下面看看extern的用法:
#include<iostream>
extern int a; // 声明全局变量
int main()
{
std::cout << a << std::endl; // 输出a=10
return 0;
}
// test.cpp
int a=10; // 声明并定义
刚才会发生错误,是因为a重定义。而使用extern声明后,就是告诉编译器,我这只是个声明,你去其他地方找定义,所以不会冲突,最后输出10。
extern用于函数差不多,具体例子可以看后面用extern "C"的用法
1.2 extern混合C语言
当C++中需要执行C风格的代码时,就需要用到extern “C”,先来看个例子:
// main.cpp
int func()
{
return 1;
}
// test.cpp
int func()
{
return 1;
}
明显看到,func函数重定义。再来看个带C语言的例子:
// main.cpp
#include<iostream>
int func()
{
return 1;
}
int main()
{
std::cout << func() << std::endl; // 输出1
return 0;
}
// test.c
int func()
{
return 2;
}
程序可以执行,输出1。说明编译器并没有把这两个函数当成同一个东西,也就说明C++和C语言风格的处理方式是不一样的。而两个C文件,也会冲突,如下:
// test.c
int func()
{
return 2;
}
// test1.c
int func()
{
return 2;
}
首先,从两个报错可以看出,C++重定义和C语言重定义报错方式是不一样的。C++的func函数处理成?func@@YAHXZ
,而C语言处理成_func
。这是因为C++支持函数重载,不能仅根据函数名区分,所以有@后面的一堆。这也就解释了刚才C语言定义的func函数在C++中输出1能够成功的原因,因为处理方式根本不一样。
现在来看在C++风格中调用C代码的例子:
// main.cpp
#include<iostream>
#include "test.h"
int main()
{
std::cout << func() << std::endl;
return 0;
}
// test.h
int func();
// test.c
int func()
{
return 2;
}
可以看到,这既有声明,也有定义,怎么会报错呢?那是因为我们在main.cpp中是当作C++风格处理的,而实际上是按C风格处理的,可以看到报错显示的_func已定义,而_func就是C语言的处理风格。
解决方案:用extern "C"声明,这是一个C风格的,如下:
// test.h
extern "C" int func(); // 将test.h中的声明加上extern "C",其他不变
// 程序输出2
还有另一种方式,这种更推荐
// main.cpp
#include<iostream>
extern "C"
{
#include "test.h"
}
int main()
{
std::cout << func() << std::endl;
return 0;
}
// test.h
int func();
// test.c
int func()
{
return 2;
}
这种方式直接对引用头文件的地方使用extern "C"声明,更方便。
extern “C” {}的形式还可以批量声明,例如
extern "C"
{
#include "a.h"
#include "b.h"
#include "c.h"
}
extern "C"
{
int f();
int f1();
int f2();
}
2. explicit
explicit主要用于类的仅有一个参数的构造函数,使用explicit的类就不能进行隐式转换。
先看隐式类型转换的例子:
class Point {
public:
int x, y;
Point(int x, int y = 0) :x(x),y(y) {}
Point(const Point& p) {
x = p.x;
y = p.y;
}
};
int main()
{
//发生隐式类型转换
//编译器会将它变成如下代码
//tmp = Point(1,0)
//Point A(tmp);
//tmp.~Point();
Point p = 1; // 注意,因为构造函数虽然有两个参数,但有默认参数,所以依然发生了隐式类型转换
Point pp = p; // 相当于隐式调用了拷贝构造
return 0;
}
加上explicit后:
class Point {
public:
int x, y;
explicit Point(int x, int y = 0) :x(x),y(y) {}
explicit Point(const Point& p) {
x = p.x;
y = p.y;
}
};
int main()
{
Point p = 1;
Point pp = p;
return 0;
}
可以看到编译器直接报错,也就是禁止了隐式类型转换。
要用如下方式:
int main()
{
// 未加explicit之前,可以 Point p = Point(1);,加了只能这样显示调用构造函数
Point p(1);
// 未加explicit之前,可以Point pp = Point(p); ,加了只能显示调用拷贝构造
Point pp(p);
return 0;
}
所以explicit 关键字作用于单个参数的构造函数或者其他参数有默认值,作用就是禁止隐式类型转换。