八股

RAII:Resource Acquisition Is Initialization

RAII要求,资源的有效期与持有资源的对象的生命期严格绑定,即由对象的构造函数完成资源的分配(获取),同时由析构函数完成资源的释放。在这种要求下,只要对象能正确地析构,就不会出现资源泄漏问题。

cache友好:

有些信息在存储时,并不需要占用一个完整的字节, 而只需占几个或一个二进制位。例如在存放一个开关量时,只有0和1 两种状态, 用一位二进位即可。为了节省存储空间,并使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。所谓“位域”是把一个字节中的二进位划分为几个不同的区域, 并说明每个区域的位数。每个域有一个域名,允许在程序中按域名进行操作。 这样就可以把几个不同的对象用一个字节的二进制位域来表示。

使用bit位:节约了空间,但是汇编要多两条指令:先移位,再与位宽常数与。

使用指针而不是数组,避免拷贝,节约内存

DOD:代码可读性与性能的平衡

面向数据设计方法:数据集中存储,同类数据批量处理

结构体初始化

等同于对内存进行memset,造成冗余初始化:memset在内存中的开销很大:复合字面量:根据变量大小自动选择清零方式

计算优化

位运算、乘法代替除法、整数代替浮点数、综合应用

条件与循环

循环次数多的放里面:执行次数和局部性原理一致循环合并:能减少次数,但是可能破坏局部性原理减少冗余判断:短路使用switch代替ifif后面跟最有可能的选项:分支预测循环体提取常数

函数

  • 过多的参数会压入栈,影响性能(8个)
  • 避免值传递:会发生内存拷贝,而是采用const 类型的引用:对于输入参数,拷贝代价小的类型传值,拷贝代价大的类型传const引用
  • 使用内联和宏代替函数,避免栈开销
  • 内联的缺点:展开空间大、只是建议
  • 宏:类型检查,容易出错,不建议使用

代码逻辑

  • 不要在循环内使用全局变量
  • 减少冗余检查

C++面向对象

  • 避免临时对象申请:构造和析构的开销
  • 优先使用初始化列表:只会调用拷贝构造函数,而在里面赋值会先调用默认构造函数和赋值重载

STL

  • vector使用emplace_back代替push_back:emplace_back函数的作用是减少对象拷贝和构造次数,是C++11中的新特性,主要适用于对临时对象的赋值。在使用push_back函数往容器中增加新元素时,必须要有一个该对象的实例才行,而emplace_back可以不用,它可以直接传入对象的构造函数参数直接进行构造,减少一次拷贝和赋值操作。
  • C++11中新增了无序容器,如:unordered_map/unordered_multimap和unordered_set/unordered_multiset容器,在实际插入时,这些容器不在进行排序,因此相对有序的map和set来说效率都有提升。map和set的底层实现是红黑树,对应的无序容器底层实现是Hash Table,拉链法解决冲突,由于内部通过哈希进行快速操作因此效率将会更高。在使用无序容器时,如果是基本类型数据,则不需要提供哈希函数和比较函数,使用方法和普通的map、set是一样的,如果数据类型是自定义的,在使用时需要提供哈希函数和比较函数。和关联式容器相比,无序容器擅长通过指定键查找对应的值(平均时间复杂度为 O(1));但对于使用迭代器遍历容器中存储的元素,无序容器的执行效率则不如关联式容器。multi:可以重复的key
  • constexp关键字:加在函数前面,自动在编译阶段检查参数是不是常数并自动计算:还可以用于数组下标。脏读:A事务在提交前对一个字段的改动会被B事务感知,那么事务之间就很容易产生干扰,假如A对一个字段改动之后被B感知,但是A又回滚了事务,则对该字段的改动依旧保留在B的查询结果中,那么这样的数据就是脏数据(处于处理中间过程的数据)。不可重复读:A事务对于一条记录的读取结果,在B事务对其修改并提交之后,A再次读取同一条记录会得到不同的结果。幻读:侧重于A事务的同一个范围查询命令,前后两次得到不同的记录数量,原因是B事务可能对其进行了插入。
  • 进程间通信方式:管道,消息队列,共享内存,信号量,信号,socket
  • CGI工作原理每次HTTP请求CGI,Web服务器都有启动一个新的进程去执行这个CGI程序,即颇具Unix特色的fork-and-execute。当用户请求量大的时候,这个fork-and-execute的操作会严重拖慢Web服务器的性能。FastCGI技术应运而生。简单来说,其本质就是一个常驻内存的进程池技术,由调度器负责将传递过来的CGI请求发送给处理CGI的handler进程来处理。在一个请求处理完成之后,该处理进程不销毁,继续等待下一个请求的到来。

