类怎么定义
类是什么呢?类就是我们上篇文说的命名空间,单独创建一个域,自己有自己的生命空间,那么类怎么定义呢?C++规定,假设 stack就是他的类名,那么前面要加个class,换行之后就是他的类的内容,类里面有定义的变量和我们自己的成员函数,要注意的是,C++兼容C语言,所以也是可以用struct代替class成为关键词的,唯一的区别就是,struct默认变量和成员函数是公有的,而class默认是私有的,为了更好理解,我们贴个代码。
上图就很好的展现了struct 和 class的区别。
访问限定符
我们上面说到类里面是有公有和私有之分的,那么我们怎么区分呢?C++规定了三个访问限定符,分别是public,private,protected。我们暂时只讲前两个,protected我们放到以后再讲。至于怎么用,我们贴个代码。
类域
我们建立了类这个东西,都知道,他的作用域是独立的,所以我们在这个类外面使用类时,我们必须使用::这个作用域操作符去指明是哪个类里面的。
还有一条注意事项是,我们成员函数声明和定义分离的时候,如果定义在类的外面,那么一定要使用::这个操作符,当编译器在全局域找不到这个函数的时候,编译器就知道去哪里找这个函数了。
实例化
实例化是什么呢,给大家举个例子吧,我们修房子之前是不是需要计划好这栋房子的样子呢?那么我们是不是需要让设计师画好图纸呢,同样的道理,我们的类也是这样
类相当于图纸,而实例化对象就是我们按照图纸建造的房屋。我们看段代码理解一下
对象大小
其实对象大小的计算也是和C语言是一样的,遵循内存对齐规则,我们来稍微复习一下。
1.第⼀个成员在与结构体偏移量为0的地址处。
2. 其他成员变量要对⻬到某个数字(对⻬数)的整数倍的地址处。
3.注意:对⻬数 = 编译器默认的⼀个对⻬数 与 该成员⼤⼩的较⼩值。
4.VS中默认的对⻬数为8
5.结构体总⼤⼩为:最⼤对⻬数(所有变量类型最⼤者与默认对⻬参数取最⼩)的整数倍。
6.如果嵌套了结构体的情况,嵌套的结构体对⻬到⾃⼰的最⼤对⻬数的整数倍处,结构体的整体⼤小就是所有最⼤对⻬数(含嵌套结构体的对⻬数)的整数倍
我们下面来看一段代码,想想为什么会这样。
A和B的内容都是空的,为什么打印出来的A和B 的大小都是1呢?
这是因为如果连大小都没有的话,那么我们怎么知道A和B存在过呢,所以这里起到的是占位符的作用。
this指针
我们下面看一段代码
Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调⽤Init和Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?,我们的this指针就派上用场了。
编译器编译以后,会自动给类成员函数的第一个形参的位置加一个该类的this指针。
void Init(Date* const this, int year,int month, int day)
就是上面红色字体这样,我们就知道是使用的d1的Init函数了。
使用Init函数时,我们需要给d1的年月日赋值,那么就是this指针的作用,就像这样
this->_year = year;
但是我们写代码的时候并不是这样写的,只是他的底层是这样的,我们就像平常一样赋值就行了,到时候编译器会自动加上的。
注意事项:我们在形参位置不可以显示使用this指针,但是在函数内部可以显示使用this指针。
其实this指针也相当于一个形参,所以他应该是在寄存器中。
类的默认成员函数
构造函数
构造函数虽然叫构造函数,但是他的作用并不是开创空间的,而是初始化实例化对象,就可以代替我们之前写的Init函数,但是构造函数有自己的规则。构造函数的特点:
1. 函数名与类名相同。
2. ⽆返回值。 (返回值啥都不需要给,也不需要写void,不要纠结,C++规定如此)
3. 对象实例化时系统会⾃动调⽤对应的构造函数。
4. 构造函数可以重载。
5. 如果类中没有显式定义构造函数,则C++编译器会⾃动⽣成⼀个⽆参的默认构造函数,⼀旦⽤⼾显式定义编译器将不再⽣成。
这就是那三种构造函数。我们不写,编译器默认⽣成的构造,对内置类型成员变量的初始化没有要求,也就是说是是否初始化是不确定的,看编译器。对于⾃定义类型成员变量,要求调⽤这个成员变量的默认构造函数初始化。如果这个成员变量,没有默认构造函数,那么就会报错,我们要初始化这个成员变量,需要⽤初始化列表才能解决,初始化列表,我们下个章节再细细讲解。
析构函数
析构函数与构造函数功能相反,析构函数不是完成对对象本⾝的销毁,⽐如局部对象是存在栈帧的,函数结束栈帧销毁,他就释放了,不需要我们管,C++规定对象在销毁时会⾃动调⽤析构函数,完成对象中资源的清理释放⼯作。析构函数的功能类⽐我们之前Stack实现的Destroy功能,⽽像Date没有Destroy,其实就是没有资源需要释放,所以严格说Date是不需要析构函数的。
有一个小tips告诉大家是否要自己写析构函数,我们只要判断成员函数是否要申请资源,如果申请了,就和实现栈一样,那就要自己写析构函数,如果不需要,像日期类一样,那就不要自己写析构函数。
拷贝构造函数
如果⼀个构造函数的第⼀个参数是⾃⾝类类型的引⽤,且任何额外的参数都有默认值,则此构造函数也叫做拷⻉构造函数,也就是说拷⻉构造是⼀个特殊的构造函数。
拷⻉构造的特点:
1. 拷⻉构造函数是构造函数的⼀个重载。
2. 拷⻉构造函数的第⼀个参数必须是类类型对象的引⽤,使⽤传值⽅式编译器直接报错,因为语法逻辑上会引发⽆穷递归调⽤。 拷⻉构造函数也可以多个参数,但是第⼀个参数必须是类类型对象的引⽤,后⾯的参数必须有缺省值。
3. C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,所以这⾥⾃定义类型传值传参和传值返回都会调⽤拷⻉构造完成。
4. 若未显式定义拷⻉构造,编译器会⽣成⾃动⽣成拷⻉构造函数。⾃动⽣成的拷⻉构造对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的拷⻉构造。
我们根据规则,写出的拷贝构造是这样的,但是我们要思考一个问题,就是为什么拷贝构造的参数是引用呢?并且还加了一个const。
我们注意一下上面的第三条,标红的那一条,说明了传值传参都会调用 拷贝构造函数,那么我们如果不用引用的话就会变成传值传参,那就回一直调用拷贝构造,如果语言不好理解,那我们看一个图片你就明白了。
运算符重载
1.当运算符被⽤于类类型的对象时,C++语⾔允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使⽤运算符时,必须转换成调⽤对应运算符重载,若没有对应的运算符重载,则会编译报错。
2.运算符重载是具有特殊名字的函数,他的名字是由operator和后⾯要定义的运算符共同构成。和其他函数⼀样,它也具有其返回类型和参数列表以及函数体。
3.重载运算符函数的参数个数和该运算符作⽤的运算对象数量⼀样多。⼀元运算符有⼀个参数,⼆元运算符有两个参数,⼆元运算符的左侧运算对象传给第⼀个参数,右侧运算对象传给第⼆个参数。
4.如果⼀个重载运算符函数是成员函数,则它的第⼀个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数⽐运算对象少⼀个。
5.运算符重载以后,其优先级和结合性与对应的内置类型运算符保持⼀致。
6.不能通过连接语法中没有的符号来创建新的操作符:⽐如operator@。
7. .* :: sizeof ?: . 注意以上5个运算符不能重载。
8.重载操作符⾄少有⼀个类类型参数,不能通过运算符重载改变内置类型对象的含义,如: int operator+(int x, int y)
9.⼀个类需要重载哪些运算符,是看哪些运算符重载后有意义,⽐如Date类重载operator-就有意义,但是重载operator+就没有意义。
10.重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,⽆法很好的区分。C++规定,后置++重载时,增加⼀个int形参,跟前置++构成函数重载,⽅便区分。
是不是觉得这个运算符重载的规矩还挺多的。其实还是很实用的,我们很多地方都要用到。
这就是他的完整用法了。