首页 > 编程语言 >我总结的一些 C++ 高频面试题(收藏)

我总结的一些 C++ 高频面试题(收藏)

时间:2023-04-25 17:48:22浏览次数:53  
标签:面试题 函数 int C++ constexpr 内存 Message 高频

extern “C”

extern 是C/C++ 语言中表明函数和全局变量作用范围的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。

被extern "C" 修饰的变量和函数是按照C语言方式编译和连接的。

extern “C” 这个声明的真实目的:解决名字匹配问题,实现C++与C的混合编程。

const 修饰指针的三种效果

const int*p=&a;

当把 const 放最前面的时候,它修饰的就是 *p,那么 p 就不可变。p 表示的是指针变量 p 所指向的内存单元里面的内容,此时这个内容不可变。其他的都可变,如 p 中存放的是指向的内存单元的地址,这个地址可变,即 p 的指向可变。但指向谁,谁的内容就不可变。

int*const p=&a;

此时 const 修饰的是 p,所以 p 中存放的内存单元的地址不可变,而内存单元中的内容可变。即 p 的指向不可变,p 所指向的内存单元的内容可变。

const int*const p=&a;

此时 *p 和 p 都被修饰了,那么 p 中存放的内存单元的地址和内存单元中的内容都不可变。

http://c.biancheng.net/view/218.html

c++ 指针和引用的区别

指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来的变量实质上是同一个东西,只不过是原变量的一个别名而已。

malloc/free 与 new/delete

如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用delete释放“malloc申请的动态内存”,理论上讲程序不会出错,但是该程序的可读性很差。所以new/delete,malloc/free必须配对使用。

C++ 虚函数表

每个包含了虚函数的类都包含一个虚表。

一个类继承了包含虚函数的基类,那么这个类也拥有自己的虚表。

虚表是一个指针数组,其元素是虚函数的指针,每个元素对应一个虚函数的函数指针。

虚表是属于类的,而不是属于某个具体的对象,一个类只需要一个虚表即可。同一个类的所有对象都使用同一个虚表。

为了指定对象的虚表,对象内部包含一个虚表的指针,来指向自己所使用的虚表。

vector push_back 和 emplace_back 区别

区别主要是针对对象而言,emplace_back 可以减少一次拷贝构造函数的调用。

vector<bool>不是存储bool类型元素的容器

//创建一个 vector<bool> 容器

vector<bool>cont{0,1};

//试图将指针 p 指向 cont 容器中第一个元素

bool *p = &cont[0];

以上代码无法编译通过。

为了节省空间,vector底层在存储各个 bool 类型值时,每个 bool 值都只使用一个比特位(二进制位)来存储。也就是说在 vector底层,一个字节可以存储 8 个 bool 类型值。

deque 容器的存储结构

vector 容器采用连续的线性空间不同,deque 容器存储数据的空间是由一段一段等长的连续空间构成,各段空间之间并不一定是连续的,可以位于在内存的不同区域。

当 deque 容器需要在头部或尾部增加存储空间时,它会申请一段新的连续空间,同时在 map 数组的开头或结尾添加指向该空间的指针,由此该空间就串接到了 deque 容器的头部或尾部。

STL map 和 unordered_map 的区别

map 是一种有序的容器,底层是用红黑树实现的,它可以做到 O(logn) 时间完成查找、插入、删除元素的操作。

unordered_map是一种无序的容器,底层是用哈希表实现的(哈希表-维基百科),哈希表最大的优点是把数据的查找和存储时间都大大降低。

C++ RVO Return Value Optimization

#include <stdio.h>

 

struct Message

{

    Message()

    { 

        printf("Message::Message() is called\n"); 

    }

    Message(const Message &)

    {

        printf("Message::Message(const Message &msg) is called\n");

    }

    Message& operator=(const Message &)

    {

        printf("Message::operator=(const Message &) is called\n");

    }

    ~Message()

    {

        printf("Message::~Message() is called\n");

    }

