软件开发面试题目

目录

软件开发面试题目

1.类的继承

2.类的大小

3.多态性

4.什么是memory leak,也就是内存泄漏

5.new和malloc的区别

6.并发(concurrency)和并行(parallelism)

7.多线程的同步,锁的机制

8.请你来说一下堆和栈的区别

9.new与malloc的10点区别

10.排序算法时间复杂度

11.内存碎片

12.类和结构体的区别

13.野指针

14.虚函数和纯虚函数

15.值传递,指针传递,引用传递


软件开发面试题目

1.类的继承

Private: 只能被本类成员函数或友元函数访问。

Public: 可以被本类和其他类以及程序中的其他函数访问。

Protected: 可以由本类成员函数访问,也可以由派生类的成员函数访问,但不允许程序中其它函数访问。

2.类的大小

  1. 仅包含一般成员函数(即没有虚函数),不含成员变量时,运行结果和(一)是一样的,系统也只是为对象创建了1个字节的占位符。因此,我们可以得出结论是,一般成员函数不会对类的大小造成影响。
  2. 依次继承的三个类中含有相同数量,相同类型的一般成员变量(不含静态成员变量)。此种情况下,类对象大小=基类对象大小+自身成员大小。A当中三个字符变量3个字节,一个整形变量4个字节,考虑内存对齐因素(默认为4),A类对象大小为8。B类对象大小为A类对象大小基础上再加8,C类对象大小在B类对象大小基础上再加8。
  3. 可以看到,类对象大小没有因为增加了静态成员而变化。因为静态成员是属于类成员共有的,不单独属于任何一个对象,对静态成员的存储不会选择在某个对象空间,而是存在于堆当中,因此不会对对象的大小造成影响。

3.多态性

  1. 静态多态性(编译时多态):指调用相同名称的函数时,编译器在编译阶段就能够根据函数参数类型的不同和个数的不同来确定要调用的函数,(函数重载和运算符重载)。
  2. 动态多态性(运行时多态):指在函数名,函数参数和返回类型都相同的情况下,在编译阶段不能确定要调用的函数,只能在程序运行时才能确定要调用的函数。动态多态性通过继承和虚函数实现。(只有通过基类的指针调用虚函数时,对该虚函数的调用才能实现动态多态性。通过基类的对象调用虚函数时,系统采用的是静态联编。

4.什么是memory leak,也就是内存泄漏

参考回答:

内存泄漏(memory leak)是指由于疏忽或错误造成了程序未能释放掉不再使用的内存的情况。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的分类:

1. 堆内存泄漏 (Heap leak)。对内存指的是程序运行中根据需要分配通过malloc,realloc new等从堆中分配的一块内存,再是完成后必须通过调用对应的 free或者delete 删掉。如果程序的设计的错误导致这部分内存没有被释放,那么此后这块内存将不会被使用,就会产生Heap Leak.

2. 系统资源泄露(Resource Leak)。主要指程序使用系统分配的资源比如 Bitmap,handle ,SOCKET等没有使用相应的函数释放掉,导致系统资源的浪费,严重可导致系统效能降低,系统运行不稳定。

3. 没有将基类的析构函数定义为虚函数。当基类指针指向子类对象时,如果基类的析构函数不是virtual,那么子类的析构函数将不会被调用,子类的资源没有正确是释放,因此造成内存泄露

5.new和malloc的区别

参考回答:

1、new分配内存按照数据类型进行分配,malloc分配内存按照指定的大小分配;

2、new返回的是指定对象的指针,而malloc返回的是void*,因此malloc的返回值一般都需要进行类型转化。

3、new不仅分配一段内存,而且会调用构造函数,malloc不会。

4、new分配的内存要用delete销毁,malloc要用free来销毁;delete销毁的时候会调用对象的析构函数,而free则不会。

5、new是一个操作符可以重载,malloc是一个库函数。

6、malloc分配的内存不够的时候,可以用realloc扩容。扩容的原理?new没用这样操作。

7、new如果分配失败了会抛出bad_malloc的异常,而malloc失败了会返回NULL。

8、申请数组时: new[]一次分配所有内存,多次调用构造函数,搭配使用delete[],delete[]多次调用析构函数,销毁数组中的每个对象。而malloc则只能sizeof(int) * n。

6.并发(concurrency)和并行(parallelism)

参考回答:

并发(concurrency):指宏观上看起来两个程序在同时运行,比如说在单核cpu上的多任务。但是从微观上看两个程序的指令是交织着运行的,你的指令之间穿插着我的指令,我的指令之间穿插着你的,在单个周期内只运行了一个指令。这种并发并不能提高计算机的性能,只能提高效率。

并行(parallelism):指严格物理意义上的同时运行,比如多核cpu,两个程序分别运行在两个核上,两者之间互不影响,单个周期内每个程序都运行了自己的指令,也就是运行了两条指令。这样说来并行的确提高了计算机的效率。所以现在的cpu都是往多核方面发展。

7.多线程的同步,锁的机制

同步的时候用一个互斥量,在访问共享资源前对互斥量进行加锁,在访问完成后释放互斥量上的锁。对互斥量进行加锁以后,任何其他试图再次对互斥量加锁的线程将会被阻塞直到当前线程释放该互斥锁。如果释放互斥锁时有多个线程阻塞,所有在该互斥锁上的阻塞线程都会变成可运行状态,第一个变为运行状态的线程可以对互斥量加锁,其他线程将会看到互斥锁依然被锁住,只能回去再次等待它重新变为可用。在这种方式下,每次只有一个线程可以向前执行。

8.请你来说一下堆和栈的区别

参考回答:

1)申请方式:

