C ++でインターフェイスをどのように宣言しますか?


回答:


686

bradtgmurrayによる回答を拡張するには、仮想デストラクタを追加して、インターフェースの純粋仮想メソッドリストに1つの例外を作成することができます。これにより、具体的な派生クラスを公開することなく、ポインターの所有権を別のパーティに渡すことができます。インターフェイスには具体的なメンバーがないため、デストラクタは何もする必要がありません。関数を仮想とインラインの両方として定義することは矛盾するように思えるかもしれませんが、私を信じてください-そうではありません。

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Parent
{
    public:
        virtual ~Parent();
};

class Child : public Parent, public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

仮想デストラクタの本体を含める必要はありません-一部のコンパイラは空のデストラクタの最適化に問題があり、デフォルトを使用する方がよいことがわかりました。


106
仮想記述子++!これは非常に重要です。また、operator =およびコピーコンストラクター定義の純粋な仮想宣言を含めて、コンパイラーがそれらを自動生成しないようにすることもできます。
XAN

33
仮想デストラクタの代替は、保護されたデストラクタです。これにより、ポリモーフィックな破壊が無効になり、状況によってはより適切な場合があります。gotw.ca/publications/mill18.htmで「Guideline#4」を探します。
Fred Larson、

9
もう1つのオプションは=0、ボディを持つ純粋な仮想()デストラクタを定義することです。ここでの利点は、理論的には、vtableに有効なメンバーがないことをコンパイラーが理論的に確認し、それを完全に破棄できることです。ボディを備えた仮想デストラクタを使用すると、たとえば、デストラクタを(仮想的に)thisポインタを介して構築の途中で(構築されたオブジェクトがまだParentタイプである場合)呼び出すことができるため、コンパイラは有効なvtableを提供する必要があります。したがって、this構築中に仮想デストラクタを明示的に呼び出さない場合:)コードサイズを節約できます。
Pavel Minaev 2009

51
C ++の典型的な答えは、トップの答えが直接質問に答えない(明らかにコードは完璧です)代わりに、単純な答えを最適化します。
Tim

18
C ++ 11では、overrideキーワードを指定してコンパイル時の引数と戻り値の型のチェックを許可できることを忘れないでください。たとえば、児童の宣言ではvirtual void OverrideMe() override;
Sean

245

純粋な仮想メソッドでクラスを作成します。これらの仮想メソッドをオーバーライドする別のクラスを作成して、インターフェースを使用します。

純粋仮想メソッドは、仮想として定義され、0に割り当てられたクラスメソッドです。

class IDemo
{
    public:
        virtual ~IDemo() {}
        virtual void OverrideMe() = 0;
};

class Child : public IDemo
{
    public:
        virtual void OverrideMe()
        {
            //do stuff
        }
};

29
IDemoには何もしないデストラクタが必要です。これにより、実行する動作が定義されます。IDemo * p = new Child; / *何でも* / pを削除;
エヴァンテラン

11
ChildクラスのOverrideMeメソッドが仮想であるのはなぜですか?それは必要ですか?
Cemre 2012年

9
@Cemre-いいえ、それは必要ではありませんが、それでも害はありません。
PowerApp101

11
一般に、仮想メソッドをオーバーライドするときは常に、キーワードを「仮想」にしておくことをお勧めします。必須ではありませんが、コードをより明確にすることができます。それ以外の場合は、そのメソッドを多態的に使用できること、または基本クラスに存在することさえできません。
ケビン

27
@Kevin Except with with overrideC ++ 11
keyser

146

C#/ Javaで抽象基本クラスに加えて特別なインターフェイスタイプカテゴリがある理由は、C#/ Javaが多重継承をサポートしていないためです。

C ++は多重継承をサポートしているため、特別な型は必要ありません。非抽象(純粋仮想)メソッドのない抽象基本クラスは、C#/ Javaインターフェースと機能的に同等です。


17
インターフェイスを作成できるようにして、それほど多くのタイプ入力を行わないようにしておくとよいでしょう(virtual、= 0、virtual destructor)。また、多重継承は私にとって本当に悪い考えのように思われ、実際に使用されるのを見たことはありませんが、インターフェースは常に必要です。C ++コミュニティに悪い影響を与えても、インターフェースが欲しいからといってインターフェースが導入されることはありません。
公開