八股

  • const int * 指向整型常量的指针,它指向的值不能更改 int * const 指向整型的常量指针,它不能再指向别的变量
  • 析构函数为什么一般写成虚函数: 为了配合多态实现:加入一个基类的指针指向一个派生类的对象,如果析构函数不是虚函数,销毁时会调用基类的析构函数,派生类的自身内容就不会被析构,会导致内存泄漏。如果析构函数是虚函数,则会根据指针所指对象的虚函数表执行派生类的析构函数,再执行基类的析构函数。
  • 单例模式:懒汉模式和饿汉模式 ①类中包含⼀个静态成员指针,该指针指向该类的⼀个对象,提供⼀个公有的静态成员⽅法,返回该对象指针; ②为了使得对象唯⼀,构造函数和拷贝构造函数设为私有。
  • 平时做题要想到DP:树枝问题:dp[j]=dp[j-a[i]];零钱问题:dp[j]+=dp[j-a[i]]
  • 所有嵌入式系统都能从系统架构角度上分成四个层次:
  1. 引导加载程序(Boot Loader):由可选的固件代码和 Boot Loader两大部分,一般情况下都指Boot Loader。
  2. Linux内核(Linux Kernel):区别于通用计算机的系统内核,基于特定的嵌入式板子的定制内核。
  3. 文件系统(File System):一种基于flash存储设备的文件系统,主要用来存储和组织计算机相关的数据。
  4. 用户应用程序 (User Application): 为了完成某项或某几项特定任务而被开发运行于特定操作系统(嵌入式系统)之上的应用程序。众所周知,PC系统的引导过程主要由固化在计算机主板上的固件程序BIOS来完成。BIOS的主要功能是对计算机进行硬件控制从而完成相关的硬件自检与硬件资源的分配,在相关硬件初始化完成后,BIOS会把控制权移交给PC系统的Boot Loader。此时Boot Lader将相应的Kernel Image从硬盘上搬运到RAM中,接着程序掉转到Kernel的入口点,接下来便是启动操作系统了。
  • 右值引用的使用场景如下:

移动语义:右值引用可以用于实现移动语义,这样可以避免重复构造对象,减少内存分配和复制的时间。因此,右值引用常用于函数返回值。

容器操作:右值引用可以被用于 STL 容器,如 vector、list 等,以实现快速插入和删除元素的操作。

临时对象的存储:右值引用可以用于存储临时对象,避免进行复制操作,从而提高程序效率。

返回大对象:右值引用可以用于返回大对象,避免复制和构造的操作,从而减少内存消耗。

  • constexp关键字:加在函数前面,自动在编译阶段检查参数是不是常数并自动计算:还可以用于数组下标。

C++11之后,懒汉模式使用局部静态变量不需要加锁

webserver的数据库连接

1.mysql_init()初始化连接2.使用mysql_real_connect()建立一个到mysql数据库的连接3.使用mysql_query()执行查询语句4.使用result = mysql_store_result(mysql)获取结果集5.使用mysql_num_fields(result)获取查询的列数,mysql_num_rows(result)获取结果集的行数6.通过mysql_fetch_row(result)不断获取下一行,然后循环输出7.使用mysql_free_result(result)释放结果集所占内存8.使用mysql_close(conn)关闭连接</br>

C++考试:第一次17分,g

  • C++ 新的标准中提倡使用匿名命名空间,而不推荐使用static,因为static用在不同的地方,涵义不同容易造成混淆。比如:带static的类成员为类共享,而变量前的static又表示内部链接、存储范围。另外,static不能修饰class定义,那样就可以将类定义放在匿名命名空间中达到同样的效果。 -快速计算一个数字的平方根的倒数: i = 0x5f3759df - ( i >> 1 );

虚函数表

空类中如果加一个虚函数或者多个,实际上空类是有一个虚指针。查看虚函数表:

typedef void (*Func)();
Derive d;
Base1 &b1 = d;
Base2 &b2 = d;
cout << "Derive对象所占的内存大小为:" << sizeof(d) << endl;//8
	
cout << "\n---------第一个虚函数表-------------" << endl;
	long* tmp1 = (long *)&d;              // 获取第一个虚函数表的指针
	long* vptr1 = (long*)(*tmp1);         // 获取虚函数表

	Func x1 = (Func)vptr1[0];
	Func x2 = (Func)vptr1[1];
	Func x3 = (Func)vptr1[2];
	Func x4 = (Func)vptr1[3];
	x1();x2();x3();x4();//ABC  MyA

指针的*号要紧随变量

getline(cin,s)

shell编程

1.head和tail查看几行

tail -5 filename

  1. grep -c 统计匹配的行数

wc -l filename 查看多少行+文件名

awk '{...}' 标准输出

cat ./nowcoder.txt | awk NF

去掉空行

epoll :一种IO多路复用的技术

int s = socket(AF_INET, SOCK_STREAM, 0);bind(s, ...);listen(s, ...)

int epfd = epoll_create(...);epoll_ctl(epfd, ...); //将所有需要监听的socket添加到epfd中