栈由系统自动分配和管理,堆由程序员手动分配和管理。

2)效率:

栈由系统分配,速度快,不会有内存碎片。

堆由程序员分配,速度较慢,可能由于操作不当产生内存碎片。

3)扩展方向

栈从高地址向低地址进行扩展,堆由低地址向高地址进行扩展。

4)程序局部变量是使用的栈空间,new(delete)/malloc(free)动态申请的内存是堆空间,函数调用时会进行形参和返回值的压栈出栈,也是用的栈空间。

9.new与malloc的区别

1. 申请的内存所在位置

new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。

2.返回类型安全性

new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。

类型安全很大程度上可以等价于内存安全,类型安全的代码不会试图方法自己没被授权的内存区域。关于C++的类型安全性可说的又有很多了。

3.内存分配失败时的返回值

new内存分配失败时,会抛出bac_alloc异常,它不会返回NULL;malloc分配内存失败时返回NULL。

int *a  = (int *)malloc ( sizeof (int ));

int * a = new int();

是否需要指定内存大小

使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算,而malloc则需要显式地指出所需内存的尺寸。

class A{...}

A * ptr = new A;

A * ptr = (A *)malloc(sizeof(A)); //需要显式指定所需内存大小sizeof(A);

是否调用构造函数/析构函数

使用new操作符来分配对象内存时会经历三个步骤:

第一步:调用operator new 函数(对于数组是operator new[])分配一块足够大的,原始的,未命名的内存空间以便存储特定类型的对象。

第二步:编译器运行相应的构造函数以构造对象,并为其传入初值。

第三部:对象构造完成后,返回一个指向该对象的指针。

使用delete操作符来释放对象内存时会经历两个步骤:

第一步:调用对象的析构函数。

第二步:编译器调用operator delete(或operator delete[])函数释放内存空间。

总之来说,new/delete会调用对象的构造函数/析构函数以完成对象的构造/析构。而malloc则不会。可以看下例子:

class A

{

public:

    A() :a(1), b(1.11){}

private:

    int a;

    double b;

};

int main()

{

    A * ptr = (A*)malloc(sizeof(A));

    return 0;}

6.对数组的处理

C++提供了new[]与delete[]来专门处理数组类型:

A * ptr = new A[10];//分配10个A对象

使用new[]分配的内存必须使用delete[]进行释放:

delete [] ptr;

new对数组的支持体现在它会分别调用构造函数函数初始化每一个数组元素,释放对象时为每个对象调用析构函数。注意delete[]要与new[]配套使用,不然会找出数组对象部分释放的现象,造成内存泄漏。

至于malloc,它并知道你在这块内存上要放的数组还是啥别的东西,反正它就给你一块原始的内存,在给你个内存的地址就完事。所以如果要动态分配一个数组的内存,还需要我们手动自定数组的大小:

int * ptr = (int *) malloc( sizeof(int) );//分配一个10个int元素的数组

10.排序算法时间复杂度

11.内存碎片

内存碎片通常分为内部碎片和外部碎片:

  1. 内部碎片是由于采用固定大小的内存分区,当一个进程不能完全使用分给它的固定内存区域时就产生了内部碎片,通常内部碎片难以完全避免;

  2. 外部碎片是由于某些未分配的连续内存区域太小,以至于不能满足任意进程的内存分配请求,从而不能被进程利用的内存区域。

  现在普遍采用的段页式内存分配方式就是将进程的内存区域分为不同的段,然后将每一段由多个固定大小的页组成。通过页表机制,使段内的页可以不必连续处于同一内存区域,从而减少了外部碎片,然而同一页内仍然可能存在少量的内部碎片,只是一页的内存空间本就较小,从而使可能存在的内部碎片也较少。

12.类和结构体的区别

