6

私は最近(4日)C/JavaのバックグラウンドからC++を学び始めました。新しい言語を学ぶために、私は通常、可能な限り言語固有のさまざまな古典的アルゴリズムを再実装することから始めます。

私はこのコード、そのDFSに到達しました-方向性のないグラフでの深さ優先探索。それでも私が読んだことから、C++の参照によってパラメーターを渡すのが最善です。残念ながら、参照の概念を完全に理解することはできません。参照が必要になるたびに、私は混乱し、ポインターの観点から考えます。現在のコードでは、値渡しを使用しています。

コードは次のとおりです(おそらくCppthonicではないはずです):

#include <algorithm>
#include <iostream>
#include <fstream>
#include <string>
#include <stack>
#include <vector>

using namespace std;

template <class T>
void utilShow(T elem);

template <class T>
void utilShow(T elem){
    cout << elem << " ";
}

vector< vector<short> > getMatrixFromFile(string fName);
void showMatrix(vector< vector<short> > mat);
vector<unsigned int> DFS(vector< vector<short> > mat);

/* Reads matrix from file (fName) */
vector< vector<short> > getMatrixFromFile(string fName)
{
    unsigned int mDim;
    ifstream in(fName.c_str());
    in >> mDim;
    vector< vector<short> > mat(mDim, vector<short>(mDim));
    for(int i = 0; i < mDim; ++i) {
        for(int j = 0; j < mDim; ++j) {
            in >> mat[i][j];
        }
    }
    return mat;
}

/* Output matrix to stdout */
void showMatrix(vector< vector<short> > mat){
    vector< vector<short> >::iterator row;
    for(row = mat.begin(); row < mat.end(); ++row){
        for_each((*row).begin(), (*row).end(), utilShow<short>);
        cout << endl;
    }
}

/* DFS */
vector<unsigned int> DFS(vector< vector<short> > mat){
    // Gives the order for DFS when visiting
    stack<unsigned int> nodeStack;
    // Tracks the visited nodes
    vector<bool> visited(mat.size(), false);
    vector<unsigned int> result;
    nodeStack.push(0);
    visited[0] = true;
    while(!nodeStack.empty()) {
        unsigned int cIdx = nodeStack.top();
        nodeStack.pop();
        result.push_back(cIdx);
        for(int i = 0; i < mat.size(); ++i) {
            if(1 == mat[cIdx][i] && !visited[i]) {
                nodeStack.push(i);
                visited[i] = true;
            }
        }
    }
    return result;
}

int main()
{
    vector< vector<short> > mat;
    mat = getMatrixFromFile("Ex04.in");
    vector<unsigned int> dfsResult = DFS(mat);

    cout << "Adjancency Matrix: " << endl;
    showMatrix(mat);

    cout << endl << "DFS: " << endl;
    for_each(dfsResult.begin(), dfsResult.end(), utilShow<unsigned int>);

    return (0);
}

このコードを参照して、参照の使用方法に関するヒントを教えてください。

私の現在のプログラミングスタイルは、C ++の構成と互換性がありますか?

C ++の2次元配列のベクトルとタイプ**の標準的な代替手段はありますか?

後で編集:

OK、私はあなたの答えを分析しました(すべてに感謝します)、そして私はよりOOPの方法でコードを書き直しました。また、私は参照が何であるかを理解し、それを使用することになっていました。これは、constポインターにいくぶん似ていますが、そのタイプのポインターがNULLを保持できるという事実が異なります。

これは私の最新のコードです:

#include <algorithm>
#include <fstream>
#include <iostream>
#include <ostream>
#include <stack>
#include <string>
#include <vector>

using namespace std;

template <class T> void showUtil(T elem);

/**
* Wrapper around a graph
**/
template <class T>
class SGraph
{
private:
    size_t nodes;
    vector<T> pmatrix;
public:
    SGraph(): nodes(0), pmatrix(0) { }
    SGraph(size_t nodes): nodes(nodes), pmatrix(nodes * nodes) { }
    // Initialize graph from file name
    SGraph(string &file_name);
    void resize(size_t new_size);
    void print();
    void DFS(vector<size_t> &results, size_t start_node);
    // Used to retrieve indexes.
    T & operator()(size_t row, size_t col) {
        return pmatrix[row * nodes + col];
    }
};

template <class T>
SGraph<T>::SGraph(string &file_name)
{
    ifstream in(file_name.c_str());
    in >> nodes;
    pmatrix = vector<T>(nodes * nodes);
    for(int i = 0; i < nodes; ++i) {
        for(int j = 0; j < nodes; ++j) {
            in >> pmatrix[i*nodes+j];
        }
    }
}

template <class T>
void SGraph<T>::resize(size_t new_size)
{
    this->pmatrix.resize(new_size * new_size);
}

