c/c++面试题

1.*a 和&a 有什么区别
&a:其含义就是“变量a的地址”。
*a:用在不同的地方,含义也不一样。
在声明语句中,*a只说明a是一个指针变量,如int *a;
在其他语句中,*a前面没有操作数且a是一个指针时,*a代表指针a指向的地址内存放的数据,如b=*a;
*a前面有操作数且a是一个普通变量时,a代表乘以a,如c=ba。
2.请你来说一下什么时候会发生段错误
1.段错误通常发生在访问非法内存地址的时候
2.使用野指针试图修改字符串常量的内容

3.请你回答一下野指针是什么?
野指针就是指向一个已删除的对象或者未申请访问受限内存区域的指针

4.请你来说一下函数指针
定义: 函数指针是指向函数的指针变量。
函数指针本身首先是一个指针变量,该指针变量指向一个具体的函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。

5.说一下指针数组
本质是数组,数组内存放的是指针
6.说一下数组指针
本质是指针,指针指向这个数组

7.请你来回答一下include头文件的顺序以及双引号””和尖括号<>的区别?
Include头文件的顺序:对于include的头文件来说,如果在文件a.h中声明一个在文件b.h中定义的变量,而不引用b.h。那么要在a.c文件中引用b.h文件,并且要先引用b.h,后引用a.h,否则汇报变量类型未声明错误。
双引号和尖括号的区别:编译器预处理阶段查找头文件的路径不一样。
对于使用双引号包含的头文件,查找头文件路径的顺序为当前头文件目录

8.变量的声明和定义有什么区别
变量的定义为变量分配地址和存储空间, 变量的声明不分配地址。一个变量可以在多个地方声明, 但是只在一个地方定义。 加入extern 修饰的是变量的声明,说明此变量将在文件以外或在文件后面部分定义。
说明:很多时候一个变量,只是声明不分配内存空间,直到具体使用时才初始化,分配内存空间, 如外部变量。

9.简述#ifdef、#else、#endif和#ifndef的作用
利用#ifdef、#endif将某程序功能模块包括进去,以向特定用户提供该功能。在不需要时用户可轻易将其屏蔽。

10.简述strcpy、sprintf 与memcpy 的区别
操作对象不同,strcpy 的两个操作对象均为字符串,sprintf 的操作源对象可以是多种数据类型, 目的操作对象是字符串,memcpy 的两个对象就是两个任意可操作的内存地址,并不限于何种数据类型。
执行效率不同,memcpy 最高,strcpy 次之,sprintf 的效率最低。
实现功能不同,strcpy 主要实现字符串变量间的拷贝,sprintf 主要实现其他数据类型格式到字 符串的转化,memcpy 主要是内存块间的拷贝。
注意:strcpy、sprintf 与memcpy 都可以实现拷贝的功能,但是针对的对象不同,根据实际需求,来 选择合适的函数实现拷贝功能。
11、new/delete和malloc/free的区别
1.malloc/free是C/C++的库函数,需要stdlib.h;new/delete是C++的关键字;
2.都可用于申请动态内存和释放内存,new/delete在对象创建的时候自动执行构造函数,对象消亡前自动执行析构函数,底层实现其实也是malloc/free
3.new无需指定内存块的大小,编译器会根据类型信息自行计算;malloc需要显式地支持所需内存的大小
4.new返回指定类型的指针,无需进行类型转换;malloc默认返回类型为void*,必须强行转换为实际类型的指针
5.new内存分配失败时会抛出bad_alloc异常;malloc失败时返回NULL

12、指针和引用的异同点;如何相互转换
1.本质:引用是别名,而指针是地址
2.指针在运行时可以改变所指向的值,而引用一旦与某个对象绑定之后就不再改变(指向的地址不能改变,但指向的内容可以改变)
3.指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值;因此指针可以改变指向的对象,而引用的对象不能修改
4.由于硬件通过地址访问内存位置,因此引用可以理解为一个常量指针,只能绑定到初始化它的对象上

13、struct、union的异同

(1)struct中每个变量依次存储;union中,每个变量都是从偏移地址零开始存储,同一时刻只有一个成员存储于该地址
(2)struct内存大小遵循结构对齐原则
(3)数据成员对齐规则:每个数据成员存储的起始位置要从该成员大小的整数倍开始
(4)数据成员包含结构体:结构体成员要从其内部最大元素对象的整数倍地址开始存储
(5)结构体总大小:其内部最大基本成员的整数倍,不足则要补齐
(6)union内存大小为其最大成员的整数倍

