目次
低レイヤーのプログラミングとOS開発が趣味。C言語を使っています。
ポインタ関連
参照とポインタの違い
ポインタと参照の決定的な違いの一つは、指す先を後から変更できるかどうかというところにあります。
#include <iostream>
using namespace std;
int main() {
// 参照
int m = 1;
int n = 2;
int& intref = m;
intref = n; // 指す先 m の値を変更しているだけ
cout << "m: " << m << ", " //=> 2
"n: " << n << ", " //=> 2
"intref: " << intref << endl; //=> 2
// ポインタ
int mm = 1;
int nn = 2;
int* intptr = &mm;
intptr = &nn; // 指す先を変更できる
cout << "mm: " << mm << ", " //=> 1 (変更されていない)
"nn: " << nn << ", " //=> 2
"intptr: " << *intptr << endl; //=> 2
return 0;
}
配列とポインタの違い
同様にポインタと配列の決定的な違いの一つも、指す先を後から変更できるかどうかというところにあります。
#include <iostream>
using namespace std;
int main() {
int arr[8];
int* ptr;
int n[8];
ptr = n; // ← これは問題ないですが
// arr = n; // ← これはエラーです。
return 0;
}
ポインタと配列のもう一つの重要な違いは、sizeof 演算子が返す値が配列全体のサイズであるか、ポインタ自身がアドレスを格納している領域のサイズであるか、というところにあります。
#include <iostream>
using namespace std;
int main() {
int arr[8];
int* ptr;
ptr = arr;
cout << sizeof arr << endl; //=> 32
cout << 8 * sizeof(int) << endl; //=> 32
cout << sizeof ptr << endl; //=> 4
cout << sizeof (int*) << endl; //=> 4
return 0;
}
そのため、一旦ポインタに入れると、配列サイズが取得できなくなるという事実が有名です。関数の引数で配列の先頭アドレスを渡すときは、サイズも渡すか、もしくは配列に (文字列のヌルターミネータのように) ターミネータを付与しておく必要があります。これは有名なはまりどころです。
#include <iostream>
using namespace std;
int main() {
int arr[8];
int* ptr;
ptr = arr;
cout << sizeof arr / sizeof *arr << endl; //=> 8
cout << sizeof arr / sizeof arr[0] << endl; //=> 8 と同じ意味です
cout << sizeof ptr / sizeof *ptr << endl; //=> 1
cout << sizeof(int*) / sizeof(int) << endl; //=> 1 と同じ意味です
return 0;
}
文字列リテラルに関しても配列とポインタの違いをみることができます。
#include <iostream>
using namespace std;
const char* MyFunc() {
// ポインタの文字列リテラル
// - 静的に確保されたデータのため return してもよいです。
// (関数を抜けた後に領域が解放されないためです)
return "MyFunc";
// 配列の文字列リテラル
// - 後述の通り静的データではないため関数を抜けた時点で
// 値の存在は保証されません。return してはなりません。
// char str[] = "MyFunc";
// return str;
}
int main() {
// ポインタの初期化
// - 静的変数と同様に、プログラム実行時に既にメモリ領域が
// 確保されており、main() 時には内容も初期化済みです。
const char* ptr = "main"; // 注意: その内容も書き換えてはいけない仕様です。
// (const を使用しましょう)
cout << ptr << endl; //=> MyFunc
cout << MyFunc() << endl; //=> main
// 配列の初期化
// - 静的なデータとはなりません
char str[] = "main";
// char str[] = {'m', 'a', 'i', 'n', '\0'}; // と同じ意味です
str[0] = 'M'; // 内容の変更も許されています
cout << str << endl; //=> Main
return 0;
}
ポインタの複数同時宣言 (のつもり)
ポインタの宣言を複数同時にやろうとすると意図しない宣言になります。
#include <iostream>
using namespace std;
int main() {
// int* p, q; // qはintになってしまいます。
int* p; // 一行ずつ分けて
int* q; // 宣言しましょう。
int n = 8;
p = &n;
q = &n;
cout << *p << endl
<< *q << endl;
return 0;
}
アドレス同士の減算
#include <iostream>
using namespace std;
int main() {
int arr[] = {10, 20, 30};
cout << &arr[2] - &arr[0] << endl; //=> 2 となります。これは、
cout << *(&arr[1] + 1) << endl; //=> 30 が
cout << arr[2] << endl; //=> 30 と等しくなるという事実を知っていると余計に混乱しますが仕様です
return 0;
}
char型関連
1バイトとは
バイトとは char 型のサイズのことです。通常は8ビットです。
#include <iostream>
using namespace std;
int main() {
cout << sizeof(char) << endl; //=> どのような環境でも常に 1 です
return 0;
}
文字コードの16進ダンプ
char型の変数用のメモリに格納された内容を、10進数の正の数としてコンソール出力するためにはやや複雑なキャストが必要です。
#include <iostream>
using namespace std;
int main() {
char ch = 'a';
// 1. char型は負の数として扱われる可能性があるため unsigned char にキャスト
// 2. cout は unsigned char を文字として表示してしまうため int にキャスト
cout << (int)(unsigned char)ch << endl; //=> 97
return 0;
}
文字セットの仕様
文字コードに関して '0' - '9' は連続していることが保証されています。そのため '0' を減することで文字を数値に変換できます。
#include <iostream>
using namespace std;
int main() {
char ch = '1';
int n = ch - '0';
cout << ch << endl; //=> 1 (文字として)
cout << n << endl; //=> 1 (数値として)
return 0;
}
ただし、アルファベットについては連続していることが保証されておらず、文字セットによっては「'A' <= ch && ch <= 'Z'」などとできません。そのため以下のようにします。
#include <iostream>
#include <cctype> // isupper, islower, isdigit
using namespace std;
int main() {
char ch;
cin >> ch;
cout << isupper(ch) << endl; // A-Z ならば真
cout << islower(ch) << endl; // a-z ならば真
cout << isdigit(ch) << endl; // '0'-'9' ならば真 ('0' <= ch && ch <= '9' と同じ)
return 0;
}
その他
数値の修飾子
整数や小数に 'l' や 'u' といった文字を末尾に付与すると、その数値の意味をより詳細に指定することができます。
#include <iostream>
using namespace std;
int main() {
// long, unsigned 関連
long int l_intval = 9999l; // 'l' を末尾に付与すると long 修飾を意味
unsigned int u_intval = 9999u; // 'u' を末尾に付与すると unsigned 修飾を意味
unsigned long int ul_intval = 9999ul;
// float, double 関連
float floatval = 10.0f; // 'f': float
double doubleval = 10.0; // '': double
long double l_doubleval = 10.0l; // 'l': long
cout << "l: " << l_intval << ", "
"u: " << u_intval << ", "
"ul: " << ul_intval << ", "
"f: " << floatval << ", "
"'': " << doubleval << ", "
"l: " << (double)l_doubleval << endl;
return 0;
}
符号判定
整数の符号を判定する有名な方法があります。
#include <iostream>
using namespace std;
int main() {
int n;
cin >> n;
int sign = (n > 0) - (n < 0);
cout << sign << endl; // -1, 0, 1
return 0;
}
有効な配列の範囲
配列において、有効なアドレスの範囲は有効な要素の範囲より一つ大きくなります。
#include <iostream>
using namespace std;
int main() {
int arr[] = {10, 20, 30};
// 値としては arr[0] 〜 arr[2] が有効ですが
// アドレスとしては arr[0] 〜 arr[3] まで有効です。
// 以下のような終端判定として利用されることを期待しての仕様です。
for(int *p = arr, *pEnd = &arr[3]; p != pEnd; ++p) {
cout << *p << endl;
}
return 0;
}
条件演算子
以下のようにシンプルに分かりやすくなる場合を除き、入れ子にした形での条件演算子の使用は避けましょう。
#include <iostream>
using namespace std;
int main() {
int a = 1;
int b = 2;
cout << (
a > b ? "a > b" :
a < b ? "a < b" :
"a = b"
) << endl;
return 0;
}
基本型の情報を取得する手段
#include <iostream>
#include <limits>
#include <climits>
#include <cfloat>
using namespace std;
int main() {
// limits: 静的メンバ関数によって提供 (STL/numeric_limits)
cout << (int)numeric_limits<unsigned char>::min() << endl; //=> 0
cout << (int)numeric_limits<unsigned char>::max() << endl; //=> 255
// climits: マクロによって提供
cout << CHAR_MIN << endl; //=> -128
cout << CHAR_MAX << endl; //=> 127
cout << CHAR_BIT << endl; // 1バイトのビット数
// cfloat: マクロによって提供
cout << FLT_MAX << endl; // float
cout << DBL_MAX << endl; // double
cout << LDBL_MAX << endl; // long double
return 0;
}
記事の執筆者にステッカーを贈る
有益な情報に対するお礼として、またはコメント欄における質問への返答に対するお礼として、 記事の読者は、執筆者に有料のステッカーを贈ることができます。
さらに詳しく →Feedbacks
ログインするとコメントを投稿できます。
関連記事
- ダウンキャスト (C++をもう一度)実行時型情報 RTTI #include <iostream> #include <typeinfo> using namespace std; class MyClass { public: virtual ~MyClass() {} // typeid で正しい RTTI // (RunTime Type Information; 実行時型情報) ...
- 競技プログラミングの基本処理チートシート (C++)限られた時間の中で問題を解くために必要となる、競技プログラミングにおける基本的な処理のチートシートです。競プロにおけるメジャー言語 C++ を利用します。その際 C++11 の機能は利用せず C++03 の機能の範囲内で記述します。 頻度高く定期的に開催されるコンテスト AtCoder Codeforces main.cpp #include <iostream>
- 構造体と列挙体 (C++をもう一度)構造体 #include <iostream> using namespace std; struct MyStruct { char charval; int intval; }; void Show(MyStruct* obj) { cout << obj->intval << endl; } int main() { ...
- Valgrind による C/C++ メモリリーク検出JVM メモリリークでは JDK の jstat や jmap で原因を調査できます。C/C++ では valgrind の Memcheck ツールが利用できます。valgrind には複数のツールが含まれており既定のツールが Memcheck です。他のツールを利用する場合は --tool オプションで指定します。 [簡単な利用例](h
- クラスの基本/初期化 (C++をもう一度)構造体のように初期化する (非推奨) #include <iostream> using namespace std; const int MAX_STR = 16; class MyClass { public: int m_integer; char m_str[MAX_STR + 1]; void Show(); }; void MyClass::Show...