C++ Primer第七章②
C++ Primer
第七章 类
构造函数
每个类都分别定义了其对象初始化的方式,就是通过构造函数来控制其对象的初始化过程;构造函数的任务是:初始化对象的数据成员,只要类的对象被创建,就会执行构造函数。
构造函数非常复杂,这一节我们只介绍些基础的。
构造函数的名字和类名相同且没有返回类型,可以重载。
当我们创建类的一个const对象时,直到构造函数完成初始化过程对象才算是常量,所以构造函数在const对象的构造过程中可以向其写值。
你可以看到,上面我用了很多粗体,因为真的都是重要的金玉良言。
读到这不知道你有没有疑问,我们前面的那个Sales_data类并没有搞这个什么构造函数,不是说也可以完成项目拿到2000块吗?比如我们写过这样的代码:
Sales_data current;
那这个current是怎么初始化的呢?
这是我们编译器的功劳:如果我们的类没有显式地定义构造函数,编译器就会为我们隐式地定义一个默认构造函数,是不是很6,那这个默认构造函数是怎么初始化数据成员呢?
- 如果存在类内的初始值,用它来初始化成员
unsigned units_sold = 0; //售出册数 double revenue = 0; //总销售收入
- 否则,默认初始化该成员:bookNo为空字符串
最好是我们不用编译器默认生成构造函数,原因如下:
- 因为我们自己才知道我们想把数据成员初始化成什么,
- 有些成员比如指针默认初始化,值未定义会出问题,
- 还有就是类包含类,被包含的那个没有类没有默认构造函数,那就报错了
- 编译器只有在我们没有定义构造函数时,才会生成默认构造函数,也就是说,如果我们定义了一个构造函数,且它不是默认构造函数,那我们就没有默认构造函数可以使用了。
最保险就是自己亲力亲为。
当然,非常简单的类确实可以偷懒用编译器默认初始化
总之,记住一条,所有的行为都要在你掌控之中,即便是编译器生成默认构造函数,你也知道它具体干了什么。
我们来改造一下Sales_data类,给它搞几个构造函数:
struct Sales_data
{
Sales_data() = default;
Sales_data(const string &s) : bookNo(s) {}
Sales_data(const string &s, unsigned n, double p) :
(bookNo(s), units_sold(n), revenue(p*n)) {};
Sales_data(istream &);
}
是不是很难懂啊,因为还有些知识没介绍,一个个来
= default
不接受任何实参的构造函数是默认构造函数,所以第一个是默认构造函数,= default的意思是,我们要求编译器生成构造函数。= default在类内部是内联,在类外部不是内联。
Sales_data(const string &s) : bookNo(s) {}
开始的s是传入参数,s用来初始化成员变量bookNo,这就是个简便写法,看得懂就行,等价于:
Sales_data(const string &s)
{
bookNo = s;
}
在类的外部定义构造函数
我们来在类外面实现一下最后一个构造函数:
Sales_data::Sales_data(istream &is)
{
read(is, *this); //调用原来的read函数从is中读取一条交易信息存入this所指的对象中
}
我们除了要初始化对象之外,还要控制拷贝、赋值和销毁对象时发生的行为,怎么样,再一次体会到了C++赋予程序员的自由以及责任吧,不过别紧张,现在暂时还不学,暂时都用编译器合成的。
访问控制与封装
这个太简单,我就不啰嗦了
class Sales_data
{
private:
//数据成员
string bookNo; //书号
unsigned units_sold = 0; //售出册数
double revenue = 0; //总销售收入
public:
//成员函数
string isbn() const //返回书本isbn号,这里的const待会解释
{
return bookNo;
}
Sales_data& combine(const Sales_data&); //函数声明
double avg_price() const; //返回售出书籍的均价,这里的const也待会解释
}
private后面的成员只能被类的成员函数访问,不能被使用该类的代码访问
graph LR
struct-->默认定义在第一个访问说明符之前的是public
graph LR
Aclass-->默认定义在第一个访问说明符之前的是privat
上面的Aclass就是class,因为格式问题 ,struct和class其他没有任何差别
那么问题来了,Sales_data类的数据成员都是private的,那我们的类外面的函数read等函数就无法编译了,因为它们无法访问成员变量。
解决方法就是让函数跟类做朋友:
struct Sales_data
{
friend istream &read(istream&, Sales_data&);
private:
//数据成员
string bookNo; //书号
unsigned units_sold = 0; //售出册数
double revenue = 0; //总销售收入
public:
//成员函数
string isbn() const //返回书本isbn号,这里的const待会解释
{
return bookNo;
}
Sales_data& combine(const Sales_data&); //函数声明
double avg_price() const; //返回售出书籍的均价,这里的const也待会解释
}
istream &read(istream&, Sales_data&) //声明在类外且无作用域符号,为非成员函数
{
double price = 0;
is >> item.bookNo >> item.units_sold >> price;
item.revenue = price * item.units_sold;
return is;
}
这里read函数的位置很暧昧,首先它是在类里面声明的,并且C++规定友元只能在类内部声明,但是它不能被public等访问控制符修饰,因为它不是类的成员函数,这样之后,我们在类外面声明定义它。
注意我这句话啊,在外面声明定义它,我们不是已经在类内部声明过了吗,外面只是定义而已啊,你是不是写错了?不是的,我故意这么写的,因为,友元的声明不同于一般的函数声明,它只是用来指定访问权限,其实没有函数声明的作用,所以,我们在类外部是要声明定义它的(当然,这个说法也不是很正确,不过我就这么理解了)