C++面向对象的常见面试题目(一)

news2024/10/6 7:19:35

1. 面向对象的三大特征

(1)封装:隐藏对象的内部状态,只暴露必要的接口。

#include <iostream>
#include <string>

// 定义一个简单的类 Person
class Person {
private: // 私有成员,外部不可直接访问
    std::string name;
    int age;

public: // 公共方法,外部可以访问
    // 构造函数,用于初始化对象
    Person(std::string n, int a) {
        name = n;
        age = a;
    }

    // 公共方法,用于设置年龄
    void setAge(int a) {
        if (a > 0 && a < 150) { // 简单的年龄验证
            age = a;
        } else {
            std::cout << "Invalid age!" << std::endl;
        }
    }

    // 公共方法,用于获取姓名
    std::string getName() {
        return name;
    }

    // 公共方法,用于获取年龄
    int getAge() {
        return age;
    }
};

int main() {
    // 创建 Person 对象
    Person p1("Alice", 30);

    // 尝试直接访问私有成员(编译错误)
    // std::cout << p1.name << std::endl;

    // 使用公共方法设置年龄
    p1.setAge(35);

    // 使用公共方法获取姓名和年龄
    std::cout << "Name: " << p1.getName() << ", Age: " << p1.getAge() << std::endl;

    return 0;
}

(2)继承:无需修改原有类的情况下对功能实现拓展

#include <iostream>
#include <string>

// 定义一个基类 Animal
class Animal {
protected: // 受保护的成员,子类可以访问
    std::string name;

public:
    // 构造函数,初始化 Animal 的名称
    Animal(std::string n) : name(n) {}

    // 公共方法,用于输出 Animal 的声音
    void makeSound() const {
        std::cout << "Animal " << name << " makes a sound" << std::endl;
    }
};

// 定义一个派生类 Dog,继承自 Animal
class Dog : public Animal {
private:
    std::string breed;

public:
    // 构造函数,初始化 Dog 的名称和品种
    Dog(std::string n, std::string b) : Animal(n), breed(b) {}

    // 重写父类的 makeSound 方法
    void makeSound() const {
        std::cout << "Dog " << name << " barks" << std::endl;
    }

    // 新的方法,用于输出 Dog 的品种
    void showBreed() const {
        std::cout << "Dog " << name << " is of breed " << breed << std::endl;
    }
};

int main() {
    // 创建 Animal 对象
    Animal a("Generic");

    // 调用 Animal 的方法
    a.makeSound();

    // 创建 Dog 对象
    Dog d("Buddy", "Labrador");

    // 调用 Dog 的方法
    d.makeSound();
    d.showBreed();

    // 使用基类指针指向派生类对象,演示多态
    Animal* animalPtr = &d;
    animalPtr->makeSound(); // 调用的是 Dog 的 makeSound 方法

    return 0;
}

(3)多态:同一个函数名在不同对象中有不同的行为。通过时下接口重用增强可拓展性

多态分为2类,静态多态和动态多态。

静态多态:又称编译时多态。通过函数重载和运算符重载实现。

动态多态:又称运行时多态,通过虚函数和继承实现。

虚函数:在基类中声明函数为虚函数,派生类可以覆盖这些虚函数,从而实现不同的行为。在运行时,根据对象的实际类型决定调用哪个版本的函数,这种机制称为动态绑定或后期绑定。

// 静态多态
#include <iostream>

class MathTool {
public:
    // 计算整数平方
    int calculateSquare(int number) {
        return number * number;
    }

    // 计算浮点数平方
    double calculateSquare(double number) {
        return number * number;
    }
};

int main() {
    MathTool tool;

    // 静态多态示例:根据参数类型自动选择合适的方法
    std::cout << "整数5的平方是: " << tool.calculateSquare(5) << std::endl;      // 调用整数版本
    std::cout << "浮点数2.5的平方是: " << tool.calculateSquare(2.5) << std::endl; // 调用浮点数版本

    return 0;
}

// 动态多态
#include <iostream>

