C++ primer 5th .Chapter 7 类(一) 读书笔记

C++ primer 5th .Chapter 7 类(一) 读书笔记

	7.1 定义抽象数据类型
	7.2 访问控制与封装
	7.3 类的其它特性
	7.4 类的作用域
	7.5 构造函数再探
	7.6 类的静态成员

7.1 定义抽象数据类型

Note:定义在类内部的函数是隐式的inline函数。

7.1.2定义改进Sales_data类

this指针

成员函数通过一个名为this的额外的隐式参数来访问调用它的那个对象。
任何对类成员的直接访问都被看作this的隐式引用。
Tips:this是一个常量指针,总是指向“这个”对象,不能改变this中保存的地址。

const成员函数

默认情况下,this的类型是指向类类型非常量版本的常量指针。这就使得我们不能在一个常量对象上调用普通的成员函数。在C++中紧跟在参数列表后面的const表示this是一个指向常量的指针。像这样使用const的成员函数被称作常量成员函数。
Tips:常量对象,以及常量对象的引用或指针对只能调用常量成员函数。

类作用域和成员函数

编译器对于类的处理分为两类:首先编译成员的声明,然后才轮到成员函数体。成员函数体可以随意使用类中的其他成员而无需在意这些成员出现的次序。

在类的外部定义成员函数

在类外部定义成员函数时需要在函数名前加上类名和作用域运算符。

定义一个返回this对象的函数

Sales_data& Sales_data::combine(const Sales_data &rhs)
{
	units_sold += rhs.units_sold; //把rhs的成员加到this对象的成员上
	revenue += rhs.reveune;
	return *this;//返回调用该函数的对象。
}

一般来说,当我们定义的函数类似于某个内置运算符时,应该令该函数的行为尽量模仿这个运算符。内置的赋值运算符把他左侧的对象当成左值返回,因此为了与他保持一致,combine函数必须返回引用类型。所以这里但会的是Sales_data&。

7.1.3定义类相关的非成员函数

如果函数在概念上属于类但是不定义在类中,则它一般应该与类声明(而非定义)在同一个头文件内。

定义read和print函数

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)
{
	double price = 0;
	os << item.isbn() <<" "<< item.units_sold << " "
	<< item.revenue << " "<< item.avg_price;
	return os;
}

IO类属于不能被拷贝的类型,因此我们只能通过引用来传递他们。

7.1.4 构造函数

类通过一个或多个特殊的成员函数来控制其对象的初始化过程,这些函数叫做构造函数。类可以包含多个构造函数,和其他重载函数差不多。不同于其他成员函数,构造函数不能被声明成const的。

合成的默认构造函数

如果我们的类没有显示的定义构造函数,那么编译器就会为我们隐式的定义一个默认构造函数。初始化规则如下:
1.如果存在类内的初始值,用它来初始化成员。
2.否则默认初始化。

某些类不能依赖于合成的默认构造函数

如果定义在块中的内置类型或者复合类型(如数组和指针)的对象被默认初始化,则他们的值将是未定义的。

构造函数初始值列表

Sales_data(const std::string &s):
			bookNo(s), units_sold(0), revenue(0){}

通常情况下,构造函数使用类内初始值不失为一种好的选择。

在类的外部定义构造函数

Sales_data::Sales_data(std::istream &is)
{
	read(is,*this);//从is中读取一条交易信息然后存入this对象
}

没有出现在构造函数初始值列表中的成员将通过相应的类内初始值初始化,或者执行默认初始化。

7.1.5 拷贝、赋值和析构

类还需要控制拷贝,赋值和销毁对象时发生的行为。如果我们不主动定义这些操作,则编译器会代替我们合成它们。

某些类不能依赖于合成的版本

当类需要分配类对象之外的资源时,合成版本常常会失效。使用vector或者string的类能避免分配和释放内存的复杂性。如果类包含vector或者string的成员,则其拷贝、赋值和销毁的版本能够正常工作。

7.2 访问控制与封装

·定义在public说明符之后的成员在整个程序内可被访问,public成员定义类的接口。
·定义在private说明符之后的成员可以被类的成员函数访问,但是不能被使用该类的代码访问,private部分封装了(隐藏了)类的实现细节。

使用class或者struct关键词

使用class和struct关键词定义类唯一的区别就是默认的访问权限。如果使用struct关键词,则定义在第一个访问说明符之前的成员是public的;相反,如果使用class关键字,则这些成员是private的。

7.2.1 友元

类可以允许其他类或者函数访问它的非公有成员,方法是令其他类或者函数成为他的友元。

class Sales_data{
	friend std::istream &read(std::istream &, Sales_data &);友元声明
	//...
	public:
	//...
	private:
	//...
};

