第七章-类

#define SALES_DATA_H
#include <string>
class Sales_data {
	//友元
	friend Sales_data add(const Sales_data &lhs, const Sales_data &rhs);
	friend std::istream &read(std::istream &is, Sales_data &item);
	friend std::ostream &print(std::ostream &os, const Sales_data &item);
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 &is) { read(is, *this); }
	//成员函数
	std::string isbn() const { return bookNo; }
	Sales_data& combine (const Sales_data&);
	//数据成员
private:
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;
	inline double avg_price() const
	{
		return units_sold ? revenue / units_sold : 0;
	}
};
Sales_data add(const Sales_data &lhs, const Sales_data &rhs);
std::istream &read(std::istream &is, Sales_data &item);
std::ostream &print(std::ostream &os, const Sales_data &item);
Sales_data& Sales_data::combine(const Sales_data &rhs)
{
	units_sold += rhs.units_sold;
	revenue += rhs.revenue;
	return *this;
}
Sales_data add(const Sales_data &lhs, const Sales_data &rhs)
{
	Sales_data sum = lhs;
	sum.combine(rhs);
	return sum;
}
//IO类属于不能被拷贝的类型
//所以只能通过引用传递
std::istream &read(std::istream &is, Sales_data &item)
{
	double price = 0.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;
}
  • 默认情况下this的类型是指向非常量版本的常量指针,就像:Sales_data *const this。这样不能修改this指向的地址,但是能修改this指向的值
  • 如果将函数声明成const比如std::string Sales_data::isbn() const { ... },那指针将是const Sales_data *const this,就不会改变指向的对象。
  • 上面的代码中,bookNo定义在isbn之后,但isbn还是能使用bookNo,因为编译器是先编译成员的声明然后再编译成员函数体的,所以可以随意使用类中的成员而无须在意次序。
  • 代码中combine的作用类似于+=,调用该函数的对象代表赋值运算符左侧的对象,右侧对象通过显式的实参传入函数。因为内置的=运算符把它的左侧运算对象当成左值返回,所以为了尽量模仿这个运算符,combine函数必须返回引用类型(第六章说过“调用一个返回引用的函数得到左值”)

构造函数

	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 &is) { read(is, *this); } //没有构造函数初始值列表,但是执行了构造函数体
  • 构造函数没有返回类型且不能声明成const的
  • 创建一个类的const对象时,直到构造函数初始化完成,对象才能真正得到它的常量属性。所以在构造函数再const对象的构造过程中可以向其写值
  • 当类没有声明任何构造函数时编译器才会自动生成默认构造函数
  • 合成的默认构造函数以 1.如果存在类内初始值则用初始值初始化成员 2.没有则默认初始化成员 的规则进行初始化
  • 一旦我们定义了一些其他的构造函数,那么除非我们在定义一个默认的构造函数,否则类将没有构造函数。换句话说,类只有没有声明任何构造函数时才会自动生成默认构造函数

访问控制和封装

  • 定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问
  • 使用struct关键字,在定义第一个访问说明符之前的成员是public的;而使用class关键字的话,这些成员是private的

友元

	friend Sales_data add(const Sales_data &lhs, const Sales_data &rhs);
	friend std::istream &read(std::istream &is, Sales_data &item);
	friend std::ostream &print(std::ostream &os, const Sales_data &item);
  • 类可以允许其他类或函数访问它的非公有成员,方法是令其他类或函数成为它的友元
  • 友元声明只能出现在类定义的内部
  • 友元声明只指定了访问权限,而不是一个函数声明。如果想要类的用户能够调用某个友元函数,那就要在友元声明之外再专门对函数进行一次声明
struct X
{
friend void f();
X() { f(); } //错误!f还没被声明
void g();
void h();
};
void X::g() { return f(); } //错误!f还没被声明
void f(); //声明了定义在X中的函数
void X::h() { return f(); } //正确

类成员

  • 类可以自定义某种类型在类中的别名,但是必须先定义再使用
  • 有时想修改类的某个数据成员,即使是在一个const成员函数中。可以通过在变量的声明中加入mutable关键字来实现
class Screen
{
public:
	typedef string::size_type pos; //别名先定义再使用
	void xxx() const;
private:
	mutable size_t ctr;
};
void Screen::xxx() const
{
	++ctr; //在const中也没问题
	...
}
  • 定义在(注意是是定义不是声明)类内部的函数自动成为内联函数,在类中声明的函数如果只在类外定义加了inline那么也是内联。如果类内声明但是类外定义时不加inline则不是内联。(最好只在类外部定义的地方说明inline)

