每天一套笔试题系列(嵌入式相关2)

本文围绕C++面试展开,包含C++与C语言对比、跳转语句、函数调用、重载函数、构造函数等知识点的题目及解析,还涉及多线程与单线程优缺点及适用场景、套接字I/O模型等内容,为C++面试提供参考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.C++语言是从早期的C语言逐渐发展演变而来的,与C语言相比,它在求解问题方法上进

行的最大改进是(B)

A.面向过程

B.面向对象

C.安全性

D.复用性

2.C++语言的跳转语句中,对于break和continue说法正确的是( )

A.break语句只应用与循环体中

B.continue语句只应用与循环体中

C.break是无条件跳转语句,continue不是

D.break和continue的跳转范围不够明确,容易产生问题

break语句用于终止循环,并跳出循环体,它只应用于循环体中。当执行到break语句时,程序将立即退出当前循环,并继续执行循环后面的语句。

continue语句用于跳过当前循环中剩余的语句,然后继续进行下一次循环的执行。它也只应用于循环体中。当执行到continue语句时,程序将跳过当前循环体中剩余的语句,直接进入下一次循环的执行。

3.for(int x=0,y=0; !x && y<=5; y++)语句执行循环的次数是( )

A.0

B.5

C.6

D.无数次

根据给定的循环条件 !x && y<=5,初始时 x=0,所以 !x 的值为真(即1)。当 y=0 时,满足 y<=5 的条件,所以循环会执行。

循环中每次迭代执行的顺序是:首先进行 y++ 操作,然后判断循环条件。每次循环中,y 的值会增加1。当 y 递增到 y=5 时,仍满足 y<=5 的条件,继续执行循环。

当 y 递增到 y=6 时,不再满足 y<=5 的条件,循环终止。

因此,循环会执行6次。选项C. 6 是正确的答案。

4.考虑函数原型void test(int a,int b=7,char="*");,下面的函数调用中,属于不合法调用的是( )

A.test(5);

B.test(5,8);

