ぷらねギアのぶろぐ

プログラミング・組み込み・モノづくり等(予定)

アップキャスト-派生クラスのインスタンスの引渡しについて

オブジェクト指向についてまだまだ初心者ですが,ちょっとした気付きをメモ.

 

クラスのあるメンバ関数を毎回実行させるような仕様にしたい際には,引数にインスタンスを入れ,関数内でそのメンバ関数を実行する.これにより,渡したインスタンスメンバ関数を実行することができる.

引き渡すインスタンスは型にしたクラスを継承した派生クラスでも同様にメンバ関数を実行することができる.ただし,呼び出されるのは親クラスのメンバ関数

 

引き渡したインスタンスのクラスでオーバーライドしたメンバ関数を実行するためにはその親クラスのメンバ関数を仮想関数にしておく必要がある模様.

 

以下サンプルコード 

#include<iostream>

//親クラス
class Hoge{
public:
virtual void run0(); //メンバ関数(仮想関数)
void run1(); //メンバ関数
};
void Hoge::run0(){
 std::cout << "run0 :This is Hoge class" << std::endl;
}
void Hoge::run1(){
 std::cout << "run1 :This is Hoge class" << std::endl;
}

//子クラス
class Koge :public Hoge{
 public:
  void run0();//メンバ関数(オーバーライド)
  void run1();//メンバ関数(オーバーライド)
};

void Koge::run0(){
 std::cout << "run0 :This is Koge class" << std::endl;
}

void Koge::run1(){
 std::cout << "run1 :This is Koge class" << std::endl;
}

//孫クラス
class Loge :public Koge{
 public:
  void run0();//メンバ関数(オーバーライド)
  void run1();//メンバ関数(オーバーライド)
};

void Loge::run0(){
 std::cout << "run0 :This is Loge class" << std::endl;
}
void Loge::run1(){
 std::cout << "run1 :This is Loge class" << std::endl;
}

void hogefunc(Hoge &a){
 a.run0();//メンバ関数のrun0を実行(親クラスでは仮想関数)
 a.run1();//メンバ関数のrun1を実行
}

int main(){
 Koge k;//子クラスのインスタンス生成
 Loge l;//孫クラスのインスタンス生成
  hogefunc(k);//子クラスのインスタンスを引渡し
 hogefunc(l);//孫クラスのインスタンスを引渡し
}
メンバ関数run0()は親クラスHogeにおいて仮想関数として宣言,メンバ関数run1()は仮想関数ではない.このHogeクラスを継承した子クラスKogeと孫クラスLogeでは2つのメンバ関数をオーバーライドしている.
また関数hogefunc()では型が親クラスHogeインスタンスを引数とし,関数内で引数のメンバ関数run0()とrun1()を実行させている.
これを実行させると
> ./hoge.out
run0 :This is Koge class
run1 :This is Hoge class
run0 :This is Loge class
run1 :This is Hoge class
このように子クラス・孫クラスどちらとも親クラスで仮想関数としたrun0()は渡したインスタンスのクラスでオーバーライドしたメンバ関数が実行されているのに対し,そうではないrun1()では親クラスのメンバ関数が実行されていることがわかる.
インスタンスを渡して派生クラスに応じて使い分けたい場合には,親クラスで仮想関数にしなくてはいけない(らしい).
(ややこしいな...) 

間違いがあったらごめんなさい.

2重振り子のあにめーしょん

ルンゲクッタにより数値的に非線形微分方程式の解析解が求まるようになったので、これを用いて2重振り子のあにめーしょんを作成。


グラフィックにはGLUTを用いた(GLUTによる「手抜き」OpenGL入門を参考)

運動方程式ラグランジュを使って手で求めた。(あってるか不安)

f:id:planetary_gear:20180301193320p:plain

f:id:planetary_gear:20180301161410p:plain

メインのコードはこんな感じ(汚くてごめんなさい)

