本篇是上一篇的继续。
9. extern “C”
9.1 特点
- 被extern限定的函数或变量是extern类型的
- 被extern "C"修饰的变量和函数是按照C语言方式编译和连接的
extern "C"的作用是让C++编译器将extern "C"声明的代码当作C语言代码处理,可以避免C++因符号修饰导致代码不能和C语言库中的符号进行链接的问题。
由于c++有重载函数名的功能,而C没有,c++编译器不能仅仅使用函数名作为链接到的唯一id,所以它通过添加有关参数的信息来破坏函数名。C编译器不需要修改名称,因为在C中不能重载函数名。当你在c++中声明一个函数具有extern“C”链接时,c++编译器不会向用于链接的名称添加实参/形参类型信息。
正如您所知,您可以明确地为每个单独的声明/定义指定 extern “C” 链接,或者使用块将一系列声明/定义分组以具有特定链接:
extern "C" void foo(int);
extern "C"
{
void g(char);
int i;
}
你会经常在C头文件中看到这样的代码:
#ifdef __cplusplus
extern "C" {
#endif
// all of your legacy C code here
#ifdef __cplusplus
}
#endif
这样做的结果是它允许您在 C++ 代码中使用该 C 头文件,因为将定义宏“__cplusplus”。但是您仍然可以将它与未定义宏的C 代码一起使用,因此它不会看到唯一的 C++ 构造。
9.2 C++与C编译区别
在C++中常在头文件见到extern "C"
修饰函数,那有什么作用呢? 是用于C++链接在C语言模块中定义的函数。
C++虽然兼容C,但C++文件中函数编译后生成的符号与C语言生成的不同。因为C++支持函数重载,C++函数编译后生成的符时带有函数参数类型的信息,而C则没有。
例如int add(int a, int b)
函数经过C++编译器生成.o
文件后,add
会变成形如add_int_int
之类的, 而C的话则会是形如_add
, 就是说:相同的函数,在C和C++中,编译后生成的符号不同。
这就导致一个问题:如果C++中使用C语言实现的函数,在编译链接的时候,会出错,提示找不到对应的符号。此时extern "C"就起作用了:告诉链接器去寻找_add
这类的C语言符号,而不是经过C++修饰的符号。
9.3.C++调用C函数
C++调用C函数的例子: 引用C的头文件时,需要加extern "C"
//add.h
#ifndef ADD_H
#define ADD_H
int add(int x,int y);
#endif
//add.c
#include "add.h"
int add(int x,int y) {
return x+y;
}
//add.cpp
#include <iostream>
#include "add.h"
using namespace std;
int main() {
add(2,3);
return 0;
}
编译:
//生成 add.o 文件
gcc -c add.c
链接:
g++ add.cpp add.o -o main
没有添加extern “C” 报错:
> g++ add.cpp add.o -o main
add.o:在函数‘main’中:
add.cpp:(.text+0x0): `main'被多次定义
/tmp/ccH65yQF.o:add.cpp:(.text+0x0):第一次在此定义
/tmp/ccH65yQF.o:在函数‘main’中:
add.cpp:(.text+0xf):对‘add(int, int)’未定义的引用
add.o:在函数‘main’中:
add.cpp:(.text+0xf):对‘add(int, int)’未定义的引用
collect2: error: ld returned 1 exit status
添加extern "C"后:
add.cpp
#include <iostream>
using namespace std;
extern "C" {
#include "add.h"
}
int main() {
add(2,3);
return 0;
}
编译的时候一定要注意,先通过gcc生成中间文件add.o。
gcc -c add.c
然后编译:
g++ add.cpp add.o -o main
9.4 C中调用C++函数
extern "C"
在C中是语法错误,需要放在C++头文件中。
// add.h
#ifndef ADD_H
#define ADD_H
extern "C" {
int add(int x,int y);
}
#endif
// add.cpp
#include "add.h"
int add(int x,int y) {
return x+y;
}
// add.c
extern int add(int x,int y);
int main() {
add(2,3);
return 0;
}
编译:
g++ -c add.cpp
链接:
gcc add.c add.o -o main
综上,总结出使用方法,在C语言的头文件中,对其外部函数只能指定为extern类型,C语言中不支持extern "C"
声明,在.c文件中包含了extern "C"
时会出现编译语法错误。所以使用extern "C"
全部都放在于cpp程序相关文件或其头文件中。
10.explicit(显式)关键字
- explicit 修饰构造函数时,可以防止隐式转换和复制初始化
- explicit 修饰转换函数时,可以防止隐式转换,但按语境转换除外
#include <iostream>
using namespace std;
struct A
{
A(int) { }
operator bool() const { return true; }
};
struct B
{
explicit B(int) {}
explicit operator bool() const { return true; }
};
void doA(A a) {}
void doB(B b) {}
int main()
{
A a1(1); // OK:直接初始化
A a2 = 1; // OK:复制初始化
A a3{ 1 }; // OK:直接列表初始化
A a4 = { 1 }; // OK:复制列表初始化
A a5 = (A)1; // OK:允许 static_cast 的显式转换
doA(1); // OK:允许从 int 到 A 的隐式转换
if (a1); // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a6(a1); // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a7 = a1; // OK:使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a8 = static_cast<bool>(a1); // OK :static_cast 进行直接初始化
B b1(1); // OK:直接初始化
// B b2 = 1; // 错误:被 explicit 修饰构造函数的对象不可以复制初始化
B b3{ 1 }; // OK:直接列表初始化
// B b4 = { 1 }; // 错误:被 explicit 修饰构造函数的对象不可以复制列表初始化
B b5 = (B)1; // OK:允许 static_cast 的显式转换
// doB(1); // 错误:被 explicit 修饰构造函数的对象不可以从 int 到 B 的隐式转换
if (b1); // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
bool b6(b1); // OK:被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
// bool b7 = b1; // 错误:被 explicit 修饰转换函数 B::operator bool() 的对象不可以隐式转换
bool b8 = static_cast<bool>(b1); // OK:static_cast 进行直接初始化
return 0;
}
11.fiend友元
11.1概述
友元提供了一种 普通函数或者类成员函数 访问另一个类中的私有或保护成员 的机制。也就是说有两种形式的友元:
(1)友元函数:普通函数访问某个类中的私有或保护成员。
(2)友元类:类A中的成员函数访问类B中的私有或保护成员
优点:提高了程序的运行效率。
缺点:破坏了类的封装性和数据的透明性。
总结:- 能访问私有成员- 破坏封装性- 友元关系不可传递- 友元关系的单向性- 友元声明的形式及数量不受限制
11.2 用例子理解 C++ 中的友元函数
- 1.C++中友元函数的语法
您可以在类的主体内使用关键字“friend”来声明友元函数。
class Box {
double width;
public:
double length;
friend void printWidth ( Box box ) ;
void setWidth ( double wid ) ;
} ;
-
2.C++中友元函数的特点
- 友元函数像普通函数一样被调用,并可以在公共或私有部分声明。
- 它不在将其声明为友元函数的类的作用域内。
- 它必须使用对象名和带有成员名的点成员操作符来访问成员名。
-
3.C++ 友元函数示例
显示友元函数工作的简单程序(1):
#include <iostream>
using namespace std;
class Distance {
private:
int meter;
// friend function
friend int addFive(Distance);
public:
Distance() : meter(0) {}
};
// friend function definition
int addFive(Distance d) {
//accessing private members from the friend function
d.meter += 5;
return d.meter;
}
int main() {
Distance D;
cout << "Distance: " << addFive(D);
return 0;
}
显示友元函数工作的简单程序(2):
使用友元函数对两个不同类对象进行add
操作:
#include <iostream>
using namespace std;
// forward declaration
class ClassB;
class ClassA {
public:
// constructor to initialize numA to 12
ClassA() : numA(12) {}
private:
int numA;
// friend function declaration
friend int add(ClassA, ClassB);
};
class ClassB {
public:
// constructor to initialize numB to 1
ClassB() : numB(1) {}
private:
int numB;
// friend function declaration
friend int add(ClassA, ClassB);
};
// access members of both classes
int add(ClassA objectA, ClassB objectB) {
return (objectA.numA + objectB.numB);
}
int main() {
ClassA objectA;
ClassB objectB;
cout << "Sum: " << add(objectA, objectB);
return 0;
}
显示友元函数工作的简单程序(3):
使用友元函数打印一个box的长度:
#include <iostream>
using namespace std;
class Box
{
private:
int length;
public:
box(): length ( 0 ) { }
friend int printLength(Box); // friend function
};
int printLength (Box b)
{
b.length += 10;
return b.length;
}
int main()
{
Box b;
Cout << “Length of box:” <<printLength(b)<<endl;
Return 0 ;
}
- 11.3用示例代码理解 C++ 中的友元类
- 1.在 C++ 中实现友元类的语法
实现友元类的语法是:
class ClassB;
class ClassA {
// ClassB is a friend class of ClassA
friend class ClassB;
... .. ...
}
class ClassB {
... .. ...
}
正如您在语法中看到的,您需要做的就是在类前面使用关键字friend 使其成为友元类。使用此语法将使 ClassB
成为 ClassA
的友元类。由于 ClassB
成为友元类,它将可以访问 ClassA
的所有公有、私有和受保护成员。然而,反过来就不成立了。那是因为 C++ 只允许授予友元关系而不允许接受它。因此,ClassA 将无法访问 ClassB 的私有成员。
- 2.C++ 友元类的例子
现在您已经了解了在 C++ 中实现友元类的语法及其使用,请查看一些示例以更好地理解该概念。
示例 1:访问其他类的私有成员的简单示例
#include <iostream>
using namespace std;
class Exmp_A{
int i =3;
// Declaring the friend class
friend class Exmp_B;
};
class Exmp_B
{
public:
void display(Exmp_A &a)
{
cout<<"The value of i is : "<<a.i;
}
};
int main(){
Exmp_A a;
Exmp_B b;
b.display(a);
return 0;
}
示例 2:使用 C++ 中的友元类执行计算
在本例中,您将类ExmpB声明为友元类,ExmpB执行一个sum
操作,该操作将类ExmpB中的私有成员变量与类ExmpA的私有成员变量进行相加。
#include <iostream>
using namespace std;
class ExmpA{
private:
int x;
// Declaring the friend class
friend class ExmpB;
public:
// Initializing x value using a constructor
ExmpA() : x(9) {}
};
class ExmpB{
private:
int y;
public:
// Initializing y value using a constructor
ExmpB() : y(13) {}
// Function to perform addition
int sum(){
ExmpA a;
return a.x + y;
}
};
int main(){
ExmpB b;
cout << "Sum is: " << b.sum();
return 0;
}
参考目录
https://www.cnblogs.com/yc_sunniwell/archive/2010/07/14/1777432.html
https://www.bookstack.cn/read/CPlusPlusThings/ec48d16ca2a6afb0.md
https://www.simplilearn.com/tutorials/cpp-tutorial/friend-class-in-cpp