14、extern C的作用
C++支持函数重载,即不同名字空间namespace的两个函数原型声明可以完全相同,或者两个函数同名但参数列表不同;g++编译器会对此进行name mangling,生成全局唯一的符号名称,使链接器可以准确识别
C语言不支持函数重载,即不允许同名符号,所以不需要这些工作,因此在C++代码中加入extern C,是为了链接规范
15、memcpy()函数需要注意哪些问题
函数原型声明void *memcpy(void *dest, void *src, unsigned int count);
memcpy函数用于把资源内存(src所指向的内存区域)中连续的count个字节数据拷贝到目标内存(dest所指向的内存区域)
数据长度count的单位是字节,1byte = 8bit
数据类型为char,则数据长度就等于元素的个数;其他数据类型则要注意数据长度的值
n * sizeof(type_name)的写法

16、strcat、strncat、strcmp、strcpy函数
strcpy拷贝函数,不会判断拷贝大小,也没有任何安全检查,不会检查目的地址内存是否够用;
strncpy拷贝函数,会计算复制字符串的大小,但没有检查目标的边界;
strcmp比较函数,把src所指向的字符串与dest所指向的字符串进行比较,若dest与src的前n个字符相同,则返回0;若dest大于src,则返回大于0的值;若dest小于src,则返回小于0的值
strcat功能是将两个char类型连接;strncat功能是在字符串的结尾追加n个字符
17、机器大小端问题
大端指数据的高字节保存在内存的低地址中,数据的低字节保存在内存的高地址中;小端与此相反。
小端:强制转换数据不需要调整字节内容,1、2、4字节的存储方式一样
大端:符号位的判定固定为第一个字节,很容易判断正负
union判断大小端的方法
union从低地址开始存,同一时间内只有一个成员占用内存;修改其中一个成员的值必然会影响另一个成员的值
18、static的用法(定义和用途)
static修饰局部变量:使其变为静态存储方式(静态数据区),函数执行完成之后不会被释放,而是继续保存在内存中;
static修饰全局变量:使其只在本文件内部有效,其他文件不可链接或引用该变量;
static修饰函数:静态函数,即函数只在本文件内部有效,对其他文件不可见;避免同名干扰,同时保护
19、const的用法(定义和用途)
const起到强制保护的修饰作用,可以预防意外改动,提高程序的健壮性

const修饰常量:定义时就初始化,以后不能更改;
const修饰形参:func(const int a); 该形参在函数里不能改变;
const修饰类成员函数:const类成员函数不能改变成员变量的数值
20、const常量和#define的区别(编译阶段、安全性、内存占用等)
const定义的常量有类型名字,存放在内存的静态区域中,在编译时确定其值;
#define定义的常量是没有类型的一个立即数,编译器会在预处理阶段将程序中所有使用到该常量的地方进行拷贝替换;
由于#define的拷贝有很多份,故宏定义的内存占用要高得多
21、volatile的用法
被定义为volatile的变量可能会被意想不到地改变,编译器不会对volatile变量有关的运算进行编译优化:每次使用该变量必须从内存地址中读取,而不是保存在寄存器中的备份
用到volatile的几种情况
并行设备的硬件寄存器(如状态寄存器)
中断服务子程序会访问到的非自动变量
多线程应用中被几个任务共享的变量
22、常量指针、指针常量、常量引用(没有引用常量)
常量指针即常量的指针,指针所指向的是个常量,可以被赋值为变量的地址,但是不能通过这个指针来修改
指针常量本质是一个常量,指针所指向的值不可以改变,但指向的地址所对应的内容可以变化
(具体参考问题17)

