目录
基础知识(原书P235)
入门知识
1.每个类都分别定义了它的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数。构造函数的任务是初始化类对象的数据成员,无论何时只要类的对象被创建,就会执行构造函数。
2.构造函数的名字和类名相同。和其他函数不同的是,构造函数没有返回类型。
3.类可以包含多个构造函数,和其他重载函数差不多,不同的构造函数必须在参数数量或参数类型上有所区别。
4.不同于其他成员函数,构造函数不能被声明为const的。当我们创建类的一个const对象时,直到构造函数完成初始化过程,对象才能真正取得其“常量”属性。因此,构造函数可以在const对象构造过程中向其写值。
默认构造函数
特殊性:
1.默认构造函数无需任何实参
2.如果我们没有显示定义构造函数,编译器就会为我们隐式定义一个默认构造函数
编译器创建的构造函数又被称为合成的默认函数,它按照如下规则初始化类的数据成员:
a.如果存在类内的初始值,用它来初始化成员
b.否则,默认初始化该成员
举个栗子:
struct Sales_data
{
Sales_data()=default;
Sales_data(const std::string &s): bookNo(s){}
Sales_data(const std::string &s,unsigned n,double p):
bookNo(s),units_sold(n),revenue(p){}
Sales_data(std::istream &);
//之前已有的其他成员
std::string isbn() const{return bookNo;}
Sales_data& combine(const Sales_data&);
double avg_price() const;
std::string bookNo;
unsigned units_sold=0;
double revenue=0.0;
}
C++11新标准中,如果我们需要默认的行为,那么可以通过在参数列表后面写上=default来要求编译器生成构造函数。
默认构造函数的作用
当对象被默认初始化时自动执行默认构造函数。默认初始化在以下情况下发生:
1.当我们在块作用域内不使用任何初始值定义一个非静态变量或数组时
2.当一个类本身含有类类型的成员且使用合成的默认构造函数时
3.当类类型的成员没有在构造函数初始值列表中显式初始化时
这上边的东西还不大理解,插个眼~。。。
值初始化在以下情况下发生:
1.数组初始化的过程中我们提供的初始值数量少于数组的大小时
2.当我们不使用初始值定义一个局部静态变量时
3.当我们通过书写T()的表达式显式地请求值初始化时,其中T是类型名
这上边的东西还不大理解,先插个眼~
Note:在实际中
在类的外部定义构造函数
接着上边那个栗子:
Sales_data::Sales_data(std::istream &is)
{
read(is, *this);//read函数的作用时从is中读取一条交易信息
//然后存入this对象中
}
构造函数再探:原书P258
构造函数初始值列表
如果没有在构造函数的初始值列表中显式初始化成员,则该成员将在构造函数体之前执行默认初始化。
构造函数的初始值有时必不可少
有时我们可以忽略数据成员初始化和赋值之间的差异,但并非总能这样。如果成员是const或者是引用的话,必须将其初始化。类似的,当成员属于某种类型且该类没有定义默认构造函数时,也必须将这个成员初始化。
成员初始化的顺序
成员的舒适化顺序与它们在类定义中的出现顺序一致:第一个成员先被初始化,然后第二个,以此类推。
委托构造函数
一个委托构造函数使用它所属类的其他构造函数执行他自己 的初始化过程,或者说他把他自己的一些或所有职责委托给了其他构造函数
实例如下:
#include <string>
#include <iostream>
using namespace std;
struct Sales_data;
std::istream &read(std::istream &is, Sales_data &item);
std::ostream &print(std::ostream &os, const Sales_data &item);
Sales_data add(const Sales_data &lhs, const Sales_data &rhs);
struct Sales_data
{
friend std::istream &read(std::istream &is, Sales_data &item);
friend std::ostream &print(std::ostream &os, const Sales_data &item);
friend Sales_data add(const Sales_data &lhs, const Sales_data &rhs);
public:
Sales_data(const std::string &s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(p*n)
{
std::cout << "Sales_data(const std::string &s, unsigned n, double p)" << std::endl;
}
Sales_data() : Sales_data("", 0, 0)
{
std::cout << "Sales_data() : Sales_data(\"\", 0, 0)" << std::endl;
}
Sales_data(const std::string &s) : Sales_data(s, 0, 0)
{
std::cout << "Sales_data(const std::string &s) : Sales_data" << std::endl;
}
Sales_data(std::istream &is) : Sales_data()
{
read(is, *this);
std::cout << "Sales_data(std::istream &is) : Sales_data()" << std::endl;
}
std::string isbn() const { return bookNo; }
Sales_data& combine(const Sales_data&);
private:
inline double avg_price() const;
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
Sales_data& Sales_data::combine(const Sales_data &rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
inline double Sales_data::avg_price() const
{
if (units_sold)
return revenue / units_sold;
else
return 0;
}
std::istream &read(std::istream &is, Sales_data &item)
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
std::ostream &print(std::ostream &os, const Sales_data &item)
{
os << item.isbn() << " " << item.units_sold << " " << item.revenue << " " << item.avg_price();
return os;
}
Sales_data add(const Sales_data &lhs, const Sales_data &rhs)
{
Sales_data sum = lhs;
sum.combine(rhs);
return sum;
}
int main() {
Sales_data a("0-1-999-9", 2, 10);
cout << endl;
Sales_data b;
cout << endl;
Sales_data c("0-1-999-9");
cout << endl;
Sales_data d(cin);
system("pause");
return 0;
}
隐式的类类型转换P263
c++语言在内置类型之间定义了几种自动转换规则。同样的,我们也能为类定义隐式转换规则。如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制。有时我们把这种构造函数称作转换构造函数。(p514有将介绍如何将一种类类型转换为另一种类类型的转换规则)
在上面的Sales_data中,接受string的构造函数和接受istream的构造函数分别定义了从这两种类型向Sales_data隐式转换的规则。也就是说,在需要使用Sales_data的地方,我们可以使用string或者istream作为替代:
string null_book="9-999-99999-9";
//构造一个临时的Sales_data对象
//该对象的units_sold和revenue等于0,bookNo等于null_book
item.combine(null_book);
只允许一步类类型转换
编译器只会自动地执行一步类型转换。例如,因为下面的代码隐式地使用了两种转换规则,所以它是错误的:
//错误:需要用户定义的两种转换:
//1.把"9-999-99999-9"转换成string
//2.再把这个临时的string转换成Sales_data
item.combine("9-999-99999-9");
要想完成上述转换可以显式把字符串转换成string或者Sales_data对象:
//显式转换成string 隐式转换成Sales_data
item.combine(string("9-999-99999-9"));
//隐式转换成string 显式转换成Sales_data
item.combine(Sales_data("9-999-99999-9"));
抑制构造函数定义的隐式转换
在要求隐式转换的程序上下文中,我们可以通过将构造函数声明为explicit加以阻止
Sales_data
{
public:
Sales_data()=default;
Sales_data(const std::string &s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(p*n) {}
explicit Sales_data(const std::string &s):book(s){}
explicit Sales_data(std::istream&);
//其他部分与之前的版本一致
}
此时,没有任何构造函数能用于隐式地创建Sales_data对象,之前的两种用法都无法通过编译:
item.combine(null_book);
item.combine(cin);
1.关键字explicit只对一个实参的构造函数有效
2.只能在类内声明构造函数时使用explicit函数,在类外部定义时不应该重复
explicit构造函数只能用于直接初始化
发生隐式转换的一种情况是我们使用拷贝形式的初始化时(=),我们只能使用直接初始化而不能使用explicit构造函数:
Sales_data item(null_book);//正确
Sales_data item=null_book;//错误,不能将explicit构造函数用于拷贝形式的初始化
为转换显示地使用构造函数
item.combine(Sales_data(null_book));
item.combine(static<Sales_data>(cin));
1.在第一个调用中我们直接使用Sales_data的构造函数,该调用通过接受string的构造函数创建了一个临时的Sales_data类对象。
2.在第二个调用中,我们使用static_cast执行了显式的而非隐式的转换。
书里边写p514 551 689和第13章还有构造函数的更多知识,先插个眼~