嵌入式面试高频——C++

C++三大特性

C++是一种高效、灵活的编程语言,它扩展了C语言的功能,增加了面向对象编程(OOP)的特性。C++的三大特性通常指的是:封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)。这三个特性是面向对象编程(OOP)的核心概念,它们共同支持了C++的强大功能和灵活性。

  1. 封装:封装是指将对象的属性和方法绑定在一起,形成一个独立的、封闭的单元。外部只能通过对象提供的公共接口来访问或操作对象的内部状态,而无法直接访问或修改对象的数据。这样可以保证对象的内部状态不受外部干扰,从而提高了程序的安全性和可靠性,简化了代码的调用方式。
  2. 继承:通过继承机制,一个类可以从另一个类中继承某些属性和方法,并在此基础上添加新的属性和方法,从而避免了重复编写代码的冗余,提高了代码的可重用性和可维护性。
  3. 多态:多态是指同一个消息可以被不同的对象解释执行,即不同的对象对同一消息作出不同的响应。具体来说,多态可以通过虚函数和模板等机制实现。通过多态,可以使代码更加灵活、可扩展,同时也能够使程序更易读懂和维护。

C++的这三个特性共同构成了其面向对象编程的基础,使得C++成为一种功能强大、灵活多变的编程语言。

C++和C的区别是什么?

C++是C的超集,也就是说,C++包括了C的所有基础特性,并且还增加了一些新的特性。下面列举一些C和C++之间的主要区别:

  • 面向对象编程

C++ 是一种面向对象的编程语言,而 C 不是。因此,C++ 支持类、继承、封装、多态等一系列面向对象的概念和特性,这些能力使 C++ 更加灵活和强大。

  • 标准库

C++ 标准库比 C 标准库更加完善和强大。C++ 标准库包括了很多容器类,如 vectormapset 等,以及输入输出流、字符串处理等常用功能。这些库函数可以在许多情况下提高开发效率。

  • 命名空间

C++ 引入了命名空间的概念,可以避免函数命名相同的冲突。使用命名空间可以将代码按照逻辑分组,并更好地组织代码。

  • 异常处理

C++ 支持异常处理机制,这个机制可以增强程序的容错性和可靠性。当程序发生异常时,可以抛出异常并在可控范围内进行处理,避免程序崩溃。而 C 不支持异常处理机制。

  • 运算符重载

C++ 允许对运算符进行重载,可以使得运算符在处理特定类型的数据时更具有描述性。而 C 不支持运算符重载。

什么是指针?

指针是C++中的一种数据类型,指针变量存储了一个内存地址,该地址指向某个变量或者对象。指针可以用来访问和修改内存中的数据,同时也可以通过指针来传递参数和返回值。对于C++程序员来说,精通指针的使用是非常重要的

重写和重载的区别

重载函数:重载则指的是在同一个作用域内声明几个同名但是参数列表不同的函数。通过函数名相同但参数类型、个数或顺序的不同,可以让多个函数具有不同的行为。(同名同域不同参数列表,不可以通过返回值来判断函数是否重写。)

重写函数:重写指的是在派生类中重新定义基类的虚函数的行为。当基类中的某个虚函数在派生类中被重新定义时,如果派生类对象调用该函数,则会覆盖掉基类中的实现,执行派生类中的实现代码。在进行对象的多态性转换时,重写非常重要。(可以使用override来检测函数是否被重写)。

主要区别:重写是通过派生类重新定义基类虚函数的行为,以实现运行时多态性;而重载是在同一作用域内声明几个相同名称的函数,以实现编译时多态性。

什么是引用?

引用也是C++中的一种数据类型,它提供了一种简洁而高效的方式来操作变量和对象,而不需要拷贝它们本身。引用被视为原变量的一个别名,其操作类似于指针,但是引用不能被赋值为NULL,也不能进行指针运算。

C/C++引用和指针的区别?

本质: 引用是别名,指针是地址 但是引用的本质内部实现其实是指针常量

