1.类的定义
1.1类定义的格式
- class是定义类的关键字,Date为类的名字,{ }中为类的主体,注意定义类结束时后面的分号不能省略。类中的内容称为类的成员;类中的变量称为类的属性或成员变量;类中的函数称为类的方法或者成员函数:
我们可以通过类名来实例化一个d1,通过d1我们可以使用类中的公有数据,但是在类外我们不能访问类中的保护和私有数据。
- C++中struct也可以定义类,C++兼容C中的struct用法,同时struct升级成为了类,并且strcut中可以定义成员函数。
1.2访问限定符
访问限定符分为三种: public、private、 protected。
- C++用类将对象的属性和方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的使用者。
- public修饰的成员变量在类外可以直接被访问;protected和private修饰的成员在类外不能直接被访问,protected和private在目前基础阶段可以看为是一样的,在后面的继承章节中才会有所区别。
- 访问权限作用域作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止,如果后面没有访问限定符,作用域到 } 即类结束。(如上图Date类,public从函数开始到打印函数结束为public的访问权限作用域)
- class定义成员没有被访问限定符修饰时默认为private,struct默认为public。
- 一般成员变量都会被protected或private修饰,需要给别人使用的成员函数会被public修饰。
2.对象大小
例如下方Date类:
当我们使用sizeof来计算d1的大小时,和C语言中的struct一致,遵循内存对齐规则:
- 第一个成员在与结构体偏移量为0的地址处。
- 其他成员变量要对齐到相应对齐数的整数倍地址处。
- 对齐数 = 编译器默认的一个对齐数与该成员大小的较小值。
- 结构体总大小为:最大对齐数的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍。
3.this指针
、
Date类中有两个成员函数,函数体中没有关于不同对象的区分,所以当d1调用两个函数时,调用的函数是该访问d1的对象还是d2的对象呢?这里就需要C++的this指针来解决这个问题。
其实在我们定义函数时,C++编译器默认会加一个当前类型的指针,叫做this指针。例如上面的函数:
类的成员函数访问成员变量,本质都是通过this指针访问的,例如Init函数中给_year赋值: this->_year = year;
C++规定不能在实参和形参位置显示的写出this指针。
4.类的默认成员函数
默认成员函数就是用户没有显示实现,编译器会自动生成的成员函数成为默认成员函数。一个类,我们不写的情况下编译器会默认生成六个默认成员函数
4.1构造函数
构造函数是特殊的成员函数,构造函数虽然名称为构造,但是构造函数的主要任务并不是开空间创建对象,而是对象实例化时就初始化对象。构造函数本质是要替代之前写Stack时Init函数的功能。
构造函数的特点:
- 函数名与雷凌相同
- 无返回值(不需要给任何返回值,包括void,C++规定如此 )
- 对象实例化时系统会自动调用相对应的构造函数。
- 构造函数可以重载
- 如果类中没有显示定义构造函数,那么C++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义构造函数,编译器将不再生成。
- 无参构造函数、全缺省构造函数、不写构造函数时编译器默认生成的构造函数,统称为默认构造函数。但是这三个构造函数有且只有一个存在,不能同时存在。例如当无参和全缺省构造函数同时存在,当我们都不传参的时候,就会引起调用歧义。
- 我们不写、编译器默认生成的构造函数,会对自定义类型成员变量,要求调用这个成员变量的默认构造函数舒适化。如果这个成员变量没有默认构造函数,那么就会报错。
4.1.1无参构造函数
在实例化d1时,d1中的成员变量已经初始化完毕,Date()函数就是无参构造函数
4.1.2带参构造函数
上文已经说到什么是默认构造函数,因此当我们定义无参构造函数时,Date中就不存在默认构造函数了,我们必须像实例化d1那样,才是正确的。
4.1.3全缺省构造函数
此时d1和d2都没有报错,并且程序正常运行,这就是全缺省构造函数和无参构造函数的却别。
4.2析构函数
析构函数与构造函数功能相反,析构函数不是完成对象本身的销毁,C++规定对象在销毁时会自动调用析构函数,完成对象中资源的清理释放工作。析构函数的功能类比Stack实现的Destroy功能。
析构函数的特点:
- 析构函数名是在类名前加上字符~。
- 无参数无返回值(与构造函数类似,也不需要void)。
- 一个类只能有一个析构函数。若未显示定义,系统会自动默认生成析构函数。
- 对象生命周期结束时,系统会自动调用析构函数。
- 与构造函数类似,我们不写,编译器自动生成的析构函数对内置成员不做处理,自定义成员会调用其的析构函数。
- 如果类中没有申请资源,析构函数可以不写,直接使用编译器生成的默认析构函数。但如Stack需要申请资源,一定要自己写析构函数,否则会造成内存泄漏。
- 一个局部域的多个对象,C++规定后定义的先析构。
例如:
像下面这种:
使用两个栈来实现一个队列,在析构时,会去调用Stack里面的析构函数,即使在MyQueue中显示定义了析构函数,也会调用Stack里面的析构函数。
那如果多次实例化类,每个类的构造和析构顺序又如何呢?我们来看看下面的一道例题:
C c;
int main()
{
A a;
B b;
static D d;
return 0;
}
假设已经有了 A B C D四个类的定义,那么程序中的西沟调用顺序是:
A. D B A C
B. B A D C
C. C D B A
D. A B D C
我们只需要记住下面的顺序即可解决这道题目:
- 全局对象优先于局部对象进行构造。
- 局部对象按照出现的顺序进行构造。
- 无论是否为static析构的顺序按照构造相反的顺序析构。
- 但要注意static改变对象的生存作用域后,会在局部对象之后进行析构。
因此顺序应该为 : B A D C
以上内容如有错误欢迎批评指正!!