
1.类的定义1.1类的定义格式类的定义要用到class关键字如class Stack {}这里的Stack为类名也可以理解为类型class和struct很相似他们两个都可以用来定义类只是在class中定义的成员默认为私有外界不允许访问而struct定义的成员默认为公有在外界允许访问。定义在类⾯的成员函数默认为inline。1.2访问限定符访问限定符共有三种1.C⼀种实现封装的⽅式⽤类将对象的属性与⽅法结合在⼀块让对象更加完善通过访问权限选择性的将其接⼝提供给外部的⽤⼾使⽤。2.public修饰的成员在类外可以直接被访问protected和private修饰的成员在类外不能直接被访问。3.• class定义成员没有被访问限定符修饰时默认为privatestruct默认为public。• ⼀般成员变量都会被限制为private/protected需要给别⼈使⽤的成员函数会放为public。1.3 类域•类定义了⼀个新的作⽤域类的所有成员都在类的作⽤域中在类体外定义成员时需要使⽤ :: 作⽤域操作符指明成员属于哪个类域。2.实例化⽤类类型在物理内存中创建对象的过程称为类实例化出对象。类和对象的关系类是对象进⾏⼀种抽象描述是⼀个模型⼀样的东西限定了类有哪些成员变量这些成员变量只是声明没有分配空间⽤类实例化出对象时才会分配空间。⼀个类可以实例化出多个对象实例化出的对象占⽤实际的物理空间存储类成员变量。打个⽐⽅类实例化出对象就像现实中使⽤建筑设计图建造出房⼦类就像是设计图设计图规划了有多少个房间房间⼤⼩功能等但是并没有实体的建筑存在也不能住⼈⽤设计图修建出房⼦房⼦才能住⼈。同样类就像设计图⼀样不能存储数据实例化出的对象分配物理内存存储数据。对象大小实例化出的每一个对象都有自己独立的空间所以对象中肯定包含成员变量那么成员函数是否包含呢⾸先函数被编译后是⼀段指令对象中没办法存储这些指令存储在⼀个单独的区域(代码段)类里面的函数并不占用实例化出的空间只是用指针将其存储起来可以理解为实例化出的每一个对象中它们有各自的成员变量但是他们的函数地址是一样的。C中对象的大小遵循结构体的内存对齐原则内存对⻬规则• 第⼀个成员在与结构体偏移量为0的地址处。• 其他成员变量要对⻬到某个数字对⻬数的整数倍的地址处。• 注意对⻬数 编译器默认的⼀个对⻬数 与 该成员⼤⼩的较⼩值。• VS中默认的对⻬数为8• 结构体总⼤⼩为最⼤对⻬数所有变量类型最⼤者与默认对⻬参数取最⼩的整数倍。• 如果嵌套了结构体的情况嵌套的结构体对⻬到⾃⼰的最⼤对⻬数的整数倍处结构体的整体⼤⼩就是所有最⼤对⻬数含嵌套结构体的对⻬数的整数倍。3.this指针形参、实参都不能显式传this指针编译器会自动绑定但是函数体可以任意使用。假如一个类中有 Init 与 Print 两个成员函数函数体中没有关于不同对象的区分那当d1调⽤Init和Print函数时该函数是如何知道应该访问的是d1对象还是d2对象呢那么这⾥就要看到C给了⼀个隐含的this指针解决这⾥的问题。类的成员函数默认都会在形参第⼀个位置增加⼀个当前类类型的指针叫做this指针。⽐如Date类的Init的真实原型为 void Init(Date* const this, int year,int month, int day)4.默认成员函数默认成员函数就是⽤户没有显式实现编译器会⾃动⽣成的成员函数称为默认成员函数。⼀个类我们不写的情况下编译器会默认⽣成以下6个默认成员函数重点讲前4个。C中一共有6大默认成员函数它们分别为1.构造函数2.析构函数3.拷贝构造函数4.赋值重载拷贝赋值运算符5.移动构造C11新增6.移动赋值C11新增1.构造函数构造函数的特点1. 函数名与类名相同。2. ⽆返回值。 (返回值啥都不需要给也不需要写void不要纠结C规定如此)3. 对象实例化时系统会⾃动调⽤对应的构造函数。4. 构造函数可以重载。5.如果类中没有显式定义构造函数则C编译器会⾃动⽣成⼀个⽆参的默认构造函数⼀旦⽤⼾显式定义编译器将不再⽣成。当然也不是绝对的如构造函数的实现6.我们不写构造函数时编译器默认⽣成的构造对内置类型成员变量的初始化没有要求也就是说是是否初始化是不确定的看编译器。对于⾃定义类型成员变量要求调⽤这个成员变量的默认构造函数初始化。如果这个成员变量没有默认构造函数那么就会报错我们要初始化这个成员变量需要⽤初始化列表才能解决。初始化列表初始化列表是构造函数的另一种形式初始化列表的使⽤⽅式是以⼀个冒号开始接着是⼀个以逗号分隔的数据成员列表每个成员变量后⾯跟⼀个放在括号中的初始值或表达式。每个成员变量在初始化列表中只能出现⼀次语法理解上初始化列表可以认为是每个成员变量定义初始化的地⽅。引⽤成员变量const成员变量没有默认构造的类类型变量必须放在初始化列表位置进⾏初始化否则会编译报错。重点尽量使⽤初始化列表初始化因为那些你不在初始化列表初始化的成员也会⾛初始化列表初始化列表总结1.⽆论是否显⽰写初始化列表每个构造函数都有初始化列表2.⽆论是否在初始化列表显⽰初始化成员变量每个成员变量都要⾛初始化列表初始化什么情况下必须自己写构造函数1.成员变量需要自定义初始化逻辑默认构造只会做简单的初始化对内置类型甚至不初始化是随机值如果你要按业务规则给成员赋初值就必须手写。2.需要在构造中申请资源动态内存、文件、网络连接等默认构造不会帮你 new 内存你不写的话 data_ 就是野指针。后续还需要你自己实现拷贝构造和赋值重载防止浅拷贝问题。3.需要对成员做特殊初始化const、引用成员初始化列表还有一个重点初始化列表是按照成员变量在类里面声明的顺序进行初始化的跟成员在初始化列表出现的顺序无关。2.析构函数析构函数与构造函数功能相反析构函数不是完成对对象本⾝的销毁⽐如局部对象是存在栈帧的函数结束栈帧销毁他就释放了不需要我们管C规定对象在销毁时会⾃动调⽤析构函数完成对象中资源的清理释放⼯作。析构函数的特点1. 析构函数名是在类名前加上字符 ~。2. ⽆参数⽆返回值。 (这⾥跟构造类似也不需要加void)3. ⼀个类只能有⼀个析构函数。若未显式定义系统会⾃动⽣成默认的析构函数。4. 对象⽣命周期结束时系统会⾃动调⽤析构函数。5. 跟构造函数类似我们不写编译器⾃动⽣成的析构函数对内置类型成员不做处理⾃定类型成员会调⽤他的析构函数。6.如果类中没有申请资源时析构函数可以不写直接使⽤编译器⽣成的默认析构函数如果默认⽣成的析构就可以⽤也就不需要显⽰写析构但是有资源申请时⼀定要⾃⼰写析构否则会造成资源泄漏7.⼀个局部域的多个对象C规定后定义的先析构。类似于栈的特性后进先出的特性我们可以看到a2是后定义的但是析构的时候是a2先析构的。3.拷贝构造函数如果⼀个构造函数的第⼀个参数是⾃⾝类类型的引⽤且任何额外的参数都有默认值则此构造函数 也叫做拷⻉构造函数也就是说拷⻉构造是⼀个特殊的构造函数。拷贝构造函数针对的是用已经存在的对象去构造另一个新的对象。拷⻉构造的特点1. 拷⻉构造函数是构造函数的⼀个重载。2. 拷⻉构造函数的第⼀个参数必须是类类型对象的引⽤使⽤传值⽅式编译器直接报错因为语法逻辑上会引发⽆穷递归调⽤。 拷⻉构造函数也可以多个参数但是第⼀个参数必须是类类型对象的引⽤后⾯的参数必须有缺省值。3. C规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造所以这⾥⾃定义类型传值传参和传值返回都会调⽤拷⻉构造完成。4. 若未显式定义拷⻉构造编译器会⽣成⾃动⽣成拷⻉构造函数。⾃动⽣成的拷⻉构造对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉)对⾃定义类型成员变量会调⽤他的拷⻉构造。拷贝构造5. 如果一个类成员变量全是内置类型且没有指向什么资源编译器⾃动⽣成的拷⻉构造就可以完 成需要的拷⻉所以不需要我们显⽰实现拷⻉构造。如果有资源申请编译器⾃动⽣成的拷⻉构造完成的值拷⻉/浅拷⻉不符合我们的需求所以需要 我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)。这⾥还有⼀个⼩技巧如果⼀个类显⽰实现了析构并释放资源那么他就 需要显⽰写拷⻉构造否则就不需要。6. 传值返回会产⽣⼀个临时对象调⽤拷⻉构造传值引⽤返回返回的是返回对象的别名(引⽤)没有产⽣拷⻉。但是如果返回对象是⼀个当前函数局部域的局部对象函数结束就销毁了那么使⽤引⽤返回是有问题的这时的引⽤相当于⼀个野引⽤类似⼀个野指针⼀样。传引⽤返回可以减少拷⻉但是⼀定要确保返回对象在当前函数结束后还在才能⽤引⽤返回。传值返回传引用返回还有一个赋值运算符重载我们在下一个专题运算符重载中讲。5.运算符重载当运算符被⽤于类类型的对象时C语⾔允许我们通过运算符重载的形式指定新的含义。C规 定类类型对象使⽤运算符时必须转换成调⽤对应运算符重载若没有对应的运算符重载则会编 译报错。运算符重载是具有特殊名字的函数他的名字是由operator和后⾯要定义的运算符共同构成。和其 他函数⼀样它也具有其返回类型和参数列表以及函数体。如果⼀个重载运算符函数是成员函数则它的第⼀个运算对象默认传给隐式的this指针因此运算 符重载作为成员函数时参数⽐运算对象少⼀个。.*::sizeof?:.注意以上5个运算符不能重载。重载运算符时有前置和后置运算符重载函数名都是operator⽆法很好的区分。 C规定后置重载时增加⼀个int形参跟前置构成函数重载⽅便区分。重载和时需要重载为全局函数若重载为成员函数this指针默认抢占了第⼀个形参位置第⼀个形参位置是左侧运算对象调⽤时就变成了 对象cout不符合使⽤习惯和可读性。重载为全局函数把ostream/istream放到第⼀个形参位置就可以了第⼆个形参位置当类类型对象。此时需要在内部类中声明其为友元函数对于友元后面会介绍1.赋值运算符重载赋值运算符重载是⼀个默认成员函数⽤于完成两个已经存在的对象直接的拷⻉赋值。赋值运算符重载的特点1. 赋值运算符重载是⼀个运算符重载规定必须重载为成员函数。赋值运算重载的参数建议写成 const 当前类类型引⽤否则会传值传参会有拷⻉2. 有返回值且建议写成当前类类型引⽤引⽤返回可以提⾼效率有返回值⽬的是为了⽀持连续赋 值场景。3. 没有显式实现时编译器会⾃动⽣成⼀个默认赋值运算符重载默认赋值运算符重载⾏为跟默认拷 ⻉构造函数类似对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉)对⾃定义 类型成员变量会调⽤他的赋值重载函数。4. 对于一个类它的成员变量全是内置类型且没有指向什么资源编译器⾃动⽣成的赋值运算符重载就 可以完成需要的拷⻉所以不需要我们显⽰实现赋值运算符重载。对于有资源指向的类编译器⾃动⽣成的赋值运算符重载完成的值拷⻉/浅拷⻉不符合我 们的需求所以需要我们⾃⼰实现深拷⻉(对指向的资源也进⾏拷⻉)。这⾥还有⼀个⼩技巧如果⼀个类显⽰实现 了析构并释放资源那么他就需要显⽰写赋值运算符重载否则就不需要。区分拷贝构造和赋值拷贝终极区分口诀新对象创建 用旧对象初始化 拷贝构造已存在的旧对象 用另一个对象赋值 拷贝赋值2.const成员函数将const修饰的成员函数称之为const成员函数const修饰成员函数放到成员函数参数列表的后 ⾯。const实际修饰该成员函数隐含的this指针表明在该成员函数中不能对类的任何成员进⾏修改。 const 修饰Date类的Print成员函数Print隐含的this指针由Date* const this变为const Date* const this对于一个日期加天数的函数我们不希望原来的日期被修改我们可以在其函数后面加上const对于const Date* const this最右边的const修饰的是this的指向代表this的原本指向为一个Date类型的对象不能别修改了。最左边的const修饰的是this指向的内容代表这个Date类型的对象它的内容不能被修改。这样一来就完全将这个对象限定死了无论如何都不能被修改了。3.取地址运算符重载取地址运算符重载分为普通取地址运算符重载和const取地址运算符重载⼀般这两个函数编译器⾃动 ⽣成的就可以够我们⽤了不需要去显⽰实现。除⾮⼀些很特殊的场景⽐如我们不想让别⼈取到当前成员的地址我们可以用点小手段给他一个假地址。6.static成员函数⽤static修饰的成员变量称之为静态成员变量静态成员变量⼀定要在类外进⾏初始化。静态成员变量为所有类对象所共享不属于某个具体的对象不存在对象中存放在静态区。⽤static修饰的成员函数称之为静态成员函数静态成员函数没有this指针。⾮静态的成员函数可以访问任意的静态成员变量和静态成员函数。突破类域就可以访问静态成员可以通过类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数。7.友元友元提供了⼀种突破类访问限定符封装的⽅式友元分为友元函数和友元类在函数声明或者类 声明的前⾯加friend并且把友元声明放到⼀个类的⾥⾯。外部友元函数可访问类的私有和保护成员友元函数仅仅是⼀种声明他不是类的成员函数。⼀个函数可以是多个类的友元函数。就拿前面所说的重载和来说如果想要重载它们直接在类里面进行定义的话它的第一个参数为this指针那么语法就与我们日常所写的有很大区别因此我们将他在类外面定义这样就可以不用考虑this指针了。但是在类外定义就不能访问类里面的私有成员因此友元函数就起到了很大的作用。注意要在类里面声明为友元。