嵌入式软件工程师面试题——2025校招社招通用(C/C++篇)(五)

说明:

  • 面试群,群号: 228447240
  • 面试题来源于网络书籍,公司题目以及博主原创或修改(题目大部分来源于各种公司);
  • 文中很多题目,或许大家直接编译器写完,1分钟就出结果了。但在这里博主希望每一个题目,大家都要经过认真思考,答案不重要,重要的是通过题目理解所考知识点,好应对题目更多的变化;
  • 博主与大家一起学习,一起刷题,共同进步;
  • 写文不易,麻烦给个三连!!!

1.编写类String的构造函数、析构函数和赋值函数

答案

#include <iostream>
using namespace std;


class String
{
public:
    // 普通构造函数
    String(const char *str = NULL);
    // 拷贝构造函数
    String(const String &other);
    // 析构函数
    ~String();
    // 赋值函数
    String& operator=(const String &other);
private:
    char *m_data;
};



int main()
{
    

    return 0;
}

1.String的析构函数
为了防止内存泄漏,我们还需要定义一个析构函数。当一个String对象超出它的作用域时,这个析构函数将会释放它所占用的内存。代码如下:

String::~String(void)
{
    delete [] m_data;
}

2.String的构造函数
这个构造函数可以帮助我们根据一个字符串常量创建一个MyString对象。这个构造函数首先分配了足量的内存,然后把这个字符串常量复制到这块内存,代码如下:

String::String(const char *str)
{
    if(str == NULL)
    {
        m_data = new char[1]; 
        *m_data = '\0';
    }
    else
    {
        int length = strlen(str);
        m_data = new char[length + 1];
        strcpy(m_data, str);
    }
}

3.String的拷贝构造函数
拷贝构造函数还可以帮助我们在函数调用中以传值方式传递一个Mystring参数,并且在当一个函数以值的形式返回Mystring对象时实现“返回时复制”。

String::String(const String &other)
{
    int length = strlen(other.m_data);
    m_data = new char[length+1];
    strcpy(m_data, other.m_data);
}

4.String的赋值函数

String &String::operator= (const String &other)
{
    if(this == &other)
        return *this;
    
    delete [] m_data;

    int length = strlen(other.m_data);
    m_data = new char[length+1];
    strcpy(m_data, other.m_data);

    return *this;
}

2.哪个子类的虚函数重新声明是正确的?

A.Base* Base::copy(Base*);
Base* Derived::copy(Derived*);
B.Base* Base::copy(Base*);
Derived* Derived::copy(Base*);
C.ostream& Base::print(int,ostream&=cout);
ostream& Derived::print(int,ostream&);
D.void Base::eval() const;
void Derived::eval();
解析
本题问的是哪个派生类的虚函数再声明是对的。
A是重载;B会导致编译错误;C是真正的多态;D是重载。

答案: C

3.什么是多态?

答案:
开门,开窗户,开电视。在这里的“开”就是多态!
多态性可以简单地概括为“一个接口,多种方法”,在程序运行的过程中才决定调用的函数。多态性是面向对象编程领域的核心概念。
多态(Polymorphisn),按字面的意思就是“多种形状”。多态性是允许你将父对象设置成为和它的一个或更多的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单地说就是一句话,允许将子类类型的指针赋值给父类类型的指针。多态性在Object Pascal和C++中都是通过虚函数(Virtual Function)实现的。

4.重载和覆盖有什么不同?

答案:
C++中的重载(Overloading)和覆盖(Overriding)是两个不同的概念,它们分别用于处理函数和方法的多态性。

重载是指在同一作用域内定义多个名称相同但参数类型或个数不同的函数。编译器会根据函数调用时传递的参数类型和个数来匹配合适的重载函数。重载函数具有相同的名称,但不同的签名,可以有不同的返回值类型,但不能只是返回类型不同而函数参数类型和个数相同。C++中支持运算符重载,允许自定义运算符的操作行为。
说法二:overload约定成俗地被翻译为“重载”,是指编写一个与已有函数同名但是参数表不同的函数。例如一个函数既可以接收整型数作为参数,也可以接收浮点数作为参数。重载不是一种面向对象的编程,而只是一种语法规则,重载与多态没有什么直接关系。

