2020 Meituan Autumn Recruitment C ++の選択されたインタビューの質問と回答(パート1)

1. B +ツリーの機能

(1)各ノードのキーワードの数は、子の数と同じです。最も低い内部ノード以外のすべてのキーワードは、対応するサブツリーの最大のキーワードであり、最も低い内部ノードにはすべてのキーワードが含まれます。 。
(2)ルートノードを除いて、各内部ノードには最大m個の子があります。[3]
(3)すべてのリーフノードはツリー構造の同じレベルにあり、情報が含まれていません(外部ノードまたは検索に失敗したノードと見なすことができます)。したがって、ツリー構造は常にツリーの高さでバランスが取れています。

2.CASとそのソリューションの欠点

CASの欠点は、ABAの問題、スピンロックの消費の問題、多変数共有の一貫性の問題などです
。1.ABA:
問題の説明:スレッドt1は、その値をAからBに変更し、次にBからAに変更します。同時に、スレッドt2は値をAからCに変更しようとしています。しかし、CASがチェックすると、変更は見つかりませんが、実際には変更されています。データが欠落する可能性があります。
解決策:CASは依然として楽観的ロックに似ており、AtomicStampedReference
2などのデータの楽観的ロックと同じ方法でバージョン番号またはタイムスタンプを追加します。スピンはリソースを消費します:
問題の説明:複数のスレッドが同じリソースをめぐって競合する場合、スピンする場合失敗した場合は、CPUを占有し続けます。
解決策:無限のforループを破棄します。特定の時間または回数を超えると、リターンが終了します。JDK8で追加されたLongAddrは、ConcurrentHashMapに似ています。複数のスレッドが競合する場合、粒度が低下し、1つの変数が複数の変数に分割されて、複数のスレッドが複数のリソースにアクセスする効果が得られます。最後に、合計が呼び出されてそれらが結合されます。
ベースとセルの両方が揮発性に変更されていますが、合計操作がロックされていないように感じられ、合計の結果はそれほど正確ではない可能性があります。
2.多変数共有の一貫性の問題:
解決策:複数の変数を操作する場合、CAS操作は1つの変数に対して行われます。

  1. ロックで解決できます。
  2. 解決するオブジェクトクラスにパッケージ化されています。

より多くの第一線のインターネット企業が質問をインタビューしますvxは受け取るためにゼロサウンドアカデミーに注意を払います!
ここに写真の説明を挿入

3.関数rand7()が1から7までのランダムな数を生成できることを知っているので、1から10までのランダムな数を生成できる関数を与えてください

このソリューションは、拒否サンプリングと呼ばれる方法に基づいています。主な考え方は、対象範囲内のランダム数が生成されている限り、直接返されるというものです。生成されたランダム数がターゲット範囲内にない場合は、値を破棄してリサンプリングします。対象範囲内の数値が同じ確率で選択されるため、このような均一な分布が生成されます。

明らかに、rand7は少なくとも2回実行する必要があります。そうしないと、1〜10の数字は生成されません。rand7を2回実行することにより、1〜49の整数を生成できます。

ここに写真の説明を挿入

49は10の倍数ではないため、いくつかの値を破棄する必要があります。必要な数値の範囲は1〜40です。この範囲にない場合は、破棄して再サンプリングします。

コード:

int rand10() {
    
    
 int row, col, idx;
 do {
    
    
 row = rand7();
 col = rand7();
 idx = col + (row-1)*7;
 } while (idx > 40);
 return 1 + (idx-1)%10;
}
 

行の範囲は1〜7、列の範囲は1〜7であるため、idx値の範囲は1〜49です。40を超える値は破棄されるため、1〜40の範囲の残りの数値はモジュロによって返されます。1〜40の範囲を満たすサンプリング回数の期待値を計算してみましょう。ここに写真の説明を挿入

4.スレッドを作成するC ++ 11の3つの方法

  1. 関数
    スレッドを介して:標準ライブラリクラス
    join:メインスレッドをブロックして待機します
// MultiThread.cpp : Defines the entry point for the console application.
#include "stdafx.h"
#include<iostream>
#include<vector>
#include<map>
#include<string> 
#include<thread>
 
using namespace std;
void myPrint()
{
    
    
 cout << "线程开始运行" << endl;
 cout << "线程运行结束了" << endl;
}
 
