LoginSignup
0
1

More than 3 years have passed since last update.

Ruby基礎文法入門を焼肉で

Last updated at Posted at 2020-12-18

この記事について

初学者による、Ruby基礎文法入門の、学習内容アウトプット・自身の振り返り用メモです。
楽しくメモするために、値が大体焼肉です。

参考資料
Progate Ruby学習コース
ドットインストール Ruby入門

【文字列】

文字列

  • 「"」:特殊文字、式展開を使える
  • 「'」:値がそのまま出力される
  • 「\n」:改行
  • 「\t」:タブを表示
  • 「* 数値」:数値回数表示する

文字列学習時に出てきたメソッド

learning.rb
price.upcase # 文字列を大文字で返す
price.upcase! # 文字列を大文字で返しつつ、元の文字列も大文字に書き換える
price.reberse # 文字列を逆順に返す
price.emyty? # 文字列が空かどうか調べる
price.include?("t") # 特定の文字が含まれているか調べる

【数値】

learning.rb
p 10 + 3 # 13
p 10 * 3 # 30
p 2.4 * 2 # 4.8
p 10 / 3 # 3
p 10.0 / 3 # 3.33333...(小数点以下を表示)
p 10 % 3 # 1
p Rational(2, 5) + Rational(3, 4) #(分数 23/20)
p 2/5r + 3/4r #(分数Rationalの省略系)

p 52.6.round # 四捨五入
p 52.6.floor # 小数点以下切り捨て ふろあ
p 52.6.ceil # 小数点以下切り上げ すぃーる

【コメント】

  • 行の先頭に「#」を書くと、その行がコメントになる
  • =begin〜=endで囲った行は全てコメントになる
learning.rb
# 焼肉食べてないのでインスタンス変数あたりから理解が乏しい

=begin
なので冷静に情報を整理したり、他記事を参考に
理解を深める、もしくは焼肉を食べry
=end

【出力の違い】

「p」はダブルクォーテーションで囲まれて表示される
複雑な構造の場合にこれは文字列だと分かりやすいよう表示してくれる

learning.rb
print "Do you like Yakiniku?" #改行なしで出力
puts "Do you like Yakiniku?" #改行ありで出力
p "Do you like Yakiniku?" #デバッグ用

【変換】

数値と文字変換

learning.rb

p 29 + "29".to_i # 整数に変換「too integer」 to_i
p 29 + "29".to_f # 浮動小数点数に変換「to float」 to_f
p 29.to_s + "29" # 文字列に返還「String」 to_s

ハッシュと配列の返還

learning.rb
# t_a (to Array) ハッシュを配列に変換
# to_ #h(to Hash) 配列をハッシュに変換

price = {上タン: 1800, 上ハラミ: 1500}
p price.to_a.to_h

【%記法】

learning.rb
puts %Q(hello) #""で囲った文字列を表現
puts %(hello) #上記のQは省略可能
puts %q(hello) #''で囲った文字列を表現

なぜ%記法を使うのか?

「""」や「''」の中で、" 'を使いたい場合
区切り文字ではないという意味で「\」をつける必要があるが
%記法は必要はなく、見やすい場合がある

learning.rb
puts "he\"llo"  puts %(hello)
puts 'he\'llo'  puts %q(hello)

配列で文字列を管理したい場合の%記法

値を多く書くのが場合はこういう書き方もできる

learning.rb
p ["red", "blue"]  p %W(red blue)
p ['red', 'blue']  p %w(red blue)

【書式付きで文字列に値を埋め込む】

"文字列" % 値
書式付きでいろいろな値を表示する

  • %s:文字列
  • %d:整数
  • %f:浮動小数点数

文字列

learning.rb
p "name: %s" % "熟成カルビ"
p "name: %10s" % "熟成カルビ" # 10桁分の幅を確保
p "name: %-10" % "熟成カルビ" # 10桁分を確保+左寄せ

数値

learning.rb
p "id: %05d, rate: %10.2f" % [29, 2,929]
# 値が複数の場合は配列で渡す
# idは整数、rateは浮動小数点数の場合
# idは5桁にしたいが、5桁に満たない場合は0で埋める
# rateは少数点の前が10桁、小数点以下が2桁

printf/Sprintf

printf
書式付きで文字列を表示するための命令

