Turbo-C
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
터보-C 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
Lua 게시판
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C/C++ 강좌/문서
[38] (09) Stream & File in C++ (2)
남병철.레조 [lezo] 58418 읽음    2009-06-21 12:21


[목차]

(1) Stream Class & Error
(2) File I/O (by stream)
(3) File I/O (by member function)
(4) Memory for stream object
(5) Print and etc.










(3) File I/O (by member function)

    멤버함수를 이용하여 파일 입출력을 한다.
 
    - 자체를 읽고 쓰는 객체
    * 각 객체가 그 자체를 파일에 읽고 쓸 책임이 있는 평범한 구성원 함수를 사용하는 Class.
    * read(), write() 구성원 함수에서 읽고 쓸 객체의 주소는 this이며, 그 크기는
      sizeof( *this ) 이다.
      (this는 메모리 상에서 현재 객체의 주소를 알려주므로 read(), write() 구성원
       함수 사용시에 각 객체의 메모리상 위치 및 크기를 채크할 수 있다.)
//---------------------------------------------------------------------------
// 작성일 : 2002.06.07
// 제  목 : 멤버 함수를 이용한 파일 입출력 (자체를 읽고 쓰는 객체)
// 작성자 : 남병철
//---------------------------------------------------------------------------

#include 

//---------------------------------------------------------------------------

class person
{
protected:
	char name[40];
	int age;

public:
	// 사람의 데이터 입력
	void getData( void )
	{
		cout << "\n   Enter name : ";
		cin >> name;
		cout << "   Enter age : ";
		cin >> age;
	}

	// 사람의 데이터를 화면에 출력
	void showData( void )
	{
		cout << "\n   Name : " << name;
		cout << "\n   Age : " << age;
	}

	// 사람 수 pn 만큼을 파일에서 읽음	
	void diskIn( int );

	// 파일 끝에 입력한 사람을 써넣음
	void diskOut();

	// 파일 내에 입력된 사람 수를 리턴
	static int diskCount();
};

//---------------------------------------------------------------------------

// 사람 수 pn 만큼을 파일에서 읽음	
void person::diskIn( int pn )
{
	ifstream infile;
	infile.open( "PERSON.DAT", ios::binary );
	infile.seekg( pn*sizeof( person ) );
	infile.read( (char*)this, sizeof( *this ) );
}

// 파일 끝에 입력한 사람을 써넣음
void person::diskOut()
{
	ofstream outfile;

	outfile.open( "PERSON.DAT", ios::app | ios::binary );
	outfile.write( (char*)this, sizeof( *this ) );
}

// 파일 내에 입력된 사람 수를 리턴
int person::diskCount()
{
	ifstream infile;
	infile.open( "PERSON.DAT", ios::binary );
	infile.seekg( 0, ios::end );

	return (int)infile.tellg() / sizeof( person );
}

//---------------------------------------------------------------------------

void main( void )
{
	person p;
	char ch;

	// 사람 데이터를 받아 디스크에 써 넣는다.
	do
	{
		cout << "\nEnter data for person : ";
		p.getData();
		p.diskOut();
		cout << "Do another (y/n)? ";
		cin >> ch;
	}
	while (ch == 'y' );

	// 파일 내의 사람 수를 읽어내어 모두 출력한다.
	int n = person::diskCount();
	cout << "\nThere are " << n << " persons in file";
	for( int j = 0; j < n; j++ )
	{
		cout << "\nPerson #" << ( j + 1 );
		p.diskIn( j );
		p.showData();
	}
}

