嵌入式软件工程师面试题——2025校招社招通用(七)

说明:

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

1.如何使用指针操作数组?

答案:
使用指针操作数组,可以通过指针来访问数组中的元素,也可以通过指针进行数组的遍历和操作。

  1. 使用指针访问数组元素:
int arr[5] = {1, 2, 3, 4, 5};
int* p = arr;  // 将指针指向数组首地址

for (int i = 0; i < 5; i++) {
    cout << *(p+i) << " ";  // 通过指针访问数组元素
}

  1. 使用指针遍历数组:
int arr[5] = {1, 2, 3, 4, 5};
int* p = arr;  // 将指针指向数组首地址

for (int i = 0; i < 5; i++) {
    cout << *(p++) << " ";  // 通过指针遍历数组并访问元素
}

  1. 使用指针修改数组元素:
int arr[5] = {1, 2, 3, 4, 5};
int* p = arr;  // 将指针指向数组首地址

*(p+2) = 10;  // 修改数组第3个元素的值为10

for (int i = 0; i < 5; i++) {
    cout << *(p+i) << " ";  // 遍历数组并输出元素值
}

  1. 使用指针作为函数的参数来操作数组:
void printArray(int* arr, int size) {
    for (int i = 0; i < size; i++) {
        cout << *(arr+i) << " ";  // 通过指针访问数组元素
    }
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    printArray(arr, 5);  // 传递数组首地址和大小给函数
    return 0;
}

2.数组指针与指针数组的区别

答案:
数组指针(Pointer to an array):数组指针是指向数组的指针。它指向数组的首地址,并且可以通过指针进行数组元素的访问。数组指针的声明形式为type (*ptr)[size],其中ptr是指向数组的指针,type是数组元素类型,size是数组的大小。例如,int (*ptr)[5]表示一个指向包含5个整数的数组的指针。通过数组指针可以遍历整个数组,访问数组中的元素。

指针数组(Array of pointers):指针数组是一个数组,其元素都是指针类型。每个元素指向内存中的某个位置。指针数组的声明形式为type *arr[size],其中arr是指针数组的名称,type是指针指向的数据类型,size是数组的大小。例如,int *arr[5]表示一个包含5个整型指针的数组。每个元素都是一个指向整数的指针。指针数组可以用来存储多个指针,并通过数组索引来访问各个指针。

因此,数组指针和指针数组的区别可以总结如下:

  • 数组指针是指向数组的指针,可以通过指针访问数组中的元素。
  • 指针数组是一个数组,其元素都是指针类型,可以用来存储多个指针,并通过数组索引来访问各个指针。

3.什么是函数指针?如何使用函数指针

分析:
函数指针就是指向函数的指针。像其他指针 一样,函数指针也指向某个特定的类型。函数类型由其返回类型及形参表确定,而与函数名无关。函数指针的示例代码如下:

int(*f)(int    x);
double(*ptr)(double  x);

由于“()”运算符的优先级高于“*”,所以指针变量名外的括号必不可少,后面 的“形参列表”表示指针变量指向的函数所带的参数列表。函数指针和它指向的函数的参数个数和类型必须保持 — 致,函数指针的类型和函数的返回值类型也必须保持一致。
函数指针的使用主要包括函数指针的赋值和通过函数指针调用函数,函数名和数组名 一样代表了函数代码的首地址,因此在赋值时,是直接将函数指针指向函数名。函数指针的赋值示例代码如下:

int  func(int	x);	    //声明一个函数
int  (*f)   (int	x); //声明一个函数指针
f =  func;		        //将 func() 函数的首地址赋给指针f

赋值时函数 func 不带括号,也不带参数,由于 func 代表函数的首地址,因此赋值以后,指针f 就指向函数 func(x)的代码的首地址。通过函数指针调用函数的示例代码如下:

int f(int x, int y)
{
    int z;
    z = (x > y) ? x : y;

    return z;
}

int main()
{
    int i, a, b;
    int (*p) (int x, int y); // 定义函数指针
    scanf("%d", &a);
    p = f;      // 给函数指针p赋值,使它指向函数f
    for(i=1; i < 9; i++)
    {
        scanf("%d", &b);
        a = (*p)(a, b); // 通过指针p调用函数f
    }
    printf("The Max Number is: %d\n", a);

    
    return 0;
}

