C++面试题和笔试题(五)

一、

#include <iostream>  
using namespace std;  
  
class Base {  
public:  
    Base(int j) : i(j) {}  
    virtual ~Base() {}  
    void func1() {  
        i *= 10;  
        func2();  
    }  
    int getvalue() {  
        return i;  
    }  
protected:  
    virtual void func2() {  
        i++;  
    }  
protected:  
    int i;  
};  
  
class Child : public Base {  
public:  
    Child(int j) : Base(j) {}  
    void func1() {  
        i *= 100;  
        func2();  
    }  
protected:  
    void func2() {  
        i += 2;  
    }  
};  
  
int main() {  
    Base* pb = new Child(1);  
    pb->func1();  
    cout << pb->getvalue() << endl;  
    delete pb;  
    return 0;  
}

输出的结果是(102)

官方解释:

  1. 基类Base:定义了一个整数成员i和两个虚函数func1func2func1函数首先将i乘以10,然后调用func2func2在基类中的实现是将i加1。

  2. 派生类Child:从Base类公开继承。它重写了func1func2函数。在Child中,func1首先将i乘以100,然后调用func2func2在派生类中的实现是将i加2。

  3. main函数:创建了一个指向Child对象的Base指针pb。通过pb调用func1函数。由于多态性,将调用Child类中的func1。最后,输出i的值并删除动态分配的对象。

程序的输出将取决于func1func2的调用顺序以及它们在基类和派生类中的实现。在这个例子中,由于Child类重写了func1func2,所以最终i的值将会是1 * 100 + 2 = 102Child类的func1乘以100,然后Child类的func2加2)。输出将是102

自己的理解:

假设现在我们有两个这样的魔法盒子,一个是爸爸的,一个是孩子的。爸爸的盒子只有“变大10倍并加1”这个按钮,而孩子的盒子既有“变大10倍并加1”按钮,又有“变大100倍并加2”按钮。

但是,孩子很调皮,他把他的盒子给了爸爸,并且让爸爸以为这就是他的盒子。当爸爸按下“变大10倍并加1”这个按钮时,其实他按的是孩子盒子的按钮,所以盒子的东西会按照孩子的规则来变大。

假设一开始盒子里有1个苹果,当爸爸按下按钮后,这个苹果会先变成100个(因为是按照孩子的盒子的“变大100倍”规则),然后再加2个,最后盒子里就有102个苹果了。

最后,爸爸会告诉我们盒子里有多少个苹果,我们就知道了。

在这个例子中,爸爸的盒子就像是基类Base,孩子的盒子就像是派生类Child。爸爸按下的按钮就像是基类的函数func1,而孩子盒子的特殊功能就像是派生类重写的函数。因为爸爸实际上拿的是孩子的盒子,所以按下按钮后,会按照孩子的规则来变化

考点

  1. 多态性:通过基类指针调用派生类对象的虚函数时,实际调用的是派生类中定义的版本。这里 pb->func1(); 和 pb->func2(); 展示了多态性的使用。

  2. 虚函数func2 被声明为 protected 和 virtual,这意味着它可以在派生类中被重写,同时保证多态性的行为。

  3. 构造函数和析构函数:构造函数用于初始化对象,析构函数用于清理对象。这里 Base 和 Child 类的构造函数展示了如何初始化基类成员变量,而析构函数则展示了如何安全地销毁对象。

  4. 继承Child 类公开继承自 Base 类,这意味着 Child 对象可以使用 Base 类的公有和保护成员。

  5. 访问控制Base 类中的 func2 和 i 被声明为 protected,这意味着它们可以在派生类中被访问,但不能从 Base 类的对象直接访问。

  6. 内存管理:使用 new 和 delete 进行动态内存分配和释放是 C++ 中的一个重要概念,这里展示了如何正确地使用它们。

  7. 错误处理:虽然代码中没有显式展示错误处理,但在实际编程中,处理动态内存分配失败(虽然在现代系统上很少见)或其他异常情况也是重要的考点。

 二、改错题

1)

void GetMemory(char*p)
{
p=(char*)malloc(100);
}
void Test(void)
{
char *str=NULL;
GetMemory(str);
strcpy(str,"hello world");
printf(str);
}

问题一:GetMemory 函数中的指针修改不反映到 Test 函数中。
在 GetMemory 函数中,p 是一个指向 char 的指针的本地副本。当你将 p 指向 malloc 分配的内存时,Test 函数中的 str 并没有改变,因为它传递的是 str 的一个副本,而不是 str 本身的地址。因此,Test 函数中的 str 仍然是 NULL

问题二:内存泄漏。
由于 GetMemory 函数中的 malloc 分配的内存没有被释放,这会导致内存泄漏。

问题三:strcpy 可能导致未定义行为。
由于 str 是 NULL,尝试使用 strcpy 将字符串复制到它指向的位置会导致未定义行为,通常是程序崩溃。

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
  
void GetMemory(char **p) {  
    *p = (char*)malloc(100);  
    if (*p == NULL) {  
        // 错误处理,分配内存失败  
        exit(EXIT_FAILURE);  
    }  
}  
  
