目录
扫描二维码关注公众号,回复: 16373392 查看本文章
一、类的概念
1.是什么?
是对有相同属性和行为的事物的抽象。
2.为什么?
把有关联的数据和函数放在一起方便管理,且类是面向对象编程的前提。
3.怎么用?
把要描述的事物的属性和行为(用函数实现)放在一起形成一个类。
二、类的引入
1.struct升级为类
在C++中 struct 不但兼容C语言中结构体的语法,而且 struct 在C++中升级成了类。
2.类中能定义函数
类包含两个部分成员变量、成员函数。所以在C++中,结构体内不仅可以定义变量,也可以定义函数。
3.class也能定义类
除struct外,C++中还能使用class来定义类,在C++中更常用class来定义类。
拓:struct 在 C 和 C++ 中的细微区别
1.C中使用typedef重命名结构体在,最后一行之后才生效,结构体中使用结构体名也必须带上struct。
2.C++中甚至不需要使用typedef重命名就可直接在类中使用类名。
三、类的定义
1.定义一个类
class className //法一 { // 类体:由成员函数和成员变量组成 }; // 一定要注意后面的分号 struct structName //法二 { // 类体:由成员函数和成员变量组成 }; // 一定要注意后面的分号
class(或struct)为定义类的关键字,className(structName)为类的名字,{}中为类的主体,注意类定义结束时后面分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的方法或者成员函数。
2.struct 和 class 的区别
C++中既可以使用struct定义类,也可以使用class定义类,二者的区别在于:
在不加限定符的情况下,struct默认为公有,class默认为私有。
拓: 类的访问限定符
3.类中成员函数的声明、实现分离
a. 成员函数的 声明和实现 全部放在类体中,需注意:成员函数如果在类中定义,编译器 可能 会将其当成内联函数处理。
b. 成员函数的声明放在.h文件中,成员函数实现放在.cpp文件中。(注意:当声明和实现分离时,因为声明在类域中,故定义时 成员函数名前需要加上 类名和域作用限定符(::),才能找到该类。 )
在大型项目中推荐把类中成员函数和普通函数一样,将声明和实现分离,类中成员函数声明和定义分离的意义在于方便阅读代码。
拓1、成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
声明和实现分离后,类的成员函数就不会再被当成内联函数了。(内联函数的声明和实现不能分离,内联函数不会进符号表,链接时靠声明无法找到实现的地址)
拓2、在声明和定义分离时:如果定义时中我们不加类的域名,这时类域中和全局域中就会出现两个同名函数,这是可行的且不构成函数重载。(在不同域中的函数不构成重载)
四、封装及类的访问限定符
1.封装
1.是什么?
本质上是对数据的管理:隐藏对象的属性和方法的实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别。
2.为什么?
提高程序的安全性和可维护性(改进的难易程度)。
3.怎么做?
C++实现封装的方式:用类将属性与方法结合在一块,通过设置访问权限选择性的将其接口提供给外部的用户使用。
2.类的访问限定符
public(公有)、private(私有)、protected(保护)
五、类的作用域和生命周期
1.类的作用域
类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域。
2.类的生命周期
类的生命周期和存储位置有关,如:全局的类和静态的类存储在静态区,它的生命周期就是整个工程,在函数中的类,生命周期随着这个函数……
六、类的实例化
【概念】
用类创建对象的过程,称为类的实例化。一个类可以实例化出多个对象。
类不占空间,实例化成对象后才会占用空间,用于存储类成员变量。但是我们也可以通过类计算对象占用的空间大小。
同理,定义结构体不占空间,定义结构体变量才会占用空间,但是我们也可以使用sizeof结构体名来计算结构体变量占用的空间大小。
1.隐式创建
由编译器调用默认构造函数,在栈中创建。
class A{}; A a;
2.显式创建
在代码中主动调用构造函数,在栈中创建。
class A{}; A a = A();
3.显式new创建
使用了new关键字,在堆中分配内存创建。
class A{}; A* pa = new A();
new出来的对象必须要用指针接收,并且需要显式delete销毁对象释放内存。
七、类和对象的存储方法
1.成员函数存储方法猜想
成员函数存储方法猜想一:将成员函数的地址和成员变量都存起来
存在问题:调的成员函数都是一样的,所以没必要都每个对象都存一个成员函数的地址
成员函数存储方法猜想二:将成员函数的地址单独存放进一张表中,然后存储时多存一个表的地址
这种方法没有什么问题,但在这里没有被采用。
成员函数存储方法猜想三:只存成员变量,将成员函数统一存放在公共代码区。
这个方案就是成员函数的存储方案,运行时不会在对象中找函数的地址,编译链接时才会根据函数名去公共代码区找到函数的地址。
所以类在遇到如下情况,程序不会崩溃。(因为找成员函数的地址不是在类中找,是去公共代码区找,所以类被实例化为nullptr我们依然能够调用成员函数)
2.类内成员在内存中的分布
1)类内的普通成员变量 ------------ 栈
2)类内的静态成员变量 ------------ 静态区
3)类内的引用变量 ------------ 看引用的变量
4)类内的const变量 ------------ 栈 (const修饰只是将变量限定为只读的,本质还是变量,存储位置看变量本身,所以const修饰的局部变量仍在栈上,且能通过指针修改。)5)类的成员函数 ------------ 普通成员函数,静态成员函数都在代码区
3.类在内存中的存储位置
类不会储存在内存中,类只是一个模板告诉程序,如何创建一个对象、需要多少个字节、以及如何销毁等等。
类实例化出的对象才会保存中内存中。
4.对象在内存中的存储位置
3.1 全局对象
全局对象存放在全局(静态)存储区,程序结束后由系统调用析构函数释放。
3.2 局部对象
局部对象存放在栈中。其生命在作用域结束时结束,它的析构函数会自动被调用,即对象自动被清理。
3.3 静态局部对象
静态局部对象存放在全局(静态)存储区,其生命在作用域结束之后仍然存在,即此时对象的析构函数并不会被调用,直到整个程序结束。
3.4 动态对象
动态对象(即new出来的对象)存放在堆中,其生命在它被 delete 之际结束。用于创建动态对象的指针存放在栈中。
注:new 的对象,必须使用 delete 去显式的调用析构函数,否则程序不会去调用其析构函数,从而造成内存泄露。
【代码演示】
class A{ }; class B{ }; class C{ }; class D{ }; A a; //全局对象 int main(){ B b; //局部对象 static C c; //静态局部对象 D *d = new D; //动态对象 delete d; return 0; }
【内存布局】
八、计算类的大小
1.计算成员变量的大小
类和结构体的一样,也遵守内存对齐规则,从第二个成员变量开始,起始位置要计算,在自己的大小和默认对齐数(VS编译器中默认对齐数为8)中选择较小的那个。最后整个类也必须要对齐:类的大小必须是默认对齐数的整数倍。
当sizeof计算类的大小的时候会忽略静态成员变量的大小。
2.计算成员函数的大小
算类的大小时,不用考虑成员函数,故不用加上函数指针的大小。
3.计算空类的大小
注意,没有成员变量的类(包括空类)的大小为1字节,不存储数据,目的是为了占位,标识对象存在,区分不同的对象。
总结:
计算类的大小就是按照内存对齐的方式计算类中所有(非静态)成员变量 的大小。