C#

  1. 结构体(sturct)是一种值类型,而类(class)是引用类型。区别在于复制方式,值类型的数据是值赋值,引用类型的数据是引用复制。
  2. 结构体使用栈存储(Stack Allocation),而类使用堆存储(Heap Allocation)。栈的空间相对较小.但是存储在栈中的数据访问效率相对较高。堆的空间相对较大.但是存储在堆中的数据的访问效率相对较低。结构体使用完之后就自动解除内存分配,类实例有垃圾回收机制来保证内存的回收处理。

如何选择结构体还是类

1. 堆栈的空间有限,对于大量的逻辑的对象,创建类要比创建结构好一些

2. 结构体表示如点、矩形和颜色这样的轻量对象,例如,如果声明一个含有 1000 个点对象的数组,则将为引用每个对象分配附加的内存。在此情况下,结构体的成本较低。

3. 在表现抽象和多级别的对象层次时,类是最好的选择,因为结构体不支持继承

4. 大多数情况下该类型只是一些数据时,结构体时最佳的选择

C++

  1. 最本质的一个区别就是:结构体的成员和成员函数在默认情况下都是公有的(public),类的成员和成员函数在默认情况下都是私有的(private)。

访问权限:

比如以下代码

struct A{int m_nNum;};struct B : A{ QString m_strFile;};

上面例子中,B是public继承A的。如果写成下面:

class A{int m_nNum;};class B : A{ QString m_strFile;};

这样的话B就变成private继承A了。这个就是默认访问权限的意思。

其实说到底继承取决于子类而非基类!

混合继承

比如下面简单那的例子

struct A{int m_nNum;};class B : A{ QString m_strFile;};struct C:B{};

上面:B是private继承A,C是public继承B!!!

13.野指针

1. 指针变量的值未被初始化。

2.指针所指向的地址空间已经被free或delete,但是没有赋值为null,此时指针指向随机一片内存。

3.指针操作超越了作用域。

14.虚函数和纯虚函数

定义一个函数为虚函数,不代表函数为不被实现的函数。

定义他为虚函数是为了允许用基类的指针来调用子类的这个函数。

定义一个函数为纯虚函数,才代表函数没有被实现。

定义纯虚函数是为了实现一个接口,起到一个规范的作用,规范继承这个类的程序员必须实现这个函数。

15.值传递,指针传递,引用传递

   首先从概念上来说一下这几种函数传参方式及区别:

   1、值传递:形参是实参的拷贝,改变函数形参的值并不会影响外部实参的值,这是最常用的一种传参方法,也是最简单的一种传参方法,只需要传递参数,返回值那是return考虑的;

   2、指针传递:指针传递参数从本质上来说也是值传递,它传递的是一个地址。【值传递过程中,被调函数的形参作为被调函数的局部变量来处理,即在函数内的栈中开辟内存空间以存放由主调函数放进来的实参的值,从而成了实参的一个副本(记住这个,函数内参数的是实参的副本)。由于指针传递的是外部实参的地址,当被调函数的形参值发生改变时,自然外部实参值也发生改变。

   3、引用传递:被调函数的形参虽然也作为局部变量在栈中开辟了内存空间,但是栈中存放的是由主调函数放进的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中实参变量。因此,形参的任何改动都会直接影响到实参。

  • 12
    点赞
  • 125
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
软件开发工程师面试题通常会涉及以下几个方面: 1. 软件开发过程:面试官可能会问到软件开发过程的阶段以及每个阶段的作用。一般软件开发过程包括可行性分析、需求分析、架构设计、代码编写、测试、部署和维护等阶段。可行性分析用于评估项的可行性和风险控制,需求分析用于明确项的功能需求,架构设计用于设计系统的整体结构,代码编写用于实现具体功能,测试用于验证系统的正确性,部署用于将软件交付给用户,维护用于保证软件的可用性和稳定性。 2. 错误和异常处理:面试官可能会问到错误和异常的区别。错误(error)通常表示一种严重的问题,恢复起来可能很困难,比如内存溢出。而异常(exception)表示设计或实现问题,通常指程序运行正常时不会发生的情况。错误往往无法被程序处理,而异常可以通过异常处理机制被捕获并进行处理。 3. 托管代码和非托管代码:面试官可能会问到托管代码和非托管代码的区别。托管代码是运行在.NET公共语言运行时(CLR)中的代码,受CLR管理内存、资源和安全性。相对而言,非托管代码是直接访问计算机硬件和操作系统的代码,不经过CLR运行,需要程序员自行分配和释放内存空间。 综上所述,软件开发工程师面试题通常会涉及软件开发过程的阶段和作用、错误和异常的区别以及托管代码和非托管代码的概念。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [C#笔试题面试题锦集](https://blog.csdn.net/Fighting515/article/details/115870562)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值