答案: 函数指针就是指向函数的存储空间地址的指针。可以对函数指针进行赋值并且通过函数指针来调用函数。

4.什么是this指针?

分析:
this指针是一个隐含的指针,它是指向对象本身的,表示当前对象的地址。

在一个非静态的成员里面, this 关键字就是一个指针,指向该函数的这次调用所针对 的那个对象。在类 a 的 非const成员函数里, this的类型是a*, 但 是this不是一个常规变量, 所以不可以获取 this的地址或者给它赋值。在类a 的 const 成员函数里, this的类型是 const a*, 不可以对这个对象本身进行修改。
this 指针的一个示例代码如下:

void  Date::setMonth(int mn)
{
	month = mn;
	this->month = mn;
	(*this).month = mn;    // 三种方式是等价的
}

答案: 在调用成员函数时,编译器会隐含地插入一个参数,这个参数就是 this 指针。 this 指
针指向当前对象本身,表示当前对象的地址。

5.引用与值传递的区别

分析:

  • 在 C++ 中,值传递是指将要传递的值作为一个副本传递。值传递过程中,被调函数的 形参作为被调函数的局部变量处理,在内存的堆栈中开辟空间以存放由主调函数放进来的 实参的值,从而成为了实参的一个副本。值传递的特点是被调函数对形参的任何操作都是作为局部变量进行,不会更改主调函数的实参变量的值。
  • 引用传递传递的是引用对象的内存地址。在地址传递过程中,被调函数的形参也作为 局部变量在堆栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地 址。被调函数对形参的任何操作都被处理成间接寻址,即通过堆栈中存放的地址访问主调函数中的实参变量。所以,被调函数对形参做的任何操作都会影响主调函数中的实参变量。

答案:
值传递传递的是一个值的副本。函数对形参的操作不会影响实参的值,而引用传递传递的引用对象的内存地址,函数对形参的操作会影响实参的值,实参的值将会随着形参值的更改而同样进行更改。

6.面向对象与面向过程的区别

分析:
面向对象至今还没有统一的概念。在这里把它定义为:按人们认识客观世界的系统思 维方式,采用基于对象(实体)的概念建立模型,模拟客观世界分析、设计、实现软件办法。通过面向对象的理念使计算机软件系统能与现实世界中的系统一一对应。
对象是指现实世界中各种各样的实体。它可以指具体的事物也可以指抽象的事物。例 如,整数1、20、300、同学、苹果、飞机、规则、法律、法规、表单等。每个对象都有自 己的内部状态和运动规律,如阿梁同学具有名字、外貌、身高等内部状态,具有吃饭、睡 觉、逛街、打球、散步等运动规律。在面向对象概念中编程者把对象的内部状态称为属性、 运动规律成为方法或事件。
面向对象设计是把分析阶段得到的需求转变成符合成本和质量要求的、抽象的系统实 现方案的过程。从面向对象分析到面向对象设计,是一个逐渐扩充模型的过程。面向对象 中任何对象都可以归属于某类对象,任何对象都是某一类对象的实例。类在面向对象中描述了一组具有相同的特性和行为的对象。 C++ 就是一种面向对象的高级语言。

面向过程是一种以过程为中心的编程思想。面向过程分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,在使用的时候一个一个依次调用。面向过程其实是最为实际的一种思考方式,就算是面向对象的方法也是含有面向过程的思想。可以说面向过程是一种基础的方法。它考虑的是实际的实现。
一般的面向过程是从上往下步步求精。在面向过程中,最重要的是模块化的思想方法。C 语言就是一种典型的面向过程语言。

答案:
面向过程是一种以过程为中心的编程思想,以算法进行驱动。面向对象是一种以对象 为中心的编程思想,以消息进行驱动。面向过程编程语言的组成为:程序=算法+数据,面向对象编程语言的组成为:程序=对象+消息。

7.面向对象的特征是什么

