scanf を簡略化したい

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
m3908714035
記事: 17
登録日時: 7ヶ月前

scanf を簡略化したい

#1

投稿記事 by m3908714035 » 7ヶ月前

~標準入力 を簡略化(?)する関数の作成について~

最近になってiostreamよりもcstdioでの標準入出力が速いことを知りました。
そこで以下のような関数の作成をしようとしています。

関数inの<>内のクラスによって挙動が変わり、in<int>であれば関数の中身が

コード:

int temp; scanf("%d", &temp); return temp;
in<char>であれば関数の中身が

コード:

char temp; scanf("%c", &temp); return temp;
in<string>であれば関数の中身が

コード:

string temp; cin >> temp; return temp;
// string は scanf を使えないためおとなしく cin
となるようにしたいのです。

すると、以下のように変数の宣言と同時に入力が可能となるはずです。

コード:

// example
int a = in<int>(); string b = in<string>();
自分で調べていく中でテンプレートの特殊化を使えば作れると思うのですが、そもそもテンプレートの特殊化をあまり使ったことがなく、また、テンプレートの特殊化について自分が理解できるような文献を見つけることが出来ませんでした。
テンプレートの特殊化を使った場合この関数の実装はどうなるのか、またそれ以外に良い方法があれば教えていただきたいです。

アバター
みけCAT
記事: 6192
登録日時: 8年前
住所: 千葉県
連絡を取る:

Re: scanf を簡略化したい

#2

投稿記事 by みけCAT » 7ヶ月前

「C++ テンプレート 特殊化」でググって最初に出てきた
テンプレートの特殊化 | Programming Place Plus C++編【言語解説】 第23章
を参考に、こんな感じでしょうか。

コード:

#include <iostream>
#include <cstdio>
#include <string>

using std::cin;
using std::string;

template <typename T>
T in() {
	T temp; cin >> temp; return temp;
}

template <>
int in() {
	int temp; scanf("%d", &temp); return temp;
}

template <>
char in() {
	char temp; scanf("%c", &temp); return temp;
}

int main(void) {
	int a = in<int>(); string b = in<string>();
	printf("%d %s\n", a, b.c_str());
	return 0;
}
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

m3908714035
記事: 17
登録日時: 7ヶ月前

Re: scanf を簡略化したい

#3

投稿記事 by m3908714035 » 7ヶ月前

みけCAT さんが書きました:
7ヶ月前
「C++ テンプレート 特殊化」でググって最初に出てきた
テンプレートの特殊化 | Programming Place Plus C++編【言語解説】 第23章
を参考に、こんな感じでしょうか。 …
回答ありがとうございます。テンプレートの特殊化に関して使い方が分かった気がします。
書いていただいたコードの入力書式のパターンを増やそうとすると

コード:

using uint = unsigned int;
using lint = long;
using ushort = unsigned short;
using ulong = unsigned long;

template<typename var>
var in() { var temp; cin >> temp; return temp; }

template<>
char in() { char temp; scanf("%c", &temp); return temp; }

template<>
int in() { int temp; scanf("%d", &temp); return temp; }

template<>
uint in() { uint temp; scanf("%u", &temp); return temp; }

template<>
float in() { float temp; scanf("%f", &temp); return temp; }

template<>
short in() { short temp; scanf("%hd", &temp); return temp; }

template<>
lint in() { lint temp; scanf("%ld", &temp); return temp; }

template<>
ushort in() { ushort temp; scanf("%hu", &temp); return temp; }

template<>
ulong in() { ulong temp; scanf("%lu", &temp); return temp; }