// 基类,含有虚函数
class Animal {
public:
    virtual ~Animal() {} // 虚析构函数,确保通过基类指针删除派生类对象时能正确调用派生类的析构函数
    virtual void makeSound() {
        std::cout << "Some animal makes a sound." << std::endl;
    }
};

// 派生类1
class Dog : public Animal {
public:
    void makeSound() override {
        std::cout << "Dog barks." << std::endl;
    }
};

// 派生类2
class Cat : public Animal {
public:
    void makeSound() override {
        std::cout << "Cat meows." << std::endl;
    }
};

int main() {
    Animal* animalPtr; // 基类指针

    // 动态分配内存给派生类对象
    animalPtr = new Dog();
    animalPtr->makeSound(); // 运行时动态决定调用Dog的makeSound()

    animalPtr = new Cat();
    animalPtr->makeSound(); // 运行时动态决定调用Cat的makeSound()

    delete animalPtr; // 由于基类析构函数为虚函数,可以安全删除派生类对象

    return 0;
}

2. 多态的实现原理

(1)静态多态

原理:函数名修饰。编辑器会根据函数的签名(包括参数类型、数量和顺序)对内部的函数名称进行编码,生成一个唯一的标识符。这个编码后的名称包含了足够的信息,使得编译器能够准确地区分不同的重载版本,即使它们的外部名称相同。

编译过程:

        预编译:把头文件中的函数声明拷贝到源文件,避免编译过程中语法分析找不到函数定义。

        编译:语法分析,同时进行符号汇总

        汇编:生成函数名到函数地址的映射,方便之后通过函数名照到函数定义从而执行函数。

        链接:讲过个文件的符号表汇总合并

早绑定:编译器编译时就已经确定对象调用的函数的地址。静态多态依赖于早绑定。

(2)动态多态

原理:虚函数重写。当一个类中声明了至少一个虚函数时,编译器会为该类生成一个虚函数表。这是一个存储虚函数指针的数组,每个指针指向类中相应虚函数的实现。含有虚函数的类实例在内存中除了包含数据成员外,还会有一个指向其所属类的虚函数表的指针(通常称为vptr)。这个vptr是在对象创建时由编译器自动初始化的。在派生类中,当你定义了一个与基类中虚函数同名且签名相同的函数时,这就是虚函数的重写。派生类的虚函数表中,对应的条目会存储派生类中该函数的地址,而非基类的。

虚函数重写:基类函数上加上virtual关键字,在派生类重写虚函数。运行时会根据对象的类型调用相应的函数。如果对象的类型是基类,那么调用基类的函数,如果对象的类型是派生类诶,则调用派生类的函数

晚绑定:程序运行时才确定对象调用的函数的地址。动态多态依赖于晚绑定。C++中,晚绑定是通过virtual关键字实现的。

3. 怎么解决菱形继承

因为c++有多重继承的特性,导致一个子类可能继承多个基类。这些基类可能继承自相同的基类,从而造成了菱形继承。

菱形继承的问题主要是数据冗余并且造成二义性。

二义性:当一个派生类(我们称之为D)从两个不同的基类(比如B1B2)继承,而这两个基类又都继承自同一个基类(A),那么在D中直接访问从A继承来的成员时,编译器无法确定应该使用B1继承的版本还是B2继承的版本。这种情况下,编译器会报错,指出存在二义性。

数据冗余:如果不使用虚拟继承(virtual关键字),每个派生类(B1B2)都会包含基类A的一个完整副本。因此,当D继承自B1B2时,它将拥有两份A的成员,导致数据冗余。这不仅浪费存储空间,还可能导致逻辑上的混乱,尤其是在修改这些成员时,可能会忘记更新所有副本,从而引发一致性问题。

使用虚继承可以解决菱形继承问题。在派生类对共同基类进行虚继承时,编译器会确保只有一份共同基类的实例,而不是每个中间类都有自己的一份。

4. 关键字override、final的作用