void Test(void) {  
    char *str = NULL;  
    GetMemory(&str); // 传递str的地址  
    if (str != NULL) {  
        strcpy(str, "hello world");  
        printf("%s\n", str);  
        free(str); // 释放分配的内存  
    }  
}  
  
int main() {  
    Test();  
    return 0;  
}
  1. GetMemory 函数现在接受一个指向 char 指针的指针(即 char **),这样它就可以修改 Test 函数中的 str
  2. 使用 malloc 分配内存后,我们检查是否成功分配了内存。
  3. 在 Test 函数中,我们检查 str 是否为 NULL,然后才尝试使用 strcpy
  4. 使用完分配的内存后,我们在 Test 函数中释放它,以避免内存泄漏。
  5. malloc 分配的内存应该用 free 来释放,而且应该在所有使用完这块内存的地方都这么做 

 自己的理解:

想象你有一个存钱罐,你想要放更多的钱进去,但是你还没有足够的硬币。于是,你决定去银行取一些钱。

GetMemory 函数就像是你去银行取钱的过程。你告诉银行你想要100个硬币(这就像是分配100个字节的内存)。银行给你一个装满硬币的袋子(这就像是malloc返回的内存地址)。

Test 函数就像是你回到家,想要把取回来的钱放进存钱罐里。但是,如果你忘记从银行带钱回来(也就是说,str 仍然是NULL),你尝试往存钱罐里放钱时,就会出问题,因为没有钱可以放(这会导致程序崩溃)。

为了避免这个问题,你在GetMemory函数里不是直接告诉银行你要取钱,而是告诉银行你存钱罐的地址(这就像是传递str的地址,即&str)。这样,银行就能直接把装满硬币的袋子放到你的存钱罐里。

当你把钱放进存钱罐后,记得以后不用了要把钱放回银行,不然你的家就会堆满钱,没地方放了(这就像是内存泄漏)。所以,在Test函数最后,我们使用free(str)来释放我们之前分配的内存。

这样,你就学会了如何正确地取钱(分配内存)和存钱(释放内存),而不会让家里变得乱七八糟。

2)

#include<iostream>
using namespace std;
class A
{
public:
A()
{
cout<<"new A"<<endl;
m=(char*)malloc(10);
}
~A()
{
cout<<"del A"<<endl;
}
protected:
char *m;
}
class B:public A
{
public:
B()
{
cout<<"new B"<<endl;
m=(char*)malloc(100);
}
~B()
{
cout<<"del B"<<endl;
}
protected:
char*m;
};

int main()
{

A*c=new B;
delete c;
}

错误和需要注意的地方:

  1. class B 中重定义了 m 成员变量。在继承关系中,如果基类 A 和派生类 B 中都有名为 m 的成员变量,那么它们将被视为两个不同的变量。这可能会导致混淆和错误。

  2. 在 main 函数中,你使用 new B 创建了一个 B 类的对象,但是将其赋值给了一个 A 类型的指针 c。虽然这是合法的(因为 B 是从 A 公有继承的),但需要注意,当你通过 A 类型的指针访问 m 成员时,你会访问到基类 A 中的 m,而不是派生类 B 中的 m

  3. 在 A 和 B 的构造函数中,你使用 malloc 为 m 分配了内存,但在析构函数中并没有使用 free 来释放这些内存。在C++中,更推荐使用 new 和 delete 来管理动态内存,因为 new 会自动调用对象的构造函数,而 delete 会自动调用析构函数。

#include<iostream>  
using namespace std;  
  
class A {  
public:  
    A() {  
        cout << "new A" << endl;  
        m = new char[10]; // 使用 new 分配内存  
    }  
    virtual ~A() { // 声明为虚析构函数,确保派生类的析构函数也会被调用  
        cout << "del A" << endl;  
        delete[] m; // 使用 delete[] 释放内存  
    }  
protected:  
    char *m;  
};  
  
class B : public A {  
public:  
    B() {  
        cout << "new B" << endl;  
        // 注意:这里不应该再次分配 m,因为 m 已经在 A 的构造函数中被分配了。  
        // 如果你确实需要在 B 中分配新的内存,你应该使用一个不同的变量名。  
    }  
    ~B() {  
        cout << "del B" << endl;  
        // 在这里,我们不需要释放 m,因为 A 的析构函数会负责释放它。  
        // 如果 B 有自己的内存需要释放,应该在这里做。  
    }  
};  
  
int main() {  
    A* c = new B; // 创建 B 的对象,但用 A 的指针指向它  
    delete c; // 使用 delete 释放 c 指向的对象,这会先调用 B 的析构函数,然后调用 A 的析构函数  
    return 0;  
}

 在这个修改后的版本中,我将 A 的析构函数声明为虚函数(virtual ~A())。这是为了确保当通过基类指针删除派生类对象时,派生类的析构函数也会被调用。此外,我还使用了 new 和 delete[] 来分配和释放内存,这是C++中更推荐的做法。

