构造函数 是特殊的成员函数, 只要创建类类型的新对象, 都要执行构造函数。 构造函数的工作是保证每个对象的数据成员具有合适的初始值。
如下面:
class Sales_item {
public:
sales_iterm(): units_sold(0), revenue(0.0) {}
};
构造函数的一些特性:
和类的名字一样,没有返回值, 可以有形参,也可以没有形参
构造函数的重载:
但有个问题是,我们如何知道应该定义哪个或多少个构造函数?
根据需要制定的方式来构造函数,必要时可以添加
系统根据实参来决定使用哪个构造函数。
构造函数自动执行:
只要创建该类型的一个对象, 编译器就运行一个构造函数。
构造函数不能声明为const, 如:
class sales_item {
public:
Sales_item() const; // error
};
构造函数的初始化式:
与其它任何函数一样,构造函数具有名字、行参表和函数体。 但与其它函数不同的是, 构造函数也可以包含一个构造函数初始化列表:
Sales_item :: Sales_item (const string &book): isbn(book), units_sold(0), revenue(0.0) {}
构造函数可以定义在类的内部或外部, 构造函数初始化式只在构造函数的定义中而不是声明中指定。
一些隐式初始化:
Sales_item::Sales_item(const string &book)
{
isbn = book;
units_sold = 0;
revenue = 0.0;
}
从概念 上讲, 可以认为构造函数分两个阶段执行: 1 初始化阶段, 2 普通的计算阶段, 计算阶段由构造函数体中的所有语句组成
在构造函数初始化列表中没有显示提及的每个成员, 使用与初始化变量相同的规则来进行初始化: 内置或复合类型的成员的初始值依赖于对象的作用域: 在局部作用域中这些成员不被初始化,而在全局作用域中它们被初始化为0。
有时需要构造函数初始化列表:
有些成员必须在构造函数中进行初始化。 对于这样的成员,在构造函数函数体中对它们赋值不起作用。 其中包括:没有默认构造函数的类类型的成员,以及const或引用类型的成员。
如:
class ConstRef {
public:
ConstRef (int ii);
private:
int i;
const int ci;
int &ri;
};
// 错误的赋值方法:
ConstRef::ConstRef(int ii)
{
i = ii;
ci = ii ; // 这里 ci是const类型,不能被赋值
ri = i; // 这里 ri是引用类型, 不属于该类对象
}
正确的赋值方法:
ConstRef:: ConstRef(int ii) : i(ii), ci (ii), ri(i){}
其实, 初始化和赋值 严格来讲都是低效率的; 数据成员可能已经被直接初始化了, 还要对它进行初始化和赋值。
但是,某些类成员 必须得初始化 如const 或引用类型成员,以及 没有默认构造函数的类类型的任何成员
初始化 最好不要用一个类成员来初始化另一个类成员
初始化式可以是任意表达式
如: Sales_item (const std::string &book, int cnt, double price):
isbn(book), units_sold(cnt), revenue(cnt * price) {}
isbn(book), units_sold(cnt), revenue(cnt * price) {}
这里,由于string是一个类,因此 book是类类型成员,
类类型的数据成员的初始化式:
如: Sales_item (): isbn(10, '9'), units_sold(0), revenue(0,0){}
默认的构造函数:
1 合成的默认构造函数:
当有类类型的成员变量时,就出现了合成的默认构造函数。
内置和复合类型的成员,如指针和数组, 只对定义在全局作用域中的对象才初始化, 也就是如果定义在main函数中的对象,就可以不初始化。 这是因为,定义在全局中的变量一般都是静态编译的,而不是动态执行的,它是需要存储在堆或者静态区的。因此,必须得初始化 。
此外, 每个构造函数应该为每个内置或复合类型的成员提供初始化式,没有初始化式的成员处于未定义的状态。 除了作为赋值的目标之外,以任何方式使用一个未定义的成员都是错误的。如果每个成员的状态已知, 则可以区分空对象和具有实际值的对象。
类通常应定义一个默认构造函数:
如果一个类定义了构造函数,那么编译器将不合成默认构造函数。 NoDefault没有默认构造函数,意味着:
1 必须通过定义的构造函数来显示的初始化NoDefault成员
2 编译器不会为具有NoDefault类型成员的类合成默认构造函数。 如果这样的类希望提供默认构造函数,就必须显示地定义
3 NoDefault类型不能用作动态分配数组的元素类型
4 NoDefault 类型的静态分配数组必须为每个元素提供一个显示的初始化。
5 不能与容器结合使用
隐式类类型转换:
这个是说,当我们调用某个类的函数时,我们向这个函数传递了一个参数,而该类的对象会首先调用它的构造函数,该构造函数有可能一句这个传递的参数来进行初始化对象, 而后,当该函数结束,时, 构造的该对象就不能再访问了,它是一个完全丢弃的对象。
1 抑制由构造函数定义的隐式转换:
可以通过 将构造函数声明为explicit, 来防止在需要隐式转换的上下文中使用构造函数:
class Sales_item {
public:
explicit Sales_item (const std::string &book = ""): isbn(book), units_sold(0), revenue(0.0) {}
explicit Sales_item(std::istream &is);
}
explicit 关键字只能用于类内部的构造函数声明上。
定义了explicit的构造函数的类, 如果再直接没有初始化而给函数传递一个参数,可能就不行了。
2 为转换而显示地使用构造函数:
string null_book = "9-99-99999-9";
item.same_isbn(Sales_item(null_book); // 那就只能通过这种方式 来给函数传递参数了。
类成员的显示初始化:
对于没有定义构造函数并且其全体数据成员 均为public类, 可以采用如下方式:
结构体的初始化:
struct Data {
int ival;
char *ptr;
};
都初始化为0
Data vall = {0 , 0};
但是, 显示初始化类类型对象的成员有三个重大的缺点:
1 要求类的全体数据成员都是public
2 程序员的工作加重了
3 如果增加和删除一个成员,那么所有初始化的对象都要更新。
最佳方式,要定义一个默认构造函数, 允许编译器自动运行那个构造函数。 以保证每个类对象在初次使用之前正确地初始化。