1. 各种数据类型的范围
#include<climits>
cout<<"int max:"<<INT_MAX;
2. 更合适的用于遍历数组的数据类型
#include<cstddef>
for(size_t i=0;i<N;i++)
3. 引用的本质是指针常量,指向不能变,值可变
4. malloc和alloc
malloc在堆上申请空间,需要用free手动释放内存;
alloc在栈上申请空间,由操作系统负责释放;
5. c++和c中的结构体
C:在定义结构体对象时,要加上struct;
C++:在定义结构体对象时,不用加struct;
6. define和const的区别
define是单纯的文本替换,没有安全校验,而const有安全检查;
define在预处理阶段起作用,const在编译、运行阶段起作用;
宏定义只是文本替换,不占内存空间,const修饰的变量要给他分配内存空间。
7. static
非类:只在本文件内可用,对其他文件隐藏;
初始化为0;
在函数内定义,始终存在,且只进行一次初始化,作用域和局部变量一样。
类内:成员变量只与类关联,所有对象公用一个变量,在类外初始化;
成员函数不具有this指针,不能使用类的非静态成员,依赖于类存在。
8. 对象所占空间大小
所有非静态成员变量 +(虚函数指针)+ 对齐所用占位;
空类的对象size是1,当作为基类时,大小是0.
9. 拷贝构造和赋值
strint str1="nihao";
拷贝构造:string str = str1;
赋值:string str; str = str1;
10. 未申请内存的对象指针调用成员函数
A* a = NULL;
a->func();
如果func函数内用到了this指针,在运行时会报错;如果没用到this指针,正常执行。
11. CRTP模板
template <typename Derived>
class Base {
public:
void foo() {
static_cast<Derived*>(this)->bar(); // 使用派生类的类型
}
};
class Derived : public Base<Derived> {
public:
void bar() { // 在基类中调用派生类的成员函数
// 执行派生类的特定代码
}
};
这种情况父类的析构函数不应该声明成虚函数,通过模板来实现运行时多态,不需要用到虚函数表。
12. 构造函数不能是虚函数
虚函数需要通过虚函数表调用,在构造函数执行的时候,对象还没有创建,自然没有虚函数表;所以构造函数不能是虚函数。
13. inline内联函数和define宏定义
内联函数在编译期展开,宏定义在预处理时期替换;
内联函数由安全检查,宏没有;
内联函数是个函数,具有函数的特性,比如重载,宏没有。
14. register和volatile
register关键字告诉编译器这个变量要尽可能的放在寄存器,以提高访问速度;
volatile关键字告诉编译器这个变量可能会被意外修改,每次都要从内存中去读;
inline和register都是建议型关键字,即编译器不一定按他说的来。
15. inline修饰构造、析构、虚函数
构造和析构声明成内联函数是没有意义的,因为在class内声明和定义的函数默认都是内联函数,但是编译器不一定会对他内联,还要具体情况具体分析。
虚函数声明成内联函数有两种情况:
指向派生类的指针调用inline虚函数,不会内联展开(一个编译器,一个运行期);
对象本身调用inline虚函数时,可以内联展开(前提是函数并不复杂)。
16. auto,decltype和decltype(auto)
auto:根据初始值自动判断变量类型
auto a = 1;//int
decltype:根据表达式的返回值判断变量类型
decltype(func()) b = 2;//func的返回值类型
decltype(auto):单纯的auto去推导变量类型可能会发生隐式类型转换,如下代码;传递的是vector,而不是vec[0],但用auto推导的话,会隐式转换成int类型,而decltype(auto)会匹配原有类型。
void foo(int x) {
auto y = x; // y 的类型是 int
decltype(auto) z = x; // z 的类型是 vector
std::cout << "y: " << y << ", z: " << z << std::endl;
}
main() {
std::vector<int>vec;
foo(vec);
}
17. 对象做返回值
linux环境下,不管是值返回还是引用返回,都不会发生拷贝构造;
windows + vs环境下,如果是值返回,会发生拷贝构造。
18. plain 、nothrow 、placement new
plain new:默认的new(int* a = new int;),如果申请内存失败,会抛出一个bad_alloc的异常,用返回值是否等于NULL是不能判断申请成功与否的;
nothrow new:(int* a = new(nothrow) int;),如果空间分配失败,会返回NULL;
placement new:不申请内存,在一块已经申请成功的内存上重新构造对象或对象数组,他只做一件事:调用构造函数;在释放的时候,不能使用delete,要显式的调用析构函数。
#include <iostream>
using namespace std;
class MyClass {
public:
MyClass() { /* Constructor code here */ }
~MyClass() { /* Destructor code here */ }
// Other member functions and variables here
};
char* buffer = new(nothrow) char[sizeof(MyClass)+1] // Allocate memory for 10 objects
MyClass* pObject = new(buffer) MyClass; // Construct object in preallocated memory
pObject->~MyClass(); // Call destructor manually to destruct the object
delete buffer; // Deallocate memory
19. thread_local
该关键字和static关键字组合,可以让被修饰的变量在每个线程里都初始化一次,即单个线程内部共享,但多个线程之间不共享。
20. static和virtual
静态成员函数不能声明为虚函数,因为虚函数是通过this指针找到他的虚表指针,进而调用虚函数;但是静态成员函数是依赖于类存在的,没有this指针。
21. static变量的初始化
在C语言里,静态变量(全局和局部)是在编译阶段分配好内存后就进行初始化,所以不能用变量去给静态变量初始化;
在C++里面,静态变量(全局和局部)是在执行相关代码的时候才会初始化,所以可以用变量来给静态变量初始化。
22. C++模板
编译器会对函数模板进行两次编译,一次是在声明的地方对模板本身进行编译,一次是在调用的地方队参数替换后的代码进行编译。模板只有在实例化之后才是真正的函数。
23. delete怎么知道释放内存的大小的
对于数组,在new的时候会额外分配四个字节大小的空间,用来存放数组的大小,在delete的时候,直接取到这个大小进行释放就好了。
24. malloc实现原理
当申请小于128K的空间时,使用brk()来申请内存,将堆的最高地址指针想高地址推;
当申请大于128K的空间时,使用mmap()来申请内存,实在堆和栈之间的文件映射区找到一块内存来分配;
申请到的空间会放在空闲链表,当程序要用到的时候,就去空闲链表中找到第一个符合要求的空间分配过来。
25. 赋值初始化和初始化列表初始化
赋值初始化,即在函数中初始化,是在所有数据成员都分配好空间之后赋值的;
初始化列表初始化是在分配内存的同时进行初始化;
后者效率高些。
26. 内存泄漏检测方式
linux工具:valgrind
sudo apt-get install valgrind
gcc -g -o myprogram myprogram.c
valgrind --tool=memcheck myprogram
valgrind --tool=memcheck --leak-check=full myprogram
27. 函数的存放位置
函数都是存放在代码区的,不管是普通函数、成员函数还是静态函数。
28. 构造函数和析构函数内部调用虚函数
在构造、析构函数内部调用虚函数是不采用动态联编的,执行的就是本类中的方法。
29. 构造、析构抛出异常
在构造函数中抛出异常可能导致对象尚未完全初始化,这可能会导致问题。同样,在析构函数中抛出异常可能导致资源释放失败或不完整,这可能会导致内存泄漏或其他问题。
30. 智能指针
智能指针的本质是一个类,用来存储一个指向动态分配对象的指针和一个引用计数;动态分配的资源,交给一个对象去管理,当类对象的生命周期结束时,自动调用析构函数释放资源。
(shared_ptr的引用计数是线程安全的,但是对象的读取需要加锁)
31. 强制转换
reinterpret_cast:指针之间的类型转换
const_cast:去常加常转换
static_cast:基本数据类型之间的转换、父子类的抓换,但没有类型检查。
dynamic_cast:父子类的转换,有类型检查,如果下行转换是不安全的会返回空。
32. 临时变量做返回值
临时变量的生命周期在函数内部,函数返回后,生命周期就结束了,不能在函数外部使用这个变量,但是该变量的值会保存在寄存器中,所以如果想承接返回值的话,直接以复制的方式保留下来就好了。
(C语言规定:16bit程序中,返回值保存在ax寄存器;32bit程序,保存在eax寄存器;64bit程序,低32bit保存在eax寄存器,高32bit保存在edx寄存器)
33. 浮点数比较
浮点数之间是不能用“==”去进行比较的,结果可能和现实由出入,如果要比较的话,可以通过想减再与某个精度进行比较。
34. 类的静态和动态分配
让类只能静态分配:把new、delete运算符重载成private属性;
让类只能动态分配:把构造、析构设成私有,再用子类来动态创建。
35. 打印字符串到屏幕的过程
1. 执行程序(./xx);
2. 找到程序的相关信息,检查其类型是否为可执行文件,根据首部信息,确定代码和数据在可执行文件中的位置并计算出对应的磁盘块地址;
3. 操作系统创建一个进程,让该进程去执行这个可执行程序;
4. 为程序设置cpu上下文环境,并跳到程序开始处;
5. 执行指令,会发生缺页异常;
6. 操作系统分配一页物理内存,将代码从磁盘读入内存,继续执行程序;
7. 程序执行puts函数(系统调用),在显示器上写字符串;
8. 操作系统找到显示设备,操作系统将字符串发送给控制该显示设备的进程;
9. 进程告诉窗口,要显示字符串,窗口将字符串转换成像素,写入到设备的存储映像区;
10. 视频硬件将像素转换成显示器可接受的一组控制数据信号;
11. 显示器解释信号,激发液晶屏,显示出来。
36. 函数模板和类模板
函数模板可以显示调用也可以隐式调用,类模板必须显示调用(<>)
37. ++ite和ite++
++ite会返回的是引用,ite++返回的是对象。后者会产生临时对象,导致效率降低。
38. 模板函数的声明和定义要写在一个文件里
如果声明和定义不在一个文件,在编译期的时候,编译器找得到模板类或函数的声明而找不到实现,就创建一个符号,希望链接程序找到实现的地址;但是模板类或函数在没被实例化的时候不会被编译成二进制代码,链接器找不到地址就会报错。
39. 成员函数调用delete this
普通成员函数调用:调用delete this会回收对象空间,在这条语句后面如果使用到对象空间的内容了(成员函数,成员变量),会发生不可预期的错误;
析构函数调用:由于delete this语句就回去调用对象的析构函数,会形成无限递归,造成堆栈溢出,系统崩溃。
40. 指针和引用做参数的使用时机
指针:内置数据类型、数组、结构体
引用:结构体、类对象
41. 函数参数的入栈顺序是从右向左
42. 禁止程序自动生成拷贝构造的方法
基类将拷贝构造函数和拷贝赋值构造函数设置成private,这样派生类中编译器将不会自动生成这两个函数。
43. memset(this,0,sizeof(*this))
可以用该语句在构造函数中给类内成员初始化,但是要注意两点:
1. 类中不能有虚函数,这样会破坏虚函数表;
2. 不能有父类,会破坏父类空间。
44. c模拟继承和多态
typedef void (*fun)();//函数指针
struct FA{
fun m_fun;//模拟成员函数
int m_a;
};
struct SON{
FA m_fa;//模拟继承
int m_b;
};
void fun_a(){}
void fun_B(){}
int main(){
FA ca;
SON cb;
ca.m_fun = fun_a;
cb.m_fa.m_fun = fun_b;
FA* pa = (SON*)&ca;
pa->m_fun();//模拟多态
}
45. STL哈希表是用拉链法解决冲突的
46. 在STL容器中删除元素
序列容器:ite = c.erase(ite);
关联容器:c.erase(ite++);
47. map的插入方式
(1) ma.insert(pair<int,int>(1,2));
(2) ma.insert(map<int,int>::value_type(1,2));
(3) ma.insert(make_pair(1,2));
(4) ma[1] = 2;
48. unordered_map和map
使用时,key如果是自定义类型,map的key需要定义operator<,unordered_map的key需要定义hash_value()函数并重载operator==
49. STL中的下标越界
vector中使用下标访问元素,没有越界检查,即使越界,也不会报错,而是返回对应的地址中的值。如果想有越界检查,可以用函数
vec.at(2);//vec[2]
50. vector
vector通过一个连续的数组存放元素,如果集合已满,在新增数据的时候,就要分配一块更大的内存,将原来的数据复制过来,释放之前的内存,再插入新增的元素;(VS中以1.5倍扩容,GCC以2倍扩容)
对vector的任何操作,只要引起了空间的重新分配,指向原vector的所有迭代器就失效了。
和数组的区别:数组在空间不足时,需要手动定义一块跟大的空间,将数据移动到新空间,释放旧空间;而vector这些步骤都由系统自动完成了。
----------------------------
51. 占位符
using namespace std::placeholders;
fun(_1,_2);
52.字符串和各种类型的转换
1. 字符串转整形、浮点型
atoi(str);
atof(str);
2. 字符串转字符数组
str.c_str();
3. 整形、浮点型转字符串
std::stringstream ss;
ss << i;
ss.str();
4. 字符串转十六进制
const char* hex = "0123456789ABCDEF";
std::stringstream ss;
for (auto ch : str)
ss << hex[(unsigned char)ch >> 4] << hex[(unsigned char)ch & 0xF];
ss.str();
53. 管理锁
lock_guard提供自动化的互斥锁管理,确保在离开作用域时释放互斥锁。
std::mutex mtx;//定义锁
std::lock_guard<std::mutex> lock(mtx);//上锁
//超出作用域自动释放锁
54. 线程相关
using namespace std::chrono;
std::thread(func,1);
std::this_thread.sleep_for(100ms);