template <class T>
void SGraph<T>::print()
{
    for(int i = 0; i < nodes; ++i){
        cout << pmatrix[i];
        if(i % nodes == 0){
            cout << endl;
        }
    }
}

template <class T>
void SGraph<T>::DFS(vector<size_t> &results, size_t start_node)
{
    stack<size_t> nodeStack;
    vector<bool> visited(nodes * nodes, 0);
    nodeStack.push(start_node);
    visited[start_node] = true;
    while(!nodeStack.empty()){
        size_t cIdx = nodeStack.top();
        nodeStack.pop();
        results.push_back(cIdx);
        for(int i = 0; i < nodes; ++i){
            if(pmatrix[nodes*cIdx + i] && !visited[i]){
                nodeStack.push(i);
                visited[i] = 1;
            }
        }
    }
}

template <class T>
void showUtil(T elem){
    cout << elem << " ";
}

int main(int argc, char *argv[])
{
    string file_name = "Ex04.in";
    vector<size_t> dfs_results;

    SGraph<short> g(file_name);
    g.DFS(dfs_results, 0);

    for_each(dfs_results.begin(), dfs_results.end(), showUtil<size_t>);

    return (0);
}
4

4 に答える 4

8

C ++の4日間、あなたは素晴らしい仕事をしています。すでに標準のコンテナ、アルゴリズムを使用しており、独自の関数テンプレートを作成しています。私が見る最もひどく欠けているものは、まさにあなたの質問に関連しています:参照/定数参照を渡す必要があります。

C ++オブジェクトを値で渡す/返すときはいつでも、その内容のディープコピーを呼び出しています。これは、特にマトリックスクラスのようなものでは、決して安くはありません。

まず、showMatrixを見てみましょう。この関数の目的は、行列の内容を出力することです。コピーが必要ですか?いいえ。マトリックス内の何かを変更する必要がありますか?いいえ、目的はそれを表示することだけです。したがって、const参照によってマトリックスを渡します。

typedef vector<short> Row;
typedef vector<Row> SquareMatrix;
void showMatrix(const SquareMatrix& mat);

[注:これを読み書きしやすくするために、いくつかのtypedefを使用しました。テンプレートのパラメータ化が多い場合にお勧めします]。

次に、getMatrixFromFileを見てみましょう。

SquareMatrix getMatrixFromFile(string fName);

ここでSquareMatrixを値で返すと、コストがかかる可能性があり(コンパイラがこの場合に戻り値の最適化を適用するかどうかによって異なります)、値で文字列を渡します。C ++ 0xでは、コピーを返す必要がないように右辺値参照があります(showMatrixと同じ理由で、const参照によって渡される文字列も変更しました。コピーは必要ありません。ファイル名):

SquareMatrix&& getMatrixFromFile(const string& fName);

ただし、これらの機能を備えたコンパイラがない場合、一般的な妥協案は、参照によって行列を渡し、関数に次のように入力させることです。

void getMatrixFromFile(const string& fName, SquareMatrix& out_matrix);

これは、クライアントに便利な構文を提供しません(現在、クライアントは1行ではなく2行のコードを記述する必要があります)が、一貫して深いコピーのオーバーヘッドを回避します。これに対処するMOJOもありますが、C++0xでは廃止されます。

簡単な経験則:ユーザー定義型(単純な古いデータ型ではない)があり、それを関数に渡したい場合:

  1. 関数が読み取りのみを必要とする場合は、const参照を渡します。
  2. 関数がオリジナルを変更する必要がある場合は、参照を渡します。
  3. 関数が変更するためにコピーを必要とする場合にのみ、値を渡します。

例外として、const参照で渡すよりもコピーするのが安価な安価なUDT(ユーザー定義型)がある場合がありますが、今のところこのルールに固執すれば、安全に書くことができます。 、不要なコピーで貴重なクロックサイクルを浪費しない効率的なC ++コード(不十分に記述されたC ++プログラムの一般的な悩みの種)。

于 2010-06-28T16:14:40.340 に答える
2

参照を渡すには、通常、これを変更します。