具体方面

  • 从现象上看 指针在运行时可以改变所指向的值,而引用一旦与某个对象绑定后就不再改变。意思是:指针可以被重新赋值以指向另一个对象,但是引用则总是在初始化时被指定的对象,以后不能改变,但是指向的内容可以改变。(下面是这个规则的理解)

  • 内存分配 程序为指针变量分配区域,而不为引用分配内存区域。因为引用声明时必须初始化,从而指向一个已经存在的对象,引用不能为空值

  • 编译

    程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。符号表生成后就不会再改,因此指针可以改变指向的对象(指针变量中的值可以改),而引用对象不能改。这是使用指针不安全而使用引用安全的主要原因。从某种意义上来说引用可以被认为是不能改变的指针。

  • 效率上 其实两者的效率是一致的,因为在底层中,指针和引用的参数都指向的是一个地址。 在高级编程语言中,因为用*传参可能会指向空的地址或者错误的地址,所以要时时判断参数是否为空,导致降低效率。 而用&传参数,则参数不可能为空或者错误地址,这也算稍微提升了一些效率。

  • 自增意义不同

    引用自增是对值自增,指针自增是对地址的自增

内联函数和普通函数有什么区别?

        内联函数和普通函数的区别在于是否进行了“内联优化”。内联函数是一种特殊的函数,编译器会在编译时将其整个函数体插入到调用该函数的地方,从而节省了函数调用的开销。普通函数则需要在调用时进行函数栈的压栈和出栈操作,这样会带来额外的时间和空间开销。因此,对于简单的函数或者频繁被调用的函数,我们可以考虑使用内联函数来提高程序的性能。

        可以使用inline关键字来定义一个内联函数

如何避免野指针?

野指针是指指向已经被释放或者无效的内存空间的指针,这是 C++ 中常见的一个程序错误。当我们访问野指针时,程序会出现不可预期的行为,甚至崩溃。

为了避免野指针,我们可以采取以下措施:

  • 在指针使用前初始化

在定义一个指针变量的时候,我们应该立即将其初始化为一个有效的地址。如果不能确定指针的初始值,可以将其初始化为 nullptr 或 0,避免野指针的产生。

int* p = nullptr; // 初始化为空指针
  • 在指针使用后及时置空

当指针变量不再使用时,我们应该将其置为空指针,防止误用。这样可以有效地避免产生野指针。

int* p = new int;
*p = 10;
delete p;
p = nullptr;  // 置空指针,避免野指针产生
  • 不要重复释放已经释放的内存

在释放指针所指向的内存空间之后,我们应该将该指针赋值为 NULL 或 nullptr,以防止该指针被误用。

  • 避免使用悬空指针

当一个指针变量超出了其所在作用域或者被删除时,它就成为了“悬空指针”,这是一种常见的野指针。我们应该避免使用悬空指针,同时要注意存储指针所指向的对象生命周期的问题。

总之,避免野指针是 C++ 中一个很重要的问题,可以通过初始化、及时置空、避免释放已经释放的内存、避免使用悬空指针等措施来避免产生野指针,从而保证程序的正确性和稳定性。

C++多态?

C++多态是指在继承关系中,子类可以重写父类的虚函数,从而使得一个指向子类对象的指针能够调用子类的函数而不是父类的函数。其底层原理涉及到虚函数表、虚指针等概念。虚函数表是一个存储类的虚函数地址的数据结构,每个包含虚函数的类都有自己的虚函数表。

虚指针是一个指向虚函数表的指针,每个含有虚函数的对象都有一个虚指针。虚指针的设置是由编译器来完成的,当一个类中含有虚函数时,编译器就会在类中增加一个虚指针来指向虚函数表,每个对象都有一个虚指针,指向它所属的类的虚函数表。

通过虚函数表和虚指针,使得程序能够在运行时根据对象的实际类型来确定调用哪个函数。

多态分为两种:

静态多态:地址早就绑定,在编译期间就已经绑定,比如函数重载

动态多态:地址没有绑定,在运行期间才绑定地址

什么是虚函数?

虚函数是C++中的一种特殊函数,它可以实现多态性。当一个类中包含至少一个虚函数时,它就被称为虚类或抽象类。这些虚函数由子类重写,使得它们可以根据需要对基类的行为进行扩展和修改。通过使用虚函数可以实现动态绑定和运行时多态。

基类的析构函数为何要声明为虚函数?

        C++基类的析构函数声明为虚函数是为了确保在通过基类的指针或引用删除派生类对象时,可以正确地释放派生类对象所占用的内存。如果基类的析构函数不是虚函数,则在这种情况下只会调用基类的析构函数,而不会调用派生类的析构函数,从而可能导致内存泄漏和未定义行为。

        因此,将基类的析构函数声明为虚函数是一种良好的编程实践,可以确保在多态情况下正确地释放内存。

数组和指针的区别?

它们虽然在某些方面相似,但是有很多区别。

  • 内存用法

数组名是一个指向数组首元素的常量指针,它存储的是数组首元素的地址。而指针是一个变量,它存储的是某个对象的地址。

  • 操作的灵活性