while(1) {int n = epoll_wait(...);for(接收到数据的socket){//处理}}第一点,epoll 在内核里使用红黑树来跟踪进程所有待检测的文件描述字,把需要监控的 socket 通过 epoll_ctl() 函数加入内核中的红黑树里,红黑树是个高效的数据结构,增删改一般时间复杂度是 O(logn)。而 select/poll 内核里没有类似 epoll 红黑树这种保存所有待检测的 socket 的数据结构,所以 select/poll 每次操作时都传入整个 socket 集合给内核,而 epoll 因为在内核维护了红黑树,可以保存所有待检测的 socket ,所以只需要传入一个待检测的 socket,减少了内核和用户空间大量的数据拷贝和内存分配。

第二点, epoll 使用事件驱动的机制,内核里维护了一个链表来记录就绪事件,当某个 socket 有事件发生时,通过回调函数内核会将其加入到这个就绪事件列表中,当用户调用 epoll_wait() 函数时,只会返回有事件发生的文件描述符的个数,不需要像 select/poll 那样轮询扫描整个 socket 集合,大大提高了检测的效率。

C++多态实现的原理:

全局变量和全局静态变量的析构顺序也是未定义的

什么是Core Dump?Core的意思是内存, Dump的意思是扔出来, 堆出来.开发和使用Unix程序时, 有时程序莫名其妙的down了, 却没有任何的提示(有时候会提示core dumped). 这时候可以查看一下有没有形如core.进程号的文件生成, 这个文件便是操作系统把程序down掉时的内存内容扔出来生成的, 它可以做为调试程序的参考.core dump又叫核心转储, 当程序运行过程中发生异常, 程序异常退出时, 由操作系统把程序当前的内存状况存储在一个core文件中, 叫core dump.

如何使用core文件?gdb -c core文件路径 [应用程序的路径]进去后输入where回车, 就可以显示程序在哪一行当掉的, 在哪个函数中.

为什么没有core文件生成呢?有时候程序down了, 但是core文件却没有生成. core文件的生成跟你当前系统的环境设置有关系, 可以用下面的语句设置一下, 然后再运行程序便成生成core文件.ulimit -c unlimitedcore文件生成的位置一般于运行程序的路径相同, 文件名一般为core.进程号

  1. 用gdb查看core文件: 下面我们可以在发生运行时信号引起的错误时发生core dump了. 发生core dump之后, 用gdb进行查看core文件的内容, 以定位文件中引发core dump的行. gdb [exec file] [core file] 如: gdb ./test test.core 在进入gdb后, 用bt命令查看backtrace以检查发生程序运行到哪里, 来定位core dump的文件->行.

使用vector、tuple、struct来返回多个值:使用指针避免拷贝开销

C++ 的类有四类特殊成员函数,它们分别是:默认构造函数、析构函数、拷贝构造函数以及拷贝赋值运算符。

sprintf(s,"%f",n)是十分危险的

  • 使用stringstream切分每一行
string s;
stringstream ss;
int n, i, sum, a;
cin >> n;
getline(cin, s); // 讀取換行
for (i=0; i<n; i++)
{
    getline(cin, s);
    ss.clear();
    ss.str(s);
    sum=0;
    while (1)
    {
        ss >> a;
        if ( ss.fail() ) break;
        sum+=a;
    }
    cout << sum << endl;
}

多次转换必须clear

template<class out_type,class in_value>
out_type convert(const in_value & t){
    stringstream stream;
    stream<<t;//向流中传值
    out_type result;//这里存储转换结果
    stream>>result;//向result中写入值
    return result;
}

4、this。函数体内可以使用Lambda所在类中的成员变量。5、a。将a按值进行传递。按值进行传递时,函数体内不能修改传递进来的a的拷贝,因为默认情况下函数是const的。要修改传递进来的a的拷贝,可以添加mutable修饰符。

ss << setw(len) << setfill('0') << i;//调整输出长度

C++标准库中的 iostream 类就是一个虚继承的实际应用案例。iostream 从 istream 和 ostream 直接继承而来,而 istream 和 ostream 又都继承自一个共同的名为 base_ios 的类,是典型的菱形继承。此时 istream 和 ostream 必须采用虚继承,否则将导致 iostream 类中保留两份 base_ios 类的成员。explicit作用:禁止隐式类型转换

使用lambda表达式直接传入函数指针:

 sort(arr.begin(),arr.end(),[](pair<string,int> &a,pair<string,int> &b){
        if(a.second == b.second){
            return a.first < b.first;
        }
        return a.second > b.second;
    });

vector里面的元素可以是pairgreater<>() 和 less<>() 都是头文件<functional>中的函数。仿函数,就是在类中重载()运算符,使得一个类表现得像一个函数。STL的sort用的优化:(1)深度太深时使用堆排序_Ideal /= 2, _Ideal += _Ideal / 2;    // allow 1.5 log2(N) divisions这个注释不对,实际上应该是log(N)/log(4/3)次划分,大概等于2.4 log2(N)_Ideal 这个量没有具体的含义,它既是一开始等于N,然后每次乘以3/4,当它变为0时我就当做深度太深,剩下的交给堆排序(2)元素太少时使用插入排序这里的常量_ISORT_MAX = 32,即当递归到只有32个元素时,对这个小片段采取插入排序。片段花费时间3231 如果N个元素分割成N/32个片段,每个片段都是32个元素,都采取插入排序,那么总时间是: N/32 * 3231 + N* log2(N/32) = N* (log2(N) +26)这个结果可以认为就是N* log2(N)

    for (auto it = v.begin(); it != v.end();)
    {
        if (*it == key) {
            it = v.erase(it);
        }
        else {
            ++it;
        }
    }

在C++中,虚函数是动态绑定的,但函数的缺省参数却是在编译时就静态绑定的。在C++语言中,继承的同时也实现了派生类型化,所以继承以后必须遵循里氏替换原则(简单来说,就是基类出现的地方,换成任何派生类也可以正常工作),如果代码中使用了dynamic_cast, 需要关注是否破坏了里氏替换原则。

thread_local对象声明时赋值,作为类成员时必须是static的,也是对于每个thread分别分配了一个,而static则是全局一个

同时作为输入和输出的参数,应优先使用非const引用类型而不是指针类型,除非该参数可能为无效值

对于在函数内部一定会被复制或移动的参数,当移动代价较小(按值传递多出的一次移动操作可被接受)时,才适合考虑按值传递。否则,应该按引用传递。另外,按值传递可能产生切片问题,所以有派生类的类型不应按值传递。事实上,有派生类的类型的拷贝构造函数应该被删除,自然也就没法按值传递了。

当输入或返回的对象总是有效时,优先使用引用取代指针。引用比指针更安全,因为它一定非空,且一定不会再指向其他目标,不需要做非空判断。

外部数据要校验:
  • 作为数组索引
  • 作为内存偏移地址
  • 作为内存分配的尺寸参数
  • 作为循环条件
  • 作为除数
  • 作为命令行参数
  • 作为数据库查询语句的参数
  • 作为输入/输出格式化字符串
  • 作为内存复制长度
  • 作为文件路径
  • 直接打开不可信路径,可能会导致目录遍历攻击,攻击者操作了无权操作的文件,使得系统被攻击者所控制。
 void Foo(const unsigned char* buffer, size_t len)
{
// buffer可能为空指针,不保证以'\0'结尾
const char* s = reinterpret_cast<const char*>(buffer);
size_t nameLen = strlen(s);
std::string name(s, nameLen);
Foo2(name);
...
}


由于构造函数没有返回值,无法通过返回错误码的形式报告错误,因此在允许使用异常的情况下,应当使用异常来报告错误。对象构造在堆上时,如果构造函数抛出异常,那么对象的堆内存会被自动释放。但是要注意:如果构造函数分配了其他关联的资源,则需要程序员保证资源可以被全部释放。使用智能指针和RAII有助于保证资源被正确释放。

strnlen 内存安全的strlen

这里引入了移动语义,所谓移动语义(Move语义),指的就是以移动而非深拷贝的方式初始化含有指针成员的类对象。对于程序执行过程中产生的临时对象,往往只用于传递数据(没有其它的用处),并且会很快会被销毁。因此在使用临时对象初始化新对象时,我们可以将其包含的指针成员指向的内存资源直接移给新对象所有,无需再新拷贝一份,这大大提高了初始化的执行效率。

A+B:把每一位转化成数字存进vector,只考虑长的vectorA-B: string result = "";

    int lastBorrow = 0;
    for (int ai = input.a.size()-1, bi = input.b.size()-1; ai >= 0 || bi >= 0; ai--, bi--) {
        int atemp = ai >= 0 ? int(input.a[ai]) - int('0') : 0;
        int btemp = bi >= 0 ? int(input.b[bi]) - int('0') : 0;
        int curBorrow = atemp - lastBorrow < btemp ? NUMERATION : 0;
        int alltemp = atemp + curBorrow - lastBorrow - btemp;
        result = to_string(alltemp) + result;
        lastBorrow = curBorrow / NUMERATION;
    }
    if (result[0] == '0') {
        result.erase(result.begin(), result.begin() + result.find_first_not_of('0'));
    }

algorithm 中的reverse函数

#include <memory>
#include <vector>

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

int main()
{
    auto p = make_unique<std::vector<int>>();
    return 0;
}


for(auto &a:b),循环体中修改a,b中对应内容也会修改

for(auto a:b),循环体中修改a,b中内容不受影响

for(const auto &a:b),a不可修改,用于只读取b中内容

派生类重写基类虚函数时,与基类虚函数返回值类型不同。原来的返回类型是指向基类的指针或引用,新的返回类型是指向派生类的指针或引用,覆盖的方法就可以改变返回类型。这样的类型称为协变返回类型inline函数可以是虚函数吗?不能,因为inline函数没有地址,无法把地址放到虚函数表中。静态成员可以是虚函数吗?不能,因为静态成员函数没有this指针,使用类型::成员函数的调用方式无法访问虚函数表,所以静态成员函数无法放进虚函数表。

mmap:memory map

mmap 技术有如下特点:利用 DMA 技术来取代 CPU 来在内存与其他组件之间的数据拷贝,例如从磁盘到内存,从内存到网卡;用户空间的 mmap file 使用虚拟内存,实际上并不占据物理内存,只有在内核空间的 kernel buffer cache 才占据实际的物理内存;mmap()函数需要配合 write()系统调动进行配合操作,这与 sendfile()函数有所不同,后者一次性代替了 read()以及 write();因此 mmap 也至少需要 4 次上下文切换;mmap 仅仅能够避免内核空间到用户空间的全程 CPU 负责的数据拷贝,但是内核空间内部还是需要全程 CPU 负责的数据拷贝;调用的整个流程如下:用户进程调用 mmap(),从用户态陷入内核态,将内核缓冲区映射到用户缓存区;DMA 控制器将数据从硬盘拷贝到内核缓冲区(可见其使用了 Page Cache 机制);mmap()反回,上下文从内核态切换回用户态;用户进程调用 write(),尝试把文件数据写到内核里的套接字缓冲区,再次陷入内核态;CPU 将内核缓冲区中的数据拷贝到的套接字缓冲区;DMA 控制器将数据从套接字缓冲区拷贝到网卡完成数据传输;write()返回,上下文从内核态切换回用户态。多个线程以只读的方式同时访问一个文件,这是因为 mmap 机制下多线程共享了同一物理内存空间,因此节约了内存。案例:多个进程可能依赖于同一个动态链接库,利用 mmap 可以实现内存仅仅加载一份动态链接库,多个进程共享此动态链接库。mmap 非常适合用于进程间通信,这是因为对同一文件对应的 mmap 分配的物理内存天然多线程共享,并可以依赖于操作系统的同步原语;

拆数问题:使用DP

int beibao(int k,int n) {
    // n 个水果分到 k 个盘子
    if(k <= 1 || n <=1) {
        return 1;
    }
    if(n<k) {
        return beibao(n,n);
    } else {
        return beibao(k,n-k)+ beibao(k-1,n);
    }
}

N个有序链表合并

class Solution {
public:
    struct Status {
        int val;
        ListNode *ptr;
        bool operator < (const Status &rhs) const {
            return val > rhs.val;
        }
    };

    priority_queue <Status> q;

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        for (auto node: lists) {
            if (node) q.push({node->val, node});
        }
        ListNode head, *tail = &head;
        while (!q.empty()) {
            auto f = q.top(); q.pop();
            tail->next = f.ptr; 
            tail = tail->next;
            if (f.ptr->next) q.push({f.ptr->next->val, f.ptr->next});
        }
        return head.next;
    }
};


Qt是一个跨平台的C++ GUI工具包,它的底层绘图是通过使用系统提供的图形API,如WinAPI、X11或Cocoa。

Qt的事件很容易和信号槽混淆。signal由具体对象发出,然后会马上交给由connect函数连接的slot进行处理;

而对于事件,Qt使用一个事件队列对所有发出的事件进行维护,当新的事件产生时,会被追加到事件队列的尾部,前一个事件完成后,取出后面的事件接着再进行处理。

但是,必要的时候,Qt的事件也是可以不进入事件队列,而是直接处理的。并且,事件还可以使用“事件过滤器”进行过滤,比如一个按钮对象, 我们使用这个按钮对象的时候, 我们只关心它被按下的信号, 至于这个按钮如何接收处理鼠标事件,再发射这个信号,我们是不用关心的。

但是如果我们要重载一个按钮的时候,我们就要面对event了。 比如我们可以改变它的行为,在鼠标按键按下的时候(mouse press event) 就触发clicked()的signal而不是通常在释放的( mouse release event)时候。

总结的说,Qt的事件和Qt中的signal不一样。 后者通常用来使用widget, 而前者用来实现widget。 如果我们使用系统预定义的控件,那我们关心的是信号,如果自定义控件我们关心的是事件。

Qt实现定时器:

Timer *timer = new QTimer(this);
connect(timer, SIGNAL(timeout()), this, SLOT(update()));
timer->start(1000);

lambda表达式的实现原理:使用匿名类:捕获的是private,构造的时候初始化;使用函数体重载 operator(),并且有一个抛出:throw()lambda 表达式中的 mutable,表明 lambda_xxxx 类成员函数 operator() 的是否具有常属性 const,即是否是常成员函数;值捕获:private 成员的类型与捕获变量的类型一致引用捕获:private 成员的类型是捕获变量的引用类型,这样就可以在类里面修改外面的参数了:则外部作用域的变量的生命周期可能会影响 Lambda 表达式内部的变量。当没有捕获时:lambda 表达式没有捕获任何外部变量,即 lambda_xxxx 类没有任何成员变量,在 operator() 中也就不会用到任何成员变量,也就是说,operator() 虽然是个成员函数,它却不依赖 this 就可以调用。

class lambda_xxxx
{
private:
    int a;
    int b;
public:
    lambda_xxxx(int _a, int _b) :a(_a), b(_b)
    {
    }
    bool operator()(int x, int y) throw()
    {
        return a + b > x + y;
    }
};
void LambdaDemo()
{
    int a = 1;
    int b = 2;
    lambda_xxxx lambda = lambda_xxxx(a, b);
    bool ret = lambda.operator()(3, 4);
}

动态规划:1,2,3三种组成N有几种

int main()
{  
  // please define the C++ input here. For example: int a,b; cin>>a>>b;;  
  // please finish the function body here.  
  // please define the C++ output here. For example:cout<<____<<endl; 
    int N;
    while(cin >> N){
        int dp[4][N+1];
        for(int i = 0;i < 4;i++){
            for(int j = 0;j < N+1;j++){
                if(i <= 1 || j <= 1){
                    dp[i][j] = 1;
                    }
                else if(j >= i)
                    dp[i][j] = dp[i][j-i] + dp[i-1][j];
                else  dp[i][j] = dp[i-1][j];  
            } 
        }
    cout << dp[3][N] << endl;
    }
  return 0;
}

PHONY是一个伪目标,可以防止在Makefile中定义的只执行命令的目标和工作目录下的实际文件出现名字冲突,另一种是提交执行makefile时的效率。

总之为了在使用 pthread 时避免线程的资源在线程结束时不能得到正确释放,从而避免产生潜在的内存泄漏问题,在对待线程结束时,要确保该线程处于 detached 状态,否着就需要调用 pthread_join() 函数来对其进行资源回收。

为输出流使用单独的 mutex。这么做是因为 IO 流并不是线程安全的!

gdb调试线程

启动 gdb:在命令行中输入 gdb 命令,然后在 gdb 中输入要调试的程序的路径和名称,例如 gdb /path/to/your/program。

运行程序:在 gdb 中使用 run 命令运行程序。

产生堆栈信息:当程序运行到一个断点或者出现了一个错误时,gdb 会停止程序的执行并且产生一个堆栈信息。

查看堆栈信息:在 gdb 中使用 backtrace 或者简写为 bt 命令来查看堆栈信息。backtrace 命令会列出当前程序调用栈的函数名和行号。

如果需要进一步查看某个函数的堆栈信息,可以使用 frame 命令来切换到该函数的堆栈信息。例如,如果想要查看第二帧的堆栈信息,可以输入 frame 2 命令。

除了 backtrace 命令以外,还有一些其他的命令可以用来查看进程的堆栈信息,例如 info stack 命令可以列出所有的堆栈信息,up 和 down 命令可以在不同的堆栈帧之间切换。这些命令都可以在 gdb 中使用 help 命令查看详细的帮助信息。

gdb ./test test.core

快手C++

上来先手撕智能指针对象到bool类型转换:需要在类里面定义类型转换函数:

 // 如果status等于1就返回true,否则返回false
    operator bool() const { return status == 1; }

C++:多态如何实现?重载和虚函数虚函数具体原理?虚函数表+vptr,很细堆和栈?

  • 在Windows操作系统中,最好的方式使用VirtualAlloc分配内存。不是在堆,不是在栈,而是在内存空间中保留一块内存,虽然用起来不方便,但是速度快,也很灵活。
  • new有一些灵活性上的局限,其中一方面表现在它将内存分配和对象构造组合在了一起。类似的,delete将对象析构和内存释放组合在了一起。我们分配单个对象时,通常希望将内存分配和对象初始化组合在一起。因为在这种情况下,我们几乎肯定知道对象应有什么值。当分配一大块内存时,我们通常计划在这块内存上按需构造对象。在此情况下,我们希望将内存分配和对象构造分离。这意味着我们可以分配大块内存,但只在真正需要时才真正执行对象的创建操作(同时付出一定开销)。一般情况下,将内存分配和对象构造组合在一起可能会导致不必要的浪费。
  • 如果是一个引用捕获,要注意什么?lambda的周期不能大于被捕获的变量的周期,否则会导致使用已经被析构的地址。
  • 在编译的时候,编译器通常不为const变量分配存储空间,而是保存在了符号表中;如果想在类中共享一个常量,想当然的会使用 const 常量,但是这是不对的,因为 const 常量只在对象的生存期内是常量,但是对于整个类是可变的,因为一个类可以创建多个对象,不同的对象 const 成员的值可以不同。不能再类声明中初始化 const 数据成员,const 成员变量的初始化只能在构造函数过程中
  • static的作用:静态函数会被自动分配在一个一直使用的存储区,直到退出应用程序实例,避免了调用函数时压栈出栈,速度快很多。不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名,因为同名也没有关系。
  • 全局变量是不显式用 static 修饰的全局变量,全局变量默认是有外部链接性的,作用域是整个工程,在一个文件内定义的全局变量,在另一个文件中,通过 extern 全局变量名的声明,就可以使用全局变量。
  • typedef std::function<int(int)> Functional;参数是int,返回值也是int

项目:

因为数据的处理只能放到固定大小的缓冲中进行处理,即上述的1024字节的缓冲中,那么如果很多数据都读取到这块区域的话,那么肯定是放不下的,我们就可以利用一个临时的缓冲区,把放不下的数据先放到临时的缓冲的位置,等到1024字节大小的内存有剩余的空间的时候我再将临时缓冲区的数据放入到1024的位置进行处理,那么何时1024缓冲中有空闲的位置呢?原理及实现如下图所示:

  • 可以从上图看出,如果我们想要写入数据,那么此时可以利用的空间就是最前面的部分和最后面的部分的位置,但是写入数据一定要连续,所以唯一的办法就是将中间的数据移动到最前面,这样就可以将空闲的区域连接在一起,方便后面的写数据。具体的实现就是将读指针到写指针之间的数据复制到最前面,再更改读指针和写指针的位置,这就是利用一个缓冲实现自动增长的原理,
  • Reactor是:同步阻塞I/O模式,注册对应读写事件处理器,等待事件发生进而调用事件处理器处理事件主线程往epoll内核上注册socket读事件,主线程调用epoll_wait等待socket上有数据可读,当socket上有数据可读的时候,主线程把socket可读事件放入请求队列。睡眠在请求队列上的某个工作线程被唤醒,处理客户请求,然后往epoll内核上注册socket写请求事件。主线程调用epoll_wait等待写请求事件,当有事件可写的时候,主线程把socket可写事件放入请求队列。睡眠在请求队列上的工作线程被唤醒,处理客户请求。

Proactor: 异步I/O模式主线程调用aio_read函数向内核注册socket上的读完成事件,并告诉内核用户读缓冲区的位置,以及读完成后如何通知应用程序,主线程继续处理其他逻辑,当socket上的数据被读入用户缓冲区后,通过信号告知应用程序数据已经可以使用。应用程序预先定义好的信号处理函数选择一个工作线程来处理客户请求。工作线程处理完客户请求之后调用aio_write函数向内核注册socket写完成事件,并告诉内核写缓冲区的位置,以及写完成时如何通知应用程序。主线程处理其他逻辑。当用户缓存区的数据被写入socket之后内核向应用程序发送一个信号,以通知应用程序数据已经发送完毕。应用程序预先定义的数据处理函数就会完成工作。

  • 利用IO多路复用模型,可以实现一个线程监视多个文件句柄;一旦某个文件句柄就绪,就能够通知到对应应用程序进行相应的读写操作;没有文件句柄就绪时就会阻塞应用程序,从而释放出CPU资源。 复用是指复用一个或多个线程资源。
  • ET和LT?LT 模式下,读事件触发后,可以按需收取想要的字节数,不用把本次接收到的数据收取干净(即不用循环到 recv 或者 read 函数返回 -1,错误码为 EWOULDBLOCK 或 EAGAIN);ET 模式下,读事件必须把数据收取干净,因为你不一定有下一次机会再收取数据了,即使有机会,也可能存在上次没读完的数据没有及时处理,造成客户端响应延迟。LT 模式下,不需要写事件一定要及时移除,避免不必要的触发,浪费 CPU 资源;ET 模式下,写事件触发后,如果还需要下一次的写事件触发来驱动任务(例如发上次剩余的数据),你需要继续注册一次检测可写事件。LT 模式和 ET 模式各有优缺点,无所谓孰优孰劣。使用 LT 模式,我们可以自由决定每次收取多少字节(对于普通 socket)或何时接收连接(对于侦听 socket),但是可能会导致多次触发;使用 ET 模式,我们必须每次都要将数据收完(对于普通 socket)或必须理解调用 accept 接收连接(对于侦听socket),其优点是触发次数少。
  • EPOLLONESHOT:epoll模型的ET模式一般来说只触发一次,然而在并发程序中有特殊情况的存在,譬如当epoll_wait已经检测到socket描述符fd1,并通知应用程序处理fd1的数据,那么处理过程中该fd1又有新的数据可读,会唤醒其他线程对fd1进行操作,那么就出现了两个工作线程同时处理fd1的情况,这当然不是我们期望看到的。此规定操作系统最多触发其上注册的一个可读或者可写或者异常事件,且只触发一次,如此无论线程再多,只能有一个线程或进程处理同一个描述符。当然处理完之后线程要重置这个epolloneshot事件,进而当此描述符有事件可读时让其他进程可以处理此描述符。从工作线程函数work来看,如果一个线程处理完某个socket上的请求之后,又接收到该socket的请求,那么该线程继续为该socket服务,如果5S之后没有接收到该socket请求,则放弃为此socket服务,同时重置该socket事件,进而使其他线程有机会为该socket服务。
  • EAGAIN:非阻塞+循环读;判断读完了,其实不算一种错误
  • 发送一个HTTP请求,服务器生成响应,怎么判断这个响应是否完整然后浏览器进行渲染?请求:头部行尾两个换行符,可能有长度:分块响应:先看是否是短连接,read()!=-1;长连接:和请求一样
  • 内存映射是有名还是匿名?文件映射就是磁盘中的数据通过文件系统映射到内存再通过文件映射映射到虚拟空间,这样,用户就可以在用户空间通过 open ,read, write 等函数区操作文件内容。至于实际的代码,open,read,write,close,mmap... 操作的虚拟地址都属于文件映射。匿名映射就是用户空间需要分配一定的物理内存来存储数据,这部分内存不属于任何文件,内核就使用匿名映射将内存中的某段物理地址与用户空间一一映射,这样用户就可用直接操作虚拟地址来范围这段物理内存。比如使用malloc申请内存。
  • 用epoll实现一个单线程的FTP文件传输器,
while(1){
开始epoll_wait;
if(如果sfd可读){ //有客户端请求
    new_fd=accept();
    分配一个空闲的进程
    把new_fd传递给这个进程
    把这个进程标记为忙碌
  }
  if(管道在父进程的一端可读){ //子进程已经完成传输任务
    就把该管道对应的子进程标识为非忙碌
  }
子进程流程
   while(1)
   {
     1、等待接收父进程发过来的new_fd
     2、发送文件给客户端
     3、通知父进程非忙碌
   }


  • 怎么判断网络拥塞?超时重传、重复ack
  • 超时重传什么时候关闭?3次后依然不成功,直接发送RST关闭连接。
  • webserver压力测试怎么测试的
  • 每秒真正成功的请求数量(每秒真正请求的qp),耗时多少
  • 请求图片多大、放在哪
  • socket整个流程
  • 客户端、服务端有什么表现(socket状态会发生什么变化),代码处理逻辑,会返回一个什么状态,你怎么知道他关闭了、服务端什么时候关闭、怎么关闭
  • 线程池开多大、消耗多少内存1024
  • 多线程客户端可以使用一个socket吗?怎么保证线程得到想要的数据:使用UDP对于 TCP,通常多线程读写同一个 socket 是错误的设计,因为有 short write 的可能。假如你加锁,而又发生 short write,你是不是要一直等到整条消息发送完才解锁(无论阻塞IO还是非阻塞IO)?对于TCP,正确的做法是,创建一个单独的发送线程,让发送线程统一发送数据。例如你的工作线程池处理完了业务task,你把需要回复的包封装成一个发送task,包含发送套接字和消息,丢给发送线程就行了。
  • Poll优点:解决了select的1024限制(定义一个结构体数组,可以自己设大小),可以无数个;解决了fd复用,因为结构体除了有events,还有revents;
malloc底层原理:malloc 函数实在虚拟地址空间中划分一片区域,而没有与物理页对应。

1)当开辟的空间小于 128K 时,malloc 的底层实现是调用 brk()系统调用函数来在虚拟地址空间分配内存,其主要移动指针 _enddata (此时的 _enddata 指的是 Linux 地址空间中堆段的末尾地址,不是数据段的末尾地址,因为堆地址是向高地址增长的。)

2)当开辟的空间大于 128K 时,malloc 的底层实现是 mmap() 系统调用函数在 虚拟地址空间中分配空间。这时候不再是单纯的堆高 _endata指针而是在堆和栈中间,称为“文件映射区域”的地方,找一块空间来开辟。