9
Ha11owed:インターフェースがあります。それらは純粋な仮想メソッドを持ち、メソッド実装を持たないクラスと呼ばれます。
Miles Rout

6
@doc:java.lang.Threadには、おそらくオブジェクトに含めたくないメソッドと定数があります。スレッドおよびパブリックメソッドcheckAccess()を使用する別のクラスから拡張する場合、コンパイラは何をすべきですか?C ++のように、厳密に名前が付けられたベースポインターを本当に使用しますか?これは悪いデザインのようです。通常、多重継承が必要であると考える場合は、構成が必要です。
2014年

4
@ Ha11owedだったので、詳細は覚えていませんが、クラスに含めたいメソッドと定数があり、さらに重要なのは、派生クラスオブジェクトをThreadインスタンスにしたかったことです。多重継承は、設計だけでなく構成も悪い場合があります。それはすべてケースに依存します。
文書14年

2
@デイブ:本当に?Objective-Cにはコンパイル時の評価とテンプレートがありますか?
Deduplicator

51

C ++には「インターフェース」自体の概念はありません。私の知る限り、インターフェースは多重継承の欠如を回避するためにJavaで最初に導入されました。この概念は非常に有用であることが判明し、C ++でも抽象基本クラスを使用して同じ効果を得ることができます。

抽象基本クラスは、少なくとも1つのメンバー関数(Javaの用語ではメソッド)が、次の構文を使用して宣言された純粋な仮想関数であるクラスです。

class A
{
  virtual void foo() = 0;
};

抽象基本クラスはインスタンス化できません。つまり、クラスAのオブジェクトを宣言することはできません。Aからクラスを派生させることしかできませんが、の実装を提供しない派生クラスfoo()も抽象化されます。抽象化をやめるために、派生クラスはそれが継承するすべての純粋仮想関数の実装を提供する必要があります。

抽象基本クラスは、純粋な仮想ではないデータメンバーとメンバー関数を含むことができるため、インターフェース以上のものになる可能性があることに注意してください。インターフェースに相当するものは、純粋な仮想関数のみを持つデータのない抽象基本クラスです。

また、Mark Ransomが指摘したように、抽象基本クラスは、基本クラスと同様に、仮想デストラクタを提供する必要があります。


13
「多重継承の欠如」以上に、多重継承に取って代わると思います。多重継承は問題を解決するよりも多くの問題を引き起こすため、Javaは最初からこのように設計されていました。良い答え
OscarRyz 2008年

11
オスカー、それはあなたがJavaを学んだC ++プログラマか、その逆かによって異なります。:)私見、C ++のほとんどすべてのように、慎重に使用すると、多重継承によって問題が解決します。「インターフェース」抽象基本クラスは、多重継承の非常に賢明な使用例です。
ディマ

8
@OscarRyz間違い。MIは誤用された場合にのみ問題を引き起こします。MIで最も疑わしい問題は、代替設計(MIなし)でも発生します。人々がMIのデザインに問題を抱えているとき、それはMIのせいです。SIに設計上の問題がある場合、それは彼ら自身の責任です。「死のダイヤモンド」(継承の繰り返し)はその好例です。MIバッシングは純粋な偽善ではありませんが、近いです。
curiousguy 2012

4
意味的には、インターフェースは抽象クラスとは異なるため、Javaのインターフェースは単なる技術的な回避策ではありません。インターフェースと抽象クラスのどちらを定義するかは、技術的な考慮事項ではなく、セマンティクスによって決まります。いくつかのインターフェース「HasEngine」を想像してみましょう。それはアスペクトであり、機能であり、非常に異なるタイプ(クラスまたは抽象クラス)に適用/実装できるため、抽象クラスではなく、そのためのインターフェースを定義します。
Marek Stanley

2
@MarekStanley、あなたは正しいかもしれませんが、もっと良い例を選んでいただければ幸いです。インターフェースを継承するか、実装を継承するかという観点から考えたいと思います。C ++では、インターフェースと実装の両方を一緒に継承する(パブリック継承)か、実装のみを継承する(プライベート継承)ことができます。Javaでは、実装なしでインターフェースのみを継承するオプションがあります。
Dima

43

