《absolute C++》
Constructors and Other Tools
---------------------------------------------------------------------------------------------------------------------------
Tip1:构造函数不能像成员函数那样调用,也没有返回值
date1.DayOfYear(7, 4); //VERY ILLEGAL
date2.DayOfYear(5, 5); //VERY ILLEGAL
♦ 像这样调用构造函数是非法的,
Tip2: 构造函数的初始化段
DayOfYear::DayOfYear(int monthValue, int dayValue)
: month(monthValue), day(dayValue)
{/*Body intentionally empty*/}
♦构造函数在类外被定义,定义在参数列表后与函数定义前。
♦构造函数的初始化段成员变量名在圆括号外,(即month,day)成员变量的初始值,也就是我们想要赋给成员变量的值放在括号里,初始化值可以是构造函数的参数值,正如本例。
♦也就是说,month(monthValue), day(dayValue)语句等同于month=monthValue; day=dayValue; 也就是说,我们习惯定义构造函数为
DayOfYear::DayOfYear(int monthValue, int dayValue)
{
month = monthValue;
day = dayValue;
}
而这与我们使用初始化段但函数体为空的构造函数效果一致。
♦ 使用了初始化段的构造函数的函数体可以不为空(事实上也常常不为空),可以用来检测赋值正确与否,比如:
DayOfYear::DayOfYear(int monthValue, int dayValue)
: month(monthValue), day(dayValue)
{
if ((month < 1) || (month > 12))
{
cout << "Illegal month value!\n";
exit(1);
}
if ((day < 1) || (day > 31))
{
cout << "Illegal day value!\n";
exit(1);
}
}
Tip3: 默认构造函数与构造函数的显式调用
♦ 如果想用无参数构造构造函数来初始化对象(date3)的时候,是不加括号的。
DayOfYear date1(2, 21), date2(5), date3;
♦ 如果你声明了一个带括号的无参数构造函数,会造成歧义。
DayOfYear date3()
编译器会认为你定义了一个函数名为date3,返回值为DayOfYear类类型的函数。
♦ 除了在声明对象时,会自动调用构造函数(必须给定相应的参数),构造函数也可以被显式调用,
Variable = Constructor_Name(Arguments_For_Constructor);
holiday = DayOfyear(10, 31)
♦ 但对象holoday必须事先已经被声明!
♦ 在C++中,具体的流程是,构造函数DayOfYear先构造了一个匿名对象,用来存储10月31号,然后将该匿名对象赋值给对象holiday。在这里,构造函数的行为类似于返回一个类型为本身,数值为显式声明的函数。
♦ 无参数构造函数函数的显式调用后面必须包含圆括号!
holiday = DayOfYear( );
Tip4:在定义构造函数后,默认的无参构造函数将不存在
♦所以,建议在定义的所有类中,都定义一个默认的无参数的构造函数。用来产生一个未初始化的对象。
SampleClass::SampleClass( )
{/*Do nothing.*/}
Tip5:类类型成员变量的构造函数声明
例:
Holiday::Holiday(int month, int day, bool theEnforcement)
: date(month, day), parkingEnforcement(theEnforcement)
{/*Intentionally empty*/}
♦类holiday的成员变量date是一个类类型的成员变量, 这样在holiday类(外层类)的构造函数定义中,参数列表必须包含date类的所有成员变量,而初始化段则直接声明使用date类的构造函数,本类(holiday)独有的成员变量则照常初始化。
Tip6:在初始化段中调用函数(Display 7.2的难点解释)
♦ 有些情况中,需要对初始化成员变量的参数值进行一定的运算,需要调用函数,这个时候可直接将函数使用在初始化段中。正如BankAccount程序中,如果用户直接声明一个double类型的balance,而不是分别用int变量表示账户余额的元部分与分部分,这个时候就需要用函数来分离这个double类型的balance,从而将分离出来的值分别初始化accountDollars和accountCents两个成员变量。使用语法如下:
BankAccount::BankAccount(double balance, double rate)
: accountDollars(dollarsPart(balance)), accountCents(centsPart(balance))
{
setRate(rate);
}
♦本例的另外几点说明: ① BankAccount的构造函数主要目的就是初始化accountDollars,accountCents和rate这三个成员变量,如果用户分别输入两个int类型的值(即账户余额的元部分与分部分)和一个double类型的值,则不需要分离函数。正如另外一个构造函数所示的那样:
BankAccount::BankAccount(int dollars, int cents, double rate)
{
setBalance(dollars, cents);
setRate(rate);
}
♦ 本例的这个构造函数没有使用初始化段,而是使用了set函数来初始化dollars和cents,事实上这也是可以的,并且set函数还可以写入检查部分(即检查输入的值是否符合要求)
为了使得解释完整,下列set函数的代码段:
void BankAccount::setBalance(int dollars, int cents)
{ if ((dollars < 0 && cents > 0) || (dollars > 0 && cents < 0) ) // 检查段
{
cout << "Inconsistent account data.\n";
exit(1);
}
accountDollars = dollars; // 赋值段 ,可以发现这里将参数dollars,cents赋值给了成员变量 accountDollars ,accountCents
accountCents = cents;
}
void BankAccount::setRate(double newRate)
{
if (newRate >= 0.0)
rate = newRate; //赋值段 ,可以发现这里将参数newRate赋值给了成员变量rate
else
{
cout << "Cannot have a negative interest rate.\n"; // 这里将检查段放在了后面
exit(1);
}
}
Tip7:const修饰参数和这么做的理由
♦ 因为在调用一个结构或者类类型的形参的时候,如果使用传值(不加&),在效率上会有所降低,所以推荐使用引用调用(加&),这两种方式在代码的实现上没有区别,区别是
①:引用调用效率更高
②:引用调用会改变参数值,所以使用const来设定不改变参数值。
传值调用方式:
bool isLarger(BankAccount account1, BankAccount account2)
//Returns true if the balance in account1 is greater than that
//in account2. Otherwise returns false.
{
return(account1.getBalance( ) > account2.getBalance( ));
}
引用调用方式:const放最前
bool isLarger(const BankAccount& account1, const BankAccount& account2)
//Returns true if the balance in account1 is greater than that
//in account2. Otherwise, returns false.
{
return(account1.getBalance( ) > account2.getBalance( ));
}
Tip8:const修饰成员函数和这么做的理由
♦有些成员函数不需要改变成员变量,如output等成员函数,修饰词const可以用来限定调用对象不可修改(包含对象里的所有成员变量)标记成员函数时,const方放最后,这与const修饰参数不同。
函数在类内的声明:
class BankAccount
{
public:
...
void output( ) const;
...
函数在类外的定义:
void BankAccount::output( ) const
{
...
}
Tip9:const使用的连贯性
♦如果使用了const修饰了一个类类型的参数,那么所有不会改变调用对象值的成员函数都需要用const来修饰,如output成员函数调用了一个声明为const的类类型参数,那么output的声明也应该使用const来修饰。
Tip10:静态成员函数与静态数据成员
1.静态数据成员实际上是类域中的全局变量。所以,静态数据成员的定义(初始化)不应该被放在头文件中。不要试图在头文件中定义(初始化)静态数据成员。在大多数的情况下,这样做会引起重复定义这样的错误。即使加上#ifndef #define #endif或者#pragma once也不行。
2.静态数据成员被 类 的所有对象所共享,包括该类派生类的对象。即派生类对象与基类对象共享基类的静态数据成员。
♦所有静态成员变量的初始化应该在类定义之外,并且一个静态成员变量不能初始化两次。初始化方法如下:
int Server::turn = 0;
int Server::lastServed = 0;
bool Server::nowOpen = true;
♦static只在静态成员函数的声明中用到(类内),而在该成员函数的定义中则不再出现