我在2018年9月份参加了几家公司的笔试,也在牛客网上做了一些题目,下面我把稍微做个小结。
1 问题:为什么不能返回局部变量地址,却可以返回局部变量?
局部变量用作返回值时,会生成一个局部变量的拷贝用作返回值,之后局部变量会被系统回收;
函数不能返回局部变量的地址,因为如果返回局部变量的地址,系统回收后,指针、引用指向的内容就无意义了。
2 传地址或者引用时,形参与实参都有相同的地址;
传值调用时,只是将数据进行了复制,不会改变实参的值。
3 面向对象的五大基本原则
单一职责原则(Single-ResposibilityPrinciple):
一个类,最好只做一件事,只有一个引起它的变化。单一职责原则可以看做是低耦合、高内聚在面向对象原则上的引申,
将职责定义为引起变化的原因,以提高内聚性来减少引起变化的原因。
开放封闭原则(Open-Closed principle):
软件实体应该是可扩展的,而不可修改的。也就是,对扩展开放,对修改封闭的。
里式替换原则(Liskov-SubstituionPrinciple):
子类必须能够替换其基类。这一思想体现为对继承机制的约束规范,只有子类能够替换基类时,才能保证系统在运行期内识别子类,这是保证继承复用的基础。
依赖倒置原则(Dependecy-InversionPrinciple):
依赖于抽象。具体而言就是高层模块不依赖于底层模块,二者都同依赖于抽象;抽象不依赖于具体,具体依赖于抽象。
接口隔离原则(Interface-Segregation Principle):
使用多个小的专门的接口,而不要使用一个大的总接口
4 编译器只要能完成程序语言翻译成机器语言即可,所以任意的语言均可实现。
5 宏与类型无关,但是C++中的函数必须指定返回类型,故宏可以做函数不能做的事。
宏只是预定义的函数,在编译阶段不进行类型安全性检查,在编译的时候将对应函数用宏命令替换。对程序性能无影响。
6 对于堆来说,生长方向是向上的,也就是向着地址增加的方向;对于栈来说,它的生长方向是向下的,就是向着内存地址减小的方向生长。
在题
union X
{
unint16_t a;
struct Z
{
unint8_t m;
unint8_t n;
}z;
};
union X x;
x.a = 0x1234;
union 就是公用一块内存,在这题中,a内存与n和m的是在一起,而这是在栈中,栈中的内存分配是大地址向小地址分配的,
所以&m > &n,而大字节序就是一个字的高字节对应高地址,低字节对应低地址,所以m中应该是0x1234中的高字节部分12,
而n中对应的就是低字节部分34;小字节序正好与这个相反,m对应34,n对应12。
7 用户态切换到内核态的 3 种方式
a. 系统调用
b. 异常
c. 外围设备的中断
8 编译的完整过程:
C源程序-->预编译处理(.c)-->编译、优化程序(.s、.asm)-->汇编程序(.obj、.o、.a、.ko)-->链接程序(.exe、.elf、.axf等)
9 指向虚函数表的指针在32位系统下占用4个字节,其地址分布在整个类成员变量的地址的首部,接下来就是变量a的地址、b的地址。当将test对象obj赋给指向整型的pInt后,指针pInt指向了地址的首部也就是虚函数表指针,所以*(pInt+0)=100改变的是虚函数表的值,接下来*(pInt+1)=200改变的是变量a的值,变量b没有变换。
10 抽象类 : 类中至少有一个方法是抽象方法,则该类就是抽象类
接口 :类中的方法全部都是抽象方法。
11 构造函数特点:
a)没有函数返回值类型
b)必须与本类名完全相同
c)当没有为一个类显示的定义一个构造函数时,系统将自动分配一个默认的无参的方法体为空的构造函数。如果定义了一个构造函数,那么默认的就没有了。
12 初始化为NULL(0)的类指针可以安全的调用不涉及类成员变量的类成员函数而不出错,但是如果类成员函数中调用了类成员变量则会出错,原因是调用成员函数的时候,函数地址是编译期间确定的,成员函数不通过对象指针去调用,对象指针仅仅作为参数传入函数然后去调用成员变量。而如果是虚函数,需要通过this去计算vptr,然后找到vtable,而this为NULL,因此会报错。
13 链接:https://www.nowcoder.com/questionTerminal/09a55229fc004c2ba921678246f8d7f8
来源:牛客网
st_task:
id value timestamp
0000|0000|0000 0000|0000 0000 0000 0000 (physical)
a:
0000 0000 0001 0001 (logic)
0001 0001 0000 0000 (physical)
st_task:
id value timestamp
0001|0001|0000 0000|0000 0000 0000 0000
1 |0 |0
14 reinterpret_cast:一个指针转化为其他类型的指针时,不做类型检测,操作结果是一个指针指向另一个指针的值的二进制拷贝;
static_cast:允许执行隐式转换和相反的转换操作,父类转换为子类是强制转换Son *son=static_cast(father),而子类转换为父类就是隐式转换;
dynamic_cast:用于对象的指针和引用,当用于多态类型转换时,允许隐式转换及相反的过程中,与static_cast的不同之处在于,在相反的转换过程中,dynamic_cast会检测操作的有效性,如果返回的不是被 请求的 有效完整对象,则返回null,反之返回这个有效的对象,如果是引用返回无效时则会抛出bad_cast异常;
const_cast:这个转换操作会操纵传递对象的const属性,或者设置或者移除该属性。
15 ++ 是一目运算符,自增运算,它只能用于一个变量,即变量值自增1, 不能用于表达式。
++(a++) 里,小括号优先。
(a++) 是 表达式,按运算规则,不能对 表达式 作 自增运算.
16 函数模板调用时不需要显式指定类型,系统自动匹配参数类型,若没有合适的,会进行报错。而类模板使用需要显式指定类型。
17
Nagle算法的规则:
(1)如果包长度达到MSS,则允许发送;
(2)如果该包含有FIN,则允许发送;
(3)设置了TCP_NODELAY选项,则允许发送;
(4)未设置TCP_CORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送;
(5)上述条件都未满足,但发生了超时(一般为200ms),则立即发送。
Nagle算法只允许一个未被ACK的包存在于网络,它并不管包的大小,因此它事实上就是一个扩展的停-等协议,只不过它是基于包停-等的,而不是基于字节停-等的。Nagle算法完全由TCP协议的ACK机制决定,这会带来一些问题,比如如果对端ACK回复很快的话,Nagle事实上不会拼接太多的数据包,虽然避免了网络拥塞,网络总体的利用率依然很低。
Nagle算法是silly window syndrome(SWS)预防算法的一个半集。SWS算法预防发送少量的数据,Nagle算法是其在发送方的实现,而接收方要做的是不要通告缓冲空间的很小增长,不通知小窗口,除非缓冲区空间有显著的增长。这里显著的增长定义为完全大小的段(MSS)或增长到大于最大窗口的一半。
注意:BSD的实现是允许在空闲链接上发送大的写操作剩下的最后的小段,也就是说,当超过1个MSS数据发送时,内核先依次发送完n个MSS的数据包,然后再发送尾部的小数据包,其间不再延时等待。(假设网络不阻塞且接收窗口足够大)
举个例子,比如之前的blog中的实验,一开始client端调用socket的write操作将一个int型数据(称为A块)写入到网络中,由于此时连接是空闲的(也就是说还没有未被确认的小段),因此这个int型数据会被马上发送到server端,接着,client端又调用write操作写入‘\r\n’(简称B块),这个时候,A块的ACK没有返回,所以可以认为已经存在了一个未被确认的小段,所以B块没有立即被发送,一直等待A块的ACK收到(大概40ms之后),B块才被发送。整个过程如图所示:
这里还隐藏了一个问题,就是A块数据的ACK为什么40ms之后才收到?这是因为TCP/IP中不仅仅有nagle算法,还有一个TCP确认延迟机制 。当Server端收到数据之后,它并不会马上向client端发送ACK,而是会将ACK的发送延迟一段时间(假设为t),它希望在t时间内server端会向client端发送应答数据,这样ACK就能够和应答数据一起发送,就像是应答数据捎带着ACK过去。在我之前的时间中,t大概就是40ms。这就解释了为什么'\r\n'(B块)总是在A块之后40ms才发出。
当然,TCP确认延迟40ms并不是一直不变的,TCP连接的延迟确认时间一般初始化为最小值40ms,随后根据连接的重传超时时间(RTO)、上次收到数据包与本次接收数据包的时间间隔等参数进行不断调整。另外可以通过设置TCP_QUICKACK选项来取消确认延迟。
18 同步是害怕在操作过程的时候被其他线程也进行读取操作,一旦是原子性的操作就不会发生这种情况。
因为一步到位的操作,其他线程不可能在中间干涉。另外三项都有读取、操作两个步骤,而X=1则是原子性操作。
19 int&)a:将a的引用强制转换为整型,意思是a所在的内存,本来定义的时候为float类型并初始为1.0f,但现在我要按int类型解释这段内存(也就是说a所在的内存地址中的数据本来是按float型存储表示的,你非要按int型来解释不可)。
1.0f 在内存中的存储为
0 011 1111 1 000 0000 0000 0000 0000 0000.
把他按整型数解释为2^29+2^28+2^27+2^26+2^25+2^24+2^23=1065353216
(int&)a 相当于*(int*)&a ,*(int*)(&a),*((int*)&a)
(int)a a在内存中的值转换成int类型
20 非常量引用的初始值必须为左值
要理解这个先得理解左值和右值的概念
一个区分左值与右值的便捷方法是:看能不能对表达式取地址,如果能,则为左值,否则为右值。
本题举例:
执行f1(0),实参0要传成A对象,那么执行
A &a1 = 0; //这是不行的。
执行f2(0),实参0要传成A对象,那么执行
const A &a2 = 0;//这是可行的。
21 文件指针指向的是一块内存区域,这块区域存储着打开的文件的相关信息,
包括文件读取指针当前位置、文件读取缓冲区大小等信息,并不是指向文件的。
fscanf是从文件中格式化读取,fprintf才是向文件中格式化写入。
22 当基类构造函数需要外部传递参数才能进行初始化时,派生类必须显式定义构造函数,
为基类传递参数;基类如果不需要传递或者可以不传递参数,派生类可以不用显式定义构造函数。
23 使用 inline 关键字的函数只是用户希望它成为内联函数,但编译器有权忽略这个请求,比如:若此函数体太大,则不会把它作为内联函数展开的。
头文件中不仅要包含 inline 函数的声明,而且必须包含定义,且在定义时必须加上 inline 。【关键字 inline 必须与函数定义体放在一起才能使函数成为内联,仅将 inline 放在函数声明前面不起任何作用】
inline 函数可以定义在源文件中,但多个源文件中的同名 inline 函数的实现必须相同。一般把 inline 函数的定义放在头文件中更加合适。
类内的成员函数,默认都是 inline 的。【定义在类声明之中的成员函数将自动地成为内联函数】
不管是 class 声明中定义的 inline 函数,还是 class 实现中定义的 inline 函数,不存在优先不优先的问题,因为 class 的成员函数都是 inline 的,加了关键字 inline 也没什么特殊的。
24 数组作为函数的参数会退化成指针
25 含有纯虚函数的类是抽象类,对于继承这样的类的类(派生类)来说,如果派生类实现了基类的纯虚函数,则派生类可以实例化。
若派生类没有实现该纯虚函数,则该派生类也是抽象类,即不能实例化。
综上所述,派生类不一定非得要实现基类的纯虚函数。
26 A:“很多程序员希望STL实现是完全线程安全的“。所以不安全。
B:vector的存在可以使开发者不必关心内存的申请和释放。但是,vector的一个缺点就是它的内存分配是按照2的倍数分配内存的。
C:错误。要知道 std::sort 不是稳定的排序算法,它不保证“相等”元素的相对位置,使用 std::stable_sort 来保证这一点
D:STL的容器可以分为以下几个大类:
一:序列容器, 有vector, list, deque, string.
二 : 关联容器, 有set, multiset, map, mulmap, hash_set, hash_map, hash_multiset, hash_multimap
三: 其他的杂项: stack, queue, valarray, bitset
E:正确。堆栈是一个线性表,插入删除操作都在一端进行,deque是先进先出的,操作原理和stack是一样的
27 函数的参数是从右向左压栈的,输出时从栈顶开始,相当于: int i = 3; ++i; ++i; printf("%d,%d",i,i);所以是 5,5;
再举一个例子,int i = 1; printf("%d,%d", i += 2, i *= 3); 在输出i之前先进行了i *= 3和 i += 2;最终i = 5;所以结果是5,5;
28 操作系统中引入多道处理技术是为了提高CPU和I/O设备的(利用率)。
29 系统中的“颠簸”是由(缺页率高)引起的
30 使用SPOOLING技术实现(虚拟设备)。
spooling(外部设备联机并行操作)技术实现了虚拟设备功能。多个进程同时使用一独享设备,而对每一个进程而言,都认为自己独占这一设备。 设备并没有分配给任何进程,在输入井或输出井中,分配给进程的是一存储区和建立一张io请求表。
31 站点A、B、C通过CDMA共享链路,A、B、C的码片序列(chipping sequence)分别是(1,1,1,1)、(1,-1,1,-1)和(1,1,-1,-1),若C从链路上收到的序列是(2,0,2,0,0,-2,0,-2,0,2,0,2),则C收到A发送的数据是 (101 )
C接收到的是A,B发送过来的叠加码片,C想要看A发送的数据,就将接收到的叠加码片与A的码片序列进行规格化内积操作:(2,0,2,0;0,-2,0,-2;0,2,0,2)每四位与(1,1,1,1)进行规格化内积,(2*1+0*1+2*1+0*1)/4=1;(0*1+-2*1+0*1+-2*1)/4=-1,-1即0;(0*1+2*1+0*1+2*1)/4=1;可以得到结果101
32 路由器的缺点是(成为网络瓶颈)
33 HTTP 2.0新特性
增加二进制分帧
压缩头部(header compression)
多路复用(multiplexing)
请求优先级
服务器提示(server push)
34 页表的作用是实现从页号到物理块号的(地址映射)
在页式管理中,页表的作用是实现从页号到物理块号的地址映射,存储页表的作用是记录内存页面的分配情况。 页表实际上就是进程的虚存空间与系统中的物理存储空间的一个映射关系。因为每个进程都有自己独立的虚存空间,所以操作系统需要为每个进程保存一个页表。进程切换的时候操作系统就会把即将调度运行的那个进程的页表加载MMU,完成地址空间的切换。
35 如果要持续的 ping 某个地址,可以使用下列哪个参数?( )
-t :不停的ping对方主机,直到你按下Control-C;
-a:解析计算机NetBios名;
-n:发送count指定的Echo数据包数;
-l :定义echo数据包大小。