learning.rb
printf("name: %10s\n", "taguchi")
printf("id: %05d, rate: %10.2f\n", 355, 3.284)

Sprintf
表示するのではなく文字列を返してくれる命令
文字列が帰ってくるのでそれをpで表示

learning.rb
p sprintf("name: %10s\n", "taguchi")
p sprintf("id: %05d, rate: %10.2f\n", 355, 3.284)

【配列】

learning.rb
colors = ["red", "blue", "yellow"]

p colors[0] # 添字(0からスタート)を出力
p colors[-1] # 末尾から数えた添字(末尾が1)を出力
p colors[0..2] # 0から2を出力
p colors[0...2] # 0から1(2の直前まで)を出力
p colors[5] # nil(何もない)

colors[0] = "pink" # 指定した添字の値を変更
colors[1..2] = ["white", "black"] # 指定した番号をまとめて変更
colors.push("gold") # 末尾に値を追加
colors << "silver" # 末尾に値を追加する省略形

配列学習時に出てきたメソッド

learning.rb
p colors.size # 要素の数を示す
p colors.sort # 要素の並び替えをしてくれる

【ハッシュ】

キーと値をペアにして複数の値をまとめて管理する方法(オブジェクト)

配列とハッシュの違い

配列 :複数の値を並べて管理
ハッシュ :それぞれの値にキーと呼ばれる名前をつけて管理

配列は要素を[ ]で囲むが、ハッシュは{ }で囲む
{キー1 => 値1, キー2 => 値2}

キーと値の間は「=>」でつなぐ
配列と同様に、要素と要素はコンマ(,)で区切る

learning.rb
prices = {"bibimbap" => 800, "gukbap" => 700}

シンボルオブジェクト

:から始まる識別子のようなオブジェクト
キーは「シンボルオブジェクト」がよく使われる
文字列を使うより、動作が高速でRubyではよく使われる

learning.rb
prices = {:bibimbap => 800, :gukbap => 700}

上記のシンボルを使ったハッシュはよく使う為、短い記法がある

learning.rb
prices = {bibimbap: 800, gukbap:700}

ハッシュ各要素へのアクセスは配列と同じ

learning.rb
p prices[:bibimbap] # シンボルを使う

ハッシュの要素を更新

learning.rb
prises[:bibimbap] = 900 # 更新

ハッシュの要素を追加
ハッシュ[新しいキー] = 値と書くことで要素を追加
既にあるキーを指定すると、要素の追加ではなく更新になるので注意

learning.rb
prices[:Namul] = 650 # 追加

ハッシュ学習時に出てきたメソッド

learning.rb
p prices.size # 要素の数を引っ張ってくるメソッド
p prices.keys # キーの一覧を引っ張ってくるメソッド
p prices.values # 値の一覧を引っ張ってくるメソッド
p prices.has_key?(:gukbap) # そのキーがあるか調べるメソッド

【条件分岐】

ユーザーから入力を受け付ける命令・メソッド「gets」

learning.rb
price = gets.to_i #数値に変換する場合to_i
signal = gets.chomp
# getsは一行読み込むが最後に改行コードがついている
# chompというメソッドでその最後の改行コードを取り除く

if

learning.rb
if money > 15000
    puts "焼肉おごりますよ!"
elsif money > 7000
    puts "焼肉行きましょう!"
else
    puts "焼肉が食べたい..."
end

単純な条件分岐の場合はifを後ろに書くこともできる

learning.rb
puts "焼肉おごりますよ!" if money > 15000

case

値に応じて何らかのメッセージを出し分けたい時の処理
下記はifで実現できるが、caseを使うとすっきり書ける場合もある

learning.rb
case meat
when "カルビ"
  puts "上カルビにしましょ!"
when "ロース", "ハラミ" #コンマで区切って並べることもできる
  puts "上にしましょ!"
when "上タン塩"
  puts "タンしか勝たん"
else
  puts "タンは絶対オーダーしましょう!"

【繰り返し処理】

使い分け

  • whileは条件
  • timesは指定した数
  • for(each)は要素の数

while

learning.rb
i= 0 #iを0で初期化

while i < 10 do #10未満で繰り返す
  puts "#{i}: I want to eat yakiniku"
  i += 1
end

time

繰り返しの回数が決まっている場合に便利
doからendの中に行いたい処理を書く