//---------------------------------------------------------------------------
- 자체를 읽고 쓰는 Class 메모리에 객체가 많이 있을때 구성원 함수가 파일을 열고 한 객체를 쓰고 닫는 방식은 너무 비효율적이다.( 객체가 많으면 많을 수록 ) 즉, 파일을 한 번 열고, 모든 객체를 쓴 다음, 닫는것이 훨씬 더 빠르다. 이러한 동작을할 수 있는 것은 static 함수와 static 변수이다. 각 객체가 만들어질때 그 포인터가 static 배열에 기록되고 static 함수는 그 배열들이 갖는 개개의 객체의 포인터를 이용하여 한번에 모든 객체를 파일에 쓸 수 있다. 여러 클래스가 어떤 기초 클래스에서 유도될 때 일어난다. 즉, 유도 객체의 크기가 서로 다를때 어떻게 파일에 쓰는지 살펴보자. 가상함수를 사용하면 각 객체를 배열에 넣어 아래와같이 객체의 가상함수를 부를 수 있다. arrap[j]->putdata(); 하지만 putdata()에서 가상객체 포인터 배열을 파일에 쓰려고 할때 문제가 발생한다. out.write( (char*)arrap[j], sizeof(*arrap[j]) ); sizeof()는 포인터 형의 크기를 리턴할 것이다. 즉, 기초 Class의 크기를 리턴할 것이다. 하지만 필요한 크기는 배열 객체 포인터의 타입 크기가 아니라 그 포인터가 가리키는 객체의 크기이다. 이를 해결하기위해 typeid()를 응용한다. //------------------------------------------------------------------------------ // 객체를 메모리에 ㅤㅆㅓㅅ다가 지우는 코드 테스트 필요(아래는 파일에 적용한것임) //------------------------------------------------------------------------------ * char* 메모리에서 데이타를 객체로 읽어들일때 - 아래 예제에서 파일에서 읽어들이는 부분과 비슷하다. 하지만 이런식으로 읽어들일 수는 없다.
      char someArray[MAX];
      aClass* aPtr_to_obj;
      aPtr_to_Obj = (aClass*)someArray; // 안된다!
char의 배열은 비록 객체의 데이터를 포함하고 있긴 하지만 객체는 아니다. 배열 또는 배열 포인터를 마치 객체를 대입하듯 사용하려면 직접 객체를 만들어서 대입해야한다. 객체를 만드는 유효한 방법은 두 가지뿐이다. 1. 컴파일 시간의 명시적 정의(static binding)
      aClass anObj;
2. 실행 시간에 new를 통해 만든 후 객체의 그 위치를 포인터에 대입
      aPtr_to_Obj = new aClass;
객체는, 그 안에 데이터를 가진 메모리 영역보다 많은 의미를 가지고 있다. 즉, 구성원 함수의 집합이기도 하며, 그 중의 일부(기본 생성자 등)는 프로그래머에게 보이지않는다. //------------------------------------------------------------------------------
//---------------------------------------------------------------------------
// 작성일 : 2002.06.08
// 제  목 : 멤버 함수를 이용한 파일 입출력 (자체를 읽고 쓰는 Class)
// 작성자 : 남병철
//---------------------------------------------------------------------------

#include 	// 파일 스트림 함수를 위해
#include 		// getche()를 위해
#include 	// exit()를 위해
#include 	// typeid()를 위해

const int LEN = 32;		// 마지막 이름의 최대 길이
const int MAXEM = 100;	// 최대 사원수

enum employee_type { tmanager, tscientist, tlaborer };

//---------------------------------------------------------------------------

class employee
{
private:
	char name[LEN];	// 사원 이름
	unsigned long number;	// 사번
	static int n;	// 현재 사원 수
	static employee* arrap[];	// 사원 포인터의 배열

public:
	virtual void getdata()
	{
		cout << "\n   Enter last name : ";
		cin >> name;
		cout << "   Enter number : ";
		cin >> number;
	}

	virtual void putdata()
	{
		cout << "\n   Name : " << name;
		cout << "\n   Number : " << number;
	}

	virtual employee_type get_type();	// 형을 알아 냄
	static void add();	// employee를 추가
	static void display();	// 모든 사원을 화면 출력
	static void read();	// 디스크 파일에서 읽음
	static void write();	// 디스크 파일에 써넣음
	static void destroy();	// 메모리에서 객체를 삭제
};

// 정적 변수
int employee::n;
employee* employee::arrap[MAXEM];

// manager 클래스
class manager : public employee
{
private:
	char title[LEN];
	double dues;

public:
	void getdata()
	{
		employee::getdata();
		cout << "   Enter title : ";
		cin >> title;
		cout << "   Enter golf club dues : ";
		cin >> dues;
	}

	void putdata()
	{
		employee::putdata();
		cout << "\n   Title : " << title;
		cout << "\n   Golf club dues : " << dues;
	}
};