23、变量的作用域(全局变量和局部变量)
全局变量:在所有函数体外部定义的,程序所在部分都可以使用,不受作用域的影响(生命期一直到程序的结束)
局部变量:局限于作用域内,默认为auto关键字修饰,即进入作用域时自动生成,离开作用域时自动消失;
局部变量可以和全局变量重名,在局部变量作用域范围内,全局变量失效,采用的是局部变量的值
24、sizeof和strlen
sizeof是一个操作符或关键字,不是一个函数,而strlen是一个函数
sizeof返回一个对象或类型所占的内存字节数,不会对其中的数据或指针做运算
strlen返回一个字符串的长度,不包括’/0’
25、sizeof(struct)和内存对齐
内存对齐作用:1、移植原因:某些硬件平台只能在某些特定地址处取特定类型的数据;2、性能原因:数据结构(尤其是栈)应尽可能在自然边界上对齐,未对齐内存需要做两次内存访问,对齐内存仅需要一次
struct内存对齐原则:
结构体成员中,第一个成员偏移量是0,排列在后面的成员的当前偏移量必须是当前成员类型的整数倍
结构体本身占用内存大小,应是结构体内最大数据成员的最小整数倍
#pragma pack(n)预编译指令,所有成员对齐以n字节为准,不再考虑当前类型和最大结构体内类型
union内存对齐原则:
union字节数必须是占用字节数最多的成员的字节数的倍数,而且需要能够容纳其他成员
26、char * const,const char *
const char ptr指向字符常量的指针,ptr是一个char类型的常量,所指向的内容不能修改;
char * const ptr指向字符的指针常数,即const指针,不能修改ptr指针,但可以修改该指针指向的内容
27、inline函数
被频繁调用的函数会导致栈空间或栈内存的大量消耗,因此引入inline修饰函数,即内联函数;内联函数将在程序的每个调用点上“内联式地”展开。内联以代码膨胀为代价,省去了函数调用的开销,从而提高函数的执行效率
28、内存四区,变量存储区域(堆/栈)
1.代码区:.text
2.全局初始化数据区/静态数据区:.data,明确被初始化的全局变量、静态变量和常量数据,整个生命周期内都可能需要访问
3.未初始化数据区:.bss,全局未初始化变量
4.栈区stack:由编译器自动分配释放,存放函数的参数值、局部参数的值等。每当一个函数被调用,该函数返回地址和调用信息,如某些寄存器内容,会被存储到栈区,这个被调用的函数再为它的自动变量和临时变量在栈区上分配空间,即C实现函数递归调用的方法
5.堆区heap:用于动态内存分配
29、strcpy和memcpy的区别
复制的内容不同:strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等
复制的方法不同:strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度
用途不同:通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy
30、递归和循环如何选择
递归算法优点是代码简洁清晰,容易验证正确性;缺点是需要多次数的函数调用,如果调用层数比较深,需要额外增加堆栈处理,会对执行效率有一定影响
循环算法速度快,结构简单;缺点是不能解决所有问题,有些问题不太适用
总结:在求解规模不确定或求解规模明显过大情况下,递归的函数调用开销会很大,因此效率会很低

31、全局变量和静态变量区别
1.存储方式上并无区别,都是静态存储方式
2.非静态全局变量作用域为整个源程序;当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的,而静态全局变量则限制了其作用域,只在定义该变量的源文件内有效
32.在使用gcc 对C语言程序进行编译时,可细分为几个阶段:
预处理(Pre-processing)、编译(Compiling)、汇编(Assembling)、链接(Linking)
33.链表和数组有什么区别
存储形式:
数组是一块连续的空间,声明时就要确定长度。链表是一块可不连续的动态空间, 长度可变,每个结点要保存相邻结点指针。

数据查找:
数组的线性查找速度快,查找操作直接使用偏移地址。链表需要按顺序检索结点, 效率低。

数据插入或删除:
链表可以快速插入和删除结点,而数组则可能需要大量数据移动。

越界问题:
链表不存在越界问题,数组有越界问题。

系统编程
1.请你说一下进程与线程的概念,以及为什么要有进程线程,其中有什么区别
基本概念:
进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发;
线程是进程的子任务, 是CPU调度的基本单位, 用于保证程序的实时性,实现进程内部的并发;
区别:
1.进程是资源分配的最小单位,线程是CPU调度的最小单位;
2.进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。
3.一个进程崩溃,不会对其他进程产生影响;而一个线程崩溃,会让同一进程内的其他线程也死掉。
4.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。
5.进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂。
(系统开销: 由于在创建或撤消进程时,系统都要为之分配或回收资源,如内存空间、I/o设备等。因此,操作系统所付出的开销将显著地大于在创建或撤消线程时的开销。类似地,在进行进程切换时,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置。而线程切换只须保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作。可见,进程切换的开销也远大于线程切换的开销。)
6.通信:由于同一进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现,也变得比较容易。进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。在有的系统中,线程的切换、同步和通信都无须操作系统内核的干预
7.进程适应于多核、多机分布;线程适用于多核
至少回答3个。
2.进程的状态
就绪态 TASK_RUNNING 等待cpu资源
运行态 TASK_RUNNING 占用cpu资源,5毫秒到10毫秒
暂停态 收到暂停的信号
睡眠态 浅度睡眠 响应信号
深度睡眠 不响应信号 硬件中断
僵尸态 进程退出时候,会变成僵尸态,继续占用cpu资源
死亡态 进程退出时候,如果有资源占用,回收数据,没有资源就可以直接退出

