C++--类的深入剖析

C++类的深入剖析(上)


0.Time类的实例研究

//Time.h
#ifndef TIME_H
#define TIME_H

class Time
{
	public:
		Time();
		void setTime(int,int,int);
		void printUniversal();
		void printStandard();
	private:
		int hour;
		int minute;
		int second;
};
#endif
//Time.cpp
#include<iostream>
using std::cout;

#include<iomanip>
using std::setfill;
using std::setw;

#include"Time.h"

Time::Time()
{
	hour=minute=second=0;
}

void Time::setTime(int h,int m,int s)
{
	hour = (h>=0&&h<24)?h:0;
	minute = (m>=0&&m<60)?m:0;
	second = (s>=0&&s<60)?s:0;
}

void Time::printUniversal()
{
	cout<<setfill('0')<<setw(2)<<hour<<":"
	<<setw(2)<<minute<<":"<<setw(2)<<second;
}

void Time::printStandard()
{
	cout<<((hour==0||hour==12)?12:hour%12)<<":"
	<<setfill('0')<<setw(2)<<minute<<":"<<setw(2)
	<<second<<(hour<12?"AM":"PM");
}
//TimeTest.cpp
#include<iostream>
using std::cout;
using std::endl;

#include"Time.h"

int main(void)
{
	Time t;
	cout<<"The initial universal time is";
	t.printUniversal();
	cout<<"\nThe initial standard time is";
	t.printStandard();
	
	t.setTime(13,27,6);
	
	cout<<"\n\nUniversal time after setTime is";
	t.printUniversal();
	cout<<"\nStandard time after setTime is";
	t.printStandard();
	
	t.setTime(99,99,99);

	cout<<"\n\nAfter attempting invalid settings";
	cout<<"\nUniversal time:";
	t.printUniversal();
	cout<<"\nStandard time:";
	t.printStandard();
	cout<<endl;
	
	return 0;	
}

1)预处理器封套
利用预处理器指令#ifndef、#define和#endif等构成预处理器封套,从而避免头文件在一个程序中被多次包含。预处理器指令中符号常量命名的通常约定是简单地将头文件名用大写形式,其中的原点用下划线代替。(如TIME_H)

2)数据成员初始化
Time构造函数将数据成员初始化为0,这就确保了对象可以以一个可靠的状态开始。类的成员函数不能在类体中声明时初始化,推荐由类的构造函数初始化这些数据成员。(数据成员也可以由用Time类的设置函数来赋值)

3)“粘性设置和非粘性设置
使用流操作元setfill,用于指定当输出域宽大于输出整数值中数字个数时所需显示的填充字符。一旦用setfill指定了填充字符,该字符将应用在后续值的显示中。也就是说,setfill是一个粘性设置;与setw相反,setw是一个非粘性设置,它只对紧接着显示的值起作用。每个粘性设置当不再需要时,应当将它恢复为以前的设置。如果不这样做,可能导致后面程序中输出格式的不正确。

4)成员函数与全局函数
printUniversal函数和printStandard函数都不接收任何参数,因为这些成员函数隐式地知道他们将打印调用它们的特定Time对象的数据成员。(对象中封装了数据成员和成员函数,使成员函数有权访问数据成员)

5)对象大小
从逻辑上来讲,程序员认为对象是包含了数据和函数。然而事实并非如此,对象只包含数据,所以同假设也包含了成员函数的对象相比要小的多。(可用sizeof检验,结果只报告该类数据成员的大小)


1.类的作用域和类成员的访问

#include<iostream>
using std::cout;
using std::endl;

class Count
{
	public:
		void setX(int value)
		{
			x=value;
		}
		void print()
		{
			cout<<x<<endl;
		}
	private:
		int x;
};

int main(void)
{
	Count counter;//实例化一个对象
	Count *counterPtr=&counter;//指向类的成员的指针
	Count &counterRef=counter;//定义一个变量,这个变量指向的是一个对象,称为对象的引用
	
	cout<<"Set x to 1 and print using the object's name:";
	counter.setX(1);
	counter.print();
	
	cout<<"Set x to 2 and print using the object's name:";
	counterRef.setX(2);
	counterRef.print();
	
	cout<<"Set x to 3 and print using the object's name:";
	counterPtr->setX(3);
	counterPtr->print();
	return 0;
}

