秋の採用アルゴリズム C++ 面接後の体験

目次

1. ポインタと参照の違い

2.constキーワード

3. オーバーロードと書き換え(上書き)の違い

4. new と malloc の違い (new は malloc をカプセル化します)

5.staticとconstの違い 

6. C++の3大特徴

7.仮想機能

8.純粋仮想関数

 9.仮想継承

10. スマートポインター

11. メモリリーク

12.C++のメモリ分散

13.STLの紹介

14.これから共有を有効にするということは何を意味しますか?

15.このポインタの使い方

16. 左辺値、右辺値および関連する演算

1. ポインタと参照の違い

a. ポインタはそれ自体を格納するために追加のアドレスを必要としますが、参照は追加のメモリを必要とせず、単なる変数のエイリアスです。
b. ポインタの指すアドレスは変更できますが、リファレンスは初期化後は変更できません。
c. ポインタは null にすることもできますが、参照は宣言時に初期化する必要があります。

2.constキーワード

これによって変更された値は変更できないため、定義時に初期値を割り当てる必要があります。

定数ポインタとポインタ定数

データ型 const* ポインタ変数は定数ポインタの形式であり、それが指すアドレスの値は変更できません。

データ型 *const ポインタ変数はポインタ定数の形式であり、それが指すアドレスは変更できません。

3. オーバーロードと書き換え(上書き)の違い

オーバーライドは通常、サブクラスが親クラスを継承し、親クラスの仮想関数メソッドをオーバーライドする場合に使用されます。override キーワードは、親クラスと子クラスの仮想関数パラメータが一貫していることを確認するためのものです。そうでない場合は、エラーがポップアップします。

メソッド名は同じだがパラメーター形式が異なるクラスにオーバーロードが存在します。

オーバーライドされたメソッドのパラメーター リストは、オーバーライドされた戻り値と一致している必要があります。

静的メソッドを非静的メソッドにオーバーライドすることはできません。静的メソッドはクラスに属し、非静的メソッドはオブジェクトに属し、オーバーライドはオブジェクトのポリモーフィズムに基づいているため、機能しません。

オーバーライドされたメソッドをプライベート メソッドにすることはできません。プライベート メソッドは継承できないため、プライバシーが破壊されます。

4. new と malloc の違い (new は malloc をカプセル化します)

new がメモリの割り当てに失敗すると、例外がスローされ、malloc は NULL を返します。

new がメモリ割り当てに適用される場合、サイズを指定する必要はありませんが、malloc を明示的に指定する必要があります。

new/delete はオーバーロードをサポートしますが、malloc/free はサポートしません。

new/delete はオブジェクトのコンストラクターとデストラクターを呼び出しますが、malloc は呼び出しません。

new は空きストレージ領域からメモリを割り当て、malloc はヒープからメモリを割り当てます。

5.staticとconstの違い 

const によって変更された変数は、スコープを超えると解放されます。定義時に初期化する必要があり、後で変更することはできません。また、関数実行直後にはstaticは解放されません。静的に変更されたメンバー関数は静的メンバー関数と呼ばれ、インスタンスに依存せず、クラス名を通じて直接呼び出すことができます。このポインターがないため、非静的メンバー変数または関数にアクセスできません。同時に、仮想関数の呼び出しはオブジェクトの仮想関数テーブル (各仮想関数へのポインターが格納されている) を通じて実装され、静的メンバー関数はクラスではなくクラスに関連付けられるため、仮想関数として宣言することはできません。クラスのオブジェクト。リンクされています。

  • static は、静的変数と静的関数を宣言するために使用されます。これらはメモリ内に 1 つのコピーのみを持ち、オブジェクト インスタンスとは何の関係もありません。

6. C++の3大特徴

クラスの外部から、パブリック プロパティのメンバーにはオブジェクトを介してのみアクセスできます。

継承: 既存のクラスのすべての関数を使用し、元のクラスを書き直すことなくこれらの関数を拡張できます。