3.fork和vfork的区别
1.fork( )的子进程拷贝父进程的数据段和代码段;
vfork( )的子进程与父进程共享数据段
2.fork( )的父子进程的执行次序不确定;
vfork( )保证子进程先运行,在调用exec或exit之前与父进程数据是共享的,在它调用exec或exit之后父进程才可能被调度运行。
3.vfork( )保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
4.当需要改变共享数据段中变量的值,则拷贝父进程。
4.进程间通信的方式:
进程间通信主要包括:管道、系统IPC(包括信号、信号量、消息队列、共享内存)、以及网络通信(套接字socket)。

4.1管道: 管道主要包括无名管道和有名管道
无名管道:PIPE====>int pipe(int pipefd[2]);
1)它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端,读端只能读,写端只能写
2)它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)
3)无名管道没有名字,只有文件描述符数组它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
4.没办法保证写入数据的原子性
有名管道:FIFO====>int mkfifo(const char *pathname, mode_t mode); (管道名,权限)
1)有名管道存储在linux环境下的文件系统里面,可以使用open函数访问
2)能在任意两个进程之间通信
3)写入的数据具有原子性,要不写入成功,要么写入失败
4)全双工

4.2信号(signal):信号是系统软件层次上对中断机制的一个模拟(软中断方式)是一种异步通信机制。
1)信号的种类
可以从两个不同的分类角度对信号进行分类
可靠性方面: 可靠信号 和 不可靠信号
时间关系方面: 实时信号 和 非实时信号
可靠性:主要问题时信号可能丢失,这些信号来源于早期的unix
后期信号是前期信号的改进和扩充,这些信号支持排队,不会丢失
时间: 非实时信号都是不支持排队,都是不可靠信号
实时信号都是支持排队的,都是可靠信号
19)SIGSTOP 9) SIGKILL 这两个信号必须响应,
34-64号实时信号 (从大到小依次响应)

2)对于一个完整的信号生命周期(从信号的发送到相应的处理函数执行完毕)可以分为四个阶段:
信号诞生
信号在进程中注册
信号的响应和处理
信号的执行和注销

4.3信号量: int semget(key_t key, int nsems, int semflg);
信号量不是一种通信方式,只是一个处理方式,一般用于控制多个进程对共享资源的访问,实现不同进程访问共享内存的互斥与同步,帮助你阻塞进程协调不同进程对于共享资源的访问。
特点:
1)信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
2)信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
3)每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
4)支持信号量组。
4.5消息队列:
特点:进程可以通过消息队列读取自己的指定消息
消息队列的操作步骤:
1)申请系统键值 ftok
2)申请消息队列 msgget
3)消息队列的数据发送和接收,配置消息结构体 msgsnd msgrcv
4)消息对列销毁 msgctl IPC_RMID

4.4共享内存(Shared Memory):key_t ftok(const char *pathname, int proj_id);
它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等
特点:
5)共享内存是最快的一种IPC,因为进程是直接对内存进行存取
6)因为多个进程可以同时操作,所以需要进行同步
7)信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问
共享内存的操作步骤:
1)申请系统键值 ftok
2)申请共享内存 shmget
3)映射共享内存到用户空间 shmat
4)对共享内存进行读写访问操作
5)解除共享内存映射 shmdt
6)删除共享内存 shmctl IPC_RMID
里面有哪些参数?
4.6套接字:int socket(int domain, int type, int protocol);
socket也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机之间的进程通信。
5.线程间通信的方式:
1、临界区:
通过多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问;
2、互斥量 Synchronized/Lock:
采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问
3、信号量 Semphare:
为控制具有有限数量的用户资源而设计的,它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目。
4、事件(信号),Wait/Notify:
通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作

6.读写锁与互斥锁的区别
读写锁:int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
分为读锁和写锁。处于读操作时,可以允许多个线程同时获得读操作。
但是同一时刻只能有一个线程可以获得写锁。其它获取写锁失败的线程都会进入睡眠状态,直到写锁释放时被唤醒。
注意:写锁会阻塞其它读写锁。当有一个线程获得写锁在写时,读锁也不能被其它线程获取;写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)。适用于读取数据的频率远远大于写数据的频率的场合。
读锁(共享锁)
----->访问临界资源,可以一个读取数据
----->读锁的时候可以同时上锁,不会阻塞
写锁(互斥锁)
----->访问临界资源,不能一起修改数据
----->写锁的时候不能同时上锁,会阻塞操作