    int a;

    int b;

    int c;

    int d;

    int e;

    int f;

};

 

Message getMessage()

{

    Message result;

    result.a = 0x11111111;

 

    return result;

}

 

int main()

{

    Message msg = getMessage();

    return 0;

}

你认为运行时会输出什么呢?是不是这样:

Message::Message() is called

Message::Message(const Message &msg) is called

Message::~Message() is called

Message::~Message() is called

RVO 优化之后:

Message::Message() is called

Message::~Message() is called

它存在的目的是优化掉不必要的拷贝构造函数的调用,基本手段是直接将返回的对象构造在调用者栈帧上。

STL unordered_map 原理

C++ STL 标准库中,不仅是 unordered_map 容器,所有无序容器的底层实现都采用的是哈希表存储结构。存储结构:数组+链表,数组用来保存链表的头指针,各个键值对存储在链表的结点中

当有新键值对存储到无序容器中时,整个存储过程分为如下几步:

将该键值对中键的值带入设计好的哈希函数,会得到一个哈希值(一个整数,用 H 表示);

将 H 和无序容器拥有桶的数量 n 做整除运算(即 H % n),该结果即表示应将此键值对存储到的桶的编号;

建立一个新节点存储此键值对,同时将该节点链接到相应编号的桶上。

Android HIDL

AIDL常用于连接App和Framework,HIDL则是用来连接Framework和HAL,AIDL使用Binder通信,HIDL则使用HwBinder通信,他们都是通过Binder驱动完成通信,只不过两个Binder域不一样。

将 Framework 与 HAL 独立出来,简化Android系统升级的影响与难度。

app -> java framework -> jni -> hidl service -> HAL

Android Malloc Debug 原理

Malloc Debug 可用于检查内存泄漏、内存越界、double free、use after free

  • 代理内存分配和释放;
  • 额外申请一部分内存用于保存内存的信息(Header)和设置内存边界(guard);
  • 调用栈跟踪。

Android ASan 原理

主要是通过 shadow 内存,用于描述应用程序内存区域是否可被读写。

程序申请的内存的前后,各增加一个redzone区域(n * 8bytes),用户申请的内存对应的shadow内存会被标记成可读写的,而redzone区域内存对应的shadow内存则会被标记成不可读写的。

free对象时,asan不会立即把这个对象的内存释放掉,而是写入1个负数到该对象的shadown内存中,即将该对象成不可读写的状态。

并将它记录放到一个隔离区(book keeping)中, 这样当有野指针或use-after-free的情况时,就能跟进shadow内存的状态,发现程序的异常;一段时间后如果程序没有异常,就会再释放隔离区中的对象。

编译器在对每个变量的load/store操作指令前都插入检查代码,确认是否有overflow、underflow、use-after-free等问题。

C++ 仿函数

重载函数调用操作符的类,其对象常称为函数对象(function object),也叫仿函数(functor),使得类对象可以像函数那样调用。

函数对象通常不定义构造和析构函数,所以在构造和析构时不会发生任何问题,避免了函数调用时的运行时问题。

lambda 表达式的内部实现其实也是仿函数。

C++ 20 协程

协程就是一段可以挂起(suspend)和恢复(resume)的程序,一般而言,就是一个支持挂起和恢复的函数。

不阻塞当前执行的线程,把当前函数(协程)执行的位置存起来,在将来某个时间点又读取出来继续执行的。

C++ 智能指针

C++中智能指针的实现主要依赖于两个技术概念:

1、析构函数,对象被销毁时会被调用的一个函数,对于基于栈的对象而言,如果对象离开其作用域则对象会被自动销毁,而此时析构函数也自动会被调用。

2、引用计数技术,维护一个计数器用于追踪资源(如内存)的被引用数,当资源被引用时,计数器值加1,当资源被解引用时,计算器值减1。

3、操作符重载。

智能指针的大致实现原理就是在析构函数中,检查所引用对象的引用计数,如果引用计数为0,则真正释放该对象内存。