上述只是完成了虚拟内存地址的分配(即,建立其虚拟内存和物理内存之间的映射关系),还没有实际的分配内存(相当于只是声明来占用虚拟内存地址,而没有进行定义占用实际的物理页,所以 malloc 申请的内存才没有初始化)。

而当进程第一次对分配的内存进行访问的时候,通过查找页表,发虚拟内存对应的页没有在物理内存中缓存,则发送缺页中断,这时候 OS 内核 才负责分配并以页位单位(4096)加载到物理内存(一般只加载缺页,或更多页)。。

C++11 新增move语义:源对象资源的控制权全部交给目标对象,可以将原对象移动到新对象, 用于a初始化b后,就将a析构的情况;2.移动构造函数的参数和拷贝构造函数不同,拷贝构造函数的参数是一个左值引用,但是移动构造函数的初值是一个右值引用;3.临时对象即将消亡,并且它里面的资源是需要被再利用的,这个时候就可以使用移动构造。移动构造可以减少不必要的复制,带来性能上的提升。条件变量:解决同步问题,notify_all1、默认构造函数;2、默认拷贝构造函数;3、默认析构函数;4、默认重载赋值运算符函数;5、默认重载取址运算符函数;6、默认重载取址运算符const函数;7、默认移动构造函数(C++11);8、默认重载移动赋值操作符函数(C++11)。