互斥锁:int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutex attr_t *mutexattr);
用于保证在任何时刻,都只能有一个线程访问该对象。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒。
互斥锁和读写锁的区别:
1)读写锁区分读者和写者,而互斥锁不区分
2)互斥锁同一时间只允许一个线程访问该对象,无论读写;读写锁同一时间内只允许一个写者,但是允许多个读者同时读对象。

7.游戏服务器应该为每个用户开辟一个线程还是一个进程,为什么?
游戏服务器应该为每个用户开辟一个进程。因为同一进程间的线程会相互影响,一个线程死掉会影响其他线程,从而导致进程崩溃。因此为了保证不同用户之间不会相互影响,应该为每个用户开辟一个进程

8.请问单核机器上写多线程程序,是否需要考虑加锁,为什么?
在单核机器上写多线程程序,仍然需要线程锁。因为线程锁通常用来实现线程的同步和通信。在单核机器上的多线程程序,仍然存在线程同步的问题。因为在抢占式操作系统中,通常为每个线程分配一个时间片,当某个线程时间片耗尽时,操作系统会将其挂起,然后运行另一个线程。如果这两个线程共享某些数据,不使用线程锁的前提下,可能会导致共享数据修改引起冲突。

9 死锁发生的条件以及如何解决死锁
死锁是指两个或两个以上进程在执行过程中,因争夺资源而造成的下相互等待的现象。死锁发生的四个必要条件如下:
互斥条件:进程对所分配到的资源不允许其他进程访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源;
请求和保持条件:进程获得一定的资源后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但该进程不会释放自己已经占有的资源
不可剥夺条件:进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用后自己释放
环路等待条件:进程发生死锁后,必然存在一个进程-资源之间的环形链
解决死锁的方法即破坏上述四个条件之一,主要方法如下:
资源一次性分配,从而剥夺请求和保持条件
可剥夺资源:即当进程新的资源未得到满足时,释放已占有的资源,从而破坏不可剥夺的条件
资源有序分配法:系统给每类资源赋予一个序号,每个进程按编号递增的请求资源,释放则相反,从而破坏环路等待的条件

10多线程,线程同步的几种方式
进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发;
线程是进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发;线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。
线程间通信的方式:
1、临界区:
通过多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问;
2、互斥量 Synchronized/Lock:
采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问
3、信号量 Semphare:
为控制具有有限数量的用户资源而设计的,它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目。
4、事件(信号),Wait/Notify:
通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作

11.Linux的4种锁机制:
互斥锁:mutex,用于保证在任何时刻,都只能有一个线程访问该对象。当获取锁操作失败时,线程会进入睡眠,等待锁释放时被唤醒
读写锁:rwlock,分为读锁和写锁。处于读操作时,可以允许多个线程同时获得读操作。但是同一时刻只能有一个线程可以获得写锁。其它获取写锁失败的线程都会进入睡眠状态,直到写锁释放时被唤醒。 注意:写锁会阻塞其它读写锁。当有一个线程获得写锁在写时,读锁也不能被其它线程获取;写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)。适用于读取数据的频率远远大于写数据的频率的场合。
自旋锁:spinlock,在任何时刻同样只能有一个线程访问对象。但是当获取锁操作失败时,不会进入睡眠,而是会在原地自旋,直到锁被释放。这样节省了线程从睡眠状态到被唤醒期间的消耗,在加锁时间短暂的环境下会极大的提高效率。但如果加锁时间过长,则会非常浪费CPU资源。
RCU:即read-copy-update,在修改数据时,首先需要读取数据,然后生成一个副本,对副本进行修改。修改完成后,再将老数据update成新的数据。使用RCU时,读者几乎不需要同步开销,既不需要获得锁,也不使用原子指令,不会导致锁竞争,因此就不用考虑死锁问题了。而对于写者的同步开销较大,它需要复制被修改的数据,还必须使用锁机制同步并行其它写者的修改操作。在有大量读操作,少量写操作的情况下效率非常高。
网络编程
1.软链接和硬链接区别
为了解决文件共享问题,Linux引入了软链接和硬链接。除了为Linux解决文件共享使用,还带来了隐藏文件路径、增加权限安全及节省存储等好处。若1个inode号对应多个文件名,则为硬链接,即硬链接就是同一个文件使用了不同的别名,使用ln创建。若文件用户数据块中存放的内容是另一个文件的路径名指向,则该文件是软连接。软连接是一个普通文件,有自己独立的inode,但是其数据块内容比较特殊。
总结:
1、软链接以路径的形式存在,硬链接以文件副本的形式存在;
2、软链接可以跨文件系统,硬链接不可以;
3、软链接可以对目录进行链接,硬链接不可以。

