C++问与答(2)
Cpp Question & Answer(2)
1. C++ 中在代码中的 extern "C" 是什么意思?
Question:
C++ 中在代码中的 extern "C"
是什么意思?
比如,
extern "C" { |
Answer:
C++ 支持函数的重载,重载这个特性给我们带来了很大的便利。为了支持函数重载的这个特性,C++ 编译器实际上将下面这些重载函数
void print(int i); |
编译为
_print_int |
这样的函数名,来唯一标识每个函数(不同的编译器实现可能不一样,但都是利用这种机制)。所以当调用 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)派生对象的时候,虚析构函数就很用了。
|
注意,我并没有把类 Base
的析构函数定义为虚(virtual)。输出如下:
Base Constructor Called |
我们发现派生类的析构函数并没有调用,这是有问题的,有可能会造成内存泄漏,而解决这个问题的办法就是将 Base 的析构函数定义为虚(virtual),
class Base |
输出如下:
Base Constructor Called |
总结起来就是:当你的程序满足以下任何一项时,都无需定义基类虚拟析构函数,否则你就应该定义为虚,
- 这个基类没有派生类
- 不在堆(heap)内存实例化
- 没有指向派生类的基类指针或引用
对于 1,还是很常见的,有的时候我们只是单纯的写一个类,并没有派生它的打算,那这个时候就无需将它的析构函数定义为虚(virtual)了。
对于 2,基本上是个工程项目都不太可能,哪有一次都不在堆(heap)上实例化对象的,我是没遇到过,肯定是有一些对象必须要堆上实例化的。
对于 3,基本上也不太可能,派生类的存在基本上都会用到它的基类指针和引用。
3. const 与指针符号不同的前后顺序会有什么区别
Question:
我经常搞混 const int *
, const int * const
和 int const *
的区别,怎么区分它们呢?
Answer:
请先阅读这篇文章:读懂 C 的类型声明(译),接着可以看下下面的例子。
int * p
- p is pointer to intint const * p
- p is pointer to const intint * const p
- p is const pointer to intint 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 intint ** const p
- p is const pointer to pointer to intint * const * p
- p is pointer to const pointer to intint const ** p
- p is pointer to pointer to const intint * 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:
gcc
和 g++
都是 GNU 编译器套件(以前只是 GNU C 编译器)的编译驱动程序。
即使它们可以自行根据文件类型决定使用哪种后端(cc1
、cc1plus
……)除非指定了 -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++ 编译器
主要区别:
gcc
会编译:*.c
、*.cpp
文件,分别作为 C 语言和 C++ 语言。g++
会编译:*.c
、*.cpp
文件,但都作为 C++ 语言文件对待。- 若用
g++
当作链接器,它会自动链接标准 C++ 库(gcc
不这么做,可以添加--lstdc++
)。 gcc
编译 C 文件时会有更少的预定义宏。gcc
编译*.cpp
文件以及g++
编译*.c
、*.cpp
时会有额外的宏。
当编译 *.cpp
文件时的额外宏:
6. 去除 std::string头尾空格有没有什么好办法?
Question:
去除 std::string
头尾空格有没有什么好办法?
Answer:
// trim from start (in place) |
7. 我想将一个 std::string 全部转为小写字母,有什么好办法么?
Question:
我想将一个 std::string
全部转为小写字母,有什么好办法么?
Answer:
|
8. C++单例模式
Question:
我想用 C++ 实现一个单例模式,下面是我之前写的,
// a lot of methods are omitted here |
上面的代码主要涉及到内存分配及析构、线程安全等等。
怎样实现一个完美的单例呢?
Answer:
下面的代码基于 C++11,
class S |
9.如何拼接两个 std::vector?
Question:
如何拼接两个 std::vector?
Answer:
// vector2 拷贝到 vector1 |
10. 静态库和动态库有什么区别?
Question:
静态库和动态库有什么区别?
Answer:
后缀名不同
动态库的后缀,在 Windows 上是 .dll
,linux 上是 .so
,在 OSX 上是 .dylib
。
静态库,在 WIndows 上是 .lib
,linux 上是 .a
。
可执行文件大小不一样
静态链接的可执行文件要比动态链接的可执行文件要大得多,因为它将需要用到的代码从二进制文件中“拷贝”了一份,而动态库仅仅是复制了一些重定位和符号表信息。
扩展性与兼容性不一样
如果静态库中某个函数的实现变了,那么可执行文件必须重新编译,而对于动态链接生成的可执行文件,只需要更新动态库本身即可,不需要重新编译可执行文件。正因如此,使用动态库的程序方便升级和部署。
依赖不一样
静态链接的可执行文件不需要依赖其他的内容即可运行,而动态链接的可执行文件必须依赖动态库的存在。所以如果你在安装一些软件的时候,提示某个动态库不存在的时候也就不奇怪了。
即便如此,系统中存在一些大量公用的库,所以使用动态库并不会有什么问题。
加载速度不一样
由于静态库在链接时就和可执行文件在一块了,而动态库在加载或者运行时才链接,因此,对于同样的程序,静态链接的要比动态链接加载更快。所以选择静态库还是动态库是空间和时间的考量。但是通常来说,牺牲这点性能来换取程序在空间上的节省和部署的灵活性时值得的,再加上局部性原理,牺牲的性能并不多。