Cpp Question & Answer(2)

1. C++ 中在代码中的 extern "C" 是什么意思?

Question:

C++ 中在代码中的 extern "C" 是什么意思?

比如,

extern "C" {
void foo();
}

Answer:

C++ 支持函数的重载,重载这个特性给我们带来了很大的便利。为了支持函数重载的这个特性,C++ 编译器实际上将下面这些重载函数

void print(int i);
void print(char c);
void print(float f);
void print(char* s);

编译为

_print_int
_print_char
_print_float
_pirnt_string

这样的函数名,来唯一标识每个函数(不同的编译器实现可能不一样,但都是利用这种机制)。所以当调用 print(3) 时,链接会去查找 _print_int(3) 这样的函数。

C++ 中的变量,编译也类似,如全局变量可能编译 g_xx,类变量编译为 c_xx 等,链接也是按照这种机制去查找相应的变量。

而 C 语言中并没有重载这个特性,故并不像 C++ 那样被编译为 _print_int,而是直接编译为 _print。所以如果直接在 C++ 中调用 C 的函数会失败,因为链接调用 C 中的 print(3) 时,它会去 找 _print_int(3)

因此 extern "C" 的作用就体现出来了。它用来告诉 C++ 编译器,这部分代码要按照 C 语言的方式去链接。说到底就是为了方便C++调用C的库。

注意,同名同参但返回值不同,这不是重载,会有二义性,编译直接报错。

2. 什么时候该定义虚析构函数?

Question:

什么时候该定义虚析构函数,为什么要这么做?

Answer:

当你通过一个基类指针去删除(delete)派生对象的时候,虚析构函数就很用了。

#include <iostream>

using namespace std;

class Base
{
public:
Base(){
cout << "Base Constructor Called\n";
}
~Base(){
cout << "Base Destructor called\n";
}
};

class Derived1: public Base
{
public:
Derived1(){
cout << "Derived constructor called\n";
}
~Derived1(){
cout << "Derived destructor called\n";
}
};

int main()
{
Base *b = new Derived1();
delete b;
}

注意,我并没有把类 Base 的析构函数定义为虚(virtual)。输出如下:

Base Constructor Called
Derived constructor called
Base Destructor called

我们发现派生类的析构函数并没有调用,这是有问题的,有可能会造成内存泄漏,而解决这个问题的办法就是将 Base 的析构函数定义为虚(virtual),

class Base
{
public:
Base(){
cout << "Base Constructor Called\n";
}
virtual ~Base(){
cout << "Base Destructor called\n";
}
};

输出如下:

Base Constructor Called
Derived Constructor called
Derived destructor called
Base destructor called

总结起来就是:当你的程序满足以下任何一项时,都无需定义基类虚拟析构函数,否则你就应该定义为虚,

  1. 这个基类没有派生类
  2. 不在堆(heap)内存实例化
  3. 没有指向派生类的基类指针或引用

对于 1,还是很常见的,有的时候我们只是单纯的写一个类,并没有派生它的打算,那这个时候就无需将它的析构函数定义为虚(virtual)了。

对于 2,基本上是个工程项目都不太可能,哪有一次都不在堆(heap)上实例化对象的,我是没遇到过,肯定是有一些对象必须要堆上实例化的。

对于 3,基本上也不太可能,派生类的存在基本上都会用到它的基类指针和引用。

3. const 与指针符号不同的前后顺序会有什么区别

Question:

我经常搞混 const int *, const int * constint const * 的区别,怎么区分它们呢?

Answer:

请先阅读这篇文章:读懂 C 的类型声明(译),接着可以看下下面的例子。

  • int * p - p is pointer to int
  • int const * p - p is pointer to const int
  • int * const p - p is const pointer to int
  • int const * const p - p is const pointer to const int

其中,下面两个是等同的,只是顺序的不同而已,

  • const int * == int const *
  • const int * const == int const * const

当然还有更复杂的,

  • int ** p - p is pointer to pointer to int
  • int ** const p - p is const pointer to pointer to int
  • int * const * p - p is pointer to const pointer to int
  • int const ** p - p is pointer to pointer to const int
  • int * const * const p - p is const pointer to const pointer to int

4. C++中POD类似是什么意思

Question:

我碰见 POD 这个术语很多次了,它到底是什么意思?

Answer:

POD 全称 Plain Old Data,维基百科上对它的的 介绍 比较详细:

C++ 中的 Plain Old Data Structure 是一个聚合类,仅包含 POD 成员,没有自定义的析构函数,没有自定义的赋值运算符,并且没有非静态成员指针。

5. g++ 和 gcc 的区别是什么

Question:

g++ 和 gcc 的区别是什么?一般 c++ 开发应该用哪一个?

Answer:

gccg++ 都是 GNU 编译器套件(以前只是 GNU C 编译器)的编译驱动程序。

即使它们可以自行根据文件类型决定使用哪种后端(cc1cc1plus ……)除非指定了 -x language,但它们还是有一些区别。

