2018年秋招面试经验总结
作为一名渣硕,秋招过程并不顺利,面了好多家也凉了好多家,最后收获的不是最想去的城市,后面想再看看。这里先把自己秋招过程中面试印象比较深的一些面经先写下来,希望后面的学弟学妹们能早做准备,不要像我一样目标不清晰,准备也不充分,最后撞得头破血流的......
鼎桥通信C++面经
鼎桥是我面的最早的一家,完全没怎么复习就去了,结果就很尴尬了....
类与结构体的区别
默认的访问权限:
(1)C++结构体内部成员变量及成员函数默认的访问级别是public,而c++类的内部成员变量及成员函数的默认访问级别是private。
(2) C++结构体的继承默认是public,而C++类的继承默认是private。
引用和指针
引用是一个变量的别名,不能为空,必须在声明的时候初始化,而且之后不能修改为其他的变量的别名;
指针的值是一块内存的地址,可以为空,可以先声明后初始化,后面可以修改其指向的内存地址。
析构函数 虚析构函数
-
析构函数
~Date() { cout << "~Date()" << this << endl; } private:
析构函数是在对象消亡的时候调用,用于回收内存。如果没有自行定义析构函数,编译器会生成一个默认的析构函数,函数体执行结束的时候会自动调用析构函数。
如果是用new定义了一个在堆上的对象 A *p=new A; 那么必须使用delete p; 才会调用析构函数回收内存。 -
虚析构函数
https://www.cnblogs.com/liushui-sky/p/5824919.html
https://blog.csdn.net/xld_hung/article/details/76776497
虚析构函数一般指的是将基类的析构函数声明为虚函数,在定义一个基类指针,其指向对象是一个派生类的时候,通过动态绑定实现在运行的时候确定该指针的指向是派生类,从而在删除对象的时候调用派生类的析构函数,防止内存泄漏。#include using namespace std; class Base { private: int i; public: Base() { cout << "Base count " << endl; } virtual ~Base() { cout << "Base descount" << endl; } }; class Inherit :public Base { private: int num; public: Inherit() { cout << "Inherit count" << endl; } ~Inherit() { cout << "Inherit descout" << endl; } }; int main() { Base *p; p = new Inherit; delete p; return 0; }
上面的代码我将指针的声明和初始化拆开成两行,调试的时候发现声明的时候并没有调用基类的构造函数,在初始化的时候一次性调用了基类和派生类的构造函数,而delete p的时候就调用了派生类和基类的析构函数。有点蒙圈,哪位大佬可以帮我解释一下?
运行结果:Base count Inherit count Inherit descout Base descount
线程与进程 多线程
https://www.cnblogs.com/fuchongjundream/p/3829508.html
进程是分配资源的基本单位,线程是独立运行和独立调度的基本单位。
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
指针常量 常量指针
int constp; 等价于 const int p; 都表示指针p指向的是一个常量
int *const p; 表示指针p是一个常量,不能更改指向
静态变量,静态函数
https://www.cnblogs.com/secondtonone1/p/5694436.html
https://www.cnblogs.com/ppgeneve/p/5091794.html
-
静态局部变量和静态全局变量
静态局部变量具有局部作用域。它只被初始化一次,自从第一次初始化直到程序结束都一直存在,即它的生命周期是程序运行就存在,程序结束就结束,
静态全局变量具有全局作用域,他与全局变量的区别在于如果程序包含多个文件的话,他作用于定义它的文件里,不能作用到其他文件里,即被static关键字修饰过的变量具有文件作用域。 -
类的静态成员变量和静态成员函数
静态成员变量和静态成员函数主要是为了解决多个对象数据共享的问题,他们都不属于某个对象,而是属于类的。
静态成员变量定义的时候前面加static关键字,初始化必须在类外进行,前面不加static。其初始化格式如下:
::= //静态变量的初始化
静态成员函数也是属于类的成员,不能直接引用类的非静态成员,如果静态成员函数中要引用非静态成员时,可通过对象来引用。静态成员函数使用如下格式:::();
科大讯飞C++面经
一面
虚函数
https://blog.csdn.net/LC98123456/article/details/81143102
https://www.jianshu.com/p/f85bd1728bf0
虚函数是声明时前面加了virtual关键字的函数,作用是实现运行时动态绑定。一般有两种函数会声明为虚函数,一种是基类的析构函数,另一种是在派生类重写了基类的普通成员函数,而且使用了一个基类指针调用该成员函数,要想实现动态绑定派生类对象,则需要将基类的该成员函数声明为虚函数。看下面的两个例子吧。
虚析构函数,可以看我上面的链接,里面有详细解释。
#include using namespace std; class Base { public: Base() { cout << "create Base " << endl; } virtual ~Base() { cout << "delete Base" << endl; } }; class Inherit :public Base { public: Inherit() { cout << "create Inherit" << endl; } ~Inherit() { cout << "delete Inherit" << endl; } }; int main() { Base *p; p = new Inherit; delete p; return 0; }
基类虚成员函数:
#include using namespace std; class A { public: A() { ver = 'A'; } void print() const { cout << "The A version is: " << ver << endl; } protected: char ver; }; class D1 :public A { public: D1(int number) { info = number; ver = '1'; } void print() const { cout << "The D1 info: " << info << " version: " << ver << endl; } private: int info; }; class D2 :public A { public: D2(int number) { info = number; } void print() const { cout << "The D2 info: " << info << " version: " << ver << endl; } private: int info; }; int main() { A a; D1 d1(4); D2 d2(100); A *p = &a; p->print(); p = &d1; p->print(); p = &d2; p->print(); system("pause"); return 0; }
The A version is: A The A version is: 1 The A version is: A
我们先看上述代码,派生类中重新定义了基类中的函数,我们在使用一个基类指针指向派生类的时候,本义想要调用派生类中的重定义函数,但是由于基类中此函数不是虚函数,因此指针会静态绑定此函数,结果就不是我们的本意。而如果我们将基类中的方法改成虚函数,如下:
class A { public: A() { ver = 'A'; } virtual void print() const { cout << "The A version is: " << ver << endl; } protected: char ver; };
The A version is: A The D1 info: 4 version: 1 The D2 info: 100 version: A
可以看到,将基类方法改成虚函数,那么就会动态绑定,在运行时才决定调用哪个函数。
补充:上面函数后面有个const修饰,表示该成员函数不能修改任何成员变量,除非成员变量用mutable修饰。const修饰的成员函数也不能调用非const修饰的成员函数,因为可能引起成员变量的改变。
mutalbe的中文意思是“可变的,易变的”,跟constant(既C++中的const)是反义词。在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量(mutable只能由于修饰类的非静态数据成员),将永远处于可变的状态,即使在一个const函数中。
详见 https://www.cnblogs.com/xkfz007/articles/2419540.html
纯虚函数
https://blog.csdn.net/qq_36221862/article/details/61413619
纯虚函数的声明:
virtual 函数类型 函数名 (参数表列) = 0;
一般用于抽象类中,抽象类不可用于实例化。
面向对象三大特性
封装,继承,多态
指针和引用的区别
引用是一个变量的别名,不能为空,必须在声明的时候初始化,而且之后不能修改为其他的变量的别名;
指针的值是一块内存的地址,可以为空,可以先声明后初始化,后面可以修改其指向的内存地址。
STL中vector和list
https://www.cnblogs.com/shijingjing07/p/5587719.html
vector是一片连续的内存空间,相当于数组。随机访问方便,插入和删除效率低。
list是不连续的内存空间,是双向链表。随机访问效率低,插入和删除方便。
统计一个班级成绩(整数)及对应的人数,按照从大到小的顺序输出
成绩是整数,可以用一个一定长度的数组统计每个分数的人数,然后逆序输出。
冒泡排序
常考题
二面
STL中vector和list
继续一面的问题,但是考了vector如果满了然后还要插入怎么解决。
指针和引用
手写代码
-
字符串变成整型数字
剑指offer原题#include #include using namespace std; int StrToInt(string str) { if (!str.size()) return 0; int s = 1; long long res = 0; if (str[0] == '-') s = -1; for (int i = (str[0] == '-' || str[0] == '+') ? 1 : 0; i < str.size(); ++i) { if (!('0' <= str[i] && str[i] <= '9')) return 0; res = res * 10 + str[i] - '0'; } return res * s; } int main() { string str; getline(cin, str); cout << StrToInt(str) << endl; return 0; }
-
取出字符串中连续重复的字符,只保留一个
#include #include using namespace std; //输入一串字符串,将其中连续重复的字符保留其中一个,如aabbccbc,保留abcbc string func(string arr) { char* p1 = &arr[0]; char* p2 = &arr[1]; while (*p2 != '\0') { if (*p2 == *p1) { *p2 = '\0'; p2++; } else { p1++; *p1 = *p2; *p2 = '\0'; p2++; } } return arr; } int main() { string str; cin >> str; cout << func(str); system("pause"); return 0; }
烽火通信C++面经
烽火面的还行吧,可能二面四个人一起俩面试官有点紧张,没怎么说话没啥存在感,然后给我调测试了,打电话的时候说的是先做一段时间手动测试,后面做测试开发,结果谈offer的时候说目前公司没有测试开发,然后工资巨低。果断拒绝了,如果是开发的话,勉强可以接受。
多线程
https://www.cnblogs.com/lyjblogs/p/7888646.html
https://www.cnblogs.com/raichen/p/5766634.html
多线程就是多个线程并发执行
锁
https://blog.csdn.net/qq_37010006/article/details/79402256
线程之间的锁有:互斥锁、条件锁、自旋锁、读写锁、递归锁。
- 互斥锁用于控制多个线程对他们之间共享资源互斥访问的一个信号量。也就是说是为了避免多个线程在某一时刻同时操作一个共享资源。例如线程池中的有多个空闲线程和一个任务队列。任何是一个线程都要使用互斥锁互斥访问任务队列,以避免多个线程同时访问任务队列以发生错乱。在某一时刻,只有一个线程可以获取互斥锁,在释放互斥锁之前其他线程都不能获取该互斥锁。如果其他线程想要获取这个互斥锁,那么这个线程只能以阻塞方式进行等待。
- 条件锁就是所谓的条件变量,某一个线程因为某个条件为满足时可以使用条件变量使改程序处于阻塞状态。一旦条件满足以“信号量”的方式唤醒一个因为该条件而被阻塞的线程。最为常见就是在线程池中,起初没有任务时任务队列为空,此时线程池中的线程因为“任务队列为空”这个条件处于阻塞状态。一旦有任务进来,就会以信号量的方式唤醒一个线程来处理这个任务。这个过程中就使用到了条件变量pthread_cond_t。
-
假设我们有一个两个处理器core1和core2计算机,现在在这台计算机上运行的程序中有两个线程:T1和T2分别在处理器core1和core2上运行,两个线程之间共享着一个资源。
首先我们说明互斥锁的工作原理,互斥锁是是一种sleep-waiting的锁。假设线程T1获取互斥锁并且正在core1上运行时,此时线程T2也想要获取互斥锁(pthread_mutex_lock),但是由于T1正在使用互斥锁使得T2被阻塞。当T2处于阻塞状态时,T2被放入到等待队列中去,处理器core2会去处理其他任务而不必一直等待(忙等)。也就是说处理器不会因为线程阻塞而空闲着,它去处理其他事务去了。
而自旋锁就不同了,自旋锁是一种busy-waiting的锁。也就是说,如果T1正在使用自旋锁,而T2也去申请这个自旋锁,此时T2肯定得不到这个自旋锁。与互斥锁相反的是,此时运行T2的处理器core2会一直不断地循环检查锁是否可用(自旋锁请求),直到获取到这个自旋锁为止。
从“自旋锁”的名字也可以看出来,如果一个线程想要获取一个被使用的自旋锁,那么它会一致占用CPU请求这个自旋锁使得CPU不能去做其他的事情,直到获取这个锁为止,这就是“自旋”的含义。
当发生阻塞时,互斥锁可以让CPU去处理其他的任务;而自旋锁让CPU一直不断循环请求获取这个锁。通过两个含义的对比可以我们知道“自旋锁”是比较耗费CPU的
深拷贝和浅拷贝
https://blog.csdn.net/libin66/article/details/53140284
浅拷贝是只对指针进行拷贝,两个指针指向同一个内存块,深拷贝是对指针和指针指向的内容都进行拷贝,拷贝后的指针是指向不同内的指针。
重写和重载
https://www.cnblogs.com/weizhixiang/articles/5760286.html
-
成员函数重载特征:
a 相同的范围(在同一个类中)
b 函数名字相同
c 参数不同
d virtual关键字可有可无 -
重写(覆盖)是指派生类函数覆盖基类函数,特征是:
a 不同的范围,分别位于基类和派生类中
b 函数的名字相同
c 参数相同
d 基类函数必须有virtual关键字 -
重定义(隐藏)是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
a 如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏。
b 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有vitual关键字,此时,基类的函数被隐藏。
C++的优点
https://www.cnblogs.com/ckings/p/3632997.html
C++的优点:
1.代码可读性好。
2.可重用性好。
3.可移植。
4.C++设计成无需复杂的程序设计环境
5.运行效率高,高效安全
6.语言简洁,编写风格自由。
7.提供了标准库stl
8.面向对象机制
9.很多优秀的程序框架包括Boost、Qt、MFC、OWL、wxWidgets、WTL就是使用的C++。
面向对象三大特性
这个可以回答上面的问题,继承,多态,封装
工具的使用,快捷键
VS2017,调试F5,开始执行不调试Ctrl+F5,逐语句F11,逐过程F10
第三方函数库
问到了boost,不是很了解,看了下才知道智能指针,内存池是这里的
https://www.cnblogs.com/findumars/p/7257415.html
https://blog.csdn.net/xiaoxiaoyeyaya/article/details/42541419
STL库
vector内存结构,如何分配内存
vector是连续内存,在插入数值时若内存空间不够,则新开辟一片2倍的内存空间,然后将原来的内容复制过来,插入新的数值,之后将原来的内存空间释放。
设计模式
https://blog.csdn.net/column/details/design.html
https://www.cnblogs.com/aeolian/p/7792587.html
设计模式(Design Pattern)是一套被反复使用、多数人知晓、经过分类编目的优秀代码设计经验的总结。使用设计模式是为了提高代码的重用性,是代码更易理解并保证代码的可靠性。
通常的设计模式可以概括为23种,按照特点可以将其分为三大类型:创建型、结构型、行为型。
感觉常考的有单例模式,适配器模式,观察者模式,工厂模式,以及对应的UML图,还有手写一个模式之类的。
网络编程
数据库工具
常用的MySQL,Oracle,DB2
增删改查,查询语句重点
-
增加
insert into(可省略) 表名(列名) values(具体值)
批量插入
insert into(可省略) 表名(列名)
values(值1) ,(值2) -
删除
Delete from(可省略) 表名 where --删除数据会导致编号中断
truncate table 表名 –清空表中数据,将表重置 -
修改
update 数据表 set 字段名1=表达式1
where -
查询
Select top n * form 表名
加班的看法
看了哪些书
海康威视C++开发
C++源码到可执行程序的过程
https://www.cnblogs.com/smile233/p/8423015.html
这篇博客讲的太详细了,我觉得实际可以精简为:
源代码-->预处理-->编译-->汇编-->链接-->可执行文件
-
预处理
(1)宏定义指令,如#define Name TokenString,#undef等。对于前一个伪指令,预编译所要做的是将程序中的所有Name用TokenString替换,但作为字符串常量的Name则不被替换。对于后者,则将取消对某个宏的定义,使以后该串的出现不再被替换。
(2)条件编译指令,如#ifdef,#ifndef,#else,#elif,#endif,等等。这些伪指令的引入使得程序员可以通过定义不同的宏来决定编译程序对哪些代码进行处理。预编译程序将根据有关的文件,将那些不必要的代码过滤掉
(3)头文件包含指令,如#include "FileName"或者#include 等。在头文件中一般用伪指令#define定义了大量的宏(最常见的是字符常量),同时包含有各种外部符号的声明。采用头文件的目的主要是为了使某些定义可以供多个不同的C源程序使用。因为在需要用到这些定义的C源程序中,只需加上一条#include语句即可,而不必再在此文件中将这些定义重复一遍。预编译程序将把头文件中的定义统统都加入到它所产生的输出文件中,以供编译程序对之进行处理。
包含到c源程序中的头文件可以是系统提供的,这些头文件一般被放在/usr/include目录下。在程序中#include它们要使用尖括号。()。另外开发人员也可以定义自己的头文件,这些文件一般与c源程序放在同一目录下,此时在#include中要用双引号("")。
(4)特殊符号,预编译程序可以识别一些特殊的符号。例如在源程序中出现的LINE标识将被解释为当前行号(十进制数),FILE则被解释为当前被编译的C源程序的名称。预编译程序对于在源程序中出现的这些串将用合适的值进行替换。 -
编译
编译过程就是把预处理完的文件进行一系列的词法分析,语法分析,语义分析及优化后生成相应的汇编代码。经过预编译得到的输出文件中,将只有常量。如数字、字符串、变量的定义,以及C语言的关键字,如main,if,else,for,while,{,},+,-,*,\,等等。编译程序所要作得工作就是通过词法分析和语法分析,在确认所有的指令都符合语法规则之后,将其翻译成等价的中间代码表示或汇编代码。 -
汇编
汇编过程实际上指把汇编语言代码翻译成目标机器指令的过程。对于被翻译系统处理的每一个C语言源程序,都将最终经过这一处理而得到相应的目标文件。目标文件中所存放的也就是与源程序等效的目标的机器语言代码。 -
链接
由汇编程序生成的目标文件并不能立即就被执行,其中可能还有许多没有解决的问题。例如,某个源文件中的函数可能引用了另一个源文件中定义的某个符号(如变量或者函数调用等);在程序中可能调用了某个库文件中的函数,等等。所有的这些问题,都需要经链接程序的处理方能得以解决。
通过调用链接器ld来链接程序运行需要的一大堆目标文件,以及所依赖的其它库文件,最后生成可执行文件。
函数重载 变量前有无const是否可以重载
https://www.cnblogs.com/qingergege/p/7609533.html
-
fun(int i) 和 fun(const int i),不能重载
二者是一样的,是因为函数调用中存在实参和形参的结合。假如我们用的实参是int a,那么这两个函数都不会改变a的值,这两个函数对于a来说是没有任何区别的,所以不能通过编译,提示重定义。 -
fun(char a) 和 fun(const char a),可以重载
因为char a 中a指向的是一个字符串变量,而const char a指向的是一个字符串常量,所以当参数为字符串常量时,调用第二个函数,而当函数是字符串变量时,调用第一个函数。 -
char a和char const a,不能重载
这两个都是指向字符串变量,不同的是char a是指针变量 而char const a是指针常量,这就和int i和const int i的关系一样了,所以也会提示重定义。 - 对于引用,比如int &i 和const int & i 也是可以重载的,原因是第一个i引用的是一个变量,而第二个i引用的是一个常量,两者是不一样的,类似于上面的指向变量的指针的指向常量的指针。
全局变量,静态全局变量可以被其他文件调用么,为什么
https://blog.csdn.net/candyliuxj/article/details/7853938
https://www.cnblogs.com/invisible2/p/6905892.html
-
全局变量
在a.h中声明全局变量:extern int a; 一般需要加上extern,否则编译器可能默认给一个初始化值。那样会导致多个cpp文件在包含此头文件时发生重复定义。
在a.cpp文件中定义全局变量:int a =10;
在b.cpp中想要使用这个全局变量,有两种方法,第一种是使用extern关键字,例如extern int a; 代表当前变量a 的定义来自于其他文件,当进行编译时,会去其他文件里面找。且在当前文件仅做声明,而不是重新定义一个新的变量。第二种方法是使用include "a.h",这种方法的好处是a里面的方法可以直接拿过来使用。
extern作用
作用一:当它与"C"一起连用时,如extern "C" void fun(int a, int b);,则编译器在编译fun这个函数名时按C的规则去翻译相应的函数名而不是C++的。
作用二:当它不与"C"在一起修饰变量或函数时,如在头文件中,extern int g_nNum;,它的作用就是声明函数或变量的作用范围的关键字,其声明的函数和变量可以在本编译单元或其他编译单元中使用。 -
静态全局变量(static)
注意使用static修饰变量,就不能使用extern来修饰,即static和extern不可同时出现。 static修饰的全局变量的声明与定义同时进行,即当你在头文件中使用static声明了全局变量,同时它也被定义了。
static修饰的全局变量的作用域只能是本身的编译单元。在其他编译单元使用它时,只是简单的把其值复制给了其他编译单元,其他编译单元会另外开个内存保存它,在其他编译单元对它的修改并不影响本身在定义时的值。即在其他编译单元A使用它时,它所在的物理地址,和其他编译单元B使用它时,它所在的物理地址不一样,A和B对它所做的修改都不能传递给对方。
多个地方引用静态全局变量所在的头文件,不会出现重定义错误,因为在每个编译单元都对它开辟了额外的空间进行存储。
TCP什么情况会重传
https://www.cnblogs.com/virusolf/p/4335613.html
发端计时器超时
快重传
深信服测试岗
TCP与UDP的区别
https://www.cnblogs.com/-wang-cheng/p/5421988.html
-
连接性(Connectivity)
TCP是面向连接(Connection oriented)的协议,UDP是无连接(Connection less)协议。
TCP用三次握手建立连接:1) Client向server发送SYN;2) Server接收到SYN,回复Client一个SYN-ACK;3) Client接收到SYN_ACK,回复Server一个ACK。到此,连接建成。UDP发送数据前不需要建立连接。 -
可靠性(Reliability)
TCP可靠,UDP不可靠;TCP丢包会自动重传,UDP不会。 -
有序性(Ordering)
TCP有序,UDP无序。消息在传输过程中可能会乱序,后发送的消息可能会先到达,TCP会对其进行重排序,UDP不会。 -
有界性(Boundary)
TCP***,UDP有界;TCP通过字节流传输,UDP中每一个包都是单独的。 -
拥塞控制(Congestion or Flow control)
TCP有流量控制(拥塞控制),方式有滑动窗口以及慢开始、拥塞避免、快重传、快恢复,UDP没有; -
传输速度(Speed)
TCP传输慢,UDP传输快;
因为TCP需要建立连接、保证可靠性和有序性,所以比较耗时。这就是为什么视频流、广播电视、在线多媒体游戏等选择使用UDP。 -
量级(Heavy/Light weight)
TCP是重量级的,UDP是轻量级的;
TCP要建立连接、保证可靠性和有序性,就会传输更多的信息,如TCP的包头比较大。 -
头部大小(Header size)
TCP首部开销20字节,UDP的首部开销小,只有8个字节
ARP协议
https://blog.csdn.net/woshifennu1234/article/details/78256395
ARP(Address Resolution Protocol)即地址解析协议, 用于实现从 IP 地址到 MAC 地址的映射,即询问目标IP对应的MAC地址。
ARP是通过一个查找表(ARP缓存)来执行这种转换的。当在ARP缓存中没有找到地址时,则向网络发送一个广播请求,网络上所有的主机和路由器都接收和处理这个ARP请求,但是只有相同IP地址的接收到广播请求的主机或路由器,发回一个ARP应答分组,应答中包含它的IP地址和物理地址,并保存在请求主机的ARP缓存中。其他主机或路由器都丢弃此分组。
具体过程如下:
1) 本地主机在局域网中广播ARP请求,ARP请求数据帧中包含目的主机的IP地址。意思是“如果你是这个IP地址的拥有者,请回答你的硬件地址”。
2) 目的主机的ARP层解析这份广播报文,识别出是询问其硬件地址。于是发送ARP应答包,里面包含IP地址及其对应的硬件地址。
3) 本地主机收到ARP应答后,知道了目的地址的硬件地址,之后的数据报就可以传送了。
滑动窗口作用
流量控制
静态IP配置
需要的参数:
IP地址
子网掩码
网关
DNS服务器
Linux常用命令
https://www.cnblogs.com/yangyquin/p/4921616.html
-
变量的显示:echo
echo $PATH
echo helloworld -
改变权限:chmod
chmod [-R] xyz 文件或目录
身份权限:u表示user,g表示group,o表示other,a表示all。
读写权限:r和4表示读,w和2表示写,x和1表示执行。
chmod 765 a.txt 文件所有者拥有所有权限,文件所在组拥有读和写权限,其他所有人拥有读和执行权限
chmod u+rwx, g+rx, o+r /home/hadoop/a.txt -
创建文件
mkdir [-mp] 目录名称
-m : 配置文件的权限,mkdir -m 711 test1;
-p : 帮助你直接将所需要的目录(包含上层目录)递归创建起来,如果没有这个参数,只能一层一层建立目录,mkdir -p /home/a/b/c/d. -
切换目录:cd
cd 【相对路径或绝对路径】 - 显示目前所在的目录:pwdpwd [-P]-P 如果是连接文件,会不以连接文件的数据显示,而是显示正确的完整路径,即获取连接文件的真正路径。
- 查看文件与目录:ls
-
移除文件或目录:rm
rm [-fir] 文件或目录
-f:就是force的意思,忽略不存在的文件,不会出现警告信息
-i:互动模式,在删除前会询问用户是否操作
-r:递归删除。最常用在目录的删除。但是很危险!!! - 列出文件系统的整体磁盘使用量:dfdf [-ahikHTm] [目录或文件名] -a:列出所有的文件系统,包括系统特有的/proc等文件系统; -k:以KB的容量显示各文件系统 -m:以MB的容量显示各文件系统 -h:以人们较易阅读的GB、MB、KB等格式自行显示 -H:以M=1000K 替代M=1024K 的进位方式 -T:连同该分区的文件系统名称(例如ext3)也列出 -i:不用硬盘容量,而已inode的数量来显示 df //将系统内所有的文件系统列出来,在Linux下如果df没有加任何参数,那么默认会将系统内所有的(不含特殊内存内的文件系统与swap)都以1KB的容量列出来。 df -h //将容量结果以易读的容量格式显示出来 df -aT //将系统内所有的特殊文件格式及名称都列出来 df -h /etc //将/etc下面的可用的磁盘容量以易读的容量格式显示,这样就可以知道某个目录下还有多少容量可以使用了 df -ih //将目前各个分区当中可用的inode数量列出来
- 评估文件系统的磁盘使用量(常用于评估目录所占容量):dudu [-ahskm] 文件或目录名称 -a:列出所有的文件与目录容量,因为默认仅统计目录下面的文件量而已 -h:以人们较易读的容量格式(G/M)显示 -s:列出容量而已,而不列出每个各别的目录占用容量 -S:不包含子目录下的总计,与-s有点差别 -k:以KB列出容量显示 -m:以MB列出容量显示 du //列出当前目录下的所有文件容量,但仅会显示目录容量(不含文件) du -a //除了显示目录容量,还会显示文件容量 du -sm ./* //检查当前目录下面每个目录所占用的容量,可检查某个目录下那个子目录占用最大的容量
- 查看进程占用内存情况 top,free
虚拟机
https://blog.csdn.net/rencaishigepi/article/details/82178662
是通过软件模拟的具有完整硬件系统功能的、运行在一个完全隔离环境中的完整计算机系统,是虚拟化技术的一种。
如何用一台电脑远程控制五十台电脑,检测他们是否可以联网
多线程
补充多线程与多进程:
https://www.2cto.com/kf/201802/719224.html
https://www.cnblogs.com/virusolf/p/5458325.html
递归,溢出
斐波拉契数列的递归实现
可能存在的问题:栈溢出,时间复杂度过大,int型溢出
创建表 id自增
create table tbname
(
id int identity(1,1) not null
)
id起始为1,步长为1。
交换机成环
一条双绞线,两端插在同一个交换机的不同端口上,导致了网络性能急骤下降,打开网页都非常困难。
https://www.bilibili.com/video/av26055168?from=search&seid=16782839902872754412
对于这个可以看视频的最后几分钟,有一句话:交换机对于未知的单播,广播,组播全部采用泛洪。
假设一个交换机有三个接口A,B,C,其中B和C连接成环。一旦A口连接的部分接到了一个广播包,就会将其往B和C口转发。而B收到的广播包沿着环路会到达C,C收到的广播包沿着环路会到达B,由于他们采用的是全双工通信,可以同时接收和发送,因此C收到B转发来的广播包就会向A和B转发,而B收到C转发来的广播包就会向A和C转发,这样就会形成一个循环的过程,环路上一直有广播包,而其他主机则一直忙于接收广播包,影响了正常通信。
招银信用卡测试岗
main主函数运行之前先会干什么
-
全局对象的构造函数会在main 函数之前执行,
全局对象的析构函数会在main函数之后执行;
用atexit注册的函数也会在main之后执行。 - 一些全局变量、对象和静态变量、对象的空间分配和赋初值就是在执行main函数之前,而main函数执行完后,还要去执行一些诸如释放空间、释放资源使用权等操作
-
进程启动后,要执行一些初始化代码(如设置环境变量等),然后跳转到main执行。全局对象的构造也在main之前。
https://blog.csdn.net/huang_xw/article/details/8542105内存分区
https://www.cnblogs.com/madonion/articles/2269195.html
分为五大块:栈,堆,全局区,常量区和代码区 -
栈区
由系统进行内存的管理,主要存放函数的参数以及局部变量。栈区由系统进行内存管理,在函数完成执行,系统自行释放栈区内存,不需要用户管理。整个程序的栈区的大小可以在编译器中由用户自行设定,默认的栈区大小为3M。 -
全局/静态区
初始化的全局变量和静态变量是在一起的。未初始化的全局变量和静态变量是在相邻的空间中。全局变量和静态全局变量的存储方式是一致的,但是其区别在于,全局变量在整个源代码中都可以使用,而静态全局变量只能在当前文件中有效。比如我们的一个程序有5个文件,那么某个文件中申请了静态全局变量,这个静态全局变量只能在当前文件中使用,其他四个文件均不可以使用。而某个文件中申请了全局变量,那么其他四个文件中都可以使用该全局变量(只需要通过关键字extern申明一下就可以使用了)。事实上static改变了变量的作用范围。 -
字符串常量区
存放字符串常量,程序结束后,由系统进行释放。比如我们定义const char * p = “Hello World”; 这里的“Hello World”就是在字符串常量中,最终系统会自动释放。 - 代码区:存放程序体的二进制代码。比如我们写的函数,都是在代码区的。
-
堆区:由用户手动申请,手动释放。在C中使用malloc,在C++中使用new(当然C++中也可以使用malloc)。
new操作符本质上还是使用了malloc进行内存的申请
1)malloc是C语言中的函数,而new是C++中的操作符。
2)malloc申请之后返回的类型是void*,而new返回的指针带有类型。
3)malloc只负责内存的分配而不会调用类的构造函数,而new不仅会分配内存,而且会自动调用类的构造函数。
数据库索引分类
https://www.cnblogs.com/newpanderking/p/3781043.html
根据索引的顺序与数据表的物理顺序是否相同分为聚集索引和非聚集索引。
-
聚集索引:对表中的数据行按指定键值(即索引列值)进行排序后再重新存储到磁盘上,使数据表的物理顺序与索引一致。每个表只能建立一个聚集索引
优点:检索速度比非聚集索引快。
缺点:重组了表的物理顺序并且在建立聚集索引时需要足够的工作空间。 -
非聚集索引:具有完全独立于数据行的结构,不需要将数据表中的数据行按索引值重新排序。非聚集索引表中存储了组成非聚集索引的键值和行定位器,行定位器是指向数据行的指针,该数据行具有与索引键值相同的字段值。
优点:不需要改变数据行的存储顺序,而且可以建立多个非聚集索引。
如果一个表中既要建立聚集索引,又要建立非聚集索引,应该先创建聚集索引,然后创建非聚集索引。
一般需要建立索引的情况:
- 主键和外键
- 检索频繁的字段
- 经常需要排序的字段
死锁
测试方法有哪些
https://www.cnblogs.com/vigo01/p/7554603.html
补充一个对测试介绍很全面的:
https://blog.csdn.net/brave_insist/article/details/72169396
快排,冒泡
以下给出常见的排序算法:
#include using namespace std; void quick_sort(int a[], int left, int right) { if (left >= right) return; int i = left; int j = right; int tmp = a[left]; while (i < j) { while (a[j] >= tmp && i<j) { j--; } while (a[i] <= tmp && i < j) { i++; } if (i < j) { int t = a[i]; a[i] = a[j]; a[j] = t; } } a[left] = a[i]; a[i] = tmp; quick_sort(a, left, i - 1); quick_sort(a, i + 1, right); return; } void insert_sort(int a[], int n) { int i, j, tmp; for (i = 1; i < n; i++) { tmp = a[i]; for (j = i - 1; j >= 0 && a[j] > tmp; j--) { a[j + 1] = a[j]; } a[j + 1] = tmp; } } void bubble_sort(int a[], int len) { int tmp{}; bool flag{}; for (int i = 0; i < len; i++) { flag = 0; for (int j = len - 1; j >= i; j--) { if (a[j] < a[j - 1]) { flag = 1; tmp = a[j]; a[j] = a[j - 1]; a[j - 1] = tmp; } } if (!flag) return; } } void select_sort(int a[], int len) { for (int i = 0; i < len; i++) { int k = i; int tmp = a[i]; for (int j = i+1; j < len; j++) { if (a[j] < tmp) { tmp = a[j]; k = j; } } a[k] = a[i]; a[i] = tmp; } } int main() { int arr[10] = {54,38,96,15,23,72,60,45,83,64}; //快速排序 //quick_sort(arr, 0, 9); //直接插入排序 //insert_sort(arr, 10); //冒泡排序 //bubble_sort(arr, 10); //选择排序 select_sort(arr, 10); for (int i = 0; i < 10; i++) { cout << arr[i] << ' '; } system("pause"); return 0; }
设计测试出厂前的杯子质量是否合格
https://www.cnblogs.com/TankXiao/p/2381284.html
https://blog.csdn.net/qq_30353203/article/details/49252149?utm_source=blogxgwz0
-
功能测试(Function test)
能否装水,
除了装水, 能否装其他液体。比如可乐,酒精
能装多少ML的水
杯子是否有刻度表
杯子能否泡茶,跑咖啡
杯子是否能放冰箱,做冰块
杯子的材质是什么(玻璃,塑料,黄金做的) -
界面测试(UI Test)
外观好不好看。
什么颜色
杯子的形状是怎么样的。
杯子的重量是多少
杯子是否有异味
杯子的图案是否合理 -
性能测试(performance test)
能否装100度的开水 (泡茶)
能否装0度冰水
装满水,放几天后,是否会漏水
杯子内壁上的涂料是否容易脱落。
杯子上的颜色是否容易褪色或者脱落 -
安全性测试(Security test)
制作杯子的材料,是否有毒
放微波炉里转的时候,是否会爆炸, 或者杯子是否会熔化。
从桌子上掉到水泥地上是否会摔碎。
杯子是否容易长细菌
杯子是否有缺口,会划坏嘴巴
杯子内壁上的材料,是否会溶解到水中
杯子破碎后,是否会对使用者造成伤害 -
压力测试(Pressure Test)
双手握紧水杯,是否会破裂
在物品的一定挤压下是否破裂、变形
在一定高度摔下来是否会摔坏 -
可用性测试(Usability Test)
杯子是否容易烫手
杯子是否好端,好拿
杯子的水是否容易喝到
杯子是否有防滑措施
测试需要的能力
https://blog.csdn.net/brave_insist/article/details/72169396
- a.沟通能力 b.技术能力 c.细心、信心、耐心
-
软件测试员自身素质
a.应对软件测试感兴趣和对自己有自信。
b.善于怀疑,世界上没有绝对正确的,总有错误的地方,具有叛逆心理,别人认为不可能发生的事,我却认为可能发生。别人认为是对的,我却认为不是对的。
c.打破砂锅问到底的精神,对于只出现过一次的bug,一定找出原因,不解决誓不罢休。
d.保持一个良好的心情,否则可能无法把测试做好。不要把生活中的不愉快的情绪带到工作中来。
e.做测试时要细心,不是所有的bug都能很容易的找出,一定要细心才能找出这些bug。
f.灵活一些,聪明一点,多制造一些容易产生bug的例子。
g.在有条件的情况下,多和客户沟通,他们身上有你所需要的。 -
其他
a.设身处地为客户着想,从他们的角度去测试系统。
b.不要让程序员,以“这种情况不可能发生”这句话说服你,相反,你应该去说服他,告诉他在客户心里,并不是这样的。
c.考虑问题要全面,结合客户的需求、业务的流程、和系统的构架,等多方面考虑问题。
d.提出问题不要复杂化,这一点和前面的有点矛盾,如果你是一新手,暂时不要管这一点,因为最终将有你的小组成员讨论解决。
e.追求完美,对于新测试员来说,努力地追求完美,这对你很好,尽管有些事无法做到,但你应该去尝试。
f.能和开发小组很好地沟通是关键。
开立医疗C++开发
问了非常多的问题,问的很细
一、双层for循环优化
- 将循环次数大的放到外层循环
- 将双层循环变成单层循环
-
原题目是将一个二维数组转换为一维数组,可以考虑指针:
# include<iostream> using namespace std; int main() { int a[3][3] = { 1,2,3,4,5,6,7,8,9 }; //int *b = (int*)a; //int *b = a[0]; int* b = &a[0][0]; for (int i = 0; i < 9; i++) { cout << *(b++) << endl; } return 0; }
这里所使用的三种方法都是将一个指针指向数组a的首地址,在内存中a是连续存储的,内存分布如下:
可以看到,a数组在内存中按照行的顺序存储,后面的行接着前面的行顺序存储,地址从低到高。
补充:查看内存(必须是调试状态下)
如果查看a的内存分布,可以在内存那里输入 &a,回车即可。
二、设计模式 适配器模式
适配器模式是将一个类的接口转换成客户希望的另外一个接口,身边很多东西都是适用于适配器模式的,笔记本的电源(也叫电源适配器),是将220V的交流电转换为笔记本电脑所需要的12V(电流先忽略),笔记本电脑的各种接口,VGA转Hdml,USB-TypeA 转 USB-TypeC,亦或者你在***买了个手机,充电器是你生活中没见过的三孔插座通过一个转换头转换为国内常用的插头,很多例子都能很形象的解释这个设计模式。适配器模式(有时候也称包装样式或者包装)将一个类的接口适配成用户所期待的。一个适配允许通常因为接口不兼容而不能在一起工作的类工作在一起,做法是将类自己的接口包裹在一个已存在的类中。
三、字符串的复制和赋值
字符串复制
-
数组复制
void strcpy(char *des,char* src) { int i; for(i=0;src[i]!='\0';i++) { des[i] = src[i]; } des[i] = '\0'; }
-
指针复制
void strcpy(char *des,char* src) { while(*des++ =* src++) ; }
字符串赋值
//1. 定义时初始化赋值 char buf[20]="hello world!"; char *str="hello world!" //2. 先定义再初始化 char buf[20]; char*str; buf = "I love china"; //错误,数组名是常量,不能被赋值 strcpy (buf, "I love china"); str = "I love china"; strcpy(str, "I love china"); //段错误,没有给str指针分配内存 //3. 上述最后一种方法的改正 str = (char *)malloc(sizeof(char)*20); strcpy(str, "I love china");
四、数据库更新数据
update scoretable set score = score + 10 where name = 'lili'
五、多态
-
编译时的多态性:重载
在一个类中,方法名相同而参数(顺序,个数和类型)不同。 -
运行时的多态性:虚函数
直到系统运行时,才根据实际情况决定实现何种操作。虚函数的重写(重写:函数三要素(函数名、函数参数、函数返回类型)完全一样)即是多态的体现。补充:重定义,也叫做隐藏,子类重定义父类中有相同名称的非虚函数(参数可以不同)。如果一个类,存在和父类相同的函数,那么这个类将会覆盖其父类的方法,除非你在调用的时候,强制转换为父类类型,否则试图对子类和父类做类似重载的调用时不能成功的。
六、虚函数的实现原理
此处贴一下讲的很好的博文:
https://www.cnblogs.com/malecrab/p/5572730.html
http://www.cnblogs.com/malecrab/p/5572119.html
虚函数的动态绑定实现机制:
只有通过基类的引用或者指针调用虚函数时,才能发生动态绑定,如果使用对象来操作虚函数的话,仍然会采用静态绑定的方式。因为引用或者指针既可以指向基类对象,也可以指向派生类对象的基类部分。绝对不要重新定义一个继承而来的virtual函数的缺省参数值,因为缺省参数值都是静态绑定(为了执行效率),而virtual函数却是动态绑定。
七、内存分区
https://www.cnblogs.com/madonion/articles/2269195.html
分为五大块:栈,堆,全局区,常量区和代码区
-
栈区
由系统进行内存的管理,主要存放函数的参数以及局部变量。栈区由系统进行内存管理,在函数完成执行,系统自行释放栈区内存,不需要用户管理。整个程序的栈区的大小可以在编译器中由用户自行设定,默认的栈区大小为3M。 -
全局/静态区
初始化的全局变量和静态变量是在一起的。未初始化的全局变量和静态变量是在相邻的空间中。全局变量和静态全局变量的存储方式是一致的,但是其区别在于,全局变量在整个源代码中都可以使用,而静态全局变量只能在当前文件中有效。比如我们的一个程序有5个文件,那么某个文件中申请了静态全局变量,这个静态全局变量只能在当前文件中使用,其他四个文件均不可以使用。而某个文件中申请了全局变量,那么其他四个文件中都可以使用该全局变量(只需要通过关键字extern申明一下就可以使用了)。事实上static改变了变量的作用范围。 -
字符串常量区
存放字符串常量,程序结束后,由系统进行释放。比如我们定义const char * p = “Hello World”; 这里的“Hello World”就是在字符串常量中,最终系统会自动释放。 - 代码区:存放程序体的二进制代码。比如我们写的函数,都是在代码区的。
-
堆区:由用户手动申请,手动释放。在C中使用malloc,在C++中使用new(当然C++中也可以使用malloc)。
new操作符本质上还是使用了malloc进行内存的申请
1)malloc是C语言中的函数,而new是C++中的操作符。
2)malloc申请之后返回的类型是void*,而new返回的指针带有类型。
3)malloc只负责内存的分配而不会调用类的构造函数,而new不仅会分配内存,而且会自动调用类的构造函数。
八、堆和栈的区别
- 管理方式:栈是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生内存泄漏。
- 空间大小:堆内存比栈大得多。
- 能否产生碎片:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因 为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出。
- 生长方向:堆生长方向是向上的,也就是向着内存地址增加的方向;栈的生长方向是向下的,是向着内存地址减小的方向增长。
- 分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编译器进行释放,无需我们手工实现。
- 分配效率:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在堆内 存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到 足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
九、栈溢出
当我们定义的数据所需要占用的内存超过了栈的大小时,就会发生栈溢出。
https://blog.csdn.net/dongtuoc/article/details/79132137
十、内存泄露和内存碎片
1. 内存泄漏
内存泄漏一般是指堆内存的泄漏。堆内存是指程序从堆中分配的,大小任意的(内存块的大小可以在程序运行期决定),使用完后必须显示释放的内存。如果没有及时释放,那么这一块内存便不能使用,从而造成内存泄漏。
2. 内存碎片
内存碎片一般是由于空闲的连续空间比要申请的空间小,导致这些小内存块不能被利用。
假设有一块100个单位的连续空闲内存空间,范围是0-99。从中申请一块内存,10个单位,那么申请出来的内存块就为0-9区间。继续申请一块内存,5个单位,第二块得到的内存块就应该为10~14区间。
如果你把第一块内存块释放,然后再申请一块20个单位的内存块。因为刚被释放的内存块不能满足新的请求,所以只能从15开始分配出20个单位的内存块。现在整个内存空间的状态是0-9空闲,10-14被占用,15-24被占用,25-99空闲。其中0-9就是一个内存碎片了。如果10-14一直被占用,而以后申请的空间都大于10个单位,那么0-9就永远用不上了,造成内存浪费。
十一、同步和异步
同步:在发出一个功能调用时,在没有得到结果之前,该调用就不返回。
异步。
异步:当一个异步过程调用发出后,调用者不会立刻得到结果。实际处理这个调用的部件是在调用发出后,通过状态、通知来通知调用者,或通过回调函数处理这个调用。异步的实现方式有两种:通过多进程和timmer。
十二、野指针
- 指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。
- 指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。别看free和delete的名字恶狠狠的(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。通常会用语句if (p != NULL)进行防错处理。很遗憾,此时if语句起不到防错作用,因为即便p不是NULL指针,它也不指向合法的内存块。释放内存后必须把指针指向NULL,防止指针在后面不小心又被解引用了。具体参考博文:https://blog.csdn.net/dangercheng/article/details/12618161
- 指针超过了变量的作用范围。即在变量的作用范围之外使用了指向变量地址的指针。这一般发生在将调用函数中的局部变量的地址传出来引起的。局部变量的作用范围虽然已经结束,内存已经被释放,然而地址值仍是可用的,不过随时都可能被内存管理分配给其他变量。
十三、指针的使用有什么注意
- 函数的返回值不能是指向栈内存的指针或引用,因为栈内存在函数结束时会被释放.
- 在使用指针进行内存操作前记得要先给指针分配一个动态内存。
- 声明一个指针时最好初始化,指向NULL或者一块内存。
十四、形参和实参
概念
-
形参
形参出现在函数定义的地方,多个形参之间以逗号分隔,形参规定了一个函数所接受数据的类型和数量。
形参和函数体内部定义的变量统称为局部变量,仅在函数的作用域内可见,同时局部变量还会隐藏在外层作用域中同名的其他所有声明(局部变量和全局变量可以重名) -
实参
出现在函数调用的地方,实参的数量与类型与形参一样,实参用于初始化形参。
实参与形参值传递的方式
-
值传递
在值传递过程中,实参和形参位于内存中两个不同地址中,参数传递的实质是将原函数中变量的值,复制到被调用函数形参所在的存储空间中,这个形参的地址空间在函数执行完毕后,会被回收掉。整个被调用函数对形参的操作,只影响形参对应的地址空间,不影响原来函数中的变量的值,因为这两个不是同一个存储空间。 -
地址传递(指针传递)
这种参数传递方式中,实参是变量的地址,形参是指针类型的变量,在函数中对指针变量的操作,就是对实参(变量地址)所对应的变量的操作,,函数调用结束后,原函数中的变量的值将会发生改变。 -
引用传递
这种参数传递方式中,形参是引用类型变量,其实就是实参的一个别名,在被调用函数中,对引用变量的所有操作等价于对实参的操作,这样,整个函数执行完毕后,原先的实参的值将会发生改变。
如果在实参前加上const关键字修饰,则引用传递可以不改变实参的值,既达到了传值的目的,提高了效率,还保证了原实参不会被修改。
https://blog.csdn.net/u012677715/article/details/73825856
https://www.cnblogs.com/kane0526/p/3913284.html
https://www.cnblogs.com/tanjuntao/p/8678927.html
十五、时间复杂度和空间复杂度
-
空间复杂度,该程序的运行所需内存的大小
计算公式:S(n) = O(f(n)),n为问题的规模,f(n)为语句关于n所占存储空间的函数。 -
时间复杂度,运行程序所需要的时间
如果一个算法的执行次数是 T(n),那么只保留最高次项,同时忽略最高项的系数后得到函数 f(n),此时算法的时间复杂度就是 O(f(n))。
https://www.jianshu.com/p/f4cca5ce055a
https://blog.csdn.net/qq_30891667/article/details/72236507