ページ 1 / 1
スキルアップのコツ
Posted: 2007年5月18日(金) 22:07
by 管理人
他トピでスキルアップのコツとして、標準関数に頼らないと言う話になったので、少しサンプルを書いてみました。
標準関数に頼ってプログラムをスマートに書くのもよいですが、
入門時は内部でどのように処理が行われているのか把握するため、また、その関数にとらわれることなく、応用力をつけるため、関数をそのたびごとに自作で作ってみてはいかがでしょうか。
関数を作る練習として一番有名なのがa2iだと思いますが、微妙に難しいのでもう少し簡単なところから。
①strlenについて
この関数はご存知の通り、文字列の長さを求める関数です。
実装内容は簡単で、単に終端記号が入っている配列要素までの個数を調べてやればいいだけです。
サンプルとしてはこちらです。strlenをstringlenと名前を変えてみました。
int stringlen(char st[/url]){
int i=0;
while(st!='\0')
i++;
return i;
}
strlenはsize_t型関数ですが、ほとんどsize_tはintでしょうし、スマートに説明するため省きます。
こちらの関数の使用サンプルはコチラです。
#include <stdio.h>
int stringlen(char st[/url]){
int i=0;
while(st!='\0')
i++;
return i;
}
void main(void){
char st[/url]="0123456789";
printf("%d\n",stringlen(st));
}
このように、\0までの個数が返っていますね。
Re:スキルアップのコツ
Posted: 2007年5月18日(金) 22:19
by 管理人
②strcopyについて
この関数は文字列のコピーです。
strcopy(②,①);
と書けば①を②にコピーします。気をつけないといけないのは、①の最後にちゃんと終端記号が入っていることと、②は①以上の配列要素を持っていないといけない事です。
ではサンプルです。
void strcopy(char st2[/url],char st1[/url]){
int i=0;
for(i=0;i<=stringlen(st1);i++)
st2=st1;
return ;
}
対象の文字列の長さ分ループしてst2に格納します。
しかし実際strcpyはキャラクタのポインタ型関数です。
実際にはこんな感じになっているでしょう。
char *strcopy(char *s1,char *s2){
char *p = s1;
while(*s1++ = *s2++);
return p;
}
ポインタを使って意味不明で嫌になってしまっては本末転倒ですので、ここでは両方書いておきました。
使用例サンプルです。
#include <stdio.h>
int stringlen(char st[/url]){
int i=0;
while(st!='\0')
i++;
return i;
}
void strcopy(char st2[/url],char st1[/url]){
int i=0;
for(i=0;i<=stringlen(st1);i++)
st2=st1;
return ;
}
void main(void){
char st1[/url]="0123456789",st2[128];
strcopy(st2,st1);
printf("%s\n",st2);
}
Re:スキルアップのコツ
Posted: 2007年5月18日(金) 22:22
by 管理人
こんな感じで掴んできたらa2iを作ってみましょう。
atoi関数は受け取った文字列を数値に変換して返す関数です。
実際、キャラクタ型に入っている数字は文字コードです。
文字コードは
printf("%d",'0');
を表示してみる事で、0の文字コードが確認できます。
printf("%d ",'0');
printf("%d ",'1');
printf("%d ",'2');
・・・・
printf("%d ",'9');
を実行してみると規則性が見えてくるはずです。
つまり
int a;
st[0] = '1';
a= st[0] - '0';
をするとaには何が入るかと言うと・・。と考えていけば関数が出来上がるはず。
様々な自作関数を作ってみてプログラムに慣れましょう~♪
Re:スキルアップのコツ
Posted: 2007年5月19日(土) 02:46
by Justy
>スキルアップのコツとして、標準関数に頼らない
アルゴリズムや標準関数の仕様を学ぶという点でとてもいいと思います。
今回の例にあったようなものは比較的シンプルなのですが
ものによっては意外と(プロでも)正しく書けるひとは少なかったりします。
実際に strcmp()を何人かで書いたことがあるのですが、一発で正答した人は
いませんでした。
もちろん、かくいう私も1回目は不正解・・・orz
Re:スキルアップのコツ
Posted: 2007年5月19日(土) 03:50
by 管理人
おぉ、おもしろそうな課題ですね。
>もちろん、かくいう私も1回目は不正解・・・orz
えぇ!?じゃ私に出来るわけが・・
と言ってもそんな難ししとは思えないんですが、何か違うんでしょうか・・。
まず、
・同じなら0を返す
・第一引数の方が第二引数より大きければ正の値、つまり("c","a")なら2を返す
・第一引数の方が第二引数より小さければ負の値、つまり("a","d")なら-3を返す
と、こんな仕様ですよね??
それなら私ならこう答えますが。。
int strcmp2(const char *st1,const char *st2){
while(1){
if(*st1=='\0' && *st2=='\0')
return 0;
if(*st1!=*st2)
return (*st1-*st2);
st1++,st2++;
}
}
何かおかしいでしょうか・・。
一応実行サンプルも
#include <stdio.h>
int strcmp2(const char *st1,const char *st2){
while(1){
if(*st1=='\0' && *st2=='\0')
return 0;
if(*st1!=*st2)
return (*st1-*st2);
st1++,st2++;
}
}
int main(void){
int i=-1;
char st[6][10]={
{"abcde"},{"abcdz"},
{"abcde"},{"abcda"},
{"abcde"},{"abcde"}
};
for(i=0;i<3;i++)
printf("st[%d],st[%d] -> %d\n",i*2,i*2+1,strcmp2(st[i*2],st[i*2+1]));
return 0;
}
ん~・・・きっとどこかにひっかけがあるんでしょうね~・・
どこだろ、どこだろ・・。
Re:スキルアップのコツ
Posted: 2007年5月19日(土) 03:57
by 管理人
strncmpならこうなるのでは・・
#include <stdio.h>
int strncmp2(const char *st1,const char *st2,size_t n){
while(n){
if(*st1=='\0' && *st2=='\0')
return 0;
if(*st1!=*st2)
return (*st1-*st2);
st1++,st2++;
n--;
}
return 0;
}
int main(void){
int i=-1;
char st[6][10]={
{"abcde"},{"abzzz"},
{"abeee"},{"abcda"},
{"abcde"},{"abcde"}
};
for(i=0;i<3;i++)
// printf("st[%d],st[%d] -> %d\n",i*2,i*2+1,strncmp2(st[i*2],st[i*2+1],2));
printf("st[%d],st[%d] -> %d\n",i*2,i*2+1,strncmp2(st[i*2],st[i*2+1],3));
return 0;
}
こんな簡単な関数でJustyさんが間違うわけないですよね。。
ん~。。どこか違うんでしょうか・・。
Re:スキルアップのコツ
Posted: 2007年5月19日(土) 04:13
by 管理人
え~と、Justyさんの回答が待ちきれずに調べてしまいましたw
http://libc.blog47.fc2.com/blog-entry-33.html
引用
>このように単純な仕様の関数ですが、一点だけ注意すべきなのは、
>文字の比較は、unsigned char型として行わなければならない点です。
ひっかけ部分ってここでした?
signedでも計算結果変わらないような気がするんですが・・。
Re:スキルアップのコツ
Posted: 2007年5月19日(土) 04:40
by フリオ
これでいいのかな。
#include <stdio.h>
#include <string.h>
int CompStr(char *str1, char *str2)
{
while(*str1 == *str2){
if(!*str1) return 0;
str1 ++;
str2 ++;
}
return (unsigned)*str1 - (unsigned)*str2;
}
int main(void)
{
printf("%d %d\n", CompStr("abc", "abd"), strcmp("abc", "abd"));
printf("%d %d\n", CompStr("abc", "abc"), strcmp("abc", "abc"));
printf("%d %d\n", CompStr("abd", "abc"), strcmp("abd", "abc"));
return 0;
}
Re:スキルアップのコツ
Posted: 2007年5月19日(土) 06:08
by フリオ
少し変えました。
#include <stdio.h>
#include <string.h>
int CompStr(char *str1, char *str2)
{
while(*str1 == *str2 && *str1){
str1 ++;
str2 ++;
}
return (unsigned)*str1 - (unsigned)*str2;
}
int main(void)
{
printf("%d %d\n", CompStr("abc", "abd"), strcmp("abc", "abd"));
printf("%d %d\n", CompStr("abc", "abc"), strcmp("abc", "abc"));
printf("%d %d\n", CompStr("abd", "abc"), strcmp("abd", "abc"));
return 0;
}
Re:スキルアップのコツ
Posted: 2007年5月19日(土) 07:22
by フリオ
訂正。
(unsigned) -> (unsigned char)
#include <stdio.h>
int main(void)
{
signed char c0 = 0x0, c1 = 0xff;
printf("%d\n", c0 - c1);
printf("%d\n", (unsigned)c0 - (unsigned)c1);
printf("%d\n", (unsigned char)c0 - (unsigned char)c1);
return 0;
}
Re:スキルアップのコツ
Posted: 2007年5月19日(土) 11:21
by Justy
>ひっかけ部分ってここでした?
そこですそこです(w
そこが穴でした。
こういう盲点が標準関数にはいろいろありそうです。
とはいえスキルアップ目的ならこんなところまで気にしなくてもいいんですけどね(w
>signedでも計算結果変わらないような気がするんですが・
フリオさんが1つ上のサンプルで書かれているように符号付きで見たときに
0と負の組み合わせや正と負の組み合わせの場合とかで結果が変わってしまいます。
@ フリオさん
引数でちょっと警告が出そうな気もしますが、中身は合っていると思います。
Re:スキルアップのコツ
Posted: 2007年5月19日(土) 11:33
by 管理人
あら・・ホントだ、、、、くぅ悔しい(T_T
人の回答の為のトピで自分が勉強になりました^^;
また面白い話題があれば教えてください~♪
Re:スキルアップのコツ
Posted: 2007年5月19日(土) 16:40
by 大工
strcopyですが、
void strcopy(char st2[/url], const char st1[/url]){
int i=0;
for(i = 0; i <= stringlen(st1); i++)
st2 = st1;
return ;
}
とした方がいかも知れませんね。
st1[/url]が関数内で替わってしまうとちょっと意味が変わってしまいますから。
Re:スキルアップのコツ
Posted: 2007年5月20日(日) 00:02
by Hermit
>strlenはsize_t型関数ですが、ほとんどsize_tはintでしょうし、スマートに説明するため省きます。
ちょっと気になったので、
ほとんどは、unsigned だと思います。
strcmp()
辞書順てのがネックですね。
これが、unsigned でしなければいけないとは気づかなかった。
私も間違えたくちです。(char で比較してしまった為、SJIS漢字が(^^;)
Re:スキルアップのコツ
Posted: 2007年5月20日(日) 08:28
by ふぐお
printfが作りたいですね
誰か作り方知ってますか?
インライン?とか使うんですかね?
Re:スキルアップのコツ
Posted: 2007年5月20日(日) 14:38
by Justy
>printfが作りたいですね
>誰か作り方知ってますか?
どこまで横着して良いのか、にも依って難易度が大きく変わりますね、これは。
最大限横着して良い(環境依存含む)のなら非常に簡単です。
フォーマット文字列を vasprintfが使えるならそれで展開、
使えないなら _vscprintf() / _vsnprintf()とかで展開後のサイズを調べて
展開バッファを作成した後、vsprintf()で展開。
あとは puts()とかで標準出力へ、という流れです。
しかし、フォーマット文字列の解析・展開を自前でやろうとすると
あの複雑でそれなりに柔軟なフォーマット文字列を1文字1文字調べて
対応しないといけないのでそれはまたなかなか大変です。