数组名是一个常量指针,不能修改,而指针可以被重新赋值,指向其他对象。因此使用指针比使用数组名更加灵活,可以在运行时动态确定指向的对象。

  • 大小

数组名的大小等于数组中元素的总数乘以每个元素的大小,而指针的大小是与系统架构有关的,通常是一个字长(word length)。

  • 函数参数传递

如果将数组名作为函数参数传递,实际上传递的是一个指向数组首元素的指针。而如果将指针作为函数参数传递,可以方便地修改指针所指向的对象。

  • 数组解引用

可以通过数组下标访问数组元素,也可以使用指针进行访问,但是需要注意的是,使用指针访问数组元素需要先将指针解引用,即使用 * 运算符。例如:*p 表示 p 指向的对象。

C/C++内存有哪几种类型?

内存分为堆、栈、程序代码区、全局/静态存储区和常量存储区。

C++中还有自由存储区 ,就是那些由malloc等分配的内存块,他和堆是十分相似的,不过它是用free来结束自己的生命的。

全局区(静态区)(static)—编译器编译时即分配内存。全局变量和静态变量的存储是放在一块的。对于C语言初始化的全局变量和静态变量在一块区域.data, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域.bss。而C++则没有这个区别 - 程序结束后由系统释放

C++堆和栈的区别?

堆存放动态分配的对象,生存期由程序控制;

栈用来保存定义在函数内的非static对象,仅在其定义的程序块运行时才存在;

静态内存用来保存static对象,类static数据成员以及定义在任何函数外部的变量,static对象在使用之前分配,程序结束时销毁;栈和静态内存的对象由编译器自动创建和销毁(c++)。

C++函数调用的过程?

C++函数调用是编程中常见的一个操作,其过程可以分为以下几个阶段:

  • 函数调用前的准备工作

在函数调用之前,需要进行一些准备工作。首先,需要将函数的参数压入栈中,以向函数传递参数。其次,需要保存当前函数的返回地址,以便在函数调用结束后返回到正确的位置。

  • 进入函数调用

调用函数时,程序会跳转到函数代码的入口点。此时,程序会为函数创建一个新的栈帧,用于存储函数的局部变量、返回值等信息。栈帧包含了多个部分,例如函数参数、局部变量、返回地址等等。函数参数通过栈传递,在栈的顶部。局部变量则被分配在栈帧的底部。返回地址保存在栈帧中,这样函数调用结束后程序才能正确返回。

  • 函数内部处理

函数内部会执行具体的操作,包括参数的读取、局部变量的声明和使用、逻辑计算、循环或者条件语句等等。函数将根据其实现过程来计算参数并进行其他操作,然后返回一个结果,该结果通常被保存在寄存器中。

  • 函数返回

当函数执行完毕时,需要将返回值存储,并恢复主函数的栈帧及处理状态。函数返回时,会跳转回调用它的函数的位置。此时,程序会弹出函数栈帧,将返回值传递给调用者,并恢复调用者的寄存器和栈。

左值和右值

左值是可以寻址的,有名字的,非临时的变量或表达式;右值是不能寻址的,没有名字的,临时的,生命周期在某个表达式之内的变量或表达式。

参考:【C++】C++11——左右值|右值引用|移动语义|完美转发_左值和右值的区别 c++11-CSDN博客

http中的301、302、404、200分别代表什么意思

HTTP状态码(HTTP Status Codes)是服务器用来告诉客户端(如浏览器)请求的结果的。这些状态码以数字形式出现,并分为五类,分别用数字的第一位来标识:

  • 1xx(信息性状态码):表示接收到请求,正在处理。
  • 2xx(成功状态码):表示请求已成功被服务器接收、理解、并接受。
  • 3xx(重定向状态码):表示需要客户端采取进一步的操作才能完成请求。
  • 4xx(客户端错误状态码):表示请求包含语法错误或无法完成请求。
  • 5xx(服务器错误状态码):表示服务器在处理请求的过程中遇到了错误。