learning.rb
10.times do |i|
    puts "#{i}: I want to eat yakiniku"
end
# 何回目のループかを知りたい時はdoの後ろに|変数|

doからendの間の処理が一行しかないときは{ }で代替できる

learning.rb
10.time { |i| puts "#{i}: hello" }

for

forでは何らかの集合的なオブジェクト(配列やハッシュ)
また、範囲を表すオブジェクトの要素数分だけを
何らかの処理を繰り返すことができる命令

learning.rb
for i in 15..29 do #doは省略可能
  p i
end

# 配列
for meat in ["tongue", "offal"] do
  p meat
end

# ハッシュ
for meat, price in {tongue:1000, offal:800} do
  p "#{meat}: #{price}"
end

each

forは内部的にはeachというメソッドを使っている為
forはeachを使って書き換えることができる

timeメソッド同様に、繰り返す処理が一行ぐらいであれば
doからendを{ }で囲って一行で表現することも可能

learning.rb
(15..29).each do |i|
    p i
end

配列
 ["tongue", "offal"].each do |meat|
    p meat
end

ハッシュ
{tongue:1000, offal:800}.each do |meat, price|
    p "#{meat}: #{price}"
end

loop

learning.rb
i = 0
loop do
     p i
     i += 1
end

永久にループする処理はループを抜ける命令とよく一緒に使われる

break

learning.rb
10.times do |i|
    if i == 7
        break #7の直前でループを抜ける
    end
    p i
end

next

learning.rb
10.times do |i|
    if i == 7
        next #7を1回スキップする
    end
    p i
end

break,nextは
loop含む、whileやforやtimesなどの繰り返し処理の中で使える

【メソッド】

複数の処理を1つにまとめたもの

メソッドの利点

メソッドを用いない場合は何度も同じような処理を書く必要があるが
共通の処理をメソッドにまとめることによって、シンプルに書くことができる

learning.rb
# メソッドを用いない場合
puts "こんばんは"
puts "私はカルビ王子です"
puts "こんばんは"
puts "私はタン先生です"
puts "こんばんは"
puts "私はハラミ大臣です"

# メソッドを用いた場合
def introduce(name)
  puts "こんにちは"
  puts "私は#{name}です"
end
introduce("カルビ王子")
introduce("タン先生")
introduce("ハラミ大臣")

メソッドの定義

「def メソッド名」と「end」の間にまとめたい処理を書く
このことを「メソッドを定義する」と言う
右の図では、2つの出力を行う、introduceメソッドを定義しています。

learning.rb
書式
def メソッド名
 処理
end

def introduce
  puts "こんにちは"
  puts "私が焼肉です"
end
introduce #introduceメソッドを呼び出し

【引数】

引数を受け取るメソッドの定義・呼び出し

learning.rb
書式
def メソッド名(引数名)
  処理
end
メソッド名(値) # メソッドの呼び出し + 値が引数に代入

メソッド内で引数を使用する

learning.rb
def introduce(name)
  puts "こんばんは"
  puts "私が#{name}です" # 引数はメソッド内で変数のように使える
end
introduce("焼肉のお兄さん")

引数のあるメソッドは、引数を渡さずに呼び出すことができない
エラーになるので注意

複数の因数を受け取る・呼び出す

learning.rb
def meat(name, price) # 左から第1引数、第2引数
 puts "#{name}"
 puts "#{price}円"
end
meat("豚トロ", 800)

【戻り値】

戻り値のあるメソッド

メソッドの中でreturnを使うと、呼び出し元で値を受け取れる
「return 値」と書くことで、メソッドはその値を戻り値として返します。

learning.rb
書式
def メソッド名
  return  #呼び出し元に値を返す(=戻り値)
end

def add(a,b)
  return a + b # aとbを足した値が戻り値として呼び出される
end

戻り値を受け取る

戻り値がある場合、メソッドの呼び出し部分がそのまま戻り値に置き換わる
ソッドの呼び出し部分を変数に代入するように書くことで
メソッドの戻り値を受け取ることができます。

learning.rb
def add(a,b)
  return a + b
end
sum = add(1,3) # 呼び出し部分
puts sum # 出力 4

様々な戻り値

戻り値も引数と同様に、様々な値を用いることができる