返回*this的成员函数

class Screen
{
	public:
		Screen &set(char);
		Screen &set(pos, pos, char);
		Screen &move(pos r, pos c);
};
inline Screen &Screen::set(char c)
{
	contents[cursor] = c;
	return *this;
}
inline Screen &Screen::set(pos r, pos col, char ch)
{
	contents[r * width + col] = ch;
	return *this;
}
inline Screen &Screen::move(pos r, pos c)
{
	pos row = r * width;
	cursor = row + c;
	return *this;
}
  • 因为返回的是引用,即对象本身,所以可以将一系列操作连接起来
myScreen.move(4, 0).set('#');
//等价于下面
myScreen.move(4, 0);
myScreen.set('#');
  • 如果返回的不是引用,则move返回的是*this的副本。此时上面连接起来的表达式将等价于下面这样
Screen temp = myScreen.move(4, 0);
temp.set('#'); 
//只会改变temp的内容
//而不会改变myScreen本身的内容
从const成员函数返回*this
class Screen
{
	public:
		Screen &display(ostream &os) { do_display(os); return *this; }
		const Screen &display(ostream &os) const { do_display(os); return *this; }
	private:
		void do_display(ostream &os) const {os << contents;}
  • const成员的display的this是const Screen *const this,而要返回这个指针,所以返回类型是const Screen&
  • 因为返回的是常量引用,所以不能像之前那样连接表达式
Screen myScreen;
myScreen.display(cout).set('*');
//display返回常量引用,无权set常量对象
  • 这里无const版的display调用do_display,它的this指针隐式的从指向非常量的指针转换成指向常量的指针。调用完后解引用返回普通的(非常量)引用
  • const对象调用const的display,非const对象调用非const的display
Screen myScreen(5, 3);
const Screen blank(5, 3);
myScreen.set('#').display(cout); //调用非常量版
blank.display(cout); //调用常量版本

类类型
  • 可以把类的声明和定义分离开,就和函数一样。但是在声明后定义前属于不完全类型,不完全类型使用情景受限:
    1. 可以定义指向这种类型的指针或引用
    2. 可以声明(不能定义)以不完全类型为参数或者返回类型的函数
    3. 创建它的对象前,这个类必须被定义过
    4. 因为一个类的名字出现后就被认为是声明过的,所以类允许包含指向自己的指针或引用

友元2

  • 可以将一个类指定为友元类,abc指定了友元类def,则def的成员函数可以访问abc包括非共有成员在内的所有成员
class abc
{
	friend class def;
	...
};
  • 友元不具有传递性,如果def也有自己的友元xyz,xyz并不能访问abc
  • 可以指把一个成员函数声明为友元
class abc
{
	friend void def::xxx(); //必须指出该成员函数属于哪个类
	...
};
//要令某个函数作为友元,必须仔细组织程序结构
1. 首先定义def类,其中声明xxx()函数,但不能定义它。在xxx使用abc的成员之前必须先声明abc
2. 接下来定义abc,包括对xxx()的友元声明
3. 最后定义xxx(),此时他才可以使用abc的成员
  • 友元声明只指定了访问权限,而不是一个函数声明。如果想要类的用户能够调用某个友元函数,那就要在友元声明之外再专门对函数进行一次声明
struct X
{
friend void f();
X() { f(); } //错误!f还没被声明
void g();
void h();
};
void X::g() { return f(); } //错误!f还没被声明
void f(); //声明了定义在X中的函数
void X::h() { return f(); } //正确

类的作用域

  • 一旦遇到类名,定义的剩余部分就在类的作用域中,这里的剩余部分包括参数列表和函数体,但是不包括返回类型。返回类型通常在函数名前,所以返回类型有时也要指明属于哪个类
class Window_mgr
{
public:
	ScreenIndex addScreen(const Screen&);
	...
};
Window_mgr::ScreenIndex //返回类型
Window_mgr::addScreen(const Screen &s)
{
	...
}

构造函数2