子类继承基类的虚函数之后,可能存在这样的问题:子类不希望这个函数会自己的子类被进一步重写;子类想重写一个新函数,但是错误的重写了基类虚函数;子类本意是重写基类的虚函数但是签名不一致导致重新构建了一个虚函数;子类希望自己绝后!但是没有办法从语法上完成这件事。

override可以指定子类的一个虚函数复写基类的一个虚函数;并且保证该重写的虚函数与子类的虚函数有相同的签名。

final可以指定某个虚函数不能在派生类中覆盖,或者某个类不能被派生。加在类前面就可以阻塞类的进一步派生。

5. C++类型推导的用法

C++类型推导主要有三个方面:auto、decltype、模板类型推导

(1)auto:用于推导变量的类型,通过强制声明一个变量的初始值,编译器会通过初始值进行推导类型

(2) decltype

用于推导表达式的类型,编译器只分析表达式类型而不参与运算

 6. C++11 中function、lambda、bind之间的关系

std::function是一个抽象了函数参数和函数返回值的类模板。它把任意函数包装成了一个对象,该对象可以保存传递以及复制。也可以进行动态绑定,只需要修改该对象,实现类似多态的效果

#include <iostream>
#include <functional>

// 函数对象(仿函数)
struct Adder {
    int operator()(int a, int b) const {
        return a + b;
    }
};

// 普通函数
int subtract(int a, int b) {
    return a - b;
}

// 成员函数
struct Calculator {
    int multiply(int a, int b) {
        return a * b;
    }
};

int main() {
    // 使用 std::function 声明不同类型的可调用对象
    std::function<int(int, int)> func1 = Adder();  // 函数对象
    std::function<int(int, int)> func2 = subtract; // 普通函数

    Calculator calc;
    std::function<int(Calculator&, int, int)> func3 = &Calculator::multiply;  // 成员函数

    // 调用并输出结果
    std::cout << "Adder result: " << func1(10, 5) << std::endl;
    std::cout << "Subtract result: " << func2(10, 5) << std::endl;
    std::cout << "Multiply result: " << func3(calc, 10, 5) << std::endl;

    return 0;
}

lambda表达式:一种方面的创建匿名函数的语法糖

原理:编译的时候把表达式转变为一个函数对象,然后根据表达式惨呼列表重载operate ()

demo:

// 简单表达式
auto sayHello = []{ std::cout << "Hello, World!\n"; };
sayHello();

// 捕获外部变量
int x = 10, y = 20;
auto incrementAndPrint = [x, &y]() {
    x++; // 按值捕获,不会改变外部x
    y++; // 按引用捕获,会改变外部y
    std::cout << "x: " << x << ", y: " << y << "\n";
};
incrementAndPrint(); // 输出:x: 11, y: 21
std::cout << "After: x=" << x << ", y=" << y << "\n"; // 输出:x=10, y=21


// 指定返回类型
int x = 10, y = 20;
auto incrementAndPrint = [x, &y]() {
    x++; // 按值捕获,不会改变外部x
    y++; // 按引用捕获,会改变外部y
    std::cout << "x: " << x << ", y: " << y << "\n";
};
incrementAndPrint(); // 输出:x: 11, y: 21
std::cout << "After: x=" << x << ", y=" << y << "\n"; // 输出:x=10, y=21


std::bind:用来通过绑定函数以及函数参数的方式生成函数对象的模板函数,提供占位符,实现灵活的参数绑定

#include <iostream>
#include <functional>

// 通用加法函数
int add(int x, int y) {
    return x + y;
}

int main() {
    // 使用std::bind固定add的第一个参数为5
    auto addFive = std::bind(add, 5, std::placeholders::_1);

    // 现在addFive是一个新的可调用对象,只需要一个参数
    std::cout << "Adding 5 to 3 gives: " << addFive(3) << '\n'; // 输出: 8
    std::cout << "Adding 5 to 10 gives: " << addFive(10) << '\n'; // 输出: 15

    return 0;
}

总结:function 用来描述函数对象的类型;lambda 表达式用来生成函数对象(可以访问外部变量的匿名函数);bind 也是用来生成函数对象(函数和参数进行绑定生成函数对象);

