'2010년 이전 글'에 해당되는 글 133건
- 2008.11.04
- 2008.06.18
- 2008.03.08
- 2008.03.04
- 2008.01.22
- 2007.11.13
- 2007.10.30
- 2007.10.30
- 2007.10.25
- 2007.10.23
TCPDUMP 요약글입니다. (0) | 2007.10.30 |
---|---|
TCPDUMP 사용법 (0) | 2007.10.30 |
블루스크린 ErrorCode Define 값입니다. (0) | 2007.09.21 |
HP-UX 쉘 명령어 총정리 (0) | 2007.08.22 |
gcc와 make 강좌 (0) | 2007.05.01 |
웹폰트 eot 파일 만들기.. (0) | 2007.08.30 |
---|---|
자전거 교통사고 급증 (0) | 2007.07.31 |
영화 "향수"를 통해 본 "보안"의 허와 실 (0) | 2007.05.15 |
상대성이론과 평행우주론 - 데자뷰(Deja vu,2006) (1) | 2007.04.24 |
나는 맵기위해 존재한다~ 119를 불러주세요~ 불닭.. (0) | 2006.11.24 |
<1>웨이크보드는 어떤 운동?
<2>일단 도전하자~!!!
<3>가서 웨이크만 타나~?
<4>패션
<5>장비
<6> 이 바닥...
제가 웨이크 보드를 처음 접한건 얼추 3년전인거 같은데
겨울에 스노우보드 타다 여름에 즐길 수 있는 뭐 보드비슷한 거시기를 찾다가
알게 되었습죠...
일단 물에서 하는 웬지 럭셔리(?) 레포츠같아
감히 나같은 대한민국 50%에 해당하는 서민들이
접하긴 정말 어려운 운동이라는 인식이 있었고
이 글을 보시는 여러분들도 분명 저와 같은 생각을 지금 가지고 계시리라 생각되네요~
일단 저는 이 웨이크보드란 녀석을 입벌어지게 타는 수준도 아니고
그냥 주말에 머리식힐 겸 몸 사려가며 타는 평범한 사람입니다.
웨이크보드의 기술적인 부분이나 전문적인 용어를 설명드리고자 함이 아니라
혹시나 도전은 하고 싶은데 어떻게 시작을 해야할 지 모르시는 분들을 위해
조금이나마 도움이 될까 싶어 제가 아는 선에서 알려드리고자 함입니다.
레포츠에 관심있는 분이라도 웨이크보드라는 레포츠가 생소하게 들릴 수도 있습니다.
예전보단 즐기시는 분들이 많이 계시긴 하지만 전체로 본다면 아주 적은 인구죠.
설명하기가 날로 귀찮아져서 그냥 아는 지인들한테는 수상스키탄다고 그럽니다만 웨이크보드나 수상스키 모두 물에서 즐긴다 해도 웨이크보드가 추구하는 것은 수상스키와 아주 많이 다릅니다.
위의 사진은 수상스키 예를 든겁니다. 두개의 스키를 이용한다 해서 투스키라고 보통 부르고
한 개의 스키에 두발을 모두 올려놓고 타는것을 원스키라 그럽니다.
아래 사진은 웨이크보드를 타는 외국 프로의 사진입니다.
입 쩍 벌어지시죠?
어떻게 물위에서 가다가 저런 높이까지 올라갈 수 있는지...
실로 놀라운 세계가 아닐 수 없습니다.
위의 사진에서 보듯이 웨이크보드는 공중기술을 위한 운동이라 해도 과언이 아닐 정도입니다.
수상스키가 물위에서의 고속의 라이딩을 추구한다면 웨이크보드는 궁극적으로 공중트릭을 추구하는 것이죠.
아...
제가 한가지 빼먹은게 있네요.
왜 웨이크보드란 이름이 붙었을까?
영어로 써보면 'wakeboard" 입니다.
눈에서 타면 'snowboard' 그런데 'wake'는 뭐냐?
가까운 네이버사전 찾아보시면
1배가 지나간 자리, 항적(航跡) 2(물건의) 지나간 자국, 흔적 |
랍니다.
배가 일정한 속도로 나아가게 되면 배가 지나간 자리로 △ 모양의 물모양이 만들어집니다.
이것을 웨이크 'wake' 라고 하는데요.
(사용된 예나 그림,동영상 그외 저의 글쓰는 방법까지... 세련된 문체나 감각적이 유머는 기대하지 마세요~넘 머라고도 하지 말아주시구요. 전공책외엔 읽은 책이 손가락으로 셀 정도랍니다...--;)
가면 이런거 실컷 탈 수있으려나... 근데 가기전에 타보고싶다
Priceline bidding에 도전하기 (0) | 2007.04.23 |
---|---|
호주 유학/장기여행 시 짐싸기(준비해야될 것들) (0) | 2006.09.08 |
[펌] 관광비자 연장 비용 (0) | 2005.11.23 |
[펌] 홈스테이에 대한 견해 (0) | 2005.11.23 |
[펌] 캐나다 교통 & 전화 (0) | 2005.11.23 |
/*************************************************************
최종수정일:
일반함수
btMov_AddList
checkNumber
일반함수
BT_getComboData 셀렉트 생성
BT_CheckLastAcctno 계좌번호 끝에 3자리 000 제거
BT_makeAcntnoDash 계좌번호에 하이픈을 넣음
BT_MakeAcntList 계좌번호를 리스트로 만듬
BT_MakePayAcntList 계좌번호를 리스트로 만듬
BT_ReduceDash 하이픈을 삭제
BT_makeCardnoDash 카드번호 하이픈 넣기
BT_getToday 오늘 날짜열을 YYYYMMDD의 형식으로 만듬
BT_make2Length 월일을 두자리로 만듬
BT_makeCardnoDash 카드번호 하이픈 넣음
BT_makeMembernoDash 회원번호(16자리) 하이픈 넣기
BT_TrimSpace 공백없애기
BT_calc 금액 더하기 계산
BT_Del_comma 콤마 삭제
BT_Add_comma 입력폼 작성시 콤마 추가
BTCom_Replace 문자변환
BT_Add_Displaycomma 금액 Display 시 콤마 추가
BT_Add_NumberComma 금액의 앞자리 0을 없애고 콤마추가
BT_Add_NumberComma1
BT_toMoneyF 환율,외화에 소수점,콤마 추가
BT_Reduce_comma 콤마 제거
BT_isAlphabet 값이 알파벳만 있는지 체크
BT_isNumber 값이 숫자만 있는지 체크
BT_number 숫자만 입력
BT_isTrsfPass 이체 비밀번호 체크 루틴
BT_getRandomNumber 씨크릿 카드 번호 생성
BT_makeCustnoDash 9자리 고객번호 하이픈을 넣음
BT_makeResnoDash 주민번호 하이픈 넣기
BT_makeLoannoDash 대출계좌에 하이픈 넣기
BT_makeDateDash yyyy-mm-dd
BT_makeDateDash_1 yyyy-mm
BT_makeDateDash_2 mm-dd
BT_makeDateDash_3 mm
BT_makeTime 00:00:00
BT_StrToInt 문자열을 10진수로 변환
BT_makeDecimalPoint 소수점이 포함된 숫자 앞에 포함된 0 을 제거
last_page 페이지 설정
BT_EngNum 숫자와영어만 허용
BT_SetServiceCode ServiceCode박아주기
BT_Setreturn_url return_url 박아주기
BT_getRandomNumber() { //씨크릿 카드 순차번호 생성
pwCheck() 비밀번호 연번 확인 => 주석확인
***************************************************************/
var alphaArray = new Array();
alphaArray[0] = 'a' ;alphaArray[1] = 'b';alphaArray[2] = 'c';alphaArray[3] = 'd';
alphaArray[4] = 'e' ;alphaArray[5] = 'f';alphaArray[6] = 'g';alphaArray[7] = 'h';
alphaArray[8] = 'i' ;alphaArray[9] = 'j';alphaArray[10] = 'k';alphaArray[11] = 'l';
alphaArray[12] = 'm';alphaArray[13] = 'n';alphaArray[14] = 'o';alphaArray[15] = 'p';
alphaArray[16] = 'q';alphaArray[17] = 'r';alphaArray[18] = 's';alphaArray[19] = 't';
alphaArray[20] = 'u';alphaArray[21] = 'v';alphaArray[22] = 'w';alphaArray[23] = 'x';
alphaArray[24] = 'y';alphaArray[25] = 'z';
alphaArray[26] = 'A';alphaArray[27] = 'B';alphaArray[28] = 'C';alphaArray[29] = 'D';
alphaArray[30] = 'E';alphaArray[31] = 'F';alphaArray[32] = 'G';alphaArray[33] = 'H';
alphaArray[34] = 'I';alphaArray[35] = 'J';alphaArray[36] = 'K';alphaArray[37] = 'L';
alphaArray[38] = 'M';alphaArray[39] = 'N';alphaArray[40] = 'O';alphaArray[41] = 'P';
alphaArray[42] = 'Q';alphaArray[43] = 'R';alphaArray[44] = 'S';alphaArray[45] = 'T';
alphaArray[46] = 'U';alphaArray[47] = 'V';alphaArray[48] = 'W';alphaArray[49] = 'X';
alphaArray[50] = 'Y';alphaArray[51] = 'Z';
function getCode(argObj1,argObj2){
Key1 = argObj1;
Key2 = argObj2;
if(top.iWallet.LoadCode_state == false){
top.iWallet.LoadCode = top.iWallet.LoadCode + 1;
if(top.iWallet.LoadCode <= 5){
setTimeout("getCode(Key1,Key2)", 1000)
}else{
alert("코드 초기화에 실패하였습니다.\n처음으로 돌아갑니다.")
SID = get_sessionid();
CryptoClient.Logout(SID);
top.location.href="/btindex.asp";
}
}else{
retString = ""
try {
sKey = argObj1 + "^" + argObj2;
retString = top.iWallet.sTradcode[sKey];
if (!retString) retString = "";
return retString;
}catch(e) {
return "";
}
}
}
//OPEN창에서 사용
function getCode2(argObj1,argObj2){
Key1 = argObj1;
Key2 = argObj2;
if(opener.top.iWallet.LoadCode_state == false){
opener.top.iWallet.LoadCode = opener.top.iWallet.LoadCode + 1;
if(opener.top.iWallet.LoadCode <= 5){
setTimeout("getCode2(Key1,Key2)", 1000)
}else{
alert("코드 초기화에 실패하였습니다.\n처음으로 돌아갑니다.")
SID = get_sessionid();
CryptoClient.Logout(SID);
top.location.href="/btindex.asp";
}
}else{
retString = ""
try {
sKey = argObj1 + "^" + argObj2;
retString = opener.top.iWallet.sTradcode[sKey];
if (!retString) retString = "";
return retString;
}catch(e) {
return "";
}
}
}
function BT_LoadCode(){
var dgbcode=ttradcode();
tmp1 = dgbcode.split("^^^");
for(k=0;k<tmp1.length;k++){
tmp2 = tmp1[k].split("^^");
top.iWallet.sTradcode[tmp2[0]]=tmp2[1];
}
}
//코드구분, 셀렉트이름, selected값
function BT_getComboData(argObj1,selname,argObj2){
Key1 = argObj1;
Key2 = selname;
Key3 = argObj2;
if(top.iWallet.LoadCode_state == false){
top.iWallet.LoadCode = top.iWallet.LoadCode + 1;
if(top.iWallet.LoadCode <= 5){
setTimeout("BT_getComboData(Key1,Key2,Key3)", 1000)
}else{
alert("코드 초기화에 실패하였습니다.\n처음으로 돌아갑니다.")
SID = get_sessionid();
CryptoClient.Logout(SID);
top.location.href="/btindex.asp";
}
}else{
try {
var data=top.iWallet.ttradcode();
var split_data=data.split("^^^");
var len = split_data.length;
var L_ListText = "<select name=\"" + selname + "\" id=\"" + selname + "\">";
for(i=0;i<len;i++) {
var splitdata_in=split_data[i].split("^^");
var splitdata_in2=splitdata_in[0].split("^");
if(splitdata_in2[0] == argObj1)
{
if (splitdata_in2[1] == argObj2) {
L_ListText += "\t<option value=\"" + splitdata_in2[1] + "\" selected>" + splitdata_in[1] + "</option>\n";
} else {
L_ListText += "\t<option value=\"" + splitdata_in2[1] + "\">" + splitdata_in[1] + "</option>\n";
}
}
}
L_ListText = L_ListText + "</select>"
return L_ListText;
}catch(e) {
return false;
}
}
}
function btAlertNoValue ( thisvalue , alertvalue )
{
if ( thisvalue.value == "" )
{
var smsg;
smsg = alertvalue + "을(를) 입력하셔야 합니다.";
alert(smsg);
thisvalue.focus();
return 1;
}
return 0;
}
function btMov_AddList(argObj, argData, argValue,selval){
argObj.length = argObj.length + 1
argObj.options(argObj.length-1).text = argData
argObj.options(argObj.length-1).value = argValue
if(selval != "" && selval == argValue) argObj.options(argObj.length-1).selected = true;
}
//숫자외의 문자 check
function checkNumber(data) {
t = data.value ;
for(i=0;i<t.length;i++)
if (t.charAt(i)<'0' || t.charAt(i)>'9') {
if (t.charAt(i) != '*') {
alert("숫자만 입력해주세요.") ;
data.value="";
data.focus() ;
return false ;
}
}
}
function btTextCheckDate(Dnum,StaYer,StaMon,StaDay,EndYer,EndMon,EndDay)
{
now = new Date();
var cur_year= now.getYear();
var cur_month = now.getMonth();
var month = now.getMonth()+1;
month=new String(month);
var cur_day = now.getDate();
cur_day=new String(cur_day);
var d_cur_date = new Date(cur_year,cur_month,cur_day);
var dategubun;
var num = Dnum;
eval("doc."+StaYer+".readOnly = true");
eval("doc."+StaMon+".readOnly = true");
eval("doc."+StaDay+".readOnly = true");
eval("doc."+EndYer+".readOnly = true");
eval("doc."+EndMon+".readOnly = true");
eval("doc."+EndDay+".readOnly = true");
if(num==0){ //당일
var before_date = new Date(Date.parse(d_cur_date) - 1*1000*60*60*24);
dategubun = "1주일전";
}else if(num==1){ //1주일전
var before_date = new Date(Date.parse(d_cur_date) - 7*1000*60*60*24);
dategubun = "1주일전";
}else if(num==2){ //15일전
var before_date = new Date(Date.parse(d_cur_date) - 15*1000*60*60*24);
dategubun = "15일전";
} else if(num==3){ //30일
var before_date = new Date(Date.parse(d_cur_date) - 30*1000*60*60*24);
dategubun = "30일";
} else if(num==4){ //기간설정(3개월)
var before_date = new Date(Date.parse(d_cur_date) - 91*1000*60*60*24);
eval("doc."+StaYer+".readOnly = false");
eval("doc."+StaMon+".readOnly = false");
eval("doc."+StaDay+".readOnly = false");
eval("doc."+EndYer+".readOnly = false");
eval("doc."+EndMon+".readOnly = false");
eval("doc."+EndDay+".readOnly = false");
dategubun = "기간설정";
}
var yest_date = new Date(Date.parse(d_cur_date)-1000*60*60*24);
var yest_year = new String(yest_date.getYear());
var yest_month = new String(yest_date.getMonth()+1);
if(yest_month.length==1){
yest_month = '0'+yest_month;
}
if(month.length==1){
month = '0'+ month;
}
if(cur_day.length==1){
cur_day = '0'+cur_day;
}
var before_year = new String(before_date.getYear());
var before_month = new String(before_date.getMonth() +1);
if(before_month.length==1){
before_month = '0'+before_month;
}
var before_day = new String(before_date.getDate());
if(before_day.length==1){
before_day = '0'+before_day;
}
if(num==0){
eval("doc."+StaYer+".value=cur_year");
eval("doc."+StaMon+".value=month");
eval("doc."+StaDay+".value=cur_day");
eval("doc."+EndYer+".value=cur_year");
eval("doc."+EndMon+".value=month");
eval("doc."+EndDay+".value=cur_day");
}
else if(num==4){
eval("doc."+StaYer+".value=''");
eval("doc."+StaMon+".value=''");
eval("doc."+StaDay+".value=''");
eval("doc."+EndYer+".value=''");
eval("doc."+EndMon+".value=''");
eval("doc."+EndDay+".value=''");
}
else
{
eval("doc."+StaYer+".value=before_year");
eval("doc."+StaMon+".value=before_month");
eval("doc."+StaDay+".value=before_day");
eval("doc."+EndYer+".value=cur_year");
eval("doc."+EndMon+".value=month");
eval("doc."+EndDay+".value=cur_day");
}
}
//*-- 필드 AutoSkip --*//
function btAutoSkip(){
var el = event.srcElement
if ((el.value == null) || (event.keyCode==13)) return(false)
// 방향키 무시
var sKeys = "8;16;46;;37;38;39;40;33;34;35;36;45;229;"
if (sKeys.indexOf(event.keyCode+";") > -1) return;
if (el.tagName == "INPUT" )
if (el.value.length >= el.maxLength ) {
var i=0
while (el != el.form.elements[i] ) i++
if ( (i+1) != el.form.elements.length ) {
while ( ( el.form.elements[i+1].type == "hidden" ) ||
( el.form.elements[i+1].type == "button" ) ||
( el.form.elements[i+1].type == "checkbox" ) ||
( el.form.elements[i+1].type == "radio" ) ||
( el.form.elements[i+1].tagName == "FIELDSET" ) ||
( el.form.elements[i+1].style.display == "none" ) ||
( el.form.elements[i+1].disabled == true ) ||
( el.form.elements[i+1].style.visibility == "hidden" )) {
i++
if ( (i+1) == el.form.elements.length ) return;
}
}
else{
el.form.elements[i].focus();
return(false);
}
el.form.elements[i+1].focus()
if(el.form.elements[i+1].tagName != "SELECT") el.form.elements[i+1].select()
else if(el.form.elements[i+1].tagName == "SELECT") {
if(el.form.elements[i+1].value==null || el.form.elements[i+1].selectedIndex==-1)
el.form.elements[i+1].value = "";
}
}
}
//계좌번호에 하이픈 넣기 - pansory
function BT_makeAcntnoDash (val) {
var DashedAcntno = val
if (val.length == 11) { //계좌번호가 11자리일 경우
DashedAcntno = val.substring(0,3) + "-" + val.substring(3,5) + "-" + val.substring(5)
} else if (val.length == 12){
DashedAcntno = val.substring(0,3) + "-" + val.substring(3,5) + "-" + val.substring(5,11) + "-" + val.substring(11)
}else if(val.length == 13) { //계좌번호가 13자리일 경우
DashedAcntno = val.substring(0,3) + "-" + val.substring(3,5) + "-" + val.substring(5,11) + "-" + val.substring(11)
} else if (val.length == 14) { //계좌번호가 14자리일 경우
DashedAcntno = val.substring(0,3) + "-" + val.substring(3,5) + "-" + val.substring(5,11) + "-" + val.substring(11)
} else if (val.length == 16){
DashedAcntno = val.substring(0,3) + "-" + val.substring(3,5) + "-" + val.substring(5,11) + "-" + val.substring(11)
}
return DashedAcntno;
}
//계좌번호 끝3자리에서 000 제거
function BT_CheckLastAcctno(val){
var TempAccntno = val;
var len = val.length;
var tempAcc = len - 3;
var lastAccNo = val.substring(tempAcc, len);
if (lastAccNo == "000"){
TempAccntno = BT_makeAcntnoDash(val.substring(0, tempAcc));
} else {
TempAccntno = BT_makeAcntnoDash(TempAccntno);
}
return TempAccntno;
}
//계좌번호 선택..
function BT_SelectAcntno(USR_AccsList, NeedAcntno) {
var no = NeedAcntno.length;
var Acntno_No = USR_AccsList.length;
var SelectedAcntno = new Array();
var k = 0;
if (no == 0) {
SelectedAcntno = USR_AccsList;
//나중에 확인 필요함.. 고른 것들 중에 첫번째 것을 세션에 넣기..????
save_session("ACNTNO", USR_AccsList[0]);
save_flush();
} else {
for (var i=0; i < Acntno_No ; i++) {
for(var j=0 ; j < no; j++) {
if(USR_AccsList[i].length > 12) {
stCode = USR_AccsList[i].substring( 9, 11);
} else {
stCode = "0"; //Case Error
}
if (stCode == NeedAcntno[j]) {
SelectedAcntno[k] = USR_AccsList[i];
if (k == "0") {
//나중에 확인 필요함.. 고른 것들 중에 첫번째 것을 세션에 넣기..????
save_session("ACNTNO", USR_AccsList[i]);
save_flush();
}
k++;
}
}
}
}
return SelectedAcntno;
}
//계좌번호 Array를 셀렉트 박스로 변환 - pansory
//조건에 따라(0: 조회, 1: 이체가능, 2: 저축성, 4: 대출, 5: 신탁, 6: 기타)
function BT_MakeAcntList(val, selectedval,selname,condition) {
var Acct = BT_KindAcct(val,condition);
var tempAcct = Acct.split(",");
var L_ListText = "<select name=\"" + selname + "\" id=\"" + selname + "\">";
for(var i=0; i< tempAcct.length - 1; i++) {
if (tempAcct[i] == selectedval) {
L_ListText += "\t<option value=\"" + tempAcct[i] + "\" selected>" + BT_makeAcntnoDash(tempAcct[i]) + "</option>\n";
} else {
L_ListText += "\t<option value=\"" + tempAcct[i] + "\">" + BT_makeAcntnoDash(tempAcct[i]) + "</option>\n";
}
}
L_ListText += "</select>\n";
return L_ListText;
}
//계좌종류별로계좌번호가져오기
/*[1] 11 -> /s100/bc101_r2.jsp (1080) (계좌상세조회 결과와 같은 화면 사용)
[2] 19 ~ 28 -> /s100/bc102_r3.jsp (1020) (적립식예금)
[3] 31 ~ 34 -> /s400/bc419_r1.jsp (4190) (외화예금)
[4] 40 ~ 49 -> /s100/bc102_r2.jsp (1130) (대출계좌)
[5] 50 ~ 73 -> /s100/bc102_r1.jsp (1020) (기타)
[6] 그 외 -> /s100/bc1090_r1.jsp (1090) (요구불예금)*/
function BT_KindAcct(Acct,kind)
{
var tmpAcct = "";//new Array;
var flag = new Array;
flag = get_session("IGBGYEJI");
for(i=0; i< Acct.length; i++){
if(kind == ""){
tmpAcct = tmpAcct + Acct[i] + ",";
}else if(BT_StrToInt(kind) == BT_StrToInt(flag[i])) {
tmpAcct = tmpAcct + Acct[i] + ",";
}
}
return tmpAcct;
}
// "-" 삭제 - pansory
function BT_ReduceDash(val)
{
var x, ch;
var i=0;
var newVal="";
for(x=0; x <val.length ; x++){
ch=val.substring(x,x+1);
if(ch != "-") newVal += ch;
}
return newVal;
}
//오늘 날짜열 만들기 - pansory
function BT_getToday() {
var today = new Date();
var yyyy, mm, dd;
var Today;
yyyy = today.getYear();
mm = BT_make2Length(today.getMonth() + 1);
dd = BT_make2Length(today.getDate());
Today = yyyy.toString() + mm.toString() + dd.toString();
return Today;
}
// 월일을 두자리로 만들기 - pansory
function BT_make2Length(val) {
if(val < 10) {
val = "0" + val;
}
return val;
}
//공백 없애기
function BT_TrimSpace(str) {
var count = str.length;
var len = count;
var st = 0;
while ((st < len) && (str.charAt(st) <= ' ')) {
st++;
}
while ((st < len) && (str.charAt(len - 1) <= ' ')) {
len--;
}
return ((st > 0) || (len < count)) ? str.substring(st, len) : str ;
}
//공백 없애기
function BT_TrimSpace1(str) {
var count = str.length;
var len = count;
var st = 0;
while ((st < len) && (toString(str.charAt(st)) <= toString(' '))) {
st++;
}
while ((st < len) && (toString(str.charAt(len - 1)) <= toString(' '))) {
len--;
}
return ((st > 0) || (len < count)) ? str.substring(st, len) : str ;
}
//session의 PAY_ACNTNO 와 ACNTNO(val) 값 비교
function BT_ComparePayAcntno(val) {
var USR_AccsList = get_session("ACCS");
var check = "";
for (var i=0; i < USR_AccsList.length; i++) {
if (USR_AccsList[i] == val) {
check = "1";
}
}
return check;
}
// 금액 더하기 계산하기
function BT_calc(string,name)
{
var sum = 0;
BT_Del_comma(doc.form.REC_AMT);
if (eval(string) == 0 ) {
doc.form.REC_AMT.value = "";
}
else {
sum = doc.form.REC_AMT.value;
if (sum == "") {
sum = 0;
}
sum = eval(sum) + eval(string);
doc.form.REC_AMT.value = sum;
}
doc.form.REC_AMT.focus();
}
// 금액 더하기 계산하기
function BT_Card_calc(string,name)
{
var sum = 0;
BT_Del_comma(doc.form.SEV_AMT);
if (eval(string) == 0 ) {
doc.form.SEV_AMT.value = "";
}
else {
sum = doc.form.SEV_AMT.value;
if (sum == "") {
sum = 0;
}
sum = eval(sum) + eval(string);
doc.form.SEV_AMT.value = sum;
}
doc.form.SEV_AMT.focus();
}
//이체 비밀번호 체크
function BT_isTrsfPass(val)
{
// 이체비밀번호는 4자리이어야 한다.(자리수 검사는 따로 해야 한다.)
// 영문자가 1개이상 포함되어야 하며, 영문자와 숫자의 조합만 허락한다.
var len = val.length;
var i;
var alphanum = 0;
for(i=0;i<len;i++) {
if ( BT_isAlphabet(val.charAt(i)) )
alphanum = alphanum + 1;
else if ( isNaN(val.charAt(i)) )
return false;
}
if ( alphanum > 0 )
return true;
else
return false;
}
//알파벳인지 체크 - 이체비밀번호 체크와 세트
function BT_isAlphabet(val)
{
var alphaStr = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
if ( alphaStr.indexOf(val) < 0 )
return false;
else
return true;
}
//숫자만 있는지 체크
function BT_isNumber(val)
{
var numberStr = "0123456789";
if ( numberStr.indexOf(val) < 0 )
return false;
else
return true;
}
function BT_number(val){
if ((event.keyCode<48)||(event.keyCode>57 )){
if(event.keyCode != 8 ) // 백스페이스 제외
{
event.returnValue=false;
}
}
}
// 금액 입력시 "," 자동 입력 & 우측 정렬
function BT_Add_comma(REC_AMT)
{
if(!BT_isAllNumber(REC_AMT.value))
{
alert("금액을 숫자로 입력해 주세요.");
REC_AMT.value = "";
REC_AMT.focus();
return false;
}
var src
var i;
var factor;
var su;
var Spacesize = 0;
factor = REC_AMT.value.length % 3;
su = (REC_AMT.value.length - factor) /3;
src = REC_AMT.value.substring(0,factor);
for(i=0; i<su ; i++)
{
if ((factor==0)&&(i==0))// " XXX "인 경우
{
src += REC_AMT.value.substring(factor+(3*i), factor+3+(3*i));
}
else
{
src +=",";
src += REC_AMT.value.substring(factor+(3*i), factor+3+(3*i));
}
}
REC_AMT.value=src;
return true;
}
function BT_Add_NumberComma(val) {
var Ret_val = BT_StrToInt(val);
Ret_val = BT_Add_Displaycomma(Ret_val);
return Ret_val;
}
function BT_Add_NumberComma1(val) {
var Ret_val = BT_StrToInt(val);
var Zerodel = Ret_val;
if (Zerodel == "0")
Ret_val = "";
else
Ret_val = BT_Add_Displaycomma(Ret_val);
return Ret_val;
}
function BT_Add_Number(val) {
var Ret_val = BT_StrToInt(val);
return Ret_val;
}
function BT_Add_Displaycomma(val) {
var src;
var i;
var factor;
var su;
var Spacesize = 0;
var String_val = val.toString();
factor = String_val.length % 3;
su = (String_val.length - factor) /3;
src = String_val.substring(0,factor);
for(i=0; i<su ; i++)
{
if ((factor==0)&&(i==0))// " XXX "인 경우
{
src += String_val.substring(factor+(3*i), factor+3+(3*i));
}
else
{
if ( String_val.substring(factor+(3*i) - 1, factor+(3*i)) != "-" ) src +=",";
src += String_val.substring(factor+(3*i), factor+3+(3*i));
}
}
return src;
}
function BT_Add_Card_comma(SEV_AMT)
{
if(!BT_isAllNumber(form.SEV_AMT.value))
{
alert("금액을 숫자로 입력해 주세요.");
form.SEV_AMT.value = "";
form.SEV_AMT.focus();
return false;
}
var src
var i;
var factor;
var su;
var Spacesize = 0;
factor = SEV_AMT.value.length % 3;
su = (SEV_AMT.value.length - factor) /3;
src = SEV_AMT.value.substring(0,factor);
for(i=0; i<su ; i++)
{
if ((factor==0)&&(i==0))// " XXX "인 경우
{
src += SEV_AMT.value.substring(factor+(3*i), factor+3+(3*i));
}
else
{
src +=",";
src += SEV_AMT.value.substring(factor+(3*i), factor+3+(3*i));
}
}
SEV_AMT.value=src;
return true;
}
//전체 값이 숫자로만 되어 있는지 체크
function BT_isAllNumber(val)
{
var len = val.length;
var i=0;
for(i=0;i<len;i++) {
if ( isNaN(val.charAt(i)) ) return false;
}
return true;
}
// 컴마(",") 자동 삭제
function BT_Del_comma(REC_AMT)
{
REC_AMT.value = BT_Reduce_comma(REC_AMT.value);
return true;
}
// 컴마(",") 자동 삭제
function BT_Del_Card_comma(SEV_AMT)
{
SEV_AMT.value = BT_Reduce_comma(SEV_AMT.value);
return true;
}
function BT_Reduce_comma(account_number)
{
var x, ch;
var i=0;
var newVal="";
for(x=0; x <account_number.length ; x++){
ch=account_number.substring(x,x+1);
if(ch != ",") newVal += ch;
}
return newVal;
}
function BTCom_Replace(originalString, findText, replaceText){
var pos = 0
var preStr = ""
var postStr = ""
pos = originalString.indexOf(findText)
while (pos != -1) {
preString = originalString.substr(0,pos)
postString = originalString.substring(pos+findText.length)
originalString = preString + replaceText + postString
pos = originalString.indexOf(findText)
}
return originalString
}
function BT_getRandomNumber() { //씨크릿 카드 순차번호 생성
k=Math.round(Math.random()*30)
if(k == 0) k = k + 1
var L_Number = BT_make2Length(k);
return L_Number;
}
//9자리 고객번호에 하이픈 넣기 - pansory
function BT_makeCustnoDash (val) {
DashedCustno = val.substring(0,3) + "-" + val.substring(3,9);
return DashedCustno;
}
//주민번호에 하이픈 넣기
function BT_makeResnoDash (val) {
if (val.length == 10) { //기업의 경우
DashedResno = val.substring(0,3) + "-" + val.substring(3,5) + "-" + val.substring(5);
} else { //개인의 경우
if (val.substring(0,3) == "000"){
DashedResno = val.substring(3,6) + "-" + val.substring(6,8) + "-" + val.substring(8,13);
} else {
DashedResno = val.substring(0,6) + "-" + val.substring(6,13);
}
}
return DashedResno;
}
//카드번호에 하이픈 넣기 - pansory
function BT_makeCardnoDash (val) {
DashedAcntno = val.substring(0,4) + "-" + val.substring(4,8) + "-" + val.substring(8,12) + "-" + val.substring(12,16);
return DashedAcntno;
}
//회원번호(16자리)에 하이픈 넣기 - pansory
function BT_makeMembernoDash (val) {
DashedAcntno = val.substring(0,4) + "-" + val.substring(4,8) + "-" + val.substring(8,12) + "-" + val.substring(12,16);
return DashedAcntno;
}
//카드번호 Array를 셀렉트 박스로 변환 - pansory
function BT_MakeCardList(val, selectedval) {
var L_ListText = "<select name=\"ACNTNO\" id=\"ACNTNO\">";
for(var i=0; i< val.length; i++) {
if (val[i] == selectedval) {
L_ListText += "\t<option value=\"" + val[i] + "\" selected>" + BT_makeCardnoDash(val[i]) + "</option>\n";
} else {
L_ListText += "\t<option value=\"" + val[i] + "\">" + BT_makeCardnoDash(val[i]) + "</option>\n";
}
}
L_ListText += "</select>\n";
return L_ListText;
}
//카드번호 Array를 셀렉트 박스로 변환 - pansory
function BT_makeCardnoList(val) {
var L_ListText = "<select name=\"CARD_NO\" id=\"CARD_NO\">";
for(var i=0; i< val.length; i++) {
if (i == 0) {
L_ListText += "\t<option value=\"" + val[i] + "\" selected>" + BT_makeCardnoDash(val[i]) + "</option>\n";
} else {
L_ListText += "\t<option value=\"" + val[i] + "\">" + BT_makeCardnoDash(val[i]) + "</option>\n";
}
}
L_ListText += "</select>\n";
return L_ListText;
}
//카드번호 선택..
function BT_SelectCardno(USR_AccsList, NeedCardno) {
var no = NeedCardno.length;
var Cardno_No = USR_AccsList.length;
var SelectedCardno = new Array();
var k = 0;
if (no == 0) {
SelectedCardno = USR_AccsList;
//나중에 확인 필요함.. 고른 것들 중에 첫번째 것을 세션에 넣기..????
save_session("CARD_NO", USR_AccsList[0]);
save_flush();
} else {
for (var i=0; i < Cardno_No ; i++) {
for(var j=0 ; j < no; j++) {
if(USR_AccsList[i].length > 12) {
stCode = USR_AccsList[i].substring( 9, 11);
} else {
stCode = "0"; //Case Error
}
if (stCode == NeedAcntno[j]) {
SelectedCardno[k] = USR_AccsList[i];
if (k == "0") {
//나중에 확인 필요함.. 고른 것들 중에 첫번째 것을 세션에 넣기..????
save_session("CARD_NO", USR_AccsList[i]);
save_flush();
}
k++;
}
}
}
}
return SelectedCardno;
}
function BT_makeLoannoDash (val) {
DashedLoanno = val.substring(0,3) + "-" + val.substring(3,9) + "-" + val.substring(9,11) + "-" + val.substring(11,16)
return DashedLoanno;
}
function BT_makeDateDash (val) {
if (val == "00000000")
DashedDate = "";
else
DashedDate = val.substring(0,4) + "-" + val.substring(4,6) + "-" + val.substring(6,8);
return DashedDate;
}
function BT_makeDateDash_1 (val) {
if (val == "000000")
DashedDate_1 = "";
else
DashedDate_1 = val.substring(0,4) + "-" + val.substring(4,6);
return DashedDate_1;
}
function BT_makeDateDash_2 (val) {
if (val == "00000")
DashedDate_2 = "";
else
DashedDate_2 = val.substring(0,2) + "-" + val.substring(2);
return DashedDate_2;
}
function BT_makeDateDash_3 (val) {
if (val == "00000000")
DashedDate_3 = "";
else
DashedDate_3 = val.substring(6,8);
return DashedDate_3;
}
function BT_makeDateSlash (val) {
if (val == "00000000")
DashedDate_1 = "";
else
DashedDate_1 = val.substring(0,4) + "/" + val.substring(4,6);
return DashedDate_1;
}
function BT_makeTime (val) {
ReturnTime = val.substring(0,2) + ":" + val.substring(2,4) + ":" + val.substring(4,6);
return ReturnTime;
}
//문자열을 10진수로 변환
function BT_StrToInt(val) {
var Re_int = 0
if (val != "")
var Re_int = parseInt(val,10);
else
Re_int = 0;
return Re_int;
}
function BT_makeDecimalPoint(val) {
var ReturnVal = val.split(".");
ReturnVal = BT_Add_NumberComma(ReturnVal[0]) + "." + ReturnVal[1];
return ReturnVal;
}
//페이지 설정
function last_page(val){
var last_page = val%10;
var index = parseInt(val/10);
if (last_page > 0){
return(index+1);
}else
return(index);
}
//소수점이하 자리수 설정
function BT_makeCipherPoint(val,cipher)
{
var PreCipher = val.length - cipher;
var Preval = val.substring(0,PreCipher);
var Nextval = val.substring(PreCipher);
var Returnval = Preval + "." + Nextval;
return Returnval
}
//숫자와 영어만 허용
function BT_EngNum(obj,isUP){
//if (event.keyCode == 9 || event.keyCode == 37 || event.keyCode == 39) return;//에러 발생 수정함.
var returnValue = "";
for (var i = 0; i < obj.value.length; i++){
if ((obj.value.charAt(i) >= "0" && obj.value.charAt(i) <= "9") || (obj.value.charAt(i) >= "a" && obj.value.charAt(i) <= "z") || (obj.value.charAt(i) >= "A" && obj.value.charAt(i) <= "Z") ){
returnValue += obj.value.charAt(i);
}else{
returnValue += "";
}
}
obj.value = returnValue;
obj.focus();
}
//날짜 계산 -- jjung
function BT_getDate(Dnum, gubun, StaYer,StaMon,StaDay,EndYer,EndMon,EndDay){
var cur_year, cur_month, cur_day, cur_hour, cur_min, cur_sec, d_cur_date
var index=0;
var Mnum=0;
var theDate = btGetCurrTime(2,1);
var now = new Date();
var daynum = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
var monname = new Array('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
if(theDate == ""){
cur_year= now.getYear();
cur_month = now.getMonth() + 1;
cur_month = new String(cur_month);
cur_day = new String(now.getDate());
cur_hour = theDate.substring(8,10);
cur_min = theDate.substring(10,12);
cur_sec = theDate.substring(12);
}
else {
cur_year = theDate.substring(0,4);
cur_month = theDate.substring(4,6);
cur_day = theDate.substring(6,8);
cur_hour = theDate.substring(8,10);
cur_min = theDate.substring(10,12);
cur_sec = theDate.substring(12,14);
}
daynum[1] = cday(cur_year);
change_month = monname[BT_StrToInt(cur_month) - 1];
tmpDate = new String(change_month + cur_day + "," + cur_year + " " + cur_hour + ":" + cur_min + ":" + cur_sec );
d_cur_date = new Date(tmpDate);
if(gubun == "mon"){
tmpMonth = BT_StrToInt(cur_month) + Dnum;
if (tmpMonth == 0)
{
tmpMonth = "12";
change_month = monname[BT_StrToInt(tmpMonth) - 1];
tmpDate = new String(change_month + cur_day + "," + (cur_year - 1) + " " + cur_hour + ":" + cur_min + ":" + cur_sec );
}
else if (tmpMonth == -1)
{
tmpMonth = "11";
change_month = monname[BT_StrToInt(tmpMonth) - 1];
tmpDate = new String(change_month + cur_day + "," + (cur_year - 1) + " " + cur_hour + ":" + cur_min + ":" + cur_sec );
}
else {
change_month = monname[BT_StrToInt(tmpMonth) - 1];
tmpDate = new String(change_month + cur_day + "," + cur_year + " " + cur_hour + ":" + cur_min + ":" + cur_sec );
}
before_date = new Date(tmpDate);
}else{
var before_date = new Date(Date.parse(d_cur_date) + BT_StrToInt(Dnum)*1000*60*60*24);
}
if(theDate.length==0) {
var before_year = new String(before_date.getYear());
var before_month = new String(before_date.getMonth() + 1);
}else {
var before_year = new String(before_date.getYear());
var before_month = new String(before_date.getMonth() + 1);
}
if(before_month.length==1){
before_month = '0'+before_month;
}
var before_day = new String(before_date.getDate());
if(before_day.length==1){
before_day = '0'+before_day;
}
if(StaYer != "") eval("doc."+StaYer+".value=before_year");
if(StaMon != "") eval("doc."+StaMon+".value=before_month");
if(StaDay != "") eval("doc."+StaDay+".value=before_day");
if(EndYer != "") eval("doc."+EndYer+".value=before_year");
if(EndMon != "") eval("doc."+EndMon+".value=before_month");
if(EndDay != "") eval("doc."+EndDay+".value=before_day");
if(StaYer == "" && StaMon == "" && StaDay == "" && EndYer == "" && EndMon == "" && EndDay == "") return before_year + before_month + before_day;
}
//val : 전체 값, point : 소수점 앞에 자리수
function BT_toMoneyF(val,point,gubun){
var foreVal = 0;
var downVale = 0;
if (BT_TrimSpace(val) == ""){
return foreVal;
} else {
if(point == "" || point == "0") {
foreVal = val;
downVal = "";
} else {
if(gubun == "1"){
foreVal = val.substring(0,point);
} else{
foreVal = BT_Add_NumberComma(val.substring(0,point));
}
downVal = val.substring(point);
}
return foreVal + "." + downVal;
}
}
function BT_SetServiceCode(code){
save_session("SERVICE_CODE",code);
save_flush();
}
function BT_Setreturn_url(val){
save_session("RETURN_URL",val);
save_flush();
}
//2년 윤년처리
function cday(year)
{
//2월달일때 윤년 처리
if ((year % 4) == 0) { //윤년
if ((year % 100) == 0) { //평년
if ((year % 400) == 0) { //윤년
daynum = 29;
}
else {//평년
daynum = 28;
}
}
else { //윤년
daynum = 29;
}
}
else {//평년
daynum = 28;
}
return daynum;
}
/**
* 입력한 숫자 및 영문(조합도 상관없음)이 비밀번호로 사용 가능한지 다음과 같은 제한사항을
* 검사하는 메소드
* 1. 숫자의 경우 하나의 숫자 조합
* 2. 숫자의 경우 오름차순 및 내림차순
* 3. 영문의 경우 하나의 영문 조합(대소문자는 다른 문자로 인식함)
* 4. 영문의 경우 오름차순 및 내림차순
* @obj 입력폼의 object
* @return true : 비밀번호로 사용가능
* @return false : 비밀번호로 사용할 수 없음. 각 상황에 맞는 메시지가 내장되어 있음.
*/
function pwCheck(obj)
{
var val = obj.value;
var flag = false;
for(i=0;i<val.length;i++)
{
if(!BT_isNumber(val.charAt(i)))//숫자 체크
{
flag = true;
break;
}
}
if(flag)//숫자와 영문 혼합 or 영문 조합으로 예상됨
{
for(i=0;i<val.length;i++)
{
if(!BT_isAlphabet(val.charAt(i)))//영문 체크
{
flag = false;
break;
}
}
if(flag)//*** 영문으로만 조합 연번 체크 요구됨 ***
{
if(check_alpha_all_dup(val))//*** 같은 영문자만의 조합 검사 ***
{
alert("입력하신 비밀번호가 모두 같은 영문입니다.");
return false;
}
else//*** 오름 차순 및 내림 차순 검사 ***
{
if(check_alpha_asc(val))//*** 오름차순 검사 ***
{
alert("입력하신 비밀번호가 영문 오름차순입니다.");
return false;
}
else//*** 내림차순 검사 ***
{
if(check_alpha_desc(val))
{
alert("입력하신 비밀번호가 영문 내림차순입니다.");
return false;
}
else//*** 비밀번호로 사용가능 ***
{
return true;
}
}
}
}
else//*** 영문 및 숫자 조합 - 비밀번호로 상용가능 ***
{
return true;
}
}
else//*** 숫자로만 조합되었으므로 연번 체크 요구됨 ****
{
if(check_digit_all_dup(val))//*** 같은 숫자만의 조합 검사 ***
{
alert("입력하신 비밀번호가 모두 같은 숫자입니다.");
return false;
}
else//*** 오름 차순 및 내림 차순 검사 ***
{
if(check_digit_asc(val))//*** 오름차순 검사 ***
{
alert("입력하신 비밀번호가 오름차순입니다.");
return false;
}
else//*** 내림차순 검사 ***
{
if(check_digit_desc(val))
{
alert("입력하신 비밀번호가 내림차순입니다.");
return false;
}
else//*** 비밀번호로 사용가능 ***
{
return true;
}
}
}
}
}
/**
* 숫자로만 구성된 문자열이 내림차순의 숫자들인지 확인
* @return true : 내림차순이다.
* @return false : 내림차순이 아니다.
*/
function check_digit_desc(val)
{
var init_cnt = 0;
var flag = true;
for(i=0;i<numberArray.length;i++)
{
if(val.charAt(0) == numberArray[i])
{
init_cnt = i;
break;
}
}
var init_cnt_tmp = init_cnt;
for(i=0;i<val.length;i++)
{
if(val.charAt(i) == numberArray[init_cnt_tmp])
{
}
else
{
flag = false;
break;
}
init_cnt_tmp -= 1;
if(init_cnt_tmp == -1)
{
init_cnt_tmp = numberArray.length - 1;
}
}
return flag;
}
/**
* 영문으로만 구성된 문자열이 내림차순인지 확인
* @return true : 내림차순이다.
* @return false : 내림차순이 아니다.
*/
function check_alpha_desc(val)
{
var init_cnt = 0;
var flag = true;
for(i=0;i<alphaArray.length;i++)
{
if(val.charAt(0) == alphaArray[i])
{
init_cnt = i;
break;
}
}
var init_cnt_tmp = init_cnt;
for(i=0;i<val.length;i++)
{
if(val.charAt(i) == alphaArray[init_cnt_tmp])
{
}
else
{
flag = false;
break;
}
init_cnt_tmp -= 1;
if(init_cnt_tmp == -1)
{
init_cnt_tmp = alphaArray.length - 1;
}
}
return flag;
}
/**
* 숫자로만 구성된 문자열이 오름차순의 숫자들인지 확인
* @return true : 오름차순이다.
* @return false : 오름차순이 아니다.
*/
function check_digit_asc(val)
{
var init_cnt = 0;
var flag = true;
for(i=0;i<numberArray.length;i++)
{
if(val.charAt(0) == numberArray[i])
{
init_cnt = i;
break;
}
}
var init_cnt_tmp = init_cnt;
for(i=0;i<val.length;i++)
{
if(val.charAt(i) == numberArray[init_cnt_tmp])
{
}
else
{
flag = false;
break;
}
init_cnt_tmp += 1;
if(init_cnt_tmp == numberArray.length)
{
init_cnt_tmp = 0;
}
}
return flag;
}
/**
* 영문으로만 구성된 문자열이 오름차순 인지 확인
* @return true : 오름차순이다.
* @return false : 오름차순이 아니다.
*/
function check_alpha_asc(val)
{
var init_cnt = 0;
var flag = true;
for(i=0;i<alphaArray.length;i++)
{
if(val.charAt(0) == alphaArray[i])
{
init_cnt = i;
break;
}
}
var init_cnt_tmp = init_cnt;
for(i=0;i<val.length;i++)
{
if(val.charAt(i) == alphaArray[init_cnt_tmp])
{
}
else
{
flag = false;
break;
}
init_cnt_tmp += 1;
if(init_cnt_tmp == alphaArray.length)
{
init_cnt_tmp = 0;
}
}
return flag;
}
/**
* 숫자로만 구성된 문자열이 같은 숫자로 조합되었는지 확인
* @return true : 동일한 값으로 조합. 비밀번호로 사용하면 안됨.
* @return false : 다른 숫자가 적어도 하나 존재. 비밀번호로 사용해도 괜찮음.
*/
function check_digit_all_dup(val)
{
var flag = true;
for(i=0;i<numberArray.length;i++)
{
flag = true;
for(j=0;j<val.length;j++)
{
if(val.charAt(j) == numberArray[i])
{
}
else
{
flag = false;
break;
}
}
if(flag)
{
break;
}
else
{
continue;
}
}
return flag;
}
/**
* 영문으로만 구성된 문자열이 같은 영문으로 조합되었는지 확인
* @return true : 동일한 영문으로 조합. 비밀번호로 사용하면 안됨.
* @return false : 다른 영문이 적어도 하나 존재. 비밀번호로 사용해도 괜찮음.
*/
function check_alpha_all_dup(val)
{
var flag = true;
for(i=0;i<alphaArray.length;i++)
{
flag = true;
for(j=0;j<val.length;j++)
{
if(val.charAt(j) == alphaArray[i])
{
}
else
{
flag = false;
break;
}
}
if(flag)
{
break;
}
else
{
continue;
}
}
return flag;
}
다중 접속 서버 프로그래밍 (0) | 2007.10.23 |
---|---|
Dialog Window 띄우면서, 작업표시줄에서 감추기 (0) | 2007.10.23 |
중복 실행 방지. (0) | 2007.10.23 |
[강좌] 기초적인 압축 알고리즘 하이텔 퍼옴.. (0) | 2007.10.23 |
class 들의 단계적 참조 관계 (0) | 2007.10.12 |
C 라이브러리에 의해서 실행되는 기능들 중 어떤 것은 C 언어 그 자체의 일부처럼 생각될 수 있다. 그 기능들은 라이브러리 매뉴얼이 아니라, C 언어 매뉴얼에서 문서화되어야 하지만, 우리는 아직 그 C 언어 매뉴얼을 만들지 않았기 때문에, 그 기능들을 이곳에서 설명할 것이다.
당신이 프로그램을 만들 때, "불가능한" 에러나 기본 가설의 위반에 대해서 전략적인 위치에서 그들을 체크하는 것은 좋은 생각이다. 그 체크는 프로그램의 다른 부분들 사이에서 불화합으로 일어나는 문제들을 디버깅하는데 유익하다.
헤더파일 `assert. h'에 정의된, assert 매크로는 프로그램의 에러가 검출된 곳에서 에러메세지를 출력하는 동안 프로그램을 중지시키기에 편리한 방법을 제공한다. 당신이 당신의 프로그램을 디버깅하려할 때, 당신은 정의된 매크로 NDEBUG를 사용해서 재컴파일하면 assert 매크로에 의해서 수행됐던 에러 체크는 디버그하지 않을 수 있다. 이것은 기능무효(disable)로 체크된 프로그램 소스 코드는 변경하지 않는다는 것을 의미한다.
그러나 그 체크를 무효화(disabling)하는 것은 그 프로그램을 확연하게 느리게 만들지 않는 한 바람직하지 않다. 또한 누가 그 프로그램을 실행시키던지 발생할 수 있는 가능한 에러에 대해서 대비하도록 좀더 많이 에러를 체크해보는 것이 좋다. 똑똑한 사용자는 어떤 것 이 잘못되었을 때 그것을 지적하지 않고 무의미한 반환값을 갖는 것보다는 차라리 프로그램이 파손되는 것을 원할 것이다.
매크로 : void assert (int expression)
매크로 assert에 의해 프린트된 진단 메시지에 있는 정보는, 당신의 프로그램을 사용하는 사용자에게 왜 입력이 무효한지 또는 왜 명령이 실행되지 않았는지를 알리는데는 유용하지 않지만, 프로그래머나 당신이, 프로그램에서 나타난 버그를 추적하는데 도움이 된다. 그러므로 당신은 예측할 수 없는 사건에 대한 에러메세지를 출력하는데 assert를 사용할 수 없다.
더 말하자면, assert에 무효한 입력이 주어진다면 당신의 프로그램은 중지된다_그것은 에러메세지를 출력한 후에 0이 아닌 상황(22. 3. 2절 [Exit Status]참조. )으로 종료되거나, 또는 다른 명령을 읽거나 다음 입력 파일로 옮긴다. 프로그램에 나타나지 않은 버그 문제에 에러메세지를 출력하는 것에 대한 정보는2. 3절 [Error Messages]참조.
ANSI C 는 인수의 타입이나 개수를 다양하게 취할 수 있는 함수를 선언하기 위한 구문을 정의한다. ( 그와같은 함수들은 varargs 함수 또는 variadic 함수라고 부른다. ) 그렇지만, 언어 그 자체는 그와같은 함수들을 위한 메커니즘을 제공하지 않는다; 대신에, `stdarg. h'에 정의된 가변 인수 매크로들을 사용하는 것이다. 이 절은 어떻게 가변인자 함수들을 선언하고, 어떻게 사용하며, 호출할 것인지에 대해서 설명한다. 이식성 노트 : 많은 오래된 C 방언들은 `varargs. h'를 사용해서 다양한 개수의 변수를 정의하는 함수 메커니즘으로 유사한 것을 제공하지만, 호환성이 없다. 원래 C 함수들은 고정된 개수의 인수들을 취한다. 당신이 함수를 정의할 때, 당신은 각 인수의 데이터 타입을 정한다. 함수가 호출될때마다 그전에 정해진 예상된 개수의 인수들이 공급되는데, 그 인수들의 타입은 정해진 것으로 변경될 수 있는 것이다. 그래서, 만일 함수 `foo'가 foo(int, char *); 로 선언된다면, 하나는 숫자 다른 하나는 문자열 포인터인 두 개의 인수를 가지고 foo 함수를 호출해야만 한다. 그러나 어떤 함수들은 정해지지 않은 개수의 인수를 받아들일 수 있는 동작을 수행한다. 어떤 경우, 함수는 한 블록에 그들의 모두를 처리함으로써 여러 개수의 값들을 처리할 수 있다. 예를 들어, 정해진 값들의 집합을 저장하기 위해서 malloc으로 일차원 배열을 할당하는 함수를 고려해보자. 이 연산은 숫자에 해당하는 배열의 길이로써 어떤 개수의 값이 있다고 이해한다. 가변 인수 기능이 없다면, 당신은 가능한 배열 크기를 얻어내는 또 다른 한 개의 함수를 정의해야만 한다. 라이브러리 함수 printf (7. 9절 [Formatted Output]참조. )는 가변인수가 유용하게 쓰이는 다른 부류의 함수에 대한 예제가 된다. 이 함수는 규정된 템플리트 문자열의 제어 하에 인수들( 개수뿐만 아니라 다양한 형을 가질 수 있다)을 프린트한다. 가변인자 함수는 많은 인수들을 처리할 수 있다는 점에서 가변인수 함수를 정의하는 이유가 된다. open과 같은 함수들은 고정된 개수의 인수들을 취하지만, 때때로 마지막 몇 개는 무시된다. ANSI C 는 그 함수를 가변으로 정의하도록 요구하지만; GNU C 컴파일러와 대부분 다른 C 컴파일러들을 고정된 인수를 취하는 함수처럼 정의하도록 허용하고 선언할 때만 가변으로써 그 함수를 선언한다 (또는 그 인수들을 전혀 선언하지 않는다. ). 가변인자 함수를 정의하고 사용하는 세 가지 단계이다. 인수리스트안에 생략표시 (`. . . ')를 사용하고, 가변 인수들을 억세스 하도록 특별한 매크로들을 사용하여서, 가변인수 함수를 정의하라.A. 2. 2. 2절 [Receving Arguments]참조. 그것을 호출하는 모든 파일에서, 생략표시 (`. . . ')와 함께 프로토타입을 사용해서 가변으로써 함수를 선언하라.A. 2. 2. 1절 [Varidic Prototypes]참조. 고정된 인수들 뒤에 가변인수들이 뒤에 나오도록 해서 함수를 호출하라.A. 2. 2. 4 [Calling Variadics]참조. 가변 인수를 받아들이는 함수는 올바른 프로토타입으로 선언되어야만 한다. 당신은 보통 고정된 인수들을 사용하고 가변인수들의 가능성을 지적하기 위해서 `. . . '을 취한다. ANSI C 구문은 `. . . '가 나오기 전에 적어도 한 개의 고정 인수를 필요로 한다. 예를 들어, 고정된 두 개의 인수로써, const char * 와 int 인수를 취하고 int형의 값을 반환하는 func 함수의 정의이다. 그 두 개의 고정인수 다음에 알려지지 않은 인수들이 몇 개가 따르게 된다. 이식성 노트 : 어떤 C 컴파일러에서, 함수 정의에서 가변인수는 형을 선언하여 등록될 수 없다. 좀더 자세히 말하면, 이 인수들의 타입은 자체-진행(self-promoting)이 되어야만 한다: 즉, 디폴트 진행은 그 타입들을 변경하지 않아야 한다. 이것은, float , char(부호가 있던지 없던지), 그리고 short int (부호가 있거나 없거나) 뿐만 아니라 배열과 함수들의 타입을 무시한다. 보통 고정된 인수들은 개별적인 이름을 갖고, 당신은 그들의 값을 억세스하기 위해서 그들의 이름을 사용할 수 있다. 그러나 가변 인수들은 아무런 이름을 갖지 않는다. 어떻게 당신이 그들을 억세스 할 것인가? 그들을 억세스하기 위한 유일한 방법은 그들이 기록된 순서대로, 순차적으로 억세스하고 다음 세 가지 단계에서 있는 헤더파일 `stdarg. h'에 선언된 특별한 매크로들을 사용해야만 한다. 당신이 만일 남겨진 가변인수를 무시하기를 원한다면 언제든지 멈출 수 있다. 호출로 공급된 인수들보다는 소수의 인수들을 억세스 하는 함수를 위해서 아주 좋지만, 만일 당신이 너무 많은 인수들을 억세스 하려 시도한다면 당신은 쓰레기 값을 얻게 될 것이다. (실제로, 대부분 C 컴파일러에서, va_end의 호출은 아무 일도 하지 않고 당신은 그것을 실제로 호출할 필요가 없다. 이것은 GNU C 컴파일러에서는 항상 참이다. 그러나 누군가 당신의 프로그램을 독특한 컴파일러에서 컴파일하는 경우라면 va_end를 호출해야만 할 것이다. va_start, va_arg 그리고 va_end에 대한 완전한 정의는A. 2. 2. 5절 [Argument Macros]를 참조하라. 단계 1과 3은 가변 인수를 받아들이는 함수에서 반드시 수행되어야만 한다. 그렇지만, 당신은 다른 함수에 인수로써 va_list 변수를 줄 수 있고 전부 또는 단계 2를 수행할 수 있다. 당신은 단일한 함수 호출에서 여러 번 세 단계의 전부를 반복해서 수행 할 수 있다. 만일 당신이 가변 인수를 무시하기를 원한다면, 세단계를 하지 않을 수 있다. 만일 당신이 원한다면 포인터 변수인 한 개의 인수보다 더 많은 것을 가질 수 있다. 당신은 당신이 원할 때 va_start로 각 변수를 초기화 할 수 있고, 그러고 나면 당신이 원하는 각각의 포인터 인수를 추출할 수 있다. 각 포인터 변수인 인수는 인수 값들의 같은 집합을 통해서 진행되지만, 그것은 자신만의 페이스(pace)를 갖는다. 이식성 노트: 어떤 컴파일러로, 당신이 서브루틴(subroutine)에 인수로써 포인터 변수를 사용한다면, 당신은 서브루틴이 반환한 후에 같은 포인터 변수인 인수를 사용해서 기록하지 않아야만 한다. 완벽한 이식성을 위해서, 당신은 va_end에 그것을 주어야한다. 이것은 실제로 ANSI C의 권장사항이지만, 대부분 ANSI C 컴파일러는 다행이 상관없이 작업한다. 가변 인수들의 타입과 개수를 알 수 있는 일반적인 방법은 없다. 그래서 누구든 그 가변인수가 얼마나 많은 인수들을 가졌고, 그것이 무슨 종류인지를 알아낼 수 있는 특별한 방법의 함수를 고안해야한다. 그것은 가변인수 함수의 호출 관습에 적당하게 정의되어야 하고, 그것에 근거하여 프로그램에서 가변 인수 함수를 호출해야 한다. 호출관습의 한가지는 한 개의 고정된 인수를 사용해서 가변인수의 개수를 공급하는 것이다. 이 방법은 공급된 가변 인수들이 모두 같은 형일 경우에 가능한 방법이다. 그와 유사한 방법으로는 가변인수가 공급될 가능성에 대한 정보를 한 비트에 담은, 비트 마스크가될 고정인수를 인수중에 하나로 갖는 것이다. 당신은 미리 선언된 시퀀스 안에 있는 비트들을 테스트할 수 있다; 만일 그 비트가 설정되면, 다음 인수의 값을 추출하는 것이고, 그렇지 않다면, 디폴트값을 사용하는 것이다. 고정된 인수는 가변 인수들의 개수와 타입, 이 둘을 지정하는 패턴으로써 사용될 수 있다. printf에서 형식화된 문자열 인수는 이것의 한 예가 된다. (7. 9. 7절 [Formatted Output Functions]참조.) 다른 가능성은 마지막 가변 인수로써 "끝 표시"값을 사용하는 것이다. 예를 들어, 예측할 수 없는 포인터 인수들의 개수를 처리하는 함수가 있다면, 널 포인터는 인수 리스트의 끝을 지적할 것이다. (이것은 널 포인터가 함수에게 의미 있는 값이 아니라고 가정한다. ) execl 함수는 이 방법으로 작업한다;23. 5절 [Executing a File]참조. 당신이 가변인수 함수를 호출할 때 특정한 어떤 것을 써서는 안된다. 단지 괄호안에 보통, 콤마에 의해 분리된 인수들(가변으로써, 요청된 인수)만 사용하라. 그러나 당신은 프로토타입으로 그 함수를 선언함으로써 준비하고, 그 인수의 값들이 어떻게 변환되는지를 알아야만 한다. 원칙적으로, 가변으로써 정의된 함수들은 당신이 그들을 호출할 때마다 함수 프로토타입을 사용해서 가변이 되도록 선언되어야한다. (A. 2. 2. 1 [Variadic Prototypes]참조. ) 이것은 함수가 가변 인수 또는 고정된 인수를 취하는지의 여부에 의존하여 함수에 인수 값들을 부여하는 다른 호출 관습을 가진 C 컴파일러 때문이다. 실제로, GNU C 컴파일러는 항상 당신이 가변인수 또는 요청된 인수를 사용하는지에 상관없이 같은 방법으로 인수형의 주어진 집합을 부여한다. 그래서, 인수들의 타입이 자체-진행인 동안, 당신은 그들의 선언을 안전하게 생략할 수 있다. 보통 가변함수를 위해서 인수의 형을 선언하는 것은 좋은 방법이고, 모든 함수들을 위해서는 물론 당연한 것이다. 그런데 몇 개의 함수는 그렇지 않은 경우가 있다_예를 들어, open과 printf 함수의 프로토타입이 가변인수들의 타입을 정하지 않았을 때, 가변인수 함수를 호출하면, 함수의 가변 인수 값들은 디폴트 인수 승급이 수행된다. 디폴트 인수 승급이란 char 또는 short int (부호가 있던지 없던지)의 형을 가진 오브젝트들은 int 나 unisgned int로 승급되고; float의 형을 가진 오브젝트들은 double로 승급되는 것을 말한다. 그래서, 가변인수에 char형의 값을 넣으면, 그것은 int로 승급되고, 그 함수는 va_arg(ap, int)과 함께 그것을 얻을 것이다. 고정 인수들은 보통 함수의 원형을 통해서 제어된다: 인수 표현식은 마치 그형의 변수로 할당되었던 것 선언된 인수의 형으로 변환된다. 다음은 가변 인수들을 가져오기 위해서 사용되는 매크로에 대한 기술이다. 그 매크로들은 헤더파일 `stdarg. h'에 정의되어 있다. 데이터 타입 : va__list 매크로 : void va__start (va_list ap, last_required) 매크로 : type va__arg (va_list ap, type) 매크로 : void va__end (va_list ap) 다음은 인수들을 가변적인 개수로 받아들이는 함수에 대한 예이다. 함수의 첫 번째 인수는 반환된 결과와 합산된, 남겨진 인수들의 개수이다. 이 함수는 가변 인수 기능을 어떻게 사용하는지 설명하는데 충분하다. ANSI C 이전에, 프로그래머들은 가변함수들을 쓰기 위해서 완전히 다른 기능을 사용했었다. GNU C 컴파일러는 여전히 그것을 지원한다; 현재, 그것은 ANSI C가 여전히 일반적이지 않기 때문에, ANSI C 기능보다는 더 이식성이 있다. 오래된-형태의 가변인수 함수를 정의하고 있는 헤더파일은 `varargs. h'라고 불린다. `varargs. h'를 사용하는 것은 `stdarg. h'를 사용하는것과 거의 같다. 가변인수 함수를 어떻게 호출하는지에 대한 것은 거의 다름이 없다;A. 2. 2. 4절 [Calling Variadics]참조. 오직 유일한 차이는 그들을 정의하는 방법이다. 무엇보다도, 당신은 오래된 형태의 비- 프로토타입 구문을 사용해야만 한다. 다음처럼: 오래된 형태의 가변인수 함수들을 정의하기 위해서는 특정한 매크로가 사용된다: 매크로 : va__alist 매크로 : va__decl 매크로 : void va__start (va_list ap) 다른 인수 매크로, va_arg 와 va_end는 `varargs. h' 와 `stdarg. h'의 것이 서로 같다;A. 2. 2. 5절 [Argument Macros]참조. 동일한 컴파일 단위에서 `varargs. h'와 `stdarg. h'가 둘다 인클루드 되어서는 안된다; va_start가 서로 충돌하게 된다. 널 포인터 상수는 어느 실제 오브젝트를 가리키고 있는 것이 아니라는 것을 말한다. 당신은 void * 형을 가진 포인터 변수로 그것을 할당할 수 있다. 널 포인터 상수를 사용하기 위한 좋은 방법은 NULL을 사용하는 것이다. 매크로 : void * NULL C에서 두 개의 포인터를 뺀 결과는 항상 정수이지만, 정밀한 데이터 타입은 C 컴파일러에 따라 다르다. . 그처럼 데이터 타입에 따라서, sizeof의 결과 또한 컴파일러에 따라서 다르다. ANSI 는 그 두 개의 데이터 타입을 위해서 표준 이름을 정의하기 때문에, 당신은 이식성을 위해서 그 데이터 타입을 사용할 수 있다. 그들은 헤더파일 `stddef. h'에 정의되어 있다. 데이터 타입 : ptrdiff__t 데이터 타입 : size__t GNU 시스템에서 size_t는 unsigned int 또는 unsigned long int 와 동일하다. 그 타입들은 GNU 시스템상에서 동일한 특성을 갖고 있고, 그들을 사용할 때 대부분은 그들 사이를 서로 변경시키지 않고도 사용할 수 있다. 그렇지만, 그들은 어떤 구문들에서는 차이를 갖기 때문에 다른 데이터타입으로 구분된 것이다. 예를 들어, 함수의 원형으로 함수 인수의 타입을 정할 때, 당신이 사용하는 것은 차이가 있다. 만일 시스템 헤더파일이 size_t 타입의 인수를 갖는 malloc 함수를 선언하고, 당신이 unisgned int의 타입을 갖는 malloc를 선언했을 때, 만일 size_t가 당신의 시스템에서 unisgned long int형으로 발생한다면, 당신은 컴파일 에러를 얻을 것이다. 이러한 문제의 가능성을 피하기 위해서, 함수의 인수나 값은 다른 방법으로 그 형을 선언하기보다는 size_t의 타입을 갖도록 선언하라. 호환성 노트 : ANSI C가 나타나기 전에 C는 포인터 뺄셈의 결과를 나타내기 위해서 int를 사용하고 오브젝트의 크기를 표현하기 위해서 unsigned int를 사용했었다. 그들은 size_t나 ptrdiff_t를 정의할 필요가 없었다. 유닉스 시스템들은 `sys/types. h'에 size_t를 정의해놓았지만, 그 정의는 보통 signed 형을 말한다. 당신이 당신의 프로그램에서 사용되는 오브젝트의 적당한 C 데이터 타입을 선정할 때 대부분 그것이 얼마나 많은 비트들을 사용하고 그 오브젝트가 어떻게 표현되는지에 관심을 가질 필요가 없다. 당신이 그와같은 정보를 필요로 할 때, C 언어 자체는 그것을 얻을 수 있는 방법을 제공하지 않는다. 헤더파일 `limits. h'와 `float. h'에 포함된 매크로들은 당신에게 그것에 관한 세심한 정보를 줄 것이다. 정수 타입이 얼마나 많은 비트로 구성되었는지 알 필요가 있는 프로그램은 비트 벡터(bit vector)로써 lont int의 배열을 사용하는 경우가 일반적이다. 당신은 vector[n / LONGBITS] & (1 << (n % LONGBITS)) 로 인덱스 N을 구성하는 비트를 억세스할 수 있다. LONGBITS는 long int를 구성하는 비트의 개수로 당신이 정의해서 공급하라. C 언어에서 정수 데이터 타입에 있는 비트의 수에 대한 정보를 당신에게 줄 수 있는 연산자는 없다. 그렇지만 헤더파일 `limits. h'에 정의된 매크로 CHAR_BIT를 사용해서 그것을 계산할 수 있다. CHAR_BIT 당신이 0에서 일 백만 사이에 있는 정수의 값을 저장할 필요가 있다고 가정해보자. 당신이 사용할 수 있는 가장 작은 타입은 무엇인가? 그것을 정하는데 일반적인 규칙은 없다; 그것은 C 컴파일러와 목표 머쉰(machine)에 의존한다. 당신은 타입을 결정하기 위해서 `limits. h'에 있는 매크로 `MIN'과 `MAX'를 사용할 수 있다. 각각의 부호가 있는 정수 타입은 그것이 저장할 수 있는 가장 작은 값과 가장 큰 값을 나타내는 한 쌍의 매크로를 갖는다. 부호가 없는 정수 타입은 최대의 값을 나타내는 한 개의 매크로를 갖는다; 최소값은 물론 0이다. 그 매크로들의 값은 모두 정수 상수 표현이다. 다른 타입들을 위한 `MAX' 와 `MIN' 매크로들은 매크로에 의해 설명된 같은 타입의 값을 갖는다_그래서 ULONG_MAX는 unisgned long int 의 타입을 갖는다. SCHAR_MIN SCHAR_MAX, UCHAR_MAX CHAR_MIN CHAR_MAX SHRT_MIN SHRT_MAX, USHRT_MAX INT_MIN INT_MAX, UINT_MAX LONG_MIN LONG_MAX, ULONG_MAX LONG_LONG_MIN LONG_LONG_MAX, ULONG_LONG_MAX WCHAR_MAX 헤더파일 `limits. h'는 또한 다양한 운영체제와 파일 시스템 제한들을 파라미터 화한 부가적인 상수들을 정의하고 있다. 그 상수들은27장[System Configuration]에 설명되어 있다. 플로팅 포인트 수의 구체적인 표현 방법은 기계마다 다르다. 플로팅 포인트 수들은 내부적으로 근사치로써 표현되기 때문에, 플로팅 포인트 데이터를 다루기 위한 알고리즘은, 때때로 기계의 플로팅 포인트 표현에 대한 자세한 정밀도를 참작하는데 사용된다. C 라이브러리에서 어떤 함수들은 이러한 정보를 필요로 한다; 예를 들어, 플로팅 포인트 숫자들을 읽거나 출력하는 알고리즘 (7장 [I/O on Stream]참조) 과 삼각함수를 계산하기 위한 알고리즘 그리고 무리수 함수들을 위한 알고리즘들은 정확성의 상실이나 반올림-에러를 피하기 위해서 그 정보를 사용한다. 수학적인 분석 기술을 다루는 프로그램들은 에러 경계를 계산하거나 최소화하기 위해서 이 정보를 필요로 한다. 헤더파일 `float. h'는 당신의 기계에 의해서 사용되는 형식을 설명한다. 이 절은 플로팅 포인트 표현법을 설명하기 위한 용어들을 설명한다. 당신은 플로팅 포인트 숫자들을 표현하는 과학적인 표기 또는 지수적인 표기의 개념에 대해서 이미 친숙할 것이다. 예를 들어, 숫자 123456. 0은 가수가 1. 23456이고 베이스가 10으로 5승임을 가리키는, 1. 234546e+05의 지수적 표기로써 표현될 수 있다. 더 형식적으로, 플로팅 포인트 수의 내부적 표현은 다음의 파라미터로써 특징을 나타낼 수 있다. 부호는 -1 또는 1 이다. 지수를 위한 베이스(base) 또는 기수(radix)는 1보다 큰 정수이다. 이것은 특정 표기에 따라서 다른 상수이다. 베이스에 몇 승인지를 나타내는 수가 지수이다. 지수의 상한과 하한은 특정한 표기에 따라서 다른 상수이다. 때때로, 플로팅 포인트 수를 표현하고 있는 실제 비트들에서, 지수를 항상 unsigned로 표현되도록 만들기 위해서 그것에 상수를 더하여 부호의 의미를 부여한다. 이것은 만일 당신이 직접 플로팅 포인트 수를 구성하고 있는 비트 영역들의 어떤 부분을 사용할 필요가 있는 경우에만 중요하다. GNU 라이브러리는 이러한 것을 지원하지 않는다. 그러므로 다음부터는 이것에 대한 논의는 무시될 것이다. 가수부(mantissa) 또는 유효수(significand)는 플로팅 포인트 숫자를 이루는 한 부분으로써 부호가 없는 정수이다. 가수부의 정밀도. 만일 어떤 플로팅 표현에서 베이스(base)가 b라고 했을 때, 정밀도는 베이스-b를 기반으로 가수부 안에 들어가 있는 숫자들의 개수이다. (즉. . 가수부가 몇 개의 비트로써 표현되느냐를 정밀도라고 한다. ) 이것은 특정한 표현에 따라 다른 상수이다. 많은 플로팅 포인트 표현들은 가수부 안에 암묵적으로 숨겨진 비트를 가지고 있다. 이것은 가수부 안에서 실질적으로는 표현되지만 그 값이 항상 1로 되어있기 때문에 메모리에는 저장되지 않는다. 정밀도 형태는(위를 보라) 숨겨진 비트들도 표함 한다. 다시, GNU 라이브러리는 플로팅 포인트 표현을 위해서 그와같은 저수준의 관점을 다루는 기능을 제공하지 않는다. 플로팅 포인트 수의 가수부는 지수의 몇 승을 가진 함축적인 소수부로써 표현된다. 그래서 가장 크게 표현할 수 있는 가수부가 이 정밀도보다 적은 것이면, 소수의 값은 항상 1보다 적다. 플로팅 포인트 수의 수학적인 값은 소수, 부호 그리고 베이스의 몇 승임을 나타내는 지수로써 만들어진다. b가 베이스라고 했을 때, 소수가 적어도 1/b라면, 플로팅 포인트 수가 일반화되었다고 말한다. 바꾸어 말하면, 가수부에 지수승이 곱해지면 맞추기에 너무 크게 될 것이다. 비-일반화된 수들은 디노멀(denomal)이라고 부른다; 그들은 플로팅 포인트 수가 일반적으로 저장될 수 있는 정밀도 보다도 작은 정밀도를 갖고 있다. 만일 그 수가 일반화되지 않았다면, 가수부를 base로 나눈 다음 나온 지수로부터 1을 뺄 수 있고, 그러면 같은값을 가진 다른 표기형식의 플로팅 포인트를 얻게 된다. 그 수가 일반화될 때까지 반복적으로 위와 같은 일을 하면 일반화된 플로팅 포인트 수가 나오게 된다. 두 개의 다른 일반화된 플로팅 포인트 수들은 값이 같을 수 없다. ( 이 규칙에는 예외가 있다: 만일 가수부가 0이라면, 그것은 일반화된 것으로 간주된다. 특정한 기계에서 발생할 수 있는 예외상황이란, 지수부가 그 표기법으로 저장할 수 있기에는 너무 작은 경우이다. 그러면 지수부로부터 1을 빼는 것이 불가능하기 때문에 , 소수가 1/b보다 적은 소수부라면 일반화될 수 있을 것이다. ) 다음 매크로 정의들은 헤더파일 `float. h'에 있다. `FLT_'로 시작하는 매크로들은 float 타입에 관한 것이고, `DBL_'로 시작되는 매크로들은 double 타입에 와 `LDBL_'로 시작되는 매크로들은 long double 타입에 관한 것이다. (현재 GCC 는 분리된 데이터형으로써 long double를 지원하지 않기 때문에, `LDBL_'상수들을 위한 값들은 double형을 위한 상수에 해당되는 값과 같다. ) 그 매크로들 중에서, 오직 FLT_RADIX는 상수 표현식이 되도록 보증된다. 이곳에 설명된 다른 매크로들은 상수 표현식, `#if'와 같은 전처리 지시자 또는 정적 배열 안의 차원을 요구하는 곳에서 사용될 수 없다. ANSI C 표준이 대부분의 파라미터들을 위한 최소값과 최댓값 정했다고 하더라도, GNU C는 목표 기계의 플로팅 포인트 표현에 따른 값을 사용한다. 그래서 GNU C는 목표 기계가 안정적이라면 ANSI C 요구를 만족시키게 되는 것이다. 실제로, 현재 지원되는 모든 기계들은 안정적이다. FLT_ROUNDS FLT_RADIX FLT_MANT_DIG DBL_MANT_DIG LDBL_MANT_DIG FLT_DIG DBL_DIG, LDBL_DIG FLT_MIN_EXP DBL_MIN_EXP, LDBL_MIN_EXP FLT_MIN_10_EXP DBL_MIN_10_EXP, LDBL_MIN_10_EXP FLT_MAX_EXP DBL_MAX_EXP, LDBL_MAX_EXP FLT_MAX_10_EXP DBL_MAX_10_EXP, LDBL_MAX_10_EXP FLT_MAX DBL_MAX, LDBL_MAX FLT_MIN DBL_MIN, LDBL_MIN FLT_EPSILON DBL_EPSILON, LDBL_EPSILON 다음은 이진 플로팅 포인트 연산을 위해서 IEEE 표준에서(ANSI/IEEE Std 754-1985) 정한, 대부분의 일반 플로팅 포인트 표기에서 산출된 float형의 대부분의 매크로 값을 보여주고 있다. 1980년대 이후에 디자인된 대부분의 컴퓨터는 이 형식을 사용한다. IEEE 단정도(single-precision) float 표기법은 베이스로 2를 사용한다. 그것은 23비트에 한 개의 숨겨진 비트를 더해서(그래서 총 정밀도는 베이스를 2로 했을 때 24가 된다. ) 부호 비트와 가수부를 나타내고, 8-비트 지수부는 -125에서 128까지의 범위에 있는 값을 표현할 수 있다. 다음은, float형 데이터를 이 표기법을 사용할 경우, 그것에 연관된 파라미터의 적당한 값을 나타내고 있다. 다음은 double 데이터 타입을 위한 값들이다. 구조체 형안에서 특정한 구조체멤버의 위치를 계산하기 위해서는 offsetof를 사용할 수 있다. 매크로 : size_t offsetof (type, member)A. 2. 1 왜 가변인자 함수들이 사용되는가?
A. 2. 2 어떻게 가변인자 함수를 정의하고 사용하는가?
A. 2. 2. 1 가변 인수들을 위한 구문
A. 2. 2. 2 인수 값들을 받기
A. 2. 2. 3 어떻게 많은 인수들이 공급되는가?
A. 2. 2. 4 가변인수 함수들을 호출하기
A. 2. 2. 5 인수 억세스 매크로들
A. 2. 3 가변인수 함수의 예제
A. 2. 3. 1 오래된-형태의 가변인수 함수들
A. 3 널 포인터 상수
A. 4 중요한 데이터 타입들
사용법 노트: size_t는 오브젝트의 크기를 저장하는 인수나 변수를 선언하는데 좋은 방법을 제공한다.A. 5 데이터 타입 측정
A. 5. 1 정수 데이터 타입의 너비 계산하기
A. 5. 2 정수 타입의 범위
A. 5. 3 부동형 매크로들
A. 5. 3. 1 플로팅 포인트 표기 개념
A. 5. 3. 2 플로팅 포인트 파라미터들
A. 5. 3. 3 IEEE 플로팅 포인트
A. 5. 4 구조체 필드 옵셋 ( offset ) 측정
C++ 클래스 설계의 정석 (0) | 2007.11.13 |
---|---|
비트 연산으로 구현한 곱셈 (0) | 2007.10.23 |
[CElapsedTimer] 프로파일링에 유용하게 사용 (0) | 2007.10.23 |
C CPP 코드 검색 엔진 (0) | 2007.09.21 |
MFC 더블버퍼링 (0) | 2007.06.25 |
G. Bowden Wise 지음, 김 용묵 번역
여러분은 클래스를 직접 설계할 때, 속으로 여러 질문을 할 것입니다. 복사 생성자가 필요한가? 생성자 함수에 대체 인자(default arguments)가 필요한가? 클래스가 형변환이 되도록 만들 필요가 있는가? 같음 연산자를 정의할 필요가 있는가? 후위(postfix) 연산자는 어떻게 구현할까? 함수가 개체 자신을 되돌리게 할까, 개체의 참조자(reference)를 되돌리게 할까? 이 개체를 스트림으로는 어떤 식으로 내보낼까?
이런 질문은 노련한 C++ 프로그래머들도 곱씹고 고민하며, 때로는 이들조차 실수를 하여 비효율적인 코드를 작성하기도 합니다. 그래서 C++ 클래스를 결함 없이 설계하는 요령을 다룬 많은 책과 기사가 나갔습니다. 좀더 나은 C++ 프로그래머가 되려면 C++에서 있을 수 있는 미묘한 함정들을 배우고 이에 대한 안목을 키워야 할 것입니다.
이 다음부터 여러 회에 걸쳐, 나는 여러 C++전문가들의 지식과 약간의 내 경험에서 따온 C++ 활용 정석을 다루려고 합니다. 이 지침들이 여러분이 클래스를 스스로 구현하는 데 유용하게 쓰였으면 좋겠습니다. 이제 클래스의 특수한 함수부터 시작해 보겠습니다. 특수한 함수란 생성자, 소멸자, 대입 연산자 함수를 말합니다.
클래스는 데이터와 함수를 모두 구성원으로 가집니다. 그리고 함수는 대개, 사용자가 개체를 대상으로 어떤 동작을 하게 해주는 중개 역할을 합니다. 그런데 함수 중에는 또다른 특별한 의미를 지닌 게 있습니다. 개체가 살아있는 동안 특별한 임무를 맡기 때문입니다. 클래스에서 특별한 구성원 함수 구실을 하는 것은 생성자, 소멸자, 대입 연산자 함수입니다.
개체는 인스턴스를 선언함으로써 프로그램 안에서 생겨납니다. 그밖에 컴파일러가 내부적인 목적으로 개체를 생성하는 코드를 집어넣기도 합니다. 개체를 선언할 때는 이름을 배당합니다. 이 이름을 가진 개체는 개체가 정의된 영역(scope) 안에서만 사용 가능합니다. 영역이란 코드가 들어있는 블록을 뜻하며, 개체는 그 영역 안에서만 사용 가능하게 됩니다.
영역에는 세 가지 범위가 있습니다. 중괄호 블록 안이 영역인 지역 범위가 있고, 소스 파일 안에 있는 모든 곳(블록 바깥 역시 포함)이 영역이 되는 파일 범위가 있으며, 클래스의 구성원으로 선언되어 모든 구성원 함수가 영역이 되는 클래스 범위가 있습니다. 파일 범위에 드는 변수는 전역 변수라고 일컬어집니다.
개체의 생명 주기는 저장 등급(storage class)에 의해 결정됩니다.
C++은 개체가 쓰이기 전에 반드시 제대로 초기화가 될 수 있음을 언어 자체가 보증합니다. 개체는 생겨나는 때가 되면 그 개체에 딸린 메모리가 할당된 후 곧 초기화됩니다. C++언어는 개체를 초기화하는 일을 하는 특별한 함수를 제공하는데, 이것이 생성자 함수입니다.
생성자는 개체가 생겨날 때마다 호출됩니다. 개체는 지역 변수로 생길 수도 있고, 전역 변수로 생길 수도 있으며, new 연산자에 의해 생길 수도 있습니다. 생성자 함수를 명시적으로 호출해줄 수도 있고, 임시 개체가 생길 때도 개체가 생겨날 수 있습니다. 어떤 개체가 생성되면 그 개체의 일부인 개체들의 생성자도 제각기 호출됩니다.
이와 마찬가지로 개체가 생성 범위를 벗어나면 그 개체에 할당돼 있던 메모리는 사라지게 됩니다. C++언어는 개체가 소멸될 때 소멸자라고 하는 특수한 함수를 호출하여 소멸되는 개체가 고유한 마무리 작업--개체가 할당한 다른 메모리나 시스템 자원을 반환하는 일 같은--을 할 수 있게 해 줍니다.
C++은 특수한 역할을 맡는 또다른 특수한 함수를 제공합니다. 어떤 개체와 똑같은 복사본이 생겨야 할 때는 복사 생성자 함수가 호출됩니다. 그리고 개체이 어떤 값이 대입되면, 그 개체의 대입 연산자 함수가 호출되지요.
생성자 함수는 개체 자신에 할당된 메모리를 가공하여, 이것을 살아있는 개체가 저장된 메모리로 바꾸는 역할을 합니다. 생성자 함수에는 기본 생성자, 복사 생성자가 있으며, 그밖에도 여러 가지 인자를 받는 생성자가 있을 수 있습니다.
기본 생성자는 아무 인자도 받지 않는 생성자를 말합니다. 클래스 X에 대한 기본 생성자는 X::X()와 같은 형태를 취합니다. X::X(const int x=0)처럼 모든 인자에 대해 대체값을 가진 생성자 또한 기본 생성자가 됩니다. 아무 인자를 주지 않고도 호출할 수 있기 때문이지요.
기본 생성자는 개체가 생성자 함수에 아무런 인자를 주지 않아도 그 개체를 초기화해줍니다. 예를 들어 다음과 같은 선언은 문자열 s가 아무런 값이 없는 상태, 즉 빈 문자열로 초기화됨을 뜻하게 됩니다.
String s;
기본 생성자는 대개, 그 클래스의 형태를 띠면서 텅 빈 상태인 개체를 생성합니다. 예를 들어 복소수를 나타내는 클래스의 기본 생성자는 값이 0인 복소수를 만드는 역할을 하고, 연결리스트 클래스의 기본 생성자는 아무 내용이 없는 리스트가 되도록 자신을 초기화하지요.
정석 1.클래스에 기본 생성자를 만들어 두십시오. 그리고 대체 인자를 활용하여, 기본 생성자를 또 만드는 수고를 피하십시오.
클래스에서 생성자에 인자를 받도록 하는 경우가 자주 있습니다. 인자를 하나도 받지 않는 기본 생성자를 따로 만들기보다는, 대체 인자가 있는 생성자 함수를 만들어서 대체 인자가 기본 생성자 역할까지 하고, 대체 인자가 생성자 함수에 정보를 제공할 수 있게 하는 게 좋습니다. 이 기법은 코드를 더욱 간결하게 하고 코드의 재활용성을 높여줍니다.
예를 들어 클래스 String에 생성자가 String(), String(const char *str)과 같이 두 개가 있다면 이것을 String(const char *str=0)과 같이 하나로 줄일 수 있습니다.
기본 생성자는 하나밖에 없습니다. 그러므로 생성자 함수마다 모든 인자에 대체값을 집어넣지는 마십시오. 컴파일러가 대체 인자가 있는 생성자를 기본 생성자와 구분하지 못해 에러를 내게 됩니다.
기본 생성자를 가진 클래스를 만들기가 어려워 보일 때가 있습니다. 클래스가 만들어질 때 특정 구성원이 반드시 초기화되어야 그 클래스가 동작할 수 있는 경우 말입니다. 그렇지만 그 특정 자료를 나중에라도(이 개체가 생성되고 나서) 제공할 수 있는 방법을 열어놓는다면 그 클래스는 기본 생성자를 만들 수 있습니다. 예를 들어 여러분이 C++ 스트림 라이브러리를 써 봤다면, 출력 파일을 나타내는 개체를 파일 이름 없이 만들 수 있다는 사실을 알 것입니다.
ofstream out;출력하는 파일 이름은 나중에 open이라는 함수로 제공할 수 있습니다.
out.open("outfile");
템플릿을 만들 땐 조심하기 바랍니다. 코딩할 때 주의를 기울이지 않으면, 여러분의 템플릿은 기본 생성자가 없는 클래스는 받아들일 수 없게 될지도 모릅니다.
T element;위와 같은 선언문이 템플릿 함수에 있다면 기본 생성자가 있는 클래스만 이 템플릿 함수를 쓸 수 있지요.
정석 2.적당한 곳이라면 어느 구성원 함수에든지 대체 인자를 넣으십시오.
대체 인자는 생성자 함수에만 쓸 수 있는 것은 아닙니다. 어느 구성원 함수에나 쓸 수 있으므로, 적당한 곳이라면 어디에나 대체 인자를 두십시오.
복사 생성자는 개체를 복사할 때 호출되는 특별한 생성자입니다. 클래스 X의 복사 생성자는 X::X(const X&)라는 꼴을 가집니다. 아래와 같은 코드가 있을 때, s2는 s1이라는 개체를 자신으로 복사하는 복사 생성자를 호출함으로써 생성됩니다.
String s1("Hello, world!"); String s2 (s1);
복사 생성자는 우리가 모르게 컴파일러가 개체의 복사본이 필요할 때마다 자주 호출됩니다. 우리가 임시 개체라고 부르는 이러한 개체는 컴파일러가 필요할 때마다 생겨났다가 자기 임무를 다하면 소멸합니다. 이런 일이 가장 일반적으로 일어나는 때는 함수가 호출될 때입니다. 예를 들어 다음과 같이 String이라는 인자를 받는 함수가 있다고 칩시다.
void DisplayError (const String s);DisplayError()가 호출될 때마다 컴파일러는 String의 복사 생성자를 호출하여 인자로 전달할 임시 개체 s를 만듭니다. 그리고 그 임시 개체가 함수로 전달됩니다.
정석 3.클래스의 복사 생성자를 항상 만들어 두고, 컴파일러가 대신 생성하도록 하지 마십시오. 특히 클래스에 포인터로 동적 메모리를 할당하는 구성원 변수가 있다면 반드시 복사 생성자를 만들어야 합니다.
만약 복사 생성자를 두지 않는다면 컴파일러는 클래스에 대해 기본 복사 생성자를 만듭니다. 기본 생성자 함수는 클래스의 각 구성원의 값을 단순히 복사해 오는 일만 합니다. 이런 동작은 포인터를 쓰지 않는 클래스에나 적합합니다. 클래스를 설계할 때마다 복사 생성자를 꼬박꼬박 만들어 두는 습관을 들이는 것이 좋습니다.
생성자 함수는 여러분이 개체를 직접 생성할 때 외에도 컴파일러가 임시 개체를 만들 때 등을 포함해도 자주 호출됩니다. 그렇기 때문에 생성자는 최대한 간결하고 효율적이어야 합니다. 다음은 생성자 함수를 만들 때 고려해 볼만한 정석입니다.
정석 4. 생성자 함수에서 구성원 변수를 초기화할 때는 대입 연산보다는 자체 초기화 방법을 쓰십시오. 이렇게 하면 코드의 효율이 높아지고 부가적인 생성자 함수 호출을 막을 수 있게 됩니다.
_center과 _color라는 구성원을 가진 Shape라는 클래스가 있습니다. 구성원의 자료형은 전자는 Point라는 클래스고 후자는 int입니다. 생성자 함수를 만들 때, 여러분은 대입 연산자를 써서 구성원을 초기화해야겠다는 생각을 언뜻 할 것입니다.
Shape::Shape (const Point center, const int color)그런데 이렇게 하면 Point 클래스의 생성자가 또 호출되게 되고 코드의 수행 속도가 떨어집니다. 그 이유를 알아보기 위해 개체들이 어떻게 생성되는지 살펴봅시다. 개체가 생성되는 과정은 다음 두 가지로 나눠 볼 수 있습니다.
{
_center = center;
_color = color;
}
파생(derive) 클래스의 경우 부모 클래스의 것부터 시작하여 위의 두 과정이 수행됩니다.
Shape 클래스의 생성자에서 _center는 1단계 동안에 생성되며, 이때 Point의 기본 생성자가 호출됩니다. 그리고 2단계로 들어가서 생성자 함수가 대입 연산자 함수를 호출하면 비로소 _center의 값이 바뀝니다.
_center는 자신을 담고 있는 클래스의 생성자 함수가 실행되기 전에--나중에 생성자 함수가 _center의 값을 어떻게 바꾸든지간에-- 언제나 먼저 초기화된다는 점을 알아두십시오. 하지만 Shape 생성자 함수의 초기화 목록을 쓰면, _center를 어떤 생성자 함수로 초기화할지를 조절할 수 있습니다.
Shape::Shape (const Pointer center, const int color)초기화 목록에 _center(center)를 써 주면 컴파일러는 구성원 변수를 초기화할 때 _center의 기본 생성자가 아닌 복사 생성자를 쓰게 됩니다. _center는 구성원 변수 초기화 과정에서 이미 초기화가 끝났기 때문에, (위에서 1단계) 2단계인 클래스 생성자 함수 자체에서는 대입 연산이 필요하지 않은 것이지요.
: _center(center) , _color (color) { }
생성자를 만들 때, 구성원 데이터를 모두 이런 식으로 초기화하려고 하세요. 대개 이런 식으로 모두 초기화가 가능하기 때문에, 정작 생성자 함수의 몸체에는 아무 코드도 필요하지 않을 수도 있습니다. 이렇게 하면 코드는 더욱 읽기 쉽고 유지하기도 쉬워집니다.
사실 int형과 같은 내장된 자료형에는 생성자 함수가 없습니다. 그렇기 때문에 기본 자료형 변수는 대입문을 쓰든 구성원 초기화를 쓰든 차이는 없습니다. 하지만 모든 구성원 변수를 구성원 초기화라는 같은 방법으로 값을 할당하면 코드가 더욱 이해하거나 유지하기 쉬워집니다.
정석 5.구성원 변수들이 어떤 순서로 초기화되는지를 주의깊게 살피십시오. d2라는 변수를 초기화하기 위해 d1을 사용한다면 실제로 d1이 d2보다 먼저 초기화되는지 확인하십시오.
StringHandle이라는 클래스에서 구성원 변수가 다음과 같은 순서대로 선언되었다고 합시다.
String _str; int _handle;C++ 언어에서는 클래스의 구성원은 클래스에서 구성원이 나열됐던 순서대로 초기화되지 생성자 함수의 개체 초기화 목록에 있는 순서대로 초기화되는 게 아닙니다.(그러므로 _str은 _handle보다 먼저 초기화됩니다.)
다음과 같은 생성자는 정상적으로 컴파일되지만 실행중 에러를 발생시킬 것입니다.
StringHandle::StringHandle (const int h)
: _handle (h),_str(QueryHandle(_handle)) { }
_str이 클래스에서 먼저 나타났기 때문에 _str은 _handle보다 먼저 선언됩니다. 불행히도 _str은 생성 과정에서 생성되지 않은 _handle을 쓰게 되는 것입니다. 클래스에서 _handle을 _str 앞에 두면 이런 문제가 생기지 않습니다.
이 문제를 해결하려면 클래스 구성원 대신 생성자에 딸려 들어온 인자를 쓰면 됩니다.
StringHandle::StringHandle (const int h)
: _handle(h),_str (QueryStringHandle(h)) { }
초기화 목록에 있는 순서대로 초기화가 되지 않는 이유는 나중에 컴파일러가 개체들을 생성되었던 반대 순서로 소멸시킬 수 있게 하기 위해서입니다. 한 소스 파일에 클래스의 생성자와 소멸자 함수가 같이 있으리라는 보장은 없습니다. 그러나 두 함수 클래스 선언부는 같은 것을 참조하기 때문에 개체가 초기화된 순서를 알아내는 데는 모호함이 발생하지 않습니다.
정석 6.참조자 구성원과 const 개체는 생성자의 초기화 문법대로 초기화되어야 합니다.
연구실에서 구해낸 센서 자료를 관리하는 SensorData라는 클래스가 있습니다. 이 클래스는 여러 다른 센서 개체들과 더불어 자료 처리를 할 수 있기 때문에 생성자에서는 기존하는 센서의 참조자를 인자로 받습니다. 모든 센서에게 개방돼 있는 전역 데이터는 상수형 자료로 전달되지요.
참조자는 반드시 기존하는 개체를 가리키고 있어야 하기 때문에, 시작 부분에서 애초부터, 자료형이 같은 다른 개체를 가리키게끔 초기화되어야 합니다. 참조자는 일생 주기를 통틀어 대입 연산을 한 번만 할 수 있고, 상수형 개체도 초기화된 뒤부터는 값을 대입할 수 없습니다. 따라서 이 둘은 초기화 목록에서 초기화되어야 합니다. 다음은 SensorData의 생성자 함수로 가능한 모습입니다.
SensorData::SensorData (Sensor& aSensor const GlobalData data)
: _rSensor(aSensor), _global (data) { }
정석 7.생성자 함수를 최대한 간결하게 만드십시오. 함수 오버헤드나 개체가 생성되는 과정에서 발생할 수 있는 에러를 줄여야 하기 때문입니다.
생성자는 개체가 생성될 때마다 호출됩니다. 여러분이 모르는 사이에 이런 일은 컴파일러가 임시 개체를 만들어낼 때를 포함해 대단히 자주 일어납니다. 개체를 초기화하는 데 긴 시간이 걸려서는 안됩니다. 개체 생성시에는 꼭 필요한 일만 하십시오. 생성 과정을 줄이면 에러의 발생 가능성도 줄어들게 됩니다.
정석 8. 상속받은 클래스라면, 생성자 안에서 부모 클래스의 생성자 함수를 반드시 호출하도록 하세요.
생성자 함수(소멸자와 대입 연산자 함수도 포함하여)는 상속이 되지 않습니다. 클래스에서 생성자 함수가 호출되기 전, 그러니까 클래스 구성원을 초기화할 때는, 구성원 하나하나에 대해서 그 클래스에 해당하는 생성자 함수가 호출되고 본 클래스의 기반 클래스의 생성자도 호출됩니다. 4번 정석에서 보았듯이, 컴파일러는 초기화 목록을 보고서 어떤 생성자 함수를 호출할 지 결정합니다. 만약 프로그래머가 파생 클래스 생성자의 초기화 목록에서 기반 클래스 생성자를 호출하지 않으면, 컴파일러는 기반 클래스의 기본 생성자를 호출하는 코드를 자동으로 삽입합니다. 하지만 만약 기반 클래스가 기본 생성자 함수를 갖추고 있지 않다면 컴파일러는 에러를 낼 것입니다.
파생 클래스의 초기화 목록에서는 구성원 변수와 기반 클래스를 초기화해 주는 습관을 들이는 것이 좋습니다. 나는 보통 파생 클래스 자신의 구성원을 초기화하기 전에 기반 클래스의 생성자를 먼저 호출하곤 합니다.
Shape라는 클래스로부터 Triangle이란 클래스를 파생한다고 합시다.
class Triangle : public ShapeTriangle의 생성자에서는, Triangle의 초기화 목록에서 Shape의 생성자를 아래와 같이 호출할 수 있습니다.
{
public:
Triangle (const Point center,
const int color,
const Point v1,
const Point v2,
const Point v3);
private:
Point _v1, _v2, _v3;
};
Triangle::Triangle (const Point center,
const int color,
const Point v1,
const Point v2,
const Point v3)
: Shape (center, color) , _v1(v1), _v2(v2), _v3(v3) { }
프로그램의 실행 위치가 어떤 개체의 생명 영역을 벗어나면 그 개체는 소멸하고, 그 개체가 차지하던 메모리는 해제됩니다. 개체가 소멸하기 직전에 그 개체의 소멸자 함수가 호출되며, 개체는 소멸자 함수 안에서 각종 마무리 작업을 할 수 있습니다. 클래스 X의 소멸자 함수는 X::~X()라는 꼴을 갖습니다.
정석 9.소멸자 함수는 이 개체가 생성됐던 순간뿐만 아니라 모든 시점에서 할당했던 모든 자원(메모리 등)을 해제해야 합니다.
메모리나 각종 자원의 할당은 개체가 생성되는 첫 시점에서 행해지는 게 보통입니다. 그러나 때에 따라서는, 그 개체로 어떤 동작이 가해지면 개체가 거기에 반응하는 과정에서 자원이 추가로 쓰일 수도 있습니다. 소멸자 함수를 코딩할 때는, 비단 생산될 때뿐만 아니라 이 개체가 살아 생전에 사용한 모든 자원을 반환· 해제하도록 하십시오.
정석 10.클래스에 반드시 소멸자를 갖추십시오. 컴파일러로 하여금 소멸자를 알아서 만들도록 하지 마십시오.
클래스가 동작하는 모습을 완전히 파악하고 있는 게 좋습니다. 컴파일러가 어떤 코드도 임의로 만들게 하지 마세요.
정석 11.만약 어떤 클래스가 상속 받아 상속받는 클래스의 기반 클래스로 쓰일 여지가 있다면 소멸자 함수를 가상 함수로 만드십시오. 또한 클래스에 가상 함수가 적어도 하나 이상 있다면 소멸자 함수 역시 가상 함수로 만들어야 합니다.
자동차 클래스에 대한 상속 관계를 생각해 봅시다. 상속 관계에 있는 모든 클래스가 Auto라는 클래스에서 상속을 받았습니다. Ford라는 개체가 다음과 같이 생성되었다면 생성자 함수는
Ford f;생성자 함수는 기반 클래스의 것부터 파생 클래스의 것 순서로 호출됩니다. 그리고 개체가 소멸할 때 소멸자 함수는 그의 역순으로, 즉 파생 클래스의 것에서 기반 클래스 것 순서로 호출됩니다. 그런데 개체를 그 개체의 기반 클래스 포인터로 생성하고 소멸할 때 문제가 생깁니다.
Auto* car1 = new Ford; // ... delete car1;Auto의 소멸자 함수가 가상함수가 아니기 때문에 delete문은 Ford::~Ford()가 아닌 Auto::~Auto()를 호출합니다. Auto::~Auto()를 가상함수로 선언하면 이 문제가 해결됩니다. 소멸자 함수를 가상함수로 만들면, delete에 기반 함수의 포인터가 넘겨지더라도, 그 포인터가 가리키는 개체의 실제 자료형에 맞는 소멸자가 호출되게 됩니다. 올바른 생성자 함수가 호출되고 나면, 기반 클래스에 대한 소멸자 함수는 위에서 설명한 바와 같은 규칙대로 호출되지요.
다음은 생성자와 소멸자 함수 작성에 대해 적용할 수 있는 몇 가지 정석입니다.
정석 12.생성자와 소멸자 함수에서 가상함수를 호출하는 일을 삼가십시오.
생성자나 소멸자 함수 안에서도 다른 함수를 호출할 수 있습니다. 그런데 여기서 가상함수를 호출하면, 자신의 기반 클래스에서 정의된 가상함수, 혹은 자신이 새로 추가한 가상 함수는 적절히 호출되지만, 자신으로부터 상속한 새로운 파생 클래스가 새로 오버라이딩한 가상 함수는 절대 호출되지 않습니다. 이는 개체의 생성 과정에서, 아직 생성되지 않은 파생 클래스 개체를 기반 클래스가 건드리지 않기 위해서입니다. (기반 클래스의 생성자가 파생 클래스의 생성자보다 먼저 호출되지요.)
그리고 생성되거나 파괴되고 있는 개체의 순수 가상 함수를 직접 혹은 간접적으로 호출했을 때의 결과는 정해져 있지 않습니다. 올바른 결과가 나올 때란, 순수 가상 함수라도 특정 클래스의 것을 명시적으로 호출했을 때 뿐입니다. 예를 들어 A라는 클래스에 순수 가상 함수 p()가 있을 때, A::p()는 명시적인 호출이라 할 수 있습니다. 이 방식이 통하는 이유는, 명시적 호출은 가상함수 호출 메카니즘을 쓰지 않기 때문이지요.
개체에 대입 연산을 시도하면 그 클래스의 대입 연산자가 호출됩니다. 클래스 X에 대한 대입 연산자는 다음 두 가지 형태 중 하나가 됩니다.
X& X::operator= (const X& x); const X& X::operator= (const X& x);
정석 13.대입 연산자는 반드시 구성원 함수여야 하며, 친구(friend)가 될 수 없습니다.
어떤 연산자는 구성원 함수로 표현되는게 가장 좋고, 또 어떤 건 친구 함수로 표현되는 게 가장 좋습니다. 하지만 대입 연산자 함수는 선택의 여지가 없이 구성원 함수여야 합니다. 형태는 위의 두 가지 꼴 중 하나가 될 수 있죠.
정석 14.대입을 받은 개체의 참조자를 되돌림값으로 주도록 하세요.
참조자란 실제로 존재하는 개체를 비춰 보이는 거울과 같습니다. 참조자에 어떤 동작이 행해지면 참조자가 가리키는 실제 개체 역시 영향을 받습니다. 참조자란 간단히 말해 개체의 또다른 이름(별명)이라는 사실을 떠올리세요. (예를 들어 printf("%p %p \n",&,&)는 0xbffff870 0xbffff870를 출력합니다.).
참조자를 되돌리는 게 좋은 이유는 다음과 같습니다.
Complex w, x, y, z; w = x = y = z = Complex(0,0);
대입이 연달아 되게 하기 위해서는 대입 연산자 함수의 인자와 되돌림의 자료형이 모두 같아야 합니다. 그래서 자료형이 같은 참조자를 되돌리고 전달하는 것입니다. 보통 return *this; 라고 하지요. 이렇게 하면 대입 연산의 결과가 다음 대입 연산 함수의 인자로 쓰일 수 있게 됩니다.
참조자를 되돌리는 것은 개체 자신을 직접 되돌리는 것보다 효율적입니다. 참조자가 넘겨지면 기본적인 함수 호출 방식인 "값으로 호출" (call-by-value) 메카니즘은 무시됩니다. 인자의 복사본을 받는 대신 함수는 실제 인자의 l-value를 받게 됩니다. 아무 개체도 복사되지 않으며, 참조자는 개체 전체보다 스택도 적게 차지합니다. 이와 마찬가지로 참조자가 되돌려졌을 때에도 개체 자체가 아닌 l-value가 되돌려집니다.
정석 15. 반드시 함수의 참조자는 상수형(const)으로 해서 되돌리십시오. 일반 참조자를 되돌리면 엉뚱한 결과가 생길 수 있습니다.
참조자를 되돌리는 것은 l-value(값을 대입할 수 있는 메모리 주소를 갖는 개체)를 되돌리는 것과 같습니다. 이것은 개체에 대한 포인터를 얻는 것과 비슷합니다. 그러므로 개체 참조자를 통해 개체를 변경할 수 있는 것입니다.
여기에 예를 들어 보겠습니다. 대입 연산은 오른쪽에서 왼쪽으로 진행되지만 아래에서는 괄호를 써서 우선 순위를 바꿨습니다.
Complex x, y, z; (x = y) = z;위의 문장은 다음과 같습니다.
(x.operator=(y)).operator=(z);무슨 뜻인가요? 먼저 y의 값이 x에 대입됩니다. 하지만 =연산자가 x의 참조자를 되돌리기 때문에 x에는 대입 연산이 한번 더 일어납니다. z의 값이 대입되지요. 하지만 =가 상수형 참조자를 되돌리면 그 참조자가 참조하는 개체가 또다시 수정되는 일을 막을 수 있습니다. (상수 개체를 대상으로 대입을 할 수 없다고 컴파일할 때 에러가 납니다.) 개체가 예상치 못한 일로 변경되는 것을 막으려면 상수형 참조자를 되돌리도록 하십시오.
다만, 상수형이 아닌 일반 참조자를 되돌려야 하는 경우가 딱 하나 있습니다. 첨자 연산자는 일반 참조자를 되돌려야 색인 번호가 매겨진 개체에 다른 값을 대입할 수 있기 때문입니다. 벡터의 좌표를 바꾸는 코드를 작성하고 싶다고 가정해 봅시다.
Vector v; v[1] = -2.0;이 코드는 다음과 같은 꼴이 됩니다.
(v.operator[](1)).operator= (-2.0);이 코드가 동작하도록 하게 위해서는 첨자 연산자 함수가 일반 참조자를 되돌리게 하면 됩니다.
float& Vector::operator[] (const int) { return _data[i]; }되롤림값은 원소의 l-value(대입이 가능한 개체)이므로 대입 연산의 대상이 될 수 있는 것입니다.
정석 16.자기를 자기 자신에게 대입하지는 않는지 검사하십시오.
개체가 자기 자신으로 대입될 경우 불미스런 일이 생길 수 있습니다.
X x; x = x;클래스 X가 포인터 데이터 구성원을 가지고 있어 개체가 만들어질 때 메모리가 동적으로 할당된다고 생각해 봅시다. 대입 작업은 언제나 존재하는 개체의 변경을 수반합니다. 그러므로 이미 있던 동적 할당 데이터는 새 값이 들어오기 전에 먼저 사라집니다. 자기 자신을 대입하는지 검사하지 않고 무작정 대입한다면 대입 연산은 자기 자신의 포인터 데이터를 지우고 이전에 지워졌던 값을 다시 대입하려는 시도가 되고 마는 것입니다.
자기 자신으로 대입하는 게 아닌지 아래 예처럼 꼭 검사하십시오.
const X& X::operator= (const X& rhs)
{
if ( &rhs != this )
{ //각 구성원에 값을 할당합니다. }
return *this;
}
정석 17.대입연산자는 상속 받지 않는 유일한 연산자입니다. 기반 클래스의 구성원도 할당 과정에서 값을 바꾸도록 하십시오.
파생 클래스의 대입 연산자 함수는 자기가 갖고 있는 구성원 뿐만 아니라 기반 클래스의 구성원까지 반드시 고쳐야 합니다. 그런데 기반 클래스의 구성원이 private로 선언돼 있어서 파생 클래스에서 그걸 건드릴 수 없는 경우가 있습니다. 그럴 때는 기반 클래스의 대입 연산자 함수를 파생 클래스의 대입 연산자 함수에서 직접 호출해야 합니다.
Base라는 클래스가 Derived의 기반 클래스라고 칩시다. Derived형 개체에 대입이 일어나면, Base 클래스도 이 때 대입 연산이 행해져야 클래스 전체의 내용이 바뀝니다. 그래서 Derived 클래스의 대입 연산자 함수는 아래와 같이, 기반 클래스의 대입 연산자를 직접 호출할 수도 있고, (d가 피연산자라고 합시다.)
Base::operator= (d);실질적인 연산구문으로 이 작업을 할 수도 있습니다.
((Base &) *this) = d;언뜻 보기 이상한 이 구문은, *this를 기반 클래스의 참조자로 바꾸고, 그 결과에 대해서 대입 연산을 행합니다. 그냥 Base가 아니라, 꼭 Base의 참조자로 형변환을 해야 합니다. 참조자 대신 Base로 *this를 형변환하면, Base 클래스의 복사 생성자가 호출되고, 연산 결과는 this 자신이 아니라 새로운 임시 개체가 됩니다. 따라서 대입은 *this가 아닌 임시 개체로 행해지고, *this는 값이 바뀌지 않게 되죠.
정석 18.클래스에 자체적인 대입 연산자 함수를 꼭 만들어 두십시오. 컴파일러가 기본 함수를 만들도록 하지 마세요.
프로그래머가 대입 연산자 함수를 만들지 않으면 컴파일러는 기본 함수(모든 구성원을 똑같이 대입하는 일만 함)를 알아서 만듭니다. 여러분의 클래스가 어떻게 돌아가는지 완전히 파악하고 있기 위해서는 항상 대입 연산자 함수를 만들도록 하십시오.
정석 19.클래스에 포인터 데이터(동적 메모리 할당)가 있으면 대입 연산자를꼭만들어야 합니다. 그리고 대입 연산자를 코딩한다면 복사 생성자도 만들어야 합니다.
컴파일러가 기본적으로 만든 대입 연산자 함수는 각 구성원을 차례대로 복사하는 작업만 합니다. 그러므로 구성원 중에 포인터가 있다면 이를 그대로 둬서는 안되는 경우가 절대 다수입니다. 두 개 이상의 개체가 똑같이 한 메모리 영역을 가리키고 있는데, 한 개체가 그 메모리 영역을 해제한다면, 다른 개체가 그 메모리를 건드릴 때 런타임 에러가 나겠지요.
정석 20.대입 연산자와 복사 생성자가 공통적으로 수행하는 작업을 따로 함수로 만들어 이들이 그 함수를 호출하게 하십시오. 이렇게 하면 코드의 중복이 사라져 보기가 더 깔끔해집니다.
문자열 클래스는 복사 생성이나 대입 연산 과정에서 문자열을 복사한다는 공통된 작업을 해야 합니다. 그래서 겹치는 작업을 다음과 같이 비공개 구성원 함수에다 두면 복사 생성자 함수와 대입 연산자 함수가 그 코드를 중복 없이 공유할 수 있을 것입니다.
void CopyString(const char* ptr);
다음에 우리는 클래스에서 연산자 함수를 구현하는 데에 도움이 되는 내용을 살펴볼 것입니다. 독자 여러분은 그때까지, 자신이 이미 만들어놓은 클래스를 살펴 보시기 바랍니다. 그리고는 생성자, 소멸자, 대입 연산자 함수가 잘 동작하고 있는지 살펴보세요. 여기 실린 정석은 여러분이 더 효율적인 코드를 작성하는 데 도움이 될 것입니다.
저작권 1995G. Bowden Wise 지음
C++에 대해 더 자세히 알고 싶다면, 본 시리즈의차례,다음 내용,이전 내용을 살펴보십시오.
고치고 더함: 2000년 2월 4일 금요일, 11:17:16
이 사이트의 원위치: www.acm.org/crossroads/xrds1-4/ovp.html
부록 A 라이브러리에 있는 C 언어 기능들 (0) | 2008.01.22 |
---|---|
비트 연산으로 구현한 곱셈 (0) | 2007.10.23 |
[CElapsedTimer] 프로파일링에 유용하게 사용 (0) | 2007.10.23 |
C CPP 코드 검색 엔진 (0) | 2007.09.21 |
MFC 더블버퍼링 (0) | 2007.06.25 |
1. tcpdump |
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
| tcpdump는 네트워크 인테페이스 상에 있는 패킷중에서 명시된 expression에 일치하는 것만을 선별하여 그 패킷의 헤더 정보를 보여주는 도구 이다. 참고로 시모무라 소토무가 케빈 미트닉을 잡을 때 사용했던 프로그램으로도 유명하다. |
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
2.소스 가져오기 |
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
3. 설치 |
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
4. 사용법 |
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
| tcpdump [ -adeflnNOpqStvx ] [ -c count ] [ -F file ] [ -i interface ] [ -r file ] [ -s snaplen ] [ -T type ] [ -w file ] [ expression ] <Option>
<expression> expression은 패킷을 선택적으로 캡처하기 위한 조건을 적는 부분이다. expression이 없다면 네트워크상의 모든 패킷을 캡처하여 보여 주며expression이 정의 되어있다면 이 expression을 참으로 하는 패킷만을 선별하여 보여준다. expression은 하나이상이 primitives로 구성되며, primitives는 일반적으로qualifer을 앞에 내세운 id(이름이나 숫자)로 구성된다. [Qualifier]
[primitives]
간단한 expression들을 다음의 관계연산자를 통하여 묶음으로서 더욱 복잡한 filter expression을 구성할 수 있다. Nagation : "!", "not"
|
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
5. tcp_dump의 사용 |
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
Example 1) shell> tcpdump ip proto '\icmp && host apple && !(sun || moon)' Example 2) shell> tcpdump src host apple and not dst host banana Example 3) shell> tcpdump 'icmp[0] != 8 and icmp[0] != 0" Example 4) shell> tcpdump 'tcp[13] & 3 != 0 and not net 123.45.67.89'
**********************************8 위의 내용은 초고속연구망실무자 협의회에서 발췌한 글이며.. 저작권 문제가 있을경우 바로 삭제 하겠습니다. |
한글 putty 0.58 (1) | 2008.11.04 |
---|---|
TCPDUMP 사용법 (0) | 2007.10.30 |
블루스크린 ErrorCode Define 값입니다. (0) | 2007.09.21 |
HP-UX 쉘 명령어 총정리 (0) | 2007.08.22 |
gcc와 make 강좌 (0) | 2007.05.01 |
http://security.kaist.ac.kr/docs/tcpdump.html
Date : January 1999
Table of Contents
이 문서는 네트워크의 패킷들을 잡아내는 Tcpdump라는 프로그램에 대해서 설명하고 있다. 프로그램의 특성상, 네트워크에 관련된 많은 용어들과 특히 TCP/IP에 대한 내용 이 많이 나온다. 따라서 보다 잘 이해하기 위해서는 네트워크 설비 자료나, 네트워크 프로그래밍 가이드, 혹은 네트워크 프로토콜(TCP/IP)에 관련된 책들을 참조하는 것이 좋을 것이다.
Tcpdump는 주어진 조건식을 만족하는 네트워크 인터페이스를 거치는 패킷들의 헤더들 을 출력해 주는 프로그램이다. 프로그램의 특성상, 네트워크 인터페이스를 아주 심도 있게 사용하기 때문에, 실행하는 사람은 반드시 네트워크 인터페이스에 대한 읽기 권 한이 있어야만 한다.
OS dependent)
위에서 말하는 읽기 권한을 가지고 있어야 하는 파일, 혹은 Tcpdump의 퍼미션 이다.
Tcpdump는ftp://ftp.ee.lbl.gov/tcpdump.tar.Z에서 최신 버전을 구할 수 있다. 유명 한 프로그램이기 때문에, 시스템소프트웨어를 패키지형태로 제공해 주는 OS들의 경우 Vendor에서 패키징된(컴파일된) 버전으로도 구할 수 있을 것이다.
Tcpdump는 libpcap(Protocol Capture Library)라는 것을 사용한다. 이 라이브러리는 Platform에 상관없이 동일한 방법으로 사용자 레벨에서 패킷들을 캡춰할 수 있게 해 준다. 따라서 이 라이브러리가 없다면,ftp://ftp.ee.lbl.gov/libpcap.tar.Z에서 구하 여 설치하도록 한다.
ANSI C 컴파일러는 아마 대부분의 시스템에서 구비하고 있을 것이다. 만약 없다면ftp://prep.ai.mit.edu/pub/gnu/gcc.tar.gz를 받아서 설치하기 바란다.
libpcap라이브러리가 완벽하게 설치되었다는 가정하에서 다음의 절차에 따라 설치를 시작한다.
# vi Makefile.in# ./configure# make# make install# make install-manOS dependent)
-DETHER_HEADER_HAS_EA=1 -DETHER_ARP_HAS_EA=1
옵션의 제일 마지막인 조건식은 어떤 패킷들을 출력할지를 선택하는데 쓰인다. 조건식이 주어지지 않는 다면 모든 패킷들이 그 대상이 될 것이다. 일단 주어지면, 아무리 패킷들이 많아도 조 건식에 부합하는 패킷만을 출력한다.
조건식들은 하나 또는 몇 개의 primitive들로 구성되어 있다. primitive들은 보통 하나 혹은 몇개의 qualifier들 다음에 오는 하나의 값으로 이루어진다. Qualifier들은 모두 3 종류이며 다음과 같다.
이 밖에도 위의 패턴을 따르지 않는 Primitive들이 존재한다(gateway, broadcst, less, greater, 산술식).
좀 더 정교한 조건식들을 사용하려면, 'and(&&)', 'or(||)', 'not(!)'들을 사용하여 여러 primitive들을 연결하면 된다. 같은 표현들은 생략될 수 있다.
사용 가능한 Primitive들
이 조건식을 사용하기 위해서는 먼저 해당하는 Protocol(proto)의 헤더에 관련된 것들을 자세히 알아야만 한다. proto에는 대상이 될 프로토콜을 지정한다. expr에는 프로토콜 헤더의 처음부터의 Byte Offset을 지정하는 식이 들어가게 된다. Size는 Option이며 지정이 안 되어 있을 경우에는 자동으로 1byte를 지칭한다. 따라서 이 조건식을 사용하게 되면 헤더에 포함된 정보를 Bitmask를 사용하여 직 접 원하는 패킷인지를 가려낼 수 있기 때문에, 보다 정밀한 사용이 가능하게 된다.
TCPDUMP는 여러모로 좋은 툴이다. libpcap을 거의 100% 활용한 프로그램의 예이며, 실제로 많은 툴들이 TCPDUMP와 병행하여 돌아가거나, TCPDUMP를 기반으로 제작되었다. TCPDUMP의 막강한 packet filter는 현재 로컬 네트워크 상에서 날아다니고 있는 특정한 패킷들을 실시간으로 기록해 줄 수 있으며, 이를 이용하여 네트워크에서 벌어지는 일들을 네트워크 관리자가 원하는 대로 뽑아 볼 수 있게 해 준다. 또한, 시스템 관리자들에게는 로컬 유저의 외부로의 커넥션들을 감시하고, 또 특정 침입자가 침투 경로로 자주 이용하는 호스트, 혹은 원하지 않는 호스트로부터의 커넥션을 실시간으로 감시할 수 있게 해 준다. libpcap을 이용하여 비슷한 툴을 제작하고자 하는 사람들에게도 TCPDUMP는 가장 훌륭한 예제가 될 것이다.
한글 putty 0.58 (1) | 2008.11.04 |
---|---|
TCPDUMP 요약글입니다. (0) | 2007.10.30 |
블루스크린 ErrorCode Define 값입니다. (0) | 2007.09.21 |
HP-UX 쉘 명령어 총정리 (0) | 2007.08.22 |
gcc와 make 강좌 (0) | 2007.05.01 |
OpenSSL은 유닉스 / 윈도우즈 환경하에서 보안과 관련된 여러가지 기능을 제공하는 라이브러리로서
OpenSSL의 자세한 설명은http://www.openssl.org웹사이트에 등록되어 있습니다.
그리고, 첨부된 파일은OpenSSL을 Visual Studio에 어떻게 설치하고 사용하는지에 대한 설명을 포함하고 있는 문서입니다.
OpenSSL 라이브러리 설정이 약간 까다로운 편이라서 나름대로 최대한 그림을 많이 넣고 쉽게 설명하려고 노력했습니다 ^_^/
MFC 유용한 팁 (0) | 2007.04.18 |
---|---|
VC++ 6.0 Command Line Compile (0) | 2007.01.30 |
VC++단축키정리 (0) | 2006.07.28 |
[펌] 기본 WINAPI 소스 (0) | 2004.11.01 |
[펌] 윈도생성모드 / 최대화, 최소화, 일반, 숨김 (0) | 2004.11.01 |
000 싸이트 자바스크립트 유틸 메소드 모음 (0) | 2008.03.04 |
---|---|
Dialog Window 띄우면서, 작업표시줄에서 감추기 (0) | 2007.10.23 |
중복 실행 방지. (0) | 2007.10.23 |
[강좌] 기초적인 압축 알고리즘 하이텔 퍼옴.. (0) | 2007.10.23 |
class 들의 단계적 참조 관계 (0) | 2007.10.12 |