// asdfa.cpp : 定义控制台应用程序的入口点。

//

 

#include "stdafx.h"

#include<iostream>

using namespace std;

//引用计数类

class counter

{

public:

    counter(){}

    counter(int parCount) :count(parCount){}

    void increaseCount() { count++; }

    void decreasCount(){ count--; }

    int  getCount(){ return count; }

private:

    int count;

};

 

//智能指针

template<class T>

class SmartPointer

{

public:

    explicit  SmartPointer(T* pT) :mPtr(pT), pCounter(new counter(1)){}

    explicit  SmartPointer():mPtr(NULL),pCounter(NULL){}

    ~SmartPointer()     //析构函数,在引用计数为0时,释放原指针内存

    {

        if (pCounter != NULL)

        {

            pCounter->decreasCount();

            if (pCounter->getCount() == 0)

            {

                delete pCounter;

                delete mPtr;

                pCounter = NULL;    //将pCounter赋值为NULL,防止悬垂指针

                mPtr = NULL;

                cout << "delete original pointer" << endl;

            }

        }

    }

 

    SmartPointer(SmartPointer<T> &rh)   //拷贝构造函数,引用加1

    {

        this->mPtr=rh.mPtr;

        this->pCounter = rh.pCounter;

        this->pCounter->increaseCount();

    }

 

    SmartPointer<T>& operator=(SmartPointer<T> &rh) //赋值操作符,引用加1

    {

        if (this->mPtr == rh.mPtr)

            return *this;

        this->mPtr = rh.mPtr;

        this->pCounter = rh.pCounter;

        this->pCounter->increaseCount();

        return *this;

    }

    T& operator*()          //重载*操作符

    {

        return *mPtr;

    }

 

    T* operator->()         //重载->操作符

    {

        return p;

    }

    T* get()

    {

        return mPtr;

    }

private:

    T* mPtr;

    counter* pCounter;

};

int _tmain(int argc, _TCHAR* argv[])

{

    SmartPointer<int> sp1(new int(10));

    SmartPointer<int> sp2 = sp1;

    SmartPointer<int> sp3;

    sp3 = sp2;

    return 0;

}

C++ lambda 表达式

实际上是一个函数对象,内部重载了函数调用操作符;

lambda 表达式就是一个函数(匿名函数),也就是一个没有函数名的函数。为什么不需要函数名呢,因为我们直接(一次性的)用它,嵌入式用的它,不需要其他地方调用它

lambda 表达式也叫闭包。闭就是封闭的意思(封闭就是其他地方都不调用它),包就是函数。

lambda 表达式其实就是一个函数对象,他内部创建了一个重载()操作符的类。

lambda 表达式的简单语法如下:capture -> return value { body }, 只有 [capture] 捕获列表和 { body } 函数体是必选的,其他可选。

C++ 右值引用

右值引用是C++11中引入的新特性 , 它实现了转移语义和精确传递。

它的主要目的有两个方面:

消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。

能够更简洁明确地定义泛型函数。

右值引用就是必须绑定到右值的引用,他有着与左值引用完全相反的绑定特性,我们通过 && 来获得右值引用。

右值引用的基本语法type &&引用名 = 右值表达式;

右值有一个重要的性质——只能绑定到一个将要销毁的对象上。举个例子:

int  &&rr = i;  //错误,i是一个变量,变量都是左值

int &&rr1 = i *42;  //正确,i*42是一个右值

右值引用和左值引用的区别:

左值可以寻址,而右值不可以。

左值可以被赋值,右值不可以被赋值,可以用来给左值赋值。

左值可变,右值不可变(仅对基础类型适用,用户自定义类型右值引用可以通过成员函数改变)。

C++ 11 constexpr

constexpr是C++11中新增的关键字,其语义是“常量表达式”,也就是在编译期可求值的表达式。最基础的常量表达式就是字面值或全局变量/函数的地址或sizeof等关键字返回的结果,而其它常量表达式都是由基础表达式通过各种确定的运算得到的。constexpr值可用于enum、switch、数组长度等场合。

