9.1 マルチファイルプログラム
一方のファイル (ヘッダー ファイル) にはユーザー定義型の定義が含まれており、もう一方のファイルにはユーザー定義型を操作する関数のコードが含まれています。これら 2 つのファイルは、さまざまなプログラムで使用できるソフトウェア パッケージを形成します。
ヘッダー ファイルによく含まれるコンテンツ:
- 関数のプロトタイプ。
- #define または const を使用して定義されたシンボリック定数。
- 構造体の宣言。
- クラス宣言。
- テンプレート宣言。
- インライン関数。
独自のヘッダー ファイルをインクルードする場合は、山括弧の代わりに引用符を使用する必要があります。
- ファイル名が山かっこで囲まれている場合、C++ コンパイラは、標準ヘッダー ファイルが保存されているホスト システムのファイル システムを調べます。
- ただし、ファイル名が二重引用符で囲まれている場合、コンパイラはまず現在の作業ディレクトリまたはソース コード ディレクトリ (コンパイラによっては他のディレクトリ) を調べます。
- ヘッダー ファイルが現在の作業ディレクトリに見つからない場合は、標準の場所で検索されます。
- IDE では、ヘッダー ファイルをプロジェクト リストに追加したり、ソース コード ファイルで #include を使用して他のソース コード ファイルをインクルードしたりしないでください。
// CMakeLists.txt
cmake_minimum_required(VERSION 3.23)
project(PrimerPlus)
set(CMAKE_CXX_STANDARD 11)
include_directories(chapter_9) // 头文件路径
// 将file1和file2一起编译链接,只要两个文件包含相同的头文件,且file2中定义的函数在头文件中声明,则file1可以调用file2的函数且不需要声明
add_executable(file1 chapter_9/file1.cpp chapter_9/file2.cpp)
// coordin.h -- structure templates and function prototypes
// structure templates
// 仅当以前没有使用预处理器编译指令#define定义名称COORDINH时,才处理#ifndef和#endif之间的语句:
#ifndef PRIMERPLUS_COORDIN_H // PRIMERPLUS是工程的名字
#define PRIMERPLUS_COORDIN_H
#endif
9.2 ストレージの永続性、スコープ、およびリンケージ
- 自動ストレージ永続性: 関数定義 (関数パラメーターを含む) 内で宣言された変数のストレージ永続性は自動的に行われます。これらは、プログラムが属する関数またはコード ブロックの実行を開始するときに作成され、関数またはコード ブロックの実行が終了すると、使用されるメモリが解放されます。
- 静的記憶永続性: 関数定義外で定義された変数およびキーワード static で定義された変数の記憶永続性は静的です。これらはプログラムの実行全体にわたって存在します。
- 動的ストレージ永続性: new 演算子で割り当てられたメモリは、delete 演算子を使用して解放されるか、プログラムが終了するまで存在します。このメモリの永続性は動的であり、フリー ストアまたはヒープと呼ばれることもあります。
- スレッド ストレージの永続性 (C++11) : 現在、マルチコア プロセッサが一般的であり、これらの CPU は同時に複数の実行を処理できます。これにより、プログラムは並列処理できる個別のスレッドに計算を配置できるようになります。変数がキーワード thread_local で宣言されている場合、その有効期間は、その変数が属するスレッドと同じ長さになります。
9.2.1 範囲と連携
- 外部結合のある名前はファイル間で共有できますが、内部結合のある名前は 1 つのファイル内の関数でのみ共有できます。
- 自動変数の名前は共有できないため、関連付けがありません。
- コードのブロックが実行されると、変数にメモリが割り当てられますが、スコープの開始点は宣言された場所になります。
9.2.2 自動ストレージ永続化
同じ名前の変数が 2 つある場合はどうなるでしょうか (1 つは外側のコード ブロックに、もう 1 つは内側のコード ブロックにあります)。
新しい定義は前の定義を非表示 (非表示) にし、新しい定義は表示され、古い定義は一時的に非表示になります。プログラムがコード ブロックを離れると、元の定義が再び表示されるようになります。以下に示すように
C++11 では、自動型推論にキーワード auto が使用されます。
ただし、C 言語と以前の C++ バージョンでは auto の意味は大きく異なり、変数が自動的に格納されることを明示的に示すために使用されます。
- 自動変数の初期化:
自動変数は、宣言時に値がわかっている任意の式で初期化できます。
- 自動変数とスタック:
- 自動変数の数は関数の開始と終了に応じて増減するため、プログラムは実行時に自動変数を管理する必要があります。一般的な方法は、メモリのセクションを確保し、それをスタックとして扱い、変数の増加と減少を管理することです。
- これがスタックと呼ばれる理由は、新しいデータが元のデータの上に (つまり、同じメモリ ユニットではなく隣接するメモリ ユニットに) 象徴的に配置されるためです。プログラムが使用し終わったら、そのデータをスタックから削除します。スタック。
- スタックのデフォルトのサイズは実装に依存しますが、コンパイラは通常、スタックのサイズを変更するオプションを提供します。プログラムは 2 つのポインターを使用してスタックを追跡します。1 つはスタックの先頭 (スタックの開始位置) を指すポインターで、もう 1 つはヒープの先頭 (次に利用可能なメモリ位置) を指すポインターです。関数が呼び出されると、その自動変数がスタックに追加され、スタック トップ ポインタは変数の後ろにある次に使用可能なメモリ ユニットを指します。関数が終了すると、スタックのトップ ポインターは関数が呼び出される前の値にリセットされ、新しい変数によって使用されていたメモリが解放されます。
- スタックは LIFO (Last In First Out) です。つまり、スタックに最後に追加された変数が最初にポップされます。この設計により、パラメータの受け渡しが簡素化されます。関数呼び出しは、引数の値をスタックの先頭に置き、スタックの先頭ポインタをリセットします。呼び出された関数は、パラメータの説明から各パラメータのアドレスを決定します。
- レジスタ変数
- C++11 では、キーワード register は、変数が自動であることを明示的に示すだけです。本質的に自動である変数にのみ使用できます。
これを使用する唯一の理由は、プログラマが外部変数と同じ名前を持つ可能性がある自動変数を使用したいことを示すことです。
これはまさに auto が行っていたことです。ただし、キーワード register を保持する重要な理由は、このキーワードを使用する既存のコードが違法になるのを防ぐためです。 - C 言語では、キーワード register は、変数へのアクセス速度の向上を目的として、コンパイラが自動変数を格納するために CPU レジスタを使用することを示唆しています。
register int count_fast;
- C++11 では、キーワード register は、変数が自動であることを明示的に示すだけです。本質的に自動である変数にのみ使用できます。
9.2.3 静的永続変数
C++ では、静的に保存された永続変数に対して 3 種類のリンク機能も提供します。
- 外部リンケージ(他のファイルからアクセス可能) コードブロック外で宣言
- 内部リンケージ (現在のファイル内でのみアクセス可能) コード ブロックの外で宣言し、静的修飾子を使用します
- リンク不可 (現在の関数またはコード ブロック内でのみアクセス可能) コード ブロック内で宣言し、静的修飾子を使用します
int global = 1000; // static duration, external linkage
static int one_file = 50; // static duration, internal linkage
int main()
{
...
}
void funct1(int n)
{
static int count = 0; // static duration, no linkage
// 即使在 funct1( )函数没有被执行时,count也留在内存中。
int llama = 0;
...
}
静的変数の初期化:
- 静的初期化には、ゼロ初期化
int a;
と定数式初期化が含まれます。int b=5;
- ゼロ初期化: 静的変数が明示的に初期化されていない場合、コンパイラーはそれを 0 に設定します。
- 静的な配列と構造体では、各要素またはメンバーのすべてのビットがデフォルトで 0 に設定されます。
- NULL ポインターは 0 で表されますが、内部的にはゼロ以外の表現が使用される場合があります。ポインター変数は、対応する内部表現に初期化されます。
- 静的初期化では、コンパイラがファイル (翻訳単位) を処理するときに変数を初期化します。動的初期化とは、コンパイル後に変数が初期化されることを意味します。
- C++11 では、定数式を作成する方法を追加するキーワード constexpr が追加されています。
#include <cmath>
int x; // zero-initialization
int y = 5; // constant-expression initialization
long z = 13 * 13; // constant-expression initialization
const double pi = 4.0 * atan(1.0); // dynamic initialization
int enough = 2 * sizeof (long) + 1; // constant expression initialization
9.2.4 静的永続性、外部リンク可能性
C++ には、変数は 1 回だけ定義できるという「単一定義ルール」(One Definition Rule、ODR) があります。
C++ では、次の 2 種類の変数宣言が提供されます。
- 1 つは定義ステートメント (定義) で、変数に記憶領域を割り当てます。
- 1 つは参照宣言 (宣言) です。これは既存の変数を参照するため、変数にストレージを割り当てません。
- 参照宣言ではキーワード extern が使用され、初期化されません。それ以外の場合、宣言は定義となるため、ストレージが割り当てられます。
- 複数のファイルで外部変数を使用する場合は、1 つのファイルに変数の定義を含めるだけで済みますが、変数が使用される他のすべてのファイルでは、キーワード extern を使用して宣言する必要があります。
double up; // definition, up is 0
extern int blem; // blem defined elsewhere
extern char gr = 'z'; // definition because initialized
関数内で変数が外部変数と同じ名前で宣言されている場合はどうなりますか?
このような宣言は自動変数の定義として扱われ、プログラムが自動変数が属する関数を実行するときに、その変数はスコープ内に含まれます。
スコープ解決演算子 (::) を変数名の前に置くと、変数のグローバル バージョンが使用されることを示します。
int a = 10;
void main(void)
{
int a = 5;
cout << a << endl; // a = 5;
cout << ::a << endl;// a = 10;
}
外部ストレージは、キーワード const を使用してデータの変更を防ぐことができるため、定数データを表すのに特に適しています。
const char * const months[12] =
{
"January", "February", "March", "April", "May",
"June", "July", "August", "September", "October",
"November", "December"
};
// 第一个const防止字符串被修改,
// 第二个const确保数组中每个指针始终指 向它最初指向的字符串。
9.2.5 静的永続性、内部リンク可能性
- 静的修飾子がファイル全体のスコープを持つ変数に使用される場合、その変数のリンク可能性は内部的なものとなり、その変数が属するファイル内でのみ使用可能になります。
- ただし、ファイルで、別のファイルで宣言された通常の外部変数と同じ名前の静的外部変数が定義されている場合、そのファイルでは、静的変数によって通常の外部変数が隠蔽されます。
- 複数ファイルのプログラムでは、外部変数は 1 つのファイル (1 つのファイルのみ) で定義できます。この変数を使用する他のファイルは、キーワード extern を使用して変数を宣言する必要があります。
// file1
int errors = 20; // external declaration
...
---------------------------------------------
// file2
static int errors = 5; // known to file2 only
// int errors = 10; //这种做法是错误的,违反了单定义规则
void froobish()
{
cout << errors; // uses errors defined in file2
...
9.2.6 静的ストレージの永続性、リンケージなし
- コード ブロック内で定義された変数には static 修飾子を使用します。
- これは、変数はそのコード ブロック内でのみ使用可能ですが、そのコード ブロックがアクティブでないときでも存在することを意味します。
- 静的ローカル変数が初期化される場合、プログラムの起動時に 1 回だけ初期化されます。後で関数が呼び出された場合、自動変数のように再度初期化されることはありません。
// 使用cin.get(next)读取行输入后的字符。
// 如果next是换行符,则说明 cin.get(input, ArSize)读取了整行;
// 否则说明行中还有字符没有被读取。 随后,程序使用一个循环来丢弃余下的字符。
// static.cpp -- using a static local variable
#include <iostream>
const int ArSize = 20; // constants
void strcount(const char * str); // function prototype
int main()
{
using namespace std;
char input[ArSize];
char next;
cout << "Enter a line:\n";
cin.get(input, ArSize);
while (cin)
{
cin.get(next); // 使用cin.get(next)读取行输入后的字符。
while (next != '\n') // string didn't fit!
cin.get(next); // dispose of remainder
strcount(input);
cout << "Enter next line (empty line to quit):\n";
cin.get(input, ArSize); // 使用get(char *, int)读取空行将导致cin为false。
}
cout << "Bye\n";
return 0;
}
void strcount(const char * str)
{
using namespace std;
static int total = 0; // static local variable
int count = 0; // automatic local variable
cout << "\"" << str <<"\" contains ";
while (*str++) // go to end of string
count++;
total += count;
cout << count << " characters\n";
cout << total << " characters total\n";
}
9.2.7 指定子と修飾子
ストレージ指定子:
- auto (C++11 では指定子ではなくなりました): C++11 より前では、キーワード auto を宣言で使用して、変数が自動変数であることを示すことができました。C++11 では、auto は自動型に使用されます。推論。
- register: は宣言内でレジスタの格納場所を示しますが、C++11 では変数が自動であることを明示的に示すだけです。
- static: 有効範囲がファイル全体である宣言で使用される場合は、内部リンケージを示し、ローカル宣言で使用される場合は、ローカル変数の記憶域の永続性が静的であることを示します。
- extern: これは参照宣言であり、初期化できないことを示します。つまり、宣言は他の場所で定義された変数を参照します。
- thread_local (C++11 の新機能): 変数の永続性が、それが属するスレッドの永続性と同じであることを示します。スレッドにとっての thread_local 変数は、プログラム全体にとっての通常の静的変数と同じです。static または extern と組み合わせられる thread_local を除き、同じ宣言内で複数の指定子を使用することはできません。
- Mutable: 構造体 (またはクラス) 変数が const であっても、そのメンバーは変更できます。
struct data
{
char name[30];
mutable int accesses; // 即使声明一个const的结构,也可以对其中的accesses进行修改
...
};
const data veep = {
"Claybourne Clodde", 0, ... };
// strcpy(veep.name, "Joye Joux"); // not allowed
veep.accesses++; // allowed
cv 修飾子: (cv は const および volatile を意味します)
- volatile: プログラム コードがメモリ ユニットを変更しない場合でも、その値は変更される可能性があります。このキーワードの役割は、コンパイラの最適化機能を向上させることです。
- const: メモリが初期化されると、プログラムはメモリを変更できなくなります。
C++11 では、グローバル変数のリンケージはデフォルトで外部ですが、const グローバル変数のリンケージは内部です。内部リンケージは、すべてのファイルが定数セットを共有するのではなく、各ファイルが独自の定数セットを持つことも意味します。各定義は、それが属するファイルに対してプライベートであるため、ヘッダー ファイルに定数の定義を含めることができます。こうすることで、両方のソース コード ファイルに同じヘッダー ファイルが含まれている限り、同じ定数セットが取得されます。
// file1
int a = 5; // 链接为外部,extern能省略。
const int b = 6; // 链接为内部,只能file1文件使用
extern const int f = 10;// 链接为外部,extern不能省略。
static const int c = 7; // 链接为内部,只能file1文件使用,c不能改变
static int e = 9; // 链接为内部,只能file1文件使用,c可以改变
{
static int d = 8; // 无链接
}
--------------------------------
// file2
extern int a; // 引用声明
extern const int f; // 引用const声明
--------------------------------
// xxx.h
// 只要在两个源代码文件中包括同一个头文件,则它们将获得同一组常量。
// const全局变量的链接性为内部,不违背“单定义规则”
const int g = 11;
const int h = 12;
9.2.8 機能とリンク可能性
- 関数プロトタイプでキーワード extern を使用すると、関数が別のファイルで定義されていることを示すことができます。
- キーワード static を使用して関数のリンケージを内部に設定し、1 つのファイルでのみ使用できるようにすることができます。このキーワードは、プロトタイプと関数定義の両方で使用する必要があります。
これは、関数がこのファイルでのみ表示されることを意味し、同じ名前の関数を他のファイルで定義できることも意味します。
変数と同様に、静的関数が定義されているファイルでは、静的関数が外側の定義をオーバーライドするため、外部で同じ名前の関数が定義されている場合でも、ファイルでは静的関数が使用されます。 - インライン関数の定義はヘッダー ファイルに配置できます。
9.2.9 言語のリンク可能性
リンカでは、異なる関数ごとに異なるシンボリック名 (内部表現) が必要です。
- C 言語では、名前は 1 つの関数にのみ対応します。
- C++ では、同じ名前が複数の関数に対応する場合があり、これらの関数は異なるシンボル名に変換する必要があります。したがって、C++ コンパイラは名前マングリングを実行して、オーバーロードされた関数に対して異なるシンボル名を生成します。
// 在C++程序中使用C库中预编译的函数,可以用函数原型来指出要使用的约定:
extern "C" void spiff(int); // use C protocol for name look-up
extern void spoff(int); // use C++ protocol for name look-up
extern "C++" void spaff(int); // use C++ protocol for name look-up
9.2.10 ストレージスキームと動的割り当て
通常、コンパイラは 3 つの個別のメモリ ブロックを使用します。1 つは静的変数 (細分化される可能性があります) 用、もう 1 つは自動変数用、もう 1 つは動的ストレージ用です。
ストレージ スキームの概念は動的メモリには適用されませんが、動的メモリを追跡するために使用される自動ポインタ変数と静的ポインタ変数には適用されます。
1. new 演算子を使用して初期化します
C++98 では、記憶域スペースを割り当てて組み込みスカラー型 (int や double など) を初期化する場合、型名の後に初期値を追加してかっこで囲むことができます。
int *pi = new int (6); // *pi set to 6
double * pd = new double (99.99); // *pd set to 99.99
C++11 では、通常の構造体または配列を初期化でき、単一値変数に対してリスト初期化も使用できます。
struct where {
double x; double y; double z;};
where * one = new where {
2.5, 5.3, 7.2}; // C++11
int * ar = new int [4] {
2,4,6,7}; // C++11
int *pin = new int {
}; // *pi set to 0
double * pdo = new double {
99.99}; // *pd set to 99.99
2.新しい失敗
new では、要求された量のメモリが見つからない可能性があります。最初の 10 年間、C++ はこの場合に new が null ポインターを返すようにしていましたが、現在は例外 std::bad_alloc が発生します。
3. 新しい: 演算子、関数、および置換関数
void * operator new(std::size_t); // used by new
void * operator new[](std::size_t); // used by new[]
void operator delete(void *);
void operator delete[](void *);
// 运算符重载,std::size_t是一个 typedef
int * pi = new int;
int * pi = new(sizeof(int));
int * pa = new int[40];
int * pa = new(40 * sizeof(int));
delete pi;
delete (pi);
4. 新しい演算子の配置
通常、new は、リクエストを満たすのに十分な大きさのメモリ ブロックをヒープ内で見つける役割を果たします。
new 演算子の別のバリエーションとして、placement new 演算子と呼ばれるものがあり、これを使用すると、使用する場所を指定できます。
- 新しい位置決め機能を使用するには、まず、このバージョンの new オペレーターのプロトタイプを提供するヘッダー ファイル new をインクルードする必要があります。
#include <new>
- 次に、新しい演算子が、目的のアドレスを提供するパラメータで使用されます。
- 位置指定の new 演算子を使用する場合、変数の後に角括弧が続く場合とそうでない場合があります。
#include <new> // 提供了这种版本的new运算符的原型
struct chaff
{
char dross[20];
int slag;
};
char buffer1[50]; // 使用两个静态数组来为定位new运算符提供内存空间。
char buffer2[500];
int main()
{
chaff *p1, *p2;
int *p3, *p4;
// first, the regular forms of new
p1 = new chaff; // place structure in heap
p3 = new int[20]; // place int array in heap
// now, the two forms of placement new
p2 = new (buffer1) chaff; // place structure in buffer1
p4 = new (buffer2) int[20]; // place int array in buffer2
...
標準の Positioned New は、2 つの引数を取る new() 関数を呼び出します。Positioned
new 関数は置換できませんが、オーバーロードできます。少なくとも 2 つのパラメータを取ります。最初のパラメータは常に std::size_t で、要求されたバイト数を指定します。
int * pi = new int; // invokes new(sizeof(int))
int * p2 = new(buffer) int; // invokes new(sizeof(int), buffer)
int * p3 = new(buffer) int[40]; // invokes new(40*sizeof(int), buffer)
9.3 名前空間
潜在的なスコープ:変数の潜在的なスコープは、宣言の時点で始まり、その宣言領域の終わりで終わります。したがって、変数は使用する前に定義する必要があるため、潜在的なスコープは宣言領域よりも小さくなります。
C++では、名前を宣言するための領域を提供する目的の一つとして、新たに宣言領域を定義して名前付き名前空間を作成する機能が追加されました。ある名前空間内の名前は別の名前空間内の同じ名前と衝突せず、プログラムの他の部分がその名前空間で宣言されたものを使用できるようになります。
- 名前空間はグローバルにすることも、別の名前空間内に配置することもできますが、コード ブロック内には配置できません。
- どの名前空間の名前も、他の名前空間の名前と衝突することはできません。
- 名前空間はオープン (オープン) です。つまり、名前を既存の名前空間に追加できます。
namespace Jack
{
double pail; // variable declaration
void fetch(); // function prototype
int pal; // variable declaration
struct Well {
... }; // structure declaration
}
namespace Jill
{
double bucket(double n) {
... } // function definition
double fetch; // variable declaration
int pal; // variable declaration
struct Hill {
... }; // structure declaration
}
namespace Jill
{
char * goose(const char *); // 名称空间中添加变量
}
namespace Jack
{
void fetch() // 函数定义
{
...}
}
Jack::pail = 12.34; // use a variable
Jill::Hill mole; // create a type Hill structure
Jack::fetch(); // use a function
9.3.1 using ステートメントと using コンパイラ ディレクティブ
- using プラグマにより、名前空間全体が使用可能になります。
using namespace std;
- using 宣言は、特定の識別子を使用可能にします。using 宣言は、キーワード using の後に続く修飾名で構成されます。
using Jill::fetch;
- using 宣言が関数の外で使用される場合、名前はグローバル名前空間に追加されます。
- コンパイラでは、2 つの using で同時に同じ名前の変数を宣言することはできません。スコープ解決演算子を使用できます。
// using声明由被限定的名称和它前面的关键字using组成:
using Jill::fetch; // a using declaration
// using声明将特定的名称添加到它所属的声明区域中
namespace Jill {
double bucket(double n) {
... }
double fetch;
struct Hill {
... };
}
char fetch;
int main()
{
using Jill::fetch; // put fetch into local namespace
double fetch; // Error! Already have a local fetch
cin >> fetch; // read a value into Jill::fetch
cin >> ::fetch; // read a value into global fetch
...
}
// 编译器不允许同时使用两个using声明相同名称变量,可以使用作用域解析运算符。
jack::pal = 3;
jill::pal =10;
// 编译器不允许同时使用两个using声明相同名称变量
// using jack::pal;
// using jill::pal;
9.3.2 using コンパイラ指令と using 文の比較
名前空間と宣言領域が同じ名前を定義すると仮定します。
- using 宣言を使用して名前空間の名前をその宣言領域にインポートしようとすると、2 つの名前が衝突し、エラーが発生します。
- using プラグマを使用してその名前空間の名前をその宣言領域にインポートすると、ローカル バージョンでは名前空間のバージョンが非表示になります。
namespace Jill {
double bucket(double n) {
... }
double fetch;
struct Hill {
... };
}
char fetch; // global namespace
int main()
{
using namespace Jill; // import all namespace names
Hill Thrill; // create a type Jill::Hill structure
double water = bucket(2); // use Jill::bucket();
double fetch; // not an error; hides Jill::fetch
cin >> fetch; // read a value into the local fetch
cin >> ::fetch; // read a value into global fetch
cin >> Jill::fetch; // read a value into Jill::fetch
...
}
int foom()
{
// Hill top; // ERROR
// Jill::Hill crest; // valid
using Jill::Hill crest;
}
9.3.3 ネストされた名前空間
- ネストされた名前空間を使用して、一般的に使用される using 宣言を含む名前空間を作成できます。
- 名前空間では using プラグマと using 宣言を使用できます。
- ネームスペースのエイリアスを作成することができます。
// 可以用嵌套式名称空间来创建一个包含常用using声明的名称空间:
namespace elements
{
namespace fire
{
int flame;
...
}
float water;
}
// 使用下面的using编译指令使内部名称可用:
using namespace elements::fire;
// 在名称空间中使用using编译指令和using声明:
namespace myth
{
using Jill::fetch;
using namespace elements;
using std::cout;
using std::cin;
}
std::cin >> myth::fetch; // 可以这样访问fetch
std::cout << Jill::fetch; // 也可以这样访问fetch
// 可以给名称空间创建别名:
namespace my_very_favorite_things {
... };
namespace mvft = my_very_favorite_things;
namespace MEF = myth::elements::fire;
using MEF::flame;
9.3.4 名前のない名前空間
- これは、あたかも using ディレクティブが続いているようなものです。つまり、その名前空間で宣言された名前の潜在的なスコープは、宣言の時点から宣言領域の終わりまでです。この点で、これらはグローバル変数に似ています。
- 名前のない名前空間内の名前は、それが属するファイル以外のファイルでは使用できません。
- これにより、内部静的変数の代わりにリンケージが提供されます。
static int counts; // 全局静态变量,链接为内部。
// 可以用下面的替代
namespace
{
int counts; // static storage, internal linkage
}
9.4 概要
- C++ では、プログラマーがプログラムを開発する際に複数のファイルを使用することが推奨されています。効果的な組織戦略は、ヘッダー ファイルを使用してユーザー タイプを定義し、ユーザー タイプを操作する関数の関数プロトタイプを提供し、関数定義を別のソース コード ファイルに配置することです。ヘッダー ファイルとソース コード ファイルは一緒に、ユーザー定義型とその使用方法を定義および実装します。最後に、main( ) と、これらの関数を使用するその他の関数を 3 番目のファイルに配置します。
- C++ のストレージ スキームは、変数がメモリ内にどのくらいの時間留まるか (ストレージの永続性)、およびプログラムのどの部分が変数にアクセスできるか (スコープとリンケージ) を決定します。自動変数は、コード ブロック (関数本体や関数本体内のコード ブロックなど) 内で定義された変数であり、プログラムの実行が定義を含むコード ブロックに到達した場合にのみ存在し、表示されます。自動変数は、記憶域タイプ指定子レジスタを使用して宣言することも、指定子をまったく使用せずに宣言することもできます。指定子が使用されない場合、変数はデフォルトで自動になります。register 指定子は、変数が頻繁に使用されることをコンパイラに示唆しますが、C++11 はこの使用法を破棄します。
- 静的変数はプログラムの実行全体にわたって存在します。関数の外部で定義された変数は、変数の定義 (ファイル スコープ) に従う所有ファイル内のすべての関数と、プログラム内の他のファイル (外部リンケージ) で使用できます。この変数を別のファイルで使用するには、extern キーワードを使用して宣言する必要があります。ファイル間で共有する変数の場合は、一方のファイルにその定義宣言を含め(extern なし、ただし同時に初期化すれば使用可能)、もう一方のファイルには参照宣言を含めます(extern を使用し、初期化なし)。関数の外でキーワード static を使用して定義された変数は、ファイル全体のスコープを持ちますが、他のファイルで使用することはできません (内部リンケージ)。コード ブロック内でキーワード static を使用して定義された変数は、コード ブロック (ローカル スコープ、リンケージなし) に制限されますが、常に存在し、プログラムの実行全体にわたって元の値を維持します。
- デフォルトでは、C++ 関数には外部リンケージがあるため、ファイル間で共有できますが、キーワード static で修飾された関数には内部リンケージがあり、それが定義されているファイルに限定されます。
- 動的なメモリの割り当てと割り当て解除は、new と delete を使用して行われ、空きストレージまたはヒープを使用してデータを保存します。new を呼び出すとメモリが占有され、delete を呼び出すとメモリが解放されます。プログラムはポインタを使用してこれらのメモリ位置を追跡します。
- ネームスペースを使用すると、識別子を宣言できる名前付き領域を定義できます。これの目的は、特にプログラムが非常に大きく、複数のベンダーのコードを使用している場合に、名前の競合を減らすことです。名前空間内の識別子は、スコープ解決演算子、using 宣言、または using プラグマを使用して利用可能になります。
9.5 質問の確認
2. 宣言を使用することとプラグマを使用することの違いは何ですか?
- using 宣言により、ネームスペース内で 1 つの名前が使用可能になり、そのスコープは using が配置される宣言領域と同じになります。
- using プラグマを使用すると、名前空間内のすべての名前が使用可能になります。
- using ディレクティブを使用すると、using 宣言と名前空間自体を含む最小限の宣言領域で名前が宣言されたかのようになります。
5. Average(3, 6) 関数が 1 つのファイルで呼び出された場合、この関数は 2 つの int パラメータの int 平均を返し、同じプログラムの別のファイルで呼び出された場合、2 つの int パラメータの二重平均を返します。それはどのように達成されるべきでしょうか?
各ファイルに個別の静的関数定義を含めることができます。または、各ファイルは、名前のない名前空間で適切な Average( ) 関数を定義し、その前に static を付けます。
9.6 プログラミング演習
2. 入力が空かどうかを判断します
- 文字配列を使用する
const ArSize = 20;
char input[ArSize];
char next;
cout << "Enter a line:\n";
cin.get(input, ArSize);
while (cin)
{
cin.get(next); // 使用cin.get(next)读取行输入后的字符。
while (next != '\n') // string didn't fit!
cin.get(next); // dispose of remainder
cout << input << endl;
cout << "Enter next line (empty line to quit):\n";
cin.get(input, ArSize); // 使用get(char *, int)读取空行将导致cin为false。
}
-------------------------------
char name[20];
cin.getline(name, 20);
if (strcmp(name, "") == 0)
break;
- 文字列を使用する
string input;
cout << "Enter a line:\n";
getline(cin, input);
while (input != "") // 这里注意
{
strcount(input);
cout << "Enter next line (empty line to quit):\n";
getline(cin, input);
}
------------------
string fullname;
getline(cin, fullname);
if (strcmp(name, "") == 0)
break;
// 3、编写一个程序,使用定位new运算符将一个包含两个这种结构的数组放在一个缓冲区中。
// 然后,给结构的成员赋值(对于char数组,使用函数strcpy( )),并使用一个循环来显示内容。
// 一种方法是像程序清单 9.10那样将一个静态数组用作缓冲区;
// 另一种方法是使用常规new运算符来分配缓冲区。
#include <iostream>
#include <cstring>
#include <new>
using namespace std;
struct chaff
{
char dross[20];
int slag;
};
const int BUF = 200;
char buffer[BUF];
void show(const chaff &p);
int main(void)
{
chaff * pd1 = new chaff[2];
chaff * pd2 = new(buffer) chaff[2]; // 使用定位new运算符
char dross[20];
int slag;
for (int i=0; i<2; i++)
{
cout << "#" << i+1 << " :" << endl;
cout << "Enter the dross:";
cin.getline(dross, 20);
cout << "Enter the slag:";
cin >> slag;
cin.get(); // 这里要注意,消耗回车
strcpy(pd1[i].dross, dross);
strcpy(pd2[i].dross, dross);
pd1[i].slag = slag;
pd2[i].slag = slag;
}
for (int i=0; i<2; i++)
{
show(pd1[i]);
show(pd2[i]);
}
delete [] pd1; // 这里只能释放动态
return 0;
}
void show(const chaff &p) // 这里用结构体引用
{
cout << "The dross is: " << p.dross << endl;
cout << "The slag is:" << p.slag << endl;
}