其实如果一直学习c++primer这本书,你就会发现他和其他书的一点明显不同,就是此书没有把指针和数组提出一章。其实在类这里也是同感,不是说没有专门提出,而是不像其他c++书或者其他高级语言那样,对这些东西大肆渲染。记得我上大一的时候开了c语言这门课,老师讲到指针这里时把指针吹得难的仿佛一辈子都学不好,同样的很多书籍讲到类时总是把类说的抽象的好像除了毕加索和梵高谁也搞不定。我们一个很有经验的雷达老师,他就说过,其实很多东西本身是很简单的,可是作者为了突出自己很牛逼,故意写的很晦涩。这本书比较好,是什么就是什么,从不多说废话。
另外还要再说一句,我们一定要做课后练习题,和csapp比起来,这本书的题真的不多,同时还有配套练习题答案,简直是自学奇书。
那么我们开始。
类的基本思想是抽象,封装。抽象就是将类分为外部可以用的接口和内部通过私有数据,私有成员函数等的实现。而封装就是完成接口和实现的分离。
成员函数
声明成员函数必须在类内部,而定义可以在外部也可在内部。而直接定义在内部的成员函数是inline的,若在外部定义inline,则需在外部声明一下inline,或者类的内外都说明是inline,不过没有必要。
1.在类内声明和定义。书上的例子如下:string isbn() const {return bookNo;}。当我们调用时,则他指向该成员函数的对象的成员。
2.this:指向该成员函数的对象,是隐式定义的,无法改变,所以是一个const 指针。所以this的类型是C *const。使用方法是a.isbn();,过程是a的地址→this→this->bookNo。
const:参数列表后的const表示this指向常量,这种函数又叫做常量成员函数。此时函数不能改变对象的内容。
编译声明顺序:编译成员声明→编译成员函数体。
类外定义成员函数时必须有他所属的类名。
返回this对象:2点,1.返回*this,即return *this;因为this是原对象的地址,所以返回*this则是返回对象本身,2.返回类型,至少是此类的类型。
构造函数
即初始化函数。无返回类型,类似函数重载,不能被声明为const。而构造函数先于类内的const对象声明,所以构造函数可以给const写值。
默认构造函数:没有为对象初始化,类自己构造一个。编译器创造的构造函数又名合成默认构造函数,原则:没有构造函数;按照初始化规则。
tip,不能使用合成默认构造函数的情况:1.有构造函数。2.会导致内置类型和复合类型未定义。3.一个类包含类其他类类型成员且此类没有默认构造函数。
格式:C(...):x() {}
分为:类名C,形参列表(...),构造函数初始值列表x(),函数体{}。构造函数初始值列表是使用形参为新对象的数据成员赋初始值。当一个构造函数初始值列表缺少数据成员时,调用此构造函数将对缺少的默认成员赋默认初始值。如果构造函数的函数体为空,则代表他只是想要为数据成员赋初值。
在类外定义构造函数,格式:C::C(...) :x {...}
有时可以不使用初始值列表进行初始化,可以将对数据成员的初始化放入构造函数函数体中进行赋值操作。但数据成员是const或引用,必须进行初始化;当成员属于类类型且没有定义默认构造函数必须将此成员初始化。
构造函数初始值列表只负责初始化,没有定义初始化顺序,顺序是按照类内成员声明顺序来执行的。所以最好保持一致。
委托构造函数
形式与一般构造函数一致,将自己的初始化部分或全部委托给另一个构造函数,先执行受委托的构造函数,如果有函数体内容则执行函数体;再执行委托函数。
默认初始化的发生情景:1.快内不使用初始值定义非静态变量或数组。2.此类含有一个类类型成员且它使用合成默认构造函数。3.类类型的成员没有在构造函数列表中显示初始化。
值初始化:1.数组初始化时初始值数量小于数组大小。2.不使用初始值定义一个局部静态变量。3.显式初始化,如t()。
类的隐式转换:当有构造函数只接受一个实参时,相当于定义类隐式转换规则。但隐式转换只允许一次转换,可以写一个中间类型,也可显式转化T(x)。
若不想隐式转换可以把成员函数定义为 explicit(直接前缀就好),explicit只对一个实参的函数有效,且此关键字只能在类内声明,不能在外部定义是重新出现。explicit必须直接初始化(用括号)不能赋值等间接初始化。接受一个单参数const *char的string构造函数不是explicit的,接受一个参数的vector构造函数是explicit的。
类会控制拷贝,赋值,销毁对象的行为,若不定义,是默认合成,如c1=c2,是将2个对象的成员依次赋值。合成操作不一定一定有效,尤其是分配类对象之外的资源。类包含vector,string其合成操作可以正常进行。
封装
public之后的成员可以被整个程序访问,private只能被类的成员函数访问,不能被使用该类的代码访问。
class默认为private,struct默认为public。
友元
概念很简单,用法也简单:在类的开始处声明其他类/函数为自己的friend。作用:使外部类/函数可以使用此类的私有成员。而将友元函数直接定义在类内时此函数是inline的。
将类声明为友元:class C1 {friend class C2;}; class C2{...};友元没有传递性,如c2是c1的友元,c3是c2的友元,但c3和c1并无友元关系。
函数作为友元:假设c2中的函数f要作为c1的友元,则:1.定义c2的类,声明f但不定义。2.定义c1,包括f的友元声明。3.定义f。
重载的函数友元必须分开声明。
tip,使用友元函数必须将此函数进行声明,不是友元声明,是此函数本身。主要用于声明友元的类想要调用此函数时,如:struct c{friend void f();}
类可以定义其他类型在类中的别名。
mutable:类中可以改变的成员声明,即使是在const函数中。用法:mutable int x;
提供类内初始值,必须使用=或者{}。
几个成员完全相同的类只要名字不同也是不同的类。
类的向前声明:只声明,不定义。在定义之前称为不完全类型。可以定义指向它的指针或引用;可以声明此类为返回参数或参数的函数,但此函数也不能定义。
类型名的处理:先类内找在类外找。若类内成员使用类外部的名字,而名字又代表一个类型,则类内不能重新定义为此名字。
类内成员函数使用名字:成员函数内有无声明→类内有无声明→成员函数定义之前的外部作用域。若有同名声明,想使用外部则需使用类名(C::)或外部域(::)是其成立。
聚合类:1.所以成员public。2.没有构造函数。3.没有类内初始值。4.没有基类和virtual函数。特点:用户可以直接访问所有成员;可用花括号直接初始化,顺序要对。如:
struct C{string a; int b;} 初始化:C data={"abcd",0};
字面值常量类:数据成员都是字面值的聚合类。若不是聚合类,则:全是字面值类型;类中至少含有一个constexpr构造函数;若数据成员有类内初始值,则内置类型成员的初始值必须是一条常量表达式,若成员属于某种类类型,初始值必须使用成员直接的constexpr构造函数;类必须使用析构函数的默认定义用来销毁类的对象。
constexpr构造函数可声明为default,其函数体基本为空,必须初始化所有数据成员。
类的静态成员
目的:希望一些成员与类本身相关,而不是类的对象。
形式:成员前加上static。
他们存在与任何对象之外,对象中没有他们,且被所有对象共享。可以使用类的对象,引用,指针来访问他们。静态函数不与对象绑定,不包含this,不能声明为const。因为静态成员不属于某个对象,所以一般(必须)在类外定义和初始化每个静态成员。但是,可以为静态成员提供const int类内初始值,但得是constexpr,如:static constexpr int x=0;。静态成员可以是不完全类型,静态数据成员可以是类类型,而非静态数据成员的不完全类型只能是类的指针或引用,如:
static C x1;//正确 C *x2;//正确 C x3;//错误
静态成员可以作为默认实参,非静态不行,因为他的值属于对象,无法提供一个对象获取成员的值。