2.Linux下一共四种IO模型:
分别是阻塞,非阻塞,信号驱动和多路复用。
3.TCP/IP模型是几层模型?分别是哪几层?
4层
应用层、传输层、网络层、网络接口物理层
4.OSI模型是几层模型?分别是哪几层?
7层
应用层、表示层、会话层、传输层、网络层、数据链路层、物理层
5.TCP通信与UCP通信的区别
tcp和upd的区别:
TCP: 数据流套接字
特点:字节传输
1.面向连接 效率较低
2.提供可靠传输
3.数据无误、数据无丢失
4.对网络要求高的传输方式,每次传输过去的数据必须是对方接受到的响应
5.在连接的时候需要”三次握手和四次挥手”,需要连接才传输
UDP:数据报套接字
特点:数据包传输
1.面向无连接 效率较高
2.可以会发生数据丢失 丢包
3.对网络要求低的传输方式,在传输的时候不会接受对方的响应
4.只管发送数据,不管对方接受数据
5.星型、蜂窝煤的网络结构
6.Tcp通信服务器端与客户端初始化流程,以及用到了哪些函数:
服务端:
1.创建未连接套接字对象 socket
2.绑定端口号和IP地址 bind
3.给等待连接的套接字设置监听 listen
4.无限等待客户端的连接,获取客户端通信套接字 accept
5.客户端和服务端通信 read write send recv
6.关闭服务端 close
客户端:
1.创建套接字 socket
2.连接服务器的端口号和IP地址 connect
3.客户端和服务端通信 read write send recv
4.关闭客户端 close
7.UDP通信服务器端与客户端初始化流程,以及用到了哪些函数:
服务端:
1.创建udp套接字 socket
2.绑定ip地址和端口号 bind
3.接收客户端的消息 recvfrom
4.关闭udp套接字 close
客户端:
1.创建udp套接字 socket
2.发送消息给服务端 sendto
3.关闭udp套接字 close
8.TCP三次握手过程
第一次握手:主机A通过向主机B 发送一个含有同步序列号的标志位的数据段给主机B,向主机B 请求建立连接,通过这个数据段, 主机A告诉主机B 两件事:我想要和你通信;你可以用哪个序列号作为起始数据段来回应我。
第二次握手:主机B 收到主机A的请求后,用一个带有确认应答(ACK)和同步序列号(SYN)标志位的数据段响应主机A,也告诉主机A两件事:我已经收到你的请求了,你可以传输数据了;你要用那个序列号作为起始数据段来回应我
第三次握手:主机A收到这个数据段后,再发送一个确认应答,确认已收到主机B 的数据段:"我已收到回复,我现在要开始传输实际数据了,这样3次握手就完成了,主机A和主机B 就可以传输数据了。 3次握手的特点 没有应用层的数据 SYN这个标志位只有在TCP建立连接时才会被置1 握手完成后SYN标志位被置0。
9.三次握手的原因:
三次握手可以防止已经失效的连接请求报文突然又传输到服务器端导致的服务器资源浪费。例如,客户端先发送了一个SYN,但是由于网络阻塞,该SYN数据包在某个节点长期滞留。然后客户端又重传SYN数据包并正确建立TCP连接,然后传输完数据后关闭该连接。该连接释放后失效的SYN数据包才到达服务器端。在二次握手的前提下,服务器端会认为这是客户端发起的又一次请求,然后发送SYN ,并且在服务器端创建socket套接字,一直等待客户端发送数据。但是由于客户端并没有发起新的请求,所以会丢弃服务端的SYN 。此时服务器会一直等待客户端发送数据从而造成资源浪费。