テストできる限り、仮想デストラクタを追加することは非常に重要です。で作成newおよび破棄されたオブジェクトを使用していますdelete

インターフェイスに仮想デストラクタを追加しない場合、継承されたクラスのデストラクタは呼び出されません。

class IBase {
public:
    virtual ~IBase() {}; // destructor, use it to call destructor of the inherit classes
    virtual void Describe() = 0; // pure virtual method
};

class Tester : public IBase {
public:
    Tester(std::string name);
    virtual ~Tester();
    virtual void Describe();
private:
    std::string privatename;
};

Tester::Tester(std::string name) {
    std::cout << "Tester constructor" << std::endl;
    this->privatename = name;
}

Tester::~Tester() {
    std::cout << "Tester destructor" << std::endl;
}

void Tester::Describe() {
    std::cout << "I'm Tester [" << this->privatename << "]" << std::endl;
}


void descriptor(IBase * obj) {
    obj->Describe();
}

int main(int argc, char** argv) {

    std::cout << std::endl << "Tester Testing..." << std::endl;
    Tester * obj1 = new Tester("Declared with Tester");
    descriptor(obj1);
    delete obj1;

    std::cout << std::endl << "IBase Testing..." << std::endl;
    IBase * obj2 = new Tester("Declared with IBase");
    descriptor(obj2);
    delete obj2;

    // this is a bad usage of the object since it is created with "new" but there are no "delete"
    std::cout << std::endl << "Tester not defined..." << std::endl;
    descriptor(new Tester("Not defined"));


    return 0;
}

前のコードをなしvirtual ~IBase() {};で実行すると、デストラクタTester::~Tester()が呼び出されないことがわかります。


3
このページのベストアンサーは、実用的でコンパイル可能な例を提供することでポイントを示します。乾杯!
Lumi

1
Testet ::〜Tester()は、objが「Testerで宣言」されている場合にのみ実行されます。
アレッサンドロL.

実際には、文字列privatenameのデストラクタが呼び出され、メモリ内でそれだけが割り当てられます。ランタイムに関する限り、クラスのすべての具象メンバーが破棄されると、クラスインスタンスも破棄されます。2つのPoint構造体が含まれるLineクラスで同様の実験を試みたところ、削除呼び出しまたは包含関数からの戻り時に両方の構造体が破壊された(Ha!)ことがわかりました。valgrindは0リークを確認しました。
クリスリード

33

私の答えは基本的に他の答えと同じですが、他にも重要なことが2つあると思います。

  1. インターフェイスで仮想デストラクタを宣言するか、保護された非仮想デストラクタを作成して、誰かがタイプのオブジェクトを削除しようとした場合の未定義の動作を回避しますIDemo

  2. 仮想継承を使用して、多重継承による問題を回避します。(インターフェースを使用すると、多くの場合、複数の継承があります。)

そして他の答えのように:

  • 純粋な仮想メソッドでクラスを作成します。
  • これらの仮想メソッドをオーバーライドする別のクラスを作成して、インターフェースを使用します。

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
            virtual ~IDemo() {}
    }

    または

    class IDemo
    {
        public:
            virtual void OverrideMe() = 0;
        protected:
            ~IDemo() {}
    }

    そして

    class Child : virtual public IDemo
    {
        public:
            virtual void OverrideMe()
            {
                //do stuff
            }
    }

2
インターフェイスにデータメンバーがないため、仮想継承は必要ありません。
ロボサイド

3
仮想継承はメソッドにとっても重要です。それがなければ、たとえその「インスタンス」の1つが純粋な仮想であるとしても、OverrideMe()であいまいさが発生します(これを自分で試しただけです)。
Knarf Navillus 2012

5
@Avishay_ " インターフェイスにデータメンバーがないため、仮想継承は必要ありません。 " 不正解です
curiousguy 2012

WinAVR 2010に同梱されているバージョン4.3.3のように、一部のgccバージョンでは仮想継承が機能しない可能性があることに注意してください:gcc.gnu.org/bugzilla/show_bug.cgi
id=

-1仮想保護されていないデストラクタを持っていることに対して申し訳ありません
ウルフ

10

C ++ 11では、継承を完全に回避できます。