类的数据成员和成员函数属于该类的作用域。圆点成员选择运算符(.)前面加对象名称或者对象的引用,则可以访问对象的成员;箭头成员选择运算符(->)前面加对象的指针,则可以访问对象的成员。(可称为通过对象句柄来访问对象的成员函数)


2.访问函数和工具函数

访问函数可以读取或者显示数据。访问函数另一个常见的用法是测试条件是真还是假–常常称这样的函数为判定函数。如一开始的Time类,isAM和isPM就是一组有用的判定函数。

工具函数不属于类的public接口部分,它是支持类的public成员函数操作的private成员函数。


3.Time类的实例研究:默认实参的构造函数

//Time.h
#ifndef TIME_H
#define TIME_H

class Time
{
	public:
		Time(int=0,int=0,int=0);
		
		void setTime(int,int,int);
		void setHour(int);
		void setMinute(int);
		void setSecond(int);
		
		int getHour();
		int getMinute();
		int getSecond();
		
		void printUniversal();
		void printStandard();
	private:
		int hour;
		int minute;
		int second;
};
#endif
//Time.cpp
#include<iostream>
using std::cout;

#include<iomanip>
using std::setfill;
using std::setw;

#include"Time.h"

Time::Time(int hr,int min,int sec)
{
	setTime(hr,min,sec);
}

void Time::setTime(int h,int m,int s)
{
	setHour(h);
	setMinute(m);
	setSecond(s);
}

void Time::setHour(int h)
{
	hour = (h>=0&&h<24)?h:0;	
}

void Time::setMinute(int m)
{
	minute = (m>=0&&m<60)?m:0;	
}

void Time::setSecond(int s)
{
	second = (s>=0&&s<60)?s:0;	
}

int Time::getHour()
{
	return hour;
}

int Time::getMinute()
{
	return minute;
}

int Time::getSecond(){
	return second;
}

void Time::printUniversal()
{
	cout<<setfill('0')<<setw(2)<<hour<<":"
	<<setw(2)<<minute<<":"<<setw(2)<<second;
}

void Time::printStandard()
{
	cout<<((hour==0||hour==12)?12:hour%12)<<":"
	<<setfill('0')<<setw(2)<<minute<<":"<<setw(2)
	<<second<<(hour<12?"AM":"PM");
}
//TimeTest.cpp
#include<iostream>
using std::cout;
using std::endl;

#include"Time.h"

int main(void)
{
	Time t1;
	Time t2(2);
	Time t3(21,34);
	Time t4(12,25,42);
	Time t5(27,74,99);
	
	cout<<"Constructed with:\n\nt1:all argument defaulted\n";
	t1.printUniversal();
	cout<<endl;
	t1.printStandard();
	
	cout<<"\n\nt2:hour specified;minute and second defaulted\n";
	t2.printUniversal();
	cout<<endl;
	t2.printStandard();
	
	cout<<"\n\nt3:hour and minute specified;second defaulted\n";
	t3.printUniversal();
	cout<<endl;
	t3.printStandard();
	
	cout<<"\n\nt4:hour ,minute and second specified\n";
	t4.printUniversal();
	cout<<endl;
	t4.printStandard();
	
	cout<<"\n\nt5:all invalues specified\n";
	t5.printUniversal();
	cout<<endl;
	t5.printStandard();
	
	return 0;	
}

实参隐式地传递给构造函数。Time的设置函数和获取函数始终在类的内部调用。本来,printUniversal和printStandard函数可以不通过调用这些设置和获取函数而直接访问类的private数据。以这样的方式来对类进行设计,可以降低因改变类的实现方法而造成的编程出错的可能性。


4. 析构函数

//CreatAndDestroy.h
#include<string>
using std::string;

#ifndef CREATE_H
#define CREATE_H

class CreatAndDestroy
{
	public:
		CreatAndDestroy(int,string);
		~CreatAndDestroy();
	private:
		int objectID;
		string message;
};
//CreatAndDestroy.cpp
#include<iostream>
using std::cout;
using std::endl;