  • 构造函数初始化和赋值有所区别
//这没使用构造函数初始值,使用的是赋值
Sales_data::Sales_data(const string &s, unsigned cnt, double price)
{
	bookNo = s;
	units_sold = cnt;
	revenue = cnt * price;
}
  • 但使用赋值的构造函数不是总有效
class test
{
public:
	tets(int ii);
private:
	int i;
	const int ci;
	int &ri;
}
//这里ci和ri必须只能使用初始化,赋值版本会出错
  • 初始化const或引用类型的数据成员唯一机会就是通过构造函数初始值
  • 成员的初始化顺序和它们在类定义中的出现顺序一致
class X
{
	int i;
	int j;
public:
	X(int val) : j(val), i(j) {} //错误!i先被赋j,但是j未定义
}

委托构造函数

  • 委托构造函数参数列表必须和类中另一个构造函数匹配
class Sales_data
{
public:
	//非委托构造函数
	Sales_data(string s, unsigned cnt, double price) :
		bookNo(s), units_sold(cnt), revenue(cnt * price) {}
	//委托构造函数,委托给另一个构造函数
	Sales_data() : Sales_data("", 0, 0) {}
	Sales_data(string s) : Sales_data(s, 0, 0) {}
	Sales_data(istream &is) : Sales_data() { read(is, *this); }
}
  • 但一个构造函数委托给另一个构造函数时,受委托的构造函数初始值列表和函数体都依次执行。

隐式的类类型转换

  • 如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制
	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 &is) { read(is, *this); }
//接受一个string和istream的构造函数
//分别定义了从string和istream类型向Sales_data转换的规则

string null_book = "9-999-99999-9";
item.combine(null_book); //没有问题
//编译器用null_book自动创建了一个临时的Sales_data
//然后注意combine的参数是一个常量引用,所以可以给它传递临时量
//但是不能把const对象、字面值或需要类型转换的对象传递给普通的引用形参
item.combine(cin);
//将cin隐式转换成Sales_data,然后执行了接受istream的构造函数
//构造函数执行读入,然后创建一个临时的Sales_data,传递给combine
//combine完成后临时量随之丢弃
  • 只允许一步类类型转换
item.combine("9-999-99999-9"); //错误
//编译器只会自动执行一步类型转换,这里const char*先转换为string,再转换为类类型
item.combine(string("9-999-99999-9")); //可以
item.combine(Sales_data("9-999-99999-9")); //可以
  • 可以利用explicit阻止构造函数的隐式转换。explicit只对一个实参的构造函数有效,并且只能在类内声明构造函数时才使用,在类外部不能重复定义
	Sales_data() = default;
	explicit 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) {}
	explicit Sales_data(std::istream &is) {}
  • 使用explicit关键字声明构造函数时,它将只能以直接初始化的形式使用
Sales_data item1(null_book); //ok
//这里null_book会通过Sales_data(const std::string &s)隐式转换为一个Sales_data对象
//但是因为设置为了explicit,所以不能隐式转换
Sales_data item2 = null_book;
  • 虽然explicit阻止了隐式转换,但还是可以显式强制转换
item.combine(Sales_data(null_book));
item.combine(static_cast<Sales_data>(cin));

聚合类

满足下面4个条件的类叫聚合类

  • 1.所有成员都是public
  • 2.没有定义任何构造函数
  • 3.没有类内初始值
  • 4.没有基类、虚函数

类的静态成员

class Account
{
public:
	void calculate() { amount += amount * interestRate; }
	static double rate() { return interestRate; }
	static void rate(double);
private:
	string owner;
	double amount;
	static double interestRate;
	static double initRate();
};
//类的静态成员存在于所有对象,所以某一个对象不包含任何于静态数据成员有关的数据
//所以一个对象只有两个数据成员,owner和amount
  • 类的静态成员函数不与任何对象绑定在一起,它们不包含this指针,静态成员函数也不能声明成const
  • 使用作用域运算符直接访问静态成员
double r;
r = Account::rate();
  • 当在类的外部定义静态成员时,不能重复static关键字。该关键字只出现在类内部的声明语句
  • 因为静态数据成员不属于任何对象,所以不是由类的构造函数初始化的。必须在类外部定义和初始化每个静态成员
double Account::interestRate = initRate();
  • 类的静态成员不应该在类内初始化,但是有个例外。可以让静态成员是字面值常量类型的constexpr,然后就能在类内初始化了,但是初始值必须是常量表达式
static constexpr int period = 30;
  • 静态成员的类型可以是所属的类类型,而且可以当作默认实参
class Screen
{
public:
	Screen& clear(char c = bkground); //静态成员作为默认实参
private:
	static const char bkground;
	static Screen smem; //自己所属的类类型
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值