カプセル化: 客観的なものをクラスに抽象化し、情報を共有および非表示にします。

ポリモーフィズム: サブクラス型のポインターを親クラス型のポインターに割り当てることができます。オーバーロードはコンパイル多態性を実装し、仮想関数は実行時多態性を実装します。ポリモーフィズムを実現するには 2 つの方法があります。上書きです。サブクラスが親クラスの仮想関数を再定義します。オーバーロード: 名前は同じだがパラメーター リストが異なる複数の関数。

class Base {
public:
    virtual void foo() {
        cout << "Base::foo()" << endl;
    }
};

class Derived : public Base {
public:
    void foo() override {   // 重写了基类的虚函数foo()
        cout << "Derived::foo()" << endl;
    }
};
Base* ptr = new Derived();
ptr->foo();  // 会调用Derived::foo()

7.仮想機能

基本クラスが派生クラス定義を自身のバージョンに合わせたい場合、これらの関数を仮想関数として宣言します。仮想関数は動的にバインドされます。基本クラスのポインタは、そのクラスの仮想関数テーブルがクエリされるクラスを指します。このメカニズムにより、派生クラスの仮想関数が確実に呼び出されます。

仮想関数はポリモーフィズムを実装します。関数を呼び出すオブジェクトはポインターまたは参照である必要があり、呼び出される関数は仮想関数である必要があり、仮想関数の書き換えが完了している必要があります。

コンストラクターはオブジェクトの作成時に呼び出されるため、コンストラクターを仮想関数にすることはできません。仮想関数は、オブジェクトの動的型に基づいて呼び出す関数のバージョンを決定する必要があります。オブジェクトの作成前にその型を知る方法。

デストラクタは仮想関数にすることができます。このクラスが基底クラスとしてポリモーフィズムを実装する場合、基底クラスのポインタは派生クラスのオブジェクトを指します。このとき、派生クラスのオブジェクトを破棄するには、基底クラスのデストラクタが必要です。仮想関数である必要があります。

インライン関数を仮想関数にすることはできません。インライン関数はコンパイル時にオブジェクトの型を認識せず、仮想関数はどの仮想関数を呼び出すかを知るためにオブジェクトの型を知る必要があるため、コンパイル時にインライン関数の展開を実行できません。 。インライン関数についてもう一度説明しますが、インライン関数は通常、スタック メモリを大量に消費する頻繁に呼び出される小さな関数の問題を解決するために、関数の定義と宣言の前に記述されます。通常、頻繁に呼び出される小さな関数本体に使用されます。inline関数はコンパイラへの提案ですので、関数が大きすぎると思われる場合はインライン関数として使用できない場合があります。

inline int add(int x, int y); //声明时
inline int add(int x, int y) {
    return x + y;
}//定义时

静的メンバーにはこのポインターがないため、静的関数を仮想関数にすることはできません。また、仮想関数はオブジェクトを通じて呼び出す必要があり、このポインターを非表示にする必要があります。

8.純粋仮想関数

純粋な仮想関数は基本クラスで宣言され、仮想関数の特定の実装はなく、インターフェイスのみを提供し、派生クラス自体によって実装される必要があります。純粋仮想関数を使用するクラスは抽象クラスであり、インスタンス化できません。したがって、派生クラスは純粋仮想関数をオーバーライドする必要があります。そうでないと、派生クラスも抽象クラスになり、インスタンス化できません。

class AbstractBase {
public:
    virtual void pureVirtualFunc() = 0;  // 纯虚函数声明
};

class Derived : public AbstractBase {
public:
    void pureVirtualFunc() override {
        // 派生类提供的纯虚函数实现
    }
};

AbstractBase* ptr = new Derived();
ptr->pureVirtualFunc();  // 调用派生类对象的纯虚函数实现
delete ptr;  // 删除派生类对象

 9.仮想継承

仮想継承は、多重継承時のダイヤモンド継承の問題を解決し、曖昧さを防ぐことです。