C.test(6,”#”);

D.test(0,0,”*”);

在考虑函数原型 void test(int a, int b=7, char c="*"); 的情况下,下面的函数调用是合法或不合法的:

A. test(5);
这个调用是合法的。它省略了其中的参数 b 和 c,但由于这两个参数有默认值,所以可以使用默认值。

B. test(5, 8);
这个调用是合法的。它提供了参数 a 和 b 的值,同时省略了参数 c,但由于 c 有默认值,所以可以使用默认值。

C. test(6, “#”);
这个调用是不合法的。在原型中,参数 c 的类型是 char,但传递的是一个字符串字面值 "#",这不符合函数的预期类型。

D. test(0, 0, “*”);
这个调用是合法的。它提供了参数 ab 和 c 的值,符合函数的原型。

所以,选项C. test(6,“#”) 是不合法的函数调用。

5.下面有关重载函数的说法中正确的是( )

A.重载函数必须具有不同的返回值类型

B.重载函数形参个数必须不同

C.重载函数必须有不同的形参列表

D.重载函数名可以不同

正确的说法是:

C. 重载函数必须有不同的形参列表

重载函数是指在同一个作用域中,有多个具有相同函数名但参数列表不同的函数。它们可以有不同的返回值类型,也可以具有相同或不同数量的参数。

在C++中,函数的重载是通过参数列表的不同来实现的。这意味着重载函数必须具有不同的形参列表,也就是参数类型、参数个数、参数顺序至少有一项不同。

关于选项的说明:
A. 重载函数的返回值类型可以相同也可以不同。重载函数的返回值类型不是区分重载的条件。
B. 重载函数的参数个数可以相同也可以不同。重载函数的参数个数不是区分重载的条件。
D. 重载函数的函数名必须相同,只有参数列表不同。

因此,选项C. 重载函数必须有不同的形参列表是正确的说法。

6.下列关于构造函数的描述中,错误的是( )

A.构造函数可以设置默认参数

B.构造函数在定义类对象时自动执行

C.构造函数可以是内联函数

D.构造函数不可以重载

错误的描述是:

D. 构造函数不可以重载

构造函数是一种特殊类型的成员函数,用于创建和初始化类的对象。与其他函数一样,构造函数可以根据不同的参数列表进行重载。通过重载构造函数,可以使用不同的参数来创建不同初始化方式的对象。

因此,选项D. 构造函数不可以重载是错误的描述。构造函数是支持重载的。其他选项A、B和C的描述都是正确的。构造函数可以设置默认参数、在定义类对象时自动执行,并且可以是内联函数。

7.下面描述中,表达错误的是( )

A.公有继承时基类中的public成员在派生类中仍是public的

B.公有继承是基类中的private成员在派生类中仍是private的

C.公有继承时基类中的protected成员在派生类中仍是protected的

D.私有继承时基类中的public成员在派生类中是private的


一般都不使用 protected 或 private 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:

  • 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有保护成员来访问。
  • 保护继承(protected): 当一个类派生自保护基类时,基类的公有保护成员将成为派生类的保护成员。
  • 私有继承(private):当一个类派生自私有基类时,基类的公有保护成员将成为派生类的私有成员

8.应在下列程序划线处填入的正确语句是( )

#include <iostream.h>

class Base

{

public:

    void fun() { cout<<"Base fun called."<<ENDL; }

};

class Derived : public Base
{

    void fun()

{

    _____________ // 显示调用基类的函数fun()

    cout<<"Derived::fun"<<ENDL;

}

};

A.fun();

B.Base.fun();

C.Base::fun();

D.Base->fun();

9.有如下程序:

#include <iostream>

using namespace std;

class BASE
{
    char c;
public:
    BASE(char n) : c(n) {}
    virtual ~BASE() { cout << c; }
};

class DERIVED: public BASE
{
    char c;
public:
    DERIVED(char n) : BASE(n+1), c(n) {}
    ~DERIVED() { cout << c; }
};

int main()
{
    DERIVED("X");
    return 0;
}

执行上面的程序将输出( )

A.XY

B.YX

C.X

D.Y

这段代码定义了一个基类 BASE 和一个派生类 DERIVED。在 main 函数中创建了一个 DERIVED 对象,并传入字符 “X”。接着在程序结束时,会依次调用 DERIVED 类和 BASE 类的析构函数,并输出结果 “YX”。

10.在进行完任何C++流的操作后,都可以用 C++流的有关成员函数检测流的状态;其中只

能用于检测输入流状态的操作函数名称是( )

A.fail

B.eof

C.bad

D.good

下面是这些函数的具体含义:

  • “good"函数:检查流的状态是否是"goodbit”,即流的状态正常,无错误发生。
  • “eof"函数:检查流的状态是否是"eofbit”,即已到达文件末尾。
  • “fail"函数:检查流的状态是否是"failbit”,即流操作失败。
  • “bad"函数:检查流的状态是否是"badbit”,即流遇到了严重的故障。

11.分析以下程序的执行结果(考察构造、析构函数的调用顺序)

#include <iostream>

class base
{
    int n;
public:
    base(int a)
    {
        std::cout << "constructing base class" << std::endl;
        n = a;
        std::cout << "n=" << n << std::endl;
    }
    ~base()
    {
        std::cout << "destructing base class" << std::endl;
    }
};

class subs : public base
{
    base bobj;
    int m;
public:
    subs(int a, int b, int c) : base(a), bobj(c)
    {
        std::cout << "constructing sub class" << std::endl;
        m = b;
        std::cout << "m=" << m << std::endl;
    }
    ~subs()
    {
        std::cout << "destructing sub class" << std::endl;
    }
};

int main()
{
    subs s(1, 2, 3);
    return 0;
}

这段代码的目的是创建一个包含基类和派生类的继承关系,并在构造和析构函数中输出相关信息。

在代码中,base 类是一个基类,其构造函数接受一个整数参数。派生类 subs 继承自 base,同时还包含一个成员变量 bobj,类型为 basesubs 类的构造函数接受三个整数参数,分别用于初始化基类 base、成员变量 bobj 和派生类自身的成员变量 m

在 main 函数中,创建了一个 subs 类的对象 s,传入参数分别为 1、2、3。程序运行时,会按照构造函数的顺序输出相应的信息,然后再按照析构函数的顺序输出相应的信息,即先构造基类对象,再构造成员变量对象,最后构造派生类对象,析构的顺序与构造相反。

运行代码后,输出结果应为:

constructing base class
n=1
constructing base class
n=3
constructing sub class
m=2
destructing sub class
destructing base class
destructing base class

其中,构造和析构函数的输出信息分别以 “constructing” 和 “destructing” 开头,后跟对应的类名。

12.分析以下程序的执行结果

​ class A

{

    ...

        virtual void
        display_infor()

    {

        printf("class A output");
    }
};

class B : public A

{

    ...

        virtual void
        display_infor()

    {

        printf("class B output");
    }
};

void displayClass(A *pA)

{

    pA->display_infor();
}

main()

{

    B b;

    displayClass(&b);
}

​

这段代码演示了多态性的概念。其中,基类 `A` 和派生类 `B` 之间存在继承关系,且基类中的虚函数 `display_infor()` 被派生类重写。

在代码中,`displayClass()` 函数接受一个指向基类对象的指针 `pA`,然后调用该指针所指向对象的 `display_infor()` 函数。由于 `display_infor()` 是虚函数,并且在派生类 `B` 中进行了重写,因此通过传入 `B` 类对象的指针,会调用 `B` 类中重写后的 `display_infor()` 函数。

在 `main()` 函数中,首先创建了一个 `B` 类对象 `b`,然后调用 `displayClass()` 函数,并将 `b` 的地址作为参数传入。由于 `displayClass()` 函数接受的是基类指针,但实际传入的是派生类对象的地址,所以会发生多态性,即会根据实际对象的类型调用相应的虚函数。

因此,运行程序会输出 "class B output",这是因为在 `displayClass()` 函数中,通过基类指针调用了派生类 `B` 中重写的虚函数。

13.分析以下程序的执行结果(考察虚继承)

#include <iostream>

class A
{
public:
    int n;
};

class B : virtual public A {};

class C : virtual public A {};

class D : public B, public C
{
public:
    int getn()
    {
        return B::n;
    }
};

int main()
{
    D d;
    d.B::n = 10;
    d.C::n = 20;
    std::cout << d.B::n << "," << d.C::n << std::endl;
    return 0;
}

在类 B 和类 C 中,它们都通过虚继承方式继承了基类 A,这意味着类 D 继承了 B 和 C 时,只会保留一个基类 A 的子对象。这种虚继承方式可以避免在 D 中存在两个不同的 A 子对象的问题。

在 main() 函数中,创建了一个 D 类的对象 d。然后给 d 对象的 B 子对象的 n 成员变量赋值为 10,C 子对象的 n 成员变量赋值为 20。最后通过 cout 输出 d.B::n 和 d.C::n 的值,以,分隔。

由于 D 类中的 getn() 函数返回的是 B::n 的值,因此输出的结果会是 10,20

14.在下面程序的横线处填上适当的语句,使该程序执行结果为10

#include <iostream>

class MyClass
{
public:
    MyClass(int a)
    {
        x = a;
    }

    //________________________________//取 x 值 
    int GetNum()
    {
        return x;
    }

private:
    int x;
};

int main()
{
    MyClass my(10);
    std::cout << my.GetNum() << std::endl;
    return 0;
}

15.以下为Windows NT下的 32 位C++程序,请计算 sizeof 的值

void Func( char str[100] )

 {

sizeof( str ) =       4    

}

void *p = malloc( 100 );

sizeof ( p ) =      4     

  1. 在 Func() 函数中,参数 str 被声明为 char str[100],但在函数内部使用 sizeof(str) 并不会返回存储的字符串的大小,而是返回指针的大小(在 32 位系统中通常为 4 字节)。这是因为数组的维度在传递给函数时会被忽略,str 实际上被视为指向 char 的指针。

  2. 在 malloc() 函数中,分配的内存块的大小为 100 字节,但对指针 p 使用 sizeof(p) 同样不会返回分配的内存块的大小,而是返回指针的大小。

16.函数void swap()完成p1与p2的互换,请指出两者的异同:

void swap(int *p1, int *p2)

{

int *p;

*p = *p1;

*p1 = *p2;

*p2 = *p;

}

左框

void swap(int *p1, int *p2)

{

int p;

p = *p1;

*p1 = *p2;

*p2 = p;

}

右框

  1. 左框函数中使用的变量 int *p 是一个指针变量,而右框函数中使用的变量 int p 是一个普通的整数变量。
  2. 左框函数中的指针变量 p 没有初始化,而右框函数的整数变量 p 初始化为 *p1
  3. 左框函数中的赋值操作是 *p = *p1,而右框函数中的赋值操作是 p = *p1
  4. 左框函数和右框函数的交换逻辑是一样的,即将 *p1 的值赋给 *p(或 p),将 *p2 的值赋给 *p1,再将 *p(或 p)的值赋给 *p2

17.请分析以下代码,找出其中的问题。

void test1()

{

char string[10];//string[11]

char* str1 = "0123456789";

strcpy( string, str1 );

}

问题在于 string 数组的大小为10个字符,而字符串常量 "0123456789" 包含了10个字符及结尾的空字符,总共11个字符。因此,使用 strcpy 将 "0123456789" 复制到 string 数组时,会导致缓冲区溢出,可能破坏邻近的内存。

为了修复这个问题,你需要将 string 数组的大小增加到至少11个字符,以确保足够存储复制的字符串及其结尾的空字符。

18.分析以下代码,写出屏幕应该输出的结果,并简要说明为什么。(忽略内存泄漏问题)

// test.cpp .

//

#include "stdio.h"

struct STRUCTA{

int n;

};

void test1(STRUCTA st);

void test2(STRUCTA* st);

void test3(STRUCTA* st);

void test4(STRUCTA& st);

void test5(STRUCTA*& st);

void test6(STRUCTA** st);

int main(int argc, char* argv[])

{

STRUCTA *pst = new STRUCTA;

pst->n = 1;

test1(*pst);

printf("%d\n", pst->n);

pst->n = 1;

test2(pst);

printf("%d\n", pst->n);

pst->n = 1;

test3(pst);

printf("%d\n", pst->n);

pst->n = 1;

test4(*pst);

printf("%d\n", pst->n);

pst->n = 1;

test5(pst);

printf("%d\n", pst->n);

pst->n = 1;

test6(&pst);

printf("%d\n", pst->n);

return 0;

}

void test1(STRUCTA st)

{

st.n = 100;

}

void test2(STRUCTA* st)

{

st = new STRUCTA;

st->n = 100;

}

void test3(STRUCTA* st)

{

 st->n = 100;

}

void test4(STRUCTA& st)

{

st.n = 100;

}

void test5(STRUCTA*& st)

{

st = new STRUCTA;

st->n = 100;

}

void test6(STRUCTA** st)

{

*st = new STRUCTA;

(*st)->n = 100;

}

  • void test1(STRUCTA st):该函数接受一个 STRUCTA 结构体对象作为参数,通过传值方式将参数副本用于修改,并将 n 成员赋值为 100。

  • void test2(STRUCTA* st):该函数接受一个 STRUCTA 结构体指针作为参数,通过传址方式传递指针,并在函数内部使用 new 运算符为指针分配新的 STRUCTA 对象,并将 n 成员赋值为 100。

  • void test3(STRUCTA* st):该函数接受一个 STRUCTA 结构体指针作为参数,通过传址方式传递指针,并将指针所指向的 STRUCTA 对象的 n 成员赋值为 100。

  • void test4(STRUCTA& st):该函数接受一个 STRUCTA 结构体的引用作为参数,通过引用方式传递对象,并将 n 成员赋值为 100。

  • void test5(STRUCTA*& st):该函数接受一个指向 STRUCTA 结构体指针的引用作为参数,通过引用方式传递指针的引用,并在函数内部使用 new 运算符为指针分配新的 STRUCTA 对象,并将 n 成员赋值为 100。

  • void test6(STRUCTA** st):该函数接受一个 STRUCTA 结构体指针的指针作为参数,通过传址方式传递指针的指针,并在函数内部使用 new 运算符为指针分配新的 STRUCTA 对象,并将 n 成员赋值为 100。

现在我们来分析代码的输出结果和原因:

  • test1(*pst):参数传递是按值传递,所以函数内部对副本的修改不会影响原始对象。因此,pst->n 的值仍为 1。输出结果应该是 1。

  • test2(pst):参数传递是按址传递,通过传递指针地址使得指针的指向可以在函数内被修改。所以,指针 pst 指向了一个新的 STRUCTA 对象,而原始对象的 n 值没有改变。输出结果应该是 1。

  • test3(pst):参数传递是按址传递,直接传递指针的地址,所以函数内部可以修改指向的对象的值。原始对象的 n 值被修改为 100。输出结果应该是 100。

  • test4(*pst):参数传递是按引用传递,所以函数内部对引用的修改会直接影响原始对象。因此,原始对象的 n 值被修改为 100。输出结果应该是 100。

  • test5(pst):参数传递是按引用传递,通过传递指针的引用使得指针的指向可以在函数内被修改。所以,指针 pst 被修改指向了一个新的 STRUCTA 对象,而原始对象的 n 值没有改变。输出结果应该是 1。

  • test6(&pst):参数传递是按址传递,直接传递指针的指针的地址,所以函数内部可以通过指针的指针修改指向的对象。原始对象指针 pst 被指向一个新的 STRUCTA 对象,而新的 STRUCTA 对象的 n 值为 100。因此,输出结果应该是 100。

总结输出结果:
1
1
100
100
1
100

19.描述一下多线程和单线程的优缺点,各自在什么环境下用最为合适?简单举例说明。

单线程的优点:

  1. 简单:单线程程序通常比较简单,因为只有一个执行流程。
  2. 资源消耗低:单线程不需要额外的线程管理开销,因此在资源有限的环境下更为适用。
  3. 调试方便:单线程程序的调试相对简单,因为只需关注单个执行流程。

单线程的缺点:

  1. 响应时间较慢:当单线程程序执行耗时任务时,整个程序会被阻塞,导致用户体验不佳。
  2. 低效率:单线程无法利用现代多核处理器的多个计算核心,无法充分发挥硬件资源的优势。
  3. 难以实现并发:单线程只能顺序执行任务,无法同时处理多个任务。

单线程场景:一个简单的计算器应用,用户可以输入一个数值进行加法运算,由于这是一个简单的任务,不涉及复杂的计算或并发操作,使用单线程即可满足需求,不需要引入多线程的复杂性。

多线程的优点:

  1. 高效利用资源:多线程可以同时执行多个任务,充分利用多核处理器的并行计算能力。
  2. 提高响应性:多线程可以在后台执行耗时任务,不影响主线程的响应时间,从而提高用户体验。
  3. 实现并发:多线程可以同时处理多个任务,实现并发执行,提高系统吞吐量和效率。

多线程的缺点:

  1. 复杂性增加:多线程编程需要考虑并发访问共享数据、避免竞态条件等复杂的问题,增加程序设计和调试的难度。
  2. 线程间同步问题:多个线程并发访问共享数据可能会导致数据不一致性或竞争条件,需要额外的同步机制来处理。

多线程适用的场景:一个图片处理应用,用户可以同时对多张图片进行滤镜处理,每张图片的处理可以在一个单独的线程中执行,从而实现图片处理的并行计算,提高处理性能。

20.套接字I/O模型有哪些?这些模型中你用过那些模型?简单举例说明。

  1. 阻塞式(Blocking)I/O模型

  2. 非阻塞式(Non-Blocking)I/O模型

  3. 事件驱动(Event-Driven)I/O模型

  4. 多路复用(Multiplexing)I/O模型

  5. 异步(Asynchronous)I/O模型

多路复用I/O模型的优点是可以有效地处理大量的并发连接,减少了线程或进程切换的开销。它适用于需要高并发、同时监听多个套接字的服务器应用。一些使用多路复用模型的系统包括高性能的Web服务器、聊天服务器和实时通信系统。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

萌新小电阻

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值