C/C++面试基础知识总结(二)

本篇是上一篇的继续。

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值