C++ Primer学习笔记(第12章-第14章)
第12章 类
-
在类内部定义的成员函数,将自动作为inline处理。也可以显式的将成员函数声 明为inline。inline成员函数的定义必须在调用该函数的每个源文件中是可见的,故inline函数的定义通常放在定义该类的头文件中。
-
类声明:为了在类定义之前使用它,我们可以先声明它,此时该类称为不完全类型。不完 全类型只能以有限访问时用。不能定义该类型的对象。不完全类型只能用于定义指向该类型的指针及引用,或者用与声明(而不是定义)使用该类型作为形参类型或 返回类型的函数。类的声明一般用来编写相互依赖的类。
-
可以把数据成员声明为nutable(不能同时为const修饰),mutable数据成员可以在const成员函数中修改。
-
当成员函数的返回类型在类中定义时,而且是在类外定义的,则定义时需要使用完全限定 名。
-
构造函数 不能声明为const。必须对任何const或引用类型成员以及没有默认构造函数的类类型的任何成员使用初始化式。使用构造函数初始化列表初始化,数据成员被初始化的次序 就是类定义成员的次序,而不是在构造函数初始化列表中的次序。
-
使用默认实参的构造函数能减少代码重复。
-
只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。
-
隐式类类型转换:可以用单个实参调用的构造 函数,定义了从形参类型到该类类型的一个隐式转换。可以将构造函数声明为explicit,来防止隐式转换。explicit只能用于类内部声明上(不能用于 定义上)。
-
注意友元 声明的顺序:声明类A ,把类A声明为B的友元(在类B的定义中),定义类A。
-
static类成员
-
static成员函数不是任何对象的组成部分,故没有this形参,可以直接使用类的static成员,但不能直接使用非static成员,不能被声明为const和虚函数。可以使用类作用域操作符从类中直接调用static成员,也可以通过对象调用。
-
static数据成员必须在类定义体的外面定义(正好一次),不能通过类构造函数初始化,而是应该在定义时初始化。static关键值只能用于类定义体内部的声明中,定义时不能标示为static。const static数据成员可以在类的定义体中初始化,但仍必须在类的定义体外部定义(不必再指定初始化值)。
-
static成员不同与非static成员的使用:static数据成员的类型可以是改成员所属的类类型(Class Bar{ static Bar mem;}),非static成员被限定声明为其自身类对象的指针或引用(因为这时类还没定义完,相当于类的向前声明);同样,static数据成员可以作默认实参,非static不能(因为它不能独立于所属对 象)。
-
第13章 复制控制
-
复制控制函数包括复 制构造函数、赋值操作符、析构函数
-
复制构造 函数:只有单个参数,而且该形参是对本类类型对象的引用(常用const修饰),如T(const T&)。如果没有定义复制构造函数(即使定义了其他构造函数),编译器会为我们合成一个——执行逐个成员初始化,将新对象初始化为原 对象的副本。所以为了防止复制,类必须显式声明其复制构造函数为private。不允许复制的类对象只能作为引 用或指针传递给函数或从函数返回,也不能用作容器的元素。
-
赋值操作符:就是重载’=‘操作符,如T& operator=(const T&)(注意返回值为类的引用, 因为=返回左操作符)。合成赋值操作符,将右操作数对象的每个成员赋值给左操作数对象的对应成员,还能对数组赋值。复制和赋值常一起 使用。
-
析构函 数:构造函数的互补,释放对象获得的资源。合成构造函数不会自动删除 指针对象所指向的对象。如果类需要析构函数,则它也需要赋值操作符 和复制构造函数,这是一个有用的经验法则,常称为三法则(rule of three)。析构函数与复制构造函数或赋值操作符之间 的一个重要区别是:即使我们编写了自己的析构函数,合成析构函数仍然运行。
-
-
管理指针成员:包含指针的类需要特别注意复制控制,原因是复制指针时只复制指针中的 地址,而不会复制指针指向的对象。
-
默认(合 成)复制/赋值管理指针,指针共享同一对象,可能出现悬垂指针。
-
用智能指针管理指针复制/赋值:新定义一个类管理指针,并增加一个计数功能,当复制/赋值时计数加一,对象销毁时(析构函数)减 一,当计数为0时,释放指针。
-
定义值型类:给指针 成员提供值语义,创建一个新对象。
-
-
复制/赋值操作要考虑自己复制/赋值自己。
第14章 重载操作符与转换
-
重载操作符不能创建任何新的操作符,不能改 变操作符的优先级、结合性或操作数数目。
-
重载操作符的定义
-
形式是保留字operator后接许定义的操作符符号,如
item operator+(const item&, const item&)
-
重载操作符必须具有至少一个类类型或枚举类 型的操作符。这条规则强制重载操作符不能重新定义用于内置类型对象的操作符的含义。
-
重载操作符可以定义为类的成员函数或非成员 函数
-
作为类成 员的重载函数,其形参看起来比操作数数目少1。作为成员函数的操作符有一个隐含的this形参,限定为第一个操作数(故输入输出操作符不能重载为类成员)
-
操作符定义为非成员函数时,通常必须将他们 设置为所操作类的友元,以访问类的私有变量。
-
-
-
重载操作符的设计
-
不要重载具有内置含义的操作(逗号、取地 址、逻辑与、逻辑或等操作通常不重载)
-
重载操作符是为了使用方便,当一个重载操作符的含义不明显,或操作很少时,没必要重载操作符,定义普通函数就行了。
-
选择成员或非成员的实现:赋值(=)、下标([ ])、调用(())、成员访问(->)、转换操作符等操作必须定义为 成员;输入和输出操作符必须定义为非对象;对等的操作符,如算法操作符、相等操作符、关系操作符和位操作符,最好定义为非成员;改变对象状态或与给定类型 紧密联系的一些操作符,如自增、自减和解引用,通常定义为类成员。
-
-
输入操作符通常应进行最小限度的格式化,不 应该输出换行符;输入操作符必须处理错误和文件结束的可能性,如果可能,要确定错误回复措施,保证数据是内在一致的。
-
重载操作符需要注意返回值的类型:输入、输 出操作符返回对第一个参对(流对象)的引用;加法(+)操作符返回新建的对象,自增、自减对象也 是返回新建的对象;复合操作符、赋值操作符返回第一个操作数的引用;下标([ ])操作符返回对应下标内容的引用;解引用 (*)返回对象的引用;箭头(->)操作符一般返回指向对象的指 针。
-
读且返回 引用的操作符,如下标、箭头、解引用等,应该提供const版本和非const版本
-
自增、自减操作符:为了区别前缀和后缀,后缀式操作符函数接受一个额外的(无用的)int型形参,如 item operator++(int) 为后缀式。
-
调用操作符和函数对象
-
调用操作符的定义: int Item operator() (int val),调用看起来像个函数调用:
int value=item(10)。定义了调用操作符的类,其对象称为函数对象,函数对象比函数更灵活,因为它们可以改变成员变量(即函数的参数)
-
标准库定义了一组算术、关系、与逻辑函数对 象类,包含在functional头文件中。
-
-
标准库定义的函数对象
-
使用:他们都是模板类,plus<int> intAdd; int sum=intAdd(10,100);
-
分为一元函数对象(接受一个参数)和二元函 数对象
-
标准库还 定义了函数适配器来特化和扩展函数对象。绑定器:bindlst和bind2nd,绑定一个参数到函数对象的第一个或第二个参数;求 反器:not1和not2,求反一元或二元函数对象。not1 ( bind2nd ( less_equal<int>() , 10 ) )
-
-
转换操作符:就是定义从类类型的转换(到类 类型的转换是构造函数)
-
通用形 式:operator type()(通常应定义为const),返回一个type类型的对象(虽然没有返回值),这样类型就可以(默认)转换为type类型。
-
类类型转换后不能再跟另一个类类型转换。
class A{ operator B();} class B{ operator C();}
A a : B b ; C c; 不能直接 c=a,应该分成两步:b=a; c=b
-
转换操作符使用好了能简洁代码,但特别容易 引起二义性。下面几条经验规则会有所帮组:
-
不要定义相互转换的类,即如果类Foo具有接受类Bar的对象的构造函数,不要再为类Bar定义到类型Foo的转换操作符,否则 Foo foo=bar有二义性
-
避免到内置算术类型的转换。集体而言,如果 定义了到算术类型的转换,则(1)不要定义接受算术类型的操作符的重载版本,让转换去完成该功能(2)不要定义转换到一个以上算术类型的转换,让标准转换提供到其他算术类型的转换。
-
-