分析:
面向对象语言的3个要素为封装、继承和多态。

  • 封装就是将抽象得到的数据和行为(或功能)相结合,形成一个有机的整体,也就是 将数据与操作数据的源代码进行有机的结合,形成“类”,其中数据和函数都是类的成员。 封装的目的是增强安全性和简化编程,使用者不必了解具体的实现细节,而只是要通过外部接口和特定的访问权限来使用类的成员。
  • 继承是指可以使用现有类的所有功能,继承可以使一个对象直接使用另一个对象的属 性和方法,并在无需重新编写原来的类的情况下对这些功能进行扩展。通过继承创建的新 类称为“子类”或“派生类”。被继承的类称为“基类”、 “父类”或“超类”。继承的过程是从一般到特殊的过程。
  • 多态性是允许将父对象设置成为和一个或多个它的子对象相等的技术,赋值之后,父 对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。例如 a:=b; 多态性使得能够利用同一类(基类)类型的指针来引用不同类的对象,以及根据所引用对象的不同,以不同的方式执行相同的操作。
    面向对象语言比较面向过程语言有类的概念。类就是把一组具有相同的方法和属性的
    对象归属为一起。面向对象中,所有对象都可以归属为一个类。

答案: 面向对象的3个要素为:封装、继承、多态。面向对象中所有的对象都可以归属为一
个类。

8.抽象类及它的用途

分析:
有的时候,基类并不需要与具体的事物关联起来,例如动物,它是一个抽象的概念, 表示所有的动物,可以派生出企鹅,猴子等。 C+ 引入了抽象类 (abstract class) 的概念来为各种派生类提供一个公共的界面。
抽象类可以提供多个派生类共享基类的公共定义,它可以提供抽象方法,也可以提供 非抽象方法。抽象类不能实例化,必须通过继承由派生类实现其抽象方法,也就是说,对 抽象类不能使用new 关键字,它也不能被封装。如果抽象类的派生类没有实现所有的抽象方法,则该派生类也必须声明为抽象类。派生类使用覆盖 (overriding) 来实现抽象方法。
抽象类一定包含有纯虚函数,因此不能定义抽象类的对象。一个使用抽象类的代码示
例如下:

#include 
using namespace std;


class A {                           // 定义抽象类
public:
    A();
    void f1();
    virtual void f2();              // 虚函数
    virtual void f3() = 0;
    virtual ~A();
};

class B : public A {               // 继承抽象类
public:
    B();
    void f1();
    void f2();  
    void f3();

    virtual ~B();  
};


int main()
{
    A *m_j = new B();
    m_j->f1();
    m_j->f2();
    m_j->f3();

    delete m_j;

    return 0;
}

答案: 包含纯虚函数的类称为抽象类。抽象类把有共同属性或方法的对象抽象成一个类。

9.如何访问静态成员

分析:
声明为 static 的类成员能在类的范围内共享,这样的类成员就是类的静态成员。在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,因此保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。

使用静态数据成员可以节省内存,因为它是所有对象所公有的。因此,对多个对象来 说,静态数据成员只会在内存中开辟一块存储空间,供所有对象共用,静态成员在类加载 的时候就存在于内存中。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。 只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。

类的静态成员是可以独立访问的,也就是说,不需要创建类的实例就可以访问静态成员。类的静态函数只能调用静态成员,因为静态函数不包含 this 指针。

静态数据成员被类的所有对象所共享,包括该类派生类的对象。即派生类对象与基类对象共享基类的静态数据成员。
示例代码:

#include 
using namespace std;

class Base
{
public:
    static int _num;   // 声明静态成员
};

int Base::_num = 0;    // 静态数据成员的真正定义

class Derived : public Base
{

};

int main()
{
    Base a;
    Derived b;
    a._num = 2;
    cout << "Base " << a._num << endl;
    cout << "Derived " << b._num << endl;

  
    return 0;
}

上面代码运行的结果为: 2 2 由此可见派生类和基类共用了一个静态成员。
类的静态成员可以成为成员函数的可选参数,而普通成员则不可以。示例代码如下:

class Base
{
public:
    static int _num;   // 声明静态成员
    int _var;

    void foo1(int i = _num);     // 正确
    void foo1(int i = _var);     // 错误
};

类的静态成员的类型可以是所属类的类型,而普通成员则不可以。普通成员的只能声明为所属类类型的指针或引用。示例代码如下:

class Base
{
public:
    static int _object;   // 正确,声明静态成员
    Base _object2;        // 错误
    Base *pobject;        // 正确
    Base &mobject2;       // 正确

};

答案: 静态成员可以独立访问,不需要创建类的实例,它也不能用实例来进行调用。类的静
态方法只能访问类的静态成员。

10.什么是多态?多态的作用

