包括:类型成员、类的成员的类内初始值,可变数据成员、内敛成员函数、从成员函数返回*this、关于如何定义并且使用类类型以及友元类的更多知识。
为了展示这些新的特性,我们需要定义一对相互关联的类,分别是Screen和Window_mgr。
Screen表示显示器中的一个窗口,每个Screen包含一个用于保存Screen内容的string成员和三个string::size_type类型成员。分别表示光标的位置和屏幕的高和宽。
除了定义数据的函数成员之外,类还可以自定义某种类型在函数内的别名。这种别名也有访问限制,public or private。
class Screen{
public:
typedef string::size_type pos;
private:
pos curspr = 0;
pos hegiht =0,width = 0;
string contents;
};
两个需要注意的地方:
关于pos的声明有两点需要注意:
为了使类更加使用,还要添加一个构造函数令用户定义屏幕的尺寸和内容,以及其它两个成员,负责移动光标,读取该位置的字符。
class Screen {
public:
typedef string::size_type pos;
Screen() = default; //如果我们再提供构造函数之后仍然需要默认构造函数,就要显示的声明出来。
Screen(pos ht, pos wd, char c) :hegiht(ht), width(wd), contents(ht*wd, c){} //cursor类内默认初始化了
char get() const { return contents[cursor]; } //隐式内敛函数
inline char get(pos ht, pos wd) const;//显示内敛函数
Screen &move(pos r, pos c); //在之后被设置为内敛
private:
pos cursor = 0;
pos hegiht = 0, width = 0;
string contents;
};
在类中,常有一些规模较小的函数适合被声明成内敛函数。定义在类内部的成员函数自动inline。
我们可以在类的内部把inline作为声明的一部分显示的声明成员函数,同样的,也能在类的外部用inline关键字修饰函数的定义。
inline Screen &move(pos r, pos c){
pos row = r*width; //计算行的位置?
cursor = row + c;//在行内将光标移动到指定的列
return *this;//以左值的形式返回对象
}
虽然我们无需在声明和定义同时说明inline,但其实这么做是合法的,不过,最好只在类外部定义的地方说明inline,这样可以使类更容易理解。
NOTE:和我们在头文件中定义inline函数的原因一样(P214),inline成员函数也应该与相应的类定义在同一头文件中。
Screen myScreen;
char ch = myScreen.get();
char cha = myScreen.get(3, 4);
有时(但并不频繁)会发生这样一种情况,我们希望能修改类的某个数据成员。即使在一个const成员函数内,可以通过变量的声明中加入mutable关键字可以做到这一点。
一个可变数据成员(mutable data member )永远不会是const,即使它是一个const对象成员。因此一个const成员函数可以改变一个可变成员的值。举个例子,我们将给Screen添加一个名为access_ctr的可变成员,通过它我们可以追踪每个Screen的成员函数被调用了多少次:
class Screen{
public:
void some_member() const;
private:
mutable size_t access_ctr;//即使一个const对象内也能被修改
};
void Screen::some_member() const
{
++access_str; //保存一个计数值,用来记录成员函数被调用的次数
}
尽管some_member()是一个const成员函数,它仍然能够改变access_ctr的值。access_ctr是一个可变成员,因此任何成员函数,包括const函数在内部都可以修改它的值。
TIPS:const函数之中无法修改变量的值。
在定义好Screen类之后,我们将继续定义一个窗口管理类Window_mgr 并用它表示显示器的一组Screen。这个类将包含一个Screen类型的vector,每个元素表示一个特定的Screen。默认情况下,我们希望Window_mgr类开始时总是拥有一个默认初始化Screen。在C++11中,最好的方式就是这个默认值声明成一个类内初始值。
class Window_mgr {
private:
//默认情况下,一条window_mgr包含一个标准尺寸的空白Screen
vector<Screen> screens{ Screen(24,80,' ') };
};
当我们初始化类类型的成员时,需要为构造函数传递一个符合成员类型的实参。如我们之前所知,类内初始值必须使用=初始化形式或者使用花括号括起来的直接初始化形式。
NOTE:当我们提供一个类内初始值时,必须使用=或者花括号表示。
接下来添加一些函数,它们负责设置光标所在位置的字符(其他任意给定位置的字符):
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;
}
和move操作一样,我们set成员的返回值是调用set的对象的引用。返回引用的函数是左值。意味着这些函数返回的是对象本身而非对象的副本。
myScreen.move(4,0).set('#');
如果我们令move和set返回Screen而非Screen&,则上述语句的行为将大为不同。等价于:
//如果move返回Screen而不是Screen&
Screen temp = myScreen.move(4,0);
temp.set('#');
假如我们定义返回类型不引用,则move的返回值将是*this的副本(P201),因此调用set只能改变临时副本,不能改变myScreen的值。
接下来,我们将继续添加一个名为display的操作,负责打印Screen的内容。我们希望这个函数能和move以及set出现在同一序列中,因此类似于move和set,display函数也应该返回执行它的对象的引用。
从逻辑上将,显示一个Screen并不需要改变它的内容,因此我们令display为一个const成员,此时this将是一个指向const的指针而*this是const对象。display的返回类型应该是const Sales_data&。然而如果令display返回一个const的引用,则我们将不能把display嵌入到一组动作的序列中去:
Screen myScreen;
//如果display返回常量引用,则调用set将引发错误
myScreen.display(cout).set('*');
即使myScreen是个非常量对象,对set的调用也无法通过编译。问题在于display的const版本返回的是常量引用,而我们显然无法set一个常量对象。
NOTE:一个const成员函数如果以引用的形式返回*this,那么它的返回类型将是一个常量引用。
通过区分成员函数是否是const的,我们可以对其进行重载,因为非常量版本的函数对于常量对象是不可用的,所以我们只能在一个常量对象上调用const成员函数;另一方便,虽然在非常量对象调用常量版本或非常量版本,但显然是非常量版本是一个更好的匹配。
根据指针参数是否指向const(P208)而重载函数。
在下面的例子中,我们将定义一个名为do_display的私有成员,有它否则打印Screen的实际工作。所有display操作都将调用这个函数,然后返回执行操作的对象:
class Screen {
public:
//根据对象是否是const重载了display函数
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;
}
};
当一个成员调用另一个成员时,this指针在其中隐式传递。因此当display调用do_display时候,它的this指针隐式地传递给do_display。而当display的非常量版本调用do_display,它将this指针隐式从非常量的指针转换成指向常量的指针。
当do_display完成后,display函数各自返回解引用this所得的对象。在非常量版本中,this指向一个非常量的对象,在const成员返回一个常量引用。
当我们在某个对象上调用display时,该对象是否是const决定了应该调用display的哪个版本:
Screen myScreen(5,4);
const Screen blank(5,3);
myScreen.set('#').dispaly(cout);
blank.display(cout);
建议:对于公共代码使用私有功能函数
为什么我们要费力定义一个单独的do_display函数。毕竟对于do_display的调用并不比do_display函数内部所做的操作简单多少。为什么还要这么做呢?实际上我们处于一下原因:
每个类定义了唯一的类型。
NOTE:即使两个类的成员列表完全一致,它们也是不同的类型。
类的声明:
我们也能仅仅声明类而暂时不定义它。
这种声明被称作前向声明(forward declaration),它向程序中引入名字Screen并且指明Screen是一种类类型。对于类Screen来说,在它声明之后定义之前是一个不完全类型,此时我们知道Screen是一个类类型,但是不清楚它到底包含哪些成员。
对于一个类来说,在我们创建它的对象之前该类必须被定义过,而不能仅仅被声明。否则编译器就不能了解这样的对象需要多少存储空间,类似的,类必须首先被定义,然后才能引用或者指针访问其成员。如果类没有定义,编译器就不清楚该类到底有多少成员。
在7.6节(P268)中我们将描述一种例外的情况:直到类被定义之后数据成员才能被声明成这种类类型。换句话说,我们必须首先完成类的定义,然后编译器才能知道存储该数据成员需要多少空间。因为只有当类全部完成后类才算被定义,所以一个类的成员类型不能是该类自己。然而,一旦一个类的名字出现过后,它就被认为是声明过了(但是尚未定义),因此类允许包含指向它自身类型的引用和指针:
class Link_screen {
Screen window;
Link_screen *next;
Link_screen *prev;
};
总结类类型:我佛了这么多书上写的!大多数都是解释!我看实际操作有用的就一句“一个类中的成员类型不能是它自己,但是可以是指向它的引用或指针”。
类之间的友元关系
举个友元类的例子,Window_mgr类的某些成员可能需要访问它管理的Screen类的内部数据。如我们需要Window_mgr添加一个clear成员,把指定Screen的内容设为空白。为了完成这操作,clear需要访问Screen的私有成员;而这种访问如果合法,Screen需要把Window_mgr指定成它的友元。
class Screen{
friend class Window_mgr;
//Window_mgr的成员可以访问Screen类的私有成分
};
如果一个类指定了友元类,则友元类的成员函数可以访问此类包括非公有成员在内的所有成员。通过上面的声明,Window_mgr被指定为Screen的友元,因此我们可以将Window_mgr的clear成员写成如下的形式:
class Window_mgr{
public:
using ScreenIndex = vector<Screen>::size_type;
void clear(ScreenIndex );//按照编号将指定的Screen重置为空白
private:
vector<Screen> screens{Screen(24,80,' ')};
};
void Window_mgr::clear(ScreenIndex i){
Screen& s = screen[i];
s.contents = string(s.height * s.width ,' ');
};
如果clear不是Screen的友元,上面的代码将无法通过编译。clear()不能访问Screen的私有成员:height、width和contents。
而当Screen声明Window_mgr为其友元之后,则Screen所有成员对Wind_mgr就都变成可见的了。
NOTE:每个类负责控制字的友元类或友元函数,友元关系不具备传递性。
令成员函数作为友元
除了上述的把整个Window_mgr声明成友元类之外,还可以专门的给clear()函数提供访问权限,只需要把这个成员函数声明成友元,并且我们必须明确指出成员函数属于哪个类!
class Screen{
//Window_mgr::clear必须在Screen类之前被声明!!!!
friend void Window_mgr::clear(ScreenIndex);
};
要想令某个成员函数作为友元:
函数重载和友元
如果一个类想把一组重载函数声明成它的友元,它需要对这组函数的每一个分别声明:
//extern关键字表示变量或函数定义在别的文件之中
extern ostream& storeOn(ostream&, Screen&);
extern BitMap& storeOn(BitMap&, Screen&);
class Screen {
friend ostream& storeOn(ostream&, Screen&);//只把ostream& storeOn声明成了友元。
};
友元声明和作用域
就算在类的内部定义该函数,我们也必须在类的外部提供相应的声明从而使得函数可见,即使是用成员函数调用该友元函数,它也必须声明过。
struct X {
friend void f(){}
X() { f(); } //错误 f还没有被声明
void g();
void h();
};
void X::g() { return f(); }
void f();
void X::h() { return f(); }
总结:额。。书上说的我没太理解,感觉不就是友元函数的声明和该函数的声明是两种,必须都要有,如果要调用它必须对函数进行声明,即使它在类内定义过了。[就算在类的内部定义该函数,我们也必须在类的外部提供相应的声明从而使得函数可见]
本文“宏基因组”公众号原创。作者:舟行天下 编辑:metagenome前言前面宏基因组公众号号推送过关于USEARCH的介绍及使用,详情见文章:扩增子分析神器USEARCH简介。USEARCH软件在扩增子测序分析上堪称神器!该软件依靠大神Robert Edgar开发的UPARSE,UNOISE等算法,在序列搜索、聚类、去重、去嵌合体等步骤的准确度以及效率上显著高于老牌的mothu...
K_Means 什么是聚类分析 聚类分析是在数据中发现数据对象之间的关系,将数据进行分组,组内的相似性越大,组间的差别越大,则聚类效果越好。 明显分离的 可以看到(a)中不同组中任意两点之间的距离都大于组内任意两点之间的距离,明显分离的簇不一定是球形的,可以具有任意的形状。 算法思想较为简单如下所示: 选择K个点作为初始质心 repeat ...
提供下载该MYSQL练习数据集的地址https://gitee.com/lzjcnb/test_db使用在下载目录路径下下打开cmd窗口输入以下命令即可mysql -uroot -p -t < employees.sql
video/audio标签倍速播放在现在浏览器中,各种播放器都支持 倍速播放,1.5倍,2.0倍等。playbackRate 属性html元素“audio”和“video”的playbackRate 属性允许我们改变一段正在播放的网页音频或者视频的速度,或速率。当为1.0的时候为正常播放示例<video id="video" controls src="**.mp4" type="video/mp4"></video>选择倍速播放:<select id=
Numpy组队学习(下)--输入和输出知识点随机抽样离散型随机变量连续型随机变量其它随机函数知识点随机抽样离散型随机变量二项分布binom.pmf(k) = choose(n, k) pk (1-p)(n-k)numpy.random.binomial(n, p, size=None)泊松分布poisson.pmf(k) = exp(-lam) lam*k / k!numpy.random.poisson(lam=1.0, size=None) 超几何分布numpy.ran
mybatis和hibernate的比较 Mybatis和hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。 Mybatis学习
其他的mock数据,基本上的原理都是前端在本地起一个服务器,然后发起http请求来获取数据。这样一来,就需要在本地管理模拟的数据,但是随着前端的发展,已经有了不少在线的mock数据平台。由他们来提供服务器,我们只要在他们平台上创建好自己的模拟数据,就可以使用了。原理:我们将mock层独立出来,通过中间服务的形式在前端和后端服务之前建立一道围栏,使用fastmock,前端只需要修改自己的XHR请求地址,后端只需要在开发前和前端约定好接口文档即可。等到后端服务开发完成,前端再将XHR请求地址替换回来进行联调
版本信息操作系统:Ubuntu 16.04.5 LTSGo版本:1.11安装下载Go安装包,地址:https://dl.google.com/go/go1.11.linux-amd64.tar.gz将下载好的go1.11.linux-amd64.tar.gz文件复制到在Ubuntu的/usr/local目录下,执行解压命令tar -zxvf go1.11.linux-amd64.tar...
版权声明:学习内容均为本人笔记,代码均为本人依据课本所写或改编,笔记均为个人心得或书中摘抄引言:内部类,即将一个类的定义放在另一个类的定义内部。内部类与组合是完全不同的概念。内部类看似是一种代码的隐藏机制,其实,它能够了解外部类,并且与之通信,这为我们的编程提供了极大的方便。内部类可以访问外部类的成员及方法,内部类对象的创建需要外部类的引用。获取外部类引用:外部类名字+圆点和this。
目录一、上拉电阻1. 应用场景2. 作用二、下拉电阻1. 应用场景2. 作用三、知乎上的一篇漫画注:下文中所说的0、1电平,并不是真正意义的电平为0、电平为1!0、1电平只代表一种逻辑状态,即低电平和高电平,比如有可能1 ~ 2v为逻辑0,5 ~ 6v为逻辑1。一、上拉电阻1. 应用场景上拉电阻应用在引脚低电平有效的情况。2. 作用为什么在低电平有效的引脚上,要连一个上拉电阻呢?我们希望引脚只有两个逻辑状态,即0和1,但如果某低电平有效的引脚不接上拉电阻,它就只有0和未知的状态:当接低电
多年前看过一部香港拍摄的武侠片,主演片名是谁是什么,统统忘记了,故事也很老套;但中间有一插曲,一回想起来就忍俊不禁: 大恶人为称霸天下,以卑鄙手段杀了名满天下的大侠。大侠临终前告诉少年去找大侠的师叔,学会绝艺,报血海深仇。少年果然不负所托,找到了那个滑稽且高深...
HTTP是超文本传输协议(HyperText Transfer Protocol, HTTP)的简写,它是TCP/IP协议的应用层协议。HTTP/0.9HTTP/0.9是HTTP的第一个版本已过时。它的组成极其简单,只允许客户端发送GET这一种请求,且不支持请求头。由于没有协议头,造成了HTTP/0.9协议只支持一种内容,即纯文本。不过网页仍然支持用HTML语言格式化,同时无法插入图片。具有典型的无状态性,每个事务独立进行处理,事务结束时就释放这个连接。一次HTTP/0.9的传输首先要建立一个由客