// scientist 클래스
class scientist : public employee
{
private:
	int pubs;

public:
	void getdata()
	{
		employee::getdata();
		cout << "   Enter number of pubs : ";
		cin >> pubs;
	}

	void putdata()
	{
		employee::putdata();
		cout << "\n   Number of publications : " << pubs;
	}
};

// laborer 클래스
class laborer : public employee
{
};

//---------------------------------------------------------------------------

// 메모리의 목록에 사원을 추가
void employee::add()
{
	cout << "\n'm' to add a manager"
			"\n's' to add a scientist"
			"\n'l' to add a laborer"
			"\nType selection : ";

	switch( getche() )
	{
		case 'm' : arrap[n] = new manager;   break;
		case 's' : arrap[n] = new scientist; break;
		case 'l' : arrap[n] = new laborer;   break;
		default : cout << "\nUnknown employee type"; return;
	}
	arrap[n++]->getdata();
}

// 모든 사원을 화면 출력
void employee::display()
{
	for( int j = 0; j < n; j++ )
	{
		cout << '\n' << ( j + 1 );
		switch( arrap[j]->get_type() )
		{
			case tmanager : cout << ". Type : Manager";     break;
			case tscientist : cout << ". Type : Scientist"; break;
			case tlaborer : cout << ". Type : Laborer";     break;
			default : cout << ". Unknown type"; return;
		}
	arrap[j]->putdata();
	}
}

// 이 객체의 형을 리턴
employee_type employee::get_type()
{
	if( typeid( *this ) == typeid( manager ) )
		return tmanager;
	else if( typeid( *this ) == typeid( scientist ) )
		return tscientist;
	else if( typeid( *this ) == typeid( laborer ) )
		return tlaborer;
	else
	{ cout << "\nBad employee type"; exit(1); }

	return tmanager;
}

// 모든 현재 메모리 객체를 파일에 써넣음
void employee::write()
{
	int size;
	cout << "\nWriting " << n << " employees.";
	ofstream ouf;
	employee_type etype;

	ouf.open( "EMPLOY.DAT", ios::trunc | ios::binary );
	if( !ouf )
	{ cout << "\nCan't open file"; return; }
	for( int j = 0; j < n; j++ )
	{
		etype = arrap[j]->get_type();

		ouf.write( ( char* )&etype, sizeof( etype ) );
		switch( etype )
		{
			case tmanager : size = sizeof( manager ); break;
			case tscientist : size = sizeof( scientist ); break;
			case tlaborer : size = sizeof( laborer ); break;
		}

		ouf.write( ( char* )( arrap[j] ), size );
		if( !ouf )
		{ cout << "\nCan't write to file"; return; }
	}
}

// 파일의 모든 사원에 대한 데이터를 메모리로 읽어들임
void employee::read()
{
	int size;
	employee_type etype;
	ifstream inf;
	inf.open( "EMPLOY.DAT", ios::binary );
	if( !inf )
	{ cout << "\nCan't open file"; return; }
	n = 0;
	while( 1 )
	{	// 다음 사원의 형을 읽음
		inf.read( ( char* )&etype, sizeof( etype ) );
		if( inf.eof() )
			break;
		if( !inf )
		{ cout << "\nCan't read type from file"; return; }

		switch( etype )
		{
			case tmanager:
				arrap[n] = new manager;
				size = sizeof( manager );
				break;

			case tscientist:
				arrap[n] = new scientist;
				size = sizeof( scientist );
				break;
			
			case tlaborer:
				arrap[n] = new laborer;
				size = sizeof( laborer );
				break;

			default : cout << "\nUnknown type in file"; return;
		}

		inf.read( ( char* )arrap[n], size );
		if( !inf )
		{ cout << "\nCan't read data from file"; return; }
		n++;
	} // end of while
	cout << "\nReading " << n << " employees";
}

// 사원에 할당된 메모리를 삭제
void employee::destroy()
{
	for( int j = 0; j < n; j++ )
		delete arrap[j];
}
//---------------------------------------------------------------------------