sizeof是一个操作符,而strlen是库函数。 sizeof的参数可以是数据的类型,也可以是变量,而strlen只能以结尾为'\0'的字符串作参数。 编译器在编译时就计算出了sizeof的结果,而strlen必须在运行时才能计算出来。 sizeof计算数据类型占内存的大小,strlen计算字符串实际长度。

GET⽅法只产⽣⼀个TCP数据包,浏览器会把请求头和请求数据⼀并发送出去,服务器响 应200 ok(返回数据)POST会产⽣两个TCP数据包,浏览器会先将请求头发送给服务器,待服务器响应100 continue,浏览器再发送请求数据,服务器响应200 ok(返回数据)

在状态管理方面,线程同步中使用std::unique_lock或std::lock_guard对互斥量std::mutex进行状态管理也是RAII的典型实现,通过这种方式,我们再也不用担心互斥量之间的代码出现异常而造成线程死锁。

浏览器根据页面内容,生成DOM Tree。根据CSS内容,生成CSS Rule Tree(规则树)。调用JS执行引擎执行JS代码。根据DOM Tree和CSS Rule Tree生成Render Tree(呈现树)根据Render Tree渲染网页 浏览器是一个边解析边渲染的过程。首先浏览器解析HTML文件构建DOM树,然后解析CSS文件构建渲染树,等到渲染树构建完成后,浏览器开始布局渲染树并将其绘制到屏幕上

在三种情况下会调用拷贝构造函数(可能有纰漏),第一种情况是函数形实结合时,第二种情况是函数返回时,函数栈区的对象会复制一份到函数的返回去,第三种情况是用一个对象初始化另一个对象时也会调用拷贝构造函数。

缺点是静态变量和函数的初始化顺序不确定

std::move的函数模型

template 
typename remove_reference::type&& move(T&& t)
{
    return static_cast::type&&>(t);

全部评论

相关推荐

点赞 1 评论
分享
牛客网
牛客企业服务