10.TCP断开连接要进行4次挥手
第一次: 当主机A完成数据传输后,将控制位FIN置1,提出停止TCP连接的请求 ;
第二次: 主机B收到FIN后对其作出响应,确认这一方向上的TCP连接将关闭,将ACK置1;
第三次: 由B 端再提出反方向的关闭请求,将FIN置1 ;
第四次: 主机A对主机B的请求进行确认,将ACK置1,双方向的关闭结束.
11.HTTP和HTTPS的区别,以及HTTPS有什么缺点?
HTTP协议和HTTPS协议区别如下:
1)HTTP协议是以明文的方式在网络中传输数据,而HTTPS协议传输的数据则是经过TLS加密后的,HTTPS具有更高的安全性
2)HTTPS在TCP三次握手阶段之后,还需要进行SSL 的handshake,协商加密使用的对称加密密钥
3)HTTPS协议需要服务端申请证书,浏览器端安装对应的根证书
4)HTTP协议端口是80,HTTPS协议端口是443
HTTPS优点:
HTTPS传输数据过程中使用密钥进行加密,所以安全性更高
HTTPS协议可以认证用户和服务器,确保数据发送到正确的用户和服务器
HTTPS缺点:
HTTPS握手阶段延时较高:由于在进行HTTP会话之前还需要进行SSL握手,因此HTTPS协议握手阶段延时增加
HTTPS部署成本高:一方面HTTPS协议需要使用证书来验证自身的安全性,所以需要购买CA证书;另一方面由于采用HTTPS协议需要进行加解密的计算,占用CPU资源较多,需要的服务器配置或数目高

12.IP地址作用,以及MAC地址作用
MAC地址是一个硬件地址,用来定义网络设备的位置,主要由数据链路层负责。而IP地址是IP协议提供的一种统一的地址格式,为互联网上的每一个网络和每一台主机分配一个逻辑地址,以此来屏蔽物理地址的差异。

C++

1.说一下C++和C的区别
设计思想上:
C++是面向对象的语言,而C是面向过程的结构化编程语言
语法上:
C++具有封装、继承和多态三种特性
C++相比C,增加多许多类型安全的功能,比如强制类型转换、
C++支持范式编程,比如模板类、函数模板等
2.面向对象和面向过程的区别
面向对象方法中,把数据和数据操作放在一起,组成对象;对同类的对象抽象出其共性组成类;类通过简单的接口与外界发生联系,对象和对象之间通过消息进行通信。面向对象的三大特性是"封装、“多态”、“继承”,五大原则是"单一职责原则"、“开放封闭原则”、“里氏替换原则”、“依赖倒置原则”、“接口分离原则”。
而面向过程方法是以过程为中心的开发方法,它自顶向下顺序进行, 程序结构按照功能划分成若干个基本模块,这些模块形成树状结构。
(过程)优点:
性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗源;比如嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。缺点:没有面向对象易维护、易复用、易扩展。
(对象)优点:
易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统。缺点:性能比面向过程低。

3.虚函数(virtual)可以是内联函数(inline)吗?
虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。
内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
inline virtual 唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 Base::who()),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生;

4.静态库和动态库比较
静态库
将静态库的内容添加到程序中区,此时程序的空间,变成了源程序空间大小+静态库空间大小。
动态库(共享库)
常驻内存,当程序需要调用相关函数时,会从内存调用。
区别
静态库:对空间要求较低,而时间要求较高的核心程序中。
动态库:对时间要求较低,对空间要求较高。虚函数、虚函数表,虚指针
在C++的标准规格说明书中说到,编译器必需要保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证正确取到虚函数的偏移量)。这意味着我们通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

5.虚继承的作用是减少了对基类的重复,代价是增加了虚表指针的负担(更多的虚表指针)。详细请查阅:虚指针、虚函数原理
(当基类有虚函数时):
每个类都有虚指针和虚表;
如果不是虚继承,那么子类将父类的虚指针继承下来,并指向自身的虚表(发生在对象构造时)。有多少个虚函数,虚表里面的项就会有多少。多重继承时,可能存在多个的基类虚表与虚指针;
如果是虚继承,那么子类会有两份虚指针,一份指向自己的虚表,另一份指向虚基表,多重继承时虚基表与虚基表指针有且只有一份。

6.C++空类的大小
本文中所说是C++的空类是指这个类不带任何数据,即类中没有非静态(non-static)数据成员变量,没有虚函数(virtual function),也没有虚基类(virtual base class)。
直观地看,空类对象不使用任何空间,因为没有任何隶属对象的数据需要存储。然而,C++标准规定,凡是一个独立的(非附属)对象都必须具有非零大小。换句话说,
C++空类的大小不为0