constexpr所修饰的变量一定是编译期可求值的,所修饰的函数在其所有参数都是constexpr时,一定会返回constexpr。

constexpr int Inc(int i) {

    return i + 1;

}

 

constexpr int a = Inc(1); // ok

constexpr int b = Inc(cin.get()); // !error

constexpr int c = a * 2 + 1; // ok

constexpr还能用于修饰类的构造函数,即保证如果提供给该构造函数的参数都是constexpr,那么产生的对象中的所有成员都会是constexpr,该对象也就是constexpr对象了,可用于各种只能使用constexpr的场合。注意,constexpr构造函数必须有一个空的函数体,即所有成员变量的初始化都放到初始化列表中。

struct A {

    constexpr A(int xx, int yy): x(xx), y(yy) {}

    int x, y;

};

 

constexpr A a(1, 2);

enum {SIZE_X = a.x, SIZE_Y = a.y};

constexpr的好处:

是一种很强的约束,更好地保证程序的正确语义不被破坏。

编译器可以在编译期对constexpr的代码进行非常大的优化,比如将用到的constexpr表达式都直接替换成最终结果等。

相比宏来说,没有额外的开销,但更安全可靠

C++ .hpp

定义与实现都包含在同一文件,调用者只需要include该hpp文件即可。

非常适合用来编写公用的开源库。

不可包含全局对象和全局函数。

类之间不可循环调用。

不可使用静态成员。

C++ 完美转发

所谓的完美转发,是指std::forward会将输入的参数原封不动地传递到下一个函数中,这个“原封不动”指的是,如果输入的参数是左值,那么传递给下一个函数的参数的也是左值;如果输入的参数是右值,那么传递给下一个函数的参数的也是右值。

防止在参数传递的过程中,右值引用会变成左值引用,从而调用拷贝构造而不是移动构造。

无论左值引用类型的变量还是右值引用类型的变量,都是左值,因为它们有名字。

C++ 锁

std::recursive_mutex 嵌套锁/递归锁/重入锁

std::recursive_mutex 允许同一个线程对互斥量多次上锁(即递归上锁),来获得对互斥量对象的多层所有权,std::recursive_mutex 释放互斥量时需要调用与该锁层次深度相同次数的 unlock(),可理解为 lock() 次数和 unlock() 次数相同,除此之外,std::recursive_mutex 的特性和 std::mutex 大致相同。

std::timed_mutex、 recursive_timed_mutex

timed_mutex.try_lock_for(std::chrono::milliseconds(200)); //在指定的时限中获取到锁则返回 true , 否则返回 false

std::shared_timed_mutex 与 std::shared_lock(C++14)

C++14 通过 std::shared_timed_mutex 和 std::shared_lock 来实现读写锁,保证多个线程可以同时读,但是写线程必须独立运行,写操作不可以同时和读操作一起进行。

struct ThreadSafe {

    mutable std::shared_timed_mutex mutex_;

    int value_;

 

    ThreadSafe() {

        value_ = 0;

    }

 

    int get() const {

        std::shared_lock<std::shared_timed_mutex> loc(mutex_);

        return value_;

    }

 

    void increase() {

        std::unique_lock<std::shared_timed_mutex> lock(mutex_);

        value_ += 1;

    }

};

C++ 20 Concepts

用于约束模板函数和模板类的模板参数。

https://zhuanlan.zhihu.com/p/107610017

C++ 20 Ranges

ranges 可以省掉很多循环,包括多重循环,写出来的代码又简单,可读性又好。

vector<int> data{4, 3, 4, 1, 8, 0, 8}; 

vector<int> result = data | actions::sort | actions::unique;

 

from:https://blog.csdn.net/liuxing__jacker/article/details/130318758

 