if文で使うような条件式をreturnすると、
その条件式の結果として得られる真偽値(trueまたはfalse)を返すことができる
真偽値を返すメソッドは、メソッド名の末尾に「?」をつける慣習があり

learning.rb
def meat_free?(price) 
  return price >= 10000
end

if meat_free?(14000)
    puts "次回御来店の際は上タン塩サービス"
else
    puts "次回御来店の際はキムチサービス"
end

returnによる処理の終了

returnは戻り値を返すだけでなく、メソッドの処理を終了させる性質も持つ
よって、returnの後にあるメソッドの処理は実行されない

learning.rb
def add(a, b)
  return a + b # ここで処理が終了する
  puts "計算しました" #実行されない
end

複数のreturn

メソッドでは、条件分岐を組み合わせることで
複数のreturnを用いることができる

learning.rb
def price_with_shipping(price)
  if price >= 10000
    return price
  end
  return price + 500
end

puts "合計金額は3000円です"
puts "お支払い金額は、持帰り肉込みで#{price_with_shipping(8000)}円です"
# 8500円で出力
puts "商品の合計金額は10000円です"
puts "お支払い金額は、送料込みで#{price_with_shipping(10000)}円です"
# 10000円で出力

【キーワード引数】

引数の数が多くなると、呼び出し側で値がどの引数に入るのか分かりにくい
キーワード引数を用いることで、呼び出し側で引数を明記することができる

通常のメソッドの書き方に加えて
1.定義側で、引数の後にコロンを付ける
2.呼び出し側で、値の前に引数名を書く
とすることで、キーワード引数を持つメソッドを書くことができる

learning.rb
# 今までの書き方
def introduce(name, age, food)
  puts "私は#{name}です"
  puts "年齢は#{nage}歳です"
 puts "好きな食べ物は#{food}です"
end
introduce("焼肉のお兄さん", 37, "焼肉")
# それぞれの値がどの引数に入るのかわかりにくい

# キーワード引数を用いた書き方
def introduce(name:, age:, food:) #コロンつける
  puts "私は#{name}です"
  puts "年齢は#{nage}歳です"
 puts "好きな食べ物は#{food}です"
end
introduce(name:"焼肉のお兄さん", age:37, food:"焼肉")
#呼び出した時に引数名を明記した書き方

【クラス】

  • 設計図:クラス
  • もの:インスタンス

クラスは設計図のようなもの
その設計図からつくる実際の「もの」にあたるものがインスタンス
インスタンスが持つ情報である「インスタンス変数」と
インスタンスに対して呼び出す「インスタンスメソッド」は
クラスの中で定義する

インスタンスを生成するSTEP

STEP1 クラスを用意する
STEP2 クラスからインスタンスを生成する
STEP3 インスタンスに情報を追加する

クラスの定義

クラスは「class クラス名」とすることで定義できる
クラス名は必ず「大文字」で始め + 「end」を書く必要がある

learning.rb
class Menu # クラス名は大文字で始める
end # endを忘れずに

インスタンス変数

attr_accessor
情報を持たせるには、「attr_accessor シンボル」とする
Menuクラスのインスタンスにnameという情報を持たせることができる
また、この「name」という情報のことを「インスタンス変数」と呼ぶ

learning.rb
class Menu
  attr_accessor :name
end
#Menuクラスのインスタンスにnameという情報をもたせることができる
#このnameという情報のことを、インスタンス変数と呼ぶ

インスタンスの生成

クラス(設計図)を元に新しくインスタンスを生成するには「クラス名.new」
また「変数名 = クラス名.new」で生成したインスタンスを変数に代入できる

インスタンス変数に値を代入

インスタンスに情報をもたせるには、クラスで用意した
インスタンス変数に値を代入する必要がある
具体的には「インスタンス.変数名 = 値」とすることで
そのインスタンス変数に値をセットすることができる

learning.rb
class Menu
  attr_accessor :name
  attr_accessor :price
end
menu1 = Menu.new
menu1.name = "シャトーブリアン" #シャトーブリアンを代入
puts menu1.name #出力 シャトーブリアン

【インスタンスメソッド】

クラスの中でメソッドを定義・呼び出し

クラスの中で定義したメソッドは、インスタンスに対して使うようにして呼び出す
具体的には「インスタンス.メソッド名」のようにすることで
そのメソッドを呼び出すことができる=インスタンスメソッド