void main()
{
	while( 1 )
	{
		cout << "\n'a' -- add data for an employee"
				"\n'd' -- display data for all employees"
				"\n'w' -- write all employee data to file"
				"\n'r' -- read all employee data from file"
				"\n'x' -- exit"
				"\nType selection : ";

		switch( getche() )
		{
			case 'a':
				employee::add();
				break;

			case 'd':
				employee::display();
				break;

			case 'w':
				employee::write();
				break;

			case 'r':
				employee::read();
				break;

			case 'x':
				employee::destroy();
				return;
			
			default: cout << "\nUnknown command";
		}
	} // end of while

}

//---------------------------------------------------------------------------
- << 및 >> 연산자의 English Class에 대한 겹지정
//---------------------------------------------------------------------------
// 작성일 : 2002.06.11
// 제  목 : English Class의 cin, cout에 << >> 연산자 겹지정
// 작성자 : 남병철
//---------------------------------------------------------------------------

#include 

//---------------------------------------------------------------------------

class English
{
private:
	int feet;
	float inches;

public:
	English()
	{ feet = 0; inches = 0.0; }
	English( int ft, float in )
	{ feet = ft; inches = in; }
	friend istream& operator >> ( istream& s, English& d );
	friend ostream& operator << ( ostream& s, const English& d );
};

//---------------------------------------------------------------------------

istream& operator >> ( istream& s, English& d )
{
	cout << "\nEnter feet : "; s >> d.feet;
	cout << "Enter inches : "; s >> d.inches;
	return s;
}

ostream& operator << ( ostream& s, const English& d )
{
	s << d.feet << "\'-" << d.inches << '\"';
	return s;
}

//---------------------------------------------------------------------------

void main()
{
	English dist1, dist2;
	cout << "\nEnter two English values.";
	cin >> dist1 >> dist2;

	English dist3( 11, 6.25 );

	cout << "\ndist1 = " << dist1 << "\ndist2 = " << dist2;
	cout << "\ndist3 = " << dist3;
}

//---------------------------------------------------------------------------
- << 및 >> 연산자의 파일에 대한 겹지정 English Class의 << 및 >> 연산자를 겹지정하여 파일 입출력과 cout과 cin에서 작동하게 만드는 방법을 보여준다. (사용자가 거리를 정확한 구두점과 함께 입력하지 않으면, 거리는 파일에 정확하게 기록되지 않고 << 연산자가 그 파일을 읽을 수 없게 된다.)
//---------------------------------------------------------------------------
// 작성일 : 2002.06.11
// 제  목 : << 및 >> 연산자의 파일에 대한 겹지정
// 작성자 : 남병철
//---------------------------------------------------------------------------

#include 

//---------------------------------------------------------------------------

class English
{
private:
	int feet;
	float inches;

public:
	English()
	{ feet = 0; inches = 0.0; }
	English( int ft, float in )
	{ feet = ft; inches = in; }

	friend istream& operator >> ( istream& s, English& d );
	friend ostream& operator << ( ostream& s, const English& d );
};

//---------------------------------------------------------------------------

istream& operator >> ( istream& s, English& d )
{
	char dummy;	// for ('), (-), and (")

	s >> d.feet >> dummy >> dummy >> d.inches >> dummy;

	return s;
}

ostream& operator << ( ostream& s, const English& d )
{
	s << d.feet << "\'-" << d.inches << '\"';
	return s;
}

//---------------------------------------------------------------------------

void main()
{
	char ch;
	English dist1;
	ofstream ofile;
	ofile.open( "DIST.DAT" );

	do
	{
		cout << "\nEnter distance : ";
		cin >> dist1;
		ofile << dist1;
		cout << "Do another (y/n)? ";
		cin >> ch;
	}
	while( ch != 'n' );
	ofile.close();

	ifstream ifile;
	ifile.open( "DIST.DAT" );

	cout << "\nContents of disk file is : ";
	while( 1 )
	{
		ifile >> dist1;
		if( ifile.eof() )
			break;
		cout << "\nDistance = " << dist1;
	}
}