其中:

        301 Moved Permanently:永久重定向(跳转);

        302 Found(早期HTTP/1.1规范中该状态码的原始定义是“临时移动”(临时跳转);

        404 Not Found:服务器无法根据客户端的请求找到资源(网页)。(找不到页面);

        200 OK:请求成功。请求所希望的响应头或数据体将随此响应返回。(正常访问);

说一声C++中那些函数不能是虚函数

1、普通函数(非成员函数):只能被重载,不能被重写,而且编译器会在编译的时候绑定函数地址;全局函数(普通函数)不是类的成员函数,因此它们也不能是虚函数。

2、构造函数:构造函数在对象创建时被自动调用,用于初始化对象。由于构造函数在对象完全形成之前就被调用,因此它无法调用虚函数(因为此时对象的虚函数表可能还未被初始化)。

3、内联函数:内联函数就是为了函数代码直接在程序中展开,节省调用函数花费的代价,而虚函数是为了继承后子类能够准确的执行自己的动作,这两着是不能统一的。

4、静态成员函数:静态成员函数属于类本身,而不是类的某个对象。因此,它们没有this指针,也不能通过对象的实例来调用(尽管可以通过类名或对象名来调用)。由于静态成员函数不与任何特定对象关联,所以它们不能是虚函数。

5、友元函数:友元函数不是类的成员函数,但它可以访问类的私有和保护成员。由于友元函数不是类的成员函数,它们也不能是虚函数。

谈谈你对set,multiset ,map,multimap的理解

1. set

set是C++标准模板库(STL)中的一个关联容器,用于存储唯一元素的集合。它的主要特性包括:

  • 唯一性set中的元素是唯一的,即不允许有重复的元素。
  • 有序性set中的元素默认按照升序排列,这是通过内部使用红黑树实现的。
  • 键即值:在set中,键(key)和值(value)是相同的,即元素既是键也是值。
  • 快速查找:由于set内部是有序的,因此可以高效地支持查找操作,查找的时间复杂度为O(log n)。

2. multiset

multisetset类似,但它允许存储重复的元素。multiset的主要特性包括:

  • 非唯一性:与set不同,multiset中的元素可以重复。
  • 有序性multiset同样保持元素的有序性,也是通过红黑树实现的。
  • 键即值:在multiset中,元素既是键也是值。
  • 操作类似multiset的大部分操作与set相同,如插入、删除和查找,但由于允许重复元素,因此在处理重复元素时会有不同的行为。

3. map

map是C++ STL中的另一个关联容器,它存储的元素是键值对(key-value pair)。map的主要特性包括:

  • 唯一键map中的键是唯一的,即不允许有重复的键。
  • 有序性map中的元素(实际上是键值对)默认按照键的升序排列。
  • 快速查找:由于map内部是有序的,并且基于红黑树实现,因此可以高效地支持通过键来查找值,查找的时间复杂度为O(log n)。
  • 键与值:在map中,键是唯一的,但值可以重复。每个键都映射到一个特定的值。

4. multimap

multimapmap类似,但它允许存储具有相同键的多个元素(即键值对)。multimap的主要特性包括:

  • 非唯一键:与map不同,multimap中的键可以重复,即可以有多个键值对具有相同的键。
  • 有序性multimap中的元素(键值对)仍然保持有序性,按照键的升序排列。
  • 键与值:在multimap中,键可以重复,但每个键值对都是唯一的。这意味着同一个键可以映射到多个不同的值。
  • 操作类似multimap的大部分操作与map相同,但由于允许重复键,因此在处理具有相同键的多个元素时会有不同的行为。

总结

容器类型是否允许重复元素类型排序方式查找效率
set不允许键即值升序O(log n)
multiset允许键即值升序O(log n)
map不允许(键)键值对键的升序O(log n)
multimap允许(键)键值对键的升序O(log n)

这些容器都提供了丰富的成员函数来支持插入、删除、查找和遍历等操作,是C++编程中处理有序集合和键值对数据的强大工具。

谈谈你对C++11中的智能指针的理解

传统指针存在的问题:

1、需要手动管理内存

2、容易发送内存泄漏(忘记释放,出现异常等)

3、释放后容易尝试野指针

智能指针就是为了解决传统指针存在的问题

  1. std::unique_ptr
    • 特性:提供独占所有权的语义,即同一时间只能有一个std::unique_ptr拥有指向对象的所有权。当std::unique_ptr被销毁时,它所管理的对象也会被销毁。
    • 用途:适用于需要严格所有权转移的情况,如对象的动态分配和释放。
    • 限制:不能复制,但可以通过std::move进行移动语义的转移。
  2. std::shared_ptr
    • 特性:允许多个智能指针共享同一个对象的所有权,通过引用计数来管理对象的生命周期。当最后一个std::shared_ptr被销毁时,对象才会被释放。
    • 用途:适用于多个对象共享同一资源的情况,可以避免手动管理资源释放带来的复杂性和风险。
    • 注意:要避免循环引用,因为这会导致内存无法释放。
  3. std::weak_ptr
    • 特性:不增加对象的引用计数,是一种弱引用型指针。它通常用于辅助std::shared_ptr,解决std::shared_ptr的循环引用问题。
    • 用途:主要用于解决std::shared_ptr的循环引用,防止内存泄漏。可以通过std::weak_ptrlock方法获取一个有效的std::shared_ptr,用于访问对象
  4. std::auto_ptr:属于c++98标准,在c++11中已经不推荐使用了(有缺陷,比如说不能使用数组)

简单介绍一下类里面的this指针:

它指向当前对象的地址。在类的成员函数内部,this 指针被隐式地使用,以便成员函数能够访问调用它的对象的成员(包括成员变量和成员函数)。尽管你通常不需要显式地使用 this 指针,但在某些情况下,它非常有用。

this指针,不可以更改指向,但是可以更改指向的内容。

1;this指针指向的是当前对象的地址;

2、this指针只能在成员函数中使用;

3、this指针是作为成员函数的隐藏参数;

4、友元函数和静态函数不能使用this指针。

        

谈谈你对深拷贝和浅拷贝的理解:

深拷贝和浅拷贝的主要区别在于是否重新申请了堆区,如果重新申请了堆区则是深拷贝。

每个类都自带一个默认的浅拷贝函数,当类中含有堆区数据的时候则是需要使用深拷贝构造;给堆区数据重新申请一遍空间,可以避免内存泄漏的问题。

c++中的const

在C++中,const关键字是一个非常有用且强大的特性,它主要用于定义常量,即一旦初始化后其值就不能被改变的量。const可以应用于变量、指针、成员函数等,提供了对程序数据和行为的更严格控制。

1. 常量变量

使用const修饰的变量称为常量变量,其值在初始化之后不能被修改。

2. 指针和const

const与指针一起使用时,可以指定指针本身是常量,或者指针指向的内容是常量,或者两者都是。

3. 成员函数中的const

在成员函数声明的末尾加上const关键字,表示该成员函数不会修改类的任何成员变量(静态成员变量除外,因为它们不属于类的任何特定对象)。

补充:const修饰的对象,只能调用const修饰的函数,以及static修饰的函数。

c++中的static

在C++中,static关键字具有多种用途,主要根据它被应用的上下文(如变量、函数、类成员等)而变化。以下是static关键字的一些主要用途:

1. 局部静态变量

static用于函数内部的变量时,它会使该变量的生命周期贯穿整个程序执行期间,但其作用域仍限制在声明它的函数内部。这意味着该变量在函数调用之间保持其值不变,而不是在每次函数调用时都重新初始化。

2. 全局静态变量

在全局或命名空间作用域中,static关键字限制变量的链接性(linkage)为内部链接(internal linkage),即该变量只能在其被声明的文件内部访问。这有助于避免命名冲突,因为它阻止了其他文件访问该变量。

3. 静态函数

静态函数也是指其链接性被限制为内部链接的函数。这意味着该函数只能在定义它的文件内部被调用。这有助于隐藏函数的实现细节,减少命名冲突。

4. 类静态成员

在类中,static用于声明类的静态成员变量和静态成员函数。静态成员变量属于类本身,而不是类的任何特定对象。因此,所有对象共享同一个静态成员变量的实例。静态成员函数也只能访问类的静态成员变量和其他静态成员函数,因为它们不依赖于类的任何特定对象实例。

什么是继承?继承有什么优缺点

C++中的继承

在C++中,继承是一种面向对象编程的特性,它允许一个类(称为子类或派生类)继承另一个类(称为父类或基类)的属性和方法。子类可以重用父类的代码,减少重复编写代码的工作量,并且可以添加自己的属性和方法来扩展父类的功能。可以提高代码的复用率和执行效率

但是继承是有侵虐性的,只要继承,子类就会继承父类的除了构造、析构运算符重载、友元的所有属性和方法,让子类有了更多的约束。

当父类的属性更改的时候,子类的属性会随之更改,如果没有标准的规范,这种修改可能会导致整个项目崩盘。

虚基类的概念

多继承(Multiple Inheritance)
是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员。尽管概念上非常简单,但是多个基类的相互交织可能会带来错综复杂的设计问题,命名冲突就是不可回避的一个。

注意: 多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如典型的是菱形继承。

如果将公共基类说明为虚基类。那么,对同一个虚基类的构造函数只调用一次,这样从不同的路径继承的虚基类的成员在内存中就只拥有一个拷贝。从而解决了以上的二义性问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值