- 我们在定义类的时候,常常需要定义一些辅助函数,比如add、read和print等。尽管这些函数的定义的操作从概念上来说属于类的接口的组成部分,但他们实际上并不属于类的本身。称作非成员函数。
- 构造函数,每个类都分别定义了它的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其初始化过程。任务时初始化类对象的数据成员,无论合适只要类的对象被创建,就会执行构造函数。
- 构造函数的名字与类名相同。但是,构造函数没有返回类型。类可以有多个构造函数,和其他的重载函数类似,不同的构造函数在参数列表上有所差别。不同于其他成员函数,构造函数不能被声明为const的,但是构造函数可以在const对象的构造过程中向它写值。
- 默认构造函数无需任何实参,只有当类没有声明任何构造函数时,编译器才会自动的生成默认构造函数。注意!如果类包含有内置类型或者符合类型的成员,则只有当这些成员全部赋予了类内的初始值时,这个类才适合于使用合成的默认构造函数。
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*n) { }
//含有三个参数的构造函数分别使用他的前两个参数初始化成员bookNo和units_sold,revenue则通过前两个计算得出初始化值
//等价sales_data(const std::string &s) : bookNo(s), units_sold(0), revenue(0) { }
sales_data(std::istream &);
}
//在C++11中,可以通过在参数列表后面写上=default来要求编译器生成构造函数
//下面两个定义中出现了冒号和冒号与花括号之间的代码
//其中花括号定义了函数体(空的)
//冒号与花括号之间出现的代码称为构造函数初始值列表
//它负责为新创建的对象的一个或者几个数据成员赋初值。
//构造函数初始值列表中的名字为成员名字,每个名字后面紧跟括号为成员初始值
//不同成员初始化用逗号隔开
#注意# 上面的两个构造函数的函数体都为空,目的是为了初始化成员变量,一旦有其他任务,则需要在函数体中定义
- 在类的外部定义构造函数时,必须指明该构造函数是哪个类的成员。
sales_data::sales_data(std::itream &)
{
read(is, *this);//read函数的作用是从is中读取一条交易信息然后存入this中
}
//sales_data::sales_data表示所定义的函数属于类sales_data,函数名字为sales_data所以为构造函数
//该构造函数的初始值列表为空,但是执行了构造函数体,因此对象依旧能被初始化
- 除了定义类的对象如何初始化之外,类还需要控制拷贝,赋值和销毁对象。当类包含vector或者string成员,则其拷贝、赋值、销毁的合成版本能够正常工作。
- 访问控制与封装:public和private。定义在public后的成员在整个程序内可被访问,public成员定义类的接口;定义在private后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private部分封装了类的实现细节。
class sales_data
{
public:
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*n) { }
//含有三个参数的构造函数分别使用他的前两个参数初始化成员bookNo和units_sold,revenue则通过前两个计算得出初始化值
//当某个数据成员被构造函数初始值列表忽略时,将与默认构造函数相同的方式隐式初始化
//等价sales_data(const std::string &s) : bookNo(s), units_sold(0), revenue(0) { }
//称为类内初始值化
sales_data(std::istream &);
std::string isbn() const { return bookNo; }
sales_data &combine (const sales_data&);
private:
double avg_price() const {return units_sold ? revenue/units_sold : 0; }
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
}
//作为接口的一部分,构造函数和部分成员函数(isbn和combine)紧跟在public后面
//作为数据成员和实现部分的函数则跟在private后面
- struct和class都可以定义一个类,区别在于默认的访问权限不同。struct默认为public访问权限,class定义的类无权限控制的话,默认为private。
- 当类的数据被定义为private后,之前定义的read、print和add函数不是成员函数无法编译。类可以允许其他类或者函数访问其非共有成员,在类中将其声明为友元函数(friend)。友元函数不是类的成员,也不受他所在区域访问控制级别的约束。一般来说,在类定义的最开始,或者最后位置集中定义友元函数,缩进与访问控制符一致。
- 友元函数的声明仅仅指定了访问权限,并非是一个函数声明,因此,在需要使用友元函数时,必须在友元函数声明外专门进行一次声明。为了使友元函数对类的用户可见,我们通常将友元函数的声明与类本身放置在同一个头文件中(类的外部)。
class sales_data
{
//为sales_data的非成员函数所作的友元函数声明
friend sales_data add(const sales_data&, const sales_data&);
friend std::istream &read(std::istream&, sales_data&);
friend std::ostream &print(std::ostream&, sales_data&);
public:
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*n) { }
sales_data(std::istream &);
std::string isbn() const { return bookNo; }
sales_data &combine (const sales_data&);
private:
double avg_price() const {return units_sold ? revenue/units_sold : 0; }
std::string bookNo;
unsigned units_sold = 0;
double revenue = 0.0;
}
//sales_data接口的非成员组成声明
sales_data add(const sales_data&, const sales_data&);
std::istream &read(std::istream&, sales_data&);
std::ostream &print(std::ostream&, sales_data&);
- 内联函数。当函数功能较小时,我们可以定义为内联函数(inline),意义在于使用函数功能时,避免函数调用的开销。
//例如,声明内联函数比较两个string对象中较短的那个
inline const string &shortString(const string &s1, const string &s2)
{
return s1.size() <= s2.size() ? s1 : s2;
}
//在调用时
cout << shortString(s1, s2) << endl;
//而实际在编译时,会展开
cout << (s1.size() < s2.size()) ? s1 : s2 << endl;
- constexpr函数。指能用于常量表达式的函数。常量表达式在编译时便得到计算,因此对声明constexpr时用到的类型必须有所限制,这些简单的类型称作“字面值类型(literal type)”,包括算术类型,引用和指针类型。注意!!!如果一个指针定义为constexpr类型,则必须初始化。
const int *p = nullptr;//该处定义的是一个指针,指向一个整型常量
constexpr int *q = nullptr;//该处定义的是常量指针,指向一个整型数