<main.c>

  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<math.h>
  4. #include<unistd.h>
  5. #include<pthread.h>
  6. #include"animat.h"
  7. #include"arm.h" 

animat.hにはanimat.cで使う関数の定義等が入ってる。また構造体の型Vector4Dを定義している。メンバはdouble theta, phi,dottheta,dotphi。それぞれの角度と角速度にしている。

 

arm.hには2重振り子の長さや重さ、重力加速度等を#define。

 

  1. Vector4D runge_2hensu(Vector4D hensu);
  2. Vector4D shoki_jouken(void);
  3. double f_the(Vector4D *p);
  4. double f_phi(Vector4D *p);
  5. double cot(double x);
  6. double sec(double x);
  7. static void *simulation(void);
  8. Vector4D k;
  9.  
  10. pthread_mutex_t mutex;
  11.  
  12. int main(int ac, char *av[]) {
  13. //test animation
  14. pthread_mutex_init(&mutex,NULL);
  15. pthread_t pthread;
  16. //f
  17. pthread_create( &pthread, NULL,&simulation,NULL);
  18. InitialGlut(ac,av);
  19. }
  20.  

main()ではグラフィックの関数InitialGlut()を呼び出す前に スレッドによって関数simulation()を呼び出し、並行処理させている。そんな便利なもんがあるとは今まで知らなかった。いろいろショック。

 

  1. static void *simulation(void){
  2. FILE *outputfile;
  3. char filename[100];
  4. sprintf(filename,"bane_2ji_th0_%5.3f_phi0_%5.3f_m_%5.3f_l1_%5.3f_l2_%5.3f_v0_%5.3f.txt",TH0,PH0,M2,L1,L2,V0);
  5. outputfile = fopen(filename,"w");
  6. if (outputfile == NULL){
  7. printf("=====ERROR=========");
  8. exit(1);
  9. }
  10.  
  11. //fprintf(outputfile,"theta0_%15.6f v0_%15.6f\n",TH0,V0);
  12. static float t = 0.0;
  13. static Vector4D kakudo,kakudop;
  14. static double x,y;
  15. static int i;
  16. kakudo = shoki_jouken();//初期条件を求める
  17. fprintf(outputfile," t theta phi dottheta dotphi \n");
  18. printf("t = %6.2f, theta = %15.6f phi = %15.6f\n", t, kakudo.theta,kakudo.phi);
  19. fprintf(outputfile,"%6.2f %15.6f %15.6f %15.6f %15.6f \n",t,kakudo.theta,kakudo.phi,kakudo.dottheta,kakudo.dotphi);
  20. for (i=0; i<=630000000; i++) {
  21. t = DT*(double)i;
  22. kakudop = runge_2hensu(kakudo);//dt後の値を求める
  23. while(kakudop.theta < -M_PI){
  24. kakudop.theta += 2*M_PI;
  25. }
  26. while(kakudop.theta > M_PI){
  27. kakudop.theta -= 2*M_PI;
  28. }
  29. while(kakudop.phi < -M_PI){
  30. kakudop.phi += 2*M_PI;
  31. }
  32. while(kakudop.phi > M_PI){
  33. kakudop.phi -= 2*M_PI;
  34. }
  35. if(i%100==0){
  36. printf("t = %11.7f, theta 1 = %15.6f phi = %15.6f\n", t, kakudop.theta,kakudop.phi);
  37. fprintf(outputfile,"%6.2f %15.6f %15.6f %15.6f %15.6f \n",t,kakudop.theta,kakudop.phi,kakudop.dottheta,kakudop.dotphi);
  38. }
  39. kakudo = kakudop;
  40. pthread_mutex_lock(&mutex);
  41. k = kakudo;
  42. pthread_mutex_unlock(&mutex);
  43. if*1{
  44. printf("==========発散!_デバックしろ_================\n");
  45. exit(1);
  46. }
  47. }

とりあえずめっちゃ長い時間計算するように設定した。DTは0.001。放置すれば保存したファイルで大変なことになる。関数runge_2hensu()に今の角度と角速度を渡し0.001秒後の状態を受け取る。これを繰り返すことで運動方程式を満たす運動をしていることになる。

  1.  
  2. }
  3.  
  4. Vector4D runge_2hensu(Vector4D hensu){
  5. Vector4D ans,buf;
  6. Vector4D k1,k2,k3,k4;//微分
  7. //Vector4D h1,h2,h3,h4;//微分
  8.  
  9. /*微小変化1次*/
  10. k1.theta = DT * hensu.dottheta;//dotを積分1次近似
  11. k1.phi = DT * hensu.dotphi;//dotを積分1次近似
  12. k1.dottheta = DT * f_the(&hensu);//ddotを積分1次近似
  13. k1.dotphi = DT * f_phi(&hensu);//ddotを積分1次近
  14.  
  15. /*微小変化2次*/
  16. k2.theta = DT * (hensu.dottheta + k1.dottheta/2);//微小変化2次近似
  17. k2.phi = DT * (hensu.dotphi + k1.dotphi/2);//微小変化2次近似
  18.  
  19. buf.dottheta = hensu.dottheta + k1.dottheta / 2;//代入用
  20. buf.dotphi = hensu.dotphi + k1.dotphi / 2;//代入用
  21. buf.theta = hensu.theta + k1.theta / 2;//代入用
  22. buf.phi = hensu.phi + k1.phi / 2;//代入用
  23. k2.dottheta = DT * f_the(&buf);//ddotを積分2次近似
  24. k2.dotphi = DT * f_phi(&buf);//ddotを積分2次近
  25.  
  26. /*微小変化3次*/
  27. k3.theta = DT * (hensu.dottheta + k2.dottheta/2);//微小変化3次近似
  28. k3.phi = DT * (hensu.dotphi + k2.dotphi/2);//微小変化3次近似
  29.  
  30. buf.dottheta = hensu.dottheta + k2.dottheta / 2;//代入用
  31. buf.dotphi = hensu.dotphi + k2.dotphi / 2;//代入用
  32. buf.theta = hensu.theta + k2.theta / 2;//代入用
  33. buf.phi = hensu.phi + k2.phi / 2;//代入用
  34. k3.dottheta = DT * f_the(&buf);//ddotを積分3次近似
  35. k3.dotphi = DT * f_phi(&buf);//ddotを積分3次近
  36.  
  37. /*微小変化4次*/
  38. k4.theta = DT * (hensu.dottheta + k3.dottheta);//微小変化3次近似
  39. k4.phi = DT * (hensu.dotphi + k3.dotphi);//微小変化3次近似
  40.  
  41. buf.dottheta = hensu.dottheta + k3.dottheta ;//代入用
  42. buf.dotphi = hensu.dotphi + k3.dotphi ;//代入用
  43. buf.theta = hensu.theta + k3.theta ;//代入用
  44. buf.phi = hensu.phi + k3.phi;//代入用
  45. k4.dottheta = DT * f_the(&buf);//ddotを積分3次近似
  46. k4.dotphi = DT * f_phi(&buf);//ddotを積分3次近似
  47.  
  48. /*変化量総和*/
  49. ans.dottheta = hensu.dottheta + (k1.dottheta + 2*k2.dottheta + 2*k3.dottheta + k4.dottheta)/6;
  50. ans.dotphi = hensu.dotphi + (k1.dotphi + 2*k2.dotphi + 2*k3.dotphi + k4.dotphi)/6;
  51. ans.theta = hensu.theta + (k1.theta + 2*k2.theta + 2*k3.theta + k4.theta)/6;
  52. ans.phi = hensu.phi + (k1.phi + 2*k2.phi + 2*k3.phi + k4.phi)/6;
  53.  
  54. return ans;
  55. }