vector<unsigned int> DFS(vector< vector<short> > mat){

に:

vector<unsigned int> DFS(vector<vector<short>> const &mat) { 

技術的には、これは参照を渡すconstことですが、元のオブジェクトを変更する予定がない場合に通常使用したいものです。

別の注意点として、私はおそらくこれを変更します:

for_each((*row).begin(), (*row).end(), utilShow<short>);

次のようなものに:

std::copy(row->begin(), row->end(), std::ostream_iterator<short>(std::cout, " "));

同じく:

for_each(dfsResult.begin(), dfsResult.end(), utilShow<unsigned int>);

次のようになります:

std::copy(dfsResult.begin(), dfsResult.end(),
          std::ostream_iterator<unsigned int>(std::cout, " "));

utilShow(...それは完全に不要になるように見えます)。

2D行列に関する限り、不規則な行列(異なる行が異なる長さになる可能性がある)が必要でない限り、通常は単純なフロントエンドを使用して単一のベクトルのインデックスを処理します。

template <class T>
class matrix { 
    std::vector<T> data_;
    size_t columns_;
public:
    matrix(size_t rows, size_t columns) : columns_(columns), data_(rows * columns)  {}

    T &operator()(size_t row, size_t column) { return data[row * columns_ + column]; }
};

これはoperator()インデックス作成に使用されるため、BASICやFortranのように、の代わりにをm[x][y]使用することに注意してください。必要に応じてその表記を使用できるように m(x,y)オーバーロードすることもできますが、それはかなりの量の余分な作業であり、(IMO)実際の利点はほとんどありません。operator[]

于 2010-06-28T16:07:21.337 に答える
1
void utilShow(T& elem);
vector< vector<short> > getMatrixFromFile(const string& fName);
void showMatrix(vector< vector<short> >& mat);
vector<unsigned int> DFS(vector< vector<short> >& mat);

私が理解できたいくつか。また、可能であれば、メソッド本体内のオブジェクトの状態を変更しない場合、または変更する予定がある場合は、変数をconstとして渡します。

最初の試行自体にすべてのC++構造を含めるようにお願いするのではなく、うつ病に圧倒されないように徐々に増やしていきます。ベクターは最も使用されているSTLコンテナです。また、コンテナの使用法は、次々に使用することに夢中になるのではなく、ニーズによって異なります。

コンテナの簡単な説明。 http://msdn.microsoft.com/en-us/library/1fe2x6kt%28VS.80%29.aspx

@ジェリー編集してくれてありがとう。Vectorは使いすぎではありませんが、大きなモノリシッククラスオブジェクトではなく、単純なオブジェクトが単純であるため、より多く使用されています。これはCスタイルの配列に似ていますが、多くの追加のアルゴリズムがあり、そうではありません。非常に頻繁に使用されるもう2つは、マップとリストです。私が働いている場所のせいで、他の場所よりもこれらのコンテナを使用する必要があるのか​​もしれません。

于 2010-06-28T16:03:26.323 に答える
1

参照とポインタは密接に関連しています。どちらも、パラメータ値をサブルーチンのスタックフレームにコピーせずにパラメータを渡す方法です。

それらの主な違い:

  • ポインタpはオブジェクトを指しますo
  • 参照i オブジェクトoです。言い換えれば、エイリアスで。

物事をより混乱させるために、私が知る限り、2つの間のコンパイラの実装はほとんど同じです。

Ptr(const T* t)関数とを想像してみてくださいRef(const T& t)

int main(){int a; Ptr(&a); Ref(a); }

Ptrtの場所を指しaます。逆参照して、の値を取得できますa。実行すると&t(のアドレスtを取得)、パラメータのアドレスを取得します。

Reft です aaの値に使用できますa。のアドレスはaで取得できます&a。これは、C++が提供する小さな構文糖衣です。

どちらも、コピーせずにパラメーターを渡すためのメカニズムを提供します。あなたの関数では(ちなみに、宣言は必要ありません):

template <class T> void utilShow(T elem) { ... }

呼び出されるたびに、Tコピーされます。Tが大きなベクトルの場合、ベクトル内のすべてのデータをコピーしています。それはかなり非効率的です。ベクトル全体を新しいスタックフレームに渡したくない場合は、「ねえ、新しいスタックフレーム、このデータを使用してください」と言います。したがって、参照で渡すことができます。それはどのように見えますか?

template <class T> void utilShow(const T &elem) { ... }

elemconst関数によって変更されないためです。またelem、スタックにコピーするのではなく、呼び出し元に格納されているメモリを使用します。

繰り返しますが、同じ理由で(パラメーターのコピーを避けるために)、以下を使用します。

vector< vector<short> > getMatrixFromFile(const string &fName) { ... }
void showMatrix(const vector< vector<short> > &mat) { ... }

トリッキーな部分の1つは、「参照はコピーがないことを意味します。常に使用します。関数から参照を返します!」と考えるかもしれません。そして、それはあなたのプログラムがクラッシュするところです。

これを想像してみてください:

// Don't do this!
Foo& BrokenReturnRef() {
  Foo f;
  return f;
}

int main() {
  Foo &f = BrokenReturnRef();
  cout << f.bar();
}

残念ながら、これは壊れています!BrokenReturnRef実行するとf、範囲内にあり、すべてがクールです。次に、に戻ってmain参照を続けますf。作成されたスタックフレームfがなくなり、その場所は無効になり、ジャンクメモリを参照しています。この場合、値で返す(またはヒープに新しいポインタを割り当てる)必要があります

「参照を返さない」という規則の1つの例外は、メモリがスタックよりも長持ちすることがわかっている場合です。これは、STLoperator[]がそのコンテナに実装する方法です。

お役に立てば幸いです。:)

于 2010-06-28T16:17:58.613 に答える