
一、类的定义1.1类定义的格式定义方式 class Stack { };·class是关键字Stack是类名可以自己修改,{ }中为类的主体注意类定义结束时后面的分号不能省略。·类体中内容称为类的成员类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或 者成员函数。·C中struck也可以定义类明显的变化就是在C中struct内可以定义函数但还是推荐使用class来定义类·在类中定义的成员函数的属性是inline的根据以上描述创建的一个类但是就这个创建的类貌似不能访问报错说是无法访问因为类里面还需要注意一个东西1.2访问限定符类里面还有访问限定符这一限制为了更好的保护数据·public公开在这个访问限定符下面的所有成员变量和函数都是可以在外部访问的·private私有在此限定符限定的范围内的成员变量和函数在外部是不可以访问的·protected保护在此限定符限定的范围内的成员变量和函数在外部是不可以访问的限定符的范围是从写下的那一行起到遇到下一个访问限定符为止就目前来看private和protected是没有区别的用哪个都可以。class的默认限定符(不写)是private的也就是私有所以在外部是不可访问的struct的默认限定符是public的也就是公开。一般成员变量都用private或protected修饰给别人用的用public。现在来修改上面的代码1.3类域每一个类都是一个独立的作用域类中的所有成员都在这个作用域中。故想要在类外面访问类里面的成员(声明与定义分离等情况)需要用到::作用域操作符就像下面的另外做声明与定义分离时只能在声明处给缺省参数在类的类域中查找某个数据时是会在整个类中搜寻不会是单向的寻找。也就是成员变量无论定义在成员函数的上还是下时成员函数需要调用时不会因为位置在下面而找不到二、实例化2.1实例化概念用一个类类型在物理的内存上创建出对象的过程也叫类实例化出对象类相当于与一个事物的描述(蓝图)而实例化就是把它实现出来只有在实例化后才会占有空间没有实例化的类是不占用任何物理空间的2.2对象大小用类实例化出的对象的空间大小也按照内存对齐的规则来需要注意如果类是空的实例化出的对象也会占用1字节这是占位符表示它是存在的3.this指针有了类之后可以用相同的类中定义不同名字变量。那么编译器是怎么分辨谁是谁的呢编译器是怎么分辨的呢其实每个成员函数都有个隐藏的参数-类类型的this指针正是因为有这个参数才能分辨谁是谁的C规定不能显示的传递和接收this指针(编译时编译器会处理)但可以在函数内显示的调用this指针三、类的默认成员函数3.1类的默认成员函数默认成员函数就是用户在不写的情况下编译器自动生成的成员函数就是默认成员函数。一个类什么都不写的情况下会生成6个默认成员函数重点去分析的只有前4个最后的两个取地址重载相对而言不重要了解即可。分2个方面去分析1.我们不写的自动生成的情况是什么行为是否能满足我们的需求2.自动生成不能满足要求时自己去实现要如何实现3.1.1构造函数构造函数是特殊的成员函数虽然叫构造但它的行为更偏向于初始化构造函数的特点1、函数名与类名相同。2、无返回值。(返回值啥都不需要给也不需要写void)3、对象实例化时系统会自动调用对应的构造函数。4、构造函数可以重载。5、如果类中没有显式定义构造函数则C编译器会自动生成一个无参的默认构造函数一旦用户显式定义编译器将不再生成。6、无参构造函数、全缺省构造函数、不写构造时编译器默认生成的构造函数都叫做默认构造函数。但是这三个函数有且只有一个存在不能同时存在。无参构造函数和全缺省构造函数虽然构成函数重载但是调用时会存在歧义。要注意很多同学会认为默认构造函数是编译器默认生成那个叫默认构造实际上无参构造函数、全缺省构造函数也是默认构造总结一下就是不传实参就可以调用的构造就叫默认构造。7、我们不写编译器默认生成的构造对内置类型成员变量的初始化没有要求也就是说是是否初始化是不确定的看编译器。最好就当作没有做处理对于自定义类型成员变量要求调用这个成员变量的默认构造函数初始化。如果这个成员变量没有默认构造函数那么就会报错。如果要初始化这个变量就会需要用到初始化列表这个在后面在说3.1.2析构函数析构函数与构造函数的功能是相反的主要任务是对申请的资源的释放析构函数的特点1、析构函数名是在类名前加上字符。2、无参数无返回值。(这里跟构造类似也不需要加void)3、一个类只能有一个析构函数。若未显式定义系统会自动生成默认的析构函数。4、对象生命周期结束时系统会自动调用析构函数。5、跟构造函数类似我们不写编译器自动生成的析构函数对内置类型成员不做处理自定类型成员会调用他的析构函数。6、还需要注意的是我们显示写析构函数对于自定义类型成员也会调用他的析构也就是说自定义类型成员无论什么情况都会自动调用析构函数。7、如果类中没有申请资源时析构函数可以不写直接使用编译器生成的默认析构函数如果默认生成的析构够用(默认生成的析构去调用其他类写好的析构)也就不需要显示写析构但是自身类中有资源申请时一定要自己写析构否则会造成资源泄漏。8.一个局部域的多个对象C规定后定义的先析构。构造和析构主要是解决创建了变量却忘记了初始化以及忘记了释放内存的场景因为它现在会自动调用3.1.3拷贝构造函数拷贝构造是一个特殊的构造函数。当一个构造函数的第一个参数是自身类类型的引用且没有其他参数或者其他参数都有缺省值时这个就是拷贝构造。拷贝构造的特点1.拷贝函数是构造的一个重载2.拷贝函数的第一个参数必须是类类型的引用如果使用传值传参编译器会报错因为语法上会导致无穷递归调用。3.C规定自定义类型对象进行拷贝行为必须调用拷贝构造所以这里自定义类型传值传参和传值返回都会调用拷贝构造完成。4.如果没有显示的去写拷贝构造编译器会自动生成拷贝构造。自动生成的拷贝构造对内置类型的数据是进行浅拷贝/值拷贝(单纯的对数据进行一个字节一个字节的拷贝)对自定义类型就调用它自身的拷贝构造。5.如果一个类显示的实现了析构函数那么它也就需要显示的实现拷贝构造否则就不需要6.传值返回会产生一个临时对象调用拷贝构造传值引用返回返回的是返回对象的别名引用没有产生拷贝。但是如果返回对象是一个当前函数局部域的局部对象函数结束就销毁了那么使用引用返回是有问题的这时的引用相当于一个野引用类似一个野指针一样。传引用返回可以减少拷贝但是一定要确保返回对象在当前函数结束后还在才能用引用返回。3.2运算符重载3.2.1运算符重载1.当运算符被用于类类型的对象时C语言允许我们通过运算符重载的形式指定新的含义。C规定类类型对象使用运算符时必须转换成调用对应运算符重载若没有对应的运算符重载则会编译报错。2.运算符重载是具有特殊名字的函数主要由operator和后面要定义的运算符共同构成。和其他函数一样它也具有其返回类型和参数列表以及函数体。3.重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数二元运算符有两个参数二元运算符的左侧运算对象传给第一个参数右侧运算对象传给第二个参数。4.如果一个重载运算符函数是成员函数则它的第一个运算对象默认传给隐式的this指针因此运算符重载作为成员函数时参数比运算对象少一个。5.运算符重载以后其优先级和结合性与对应的内置类型运算符保持一致。6.不能通过连接语法中没有的符号来创建新的操作符比如operator。7..* :: sizeof ?: . 这五个运算符不能重载。8.重载运算符至少得有一个是类类型的参数不能通过运算符重载改变内置类型的运算符含义如int operator(int x,int y) {return xy1;} 这样是不行的。9.一个类要重载哪些运算符主要是看重载哪些运算符有意义。10.重载运算符时有前置和后置之分但重载时的函数名都是 operator 。故C规定后置重载时要在参数列表里面加一个 int 的形参可以不传参数因为只是用来区分的。11.重载和时需要重载为全局函数因为重载为成员函数this指针默认抢占了第一个形参位置第一个形参位置是左侧运算对象调用时就变成了对象cout不符合使用习惯和可读性。重载为全局函数把ostream/istream放到第一个形参位置就可以了第二个形参位置当类类型对象。友元在下面介绍3.2.2赋值运算符重载赋值运算符重载也是一个默认成员函数。它主要用于两个已经存在的对象直接进行拷贝赋值它与拷贝构造不同拷贝构造主要用于一个对象拷贝去初始化另一个要创建的对象赋值运算符重载的特点1.赋值运算符重载是一个运算符重载规定必须重载为成员函数。赋值运算重载的参数建议写成const当前类类型引用(因为不会对实参进行修改一般也不希望会被修改)否则会传值传参会有拷贝。2.有返回值且建议写成当前类类型引用引用返回可以提高效率有返回值自的是为了支持连续赋值场景。3.没有显式实现时编译器会自动生成一个默认赋值运算符重载默认赋值运算符重载行为跟默认拷贝构造函数类似对内置类型成员变量会完成值拷贝/浅拷贝对自定义类型成员变量会调用他的赋值重载函数。4.如果一个类显示的实现了析构函数那么它也就需要显示的实现赋值运算符重载否则就不需要3.3取地址运算符重载3.3.1 const 成员函数将const修饰的成员函数称为const成员函数const修饰成员函数时放到参数列表的后面const实际修饰该成员函数隐含的this指针表明在该成员函数中不能对类的任何成员进行修改。const 修饰Date类的Print成员函数隐含的this指针由Date* const this变为const Date* const this。3.3.2取地址运算符重载取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载一般这两个函数编译器自动生成的就可以够用了不需要去显示实现。除非一些很特殊的场景比如我们不想让别人取到当前类对象的地址就可以自己实现一份胡乱返回一个地址。4.1在探构造函数1.之前介绍构造函数时初始化成员变量主要使用函数体内赋值构造函数初始化还有一种方式就是初始化列表初始化列表的使用方式是以一个冒号开始接着是一个以逗号分隔的数据成员列表每个”成员变量”后面跟一个放在括号中的初始值或表达式。2.每个成员变量在初始化列表中只能出现一次语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。3.引用成员变量const成员变量没有默认构造的类类型变量必须放在初始化列表位置进行初始化否则会编译报错。4.C11支持在成员变量声明的位置给缺省值这个缺省值主要是给没有显示在初始化列表初始化的成员使用的不是在此时就初始化。5.尽量使用初始化列表初始化因为那些你不在初始化列表初始化的成员也会走初始化列表如果这个成员在声明位置给了缺省值初始化列表会用这个缺省值初始化。如果你没有给缺省值对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器C并没有规定。对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数如果没有默认构造会编译错误。6.初始化列表中按照成员变量在类中声明顺序进行初始化跟成员在初始化列表出现的的先后顺序无关。建议声明顺序和初始化列表顺序保持一致。总结无论是否显示写初始化列表每个构造函数都有初始化列表无论是否在初始化列表显示初始化成员变量每个成员变量都要走初始化列表初始化无论初始化列表的顺序如何写初始化的顺序始终与声明顺序一致四、类型转换1.C支持内置类型隐式类型转换为类类型对象需要有相关内置类型为参数的构造函数。2.构造函数前面加explicit就不再支持隐式类型转换。3.类类型的对象之间也可以隐式转换需要相应的构造函数支持。五、static成员1.用static修饰的成员变量称之为静态成员变量静态成员变量一定要在类外进行初始化。2.静态成员变量为所有类对象所共享不属于某个具体的对象不存在对象中存放在静态区。3.用static修饰的成员函数称之为静态成员函数静态成员函数没有this指针。4.静态成员函数中可以访问其他的静态成员但是不能访问非静态的因为没有this指针。5.非静态的成员函数可以访问任意的静态成员变量和静态成员函数。6.突破类域就可以访问静态成员可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数。7.静态成员也是类的成员受public、protected、private访问限定符的限制。8.静态成员变量不能在声明位置给缺省值初始化因为缺省值是个构造函数初始化列表的静态成员变量不属于某个对象不走构造函数初始化列表。六、友元1.友元提供了一种突破类访问限定符封装的方式友元分为友元函数和友元类在函数声明或者类声明的前面加friend并且把友元声明放到一个类的里面。2.外部友元函数可访问类的私有和保护成员友元函数仅仅是一种声明它不是类的成员函数。3.友元函数可以在类定义的任何地方声明不受类访问限定符限制。4.一个函数可以是多个类的友元函数。5.友元类中的成员函数都可以是另一个类的友元函数都可以访问另一个类中的私有和保护成员。6.友元类的关系是单向的不具有交换性比如A类是B类的友元但是B类不是A类的友元。7.友元类关系不能传递如果A是B的友元B是C的友元但是A不是C的友元。8.有时提供了便利。但是友元会增加耦合度破坏了封装所以友元不宜多用。七、内部类1.如果一个类定义在另一个类的内部这个内部的类就叫做内部类。内部类是一个独立的类跟定义在全局相比它只是受外部类类域限制和访问限定符限制所以外部类定义的对象中不包含内部类。2.内部类默认是外部类的友元类。3.内部类本质也是一种封装当A类跟B类紧密关联A类实现出来主要就是给B类使用那么可以考虑把A类设计为B的内部类如果放到private/protected位置那么A类就是B类的专属内部类其他地方都用不了。八、匿名对象1.用类型(实参)定义出来的对象叫做匿名对象相比之前我们定义的类型对象名(实参)定义出来的叫有名对象。2.匿名对象生命周期只在当前一行一般临时定义一个对象当前用一下即可就可以定义匿名对象。......mkk