分析:
多态是面向对象的重要特性之一,它是一种行为的封装,简单的描述就是“一个接口,多种实现”,也就是同一种事物所表现出的多种形态。

编写程序实际上就是一个将世界的具体事物进行抽象化的过程,多态就是抽象化的一 种体现,把一系列具体事物的共同点抽象出来,再通过这个抽象的事物,与不同的具体事物进行对话。

对不同类的对象发出相同的消息的时候,将会有不同的行为。例如,你的老板让所有员工在9点钟开始工作。他会在9点钟的时候发公开消息说: “开始工作”即可,而不需 要对销售人员说: “开始销售工作”,对技术人员说: “开始技术工作”。 因为“员工”是一个抽象的事物,只要是员工就可以开始工作,他知道这一点就行了。至于每个员工,当然会各司其职,做各自的工作。

在面向对象编程中多态的主要作用表现为以下两点:
(1)应用程序可以不必为每一个派生类编写功能调用,而只需要对抽象基类进行处理 即可。这样就大大提高程序的可复用性,精简了代码的编写量。尽量多的编写可复用代码是编写好面向对象语言程序的一个重要规则。
(2)派生类的功能可以被基类的方法或引用变量所调用,这称作向后兼容,可以在很大程度上提高可扩充性和可维护性。

答案: 多态是面向对象编程的核心概念之一。多态技术允许将父类设置成和它的一个或更多 的子对象相等。

11.C++中多态都有什么类型

分析:

  • 动态多态(运行时多态/基于对象的多态):在程序运行时根据对象的实际类型来调用相应的函数,也称为后期绑定或运行时绑定。C++通过虚函数来实现动态多态。
#include 
using namespace std;

class Vehicle       // 抽象类
{
public:
    virtual void run() const = 0;
};

class Car : public Vehicle
{
public:
    virtual void run() const 
    {
        cout << "run a car" << endl;
    }
};

class Airplane : public Vehicle
{
public:
    virtual void run() const 
    {
        cout << "run a Airplane" << endl;
    }
};

void run_vehicle(const Vehicle* vehicle)
{
    vehicle->run();
}

int main()
{   
    Car car;
    Airplane plane;
    run_vehicle(&car);
    run_vehicle(&plane);

    return 0;
}

}
  • 静态多态(编译时多态/基于类型的多态):在程序编译时根据函数的声明和参数类型来确定调用哪个函数,也称为前期绑定或编译时绑定。C++通过函数重载和模板来实现静态多态。
#include 
using namespace std;

class Vehicle       // 抽象类
{
public:
    virtual void run() const = 0;
};

class Car : public Vehicle
{
public:
    void run() const 
    {
        cout << "run a car" << endl;
    }
};

class Airplane : public Vehicle
{
public:
    void run() const 
    {
        cout << "run a Airplane" << endl;
    }
};

void run_vehicle(const Vehicle& vehicle)
{
    vehicle.run();
}

int main()
{   
    Car car;
    Airplane plane;
    run_vehicle(car);
    run_vehicle(plane);

    return 0;
}
  • 函数多态(基于函数的多态):在函数的实现中根据参数类型、个数和顺序来实现不同的功能,也称为重载函数。C++通过函数重载和默认参数来实现函数多态。
#include 
#include 
using namespace std;

int my_add(int a, int b)
{
    return a+b;
}

int my_add(int a, string b)
{
    return a + atoi(b.c_str());
}

int main()
{   
    int i = my_add(1, 2);
    int s = my_add(1, "2");
    
    cout << i << endl;  // 3
    cout << s << endl;  // 3

    return 0;
}

  • 宏多态:通过宏定义来实现多态。它比较简单,但不够安全且不易维护,因此不推荐使用。
#include 
#include 
using namespace std;

#define ADD(A, B) (A) + (B)

int main()
{   
    int i1(1), i2(2);
    string s1("Hello "), s2("World  ");

    int i = ADD(i1, i2);
    string s = ADD(s1, s2);

    cout << i << endl;   // 3
    cout << s << endl;   // Hello World

    return 0;
}

答案:
多态有动态多态、静态多态、函数多态和宏多态等。编程者常说的多态指的是动态多态,它是基于继承机制和虚函数来实现的。

你可能感兴趣的:(嵌入式面试题,嵌入式,面试,校招)