int main()
{
    
    
 std::thread my2Obj(myPrint); // 可调用对象
 my2Obj.join();// 主线程阻塞在这,并等待myPrint()执行完
 cout << "wangtao" << endl;
 return 0;
}
 
detach(): 将主线程和子线程完全分离,子线程会驻留在后台运行,被C++运行时库接管,失去控制
 
void myPrint()
{
    
    
 cout << "线程开始运行1" << endl;
 cout << "线程开始运行2" << endl;
 cout << "线程开始运行3" << endl;
 cout << "线程开始运行4" << endl;
 cout << "线程开始运行5" << endl;
 cout << "线程开始运行6" << endl;
 cout << "线程开始运行7" << endl;
 cout << "线程开始运行8" << endl;
 cout << "线程开始运行9" << endl;
 
}
 
int main()
{
    
    
 std::thread my2Obj(myPrint); // 主线程阻塞在这,并等待myPrint()执行完
 my2Obj.detach();
 cout << "wangtao1" << endl;
 cout << "wangtao2" << endl;
 cout << "wangtao3" << endl;
 cout << "wangtao4" << endl;
 cout << "wangtao5" << endl;
 cout << "wangtao6" << endl;
 cout << "wangtao7" << endl;
 cout << "wangtao8" << endl;
 return 0;
}
 
joinable():判断是否可以成功使用join()或者detach()
 
程序说明:detach后不能在实施join
 
int main()
{
    
    
 std::thread my2Obj(myPrint); // 主线程阻塞在这,并等待myPrint()执行完
 if (my2Obj.joinable()){
    
    
 cout << "1:joinable() == true" << endl;
 }
 else {
    
    
 cout << "1:joinable() == false" << endl;
 }
 my2Obj.detach();
 
 if (my2Obj.joinable()) {
    
    
 cout << "2:joinable() == true" << endl;
 }
 else {
    
    
 cout << "2:joinable() == false" << endl;
 }
 cout << "wangtao1" << endl;
 cout << "wangtao2" << endl;
 cout << "wangtao3" << endl;
 cout << "wangtao4" << endl;
 cout << "wangtao5" << endl;
 cout << "wangtao6" << endl;
 cout << "wangtao7" << endl;
 cout << "wangtao8" << endl;
 return 0;
}
 
int main()
{
    
    
 std::thread my2Obj(myPrint); // 主线程阻塞在这,并等待myPrint()执行完
 if (my2Obj.joinable()){
    
    
 my2Obj.join();
 }
 cout << "wangtao1" << endl;
 cout << "wangtao2" << endl;
 cout << "wangtao3" << endl;
 cout << "wangtao4" << endl;
 cout << "wangtao5" << endl;
 cout << "wangtao6" << endl;
 cout << "wangtao7" << endl;
 cout << "wangtao8" << endl;
 return 0;
}
 
 
2.通过类对象创建线程
class CObject
{
    
    
public:
 void operator ()() {
    
    
 cout << "线程开始运行" << endl;
 cout << "线程结束运行" << endl;
 }
};
 
 
int main()
{
    
    CObject obj;
 std::thread my2Obj(obj); // 主线程阻塞在这,并等待myPrint()执行完
 if (my2Obj.joinable()){
    
    
 my2Obj.join();
 }
 cout << "see you " << endl;
 
 return 0;
}
 
 
class CObject
{
    
    
 int& m_obj;
public:
 CObject(int& i) :m_obj(i) {
    
    }
 void operator ()() {
    
     // 不带参数
 cout << "线程开始运行1" << endl;
 cout << "线程开始运行2" << endl;
 cout << "线程开始运行3" << endl;
 cout << "线程开始运行4" << endl;
 cout << "线程开始运行5" << endl;
 }
};
int main()
{
    
    
 int i = 6;
 CObject obj(i);
 std::thread my2Obj(obj); // 主线程阻塞在这,并等待myPrint()执行完
 if (my2Obj.joinable()){
    
    
 my2Obj.detach();
 }
 cout << "see you " << endl;
 
 return 0;
}detach() 主线程结束对象即被销毁,那么子线程的成员函数还能调用吗?
这里的的对象会被复制到子线程中,当主线程结束,复制的子线程对象并不会被销毁
只要是没有引用、指针就不会出现问题 
通过复制构造函数和析构函数来验证对象是否复制到了子线程中 
// MultiThread.cpp : Defines the entry point for the console application.
//
 
