41. C++ 代码报错 coredump
- coredump 是程序由于异常或者 bug 在运行时异常退出或者终止,在一定的条件下生成的一个叫做 core 的文件,这个 core 文件会记录程序在运行时的内存,寄存器状态,内存指针和函数堆栈信息等等,对这个文件进行分析可以定位到程序异常的时候对应的堆栈调用信息,使用 gdb 工具对 core 文件进行调试
42. 静态类型/绑定 & 动态类型/绑定
- 静态类型:对象在声明时采用的类型,在编译期既已确定
- 静态绑定:绑定的是静态类型,所对应的函数或属性依赖于对象的静态类型,发生在编译期
- 动态类型:通常是指一个指针或引用目前所指对象的类型,是在运行期决定的
- 动态绑定:绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期
- 在继承体系中只有虚函数使用的是动态绑定(如此实现多态性),其他的全部是静态绑定
引用也能实现动态绑定
- 引用在创建的时候必须初始化,在访问虚函数时,编译器会根据其所绑定的对象类型决定要调用哪个函数(注意只能调用虚函数)
43. 怎样判断两个浮点数是否相等
- 不能直接用 == 来判断,对于两个浮点数比较只能通过相减并与预先设定的精度比较(记得要取绝对值)
44. 指针加减计算要注意什么
- 指针加减本质是对其所指地址的移动,移动的步长跟指针的类型是有关系的
- 指针每移动一位,它实际跨越的内存间隔是指针类型的长度,建议都转成十进制计算,计算结果除以类型长度取得结果
45. 类如何实现只能静态分配和只能动态分配
- 静态分配
- 把 new、delete 运算符重载为 private 属性
- 动态分配
- 把构造、析构函数设为 protected 属性,再用子类来动态创建
- 建立类的对象有两种方式
- 静态建立,由编译器为对象在栈空间中分配内存
- 动态建立,使用 new 运算符为对象在堆空间中分配内存
- 第一步执行 operator new() 函数,在堆中搜索一块内存并进行分配
- 第二步调用类构造函数构造对象
只有使用 new 运算符,对象才会被建立在堆上,因此只要限制 new 运算符就可以实现类对象只能建立在栈上,可以将 new 运算符设为私有
46. 函数指针
47. printf 函数的实现原理
- 在C/C++中,对函数参数的扫描是从后向前的
- C/C++ 的函数参数是通过压入堆栈的方式来给函数传参(堆栈是一种先进后出的数据结构)
- 在计算机的内存中,数据有 2 块,一块是堆,一块是栈(函数参数及局部变量在这里),而栈是从内存的高地址向低地址生长的,控制生长的就是堆栈指针
- 最先压入的参数在所有参数的最后面,最后压入的参数在最前面(第一个),所以最后压入的参数总是能够被函数找到,因为它就在堆栈指针的上方
cout 和 printf 区别
- cout 是类 std::ostream 的全局对象
- cout << 后可跟不同的类型是因为已存在针对各种类型数据的重载,所以会自动识别数据的类型
- cout 是有缓冲输出(先将输出字符放入缓冲区,然后输出到屏幕),printf 是行缓冲输出
48. 静态成员与普通成员的区别
- 生命周期
- 静态成员变量从类被加载开始到类被卸载,一直存在
- 普通成员变量只有在类创建对象后才开始存在,对象结束它的生命期也结束
- 共享方式
- 静态成员变量是全类共享,普通成员变量是每个对象单独享用
- 定义位置
- 普通成员变量存储在栈或堆中,而静态成员变量存储在静态全局区
- 初始化位置
- 普通成员变量在类中初始化,静态成员变量只能在类外初始化
- 默认实参
49. ifdef endif 的理解
- 一般情况下,源程序中所有的行都参加编译,但是有时希望对其中一部分内容只在满足一定条件才进行编译,也就是对一部分内容指定编译的条件,这就是 “条件编译”:有时希望当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句
#ifdef 标识符
#else
#endif
- 在一个大的软件工程里面,可能会有多个文件同时包含一个头文件,当这些文件编译链接成一个可执行文件上时,就会出现大量 “重定义” 错误,在头文件中使用 #define、#ifndef、#ifdef、#endif 能避免头文件重定义
50. strcpy、sprintf 和 memcpy 的区别
- 操作对象不同
- strcpy 的两个操作对象均为字符串
- sprintf 的操作源对象可以是多种数据类型,目的操作对象是字符串
- memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型
- 执行效率不同
- memcpy 最高,strcpy 次之,sprintf 的效率最低
- 实现功能不同
- strcpy 主要实现字符串变量间的拷贝
- sprintf 主要实现其他数据类型格式到字符串的转化
- memcpy 主要是内存块间的拷贝
51. int main(int argc, char *argv[]) 的内存结构
- 程序在命令行下运行时,需要输入 argc 个参数,每个参数是以 char 类型输入的,依次存在数组 argv[] 中,所有的参数在指针 char* 指向的内存中,数组的中元素的个数为 argc 个,第一个参数为程序的名称
52. Debug 和 Release 的区别
- Debug 调试版本
- 包含调试信息,所以容量比 Release 大很多,并且不进行任何优化(优化会使调试复杂化,因为源代码和生成的指令间关系会更复杂),便于程序员调试
- Debug 模式下生成两个文件,除了 .exe 或 .dll 文件外,还有一个 .pdb 文件,该文件记录了代码中断点等调试信息
- Release 发布版本
- 不对源代码进行调试,编译时对应用程序的速度进行优化,使得程序在代码大小和运行速度上都是最优的(调试信息可在单独的PDB文件中生成)
- Release 模式下生成一个文件:.exe 或 .dll 文件
53. 回调函数
- 回调函数相当于一个中断处理函数,由系统在符合设定的条件时自动调用,为此需要做三件事
- 1、声明
- 2、定义
- 3、设置触发条件(就是在函数中把回调函数名称转化为地址作为一个参数,以便系统调用)
- 回调函数就是一个通过函数指针调用的函数
- 如果把函数指针(地址)作为参数传递给另一个函数,当这个指针被用为:调用它所指向的函数时,就说这是回调函数
54. C++ 从代码到可执行程序的过程
- 预编译
- 编译
- 把预编译之后生成的 xxx.i 或 xxx.ii 文件,进行一系列词法分析、语法分析、语义分析及优化后,生成相应的汇编代码文件
- 汇编
- 链接
- 将不同的源文件产生的目标文件进行链接,从而形成一个可以执行的程序
55. 静态链接和动态链接
55(补充). 动态库和静态库的区别
- 在 Linux 下,动态库文件以 .so 结尾,静态库以 .a 结尾
- 在 Windows 下,动态库以 .dll 结尾,静态库以 .lib 结尾
55.1 Linux 动态库和静态库
动态库和静态库的区别
- 链接时刻不同:静态库在编译时被链接到可执行文件中,而动态库则在运行时被加载到内存中
- 文件大小不同:静态库会被完整地复制到可执行文件中,因此可执行文件的大小会增加;而动态库是独立的文件,可执行文件只包含动态库的引用信息,因此可执行文件的大小较小
- 内存占用不同:静态库被完整地加载到内存中,因此静态库会增加可执行文件的内存占用;而动态库在运行时才被加载到内存中,多个可执行文件可以共享同一个动态库,从而减少内存占用
- 变动性不同:静态库在编译时就被链接到可执行文件中,如果静态库发生了变化,需要重新编译可执行文件;而动态库可以独立于可执行文件进行更新,不需要重新编译可执行文件,只需替换动态库文件即可
- 兼容性不同:由于动态库在运行时才被加载,可以动态地链接到不同的系统和平台上;而静态库在编译时就被链接到可执行文件中,因此需要针对不同的系统和平台进行编译
55.2 Windows 动态库和静态库
- 静态链接库 .lib 在链接时,编译器会将 .obj 文件和 .LIB 文件组织成一个 .exe 文件,程序运行时,将全部数据加载到内存
- 如果程序体积较大,功能较为复杂,那么加载到内存中的时间就会比较长,最直接的一个例子就是双击打开一个软件,要很久才能看到界面。这是静态链接库的一个弊端
- 动态链接库 DLL:(Dynamic Link Library) 是一个被其他应用程序调用的程序模块,其中封装了可以被调用的资源或函数
- DLL 文件属于可执行文件,它符合 Windows 系统的 PE 文件格式,不过它是依附于 EXE 文件创建的的进程来执行的,不能单独运行
- 动态链接库有两种加载方式:隐式加载和显示加载
- 隐式加载又叫载入时加载,指在主程序载入内存时搜索 DLL,并将 DLL 载入内存。隐式加载也会有静态链接库的问题,如果程序稍大,加载时间就会过长,用户不能接受
- 显式加载又叫运行时加载,指主程序在运行过程中需要 DLL 中的函数时再加载。显式加载是将较大的程序分开加载的,程序运行时只需要将主程序载入内存,软件打开速度快,用户体验好
56. 动态编译与静态编译
57. 友元函数和友元类
-
友元提供了不同类的成员函数之间、类的成员函数和一般函数之间进行数据共享的机制
- 通过友元,一个不同函数或者另一个类中的成员函数可以访问类中的私有成员和保护成员
- 友元的正确使用能提高程序的运行效率,但同时也破坏了类的封装性和数据的隐藏性,导致程序可维护性变差
-
友元函数
- 友元函数是定义在类外的普通函数,不属于任何类,可以访问其他类的私有成员,但是需要在类的定义中声明所有可以访问它的友元函数
- 一个函数可以是多个类的友元函数,但是每个类中都要声明这个函数
class A {
public:
friend void set_show(int x, A &a);
private:
int data;
};
void set_show(int x, A &a) {
a.data = x;
cout << a.data << endl;
}
int main(void){
class A a;
set_show(1, a);
return 0;
}
-
友元类
- 友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员),但是另一个类里面也要相应的进行声明
- 使用友元类注意事项
- 友元关系不能被继承
- 友元关系是单向的,不具有交换性。若类 B 是类 A 的友元,类 A 不一定是类 B 的友元,要看在类中是否有相应的声明
- 友元关系不具有传递性。若类 B 是类 A 的友元,类 C 是 B 的友元,类 C 不一定是类 A 的友元,同样要看类中是否有相应的申明
class A {
public:
friend class C;
private:
int data;
};
class C {
public:
void set_show(int x, A &a) { a.data = x; cout<<a.data<<endl;}
};
int main(void) {
class A a;
class C c;
c.set_show(1, a);
return 0;
}
58. 为什么 C++ 没有垃圾回收机制
- 实现一个垃圾回收器会带来额外的空间和时间开销
- 需要开辟一定的空间保存指针的引用计数和对他们进行标记 mark
- 然后需要单独开辟一个线程在空闲的时候进行 free 操作
59. 内存池及其实现
60. 类的成员变量和成员函数内存分布情况
- 一个类对象的地址就是类所包含的这一片内存空间的首地址,这个首地址也就对应具体某一个成员变量的地址
- 从代码运行结果来看,对象的大小和对象中数据成员的大小是一致的,也就是说成员函数不占用对象的内存,这是因为所有的函数都是存放在代码区的,不管是全局函数,还是成员函数
- 静态成员函数与一般成员函数的唯一区别就是没有 this 指针,因此不能访问非静态数据成员
#include <iostream>
using namespace std;
class Person {
public:
Person() {
this->age = 23;
}
void printAge() {
cout << this->age <<endl;
}
~Person(){}
public:
int age;
};
int main() {
Person p;
cout << "对象地址:"<< &p <<endl;
cout << "age地址:"<< &(p.age) <<endl;
cout << "对象大小:"<< sizeof(p) <<endl;
cout << "age大小:"<< sizeof(p.age) <<endl;
return 0;
}
对象地址:0x7fffec0f15a8
age地址:0x7fffec0f15a8
对象大小:4
age大小:4