最重要的区别可能就是默认上它们会自动链接到不同的库。

根据 GCC 的在线文档 3.15 Options for Linking 以及 g++ 是如何被调用的g++gcc -xc++ -lstdc++ -shared-libgcc (第一个是编译器选项,后面两个是链接器选项)是等价的。通过带 -v 参数(显示将要运行的后端工具链指令)运行这两个指令,可以证明它们确实等价。

Answer:

GCC:GNU 编译器套件

它包含所有支持不同语言的 GNU 编译器。

gcc:GNU C 编译器 g++:GNU C++ 编译器

主要区别:

  1. gcc 会编译:*.c*.cpp文件,分别作为 C 语言和 C++ 语言。
  2. g++ 会编译:*.c*.cpp 文件,但都作为 C++ 语言文件对待。
  3. 若用 g++ 当作链接器,它会自动链接标准 C++ 库(gcc 不这么做,可以添加 --lstdc++)。
  4. gcc 编译 C 文件时会有更少的预定义宏。
  5. gcc 编译 *.cpp 文件以及g++ 编译 *.c*.cpp 时会有额外的宏。

当编译 *.cpp 文件时的额外宏:

#define __GXX_WEAK__ 1
#define __cplusplus 1
#define __DEPRECATED 1
#define __GNUG__ 4
#define __EXCEPTIONS 1
#define __private_extern__ extern

6. 去除 std::string头尾空格有没有什么好办法?

Question:

去除 std::string 头尾空格有没有什么好办法?

Answer:

// trim from start (in place)
static inline void ltrim(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) {
return !std::isspace(ch);
}));
}

// trim from end (in place)
static inline void rtrim(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) {
return !std::isspace(ch);
}).base(), s.end());
}

// trim from both ends (in place)
static inline void trim(std::string &s) {
ltrim(s);
rtrim(s);
}

7. 我想将一个 std::string 全部转为小写字母,有什么好办法么?

Question:

我想将一个 std::string 全部转为小写字母,有什么好办法么?

Answer:

#include <algorithm>
#include <cctype>
#include <string>

std::string data = "Abc";
std::transform(data.begin(), data.end(), data.begin(),
[](unsigned char c){ return std::tolower(c); });

8. C++单例模式

Question:

我想用 C++ 实现一个单例模式,下面是我之前写的,

// a lot of methods are omitted here
class Singleton
{
public:
static Singleton* getInstance( );
~Singleton( );
private:
Singleton( );
static Singleton* instance;
};

上面的代码主要涉及到内存分配及析构、线程安全等等。

怎样实现一个完美的单例呢?

Answer:

下面的代码基于 C++11,

class S
{
public:
static S& getInstance()
{
static S instance; // C++11 保证这是线程安全的
return instance;
}

private:
S() {}

public:
S(S const&) = delete; // 《Effective Modern C++》提到,用 delete 更有益于编译器的错误提示
void operator=(S const&) = delete;
};

9.如何拼接两个 std::vector?

Question:

如何拼接两个 std::vector?

Answer:

// vector2 拷贝到 vector1
vector1.insert(vector1.end(), vector2.begin(), vector2.end());

// vector2 移动到 vector1,此时 vector2 不可再用
vector1.insert(vector1.end(), std::make_move_iterator(vector2.begin()), std::make_move_iterator(vector2.end()));

10. 静态库和动态库有什么区别?

Question:

静态库和动态库有什么区别?

Answer:

后缀名不同

动态库的后缀,在 Windows 上是 .dll,linux 上是 .so,在 OSX 上是 .dylib

静态库,在 WIndows 上是 .lib,linux 上是 .a

可执行文件大小不一样

静态链接的可执行文件要比动态链接的可执行文件要大得多,因为它将需要用到的代码从二进制文件中“拷贝”了一份,而动态库仅仅是复制了一些重定位和符号表信息。

扩展性与兼容性不一样

如果静态库中某个函数的实现变了,那么可执行文件必须重新编译,而对于动态链接生成的可执行文件,只需要更新动态库本身即可,不需要重新编译可执行文件。正因如此,使用动态库的程序方便升级和部署。

依赖不一样

静态链接的可执行文件不需要依赖其他的内容即可运行,而动态链接的可执行文件必须依赖动态库的存在。所以如果你在安装一些软件的时候,提示某个动态库不存在的时候也就不奇怪了。

即便如此,系统中存在一些大量公用的库,所以使用动态库并不会有什么问题。

加载速度不一样

由于静态库在链接时就和可执行文件在一块了,而动态库在加载或者运行时才链接,因此,对于同样的程序,静态链接的要比动态链接加载更快。所以选择静态库还是动态库是空间和时间的考量。但是通常来说,牺牲这点性能来换取程序在空间上的节省和部署的灵活性时值得的,再加上局部性原理,牺牲的性能并不多。

参考:https://zhuanlan.zhihu.com/p/71372182