C/C++面试题

1.C++源文件从文本到可执行程序的过程
预处理,头⽂件包含、宏替换、条件编译、删除注释,产生.ii文件
编译,主要进⾏词法、语法、语义分析等,产生汇编.s文件
汇编,将汇编⽂件转换成⼆进制⽬标⽂件,产生目标.o/.obj文件
链接,将项⽬中的各个⼆进制⽂件+所需的库+启动代码链接成可执⾏⽂件,产生可执行.out/.exe文件
2.#include的顺序
若要在a.h中声明b.h中定义的变量,则在a.c文件中必须先引用b.h文件,再引用a.h文件
3. #include<>和“”的区别
<>表示标准头文件,编译器在预定义位置查找该文件
“”表示非系统头文件,查找从源文件所在路径开始,查找范围大于<>
4. 进程与线程区别
资源:线程比进程节俭。启动新的进程需要分配独立的地址空间,建立数据表来维护代码段、堆栈段和数据段。
切换效率:同一进程中的不同线程共享地址空间,线程彼此切换的时间远小于进程切换
通信:线程之间共享数据空间,进行通信时,一个线程的数据可以直接由其他线程共享。而进程之间进行数据传递必须使用进程间的通信方式进行
5.使用线程的优点
具有多任务、并发性的特点
多CPU系统更加高效
改善程序结构,长而复杂的进程可以分为多个线程,成为几个独立或半独立的部分,是的代码更易理解和修改。
6.malloc()实现方案
实质:将空闲的内存块链接起来的空闲链表
调用malloc()函数后,顺序遍历空闲链表找到满足大小需要的空闲块,将多余的部分继续链入空闲链表
调用free()函数,将用户释放的内存块连接到空闲链表上
malloc()请求延时,即将相邻的小空闲块合并为大的空闲块
从OS角度,进程分配内存的两种方式分别由两个系统调用完成:brk和mmap。这两种方式均分配的是虚拟内存,并未分配物理地址。只有在第一次访问发生缺页中断时,才由OS分配物理内存并建立映射关系。
brk将数据段的最高地址指针往高地址推
mmap是在进程的虚拟地址空间中(堆栈之间成为文件映射区域的位置)找空闲的虚拟内存
7.有了malloc()和free()为什么还要有new()和delete()
malloc()是C++/C语言的标准库函数,new/delete是C++的运算符,都可用于申请动态内存和释放内存。
非内部数据类型的对象而言,光使用malloc/free无法满足动态对象的要求。对象在创建的同时要自动执行构造函数,消亡前要自动执行析构函数。Malloc/free是库函数不是运算符,不在编译器控制权限内,不能够把执行析构函数和析构函数的任务交给malloc/free,故创造新的运算符
C++需要能够完成动态分配内存和初始化工作的运算符new,一个能完成清理和释放内存工作的运算符delete
8. C++内存管理方式
栈(Stack):存放局部变量,函数参数,由编译器自动分配和释放。进出栈由计算机指令支持,分配有专门的寄存器存储栈的地址,内存空间连续有限。
堆(Heap):程序员手动分配释放(new,delete),动态分配,内存空间无限制,因此产生内存碎片。OS中有记录空闲块的链表,收到内存请求即遍历链表找到第一个符合的空间进行分配,并将节点从链表中移除。在内存空间首地址中记录本次分配的大小,用于delete释放内存空间
全局/静态存储区:全局变量和静态变量分配到该区,程序结束时自动释放,包括DATA(全局初始化区)段,BSS段(全局未初始化区)。初始化和未初始化的变量放到不同的区。BSS段在程序执行前自动清零,而DATA段在程序执行前已经为0
文字常量区,存放常量字符串,程序结束后系统自动释放
程序代码区,存放二进制代码
9. hash表实现
散列函数hash function,最常见的为f(x)=x%tablesize
碰撞问题,即不同元素的散列值相同。解决方法包括线性探测(依次后移),二次探测(用新的散列值再次散列),拉链法(直接链接在同一个地址上)
10.TCP模型以及协议
四层模型:
应用层:Telnet、FTP和e-mail
传输层:TCP和UDP
网络层:IP、ICMP和IGMP
链路层:设备驱动程序以及接口卡
11.进程间的同步方法
互斥量(mutex)
条件变量(Condition Variable)
信号量(semaphore)
一般手写就是PV操作,对应的是wait()和signal()
12.const的用途
定义只读变量即常量
修饰函数的参数和函数的返回值
修饰函数的定义体,被const修饰表示不修改成员变量的值
13. 指针和引用的区别
引用是变量的别名,内部实现是只读指针
引用只能在初始化时被赋值,其他时候值不能被改变,指针的值在任何时候都可以被改变
引用不能为NULL,指针可以
引用变量内存单元保存的是被引用变量的地址
“sizeof引用”=指向变量的大小,”sizeof指针”=指针本身的大小
引用变量在源代码中当作普通变量使用,而作函数参数时,内部传递的实际上是变量地址
14.Static作用
函数体内static变量作用范围为函数体,不同于auto变量,该变量的内存只被分配一次,因此下次调用时仍然维持上次的值
在模块内的static全局变量可以被模块内所有函数访问,但不能被模块外的其他函数访问
在模块内的static函数只可被这一模块内的其他函数调用,适用范围限定在声明的模块内
在类的static成员变量为整个类所拥有,对类的所有对象只有一份拷贝
在类的static成员函数为整个类所拥有,这个函数不接受this指针,因此只能访问类的static成员变量
全局变量存储在静态存储区中,局部变量在堆栈中
静态局部变量:和普通局部变量不同。静态局部变量也是定义在函数内部的,静态局部变量所在的函数在调用多次时,只有第一次才经历变量定义和初始化,以后多次在调用时不再定义和初始化,而是维持之前上一次调用时执行后这个变量的值。下次接着来使用。但是他的作用域仅限于当前函数内。
静态全局变量也只初始化一次,但是作用域在当前文件/模块中。每次调用当前文件时,会使用上一次保存的值。
静态函数:只在本模块或者文件中使用。被限定了范围。