标签:面试题,函数,int,C++,constexpr,内存,Message,高频
From: https://www.cnblogs.com/im18620660608/p/17353342.html

相关文章

  • c++11 std::forward使用场景以及作用
    不使用 std::forward时,下述代码G不管传入什么类型的参数,只会最终调用 voidF(int&a);usingnamespacestd; voidF(int&a){  cout<<"int&version"<<a<<endl;} voidF(int&&a){  //dosomething  cout<<"in......
  • C++语言亚马逊国际获取AMAZON商品详情 API接口
    跨境电子商务是一种全新的互联网电商模式,运用电子化方式促成线上跨境交易,利用跨境物流运送商品,有利于打破传统的贸易格局,成为新的经济增长点。对我国来说,跨境电商平台正用一种全新的力量改变我国产业链的结构,并有利于增加贸易机会,拓展我国外贸在国际市场的广度与深度,赢得广阔的海......
  • 名字修饰约定: extern "C"、extern "C++" 和__stdcall、__cdecl相关的约定、__imp_前
    关于extern_C通常,在C语言的头文件中经常可以看到类似下面这种形式的代码#ifdef__cplusplusextern"C"{#endif/****somedeclarationorso*****/#ifdef__cplusplus}#endif/*endof__cplusplus*/那么,这种写法什么用呢?实际上,这是为了让CPP能够与C......
  • java面试题--springboot
    一、SpringBoot自动装配原理是什么?@SpringBootApplication@EnableAutoConfigration\@SpringBootConfigration\@ComponentScan@AutoConfigrationPackage\@ImportMETA-INF\spring.factories二、说一下@Configuration中的属性proxyBeanMethods的作用?首先,引入两个概念:Full全......
  • 《c++徒步》方法篇
    按值传递和按地址传递参考链接:https://blog.csdn.net/scrence/article/details/79835572参考链接:https://www.jb51.net/article/250343.htm1、按值传递#include<iostream>usingnamespacestd;voidchangeNumber(intx);intmain(void){ inta=10; cout<<"a="......
  • C++ shared_ptr 虚析构函数 特殊
    classa{public:~a(){cout<<"a"<<endl;}classb:publica{public:~b(){cout<<"b"<<endl;}voidmain(){shared_ptrA;{shared_ptrB(newb());//智能指针内部,uses引用值为1A=B;//智能指针内部,uses引用值为2,子类父类的智能指针可以一起计数}//离开作用......
  • 测试工程师面试题1
    转载地址:https://blog.csdn.net/weixin_46658581/article/details/119678292?spm=1001.2014.3001.5502软件的生命周期(基础)计划阶段-〉需求分析-〉设计阶段-〉编码->测试->运行与维护测试流程有啥(还是基础)1)、测试需求分析阶段:阅读需求,理解需求,主要就是对业务的学习,分析需求点,参与......
  • 【面试宝典】C/C++ 基础
    一.语言基础 数组和指针的区别 数组指针概念是用于储存多个相同类型数据的集合。 数组名是首元素的地址特殊的变量,存放的是其它变量在内存中的地址。 指针名指向了内存的首地址赋值只能一个一个元素的赋值或拷贝同类型指针变量可以相互赋值存放方式连续......
  • Effective C++总结
    1.视C++为一个语言联邦c++是C、面向对象C++、泛型编程、以及stl的集合。2.尽量以const\enum\inline替换#define3.尽可能使用const4.确定对象使用前已经被初始化5.了解C++默认生成并调用哪些函数7.为多态基类声明virtual析构函数8.别让异常逃离析构函数9.绝不在构造和析构过程中调......
  • C++STL学习经典
    C++语言学习之STL的组成STL有三大核心部分:容器(Container)、算法(Algorithms)、迭代器(Iterator),容器适配器(containeradaptor),函数对象(functor),除此之外还有STL其他标准组件。通俗的讲:容器:装东西的东西,装水的杯子,装咸水的大海,装人的教室……STL里的容器是可容纳一些数据的模板类。算法:......