面试官您好!很荣幸获得这次面试机会。我叫毛子彧,现在是东南大学机械工程学院的2023应届硕士毕业生,我的专业是工业工程与管理。我应聘的岗位是C++软件工程师。我本科就读于湖南农业大学的信息工程。在本科的时候我就对C++、Linux、还有数据结构、网络、系统等有了系统性的学习,有一定的基础。在东南大学,我所在的是江苏省微纳重点实验室,我们组主要研究内容是数值模拟和仿真这一块,在该过程中主要在Windows平台开发C++语言编写的数值计算软件。
硕士期间,我主要做了三个项目,首先是我自己独立在Linux平台上开发的一个轻量级高并发服务器,主要设计网络编程、多线程、socket、IO多路复用等;我的研究生工作是微尺度细胞数值模拟,这个工作从无到有都是我一人负责。主要开发了一套可靠模拟细胞流动现象,并且可以定量分析其参数变化的程序,程序具有低耦合、可扩展、可复用性特点,并将结果转化为一篇SCI论文和一项发明专利。我看到岗位描述里提到并行计算和几何计算这两点,这在我硕士工作的两个项目里都有涉及,都利用了OpenMP并行编程去提高计算效率;然后几何计算的话是设计了大量的梯度、速度、涡量等向量之间的计算,虽然我不确定这和EDA的几何计算是不是一回事。
现在的话我在深圳拿了一些offer,但我经过一些了解和比较的话,其实我秋招最想加入的是国微芯,希望我可以顺利走完面试流程,取得一个好的结果。
了解:设计和制造国产的EDA工具系统和相关服务。像后端、制造端EDA工具和后端设计服务。
首先我开学那年东南大学和国微集团就成立了一个EDA联合实验室。所以我早先对国微集团有了了解(比方说它是深圳第一家半导体设计企业),首先是一个很大的平台,它的规模和国家的投入都是很大的;其次就是我对EDA软件开发非常有兴趣,我了解了一下芯片设计的主要流程,发现在前端和后端的每个步骤里都要用到大量的EDA工具,所以说即使现在有一些干扰因素,但也是非常有前景的,放在13年以前,中国人自己也不相信国产新能源汽车可以弯道超车。另外细分的步骤和工具非常多,所以我觉得可以有机会钻研具体某一类型的去提高自己的技术深度和竞争力;最后是我朋友的姐姐在国微芯,了解的一些研发氛围很好,沟通简单有效,是我很向往的那种氛围。刚刚提到的是我考虑的几个点,平台、行业、氛围,薪资,还有城市吧,综合来说我最想加入的就是国微芯。
楷登和新思
布局布线、时序分析、功耗分析、仿真
芯片的设计
的主要流程可以分为前端和后端,前端负责芯片的逻辑电路设计
,包括系统架构的定义,RTL编码,逻辑综合,这期间会进行多次的仿真和验证,最终得到门级的网表。后端主要负责芯片的物理设计
,包括布局规划,时钟树综合,布线,参数提取等等步骤,最终会得到一个芯片电路的物理版图,然后提供给晶圆厂去制造。
比如要做一个简单的加法电路,比如a+b=c,我们需要先用Verilog,或者VHDL这些硬件专用语言,把这个加法电路实现出来,为了验证加法的功能是不是正确,就要用EDA的仿真软件,比如新思的VCS和VC Formal,让a=1,b=1,看c是不是等于2。如果输入1+1,结果等于3,那么就需要调试软件,比如Verdi来确定问题出现在什么地方,还需要用到静态和动态的分析软件,比如SpyGlass来诊断分析电路是否有一些潜在的问题。如果代码没有问题就可以去编译了,这在芯片设计里叫做综合Synthesis。综合的结果就是生成一堆互相连接的门电路,也叫做网表,这就需要使用专门的综合工具 Design Complier综合生成网表,再用IC Compiler做布局布线,用Prime Time做时序分析,用PrimePower做功耗优化,用IC Validator做物理验证,用StarRC做寄生参数提取等等,最终生成一个符合设计要求,也符合晶圆代工厂要求的GDSII文件,这个文件就被拿去做流片生产
仅对C++部分知识点做一些记录,大部分内容为搬运,会标明出处。该文章不定期更新。
1.声明一个返回数组指针的函数(P205)
btw:
给数组定义一个类型别名:
typedef int[10] arr; //错误
typedef int arr[10]; //正确
基础前提:
int arr[10]; //含有10个整数的数组
int *p1[10]; //含有10个指针的数组
int (*p2)[10]=&arr; //p2是一个指针,它指向含有10个整数的数组
声明一个返回数组指针的函数:
类型 (*fun (参数))[length]
int (*func(int i))[10];
eg1:(反例)
int(*f())[10]
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
return &arr;
}
int main() {
int (*p)[10] = f();
cout << *(*p+2) << endl; //输出3
cout << *(*p+3) << endl; //此时输出错误,原因应该是返回了局部对象的指针
return 0;
}
eg2:
我们要返回a数组的值给别的类使用
int a[40];
int(*GetData())[40]
{
return &a;
}
int main() {
int recvData[40] = { 0 };
int(*p)[40] = GetData();
for (int i = 0; i < 40; i++)
{
recvData[i] = (*(*p + i)); //本质上p解引用一次后才是指向a[0]的指针
cout << recvData[i] << endl;
}
return 0;
}
来源:《C++ 返回数组指针的函数》
2.头文件应该放什么?
头文件中通常包含那些只能被定义一次的实体。
const
和constexpr
变量inline
函数(包括inline
成员函数)和constexpr
函数的定义(可以多次定义)3.assert和NDEBUG(P216)
使用assert
需要满足两个条件:
assert
头文件:#include
#include
此外,assert可以输出错误信息:
assert(("b不是0", b)); //提示信息放前,加双括号
开发调试阶段 -> 需要assert
发行版 -> 不需要
所以需要定义#define NDEBUG
(该定义一定要在assert头文件之前)
《NDEBUG预处理变量问题》
4.成员函数作友元(P252)
如果是另一个类作友元,直接声明即可:
friend class Y;
如果需要另一个类的成员函数作友元,则需要遵守一些顺序:
class Y {
void f(); //1.先定义友元函数的类并声明这个函数
};
class X {
friend void Y::f(); //2.定义要访问私有成员的类,并声明友元
int x = 9l;
};
void Y:: f() { //3.最后在类外定义这个友元函数
X obj;
cout << obj.x << endl;
}
5.explicit关键字(P265)
《C++ explicit关键字详解》
作用:修饰一个构造函数,表示该构造函数是显示的,而非隐式的(默认情况下构造函数是隐式的,且 ,没有implicit关键字)。主要防止类构造函数的隐式自动转换
使用条件:
作用:
第一种:有一个构造函数作为类型转换的“桥梁”,将它设为explicit后阻止隐式转换
class Sale {
public:
int a = 2;
int b = 2;
Sale combine(Sale obj) {
a += obj.a;
b += obj.b;
cout << a << endl << b << endl;
return *this;
}
explicit Sale(int num):a(2),b(num){} //桥梁
Sale(){}
};
int main() {
Sale obj1;
//Sale obj2;
int temp = 5;
obj1.combine(temp);//没加explicit时,输出4,7;加了后报错。本来这里是可以隐式转换的
return 0;
}
解决方法:
obj1.combine(Sale(temp));
=
)class Sale {
public:
int a = 2;
explicit Sale(int num){
a = num;
}
};
int main() {
Sale obj1(66); // 直接初始化
Sale obj1 = 66; // 拷贝形式初始化。原本是可以的,用explicit关键字后,只能使用直接初始化了。(
/? ps:构造函数用初始化列表貌似不受这一影响)
cout << obj1.a << endl;
return 0;
}
6.动态绑定(P527、550)
《C++动态绑定》
条件:
作用:
当我们用指针/引用调用虚函数时,该调用将被动态绑定;该调用可能执行基类或某个派生类的版本
同一段代码,既能调用基类虚函数,也能调用派生类虚函数
条件详解:(B是A的派生类)
示例情况:
A指针指向B,调用B的函数,但A类没有这个成员函数、或者调用的是非虚函数(没有满足重写条件),调用失败 ×
这个时候实际调用的函数版本仅仅只由指针的静态类型决定了
class A {
public:
virtual void f1();
void f2();
};
class B :public A {
public:
void f1();
void f(int x);
void f2();
};
int main() {
A a; B b;
A* p1 = &a;
A* p2 = &b;
p1->f1(); //动态绑定,调用A::f1()
p2->f1(); //动态绑定,调用B::f1()
p2->f1(5); //错误,没有重写,A内没有f1(int)
p2->f2(); //因为A::f2()没有声明为虚函数,所以调用A::f2()(仅由静态类型决定)
}
首先,我们先将一类对象的共同特征抽象出来构造类,抽象只关注对象的静态特征(属性)和动态特征(函数)。
封装
对外隐藏复杂细节,提供简单易用的接口
利用抽象数据类型将静态特征和动态特征封装在一起,使其构成一个独立实体
继承
复用代码最直接重要的手段
在原有类基础上进行扩展,以满足新的需求和功能
多态
派生类重写基类的虚函数,基类指针指向派生类
多态指方法而非属性;不同继承关系对象去调用同一函数,产生不同行为
重载
。虚函数
和重写
概念,可以实现多态
。struct
新增成员函数
和权限访问
;class
表示类。模板
,提供了更强大的STL
标准库。头文件
和命名空间
的不同,而且可以自己定义自己的空间,C中不可以;new
和delete
,增加了引用
。程序中
类中
静态数据成员:
对象.
或作用域::
访问静态成员函数:
const作用
一、修饰变量
二、引用
引用及其对象都得是常量
三、指针
顶层const | 底层const | |
---|---|---|
对象 | 基本类型和复合类型 | 复合类型:指针引用 |
特点 | 指针指向不能变(自己的值不能变) | 指针指向的对象不能变 |
举例 | 指针常量int* const p |
常量指针const int* p 或者int const* p |
四、函数
成员
函数(非全局函数):(1) 该函数不能修改成员变量 (2) 不能调用非const成员函数五、类成员
哪些函数不能声明为const?
构造函数
:因为构造函数需要修改成员变量static函数
:const函数本质想修饰this指针,表示this指向内容不可变,static静态成员没有this指针const常量和宏的区别:
《C/C++中的 extern 和extern“C“关键字的理解和使用》
extern "C"
按C语言去编译函数、编译文件、头文件(仅限C++)#ifdef __cplusplus
即可虚函数
基类希望派生类进行覆盖的函数:定义为虚函数。
当我们用指针/引用调用虚函数时,该调用将被动态绑定;该调用可能执行基类或某个派生类的版本
static
一样,只能出现在类内声明,不能出现在类外函数定义。待补充 -------------------
纯虚函数
将一个函数定义为纯虚函数,告诉用户这个函数没有实际意义。
= 0
即可讲一个虚函数说明为纯虚函数,且只能再类内部的虚函数声明语句处操作;此后它就不能在类内定义了,要提供定义也只能在类外重构: 在继承体系中增加一个抽象类是重构的典型示例。重构负责重新设计类的体系以便将操作或数据从一个类移动到另一个类中。《什么是重构?》
《C++中三种访问权限》
访问权限 | public |
protected |
private |
---|---|---|---|
对本类 | 可见 | 可见 | 可见 |
对派生类 | 可见 | 可见 | 不可见 |
对外部调用 | 可见 | 不可见 | 不可见 |
protected
成员,不能访问基类对象的protected
成员派生类的继承权限 | public |
protected |
private |
---|---|---|---|
对派生类 | 保持不变 | 保持不变 | 保持不变 |
对用户&派生类的派生类 | 保持不变 | public->protected | 全部private |
可以概括为:将类型转换视为基类中的一个public对象,再看继承权限结果对它的影响。
using
声明,通过在类中某一个权限后声明,来改变基类中的成员的权限public:
using Base::size; //记得加上类作用域
类型 | 特点 | 软件 | 举例 | 优点 | 使用场景 |
---|---|---|---|---|---|
编译型语言 | 必须在程序运行之前将所有代码都翻译成二进制形式,也就是生成可执行文件,用户拿到的是最终生成的可执行文件,看不到源码 | 完成编译过程的叫做编译器(Compiler) IDE集成开发环境包括了编译器,还有编辑器、调试器、用户界面等 |
C++、Go、Pascal | 执行速度快、硬件要求低、保密性好 | 开发操作系统、大型应用程序、数据库 |
脚本型语言(解释型) | 程序运行后会即时翻译,一边执行一边翻译,不会生成任何可执行文件,用户必须拿到源码才能运行程序 | 解释器 | Shell、JavaScript、Python、PHP | 使用灵活、部署容易、跨平台性好 | Web开发和小工具制作 |
《跨平台应用程序开发方法大盘点》
《跨平台C、C++代码注意的事项及如何编写跨平台的C/C++代码》
脚本型语言
脚本型编程语言有良好的平台兼容性,它一边解释一边运行,在任何环境中都可以运行–前提是安装了解释器。
php、python、Ruby等,这些语言在设计之初就考虑了在PC操作系统上的运行问题,分别为不同操作系统设计了对应的解释器,因此它们能够实现“写一份代码,在多个操作系统上运行“–移动端系统除外(IOS和Android等)
C/C++
预编译宏
,对于代码中的部分系统API调用代码单独编写,然后通让特定平台的编译器去编译对应的代码。GUI
)界面,问题就会比较复杂。不能使用与操作系统绑定的GUI接口或框架,例如windows下的MFC库;应该使用一个跨平台的GUI库,例如QT,GTK。#ifdef _WIN32
//define something for Windows (32-bit and 64-bit, this part is common)
#ifdef _WIN64
//define something for Windows (64-bit only)
#else
//define something for Windows (32-bit only)
#endif
#elif __APPLE__
#include "TargetConditionals.h"
#if TARGET_IPHONE_SIMULATOR
// iOS Simulator
#elif TARGET_OS_IPHONE
// iOS device
#elif TARGET_OS_MAC
// Other kinds of Mac OS
#else
# error "Unknown Apple platform"
#endif
#elif __ANDROID__
// android
#elif __linux__
// linux
#elif __unix__ // all unices not caught above
// Unix
#elif defined(_POSIX_VERSION)
// POSIX
#else
# error "Unknown compiler"
#endif
malloc |
new |
|
---|---|---|
性质 | 标准库函数,可覆盖 | 运算符,可重载 |
分配时 | 需显示指出尺寸 不调用构造函数 |
无需指定大小,编译器自动计算 调用构造函数进行初始化 |
返回类型 | 返回void指针 | 返回具体指针 |
失败后 | 失败时返回NULL | 失败时抛出异常 |
包含关系 | new封装了malloc,直接free不会报错, 但是这只是释放内存,而不会析构对象 |
delete
和free
的区别:
delete会调用析构函数进行清理
delete释放对象数组时候不要忘了 []
《C++运算符重载》
=
只能重载为成员函数,注意最好深拷贝(a =b) = c;
a = b其实是a的引用,为了保持=
的这个特性,=
的返回值为&
才是风格最好的写法<<
时,os只能是引用,因为ostream的拷贝构造函数时私有的,无法生成对象。++
和--
时,写一个无用int类型形参的版本operator int() { return n; }
用于后置表达式传参;前置返回引用,后置拷贝this,操作后再返回(本身,前置就是返回引用;后置不能返回引用,不能作为左值).
、.*
、::
、? :
、sizeof
成员函数 | 非成员函数 |
---|---|
= 、-> 、[] 、() 必须是成员函数改变状态的: ++ 、-- 、& 必须是 |
因为没办法修改ostream和istream类,<< 、>> 必须非成员且声明友元具有对称性、两边可互换的应该是非成员 |
总结:
特点 | 优点 | 缺点 | |
---|---|---|---|
shared_ptr |
具有引用计数器 | 解决 auto_ptr 在对象所有权上的局限性 | 循环引用 :当两个对象相互使用一个shared_ptr成员变量指向对方,使引用计数失效,导致内存泄漏 |
weak_ptr |
协助 shared_ptr 工作,可获得资源的观测权 没有引用计数,不控制对象生存期 |
解决循环引用问题 | |
unique_ptr |
持有对对象的独有权,不能拷贝只能移动 | 禁止了拷贝语义,会在编译期报错 | |
auto_ptr |
类似于unique,new获得对象,auto销毁时对象也被delete | 可能会对一块堆空间多次delete,导致报错(解决方法 1.深拷贝 2.unique 3.计数shared)《auto被废弃的原因》 |
如何选择?
RAII
(Resource Acquisition Is Initialization):资源获取就是初始化。保证使用对象时先构造对象最后析构对象,是常用的管理资源、避免内存泄漏的办法。
引入了智能指针类型来管理动态对象,用来安全使用动态内存
《终于有一篇小白能看懂的智能指针详解了》
《C++ STL 四种智能指针》
《shared_ptr循环引用问题以及解决方法》
问题:
解决:
智能指针注意事项:
shared_ptr 允许多个指针指向同一对象
对象具有引用计数器,故会出现循环依赖问题。
#include
make_shared<int>(arg) 返回一个arg初始化的对象
shared_ptr<int>p(obj) 可以拷贝 (递增obj计数器)
p = obj (递增obj,递减p)
p.use_count() 返回与p共享对象的智能指针数量
p.unique() 若p.use_count()为1返回true,否则false
p.reset(new T(obj)) 指向一个新对象,更新引用计数
唯一内存泄漏的情况:循环引用。 当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏
对ap来说:只有调用了A的析构函数,才会去释放它的成员变量bptr。何时会调用A的析构函数呢?就是ap的引用计数为0;对于ap和bp来说,它们都拿着对方的share_ptr(有点类似于死锁的现象),没法使得ab和bp的引用计数为0。那么A和B的对象均无法析构
解决方法是弱引用 weak_ptr
unique_ptr 独占对象
auto_ptr
是C++98中的智能指针,有拷贝语义,原对象拷贝给新对象的时候,原对象就会被设置为nullptr,如果再去使用原来的对象的话,那么整个程序就会崩溃掉;unique_ptr会在编译期报错
unique_ptr<int>p(new int(10)) 定义时需要绑定到new返回的指针上
不支持拷贝和赋值
p.release() 放弃指针控制权,返回指针并将p置为空
p.reset() 释放对象
p.reset(u) 如果提供了内置指针u,令p指向u,否则将p置为空
将所有权从p1转移给p2
unique_ptr<int>p2(p1.release());
将所有权从p3转移给p2
unique_ptr<int>p3(new int(6));
p2.reset(p3.release());
weak_ptr 弱引用,指向shared_ptr管理的对象
没有引用计数,不控制对象生存期;
指向由shared_ptr管理的对象,不影响引用计数;
用于打破循环引用
weak_ptr<int>p
p = obj; obj可以是shared_ptr或weak_ptr,赋值后共享对象
p.reset() 置为空
p.use_count() 与p共享的shared_ptr的数量
p.expired() 若use_count为0,返回true,否则false
p.lock() 如果expired为true,返回空shared_ptr;否则返回指向w对象的shared_ptr
三五法则:
C++11前,对拷贝行为的管理通过 “一析两拷” 来控制;
后扩充两个 “移动” 后成为五法则,用于对对象的拷贝、移动、赋值和销毁的控制。
如果一个类
需要一个析构 ==>
需要拷贝构造和拷贝赋值
需要拷贝构造 <==>
需要拷贝赋值 (不一定需要析构)
构造函数
析构函数
释放对象在生存期分配的所有资源
销毁时机:
为什么一个类定义了析构函数就几乎肯定要定义拷贝构造函数和拷贝赋值运算符
如果需要析构函数,类中必然出现了指针类型的成员;有指针类型的成员,我们必须防止浅拷贝问题,拷贝构造函数和赋值操作符是防止浅拷贝问题所必须的
拷贝构造函数
初始化
三种调用情况:(1)
赋值初始化;
(2)
对象作为函数参数,值传递; (3)
对象作为函数返回值,值传递返回
拷贝赋值运算符
赋值时
同样地,当成员变量存在指针时需要自己定义拷贝赋值运算符
三部曲(同重载运算符 = ):
移动构造函数
C++11移动构造函数详解
适用情况: 以移动而非深拷贝的方式初始化含有指针成员的类对象(用a初始化b后就不需要了)
Str(Str &&s)
,只有用一个右值 / 将亡值初始化另一个对象的时候,才会调用nullptr
move()
可以将一个左值变成右值,a = move(b) 调用移动构造优点:
拷贝构造 | 移动构造 |
---|---|
先将传入的参数对象进行一次深拷贝,再传给新对象 | 省略了拷贝开销 |
移动赋值运算符
同样地,是内存移动而不是拷贝,省略了拷贝开销
《OpenMP和MPI并行模式的区别》
OpenMP是多线程程序设计的编译处理方案。
并行编程框架对比:
MPI
(message passing interface):多主机联网协作并行计算的接口。 进程级。优点是并行规模的伸缩性强,可处理大规模问题;缺点是如果在单主机上并行计算效率低,因为使用进程间通信来协调,内存开销大、编程复杂、调试麻烦。通信延迟和负载平衡问题;一个进程出问题会造成程序错误;另外对源代码的改动较大OpenCL
(Open Computing Language):主要面向异构系统(不同语言实现的系统)的GPU并行编程。OpenMP
(Open Multi-Processing):单主机上多核并行计算的工具,主要针对循环并行化。 线程级。使用线程间共享内存的方式协调并行,在多核CPU上效率很高,内存开销小,编程语句简洁,编译器普遍支持;缺点是只能在单台主机上工作,不适用于计算机集群、且不适用于复杂的线程间同步和互斥。为什么选OpenMP?
如何使用?
利用OpenMP预编译指令,并行化处理区域、调度方式等。
也可以在需要的时候加入线程同步及通信机制
代码须满足的条件:
《OpenMP并行构造的schedule子句详解》
#pragma omp 指令[子句,[子句]…]
指令:
并行域产生 - parallel;
任务分担 - for;(需保证无数据依赖)
子句:
#pragma omp parallel for schedule(static, 16) 指定任务调度
不同参数采用不同调度方式,chunk表示每一块分块的大小(chunk个for子句一起算)
shedule(static,[chunk]):直接获取整块内容;低开销,但分配不均衡(for循环长度不变时用)
shedule(dynamic,[chunk]):执行完毕后自动获取下一个块;高开销,但解决分配不均衡(for循环长度变化的时候使用)
shedule(guide,[chunk]):
#pragma omp parallel for private(i,x,y)
private(<variable list>):指定变量在线程中有自己的私有副本
具体:所有线程不能访问其他的i;所有线程不能给共享的i赋值
1. 线程池
2. 触发模式
工作模式:(回调次数的差异)
LT 模式(水平触发) |
ET 模式(边沿触发) |
|
---|---|---|
阻塞 | block、non-block | non-block,否则会在最后一次阻塞 |
特点 | 就算对就绪的fd不作IO操作,内核还是会继续通知 | 对fd的就绪只通知一次 |
优点 | 减少了epoll事件重复触发的次数,效率比LT高。(必须非阻塞、必须一次读完) | |
缺点 | 系统中一旦有大量你不需要读写的就绪文件描述符,它们每次调用epoll_wait都会返回 | |
底层原理 | 检测到buf里面有数据就调用回调 | 从协议栈检测到接收数据,就调用一次回调 |
如何设置? 1. 添加fd时设置为非阻塞 2. fd的epoll_event为EPOLLET
3. while读出
特别注意: read返回-1时要判断EAGAIN,此时不退出,下次还可继续读取
本来1w左右并发量,试一试水平和边缘触发的区别
3. 数据复制
4. 上下文切换 锁
socket
《深入理解socket中的recv函数和send函数》
send()
仅仅是把应用层buffer的数据拷贝到socket的内核发送缓冲区中,发送是TCP的事情
recv()
所做的工作,就是把内核缓冲区中的数据拷贝到应用层用户的buffer里面,并返回拷贝的字节数。(注意:是拷贝,不是像read那样取之后,清空接受缓冲区内的数据。)
epoll
epoll_create
会创建一个eventpoll
的对象,并返回一个与之对应文件描述符;
struct eventpoll {
spin_lock_t lock;
struct mutex mtx;
wait_queue_head_t wq;
wait_queue_head_t poll_wait;
struct list_head rdllist; //就绪链表
struct rb_root rbr; //红黑树根节点
struct epitem *ovflist;
}
epoll_ctl
控制红黑树结点的增删,其中每个结点上保存的是epitem
类型的结构体
struct epitem {
struct rb_node rbn;
struct list_head rdllink;
struct epitem *next;
struct epoll_filefd ffd;
int nwait;
struct list_head pwqlist;
struct eventpoll *ep;
struct list_head fllink;
struct epoll_event event;
}
epoll_ctl
原理:
ep_poll_callback()
(事件就绪时会调用)epoll_wait
原理:(回调)
协议栈将数据解析出来触发回调通知epoll,epoll根据四元组+协议这么一个五元组来确定fd,进而在红黑树中找到结点,回调函数执行以下操作:
一共有5个通知的地方:
再就是LT和ET的知识点了
《设计模式六大原则》
各种性质
名字 | 定义 | 具体使用 | 优点 |
---|---|---|---|
1. 单一职责原则 | 一个类 / 接口只负责单一职责 。如果有两个职责且发生变化影响到这两个职责,那就不需要拆分。 |
可以降低类的复杂度;提升可读性; 降低修改风险 |
|
2. 开闭原则(基础设计原则) | 软件应该对扩展开放,对修改关闭 ;即应尽量在不修改原有代码的情况下进行扩展;抽象层是关键,将实现行为移至实现层。 |
可以提高复用性和维护性 是面向对象开发的要求 |
|
3. 里氏代换原则 | 所有引用基类的地方能使用派生类对象 ,尽量不要改写,而是实现 |
父类尽量设计为抽象类或接口,子类所有方法必须在父类中声明 或 必须实现父类中声明的所有方法。 在程序中尽量使用基类类型来对对象进行定义,而在运行时再确定其子类类型,用子类对象来替换父类对象 |
在一定程度上克服了继承的缺点:子类必须拥有父类的属性和方法,且修改父类时还得考虑子类的修改 |
4. 依赖倒置原则 | 要面向接口编程,不要面向实现编程 |
降低了耦合性、提高了稳定性、可读性和可维护性 | |
5. 接口隔离原则 | 接口要精简 ,即客户端不应该依赖那些它不需要的接口 |
减小接口的粒度,提高灵活性和可维护性 高内聚低耦合,减少代码冗余 |
|
6. 迪米特法则 | 降低耦合 ,不和不相关的对象联系。 |
缺点:容易引入太多中介类。因此应该保证结构清晰 | 降低耦合性 提高可复用性和可扩展性(耦合性低的结果) |
菱形继承
的模式来设计(用虚继承来避免二义性和重复存储),比较符合逻辑。低耦合
度。因为无量纲和有量纲的数据之间交互较少。《如何降低耦合度》、可扩展
性。在我的理解看来,这一块儿类会自动把读到的物理参数转换为无量纲参数,这样在未来接入流场,需要无量纲速度的时候可以直接处理;因为统一采用无量纲参数对外交互(好比网络字节序统一用大端),所以便于扩展。可复用
性。输入端采用XML文件解析(调库),输出端按照可视化软件Paraview的读取格式手写了一个输出的函数,可以打包成库,直接在其他数值模拟软件里复用。单例模式
的饿汉模式,保证只生成一个实例且提供一个全局访问点。单例模式三要素:
1.构造函数私有化
2.实例具有静态属性
3.get方法返回静态实例
class Singleton //实现单例模式的类
{
private:
Singleton() {} //私有的构造函数
public:
static Singleton& GetInstance() // 实例化创建。获得本类实例的唯一全局访问点
{
static Singleton instance; // 实例
return instance;
}
};
模式 | 懒汉 | 饿汉 |
---|---|---|
内容 | 第一次调用时创建 | 一开始就创建 |
优点 | 节省空间 | 节省运行时间 |
缺点 | 双重锁定式才保证线程安全;另外耗费一定的判断时间 | 提前占用系统资源,加载耗时 |
适用情况 | 第一次调用时创建 | 一开始就创建 |
《912.排序数组》
分类 | 简单算法 | 改进算法 |
---|---|---|
交换 | 冒泡 | 快速 |
插入 | 直接插入 | 希尔 |
选择 | 简单选择 | 堆 |
归并 | 归并 |
排序方法 | 平均 | 最好 | 最坏 | 空间 | 稳定 | 备注 |
---|---|---|---|---|---|---|
冒泡排序 | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( n 2 ) O(n^2) O(n2) | 1 | 稳定 | |
简单选择排序 | O ( n 2 ) O(n^2) O(n2) | 同 | 同 | 1 | 稳定 | 略优于冒泡i |
直接插入排序 | O ( n 2 ) O(n^2) O(n2) | O ( n ) O(n) O(n) | O ( n 2 ) O(n^2) O(n2) | 1 | 稳定 | 略优于前二者 |
希尔排序 | O ( n l o g n ) O(nlogn) O(nlogn)~ O ( n 2 ) O(n^2) O(n2) | O ( n 1 . 3 ) O(n^1.3) O(n1.3) | O ( n 2 ) O(n^2) O(n2) | 1 | ||
堆排序 | O ( n l o g n ) O(nlogn) O(nlogn) | 同 | 同 | 1 | 不适用个数少 | |
归并排序 | O ( n l o g n ) O(nlogn) O(nlogn) | 同 | 同 | O ( n ) O(n) O(n) | 稳定 | |
快速排序 | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n l o g n ) O(nlogn) O(nlogn) | O ( n 2 ) O(n^2) O(n2) | O ( l o g n ) O(logn) O(logn)~ O ( n ) O(n) O(n) |
最好情况: 基本有序时,用简单算法即可
最坏情况: 堆 / 归并
个数少: 用简单算法即可;个数多: 用改进算法
《sizeof和strlen的区别及使用详解》:strlen
除了空字符\0
不统计外,其余的转义字符都统计
《typedef和define区别》
本文章不定期更新,欢迎讨论交流。