struct Interface {
  explicit Interface(SomeType& other)
  : foo([=](){ return other.my_foo(); }), 
    bar([=](){ return other.my_bar(); }), /*...*/ {}
  explicit Interface(SomeOtherType& other)
  : foo([=](){ return other.some_foo(); }), 
    bar([=](){ return other.some_bar(); }), /*...*/ {}
  // you can add more types here...

  // or use a generic constructor:
  template<class T>
  explicit Interface(T& other)
  : foo([=](){ return other.foo(); }), 
    bar([=](){ return other.bar(); }), /*...*/ {}

  const std::function<void(std::string)> foo;
  const std::function<void(std::string)> bar;
  // ...
};

この場合、インターフェースには参照セマンティクスがあります。つまり、オブジェクトがインターフェースよりも長く存続することを確認する必要があります(値セマンティクスでインターフェースを作成することも可能です)。

これらのタイプのインターフェースには長所と短所があります。

最後に、継承は複雑なソフトウェア設計におけるすべての悪の根です。でショーン親の値セマンティクスと多型を概念ベース(強く推奨、この技術のより良いバージョンが説明されている)以下の場合を検討します。

MyShapeインターフェイスを使用して多形的に形状を処理するアプリケーションがあるとします。

struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle

アプリケーションでは、YourShapeインターフェースを使用して異なる形状で同じことを行います。

struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...

ここで、アプリケーションで開発したいくつかの形状を使用したいとします。概念的には、形状は同じインターフェースですが、アプリケーションで形状を機能させるには、形状を次のように拡張する必要があります。

struct Circle : MyShape, YourShape { 
  void my_draw() { /*stays the same*/ };
  void your_draw() { my_draw(); }
};

まず、私の形状を変更することはまったく不可能かもしれません。さらに、多重継承はスパゲッティコードへの道を導きます(TheirShapeインターフェイスを使用する3番目のプロジェクトが登場すると想像してください...描画関数も呼び出すとどうなりますmy_drawか?)。

更新:非継承ベースのポリモーフィズムに関する新しい参照がいくつかあります。


5
TBHの継承は、C ++ 11のインターフェイスよりもはるかに明確ですが、一部の一貫性のない設計をバインドするための接着剤です。形状の例は現実から切り離されており、Circleクラスは貧弱なデザインです。このAdapterような場合はパターンを使用する必要があります。少し厳しいように聞こえる場合は申し訳ありませんが、Qt継承について判断する前に、実際のライブラリを使用してみてください。継承は人生をずっと簡単にします。
2014年

2
耳障りに聞こえません。形状の例はどのようにして現実から切り離されていますか?Adapterパターンを使用してCircleを修正する例(おそらくideone)を教えていただけますか?その利点を知りたいです。
gnzlbg 2014年

はい、この小さな箱に入れてみます。まず最初に、自分のアプリケーションを作成する前に、通常、作業を安全にするために「MyShape」などのライブラリを選択します。そうでなければ、どうしてあなたSquareはまだそこにいないのか知ることができますか?予知?それが現実から切り離されている理由です。そして実際には、「MyShape」ライブラリに依存することを選択した場合、最初からそのインターフェースに採用できます。形状の例では多くのナンセンスがあります(そのうちの1つは2つのCircle構造体があることです)が、アダプターは次のようになります-> ideone.com/UogjWk
doc

2
その時、それは現実から切り離されていません。会社Aが会社Bを購入し、会社BのコードベースをAに統合する場合、2つの完全に独立したコードベースがあります。それぞれに異なるタイプのシェイプ階層があると想像してください。それらを継承と簡単に組み合わせることができず、C社を追加すると、大きな混乱が生じます。私はあなたがこの話を見る必要があると思います:youtube.com/watch?v=0I0FD3N5cgM私の答えは古いですが、類似点が表示されます。常にすべてを再実装する必要はありません。インターフェースで実装を提供し、可能であればメンバー関数を選択できます。
gnzlbg 2014年

1
私はビデオの一部を見ました、そしてこれは完全に間違っています。デバッグ以外の目的でdynamic_castを使用することはありません。ダイナミックキャストとは、デザインに問題があることを意味し、このビデオのデザインは意図的に間違っています:)。ガイはQtについてさえ言及していますが、ここでも彼は間違っています-QLayoutはQWidgetから継承されず、逆もまた同様です!
文書