//间接基类A
class A{
protected:
    int m_a;
};

//直接基类B
class B: virtual public A{  //虚继承
protected:
    int m_b;
};

//直接基类C
class C: virtual public A{  //虚继承
protected:
    int m_c;
};

//派生类D
class D: public B, public C{
public:
    void seta(int a){ m_a = a; }  //正确
    void setb(int b){ m_b = b; }  //正确
    void setc(int c){ m_c = c; }  //正确
    void setd(int d){ m_d = d; }  //正确
private:
    int m_d;
};

int main(){
    D d;
    return 0;
}

10. スマートポインター

スマート ポインタは、メモリの動的割り当てと同じメモリの複数の解放によって引き起こされるメモリ リークの問題を解決するために提案されています。それを <memory> ヘッダー ファイルに配置します。共有ポインタ、排他的ポインタ、ウィーク ポインタを含む

shared_ptr: は、カウンターを使用して同じリソースを共有するスマート ポインターの数を追跡する参照カウント スマート ポインターです。カウントが 0 に達すると、リソースが解放されます。つまり、このリソースを指すスマート ポインターが存在しない場合、このリソースは解放されます。

読み取り専用の場合、そのマルチスレッドは安全です。複数のスレッドが参照カウントを同時に変更する場合、スレッドの安全性を確保するために追加の同期が必要です。そうしないと、参照カウントを同時に変更するときにデータの競合と未定義が発生する可能性があります。行動。

#include <memory>

std::shared_ptr<int> ptr1 = std::make_shared<int>(42); //第三种构造方式
std::shared_ptr<int> ptr2 = ptr1; // 共享所有权

'''
这两种不常用
int* a = new int(4);
std::shared_ptr<int> ptr3(a); //第一种构造方式
std::shared_ptr<int> ptr1(new int); //第二种构造方式
'''

 ptr1とptr2の両方が破壊された場合のみ、int型リソースが解放されます。

unique_ptr: 排他的所有権を持つスマート ポインタであり、管理権を他人と共有することはできません。つまり、通常のコピーや代入はサポートされていません。破棄されると、管理しているリソースも破棄されます。

#include <memory>

std::unique_ptr<int> ptr = std::make_unique<int>(42);

weak_ptr:shared_ptr と連携するために導入され、傍観者のようにリソースの使用状況を監視できますが、リソースを共有せず、その構造によりポインタの参照数が増加することはありません。

11. メモリリーク

ヒープ メモリ リーク: malloc および new を使用してヒープからメモリの一部を割り当て、使用後に free または delete でメモリを削除するのを忘れたプログラムを指します。

システム リソースの漏洩: システムによって割り当てられたリソースを使用しているプログラムが、リソースを解放するための対応する関数を呼び出していないため、システム リソースが浪費されることを指します。

基本クラスのデストラクターは仮想関数として定義されていません。基本クラスのポインターがサブクラスのオブジェクトを指している場合、基本クラスのデストラクターが仮想関数でない場合、サブクラスのデストラクターは仮想関数として定義されません。が呼び出され、メモリ リークが発生しました。

メモリリークを防ぐにはどうすればよいでしょうか? メモリ割り当てをクラスにカプセル化し、コンストラクタがメモリを割り当て、デストラクタがメモリを解放します。スマート ポインターを使用します。

12.C++のメモリ分散

スタック領域には、ローカル変数と関数のパラメータ値が格納されます。高アドレスから低アドレスへ。

ヒープ領域に格納される動的に割り当てられたメモリ。

グローバル領域に格納されるグローバル変数とスタティック変数。メモリはプログラムのコンパイル時にすでに割り当てられています。

13.STLの紹介

コンテナ: ペア、ベクトル、リスト、デキュー、セット、マップなどのさまざまなデータ構造。

アダプター: キューとスタック、その最下層は deque によって実装されます。