7.请说一下C/C++ 中指针和引用的区别?
1.指针有自己的一块空间,而引用只是一个别名;
2.使用sizeof看一个指针的大小是4,而引用则是被引用对象的大小;
3.指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象 的引用;
4.作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引 用的修改都会改变引用所指向的对象;
5.可以有const指针,但是没有const引用;
6.指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能 被改变;
7.指针可以有多级指针(**p),而引用至于一级;
8.指针和引用使用++运算符的意义不一样;
9.如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。

8.请你说一说C++的内存管理是怎样的?
代码段:包括只读存储区和文本区,其中只读存储区存储字符串常量,文本区存储程序的机器代码。
数据段:存储程序中已初始化的全局变量和静态变量
bss 段:存储未初始化的全局变量和静态变量(局部+全局),以及所有被初始化为0的全局变量和静态变量。
堆区:调用new/malloc函数时在堆区动态分配内存,同时需要调用delete/free来手动释放申请的内存。
映射区:存储动态链接库以及调用mmap函数进行的文件映射
栈:使用栈空间存储函数的返回地址、参数、局部变量、返回值

9.栈和堆的区别,以及为什么栈要快
堆和栈的区别:
堆是由低地址向高地址扩展;栈是由高地址向低地址扩展
堆中的内存需要手动申请和手动释放;栈中内存是由OS自动申请和自动释放,存放着参数、局部变量等内存
堆中频繁调用malloc和free,会产生内存碎片,降低程序效率;而栈由于其先进后出的特性,不会产生内存碎片
堆的分配效率较低,而栈的分配效率较高
栈的效率高的原因:
栈是操作系统提供的数据结构,计算机底层对栈提供了一系列支持:分配专门的寄存器存储栈的地址,压栈和入栈有专门的指令执行;而堆是由C/C++函数库提供的,机制复杂,需要一些列分配内存、合并内存和释放内存的算法,因此效率较低。
10.请你回答一下静态变量什么时候初始化
静态变量存储在虚拟地址空间的数据段和bss段,C语言中其在代码执行之前初始化,属于编译期初始化。而C++中由于引入对象,对象生成必须调用构造函数,因此C++规定全局或局部静态对象当且仅当对象首次用到时进行构造

11.请你回答一下如何判断内存泄漏?
内存泄漏通常是由于调用了malloc/new等内存申请的操作,但是缺少了对应的free/delete。为了判断内存是否泄露,我们一方面可以使用linux环境下的内存泄漏检查工具Valgrind,另一方面我们在写代码时可以添加内存申请和释放的统计功能,统计当前申请和释放的内存是否一致,以此来判断内存是否泄露。
12.对虚函数和多态的理解
多态的实现主要分为静态多态和动态多态,静态多态主要是重载,在编译的时候就已经确定;动态多态是用虚函数机制实现的,在运行期间动态绑定。举个例子:一个父类类型的指针指向一个子类对象时候,使用父类的指针去调用子类中重写了的父类中的虚函数的时候,会调用子类重写过后的函数,在父类中声明为加了virtual关键字的函数,在子类中重写时候不需要加virtual也是虚函数。

虚函数的实现:在有虚函数的类中,类的最开始部分是一个虚函数表的指针,这个指针指向一个虚函数表,表中放了虚函数的地址,实际的虚函数在代码段(.text)中。当子类继承了父类的时候也会继承其虚函数表,当子类重写父类中虚函数时候,会将其继承到的虚函数表中的地址替换为重新写的函数地址。使用了虚函数,会增加访问内存开销,降低效率。

13.简述类成员函数的重写、重载和隐藏的区别
(1)重写和重载主要有以下几点不同。
范围的区别:被重写的和重写的函数在两个类中,而重载和被重载的函数在同一个类中。
参数的区别:被重写函数和重写函数的参数列表一定相同,而被重载函数和重载函数的参数列表一 定不同。
virtual 的区别:重写的基类中被重写的函数必须要有virtual 修饰,而重载函数和被重载函数可以被 virtual 修饰,也可以没有。
(2)隐藏和重写、重载有以下几点不同。
与重载的范围不同:和重写一样,隐藏函数和被隐藏函数不在同一个类中。
参数的区别:隐藏函数和被隐藏的函数的参数列表可以相同,也可不同,但是函数名肯定要相同。 当参数不相同时,无论基类中的参数是否被virtual 修饰,基类的函数都是被隐藏,而不是被重写。
注意:虽然重载和覆盖都是实现多态的基础,但是两者实现的技术完全不相同,达到的目的也是完 全不同的,覆盖是动态态绑定的多态,而重载是静态绑定的多态。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值