//---------------------------------------------------------------------------
- binary 파일 입출력에 대한 겹지정 << >> 연쇄는 main()의 한 명령문이 두 person 객체를 디스크 파일에 써넣도록 겹지정 연산자를 통해 가능하게 된다. (스트림 객체가 왼쪽 인수로 사용되므로 이들 연산자는 프렌드 함수이어야 한다.)
//---------------------------------------------------------------------------
// 작성일 : 2002.06.11
// 제  목 : binary 입출력에 대한 겹지정
// 작성자 : 남병철
//---------------------------------------------------------------------------

#include 

//---------------------------------------------------------------------------

class person
{
protected:
	enum { SIZE = 40 };
	char name[SIZE];
	int age;

public:
	void getData()
	{
		cout << "\n   Enter name : "; cin.getline( name, SIZE );
		cout << "   Enter age : "; cin >> age;
		cin.ignore( 10, '\n' );
	}

	void putData()
	{
		cout << "\n   Name = " << name;
		cout << "\n   Age = " << age;
	}

	friend istream& operator >> ( istream& s, person& d );
	friend ostream& operator << ( ostream& s, person& d );

	void persin( istream& s )
	{
		s.read( ( char* )this, sizeof( *this ) );
	}

	void persout( ostream& s )
	{
		s.write( ( char* )this, sizeof( *this ) );
	}
};

//---------------------------------------------------------------------------

istream& operator >> ( istream& s, person& d )
{
	d.persin( s );
	return s;
}

ostream& operator << ( ostream& s, person& d )
{
	d.persout( s );
	return s;
}

//---------------------------------------------------------------------------

void main( void )
{
	person pers1, pers2, pers3, pers4;
	cout << "\nPerson 1";
	pers1.getData();
	cout << "\nPerson 2";
	pers2.getData();

	ofstream outfile( "PERSON.DAT", ios::binary );
	outfile << pers1 << pers2;
	outfile.close();

	ifstream infile( "PERSON.DAT", ios::binary );
	infile >> pers3 >> pers4;
	cout << "\nPerson 3";
	pers3.putData();
	cout << "\nPerson 4";
	pers4.putData();
}

//---------------------------------------------------------------------------
(4) Memory for stream object * 인메모리 서식화 메모리의 어떤 구획을 스트림 객체로 취급하여, 파일의 경우처럼 그 안에 데이터를 삽입할 수 있다. [용도] 1. ostrstream객체를 사용하여 문자열을 메모리에 저장하는 것이다. 2. istrstream객체를 사용하여 서식화된 데이터를 메모리에서 읽고 변수에 저장할 수도 있다. 이 변환 과정이 작동 하려면 membuff의 각 변수를 공백으로 구분해야 한다. 또한 변수로 읽어들일때 불필요한 문자열들은 dummy버퍼로 읽어 들인후 그에 대해 더이상 처리하지 않는 구현이 보통이다. (atof(), atoi()와 같은 C언어 변환 함수를 일일이 사용할 필요없이, 많은 변수를 영숫자 형태에서 수치변수로 변환할 수 있다.) [장점] - 수를 화면 출력하더라도 데이터가 문자열이어야 하는 대화 상자나 창에 써넣는 GUI 환경의 함수가 있다. 서식화 입출력과 함께 iostream을 사용하여 이 문자열을 메모리의 한 구획에 구성한 후 해당 문자열을 인수로 가진 GUI 함수를 호출하면 편리하다. C프로그래머들은 sprintf()함수로 비슷한 구현을 하기도 한다. * 필요한 것들 - 메모리에 쓰기 위한 것으로는 주로 ostream에서 유도된 ostrstream이 있다. (다른 Class도 많이 있다.) - 메모리에서 읽기 위한 것으로는 istream에서 유도된 istrstream이 있다. - 메모리 객체 읽기/쓰기를 위한 것으로는 iostream에서 유도된 strstream이 있다. - 메모리 스트림 객체를 사용하려면 strstrea.h 헤더 파일이 필요하다. * ostrstream 객체 사용시 주의점 ostrstream 객체를 사용하는 방법중 하나는 char*인 데이타 버퍼로 사용하는 것이다. 그리고 메모리 버퍼와 그 크기를 객체 생성자의 인수로 사용하여 ostrstream 객체를 만든다. 이제 cout이나 디스크 파일에 문자열을 보낼 때처럼 << 연산자를 사용하여 ostrstream 객체에 서식화 문자열을 보낼 수 있다. 문자열의 끝에는 조정자 ends(\0)를 삽입한다. (이 조정자를 잊지 말도록 한다. 문자열은 0으로 끝나야 하는데, 이것은 자동으로 나오지 않는다.) * 보편성(구현할 사항) 인메모리 서식화를 사용하는 객체 지향 프로그램을 만들때는 인메모리 입출력 루틴을 구성원 함수에 넣어서 객체가 그 자체를 메모리에 읽고 쓰게 하는 것이 좋다. 디스크 파일 읽고 쓰기에 사용하는 것과 같은 구성원 함수를 사용하여 메모리에 객체를 읽고 쓸 수 있다. << 및 >> 연산자를 겹지정하여 디스크 파일을 다룬다. 그런 다음에, 파일 객체의 이름이 함수에 직접 들어가지 않고 함수에 인수로 전달되게 한다. istrstream, ostrstream과 같은 인메모리 객체에 대해서도 파일 객체에서와 마찬가지로 seekg(), tellp()로 다루는 것과 같은 파일 포인터를 사용할 수 있다.
//---------------------------------------------------------------------------
// 작성일 : 2002.06.13
// 제  목 : 고정 버퍼 크기 인메모리 서식화
// 작성자 : 남병철
//---------------------------------------------------------------------------