9

上記のすべての良い答え。覚えておくべきもう1つのこと-純粋な仮想デストラクタを持つこともできます。唯一の違いは、まだ実装する必要があることです。

混乱していますか?


    --- header file ----
    class foo {
    public:
      foo() {;}
      virtual ~foo() = 0;

      virtual bool overrideMe() {return false;}
    };

    ---- source ----
    foo::~foo()
    {
    }

これを行いたい主な理由は、私が持っているように、インターフェイスメソッドを提供したいが、それらのオーバーライドをオプションにしたい場合です。

クラスをインターフェースクラスにするには純粋仮想メソッドが必要ですが、すべての仮想メソッドにはデフォルトの実装があるため、純粋仮想にするために残された唯一のメソッドはデストラクタです。

派生クラスでデストラクタを再実装することは大したことではありません-私は常に、派生クラスでデストラクタを再実装します。


4
なぜ、ああ、なぜ、この場合のdtorを純粋に仮想化したいのでしょうか?その利点は何でしょうか?派生クラスに何かを強制するだけで、dtorを含める必要はないと思われます。
ヨハンゲレル

6
あなたの質問に答えるために私の答えを更新しました。純粋な仮想デストラクタは、すべてのメソッドにデフォルトの実装があるインターフェースクラスを実現する有効な方法(実現する唯一の方法?)です。
ロディランド2008

7

MicrosoftのC ++コンパイラを使用している場合は、次のことを実行できます。

struct __declspec(novtable) IFoo
{
    virtual void Bar() = 0;
};

class Child : public IFoo
{
public:
    virtual void Bar() override { /* Do Something */ }
}

この方法が気に入ったのは、インターフェースコードが大幅に小さくなり、生成されるコードのサイズが大幅に小さくなるためです。novtableを使用すると、そのクラスのvtableポインターへのすべての参照が削除されるため、直接インスタンス化することはできません。こちらのドキュメントをご覧ください-novtable


4
novtable標準よりもvirtual void Bar() = 0;
フレキソ

2
これに追加されました(追加した欠落に気づき= 0;ました)。理解できない場合は、ドキュメントを読んでください。
Mark Ingram

私はそれなしでそれを読んで、= 0;それがまったく同じことをする非標準的な方法だと思った。
Flexo

4

そこに書かれていることへの少しの追加:

まず、デストラクタも純粋に仮想であることを確認してください

第2に、適切な対策のために、実装するときは(通常ではなく)仮想的に継承したい場合があります。


概念的には継承されたクラスのインスタンスが1つしかないことを意味するため、仮想継承が好きです。確かに、ここのクラスにはスペース要件がないため、不要な場合があります。私はしばらくC ++でMIを実行していませんが、非仮想継承はアップキャストを複雑にしませんか?
ウリ

なぜ、ああ、なぜ、この場合のdtorを純粋に仮想化したいのでしょうか?その利点は何でしょうか?派生クラスに何かを強制するだけで、dtorを含める必要はないと思われます。
ヨハンゲレル

2
インターフェイスへのポインタを通じてオブジェクトが破棄される状況がある場合は、デストラクタが仮想であることを確認する必要があります...
Uri

純粋な仮想デストラクタには何の問題もありません。必須ではありませんが、問題はありません。派生クラスにデストラクタを実装することは、そのクラスの実装者にとって大きな負担にはなりません。これを行う理由については、以下の私の回答を参照してください。
ロディランド2008

仮想継承の場合は+1。インターフェイスを使用すると、クラスが2つ以上のパスからインターフェイスを派生する可能性が高くなるためです。私はインターフェースで保護されたデストラクタを選びます。
2014年

4

NVI(非仮想インターフェイスパターン)で実装されたコントラクトクラスを検討することもできます。例えば:

struct Contract1 : boost::noncopyable
{
    virtual ~Contract1();
    void f(Parameters p) {
        assert(checkFPreconditions(p)&&"Contract1::f, pre-condition failure");
        // + class invariants.
        do_f(p);
        // Check post-conditions + class invariants.
    }
private:
    virtual void do_f(Parameters p) = 0;
};
...
class Concrete : public Contract1, public Contract2
{
private:
    virtual void do_f(Parameters p); // From contract 1.
    virtual void do_g(Parameters p); // From contract 2.
};