3)

#include<iostrem>
using namespace std;

class Test
{
public:
     long a;
     long b;
  virtual void fun(){}

Test(long temp1=0,long temp2=0)
{

  a=temp1;
  b=temp2;
}

long getA(){return a;}
long getB(){return b;}
};

int maint()
{

  Test obj(5,10);
long * pint =(long*)&obj;
*(pint)=100;
*(pint+1)=200;
cout<<"a="<<obj.getA()<<endl;
cout<<"b="<<obj.getB()<<endl;
return 0;
}

程序在64位机器上打印输出结果是(a=100,b=200)

官方解释:

    在 main 函数中,你创建了一个 Test 类的对象 obj,并初始化了 a 为5,b 为10。然后,你创建了一个指向 long 的指针 pint,并将其设置为指向 obj 的地址。接下来,你通过 pint 修改了 obj 的内存内容。

long * pint =(long*)&obj;  
*(pint)=100;  
*(pint+1)=200;

这里,*(pint)=100; 将 obj 的起始地址(即 a 的值)设置为100。*(pint+1)=200; 将 obj 起始地址后8字节的位置(即 b 的值)设置为200。

因此,obj 的成员变量 a 和 b 现在分别被设置为100和200。

输出将是:a=100 b=200。

自己的理解:

当然可以,让我们用小学生能懂的方式和生活中的例子来解释这段代码。

首先,想象一下你有一个魔法盒子,这个盒子里面可以放两个魔法球,每个魔法球都有一个数字。这个魔法盒子就是我们代码中的Test类,而两个魔法球就是ab这两个变量。

现在,我们有一个特别的咒语,可以让这个魔法盒子里的魔法球变出数字来。这个咒语就是Test类的构造函数,它可以在我们创建魔法盒子的时候,给两个魔法球分别赋予数字。

Test(long temp1=0,long temp2=0)  
{  
    a=temp1;  
    b=temp2;  
}

比如说,我们念这个咒语:“魔法盒子,给我一个5和一个10!”然后,魔法盒子里的两个魔法球就分别变成了5和10。

main函数中,你创建了一个这样的魔法盒子,并给它念了咒语,让里面的两个魔法球变成了5和10。

接下来,你有一个魔法棒,这个魔法棒可以让你直接看到魔法盒子里面的东西。但是,你这次没有用魔法棒去看,而是直接用手去摸魔法盒子,并试图改变魔法球里的数字。

long * pint =(long*)&obj;  
*(pint)=100;  
*(pint+1)=200;

你用手伸进了魔法盒子,摸到了第一个魔法球,然后把它变成了100。接着,你又摸到了第二个魔法球,把它变成了200。

最后,你用魔法棒去看魔法盒子里的魔法球,发现它们已经变成了100和200,而不是原来的5和10了。

cout<<"a="<<obj.getA()<<endl;  
cout<<"b="<<obj.getB()<<endl;

所以,这个程序在64位机器上打印的输出结果是:a=100 b=200;

相关推荐

  1. C++面试试题

    2024-03-15 04:50:04       19 阅读
  2. C++面试试题(三)

    2024-03-15 04:50:04       21 阅读
  3. 嵌入式C语言面试试题

    2024-03-15 04:50:04       7 阅读
  4. QtC/C++开发-常见面试试题

    2024-03-15 04:50:04       10 阅读
  5. Hive窗口函数试题面试

    2024-03-15 04:50:04       20 阅读
  6. 40 道高频 C++ 面试试题及答案

    2024-03-15 04:50:04       20 阅读
  7. promise面试试题

    2024-03-15 04:50:04       14 阅读
  8. C语言:指针试题

    2024-03-15 04:50:04       7 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-15 04:50:04       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-15 04:50:04       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-15 04:50:04       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-15 04:50:04       20 阅读

热门阅读

  1. 题记(54)--L1-050 倒数第N个字符串

    2024-03-15 04:50:04       24 阅读
  2. c++特殊类设计

    2024-03-15 04:50:04       19 阅读
  3. 什么是API签名认证?

    2024-03-15 04:50:04       23 阅读
  4. 2024年科技前瞻:AI辅助研发引领未来创新浪潮

    2024-03-15 04:50:04       22 阅读
  5. Android JNI常用API函数介绍

    2024-03-15 04:50:04       21 阅读
  6. 有来团队后台项目-解析10

    2024-03-15 04:50:04       24 阅读
  7. Qt/QML编程之路:线程及Slot的指针传递(47)

    2024-03-15 04:50:04       19 阅读
  8. 2024.3.14 训练记录(16)

    2024-03-15 04:50:04       22 阅读
  9. 大模型prompt提示词如何调优?

    2024-03-15 04:50:04       20 阅读
  10. 【点云】激光点云建图评测

    2024-03-15 04:50:04       21 阅读
  11. OpenAI GPT-4.5 Turbo 泄露,六月或将发布

    2024-03-15 04:50:04       21 阅读
  12. 4. git 添加版本标签

    2024-03-15 04:50:04       23 阅读