ペア: 内部の要素は a->first, a->second を呼び出します。ペアを挿入する場合は、insert({first, Second}) または insert(pair<type, type>(data 1, data 2));

ベクトル: 連続したメモリ空間がヒープ内に割り当てられます。拡張は毎回2倍になりますが、容量が足りない場合は2倍になります。容量は常にサイズより大きくなります。基礎となる実装は配列です。

操作:プッシュバック、エンプレイスバック、ポップバック、クリア、

  • insert(position, value): 指定した位置に要素を挿入します。
  • erase(position): 指定した位置の要素を削除します。
  • erase(first, last): 指定範囲内の要素を削除します

 list: メモリ空間は不連続であり、データはポインタを介してアクセスされます。通常、ランダムな挿入および削除操作に使用されます。二重リンクリストです。

deque: 両端キュー。内部ジャンプが必要なため、ランダム アクセスはベクトルほど高速ではありません。deque の反復子は、vector の反復子よりも複雑であるため、deque をソートする場合は、まず deque を Vector にコピーし、ソートしてから、deque に戻すことができます。操作: プッシュバック、ポップバック、プッシュフロント、ポップフロント

スタックとキューはアダプターと呼ばれ、最下層は deque です。操作: プッシュ、ポップ、空、サイズ。さらに、キューにはフロント、バック、スタック、トップもあります。

マップとセット: 最下層は赤黒ツリーを使用して実装され、検索複雑さは log(n) です。

unowned_map および unowned_set: 基礎となる実装は、ルックアップ複雑度 1 のハッシュ テーブルです。

14.これから共有を有効にするということは何を意味しますか?

場合によっては、オブジェクト自体をスマート ポインターとして管理する必要があります。通常、オブジェクト内には他のオブジェクトや関数があり、それらは現在のオブジェクトを参照する必要がありますが、生のポインタを使用してオブジェクトを渡したりコピーしたりすると、リソース管理が困難になります。

「enable_shared_from_this」は、オブジェクト自体がshared_ptrリソースとして使用される場合に、主に共有リソース管理の問題を解決するために使用される基本クラスです。この基本クラスは、メンバー関数shared_from_this()を定義します。この関数を通じて、現在のオブジェクトを指すshared_ptrを返し、リソースの正しい管理と共有を保証できます。

#include <iostream>
#include <memory>

class MyClass : public std::enable_shared_from_this<MyClass> {
public:
    std::shared_ptr<MyClass> getShared() {
        return shared_from_this();
    }
};

int main() {
    std::shared_ptr<MyClass> obj1 = std::make_shared<MyClass>();

    std::shared_ptr<MyClass> obj2 = obj1->getShared();

    std::cout << "obj1 use count: " << obj1.use_count() << std::endl;
    std::cout << "obj2 use count: " << obj2.use_count() << std::endl;

    return 0;
}

上記の例では、Myclass は基本クラスenable_shared_from_this を継承し、メンバー関数 getshared() を定義します。この関数は、shared_from_this() を呼び出すことによって現在のオブジェクトを指すスマート ポインターを返します。

15.このポインタの使い方

クラス内に変数を定義し、同時にそのクラスのメンバ関数内にも同じ変数を定義し、その変数をクラス内で使用したい場合、このポインタが必要になります。

クラスの非静的メンバー関数がクラス オブジェクト自体を返す場合は、return *this を直接使用します。

このポインターのスコープはクラス内にあり、静的メンバー関数やグローバル関数ではなく、メンバー関数でのみ使用できます。

16. 左辺値、右辺値および関連する演算

左辺値は、式が終了した後もまだ存在すること、特定のメモリ アドレスを持つことなどを意味します。

右辺値は一時的なオブジェクトであり、式が終了すると存在しなくなります。

int a = 5; // 5 是一个右值
int b = a + 3; // a + 3 是一个右值表达式