15.头文件中的endif/ifndef/define作用
防止该头文件被重复引用。头文件重复包含会增大程序大小,重复编译增加编译时间。
16.宏定义与内联区别
内联函数在编译时展开,宏在预处理时展开;
内联函数直接嵌入到目标代码中,宏是简单的做文本替换;
内联函数有类型检测、语法判断等功能,而宏没有;
inline函数是函数,宏不是;
17.宏定义与typedef的区别
#define 用于为各种数据类型定义别名,与 typedef 类似,但是它们有以下几点不同:
typedef 仅限于为类型定义符号名称, 定义一种类型的别名,而不只是简单的宏替换 。#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
typedef 编译阶段会检查错误,#define 预处理阶段不检查错误。
18.宏定义与const的区别
1.数据类型:const修饰的变量有明确的类型,而宏没有明确的数据类型
2.安全方面:const修饰的变量会被编译器检查,而宏没有安全检查
3.内存分配:const修饰的变量只会在第一次赋值时分配内存,而宏是直接替换,每次替换后的变量都会分配内存
4.作用场所:const修饰的变量作用在编译、运行的过程中,而宏作用在预编译中。从编译器的角度讲,最大的优势是简单,方便。因为预处理就可以解决掉#define,不必让编译器来处理这个。
5.代码调试:const方便调试,而宏在预编译中进行所以没有办法进行调试。
在程序语句中使用的常量的地方, 最好是使用const定义,在这方面来说, const只有优势,没有劣势.如果要说const劣势的地方,那就是它不能做上面3中的define的需要在预处理的时候做的事情.其实,这并非它的劣势,而只是不是它所要担负的工作罢了.

19. TCP和UDP区别
TCP传输控制协议,提供面向连接、可靠的字节流服务。客户和服务器彼此交换数据前,必须双方建立一个TCP连接,之后才能传输数据。且提供超时重发,丢弃重复数据,检验数据,流量控制等功能,保证数据端到端传输。
UDP用户数据报协议,简单的面向数据报的传输层协议。不可靠,只负责把应用程序传给IP层的数据包发送数据,不保证到达目的地。由于不需要建立连接,且无超时重发机制,因此传输速度很快。

20.解释堆和栈的区别
申请方式
栈:由系统自动分配。例如,声明在函数中一个局部变量int b;系统自动在栈中为b开辟空间
堆:需要程序员自己申请,并指明大小,在c中malloc函数

申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

申请效率的比较:
栈:由系统自动分配,速度较快。但程序员是无法控制的。
堆:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.

堆和栈中的存储内容
栈:局部变量和形参
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容由程序员安排。

21.结构体与共⽤体的区别
结构体中的成员拥有独⽴的空间,共⽤体的成员共享同⼀块空间,但是每个共⽤体成员能访问共⽤区的空间⼤⼩是由成员⾃身的类型决定。
共用体使用覆盖技术,成员变量相互覆盖。

22.violate关键字
volatile是一个类型修饰符(type specifier), 防止编译器对代码进行优化。
在编译期间,编译器可能对代码进行优化
当编译器看到此处的n被const修饰,从语义上来讲,n是不期望被修改的
所以优化的时候把n的值存放到寄存器中,以提高访问的效率
只要以后使用n的地方都去寄存器中取,即使n在内存中的值发生变化,寄存器也不受影响,所以输出的n的值为10。也就是多线程其他线程可能读到的值是修改之前的。
使用volatile修饰之后,就不会将此值拷贝到寄存器中,那么其他线程可以读到修改后的值,也就是可见性。
volatile的作用就是被设计用来修饰被不同线程访问和修改的变量

23.变量/函数的声明和定义之间有什么区别
变量/函数的声明仅声明变量/函数存在于程序中的某个位置也就是后面程序会知道这个函数或者变量的类型,但不分配内存。
关于定义,当我们定义变量/函数时,除了声明的作用外,它还为该变量/函数分配内存。

24.各种指针
NULL指针
NULL用于指示指针未指向有效位置。理想情况下,如果在声明时不知道指针的值,则应将指针初始化为NULL。
当由它指向的内存在程序中间被释放时,我们应该使指针为NULL。

悬空指针
悬空指针是没有指向正确内存位置的指针。当删除或释放对象时,如果不修改指针的值或者不置为NULL,就会出现悬空指针。
这时这个指针指向的内存可能被分配给了其他变量就会造成错误。所以是比较危险的。

野指针
就是只声明没有被初始化过的指针,他可能指向任何内存。

25.指针常量与常量指针
int * const p //指针常量:指针在前,常量在后
这个p只能指向一个位置,而不能指向其他位置。指向的变量值可以改变。
const int *p = &a; //常量指针 : 常量在前,指针在后
指向的变量值不能改变,但是可以改变这个指针指向的位置。

26.“引用”与指针的区别是什么?
引用是C++的概念在C中叫取地址符号
本质:引用是别名,指针是地址
指针是独立的可以指向空值,这时我们为指针分配了内存。而引用必须初始化指定的对象自始至终只能依附于同一个变量,他只是别名。标准没有规定引用要不要占用内存,也没有规定引用具体要怎么实现,具体随编译器

你可能感兴趣的:(c语言,c++,java)