#include"CreatAndDestroy.h"

CreatAndDestroy::CreatAndDestroy(int ID,string messageString)
{
	objectID=ID;
	message=messageString;
	
	cout<<"Object "<<objectID<<" constructor runs  "<<message<<endl;
}
CreatAndDestroy::~CreatAndDestroy()
{
	cout<<(objectID==1||objectID==6?"\n":"");
	
	cout<<"Object "<<objectID<<"  destructor runs  "<<message<<endl;
}
//CreatAndDestroytest.cpp
#include<iostream>
using std::cout;
using std::endl;

#include"CreatAndDestroy.h"

void creat(void);

CreatAndDestroy first(1,"(global before main)");

int main(void)
{
	cout<<"\nMAIN FUNCTION:EXECUTION BEGINS"<<endl;
	CreatAndDestroy second(2,"(local automatic in main)");
	static CreatAndDestroy third(3,"(local static in main)");
	
	creat(); 
	
	cout<<"\nMAIN FUNCTION:EXECUTION RESUMES"<<endl;
	CreatAndDestroy fouth(4,"(local automatic in main)");
	cout<<"\nMAIN FUNCTION:EXECUTION ENDS"<<endl;
	return 0; 
}
void creat(void)
{
	cout<<"\nCREAT FUNCTION:EXECUYION BEGINS"<<endl;
	CreatAndDestroy fifth(5,"(local static in creat)");
	static CreatAndDestroy sixth(6,"(local automatic in creat)");
	CreatAndDestroy seventh(7,"(local automatic in creat)");
	cout<<"\nCREAT FUNCTION:EXECUTION ENDS"<<endl; 
}

析构函数是另一种特殊的成员函数,类的析构函数的名字是在类名之前添加发音字符(~)后形成的字符序列。析构函数不接收任何参数,也不返回任何值;当对象撤销后,类的析构函数会隐式地调用。构造函数和析构函数都被隐式调用,一般而言析构函数的调用顺序与相应构造函数的调用顺序相反。析构函数本身不释放对象占用的内存空间,它只是在系统回收对象的内存空间之前执行扫尾工作,这样内存可以成行用于保存新的对象。

全局作用域内定义的对象的构造函数,在文件内任何其他函数(包括main函数)开始执行前调用。当main函数执行结束时,相应的析构函数被调用。

当程序执行到自动局部对象的定义处时,该对象的构造函数被调用;当程序执行离开对象的作用域时,相应的析构函数被调用。

static局部对象的构造函数只调用一次,即在程序第一次执行到该对象的定义处时;而相应的析构函数的调用则发生在main函数结束时。

如果程序的终止是调用exit函数,那么自动对象的析构函数不被调用;如果程序的终止是调用abort函数,那么任何对象的析构函数不被调用;全局或者静态对象的撤销顺序与他们建立的顺序正好相反。


5.默认的逐个成员赋值

//Date.h
#ifndef DATE_H
#define DATE_H

class Date
{
	public:
		Date(int=1,int=1,int=2000);
		void print();
	private:
		int month;
		int day;
		int year;
};

#endif
//Date.cpp
#include<iostream>
using std::cout;
using std::endl;

#include"Date.h"

Date::Date(int m,int d,int y)
{
	month = m;
	day = d;
	year = y;
}

void Date::print()
{
	cout<<month<<"/"<<day<<"/"<<year;
}
//DateTest.cpp
#include<iostream>
using std::cout;
using std::endl;

#include"Date.h"

int main(void)
{
	Date date1(7,4,2004);
	Date date2;
	
	cout<<"date1=";
	date1.print();
	cout<<"\ndate2=";
	date2.print();
	
	date2=date1;
	
	cout<<"\n\nAfter default memberwise assignment,date2=";
	date2.print();
	cout<<endl;
	return 0;
}

赋值运算符可以将一个对象赋给另一个类型相同的对象。当所用类的数据成员包含指向动态分配内存的指针时,逐个成员赋值可能会引发严重的问题。


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会思想的苇草i

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值