他の読者のために、ジム・ヒスロップとハーブ・サッターによるこのドブズ博士の記事「会話:事実上あなたのもの」は、なぜNVIを使用したいのかについてもう少し詳しく述べています。
user2067021

また、ハーブサッターによるこの記事「仮想性」。
user2067021

1

私はまだC ++開発の新人です。私はVisual Studio(VS)から始めました。

しかし、__interfaceVS (.NET)については誰も言及していないようです。これがインターフェースを宣言する良い方法であるかどうかは、私によくわかりません。しかし、それは追加の執行を提供するようです(ドキュメントで言及されています)。virtual TYPE Method() = 0;自動的に変換されるため、明示的にを指定する必要はありません。

__interface IMyInterface {
   HRESULT CommitX();
   HRESULT get_X(BSTR* pbstrName);
};

ただし、.NETでのみ使用できるため、クロスプラットフォームコンパイルの互換性について懸念があるため、使用しません。

何か面白いことがあったらシェアしてください。:-)

ありがとう。


0

それがvirtualインターフェースを定義する事実上の標準であるのは事実ですが、C ++のコンストラクターに付属している古典的なCのようなパターンを忘れないでください。

struct IButton
{
    void (*click)(); // might be std::function(void()) if you prefer

    IButton( void (*click_)() )
    : click(click_)
    {
    }
};

// call as:
// (button.*click)();

これには、クラスを再構築せずにイベントランタイムを再バインドできるという利点があります(C ++には多相型を変更するための構文がないため、これはカメレオンクラスの回避策です)。

チップ:

  • これから基本クラスとして継承し(仮想と非仮想の両方が許可さclickれます)、子孫のコンストラクターに入力します。
  • protectedメンバーとして関数ポインターがあり、public参照やゲッターがある場合があります。
  • 上記のように、これにより実行時に実装を切り替えることができます。したがって、状態を管理する方法でもあります。ifコード内のsと状態の変化の数によっては、これswitch() esまたはifs よりも速い場合があります(所要時間は約3〜4 if秒と予想されますが、常に最初に測定します。
  • std::function<>関数ポインタを選択すると、内のすべてのオブジェクトデータを管理できる場合ありますIBase。この時点から、値の回路図を作成できますIBase(たとえば、std::vector<IBase>機能します)。コンパイラとSTLコードによっては、これ遅くなる可能性があることに注意してください。また、現在のの実装でstd::function<>は、関数ポインターや仮想関数(これは将来変更される可能性があります)と比較すると、オーバーヘッドが発生する傾向があります。

0

これはabstract classC ++標準での定義です

n4687

13.4.2

抽象クラスは、他のクラスの基本クラスとしてのみ使用できるクラスです。抽象クラスのオブジェクトは、そこから派生したクラスのサブオブジェクトとして作成することはできません。純粋な仮想関数が少なくとも1つあるクラスは抽象クラスです。


-2
class Shape 
{
public:
   // pure virtual function providing interface framework.
   virtual int getArea() = 0;
   void setWidth(int w)
   {
      width = w;
   }
   void setHeight(int h)
   {
      height = h;
   }
protected:
    int width;
    int height;
};

class Rectangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height); 
    }
};
class Triangle: public Shape
{
public:
    int getArea()
    { 
        return (width * height)/2; 
    }
};

int main(void)
{
     Rectangle Rect;
     Triangle  Tri;

     Rect.setWidth(5);
     Rect.setHeight(7);

     cout << "Rectangle area: " << Rect.getArea() << endl;

     Tri.setWidth(5);
     Tri.setHeight(7);

     cout << "Triangle area: " << Tri.getArea() << endl; 

     return 0;
}

結果:長方形領域:35三角形領域:17

抽象クラスがどのようにgetArea()の観点からインターフェースを定義し、他の2つのクラスが同じ関数を実装したが、形状に固有の領域を計算するアルゴリズムが異なるのを見てきました。


5
これはインターフェースとは見なされていません!これは、オーバーライドが必要な1つのメソッドを持つ抽象基本クラスです。インターフェースは通常、メソッド定義のみを含むオブジェクトです。他の「契約」クラスは、インターフェースを実装するときに満たす必要があります。
Guitarflow 2014年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.