二级C++辅导笔记:类的其他几点问题
一、拷贝构造函数
拷贝构造函数在下列情况下被调用:用已经存在的对象去初始化同一个类的另一个对象;在函数的参数中,以传值方式传递类对象的拷贝;类对象的值被用做函数的返回值。拷贝构造函数和前面说到的转换构造函数有些相似。转换构造函数是把一个类的对象转化为另一个类的对象;拷贝构造函数是用一个已经存在的对象的值实例化该类的一个新对象。
不同对象间的初始化和赋值的区别:赋值操作是在两个已经存在的对象间进行的;而初始化是要创建一个新的对象,并且其初值来源于另一个已存在的对象。编译器会区别这两种情况,赋值的时候调用重载的赋值运算符,初始化的时候调用拷贝构造函数。
如果类中没有拷贝构造函数,则编译器会提供一个默认的。这个默认的拷贝构造函数只是简单地复制类中的每个成员。
#include iostream.h
#include string.h
class Date
{
int mo, da, yr;
char* month;
public:
Date(int m = 0, int d = 0, int y = 0);
Date(const Date &);
~Date();
void display() const;
};
Date::Date(int m, int d, int y)
{
static char* mos[] =
{
January, February, March, April, May, June,
July, August, September, October, November, December
};
mo = m; da = d; yr = y;
if (m != 0)
{
month = new char[strlen(mos[m-1])+1];
strcpy(month, mos[m-1]);
}
else
month = 0;
}
Date::Date(const Date & dt)
{
mo = dt.mo;
da = dt.da;
yr = dt.yr;
if (dt.month != 0)
{
month = new char [strlen(dt.month)+1];
strcpy(month, dt.month);
}
else
month = 0;
}
Date::~Date()
{
delete [] month;
}
void Date::display() const
{
if (month != 0)
cout << month <<' '<< da << , << yr << std::endl;
}
int main()
{
Date birthday(6,24,1940);
birthday.display();
Date newday = birthday;
newday.display();
Date lastday(birthday);
lastday.display();
return 0;
}
本例中,用到了两次拷贝构造函数。一个是使用普通的C++初始化变量的语句:
Date newday = birthday;
另一个是使用构造函数的调用约定,即把初始化值作为函数的参数:
Date lastday(birthday);
二、类的引用
在函数参数和返回值中,如果一定要使用传值方式,那么使用类对象的引用,是一个提高效率的方法。
类的数据成员也可以是一个引用,但必须注意:第一,一个引用必须初始化。通常一个类对象并不会像结构那样用大括号来初始化,而是调用构造函数。因此在构造函数里必须初始化类当中的引用成员。第二,引用是一个别名。尽管类里面的引用在使用方式上看起来和类的一般数据成员没有什么区别,但是作用在其上的操作,实际上是对用来初始化它的那么对象进行的。
#include iostream.h
class Date
{
int da, mo, yr;
public:
Date(int d,int m,int y)
{ da = d; mo = m; yr = y; }
void Display() const
{ cout << da << '/' << mo << '/' << yr; }
};
class Time
{
int hr, min, sec;
public:
Time(int h, int m, int s)
{ hr = h; min = m; sec = s; }
void Display() const
{ cout << hr << ':' << min << ':' << sec; }
};
class DateTime
{
const Date & dt;
const Time & tm;
public:
DateTime(const Date & d, const Time& t) : dt(d), tm(t)
{
//empty
}
void Display() const
{
dt.Display();
cout << ' ';
tm.Display();
}
};
int main()
{
Date today(7,4,2004);
Time now(15,20,0);
DateTime dtm(today, now);
dtm.Display();
return 0;
}
我们来看看这个程序中DateTime的构造函数的格式:冒号操作符引出了一个参数初始化表。必须使用这种格式来初始化引用数据成员,而不可以在函数体内来进行初始化工作。如果构造函数像上例一样不是内联的,那么最好不要在类声明中构造函数的原型上使用冒号和初始化值表,而是像下面这样,把参数初始化表放在定义构造函数的地方:
Class DateTime
{
const Date & dt;
const Time & tm;
public:
DateTime(const Date & d,const Time& t);
}
DateTime::DateTime(const Date & d,const Time& t):dt(d),tm(t)
{
//empty
}
可以使用构造函数的参数初始化表来初始化任何数据成员。特别是常量数据成员,和引用一样,只能在参数初始化表里进行初始化,这是因为不可以在构造函数内部为常量数据成员赋值。
当一个类含有引用数据成员时,一旦引用被实例化和初始化以后,就无法修改它的值,所以该类不可能彻底地重载赋值运算符函数。
三、构造函数的参数初始化表
如果类对象的某些数据成员没有载构造函数内部被初始化,那么必须使用构造函数的参数初始化表对他们进行初始化。否则,编译器不止到该如何初始化这些还等着在构造函数内部赋值的成员。我们习惯用参数初始化表来初始化所有数据成员。
class Date
{
int mo,da,yr;
public:
Date(int m=0,int d=0,int y=0);
};
class Employee
{
int empno;
Date datehired;
public:
Employee(int en,Date & dh);
};
可以用下面两种方法编写Employee类的构造函数:
Employee::Employee(int en,Date & dt)
{
empno=en;
datehired=dh;
}
或者;
Employee::Employee(int en,Date & dt):empno(en),datehired(dh)
{
//empty
}
虽然这两种方法效果是一样的,但是根据Date对象默认构造函数的复杂性的不同,这两种形式的效率差别是很大的。
四、对const修饰符的简单说明
如果一个对象被声明为常量,那么该对象就不可以调用类当中任何非常量型的成员函数(除了被编译器隐式调用的构造函数和析构函数)。看下面的代码;
#include iostream.h
class Date
{
int month,day,year;
public:
Date(int m,d,y):month(m),day(d),year(y) {}
void display()
{
cout < }
}
int main()
{
const Date dt(4,7,2004);
dt.display(); //error
return 0;
}
这个程序尽管编译时没有问题,但运行时却出错了。这是因为常量对象不能调用非常量函数。编译器只看函数的声明,而不在乎函数的具体实现。实际上函数的实现可以在程序中的任何地方,也可以是在另一个源代码文件中,这就超过了编译器的当前可见范围。
//date.h
class Date
{
int month,day,year;
public:
Date(int m,d,y);
void display();
};
//date.cpp
#include iostream.h
#include date.h
Date::Date(int m,d,y):month(m),day(d),year(y) {}
void Date::display()
{
cout < }
//program.cpp
#include iostream.h
#include date.cpp
int main()
{
const Date dt(4,7,2004);
dt.display();
return 0;
}
解决出错的问题有两个方法:第一是声明display()函数为常量型的
//in date.h
void display() const
//int date.cpp
void Date::display() const
{
cout < }
另一个解决方式就是省略掉Date对象声明里的const修饰符。
Date dt(4,7,2004);
还有另一个容易出错的地方:
void abc(const Date & dt)
{
dt.display(); //error 提示display没有const修饰符
}
函数abc()声明了一个Date对象的常量引用,这说明该函数不会修改传递进来的参数的值。如果Date::display()函数不是常量型的,那么在函数abc()里就不能调用它,因为编译器会认为Date::display()函数有可能会修改常量的值。
不论类对象是否是常量型的,它必须修改某个数据成员的值时,ANSI委员会设立了mutable关键字。
五、可变的数据成员
假设需要统计某个对象出现的次数,不管它是否是常量。那么类当中就应该有一个用来计数的整型数据成员。只要用mutable修饰符来声明该数据成员,一个常量型的成员函数就可以修改它的值。
#include iostream.h
class AValue
{
int val;
mutable int rptct;
public:
AValue(int v) : val(v), rptct(0) { }
~AValue()
{
cout < }
void report() const;
};
void AValue::report() const
{
rptct++;
cout << val << endl;
}
int main()
{
const AValue aval(123);
aval.report();
aval.report();
aval.report();
return 0;
}
相关推荐:
北京 | 天津 | 上海 | 江苏 | 山东 |
安徽 | 浙江 | 江西 | 福建 | 深圳 |
广东 | 河北 | 湖南 | 广西 | 河南 |
海南 | 湖北 | 四川 | 重庆 | 云南 |
贵州 | 西藏 | 新疆 | 陕西 | 山西 |
宁夏 | 甘肃 | 青海 | 辽宁 | 吉林 |
黑龙江 | 内蒙古 |