template<>
double in() { double temp; scanf("%lf", &temp); return temp; }
と非常に長くなってしまうのですが、template<>を省略する良い方法はあるのでしょうか。
var in() の中で if(typeid(var)==typeid(int)){... のようにif文を複数使うことを検討したのですが、短くなるどころか長くなってしまいました。

かずま

Re: scanf を簡略化したい

#4

投稿記事 by かずま » 7ヶ月前

m3908714035 さんが書きました:
7ヶ月前
と非常に長くなってしまうのですが、template<>を省略する良い方法はあるのでしょうか。
マクロを使えば短くはなりますが、いかがでしょうか?

コード:

#include <iostream>
#include <string>

using uint = unsigned int;
using ushort = unsigned short;
using ulong = unsigned long;

template<typename T> T in() { T v; std::cin >> v; return v; }

#define IN(T, F) template<> T in() { T v; scanf(F, &v); return v; }

IN(char, " %c")  IN(int, "%d")  IN(uint, "%u")
IN(short, "%hd") IN(ushort, "%hu") IN(long, "%ld") IN(ulong, "%lu")
IN(float, "%f")  IN(double, "%lf")

int main()
{
	char c   = in<char>();   std::cout << "c = " << c << "\n";
	int i    = in<int>();    std::cout << "i = " << i << "\n";
	uint u   = in<uint>();   std::cout << "u = " << u << "\n";
	short h  = in<short>();  std::cout << "h = " << h << "\n";
	ushort H = in<ushort>(); std::cout << "H = " << H << "\n";
	long l   = in<long>();   std::cout << "l = " << l << "\n";
	ulong L  = in<ulong>();  std::cout << "L = " << L << "\n";
	float f  = in<float>();  std::cout << "f = " << f << "\n";
	double d = in<double>(); std::cout << "d = " << d << "\n";
	std::string s = in<std::string>(); std::cout << "s = " << s << "l\n";
}
入力例

コード:

A
-1234567890
2147483648
-32768
65535
-2147483648
4294967295
2.2360679
3.1415926535897932
string
出力例

コード:

c = A
i = -1234567890
u = 2147483648
h = -32768
H = 65535
l = -2147483648
L = 4294967295
f = 2.23607
d = 3.14159
s = stringl
" %c" は空白でない文字を入力します。

m3908714035
記事: 17
登録日時: 7ヶ月前

Re: scanf を簡略化したい

#5

投稿記事 by m3908714035 » 7ヶ月前

なるほど。マクロを使うという手があったんですね。
とても助かりました。ありがとうございました。

アバター
usao
記事: 1546
登録日時: 6年前

Re: scanf を簡略化したい

#6

投稿記事 by usao » 7ヶ月前

呼び出し時にいちいちテンプレート型引数を書かなきゃいけないのがめんどくさそう.

【特定の型に関してはscanf()を使いたいが,他の型(? 少なくともstring)に関してはiostream 使う】みたいな話であれば,
↓のような感じにすればどうか.

コード:

class IN
{
public:
	template< class T >
	IN &operator>>( T &V ){	std::cin >> V;	return *this;	}

	template<>
	IN &operator>>( int &V ){	scanf( "%d", &V );	return *this;	}
};

//
int main(void)
{
	IN in;
	int I;
	std::string S;

	in >> S >> I;
}

m3908714035
記事: 17
登録日時: 7ヶ月前

Re: scanf を簡略化したい

#7

投稿記事 by m3908714035 » 7ヶ月前

usao さんが書きました:
7ヶ月前

コード:

class IN
{
public:
	template< class T >
	IN &operator>>( T &V ){ std::cin >> V; return *this; }

	template<>
	IN &operator>>( int &V ){ scanf( "%d", &V ); return *this; }
};
このような書き方をするとcinと似た形で使えるんですね。回答ありがとうございます。
本題とは少しずれるのですが、cinはiostreamをインクルードしてstd::cin>>s;のように使えますが、
今回提示いただいたコードだと一度IN inと宣言が入ってからin>>s;となっていると思います。
何かコードに工夫をし、IN inの宣言なしにin>>s;と書くことはできるのでしょうか。

かずま

Re: scanf を簡略化したい

#8

投稿記事 by かずま » 7ヶ月前

m3908714035 さんが書きました:
7ヶ月前
本題とは少しずれるのですが、cinはiostreamをインクルードしてstd::cin>>s;のように使えますが、
今回提示いただいたコードだと一度IN inと宣言が入ってからin>>s;となっていると思います。
何かコードに工夫をし、IN inの宣言なしにin>>s;と書くことはできるのでしょうか。
IN in; の宣言をなくすのなら、in >> s; を IN() >> s; と書けば
よいのではありませんか?
宣言でオブジェクトを確保するのではなく、コンストラクタの
呼び出しによる一時オブジェクトを使えばよい、ということです。

クラス名を in にし、マクロ名を IN にすると、

コード:

#include <iostream>
#include <string>

using uint = unsigned int;
using ulong = unsigned long;
using ushort = unsigned short;

#define IN(T, F) in& operator>>(T& v) { scanf(F, &v); return *this; }

struct in {
	template<typename T> in& operator>>(T& v) { std::cin >> v; return *this; }
	IN(char, " %c")  IN(int, "%d")  IN(uint, "%u")
	IN(short, "%hd") IN(ushort, "%hu") IN(long, "%ld") IN(ulong, "%lu")
	IN(float, "%f")  IN(double, "%lf")
};

int main()
{
	char c;  int i;  uint u;  short h;  ushort H;  long l;  ulong L; 
	float f;  double d;  std::string s; 

	in() >> c >> i >> u >> h >> H >> l >> L >> f >> d >> s;

	std::cout << c << "\n" << i << "\n" << u << "\n" << h << "\n" << H << "\n"
	          << l << "\n" << L << "\n" << f << "\n" << d << "\n" << s << "\n";
}
ところで、本当に scanf は速いんでしょうか?
実行時に書式を解釈しないといけないので遅くなるような気がします。
まあ、実装に依存するんでしょうね。

fgetc や fgets は cin >> c や getline(cin, s) より速そうですが、
getline は、string s なので、サイズの制限がありませんよね。
動的にメモリを確保するから遅いのではありませんか?

また、このやり方だと EOF やエラーの検出はどうするのでしょうか?

m3908714035
記事: 17
登録日時: 7ヶ月前

Re: scanf を簡略化したい

#9

投稿記事 by m3908714035 » 7ヶ月前

かずま さんが書きました:
7ヶ月前
ところで、本当に scanf は速いんでしょうか?
実行時に書式を解釈しないといけないので遅くなるような気がします。
まあ、実装に依存するんでしょうね。

fgetc や fgets は cin >> c や getline(cin, s) より速そうですが、
getline は、string s なので、サイズの制限がありませんよね。
動的にメモリを確保するから遅いのではありませんか?
下記リンク先からの引用となります。
C/C++ におけるデータ入力の速度

1000 万行のテキストファイルを C/C++ で作成したプログラムで読み込むとき,どのくらいの時間がかかるかを調べた結果です。
キャプチャ.JPG
※ 1. nosync: std::ios_base::sync_with_stdio(false)
※ 2. _IONBF: std::setvbuf(stdin, NULL, _IONBF, 0)
※ 3. _IOLBF: std::setvbuf(stdin, io_buf, _IOLBF, 1048576)
※ 4. _IOFBF: std::setvbuf(stdin, io_buf, _IOFBF, 1048576)
※ 5. real, user, sys: time -f 'real %E, user %U, sys %S'
どうやらfgets>getline(std::ios_base::sync_with_stdio(false)有のもの)>>fgetcとなるようです。関数の中身までは把握しきれていないので、どうしてこのような結果になるのかは分かりませんが…。

また 競プロ初心者が書く「標準入出力からはじめる競プロ入門」 では、10^6個のランダムな符号付き32ビット整数のデータの入力がiostream - 4.96sに対し、cstdio - 0.42sという結果もありました。

scanfは基礎を学ぶ際によく用いていたということもあり、今回はscanfでの入力を考えました。もし、私が勘違いをしていて「やっぱりscanf遅いで!」みたいなことがあれば教えていただけると幸いです。
かずま さんが書きました:
7ヶ月前
また、このやり方だと EOF やエラーの検出はどうするのでしょうか?
この件に関しては、今回のコードは私の中で競技プログラミングにおけるコードの高速化を想定していたので、考えていませんでした。以前、cinでのエラーやEOFについては考えたことがあり、

コード:

if (cin.fail() && !cin.bad() && !cin.eof()) {
	cerr << error << endl; // error は string
	cin.clear();
	cin.ignore(INT_MAX, '\n');
}
上のようなコード(一部抜粋)を書いた記憶があります。しかし、scanfにおけるエラーやEOFに関しては全くの無知ですので、質問を返すようで申し訳ないのですが、逆に教えていただきたく思います。

アバター
usao
記事: 1546
登録日時: 6年前

Re: scanf を簡略化したい

#10

投稿記事 by usao » 7ヶ月前

m3908714035 さんが書きました:
7ヶ月前
本題とは少しずれるのですが、cinはiostreamをインクルードしてstd::cin>>s;のように使えますが、
今回提示いただいたコードだと一度IN inと宣言が入ってからin>>s;となっていると思います。
何かコードに工夫をし、IN inの宣言なしにin>>s;と書くことはできるのでしょうか。
単純にオブジェクトを1個作ってその名前を公開しておけば良いのではないかと.
(cinだって,単に cinという名前のグローバルなオブジェクトが存在しているだけでしょうから)

m3908714035
記事: 17
登録日時: 7ヶ月前

Re: scanf を簡略化したい

#11

投稿記事 by m3908714035 » 7ヶ月前

usao さんが書きました:
7ヶ月前
単純にオブジェクトを1個作ってその名前を公開しておけば良いのではないかと.
(cinだって,単に cinという名前のグローバルなオブジェクトが存在しているだけでしょうから)
cinの定義をiostrean->iosfwd->istreamと辿ってみると、おっしゃる通りでした。教えて下さりありがとうございます。

かずま

Re: scanf を簡略化したい

#12

投稿記事 by かずま » 7ヶ月前

scanf は遅いはずだと思って測ってみました。

g++ (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609

コード:

#include <iostream>
#include <fstream>
#include <cstdio>
#include <ctime>
using namespace std;

const int N = 10000000;

char buf[N];

void read_char()
{
    clock_t t0, t1;
    FILE *fp;
    fp = fopen("/usr/bin/nodejs", "r");
    if (!fp) return;
    t0 = clock();
    for (int i = 0; i < N; i++)
        buf[i] = fgetc(fp);
    t1 = clock();
    fclose(fp);
    printf("%f sec: fgetc\n", (t1 - t0) / double(CLOCKS_PER_SEC));

    ifstream ifs("/usr/bin/nodejs");
    if (!ifs) return;
    t0 = clock();
    for (int i = 0; i < N; i++)
        ifs >> buf[i];
    t1 = clock();
    ifs.close();
    printf("%f sec: ifs >>\n", (t1 - t0) / double(CLOCKS_PER_SEC));

    fp = fopen("/usr/bin/nodejs", "r");
    if (!fp) return;
    t0 = clock();
    for (int i = 0; i < N; i++)
        fscanf(fp, "%c", &buf[i]);
    t1 = clock();
    fclose(fp);
    printf("%f sec: fscnaf\n", (t1 - t0) / double(CLOCKS_PER_SEC));
}

int main()
{
    ios_base::sync_with_stdio(false);
    for (int i = 0; i < 3; i++) {
        read_char();
        puts("---");
    }
}
実行結果

コード:

0.172367 sec: fgetc
0.293313 sec: ifs >>
0.599683 sec: fscnaf
---
0.165610 sec: fgetc
0.294515 sec: ifs >>
0.598882 sec: fscnaf
---
0.167828 sec: fgetc
0.294134 sec: ifs >>
0.601244 sec: fscnaf
---
使用したファイルは 10000000バイト以上あります。

コード:

$ wc /usr/bin/nodejs
   76561   297132 11187096 /usr/bin/nodejs

かずま

Re: scanf を簡略化したい

#13

投稿記事 by かずま » 7ヶ月前

最初 getchar, cin >>, scanf でやっていたのを
fgetc, ifs >>, fscanf に変更したので、
sync_with_stdio(false) は無意味になりました。

また、cin >> noskipws; または ifs >> noskipws; を
実行しておかないと、スペースや改行などの空白が読み飛ばされますから、
getchar, scanf("%c" よりもファイルを多く読むことになります。

忘れていました。

返信

“C言語何でも質問掲示板” へ戻る