7-1 类的定义与构造函数
7.1.1 定义Sale_data类
目标代码
弄懂下面的代码
#include<iostream>
using namespace std;
struct Sale_data{
//关于Sale_data对象的操作函数
string ibsn() const {return bookNo;}
Sale_data& combine(Sale_data& );
double avg_price() const;
//数据成员
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
Sale_data& Sale_data::combine(Sale_data& trans){
units_sold += trans.units_sold;
revenue += trans.revenue;
return *this;
}
double Sale_data::avg_price() const{
if(units_sold != 0)
return revenue/units_sold;
else
return 0;
}
//非成员函数接口
istream &read (istream &, Sale_data &);
ostream &print(ostream &, Sale_data &);
成员函数可以定义在类的内部,也可以定义在类的外部,但定义在外部需要加上定位符
引入this
每一个成员函数都会有一个隐式参数来访问调用对象的地址,this
如string ibsn() const
实际上被编译为string ibsn(&total) const
total
是一个Sale_data对象
this 是调用对象的地址,即非常量指针,而我们知道,如果调用对象是一个常量对象,那么,this就必须是一个常量指针才能指向该调用对象,而this又是隐式参数,那么如何使this变成一个const指针呢?
C++语言的做法是允许把 const关键字放在成员函数的参数列表之后
而this为const指针的成员函数就成为常量成员函数
常量对象,常量对象的引用和指针都只能使用常量成员函数
成员的编译顺序
先编译成员,再编译成员函数
用this返回调用对象的引用
Sale_data& conbine(Sale_data& )
的实现中return *this;
表示返回调用对象。
注:参数使用引用传入是因为拷贝传入需要复制,会有额外开销
为什么需要返回类型是对象的引用而不是非引用?
为了链式使用成员函数
-
如果
conbine
返回的是引用类型,那么Sale_data s1,s2,s3; s1.conbine(s2).conbine(s3);
表示把s2,s3加到s1,和我们的预期结果一致
-
如果
conbine
返回的是非引用类型那么s1.conbine(s2).conbine(s3)
等价于Sale_data temp = s1.conbine(s2); tmp.conbine(s3);
也就是说,只有s2加到了s1上,而s3加到了一个临时变量上
一个const成员函数以引用形式返回*this,那么该引用必须为常量引用
非成员函数
实现与测试函数
istream &read(istream &is, Sale_data &item){
double price = 0;
is >> item.bookNo >>item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream &print(ostream &os, Sale_data &item){
os << item.bookNo << " " << item.units_sold << " "
<< item.revenue<<endl;
return os;
}
int main(){
Sale_data item;
read(cin, item);
print(cout, item);
return 0;
}
read
和print
的写法可以视为模板直接记下来,重点是函数三要素
7.1.2 构造函数
目标代码
struct Sale_data{
//新增的构造函数
Sale_data() = default;
Sale_data(string s) : bookNo(s) {}
Sale_data(string s, unsigned n, double p) :
bookNo(s), units_sold(n), revenue(n*p) {}
Sale_data(istream &);
/*
. . .
. . .
*/
}
//非成员函数
//定义在外部的构造函数
Sale_data::Sale_data(istream &is){
read(is, *this);
}
//外部成员函数
构造函数的特点
-
无返回值
-
函数名称与类名相同
-
(支持重载)
-
(函数体常为空)
-
不能被声明为const 如
Sale_data const(string &s) : bookNo(s) {}
合成的默认构造函数
默认构造函数
没有实参的构造函数称为默认构造函数,编译器创建的默认构造函数称为合成的默认构造函数
默认构造函数按如下规则进行初始化:
- 如果存在类中初始值,用类中初始值初始化
- 否则,默认初始化
类内初始值只能用“=”或者“{}”
某些类不能依赖合成的默认构造函数
- 编译器只有发现一个类没有任何构造函数时,才会为它添加合成的默认构造函数。也就是说,如果已有一个非默认构造函数时,如果不手动添加默认构造函数的话,这个类是没有构造函数的!
- 合成的默认构造函数由于在没有值初始化时执行默认初始化,所以块中的内置类型和复合类型的初始值是未定义的。
- 第三个原因是有的时候编译器不能为某些类合成默认的构造函数。例如,如果类中包含一个其他类类型的成员且这个成员的类型没有默认构造函数,那么编译器将无法初始化该成员。对于这样的类来说,我们必须自定义默认构造函数,否则该类将没有可用的默认构造函数。在13.1.6节(第449页)中我们将看到还有其他一些情况也会导致编译器无法生成一个正确的默认构造函数。
=default 的含义
Sale_data() = default
表示手动添加默认构造函数,且该默认构造函数的作用等价于合成的默认构造函数
构造函数的初始值列表
Sale_data(string s) : bookNo(s) {}
- 实参为string s
- 表示用s来初始化bookNo
- 其他成员仍按默认初始化处理
Sale_data(string s, unsigned n, double p) : bookNo(s), units_sold(n), revenue(n*p) {}
- 实参为string s, unsigned n, double p
- 表示
- 用s初始化bookNo
- 用n初始化units_sold
- 用n*p初始化revenue
有一点需要注意,在上面的两个构造函数中函数体都是空的。这是因为这些构造函数的唯一目的就是为数据成员赋初值,一旦没有其他任务需要执行,函数体也就为空了。
在类的外部定义的构造函数
//定义在外部的构造函数
Sale_data::Sale_data(istream &is){
read(is, *this);
}
由于构造函数是定义在类的外部,所以需要定位符Sale_data::
由于是构造函数,所以函数名也为Sale_data
,故为Sale_data::Sale_data
这个构造函数没有构造函数初始值列表,或者讲得史准确一点,它的构造函数初始值列表是空的。尽管构造函数初始值列表是空的,但是由于执行了构造函数体,所以对象的成员仍然能被初始化。
7.1.3 完整代码1
#include<iostream>
using namespace std;
struct Sale_data{
//新增的构造函数
Sale_data() = default;
Sale_data (string s) : bookNo(s) {}
Sale_data(string s, unsigned n, double p) :
bookNo(s), units_sold(n), revenue(n*p) {}
Sale_data(istream &);
//关于Sale_data对象的操作函数
string ibsn() const {return bookNo;}
Sale_data& combine(Sale_data& );
double avg_price() const;
//数据成员
string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
};
//非成员函数接口
istream &read (istream &, Sale_data &);
ostream &print(ostream &, Sale_data &);
//外部构造函数(可以写在内部)
Sale_data::Sale_data(istream &is){
read(is, *this);
}
istream &read(istream &is, Sale_data &item){
double price = 0;
is >> item.bookNo >>item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
ostream &print(ostream &os, Sale_data &item){
os << item.bookNo << " " << item.units_sold << " "
<< item.revenue<<endl;
return os;
}
//外部成员函数(可以写在内部)
Sale_data& Sale_data::combine(const Sale_data& trans){
units_sold += trans.units_sold;
revenue += trans.revenue;
return *this;
}
double Sale_data::avg_price() const{
if(units_sold != 0)
return revenue/units_sold;
else
return 0;
}
int main(){
Sale_data item(cin);
print(cout, item);
return 0;
}
类的结构图
public与private见 7-2 笔记