#include 
const int SIZE = 80;

//---------------------------------------------------------------------------

void main()
{
	// 메모리에 쓰기위한 객체 생성
	char membuff[SIZE];
	ostrstream omem( membuff, SIZE );

	int oj = 77;
	double od = 890.12;
	char ostr1[] = "Kafka";
	char ostr2[] = "Freud";

	omem << "oj= " << oj << endl
		 << "od= " << od << endl
		 << "ostr1= " << ostr1 << endl
		 << "ostr2= " << ostr2 << endl
		 << ends;
	// 메모리에 쓰여진 내용 표시
	cout << membuff;


	char dummy[20];
	int ij;
	double id;
	char istr1[20];
	char istr2[20];

	// 메모리에서 읽기위한 객체 생성
	istrstream imem( membuff, SIZE );

	imem >> dummy >> ij >> dummy >> id
		 >> dummy >> istr1 >> dummy >> istr2;

	// 메모리에서 읽어들인 내용 표시
	cout << "\nij=" << ij << "\nid=" << id
		 << "\nistr1=" << istr1 << "\nistr2=" << istr2;
}

//---------------------------------------------------------------------------
* 동적 버퍼 크기 정적 버퍼에 연계시키지 않고, 자체의 저장 영역을 할당하는 ostrstream 객체를 만들고 그 크기를 동적으로 조정하여 그 안에 넣는 내용을 제대로 보관할 수 있다. (인수가 없다는데 유의:메모리 구획 포인터 char*를 리턴) 아래 코드에서 보여준 접근방식대로 사용하면 모든 일이 예상대로 이루어진다. (한 명령문을 사용하여 데이터를 객체에 넣고, 데이터 포인터의 값을 알아내고, 결코 내용을 바꾸거나 데이터에 다시 접근하지 않는다) 데이터 영역에 데이터를 추가하고 메모리를 추가하는 등의 방법은 프로그램을 마술과 같은 영역으로 빠뜨릴 것이며 이 모든 것을 고려할 때, 아마도 여기서 말한 간단한 접근 방식을 넘어서는 것은 그만한 가치가 없을 것이다.
//---------------------------------------------------------------------------
// 작성일 : 2002.06.13
// 제  목 : 동적 버퍼 크기 인메모리 서식화
// 작성자 : 남병철
//---------------------------------------------------------------------------

#include 

//---------------------------------------------------------------------------

void main()
{
	ostrstream omem;

	omem << "j=" << 111 << endl
		 << "k=" << 2.3456 << endl;
	omem << ends;

	char* p = omem.str();
	cout << p;
}

//---------------------------------------------------------------------------
(5) Print and etc. (차후 윈도우 및 도스용으로 확대 보강할것.) * 명령줄 인수 * 프린터 출력 * 재지정

+ -

관련 글 리스트
38 (09) Stream & File in C++ (2) 남병철.레조 58418 2009/06/21
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.