learning.rb
class Menu
  attr_accessor :name
  attr_accessor :price
  def show
    puts "私はお肉です"
  end
end
menu1 = Menu.new
menu1.show #インスタンス.メソッド名で呼び出し

インスタンスメソッドも、引数を受け取ったり戻り値を返すことができる

learning.rb
class Menu
    |
  def show(data) # 引数
    return "私は#{data}です" # 戻り値
  end
end
menu1 = Menu.new
puts menu1.show("お肉")

インスタンスメソッドの中でインスタンス変数を扱う

インスタンスメソッドの中では「self」を用いて「self.変数名」とすることで
インスタンス変数を扱うことができる
インスタンスメソッドでは変数「self」に
呼び出したインスタンス自身が代入されている

learning.rb
class Menu
  |
 def show_name
  puts "私は#{self.name}です"
 end
end
menu1 = Menu.new
menu1.name = にんにく
menu1.show_name # インスタンスメソッドの呼び出し

initializeメソッド

インスタンスを生成した直後に処理を実行することができる
「クラス名.new」でインスタンスを生成した直後に自動で呼び出される

initializeメソッドは、他のインスタンスメソッドと同じように定義できる
下記は、「Menu.new」でMenuインスタンスが生成された直後に
initializeメソッドが呼び出され、その中の処理が実行されている

learning.rb
class Menu
    |
  def initialize
    puts "メニューが生成されました"
  end
    |
end
menu1 = Menu.new
# Menu.newが実行されると自動でinitializeメソッドが呼び出される

initializeメソッドでインスタンス変数を扱う

インスタンスメソッドの中では「self.変数名」でインスタンス変数を扱える為
「self.変数名 = 値」でインスタンス変数に値を代入できる

learning.rb
class Menu
    |
  def initialize
    self.name = "マルチョウ"
  end
    |
end
menu1 = Menu.new
puts menu1.name
# インスタンス生成直後に"マルチョウ"をインスタンス変数nameに代入

initializeメソッドの引数

initializeメソッドは通常のインスタンスメソッドと同じように引数を渡せる
その際「クラス.new」に対して引数を渡すことで
initializeメソッドにその値を渡すことができる

learning.rb
class Menu
    |
  def initialize(message) # 肉は一度しか裏返しませんが渡ってくる
    puts message
  end
    |