覆盖是指在派生类定义一个与基类相同函数名、参数列表和返回类型的函数,并使用override关键字显式地表示这个函数是对基类函数的覆盖。当通过基类指针或引用调用这个函数时,实际执行的是派生类的函数。覆盖的函数必须与基类函数在参数列表、返回类型和const属性上完全匹配,否则会出现编译错误。如果基类函数是虚函数,则覆盖的函数也必须是虚函数。
说法二:override(覆盖)是指派生类重写基类的虚函数,就像我们前面在B类中重写了A类中的foo()函数。重写的函数必须有一致的参数表和返回值(C++标准允许返回值不同的情况,但是很少有编译器支持这个特性)。Override这个单词好像一直没有什么合适的中文词汇来对应。有人译为“覆盖”,还贴切一些。

5.下面哪个不能在编译时间被解析?

A.Macros
B.Inline functions
C.Template in C++
D.virtual function calls in C++

答案:D

6.请描述模板类的友元重载,用C++代码实现

答案

#include <iostream>
using namespace std;

// 定义一个非模板函数,作为模板类的友元,并与模板类中的某个成员函数同名
template<typename T>
class MyClass {
private:
    T x;
public:
    MyClass(T a) : x(a) {}
    // 声明友元函数
    friend void func(MyClass obj);
};

// 定义友元函数,通过友元重载可以访问模板类中的私有成员x
void func(MyClass<int> obj) {
    cout << "x = " << obj.x << endl;
}

int main() {
    MyClass<int> obj(10);
    // 调用友元函数
    func(obj);
    return 0;
}

7.一个类有5个虚方法,下列说法正确的是哪项?

A. 类中的每个对象都有第一个虚方法的地址,每一个方法都有下一个虚方法的地址。
B. 类中的每个对象都有一个链表用来存虚方法地址。
C. 类中的每个对象都保存5个虚方法的地址。
D. 类中的每个对象有一个结构用来保存虚方法地址。

解析
在C++中,虚函数通过虚函数表(vtable)来实现。每个类只有一个虚函数表,其中保存了该类所有虚函数的地址。每个对象都有一个指向虚函数表的指针,这个指针被称为虚函数表指针(vptr)。当通过对象调用虚函数时,实际上是通过该对象的虚函数表指针来查找并调用相应的虚函数。
对于一个有5个虚函数的类,每个对象在内存中都会有一个虚函数表指针,并且这个虚函数表中包含了这个类的5个虚函数的地址。所以每个对象都保存了5个虚方法的地址。

选项A、B和D都不正确。类中的每个对象并不保存每个虚函数的地址,而是保存一个指向虚函数表的指针,并通过这个指针来访问虚函数表的地址。同时,虚函数表中的地址存储的是每个虚函数的地址,而不是下一个虚函数的地址。

答案: B

8.下面程序的结果是什么?

#include <iostream>
#include <memory.h>
#include <assert.h>
using namespace std;

class A{
    char k[3];
public:
    virtual void aa() {};
};

class B: public virtual A{
    char j[3];
public:
    virtual void bb() {};
};

class C: public virtual B {
    char i[3];
public:
    virtual void cc() {};
};

int main()
{
    cout << "sizeof(A): " << sizeof(A) << endl;
    cout << "sizeof(B): " << sizeof(B) << endl;
    cout << "sizeof(C): " << sizeof(C) << endl;

    return 0;
}

解析:

  • 对于class A,由于有一个虚函数,那么必须有一个对应的虚函数表来记录对应的函数入口地址。每个地址需标有一个虚指针,指针的大小为4。类中还有一个char k[3],每一个char值所占位置是1,所以char k[3]所占大小是3。做一次数据对齐后(编译器里一般以4的倍数为对齐单位),char k[3]所占大小变为4。sizeof(A)的结果就是char k[3]所占大小4和虚指针所占大小4,两者之和等于8。
  • 对于class B,由于class B虚继承了class A,同时还拥有自己的虚函数,那么class B中首先拥有一个vfptr_B,指向自己的虚函数表。还有char j[3],大小为4。可虚继承该如何实现?首先要通过加入一个虚类指针(记vbptr_B_A)来指向其父类,然后还要包含父类的所有内容。有些复杂,不过还不难想象。sizeof(B)的结果就是char k[3]所占大小4和虚指针vfptr_B所占大小4加sizeof(A)所占大小8,三者之和等于16。
  • 下面是class C了。class C首先也得有个vfptr_C,然后是char i[3],然后是sizeof(B),所以sizeof(C)的结果就是char i[3]所占大小4和虚指针vfptr_C所占大小4加sizeof(B)所占大小16,三者之和等于24。

在这里插入图片描述