#include "stdafx.h"
#include<iostream>
#include<vector>
#include<map>
#include<string> 
#include<thread>
using namespace std;
class CObject
{
    
    
 int& m_obj;
public:
 CObject(int& i) :m_obj(i) {
    
    
 cout << "ctor" << endl;
 }
 CObject(const CObject& m) :m_obj(m.m_obj) {
    
    
 cout << "copy ctor" << endl;
 }
 ~CObject(){
    
    
 cout << "dtor" << endl;
 }
 void operator ()() {
    
     // 不带参数
 cout << "线程开始运行1" << endl;
 cout << "线程开始运行2" << endl;
 cout << "线程开始运行3" << endl;
 cout << "线程开始运行4" << endl;
 cout << "线程开始运行5" << endl;
 }
};
int main()
{
    
    
 int i = 6;
 CObject obj(i);
 std::thread my2Obj(obj); // 主线程阻塞在这,并等待myPrint()执行完
 if (my2Obj.joinable()){
    
    
 my2Obj.detach();
 }
 cout << "see you " << endl;
 
 return 0;
}

子スレッドのデストラクタはバックグラウンドで実行されるため、出力dtorがメインスレッドになります。join()の結果は次のとおりです
。3。ラムダ式を使用してスレッドを作成します

int main()
{
    
    
 auto myLamThread = [] {
    
    
 cout << "线程开始运行" << endl;
 cout << "线程结束运行" << endl;
 };
 thread cthread(myLamThread);
 cthread.join();
 std::cout << "see you " << endl;
 
 return 0;
}

6. C ++インライン関数について話します

インライン関数インライン:インライン関数を導入する目的は、プログラム内の関数呼び出しの効率の問題を解決することです。つまり、プログラムがコンパイラーによってコンパイルされるときに、コンパイラーはプログラム内のインライン関数呼び出し式を使用します。連想関数の関数本体は置き換えられますが、他の関数の場合は実行時に置き換えられます。これは実際にはスペースと時間の節約です。したがって、インライン関数は通常、1〜5行目の小さな関数です。インライン関数を使用するときは注意してください:
1。ループステートメントとスイッチステートメントは
インライン関数では許可されていません; 2。インライン関数の定義はインライン関数の最初の呼び出しの前に表示する必要があります;
3。クラス構造内の場所クラス記述の内部定義関数はインライン関数です。

7.マクロ定義の長所と短所

利点:

  1. プログラムの読みやすさを向上させるだけでなく、変更を容易にします。
  2. プログラムの運用効率の向上:パラメーター付きのマクロ定義を使用すると、関数呼び出しの機能を完了するだけでなく、関数のスタッキングとスタッキング操作を回避し、システムオーバーヘッドを削減し、運用効率を向上させること
    ができます。3。マクロはプリプロセッサーによって処理されます。 、コンパイラでは実現できない多くの機能は、文字列操作で実現できます。## connectorなど。

短所:

  1. 直接埋め込まれているため、コードは比較的多くなる可能性があります。
  2. ネストされた定義が多すぎると、プログラムの読みやすさに影響を与える可能性があり、間違いを犯しやすくなります。
  3. パラメータ付きのマクロの場合、直接置換されるため、パラメータが有効かどうかはチェックされず、セキュリティ上のリスクが発生します。
  4. CPUはどのようにメモリにアクセスしますか?
    最初に、メモリ管理ユニット(MMU)を介してメモリに
    アクセスするCPUの簡単なフローチャートを見てください。
    ここに写真の説明を挿入

TLB:ルックアサイドキャッシュを変換します。これにより、仮想アドレスから物理アドレスへの変換速度を大幅に向上させることができます。
上の図から、CPU、DDR、MMUの関係がはっきりとわかります。MMUがオンになると、CPUはすべての仮想アドレスにアクセスします。
最初に、仮想アドレスがMMUを介して物理アドレスに変換され、
次にメモリがバスを介してアクセスされます(メモリがバスにハングしていることは誰もが知っています)。
それでは、MMUはどのようにして仮想アドレスを物理アドレスに変換しますか?もちろん、それはページテーブルを介して行われます。MMUは、ページテーブルから仮想アドレスに対応する物理アドレスを見つけて、物理メモリにアクセスします。

おすすめ

転載: blog.csdn.net/lingshengxueyuan/article/details/108602597