2014. 4. 28.
石立 喬

Visual Studio Express 2013 の新しい使い方(3)

―― コンソールアプリケーションで、complexテンプレートクラスを使う ――

 ここでは、complexテンプレートクラスで複素数計算を行い、簡単な増幅回路の周波数特性を求める。本来ならば、周波数特性はグラフで表示する方が分かり易いが、プログラムの作成に重点を置いた。
 新しい方法としては、後置戻り値形式で関数を作成し、vector型の配列に対してrange-based forを使い、coutによる数値の書式指定出力も行った。

負帰還増幅回路
 図1は演算増幅器(Operational Amplifier)を用いた負帰還(Negative Feedback)増幅回路で、入力端子Vinと出力端子Voutの間を二つのインピーダンスZ1とZ2の直列回路で結び、中点を演算増幅器のマイナス入力端子に接続する。図1で、増幅器(三角で表す)の増幅率を無限大と仮定すると、有限の出力端子電圧に対して、入力端子(−記号で示す)電圧はゼロでなければならない(これを仮想接地と呼ぶことがある)。したがって、Z1に流れ込む電流はVin / Z1であり、入力端子のインピーダンスを無限大と仮定すると、Z1を流れる電流は、そのままZ2に流れることになり、Voutは、−(Vin / Z1) x Z2である。この増幅器は位相反転増幅器なので、−符号を省略して、増幅率だけで表すと、 ratio = Vout / Vin = Z2 / Z1 である。ただし、実際には、増幅器の増幅率は有限で周波数特性を持ち、入力端子のインピーダンスも有限であるので、この通りにはならない。近似式として考えるべきである。


図1 演算増幅器を用いた負帰還増幅回路


負帰還増幅回路を用いた高音域・低音域増強トーンコントロール回路
 図1の回路をオーディオ増幅器として使用する場合、Z1とZ2を共に抵抗のみとすると、演算増幅器の特性の範囲内で、周波数に対してフラットな増幅器が得られる。しかし、スピーカや部屋の音響特性、鑑賞者の好みなどで、高音域や低音域を強調したい場合があり、これを行うのがトーンコントロール回路である。オーディオ周波数の高周波部を増強するために、高周波領域ではZ1が小さくなるようにし、一方、低周波部も増強するために、低周波領域ではZ2が大きくなるようにしたのが図2のトーンコントロール回路で、中間周波数領域では、相対的に増幅率が低下する。


図2 高周波部と低周波部を増強したトーンコントロール回路


周波数特性の計算
 周波数特性は、増幅率ratioの周波数特性である。ratio = Z2 / Z1 なので、まずZ1とZ2を求める。
 Z1は、抵抗R1とコンデンサC1の並列接続である。R1のインピーダンスをZR1とし(単にR1でも良い)、C1のインピーダンスをZC1(これは周波数によって変化し、複素数の虚数部としての計算が必要)とすると、下の式で求まる。複素数の並列計算は、ユーザ関数para()として作成する。
 Z2は、抵抗R2とコンデンサC2の直列回路であり、これはC2のインピーダンスZC2と抵抗R1の加算で求める。加算には、complexクラスに標準として用意されている四則演算operatorが使える。
       
 コイルのインダクタンスL(ここでは使用しないが)から、そのインピーダンスを求める式と、コンデンサのキャパシタンスCから、そのインピーダンスを求める式を以下に示す。jは、複素数の虚数を表し(数学では、imaginaryのiが用いられるが、電気計算ではjを用いる)、ωは角周波数すなわち周波数を2π倍したものである。

     
 プログラムでは、
  complex<double> zl(0.0, omega * l);
  complex<double> zc(0.0, -1.0 / (omega * c));
のように宣言と同時に、実数部と虚数部を設定する。

ここで使用する新しい言語機能の簡単な説明
 本サイトは、従来から、文法よりも理工系技術者が関心を持つ応用に重点を置き、文法の解説は他のサイトや書籍に頼ってきた。この方針は現在でも変わらないが、ここで初めて取り上げた機能を簡単に説明する。

◎後置戻り値(trailing return type)
 autoキーワードと共に用いる。ここでのautoの意味は、型の推論ではなく、単なるplaceholder(代用語、場所を取っておき、あとで別の語に変える)で、真の戻り値の型は「->」演算子の後に来る。「->」演算子の後には、decltype(変数名)が来るのが一般的である。
 後置戻り値を使う理由は、テンプレート関数のように、戻り値の値が事前に決まってなく、引数の型や関数内部での処理の結果で戻り値の型が変化するような場合に、コンパイラが判断するのに便利だからである。
 ここで使用するケースのように、引数の型はcomplex型に決まっていて、戻り値もcomplex型に決まっている場合には、特に、後置戻り値を使うメリットはない。ここでは、dscltypeを使わないで、直接、complexを指定している。

◎complexテンプレートクラス
 標準のヘッダ<complex>で定義されていて、namespace stdを必要とする。テンプレートであるので、complex<double>、complex<float>のようにして型を指定する。
 複素数変数を宣言と同時に、実数部と虚数部を設定する場合には、
  complex<double> z1(1.0, 2.0);
を用いる。
  complex<double> z1;
と宣言だけしておいて、後で値を設定する場合には、
  z1 = {1.0, 2.0};
