目次
1.4.2. auto を直接使用して配列を宣言することはできません
1. auto キーワード (C++11)
1.1 はじめに
学びが深まると、文字が綴りにくい・意味が不明瞭になるなど、プログラムや使用する文字が複雑になり、間違いが起こりやすくなります。
例えば:
#include <string>
#include <map>
int main() {
std::map<std::string, std::string> m{
{"apple", "苹果"},
{"orange","橙子"},
{"pear", "梨"}};
std::map<std::string, std::string>::iterator it = m.begin();
while (it != m.end()) {
//....
}
return 0;
}
std::map<std::string, std::string>::iteratorは型ですが、型が長すぎて書き間違えやすいし、使うのが割と面倒です。
1.2 はじめに
初期の C/C++におけるauto の意味は次のとおりです: auto で変更された変数は自動メモリを備えたローカル変数ですが、残念ながら誰もそれを使用していません。
C++11 では、標準委員会は auto にまったく新しい意味を与えました。auto はもはやストレージ型インジケータではなく、コンパイラに指示する新しい型インジケータであり、auto で宣言された変数は、次から派生したコンパイラによってコンパイルされる必要があります。期間。
#include <iostream>
#include <typeinfo>
using namespace std;
int TestAuto()
{
return 10;
}
int main() {
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
return 0;
}
C++ では、
typeid
式の型情報を取得するために使用される演算子です。これを呼び出すと、式のタイプを表す文字列が返されます。この文字列は通常、コンパイラ固有であり、人間が判読できない場合がありますが、デバッグやその他の目的に使用できます。typeid(d).name()
d
を使用するには
typeid
、ヘッダー ファイルをインクルードする必要があります<typeinfo>
。
注意: auto を使用して変数を定義する場合は、初期化する必要があります。コンパイル段階で、コンパイラは初期化式に従って auto の実際の型を推測する必要があります。したがって、auto は「型」宣言ではなく、型が宣言されたときの「プレースホルダー」であり、コンパイラはコンパイル中に auto を変数の実際の型に置き換えます。
1.3 使用規則
1.3.1 ポインターおよび参照と自動結合
auto を使用してポインター型を宣言する場合、autoとauto*を使用することに違いはありませんが、 auto を使用して参照型を宣言する場合は、 & を追加する必要があります。
int main() {
int x = 10;
auto a = &x;
auto *b = &x;
auto &c = x;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
*a = 20;
*b = 30;
c = 40;
return 0;
}
ここで、 aとb は両方ともint*型であり、cはint型であると推定されます。
1.3.2 同じ行で複数の変数を定義する
同じ行で複数の変数を宣言する場合、変数は同じ型でなければなりません。そうでない場合、コンパイラはエラーを報告します。
なぜなら、コンパイラーは実際には最初の型のみを推定し、その後、推定された型を使用して他の変数を定義するからです。
void TestAuto()
{
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}
1.3.3 その他のアプリケーション
実際の auto の最も一般的な利点は、後述する C++11 で提供される新しい for ループやラムダ式と組み合わせて使用することです。
1.3.4 C++11 自動
C++98 の auto との混同を避けるために、C++11 では型インジケーターとしての auto の使用のみが維持されます。
1.4 auto を導出できないシナリオ
1.4.1 auto は関数パラメータとして使用できません
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
C++ 標準では、関数の仮パラメータの型は型推定を使用するのではなく、明示的に指定された型でなければならないと規定しています。したがって、このようなコードを
auto
関数パラメーターの型として使用すると、コンパイルに失敗します。
1.4.2. auto を直接使用して配列を宣言することはできません
void TestAuto()
{
int a[] = {1,2,3};
auto b[] = {4,5,6};
}
C++ では、auto
を使用して変数を宣言すると、コンパイラは初期化式の型に基づいて変数の型を推測します。
ただし、auto b[] = {4, 5, 6};
そのような構文は許可されません。
この場合、初期化子はb
具体的な配列型で{4, 5, 6}
はなく初期化子リストであるため、コンパイラは配列の型を推定できません。使用する場合auto
、コンパイラは、メモリを正しく割り当てて型チェックを実行するために、変数の型を明示的に知る必要があります。
2. 範囲ベースの for ループ (C++11)
C++11 では、範囲ベースの for ループと呼ばれる新しい for ループ構文が導入されています。これにより、イテレータやインデックスを使用せずに、コンテナ内の要素をより簡単に反復できるようになります。このループは、配列、ベクトル、リスト、セットなどの反復子をサポートするすべてのコンテナーで機能します。
2.1 基本的な構文
for (element_type variable : container) {
// 循环体,使用 variable 访问容器中的元素
}
ここでelement_type
、 はコンテナ内の要素のタイプ、variable
はループ内で使用する変数名、container
は走査するコンテナです。
例として、範囲ベースの for ループを使用してベクトルを反復処理します。
#include <iostream>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 使用基于范围的for循环遍历向量
for (int num : numbers) {
std::cout << num << " ";
}
return 0;
}
この例では、 という名前のベクトルを宣言しnumbers
、範囲ベースの for ループを使用してそのベクトル内の要素を反復し、各要素を標準出力に出力します。
範囲ベースの for ループには、コードの読みやすさと簡潔さの点で、特にコンテナ内のすべての要素を走査する場合に大きな利点があります。ただし、ループ本体内のインデックスまたはイテレータにアクセスする必要がある場合は、従来の for ループを使用する必要があります。
読者はコンテナに触れていない可能性があるため、ここを参照してください。作成者は配列の例を再度使用しています。
#include <iostream>
int main() {
int arr[] = {1, 2, 3, 4, 5};
// 使用基于范围的for循环遍历数组
for (int num : arr) {
std::cout << num << " ";
}
return 0;
}
出力は次のようになります。1 2 3 4 5
上記の例では、 という名前の配列を宣言しarr
、範囲ベースの for ループを使用してその配列内の要素を反復し、各要素を標準出力に出力しました。
配列を走査するために範囲ベースの for ループを使用する場合、ループ変数の型は配列要素の型と一致する必要があり、そうでない場合はコンパイル エラーが発生することに注意してください。
2.2 使用条件
for ループの反復範囲は決定的である必要があります
配列の場合、それは配列内の最初の要素と最後の要素の範囲です。クラスの場合、begin と end のメソッドが提供される必要があり、begin と end は for ループの反復の範囲です。
例:
void TestFor(int array[])
{
for(auto& e : array)
cout<< e <<endl;
}
上記のコードには問題があります。配列が渡された後は、配列は配列ではなくポインターになり、 for の範囲が不確かになるためです。
3. ポインタ空値 nullptr (C++11)
3.1 C++98 におけるポインター null
C/C++ プログラミングを適切に実践するには、変数を宣言するときに変数に適切な初期値を与えることが最善です。そうしないと、初期化されていないポインタなどの予期しないエラーが発生する可能性があります。ポインタが正当なポイントを持たない場合、基本的に次のように初期化します。
void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
// ……
}
NULL は実際にはマクロであり、従来の C ヘッダー ファイル (stddef.h) には次のコードがあります。
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
ご覧のとおり、NULL はリテラル定数 0 として定義することも、型なしポインターの定数 (void*) として定義することもできます。どのような定義を採用しても、null 値のポインタを使用する場合、次のようないくつかの問題が必然的に発生します。
void f(int) {
cout << "f(int)" << endl;
}
void f(int *) {
cout << "f(int*)" << endl;
}
int main() {
f(0);
f(NULL);
f((int *) NULL);
return 0;
}
プログラムの本来の意図は f(NULL) を通じて f(int*) 関数のポインタ版を呼び出すことですが、NULL は 0 として定義されているため、プログラムの本来の意図に反します。
C++98 では、リテラル定数 0 は整数または型なしポインター (void*) 定数のいずれかになりますが、コンパイラーはデフォルトでそれを整数定数として扱います。ポインター モードで使用するには、次のようにキャストする必要があります。 (void *)0。
3.2 注意事項
- 1. nullptr を使用してポインタの null 値を表す場合、nullptr は C++11 の新しいキーワードとして導入されるため、ヘッダー ファイルをインクルードする必要はありません。
- 2. C++11 では、sizeof(nullptr) と sizeof((void*)0) は同じバイト数を占めます。
- 3. コードの堅牢性を向上させるために、ポインタの null 値を表す場合は nullptr を使用することをお勧めします。
これで C++ の入門は終わりです。!