C++11 では右辺値参照の概念が導入されており、右辺値を参照したり右辺値にバインドしたりできるため、移動セマンティクスや移動構築をそれらに適用できるようになります。右辺値参照を使用すると、転送セマンティクス、完全転送、移動セマンティクスなどの効率的な操作を効果的に実装できます。

int&& rref = 42; // 右值引用绑定到右值

std::move() は <utility> ライブラリにあり、その機能は左辺値を右辺値参照に変換し、左辺値を右辺値に強制的に変換することで、移動セマンティクスと完全転送をトリガーします。

#include <utility>

void func(int&& value) {
    // 对右值引用的参数进行操作
}

int main() {
    int x = 42; // x 是一个左值

    int&& rref = std::move(x); // 使用 std::move 将 x 转换为右值引用
    func(std::move(x)); // 将 x 转换为右值引用并传递给函数

    return 0;
}

std::forward はパラメータを渡し、左辺値や右辺値などの元の値の型を保存します。通常、完全な転送を完了するためにテンプレート関数でのみ使用されます通常の関数で std::forward を使用するのは意味がありません。テンプレート関数は、さまざまな種類のパラメーターに基づいて複数の特定の関数インスタンスを生成できます。テンプレート関数の定義では、キーワードtemplateとテンプレート パラメーター リストを使用してテンプレート パラメーターを指定し、その後に関数を指定します。

#include <utility>

// 使用 std::forward 完美转发参数
template<typename T>  //模板函数
void wrapper(T&& arg) {    //和万能引用放在一起,这样T既可以接收左值,也可以接收右值。
    other_function(std::forward<T>(arg)); // 将参数 arg 以其原始的值类别(左值引用或右值引用)传递给其他函数
}

void other_function(int& x) {
    // 处理左值引用参数
}

void other_function(int&& x) {
    // 处理右值引用参数
}

int main() {
    int x = 42;
    wrapper(x); // 调用传递左值引用的版本
    wrapper(123); // 调用传递右值引用的版本

    return 0;
}

移動コンストラクター: 渡される右辺値参照により、不必要なメモリのコピーを回避できます。そして、ソース オブジェクトのリソース ポインタまたは状態ポインタをターゲット オブジェクトに移動し、破棄中にリソースの解放が正しく処理できるように、ソース オブジェクトを有効ではあるが未定義の状態にします。noexcept通常、オブジェクトの移動中に例外がスローされないように、移動コンストラクターをマークする必要があります。これにより、コンパイラーはコードを最適化し、パフォーマンスを向上させることができます。

移動セマンティクスとは、オブジェクトが渡されるか割り当てられるときに、ディープ コピー操作を実行せずに、あるオブジェクトから別のオブジェクトにリソースを転送することを指します。移動操作は、特に大きなオブジェクトや多数のリソースを保持するオブジェクトを操作する場合、コピー操作よりも効率的です。移動セマンティクスは、移動コンストラクターと移動代入演算子を通じて実装されます。

転送セマンティクスとは、移動操作で使用するために、転送操作 (std::move など) を通じてオブジェクトを転送可能な右辺値参照 (Rvalue Reference) としてマークすることを指します。

17.ベクターのリサイズとリザーブの違い

サイズ変更は、ベクターのサイズを変更し、ベクター内の要素を追加および削除します。Reserve は、vector の元の値を変更しませんが、頻繁なメモリ割り当てを避けるために、その空間サイズを事前に割り当てるために使用されます。

18.ベクトルとリストの違い

ベクトル内の要素は順番に格納されるため、十分なメモリがない場合はメモリを拡張する必要があります。list の基礎となる実装は二重リンク リストであり、そのメモリは個別に分散されます。
これは二重リンクのリスト構造であるため、任意の位置でリストを挿入および削除する場合の計算量は o(1) ですが、添字を介して要素にアクセスすることはできず、要素を見つけるためにトラバースすることしかできません。Vector は添字を介して要素 O(1) にアクセスできますが、挿入と削除は O(n) です。

おすすめ

転載: blog.csdn.net/slamer111/article/details/131522109