これがルンゲクッタの演算部分。ルンゲクッタについてはルンゲ=クッタ法 - Wikipediaを参照。

2変数だからどうってことはない。2階微分について式をまとめてあと2つのは角度と角速度を打ち込めばいい。ただ、↑の式を手で式変形させたものを使ったら発散して吹っ飛んだので、クラメルを使ってプログラムで求めるようにした。(あとで修正しやすいというのもある)

2階微分項を求めるのが関数f_the()f_phi()

  1. // \ddot{\theta}の方程式
  2. double f_the(Vector4D *p){
  3. double ddt;
  4. double keisu;
  5. /*keisu = 1/(M*L1*L2*(sec(p.theta + p.phi) - cos(p.theta + p.phi)));
  6. ddt = keisu*(M*L2*L2*pow(p.dotphi,2)*tan(p.theta + p.phi) -M*L1*L2*pow(p.dottheta,2)*sin(p.theta + p.phi)
  7. + M*G*L2*sec(p.theta + p.phi)*sin(p.theta) +M*G*L2*sin(p.phi)
  8. - L2/L1*sec(p.theta + p.phi)*K*(p.theta + p.phi - ALPHA) + K*(p.theta + p.phi - ALPHA) );*/
  9. Matrix4D A,B;
  10. A.a11 = (M1+M2)*L1*L1;
  11. A.a21 = M2*L1*L2*cos(p->theta + p->phi);
  12. A.a12 = M2*L1*L2*cos(p->theta + p->phi);
  13. A.a22 = M2*L2*L2;
  14. B.a11 = M2*L1*L2*p->dotphi*p->dotphi*sin(p->theta + p->phi) + (M1+M2)*G*L1*sin(p->theta) - K*(p->theta + p->phi - ALPHA);
  15. B.a21 = M2*L1*L2*p->dottheta*p->dottheta*sin(p->theta + p->phi) - M2*G*L2*sin(p->phi) - K*(p->theta + p->phi - ALPHA);
  16. B.a12 = A.a12;
  17. B.a22 = A.a22;
  18.  
  19. if (A.a11*A.a22 == A.a12*A.a21){
  20. printf("=======_ERROR_det(A) = 0_ =======\n");
  21. exit(1);
  22. }
  23. ddt = (B.a11*B.a22 - B.a12*B.a21) / (A.a11*A.a22 - A.a12*A.a21);
  24. return ddt;
  25. }
  26.  
  27. // \ddot{\phi}の方程式
  28. double f_phi(Vector4D *p){
  29. double ddp;
  30. double keisu;
  31. /*keisu = 1/(M*L1*L2*(cos(p.theta + p.phi) - sec(p.theta + p.phi)));
  32. ddp = keisu*(M*L1*L2*pow(p.dotphi,2)*sin(p.theta + p.phi) - M*L1*L1*pow(p.dottheta,2)*tan(p.theta + p.phi)
  33. + M*G*L1*sin(p.theta) +M*G*L1*sec(p.theta + p.phi)*sin(p.phi)
  34. + L1/L2*sec(p.theta + p.phi)*K*(p.theta + p.phi - ALPHA) - K*(p.theta + p.phi - ALPHA) );*/
  35. Matrix4D A,C;
  36. A.a11 = (M1+M2)*L1*L1;
  37. A.a21 = M2*L1*L2*cos(p->theta + p->phi);
  38. A.a12 = M2*L1*L2*cos(p->theta + p->phi);
  39. A.a22 = M2*L2*L2;
  40. C.a12 = M2*L1*L2*p->dotphi*p->dotphi*sin(p->theta + p->phi) + (M1+M2)*G*L1*sin(p->theta) - K*(p->theta + p->phi - ALPHA);
  41. C.a22 = M2*L1*L2*p->dottheta*p->dottheta*sin(p->theta + p->phi) - M2*G*L2*sin(p->phi) - K*(p->theta + p->phi - ALPHA);
  42. C.a11 = A.a11;
  43. C.a21 = A.a21;
  44.  
  45.  
  46. if (A.a11*A.a22 == A.a12*A.a21){
  47. printf("=======_ERROR_det(A) = 0_ =======\n");
  48. exit(1);
  49. }
  50. ddp = (C.a11*C.a22 - C.a12*C.a21) / (A.a11*A.a22 - A.a12*A.a21);
  51. //単バネで確認
  52. //ddp = -K/M*p.phi;
  53. //printf("%20.18f\n",ddp);
  54. return ddp;
  55. }
  56. //数学_未定義三角関数
  57. double cot(double x){
  58. double y;
  59. y = 1/(tan(x));
  60. return y;
  61. }
  62. double sec(double x){
  63. double y;
  64. y = 1/(cos(x));
  65. return y;
  66. }
  67.  
  68. //初期条件
  69. Vector4D shoki_jouken(void){
  70. Vector4D ans;
  71. ans.theta = TH0;
  72. ans.phi = PH0;
  73. ans.dottheta = V0/L1/(cos(TH0) + sin(TH0)*cot(PH0));
  74. //theta固定にしてみる
  75. //ans.dottheta = 0;
  76. ans.dotphi = V0/L2/(cot(TH0)*sin(PH0) + cos(PH0));
  77. //ans.dotphi = 0;
  78. return ans;
  79. }

 