这是一条吃饭博客,由挨踢零声赞助。学C/C++就找挨踢零声,加入挨踢零声,面试不挨踢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1905116.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【LeetCode】十三、分治法:多数元素 + 最大子序列和

文章目录 1、分治法2、leetcode169&#xff1a;多数元素3、leetcode53&#xff1a;最大子序和 1、分治法 分治一般都搭配递归使用&#xff1a; 用分治法的一个应用——归并排序&#xff1a;将一组数不停的一分为二&#xff0c;直到分到每组只有一个数的时候 分到每组只有一个数…

【qt】TCP 服务端怎么收到信息?

上一节,我已经讲了,TCP的监听,是基于上一节的,不知道的可以看看. 当我们的TCP 服务器 有 客户端请求连接的时候,会发出一个信号newConnection(). 在TCP服务端与客户端的通信中,我们需要使用到套接字 QTcpSocket类. 套接字相当于是网络通信的接口,服务段和客户端都要通过它进行通…

【网络安全】实验一(网络拓扑环境的搭建)

一、本次实验的实验目的 学习利用 VMware 创建虚拟环境 学习利用 VMware 搭建各自网络拓扑环境 二、创建虚拟机 三、克隆虚拟机 选择克隆的系统必须处于关机状态。 方法一&#xff1a; 方法二&#xff1a; 需要修改克隆计算机的名字&#xff0c;避免产生冲突。 四、按照要求完…

JavaWeb—js(1)

概述 javascript简称js 1.js的特点&#xff0c;基本语法&#xff0c;对象等 2.js的功能&#xff0c;用来操作页面元素&#xff0c;用户交互&#xff0c;浏览器交互&#xff0c;网络数据的请求 后期使用的更多的是js的框架 jquery.js vue.js react.js JS相关概念 javascrip…

python使用pywebview集成vue3和element-plus开发桌面系统框架

随着web技术越来越成熟&#xff0c;就连QQ的windows客户端都用web技术来开发&#xff0c;所以在未来&#xff0c;web技术来开发windows桌面软件也会越来越多&#xff0c;所以在此发展驱动之下&#xff0c;将最近流程的python与web技术相结合&#xff0c;使用vue3和element-plus…

蚁剑编码器编写——中篇

看第二个示例 hex编码 木马 <?php foreach($_POST as $k > $v){$_POST[$k]pack("H*", $v);} eval($_POST[ant]); ?>pack(“H*”, $v)是将 $v 转换为为二进制&#xff0c;也就是ASCII解码 编码器 module.exports (pwd, data) > {let ret {};for (…

C/C++ 代码注释规范及 doxygen 工具

参考 谷歌项目风格指南——注释 C doxygen 风格注释示例 ubuntu20 中 doxygen 文档生成 doxygen 官方文档 在 /Doxygen/Special Command/ 章节介绍 doxygen 的关键字 注释说明 注释的目的是提高代码的可读性与可维护性。 C 风格注释 // 单行注释/* 多行注释 */ C 风格注…

ESP32 步进电机精准控制:打造高精度 DIY 写字机器人,实现流畅书写体验

摘要: 想让你的 ESP32 不再仅仅是控制灯光的工具吗&#xff1f; 本文将带你使用 ESP32 开发板、步进电机和简单的机械结构打造一个能够自动写字的机器人。我们将深入浅出地讲解硬件连接、软件代码以及控制逻辑&#xff0c;并提供完整的项目代码和电路图&#xff0c;即使是 Ardu…

AI与编程:一个学生的心路历程与思考

前言 大家好&#xff0c;本人是在一个在校的大学生&#xff0c;方向是前端语言。爱好是码代码和看一点小新闻&#xff0c;游戏也是喜爱的。其实本篇文章的想法是源于网上一些人对AI以及对前端的看法&#xff0c;看完网上的评论后我也是有感而发。本篇文章的讨论中心也是围绕着A…

我国网络安全领域有哪些法律法规?主要内容是什么?