答案: 8 16 24

9.说一下多重继承的优点和缺陷

答案:
多重继承是指一个类可以同时从多个基类派生而来。它具有以下优点和缺陷:
优点:

  1. 提供了更大的灵活性:多重继承使得一个类可以从多个基类中继承不同的特性和行为,从而提供更大的灵活性。这对于构建复杂的对象结构和实现多样化的功能非常有用。
  2. 实现代码重用:通过多重继承,可以将不同的基类中的公共代码和功能集成到一个派生类中,实现代码的重用。这样可以减少重复编写相似代码的工作量,提高代码的可维护性和复用性。
  3. 更好地模拟现实世界:在一些情况下,某个类可能具有多个角色或特性,而多重继承可以更好地模拟现实世界中的这种复杂关系。例如,一个类可以同时继承自动物和会飞行的特性,这样更贴近真实世界的设计。

缺陷:

  1. 命名冲突和二义性:由于多个基类可能具有相同的成员函数或数据成员,多重继承可能导致命名冲突和二义性。这就需要在派生类中进行解决,可能需要使用作用域解析运算符(::)来明确指定使用的成员。
  2. 复杂性增加:多重继承会增加程序的复杂性,因为需要考虑多个基类之间的关系和调用顺序。特别是在多层次或菱形继承中,派生类可能会继承相同的基类多次,产生冗余的数据副本和额外的开销。
  3. 设计和维护困难:多重继承可能导致设计和维护上的困难,因为需要考虑更多的关系和交互。同时,当基类发生改变时,派生类可能也需要进行相应的修改,增加了代码的脆弱性。

10.下面的程序有何错误?

#include <iostream>
using namespace std;

class Shape
{
public:
    Shape() {}
    ~Shape() {}
    virtual void Draw() = 0;
};

int main()
{
    Shape s1;

    return 0;
}

答案:
因为Shape类中的Draw函数是一个纯虚函数,所以Shape类是不能实例化一个对象的。Shape s1;是不可以的,解决方法是把Draw函数修改成一般的虚函数。

#include <iostream>
using namespace std;

class Shape
{
public:
    Shape() {}
    ~Shape() {}
    virtual void Draw();
};

int main()
{
    Shape s1;

    return 0;
}

11.什么是虚指针?

答案:
虚指针(vptr)是C++中用于实现多态性的一种机制。它是一个指向虚函数表的指针,每个对象都有自己的虚指针。
虚函数表是一个存储每个类的虚函数地址的表格,编译器在编译时会为每个类生成一个虚函数表。当类中有虚函数时,编译器会在类的布局中增加一个指向虚函数表的指针vptr,并将其作为类的首个成员变量。
当对象被创建时,虚指针vptr会被初始化指向该对象所属类的虚函数表。当通过指向基类的指针或引用调用虚函数时,程序会先根据对象的虚指针找到该对象所属的类的虚函数表,再根据虚函数表中相应的索引找到实际要调用的函数地址,最后进行函数调用。
使用虚指针和虚函数表可以实现动态绑定,即在运行时根据对象类型确定调用哪个函数,从而实现多态性。这也是C++中面向对象编程的一个重要特性。

12.求下列程序的输出结果

int main()
{
    printf("%f\n", 5);
    printf("%d\n", 5.01);

    return 0;
}

解析:

  • 第一个printf语句中,使用了%f格式说明符,但是传入的参数是整数5。因为%f用于输出浮点数,而不是整数,所以这会导致输出的结果可能是不确定的。在某些编译器中,可能会将整数解释为浮点数,输0.000000。但是这并不是一个可靠的行为。
  • 第二个printf语句中,使用了%d格式说明符,但是传入的参数是浮点数5.01。因为%d用于输出整数,而不是浮点数,所以这也会导致输出的结果不确定。

答案:第一个答案是0.000000。 第二个答案是一个大数。

13.关键字volatile有什么含意?并给出3个不同的例子

答案:
一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:

  • 并行设备的硬件寄存器(如状态寄存器)。
  • 个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
  • 多线程应用中被几个任务共享的变量

14.关键字static的作用是什么?

答案:

  • 函数体内static变量的作用范围为该函数体,不同于auto变量,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值。
  • 在模块内的static全局变量可以被模块内所有函数访问,但不能被模块外其他函数访问。
  • 在模块内的static函数只可被这一模块内的其他函数调用,这个函数的使用范围被限制在声明它的模块内。
  • 在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝。
  • 在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值