↑これはちょっと特殊な初期条件でやっている。動画のものはdottheta =0,dotphi=0の初期条件を途中から撮ったもの。

 

また、メイン文の方でグラフィック関数を呼び出し、数値計算の方はスレッドを使った。

一方、GLUTの方のコードはこんな感じ

<animat.c>

  1. #include <stdio.h>
  2. #include <GL/glut.h>
  3. #include <stdlib.h>
  4. #include <GL/gl.h>
  5. #include <GL/glu.h>
  6. #include <pthread.h>
  7. #include <string.h>
  8. #include <math.h>
  9. #include "arm.h"
  10.  
  11. pthread_mutex_t mutex;
  12.  
  13. typedef struct{
  14. double dottheta,dotphi;
  15. double theta,phi;
  16. } Vector4D;
  17.  
  18. typedef struct{
  19. double x;
  20. double y;
  21. } point;
  22.  
  23.  
  24. pthread_mutex_t mutex;
  25. extern Vector4D k;
  26.  
  27. static void display(void)
  28. {
  29. point a,b;
  30. Vector4D k_c;
  31. glClear(GL_COLOR_BUFFER_BIT);
  32. pthread_mutex_lock(&mutex);
  33. k_c = k;
  34. pthread_mutex_unlock(&mutex);
  35. glBegin(GL_POINTS);
  36. glColor3d(1.0, 0.0, 0.0);// 赤
  37. glVertex2d(0,0);
  38. /*calculation poit*/
  39. a.x = -L1*sin(k_c.theta);
  40. a.y = L1*cos(k_c.theta);
  41. b.x = -L1*sin(k_c.theta) - L2*sin(k_c.phi);
  42. b.y = L1*cos(k_c.theta) - L2*cos(k_c.phi);;
  43. glColor3d(1.0, 0.0, 0.0);// 赤
  44. glVertex2d(a.x,a.y);
  45. glVertex2d(b.x,b.y);
  46. glEnd();
  47. draw_a_link(&a,&k_c);
  48. draw_b_link(&a,&b,&k_c);
  49. drawCircle(&a,R1);
  50. drawCircle(&b,R2);
  51. glFlush();
  52. }
  53.  
  54. void drawBox(point *p0,point *p1,point *p2,point *p3){
  55. glBegin(GL_POLYGON);
  56. glColor3d(1.0, 1.0, 0.0);// 赤+?
  57. glVertex2d(p0->x,p0->y);
  58. glVertex2d(p1->x,p1->y);
  59. glVertex2d(p2->x,p2->y);
  60. glVertex2d(p3->x,p3->y);
  61. glEnd();
  62. }
  63.  
  64. void draw_a_link(point *a,Vector4D *kc){
  65. point p0,p1,p2,p3;
  66. p0.x = BB/2*cos(kc->theta);
  67. p0.y = BB/2*sin(kc->theta);
  68. p1.x = a->x + BB/2*cos(kc->theta);
  69. p1.y = a->y + BB/2*sin(kc->theta);
  70. p2.x = a->x - BB/2*cos(kc->theta);
  71. p2.y = a->y - BB/2*sin(kc->theta);
  72. p3.x = -BB/2*cos(kc->theta);
  73. p3.y = -BB/2*sin(kc->theta);
  74. drawBox(&p0,&p1,&p2,&p3);
  75. }
  76.  
  77. void draw_b_link(point *a ,point *b,Vector4D *kc){
  78. point p0,p1,p2,p3;
  79. p0.x = a->x + BB/2*cos(kc->phi);
  80. p0.y = a->y - BB/2*sin(kc->phi);
  81. p1.x = b->x + BB/2*cos(kc->phi);
  82. p1.y = b->y - BB/2*sin(kc->phi);
  83. p2.x = b->x - BB/2*cos(kc->phi);
  84. p2.y = b->y + BB/2*sin(kc->phi);
  85. p3.x = a->x - BB/2*cos(kc->phi);
  86. p3.y = a->y + BB/2*sin(kc->phi);
  87. drawBox(&p0,&p1,&p2,&p3);
  88. }
  89.  
  90.  
  91. #define D_C (16) //bunkatusuu
  92. void drawCircle(point *n,double r){
  93. double st = 2.0*M_PI/D_C;
  94. int i;
  95. double th,x,y;
  96. glBegin(GL_POLYGON);
  97. glColor3d(0.8, 0.0, 1.0);// 赤
  98. for(i=0;i<D_C;i++){
  99. th = st*(double)i;
  100. x = n->x + r*cos(th);
  101. y = n->y + r*sin(th);
  102. glVertex2d(x,y);
  103. }
  104. glEnd();
  105. }
  106.  
  107.  
  108. void init(void)
  109. {
  110. glClearColor(0.0, 0.0, 0.0, 0.5);
  111. }
  112.  
  113. void timer(int value){
  114. glutPostRedisplay();
  115. glutTimerFunc(1,timer,0);
  116. }
  117.  
  118. void InitialGlut(int ac,char **av)
  119. {
  120. glutInit(&ac,av);
  121. printf("Welcome! \n");
  122. glutInitDisplayMode(GLUT_RGBA);
  123. glutCreateWindow("animation");
  124. glutDisplayFunc(display);
  125. //init();
  126. glutTimerFunc(1,timer,0);
  127. glutPostRedisplay();
  128. glutMainLoop();
  129. }

やはりグラフィックの方が大変であった。

正直全部説明するのはしんどい。

関数glutPostRedisplay()だけではディスプレイを更新させてくれないようで、glutTimerFunc()関数を使って強制的に更新させてる。原理はまだよくわかってない。

あと、振り子のおもりは図形を移動させているのではなく、毎回書き出してる。linuxだからちゃんと動いてるように見えるけど、windowsだったらカクカクしそう。

 

GLUT使ってもっといろいろやってみたいな〜。

*1:kakudop.dotphi>100000)||(kakudop.dottheta>100000

初ブログ

ブログを始めてみました。

機械専攻の元高専生です。

ロボコン等で長らく機械の設計等をやってましたが、最近は制御手法を勉強しています。

プログラミングや組み込みについて日記感覚で書いて行こうと思います。

プログラミングは低級&初級レベルです。ご容赦ください。