それがまとめた「C ++入門」(第5版)253回の7.3.4フェルト演習を、やります
1つの声明
1.1宣言変数と関数
一般的な宣言は、通常ヘッダーファイルで、変数または関数を宣言することで.h
、例えば、宣言します:
pos cursor = 0; // 给定初始值
char get(pos r, pos col) const;
1.2クラスを宣言
クラスに対して、通常直接ヘッダファイルに直接書かれたclass ClassName { ... }
クラスと呼ばれ、定義し、クラス本体内{...}
及びメンバ変数とメンバ関数を宣言または定義されています。クラス宣言には、クラスのボディ、クラス名のみではありません。
class Screen; // 声明一个类
そのようなクラス宣言は、オブジェクトまたはアクセスそのメンバーを作成することができない、不完全な型です。
2つの定義
関数は、関数定義部、又は身体部分定義クラス、または変数のために定義されています。関数やヘッダファイル内の変数の一般的な宣言、ソースファイルに定義され、対応する機能。
以下のためにクラスclass
、一般的にヘッダファイルで定義され、クラスまたは関数の変数宣言の部材であり、ソース・ファイルに#include xxx.h
、その後、クラスのメンバ関数を定義します。
。でcpp
、ファイル#include .h
のファイルの後に、この時間クラスが定義されているので、あなたはそのメンバーにアクセスすることができます。
3前方宣言
一般的に前方宣言関数またはクラスは、クラスの前方宣言は、一般に、そのようなクラス定義として、ヘッダファイルで使用され、それは、クラスの特定のタイプを必要とし、そのメンバーがアクセスできないか、オブジェクトを作成するが、今回はクラスはまだされていません定義はなく、include
その前に、あなたは後に、最初の使用に文を書くことができinclude
、この場所(の最初の文書.h
または.cpp
)前方宣言の定義。(宣言ヘッダーファイル前関数は、それを宣言するために呼び出されていない場合には、宣言文と実際の前に違いはない)関数の前方宣言は、通常、ソースファイルで使用されています。
4環状依存性を含み、
現在のファイルにクラス定義または関数宣言を含むが、その定義内の2つのクラスの場合のヘッダお互いにinclude
、そしてその後、クラスA、Bのクラス定義は、その逆に定義されずこのとき、クラス・タイプを使用します。エラーが発生したサイクルに依存します。そのため、include
以下の根からトランクに後方ツリーのような一方向のみの引用、、、include
上記は、訪問にロジック上に存在しない何かを行くことはできません。
循環依存の問題を解決し、直接的な方法は、このような参照BhのAを提供するなど、親子関係の呼び出しを、整理することで、その後、BはああではBタイプを使用することにより、前方宣言です。もちろん、これはのみのみBを入力して、Bがアクセスメンバーできないか、オブジェクトの作成に使用することができます。
二つのクラスに.cpp
互いにドキュメントinclude
ヘッダファイルに定義されている2つのクラスが終了するので、正当なものです。
5共通の友人の機能
注:フレンド関数がのちょうど機能である権威は、宣言された関数の宣言と同じではありません。
だけ追加する必要があり、一般的なfriend関数の宣言friend
クラスの関数宣言キーワードは、比較的簡単で、ソースファイルで定義することができます。
たとえば、次のようにSales_data.h
#pragma once
#include <string>
#include <ostream>
#include <istream>
class Sales_data
{
// 为Sales_data的非成员函数所作的友元声明
friend Sales_data add(const Sales_data&, const Sales_data&);
friend std::ostream &print(std::ostream&, const Sales_data&);
friend std::istream &read(std::istream&, Sales_data&);
// 其他成员及访问说明符与之前一致
public:
// 构造函数
// 空参的默认构造函数,居然还有这种操作,C++11标准的东西,表示默认行为
Sales_data() = default;
Sales_data(const std::string &s) : bookNo(s) { }
// 这部分有了一个新的名字,构造函数初始值列表
Sales_data(const std::string &s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(p * n) { }
// 不同于上面三个类内定义的构造函数,这个将在类外(cpp文件)定义
Sales_data(std::istream &is);
~Sales_data() = default; // 析构函数
// 成员函数: 关于Sales_data对象的操作
// 注意: 定义在类内部的函数是隐式的inline函数,这里的const与this指针有关,在下面另开篇幅单独说明
std::string isbin() const { return bookNo; } // 返回对象的ISBN编号
Sales_data& combine(const Sales_data &rhs); // 将一个Sales_data对象加到另一个对象上
private:
// 数据变量: 定义成员的属性
std::string bookNo; // ISBN编号
unsigned units_sold = 0; // 销量
double revenue = 0.0; // 总销售收入
double avg_price() const; // 返回出售书籍的平均价格
};
// Sales_data的非成员接口函数,上面已声明为类的友元,可以访问私有成员
Sales_data add(const Sales_data&, const Sales_data&); // 执行两个Sales_data对象的加法,后期以重载运算符代替
std::ostream &print(std::ostream&, const Sales_data&); // 将Sales_data对象的值输出到ostream
std::istream &read(std::istream&, Sales_data&); // 将数据从istream读入到Sales_data中
7 友元类
フレンドクラス宣言でも比較的簡単である、のみ追加する必要がありfriend
、使用の友人のクラス宣言にまずここで、メインクラスに型宣言キーワードを:
例えば、クラスがあります:Window_msg、Window_msgは、以下の
#pragma once
#include <vector>
#include "Screen.h"
/**
* 窗口管理类
* 表示显示器上的一组Screen
*/
class Window_mgr
{
public:
Window_mgr();
~Window_mgr();
// 窗口中每个屏幕的编号类型
using ScreenIndex = std::vector<Screen>::size_type;
// 按照编号将指定的Screen重置为空白
void clear(ScreenIndex sindex);
private:
// 这个Window_mgr追踪的Screen
// 类内初始值:默认情况下,一个Window_mgr包含一个标准尺寸的空白Screen
std::vector<Screen> screens{Screen(25, 25, ' ')};
// 如上所示,当我们提供一个类内初始值时,必须以 "=" 或 "{ }" 表示的直接初始化
};
私たちは、別のクラスとしてそれを宣言する:画面の友人、Screen.hファイルを次のように:
#pragma once
#include <string>
#include <ostream>
// 前向声明,防止循环引用,Window_mgr.h单向引用了Screen.h,所以符合先声明再定义的原则
class Window_mgr;
/**
* 窗口类
*/
class Screen
{
public:
// 声明Window_mgr类为Screen类的友元,友元类的成员函数可以访问此类的所有成员
// 友元可以是某个类,某个类的成员函数,或普通函数
friend class Window_mgr; // 此时只用到Window_mgr类型,而不用其成员,所以可以使用前向声明
// 声明一个类型别名,或可使用typedef,用户可以使用这个名字
using pos = std::string::size_type;
Screen() = default; // 默认构造函数,内联
~Screen() = default; // 默认析构函数,内联
// 自定义构造函数
Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(width * height, c){ }
// 读取光标处的字符,隐式内联
char get() const { return contents[cursor]; }
// 重载,类成员函数要想声明内联,必须在声明的同时定义,就像上一个get一样,否则无法编译
char get(pos r, pos col) const;
// 设置光标所在位置的字符
Screen &set(char c);
// 设置给定位置的字符
Screen &set(pos r, pos col, char c);
// 移动光标
Screen &move(pos r, pos c);
// 打印屏幕内容,返回当前对象的常量引用
Screen &display(std::ostream &os);
// 根据调用对象是否是const重载display函数
const Screen &display(std::ostream &os) const;
private:
pos cursor = 0; // 鼠标位置
pos height = 0, width = 0; // 屏幕宽高
std::string contents; // 屏幕内容
// 可变数据成员,即使在一个const对象内也能被修改,用来对函数调用计数
mutable size_t access_ctr;
void do_display(std::ostream &os) const;
};
友人のクラスを宣言した後、友人のクラスのすべての機能は明らかに、安全ではないマスタークラスのすべてのメンバーにアクセスすることができます。
8友人のメンバ関数
上に示したように、2つのクラス、多くの問題だろうというクラス全体よりも友人の機能画面としてのみ明確な声明Window_msgクラスのメンバ関数、、。
明確でScreen.hメンバーWindow_msgにこのポイントにアクセスするには、明らかに宣言、必見の前に使用することはできません#include Window_msg.h
ので、:
- まず、明確な機能を述べWindow_msgクラスを定義し、それを定義することはできません。画面は、画面のメンバーを明確に使用する前に定義する必要がありますので、
- 次に、クリアするfriend宣言を含め、画面を定義します
- 最後に、明確な定義は、その時点でそれは画面のメンバーを使用することができます
多くの宣言、定義を伏線の上、前方宣言は次のように実装し、これらの言葉を理解することです。
Window_mgr.h
#pragma once
#include <vector>
// 该类会被Screen类引用,所以这里可以使用前向声明
// 前向声明只能使用类型名,不能访问成员
class Screen;
/**
* 窗口管理类
* 表示显示器上的一组Screen
*/
class Window_mgr
{
public:
Window_mgr();
~Window_mgr();
// 窗口中每个屏幕的编号类型
using ScreenIndex = std::vector<Screen>::size_type;
// 按照编号将指定的Screen重置为空白
void clear(ScreenIndex sindex);
private:
// 这个Window_mgr追踪的Screen
// 类内初始值:默认情况下,一个Window_mgr包含一个标准尺寸的空白Screen
std::vector<Screen> screens; // 前向声明无法访问成员,所以这里不能赋予默认值
// 如上所示,当我们提供一个类内初始值时,必须以 "=" 或 "{ }" 表示的直接初始化
};
Screen.h
#pragma once
#include <string>
#include <ostream>
#include "Window_mgr.h"
/**
* 窗口类
*/
class Screen
{
public:
// 声明Window_mgr类为Screen类的友元,友元类的成员函数可以访问此类的所有成员
// 友元可以是某个类,某个类的成员函数,或普通函数
// 声明友元成员函数:将Window_mgr类的clear成员声明为Screen的友元
// 要访问Window_mgr的成员就必须先定义它,然后include到这里,不能是前向声明
friend void Window_mgr::clear(ScreenIndex si);
// 声明一个类型别名,或可使用typedef,用户可以使用这个名字
using pos = std::string::size_type;
Screen() = default; // 默认构造函数,内联
~Screen() = default; // 默认析构函数,内联
// 自定义构造函数
Screen(pos ht, pos wd, char c) : height(ht), width(wd), contents(width * height, c){ }
// 读取光标处的字符,隐式内联
char get() const { return contents[cursor]; }
// 重载,类成员函数要想声明内联,必须在声明的同时定义,就像上一个get一样,否则无法编译
char get(pos r, pos col) const;
// 设置光标所在位置的字符
Screen &set(char c);
// 设置给定位置的字符
Screen &set(pos r, pos col, char c);
// 移动光标
Screen &move(pos r, pos c);
// 打印屏幕内容,返回当前对象的常量引用
Screen &display(std::ostream &os);
// 根据调用对象是否是const重载display函数
const Screen &display(std::ostream &os) const;
private:
pos cursor = 0; // 鼠标位置
pos height = 0, width = 0; // 屏幕宽高
std::string contents; // 屏幕内容
// 可变数据成员,即使在一个const对象内也能被修改,用来对函数调用计数
mutable size_t access_ctr;
void do_display(std::ostream &os) const;
};
Window_mgr.cpp
#include "Window_mgr.h"
#include "Screen.h" // 定义中需要访问Screen的成员,所以此时要include,不能再使用前向声明
Window_mgr::Window_mgr(/* args */)
{
}
Window_mgr::~Window_mgr()
{
}
void Window_mgr::clear(ScreenIndex sindex)
{
// s是一个Screen类型的引用,指向我们想清空的那个屏幕
Screen &s = screens[sindex];
// 将选定的Screen重置为空白
s.contents = std::string(s.height * s.width, ' ');
}
フォーカス:使用使用して、ソース・ファイル内のステートメントにメインクラスの前に友人のカテゴリWindow_msgヘッダファイルinclude
循環依存を避けるために、メインクラス、メンバ関数とアクセスの定義メインクラスのメンバーを。直接メインクラスのヘッダファイルでinclude
友人クラス。