または、
  z1.real(1.0);
  z1.imag(2.0);
を用いる。虚数部がゼロの場合は、z1 = 1.0; が使える。実数部がゼロの場合の z1 = 2.0i; は、Visual Studio 2013 ではまだ使えない(C++14にはあるとされる)。
 注意すべきは、z1 = (1.0, 2.0); で、これはビルドできるが、虚数部を指定したはずの2.0が実数部に入り、虚数部はゼロになるので、誤って使用しないように
実数部を取り出す場合には、
 re = z1.real();
虚数部を取り出す場合には、
 im = z1.imag();
を使う。
 complexクラス同士、complexクラスと通常の変数(double型、float型など、虚数部がゼロの複素数として扱われる)間では、四則演算記号が使用できる。abs(z)は複素数zの絶対値を返し、arg(z)は、偏角を返す。

◎定数M_PIの使用
 円周率πの値を持つ定数M_PIを使用するには、
   #define _USE_MATH_DEFINES
   #include <cmath>
を記述しておく必要がある。ここで注意すべきは、#define _USE_MATH_DEFINESの場所で、#inclede "stdafx.h" のすぐ下の、他のインクルードの前に置かないといけない。#include <cmath> は、上記のすぐ下に置く必要はないが、並べておいた方が分かり易い。
 M_PIの他に、M_PI_2 --- π/2、 M_1_PI --- 1/πなどがあるが、電気計算に良く用いる 2πが無いのが残念である。

◎cout標準出力ストリームのマニピュレータ
 コンソールに表示させる場合、C言語には、書式指定ができるprintf系があった。 C++で書式設定をするには、フラグのオンオフを使う方法とマニピュレータを使う方法とがある。フラグの制御にはsetf()関数やunsetf()関数を用いる。しかし、大部分の設定はマニピュレータの使用で十分で、簡単である。マニピュレータを使用するには、ヘッダファイル<iostream>の他に <iomanip> を追加しておく必要がある。
 以下に、比較的便利なマニピュレータを示す。
 setw(n) --- 小数点を含めた表示桁数を設定する。デフォルトは右寄せで、頭には空白が入る。その都度設定が必要整数を右寄せで並べて出力するのに便利である
 setprecision(n) ---- n = 6がデフォルト。後述のfixedモードやscientificモードが設定されていない場合には(これをdefaultfloatモードと言う)、+−符号と小数点を除いた有効数字の数が6個になるように出力する。固定小数点形式にするか、指数形式にするかは、自動的に判断される。fixedモードでは、小数点以下の桁数、scientificモードでは、仮数部の小数点以下の桁数を設定する。以降も有効で、デフォルト状態に戻すにはsetprecision(6)を用いる。
 scientific --- 指数表示モードにする。以降有効。
 fixed --- 固定小数点モードにする。以降有効。小数点以下の桁数を指定して右寄せにすると、小数点の位置を揃えるのに便利である
 defaultfloat --- 固定小数点表示と指数表示を自動的に判断するデフォルトモードに戻す。
 right ---- setw(n)で確保された領域内で右寄せにする。以降有効。
 left --- setw(n)で確保された領域内で左寄せにする。以降有効。

プログラム
 図3に示すように、まず、para(paraはparallel、並列の意)関数を作成する。引数はcomplex型インピーダンスを2個与え、それらの並列インピーダンスを戻り値とする。autoキーワードを用いて、後置戻り値方式を用いているが、本来、その必要性はない。


図3 インクルードファイルと、作成したpara()関数


 図4に示すメイン関数では、range-based forを使用する。vector型の配列freqから逐次要素を取り出してfとし、それに2πを掛けて角周波数omegaとする。このomegaを使って、インピーダンスZ1とZ2を計算し、ratioを求める。
 コンソールへの表示は、まず、すべてを固定小数点表示とし、与えられた範囲に右寄せで出力するものとする(44行)。周波数fは小数点以下を表示せず、8桁幅の右寄せで出力する(45〜46行)。増幅率ratioの絶対値と偏角は共に小数点以下2桁の出力とし(47行)、それぞれに出力範囲の幅をその都度設定する(48〜49行)。


図4 range-based forを用いた計算部分


得られた結果
 図5は、上述したプログラムを実行した結果である。coutを使って、きれいに書式設定ができている。中間域の周波数(500Hz〜1000Hz)に比べて、低音域や高音域の増幅率が増強されている。実際には、演算増幅器に周波数特性があるので、このようには増幅率が上がらない。


図5 実行結果の例


range-based forを使わない場合
 最も標準的な、単純forループを使うと、以下のようになる。freq.size()は、vector型配列freqの要素数である。これは10であることが分かっているので、直接入れても良い。空欄部分は、range-based forの場合と同じであるので、省略してある。


 for-each関数を使用すると、以下のようになる。これには、<algorithm>のヘッダファイルが必要である。これも、range-based forの場合と同じ部分は省略してある(omega = の部分も同じであるが・・・)。


 いずれも、全く同じ結果が得られるが、分かり易さでは従来のforループ、コードの簡潔さではrange-based forと言うことになる。

coutを使わないでprintf_sを使った場合
 C言語で広く使われているprintf_sによれば、全く同じ出力を以下のように簡単に記述することができる。C++を使っていても、書式付出力だけは、printf系を使うプログラマが多いのも理解できる。