end
menu1 = Menu.new("肉は一度しか裏返しません"

initializeメソッドでインスタンス変数に値を代入

initializeメソッドでインスタンス変数に引数の値を代入することで
インスタンスごとにインスタンス変数の値を変えることができる
その際に、キーワード引数を使うことで見やすく書ける

learning.rb
class Menu
    |
  def initialize(name:, price:)
    self.name = name
    self.price = price
  end
   |
end
menu1 = Menu.new(name: "マルチョウ", price: 800)

【ファイルの分割】

(分割時の例)
index.rbからMenuクラスの定義をmenu.rbに移動させたら
menu.rbのコードをindex.rbでも使えるようにする
index.rbの一番上の行で「require "./menu"」とすることで
menu.rbのコードを読み込めるようになる

learning.rb
# index.rb
require "./menu"
menu1 = Menu.new(name: "ハツ", ・・・)
  |

Menuクラスから生成したインスタンスも、配列の要素にすることが可能
各インスタンスを要素とする配列を変数menusに代入し
その配列に対してeach文を用いることで各メニューを表示してみた場合が下記

learning.rb
# index.rb
menu1 = Menu.new(name: "ハツ", price: 900)
menu2 = Menu.new(name: "レバー", price: 800)
  |
menus = [menu1, menu2,  ....]
menus.each do |menu| # each文を用いて、1つずつメニューを表示
  puts menu.info
end

【継承】

あるクラスを元にして新たなクラスをつくることを「継承」と呼ぶ
「class 新しいクラス名 < 元となるクラス名」とすることで
他のクラスを継承して、新しいクラスを定義できる
新しいクラスは「子クラス」
元となるクラスは「親クラス」

learning.rb
require "./menu"
class Food < Menu
end

子クラスのインスタンス

子クラスは親クラスのインスタンス変数とインスタンスメソッドを引き継ぐ
下記例のようにFoodクラスのインスタンスは
Menuクラスのインスタンス変数やインスタンスメソッドを呼び出すことが出来る

learning.rb
# index.rb
food1 = Food.new(name: "アカセン", price: 900)
puts food1.name
puts food1.info

# menu.rb
class Menu
  attr_accessor :name
   |
  def info
    return "#{self.name} #{self.price}円"
  end
end

子クラスにインスタンス変数を追加する

子クラスにインスタンス変数を追加には通常通り「attr_accessor」を用いる
下記のFoodクラスはMenuクラスを継承しているので
・name ・price ・calorie
の3つのインスタンス変数を用いることができる

learning.rb
# index.rb
food1 = Food.new(name: "アカセン", price: 900)
puts food1.name
puts food1.info

# menu.rb
class Menu
  attr_accessor :name
  attr_accessor :price
   |
end

# food.rb
class Food < Menu
  attr_accessor :calorie # Foodクラスには「calorie」を追加
end

オーバーライド

親クラスにあるメソッドと同じ名前のメソッドを子クラスで定義すると
メソッドを上書きすることができる = 「オーバーライド」
オーバーライドをすると、子クラスのインスタンスは親クラスのメソッドではなく、
子クラスで定義したメソッドを呼び出すようになる

(オーバーライドの仕組み)
子クラスのインスタンスは、子クラスで定義したメソッドを優先して呼び出す
したがって、子クラスと親クラスに同名のメソッドがある場合は
子クラスのメソッドを呼び出すので、結果的にメソッドの内容が上書きされたようになる

super

オーバーライドしたメソッドの中で「super」とすることで、
親クラスの同名のメソッドを呼び出すことができる
あくまでメソッドを呼び出しているので、
親クラスのメソッドの定義に合わせて、superに対して引数を渡す必要がある

learning.rb
# menu.rb
class Menu
  attr_accessor :name
  attr_accessor :price
  def initialize(name:, price:)
    self.name = name
    self.price = price
  end
   |
end

# food.rb
class Food < Menu
  attr_accessor :calorie
  def initialize(name:, price:, calorie:)
    super(name: name, price: price) # 親クラスの同名メソッドを呼び出す
    self.calorie = calorie
  end
   |
end

【データクラス】

Dateクラス = 日付を扱うクラス
DateクラスはRubyがすでに用意しているクラスで
requireを用いて読み込むことで、自分でクラスを定義しなくても使うことができる

(すでに用意されているクラスは、requireの書き方が少し異なるので注意)

learning.rb
require "date" 
#  "./date"ではないことに注意

Dateクラスのインスタンス

Dateクラスは今まで扱ってきたクラスと同様に
Date.newとすることでインスタンスを生成できる
Dateクラスのインスタンスをputsすると
下記のようにその日付を表示できる

learning.rb
reequire "date"
date1 = Date.new(2020,12,18) # 引数に「年月日」を渡してDateインスタンスを生成
puts date1
# 出力 2014-07-31

今日の日付のDateインスタンスを取得

Dateクラスでは、Date.todayとすることで、
今日の日付のインスタンスを作ることができる

learning.rb
require "date"
date1 = Date.today
puts date1

【クラスメソッド】

クラスメソッドは、「def クラス名.メソッド名」とすることで定義できる
インスタンスメソッドとの違いはメソッド名の前にクラス名を書く必要がある点

下記はMenuクラスに、「is_discount_day?」というクラスメソッドを定義

クラスメソッドは定義時と同じように、
「クラス名.メソッド名」とすることで呼び出すことができる

learning.rb
定義
def クラス名. メソッド名
  # 処理
end


class Menu
  |
  def Menu.is_discount_day?
    # 処理
  end
end
puts Menu.is_discount_day?

インスタンスメソッドとクラスメソッドの復習

インスタンスに対して呼び出すメソッドは「インスタンスメソッド」
クラスに対して呼び出すメソッドは「クラスメソッド」

learning.rb
class Menu
  |
  def info # インスタンスメソッドの定義
  end
end
menu1 = Menu.new
menu1.info # インスタンスメソッドは、インスタンスに対して呼び出す
learning.rb
class Menu
 def Menu.is_discount_day? # クラスメソッドの定義
 end
end
Menu.is_discount_day? # クラスメソッドは、クラスに対して呼び出す
0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1