自分の中で情報整理するために書いたC++とGo、Pythonに関するメモです。随時更新しています。
目次
クラス
クラスを作成する
C++でクラスを作成する
#include <iostream>
#include <utility>
class MyClass {
public:
std::string public_name;
MyClass(std::string public_name, std::string private_name) {
this->public_name = std::move(public_name);
this->private_name = std::move(private_name);
std::cout << "constructor called" << std::endl;
}
~MyClass() {
std::cout << "destructor called" << std::endl;
}
void show_name() {
std::cout << "public_name: " << this->public_name << std::endl;
std::cout << "private_name: " << this->private_name << std::endl;
}
private:
std::string private_name;
};
int
main() {
MyClass myclass("yamada", "taro");
myclass.show_name();
return 0;
}
Goでクラスを作成する
Goにはそもそもクラスがない。しかし関数を構造体に紐付けてクラスのような振る舞いをさせることができる。Goは大文字小文字で公開・非公開範囲が決まり、その単位はpackage単位となる(以下の例ではmain package)。
package main
import "fmt"
type MyClass struct {
Public_name string
Private_name string // プライベートではない
}
func (p *MyClass) print_name() {
fmt.Println(p.Public_name)
fmt.Println(p.Private_name)
}
func main() {
myclass := MyClass{"yamada", "taro"}
myclass.print_name()
}
Pythonでクラスを作成する
Pythonのクラスにはpublic
やprivate
はないが、メンバ変数の名前の先頭に__
(アンダースコアを2つ)を付けるとprivate
のような振る舞いをする。コンストラクタは__init__(self, 引数, …)
となり、デコンストラクタは__del__(self)
になる。
class MyClass:
def __init__(self, public_name, secret_name):
self.public_name = public_name
self.__secret_name = secret_name # privateにする
def __del__(self):
print("destructor called")
def print_name(self):
print(self.public_name)
print(self.__secret_name)
if __name__ == '__main__':
MyClass = MyClass("yamada", "taro")
# print(MyClass.__secret_name) ※privateのような振る舞いをするのでアクセスできない
MyClass.print_name()
クラスの継承
C++の継承
C++は基本クラス(BaseClass
)のコンストラクタとデコンストラクタが派生クラス(NewClass
)で自動的に実行される。派生クラスはpublic
とprotected
にアクセスできるがprivate_val
にはアクセスできない。
#include <iostream>
class BaseClass {
public:
BaseClass() {
std::cout << "BaseClass constructor" << std::endl;
}
~BaseClass() {
std::cout << "BaseClass destructor" << std::endl;
}
private:
int private_val{};
protected:
int protected_val{};
};
class NewClass : public BaseClass {
public:
NewClass() {
std::cout << "NewClass constructor" << std::endl;
}
~NewClass() {
std::cout << "NewClass destructor" << std::endl;
}
void show_val();
};
void NewClass::show_val() {
std::cout << "protected_val: " << protected_val << std::endl;
// std::cout << "private_val: " << private_val << std::endl; // これはアクセス不可
}
int main() {
NewClass newClass;
newClass.show_val();
return 0;
}
Goの継承
Goにはクラスがないため継承もない。ただし、構造体の中に別の構造体を定義することで継承のような振る舞いをさせることができる。以下のコードは基本クラスをBaseClass、派生クラスをNewClassとして模倣した例です。オーバーライドのようなこともできて、h.hello()
の実行結果はnew hello
が表示されます。
package main
import "fmt"
type BaseClass struct {
}
func (b *BaseClass) hello() {
fmt.Println("hello")
}
type MewClass struct {
BaseClass
}
func (n *MewClass) hello() {
fmt.Println("new hello")
}
func (n *MewClass) hi() {
fmt.Println("hi")
}
func main() {
var n MewClass
n.hello() // new hello
n.hi()
}
Pythonの継承
Pythonは継承元のコンストラクタとデコンストラクタを明示的に呼ばないと実行されない。
class BaseClass:
def __init__(self):
print("BaseClass constructor")
def __del__(self):
print("BaseClass destructor")
class SubClass(BaseClass):
def __init__(self):
super().__init__()
print("SubClass constructor")
def __del__(self):
super().__del__()
print("SubClass destructor")
if __name__ == '__main__':
sub = SubClass()
del sub
オーバーライド
C++のオーバーライド
派生クラスでメソッドを上書きする場合、基本クラスでvirtual
を付ける。virtual
を付けた関数は仮想関数となる。
#include <iostream>
class BaseClass {
public:
void func1() {
std::cout << "base func1" << std::endl;
}
virtual void func2() {
std::cout << "base func2" << std::endl;
}
};
class NewClass : public BaseClass {
public:
void func1() {
std::cout << "new func1" << std::endl;
}
void func2() override {
std::cout << "new func2" << std::endl;
}
};
int main() {
NewClass newClass;
newClass.func1(); // new func1
newClass.func2(); // new func2
BaseClass *p;
p = new NewClass;
p->func1(); // base func1
p->func2(); // new func1
return 0;
}
基本クラスに仮想関数がある場合、基本クラスのデコンストラクタをvirtual
で定義する必要がある(と教えられる)。なぜならば派生クラスを基本クラスのポインタとして利用する場合、派生クラスのデコンストラクタが実行されないから。たとえば次のコード。
#include <iostream>
class BaseClass {
public:
BaseClass() {
std::cout << "BaseClass constructor" << std::endl;
}
~BaseClass() {
std::cout << "BaseClass destructor" << std::endl;
}
virtual void func1() = 0;
};
class NewClass : public BaseClass {
public:
NewClass() {
std::cout << "NewClass constructor" << std::endl;
}
~NewClass() {
std::cout << "NewClass destructor" << std::endl;
}
void func1() override {
std::cout << "new func1" << std::endl;
}
};
int main() {
BaseClass *baseClass = new NewClass();
baseClass->func1();
delete baseClass; // NewClassのデコンストラクタが実行されない
return 0;
}
コンパイルして実行すると次のように表示される。NewClass
のデコンストラクタが実行されていない。
BaseClass constructor
NewClass constructor
new func1
BaseClass destructor
次にBaseClass
のデコンストラクタにvirtual
を付ける。
#include <iostream>
class BaseClass {
public:
BaseClass() {
std::cout << "BaseClass constructor" << std::endl;
}
virtual ~BaseClass() {
std::cout << "BaseClass destructor" << std::endl;
}
virtual void func1() = 0;
};
class NewClass : public BaseClass {
public:
NewClass() {
std::cout << "NewClass constructor" << std::endl;
}
~NewClass() override {
std::cout << "NewClass destructor" << std::endl;
}
void func1() override {
std::cout << "new func1" << std::endl;
}
};
int main() {
BaseClass *baseClass = new NewClass();
baseClass->func1();
delete baseClass;
return 0;
}
コンパイルして実行すると今度はNewClass
のデコンストラクタが実行されている。
BaseClass constructor
NewClass constructor
new func1
NewClass destructor
BaseClass destructor
派生クラスでリソースを確保している場合は開放されないままプログラムが継続されることになり、メモリリークなどの問題が発生します。このような事態を避けるためにも、仮想関数をメンバーに持つクラスはデコンストラクタにvirtual
を付ける必要があります。
抽象クラス
C++の抽象クラス
virtual void func1() = 0;
のように記述すると実装を伴わない純粋仮想関数となり、このクラスのインスタンスを生成できなくなる。
#include <iostream>
class BaseClass {
public:
virtual void func1() = 0;
};
class NewClass : public BaseClass {
public:
void func1() override {
std::cout << "new func1" << std::endl;
}
};
int main() {
NewClass newClass;
newClass.func1();
// BaseClass baseClass; ※抽象クラスはインスタンスを生成できない
return 0;
}
インターフェース
インターフェースはクラスやオブジェクトがどのようなメソッドを持つべきなのか定義するもので、実装を伴いません。
Javaのインターフェース
Javaには言語仕様としてインターフェースが用意されていて、interface
で定義しクラスではimplements
を使用して実装する。
interface Animal {
void eat();
void sleep();
}
class Dog implements Animal {
public void eat() {
System.out.println("Dog eats");
}
public void sleep() {
System.out.println("Dog sleeps");
}
}
C++のインターフェース
C++にはインターフェースはないけれど、やることは抽象クラスそのものです。
#include <iostream>
class Animal {
public:
virtual void eat() = 0;
virtual void sleep() = 0;
};
class Dog : public Animal {
public:
void eat() override {
std::cout << "Dog eats" << std::endl;
}
void sleep() override {
std::cout << "Dog sleeps" << std::endl;
}
};
int main() {
Dog dog;
dog.eat();
dog.sleep();
return 0;
}
Goのインターフェース
Goではtype
としてinterface
を定義できます。interfaceとして定義されたすべてのメソッドを実装した場合、自動的にインターフェースを実装したとみなされます。
package main
import "fmt"
type Animal interface {
Eat()
Sleep()
}
type Dog struct {
name string
}
func (d Dog) Eat() {
fmt.Println("Dog eats")
}
func (d Dog) Sleep() {
fmt.Println("Dog sleeps")
}
func (d Dog) ShowName() {
fmt.Println("Dog's name is ", d.name)
}
func EatAndSleep(a Animal) {
a.Eat()
a.Sleep()
//a.ShowName() ※これはインターフェースに含まれないのでエラー
}
func main() {
d := Dog{name: "Taro"}
d.ShowName()
EatAndSleep(d)
}
Pythonのインターフェース
Pythonにはインターフェースはありませんが、ABC
という抽象基底クラスを使ってインタフェースのような振る舞いをさせることができます。
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def eat(self):
pass
@abstractmethod
def sleep(self):
pass
class Dog(Animal):
def eat(self):
print("Dog eats")
def sleep(self):
print("Dog sleeps")
if __name__ == '__main__':
dog = Dog()
dog.eat()
dog.sleep()