1. 背景介绍 网络信息安全方面的法规在全球范围内都有相应的立法&#xff0c;我们主要的立法有《网络安全法》、《密码法》、《数据安全法》以及《个人信息保护法》。当前也有一些相关的条例和管理办法&#xff0c;接下来就为大家一一介绍。 2. 法规介绍 在中国&#xff0c;…

昇思25天学习打卡营第14天|基于MindNLP的文本解码原理

基于MindNLP的文本解码原理 文本解码 文本解码是自然语言处理中的一个关键步骤,特别是在任务如机器翻译、文本摘要、自动回复生成等领域。解码过程涉及将编码器(如语言模型、翻译模型等)的输出转换为可读的文本序列。以下是一些常见的文本解码方法和原理: 1. 自回归解码:…

2.3.2 主程序和外部IO交互 (文件映射方式)----C#调用范例

2.3.2 主程序和外部IO交互 &#xff08;文件映射方式&#xff09;----C#调用范例 效果显示 1 说明 1 .1 Test_IOServer是64bit 程序&#xff0c; BD_SharedIOServerd.dll 在 /Debug文件夹中 1 .2 Test_IOServer是32bit 程序&#xff0c; BD_SharedIOClientd.dll (32bit&#…

安卓的组件

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 目录 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌…

六、数据可视化—flask框架入门(爬虫及数据可视化)

六、数据可视化—flask框架入门&#xff08;爬虫及数据可视化&#xff09; 1&#xff0c;数据可视化简介2&#xff0c;flask&#xff08;1&#xff09;创建flask项目&#xff08;2&#xff09;开启debug模式&#xff08;3&#xff09;通过访问路径传递参数&#xff08;4&#x…

【鸿蒙学习笔记】页面布局

官方文档&#xff1a;布局概述 常见页面结构图 布局元素的组成 线性布局&#xff08;Row、Column&#xff09; 了解思路即可&#xff0c;更多样例去看官方文档 Entry Component struct PracExample {build() {Column() {Column({ space: 20 }) {Text(space: 20).fontSize(15)…

C语言 指针和数组——指针和二维数组之间的关系

目录 换个角度看二维数组 指向二维数组的行指针 按行指针访问二维数组元素 再换一个角度看二维数组 按列指针访问二维数组元素 二维数组作函数参数 指向二维数组的行指针作函数参数 指向二维数组的列指针作函数参数​编辑 用const保护你传给函数的数据 小结 换个角度看…

Mysql explain语句详解与实例展示

首先简单介绍sql&#xff1a; SQL语言共分为四大类&#xff1a;数据查询语言DQL&#xff0c;数据操纵语言DML&#xff0c;数据定义语言DDL&#xff0c;数据控制语言DCL。 1. 数据查询语言DQL 数据查询语言DQL基本结构是由SELECT子句&#xff0c;FROM子句&#xff0c;WHERE子句…

Kafka(一)基础介绍

一&#xff0c;Kafka集群 一个典型的 Kafka 体系架构包括若Producer、Broker、Consumer&#xff0c;以及一个ZooKeeper集群&#xff0c;如图所示。 ZooKeeper&#xff1a;Kafka负责集群元数据的管理、控制器的选举等操作的&#xff1b; Producer&#xff1a;将消息发送到Broker…

k8s学习之cobra命令库学习

1.前言 打开k8s代码的时候&#xff0c;我发现基本上那几个核心服务都是使用cobra库作为命令行处理的能力。因此&#xff0c;为了对代码之后的代码学习的有比较深入的理解&#xff0c;因此先基于这个库写个demo&#xff0c;加深对这个库的一些理解吧 2.cobra库的基本简介 Git…

《昇思 25 天学习打卡营第 11 天 | ResNet50 图像分类 》

《昇思 25 天学习打卡营第 11 天 | ResNet50 图像分类 》 活动地址&#xff1a;https://xihe.mindspore.cn/events/mindspore-training-camp 签名&#xff1a;Sam9029 计算机视觉-图像分类&#xff0c;很感兴趣 且今日精神颇佳&#xff0c;一个字&#xff0c;学啊 上一节&…