友元声明只能出现在类定义的内部,但是在类内出现的具体位置不限。一般来说,最好在类定义开始或者结束前的位置集中声明友元。

封装的好处:
1.确保用户代码不会无意间破坏封装对象的状态。
2.被封装的类的具体实现细节可以随时改变,而无需调整用户级别的代码。

友元的声明仅仅指示了访问的权限,而不能代替函数声明本身。如果类的用户能够调用某个友元函数,那么我们就必须在友元声明之外再专门对函数进行一次声明。

7.3 类的其它特性

7.3.1 类成员再探

定义一个类型成员

class Screen{
public:
	typedef std::string::sizetype pos;
private:
	pos cursor = 0;
	pos height = 0, width = 0;
	std::string contents;
	
};

用来定义类型的成员必须先定义后使用,这一点与普通成员不同,因此类型成员通常出现在类开始的地方。

令成员作为内联函数

定义在类内部的成员函数是自动inline的,也可以在类的外部用inline修饰函数定义。

inline
Screen &Screen::move(pos r, pos c)
{
	pos row = r*width;
	cursor = row + c;
	return *this;
}

可变数据成员

我们希望能修改类的某个数据成员,即使是在一个const成员函数内。可以通过在变量的声明中假如mutable关键字做到这一点。一个可变数据成员永远不会是const,即使它是const对象的成员。

class Screen{
public:
	void some_member() const;
private:
	mutable size_t access_ctr;//即使在一个const对象内也能修改
};
void Screen::some_member() const
{
	++access_ctr;//保存计数值,用于记录成员函数被调用的次数
	
}

类数据成员的初始值

类内初始值必须使用=的初始化形式或者花括号括起来的直接初始化形式。

7.3.2 返回*this的成员函数

class Screen{
public:
	Screen &set(char);
	Screen &set(pos, pos, char);
};
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;//将this对象作为左值返回
}
//将光标移动到一个指定的位置,然后设置该位置的字符值
myScreen.move(4,0).set('#');
//如果move返回Screen而非Screen&
Screen temp = myScreen.move(4,0);
temp.set('#');

如果当初我们定义的返回类型不是引用,则move的返回值将是*this的副本,因此调用set只能改变临时副本,而不能改变myScreen的值。

从const成员函数返回*this

一个const成员函数如果以引用的形式返回*this,那么它的返回类型将是常量引用。

基于const的重载

因为非常量版本的函数对于常量对象是不可用的,所以我们只能在一个常量对象上调用const成员函数,而在非常量对象上调用常量版本或者非常量版本都可以,但是非常量版本是更好的匹配。

class Screen{
public:
	//根据对象是否是const重载了display函数
	Screen& display(std::ostream &os)
	{
		do_display(os);
		return *this;
	}
	const Screen& display(std::ostream &os) const
	{
		do_display(os);
		return *this;
	}
private:
	//该函数负责显示Screen的内容
	void do_display(std::ostream &os) const
	{
		os << contents;
	}
}

当一个成员调用另一个成员时,this指针在其中隐式的传递。因此,当display调用do_display时,它的this指针隐式地传递给do_display。而当display的非常量版本调用do_display时,它的this指针将隐式的从指向非常量的指针转换成常量的指针。

7.3.3 类类型

struct Fst{
	int mem;
}
struct Sec{
	int mem;
}
Fst obj1;
Sec obj2 = obj1; //错误,obj1和obj2的类型不同

即使两个类的成员列表完全一致,他们也是不同的类型。

类的声明

class Screen;

我们可以在定义类之前先声明它,这种声明叫做前向声明,对于这个类Screen来说,在它声明之后,定义之前是一个不完全类型。

对于一个类来说,在我们创建它的对象之前该类必须被定义过,而不能仅仅被声明,否则编译器就无法了解这样的对象需要多少存储空间。

7.3.4 友元再探

类还可以把其他类定义成友元,也可以把其他类的成员函数定义成友元。

类之间的友元关系

如果一个类制定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员。

令成员函数作为友元
可以把另一个类的成员函数声明成友元,但是必须明确指出这个成员函数属于哪个类。

class Screen
{
	//Window_mgr::clear必须在类之前被声明
	friend void Window_mgr::clear(ScreenIndex);
}

重载函数和友元
如果一个类想把一组重载函数声明成它的友元,他需要对这组函数中的每一个分别声明。

再次强调:友元声明只是对于访问权限的影响,不能替代正常的函数声明。

上一篇:python爬虫多进程,多线程,协程以及组合应用的效率对比--以爬取小说全文为例


下一篇:《Small Talk》读书笔记