当前位置:   article > 正文

「C++ 类和对象篇 1」类的引入_c++如何在一个程序里面加入类

c++如何在一个程序里面加入类

目录

一、类的概念

二、类的引入

三、类的定义 

1.在C++中定义一个类

2.struct 和 class 的区别

3.成员函数的声明与定义

四、封装及类的访问限定符

1.封装

2.类的访问限定符       

五、类的作用域和生命周期

六、类的实例化

0. 概念

1. 隐式创建

2. 显式创建

3. 显式new创建

七、类、对象、类的成员的存储

1. 类在内存中的存储位置

2.对象在内存中的存储位置

2.1 全局对象

2.2 非静态局部对象

2.3 静态局部对象

2.4 动态对象

【代码演示】 

【内存布局】

3. 成员函数存储方法

4. 成员变量在内存中的存储

八、计算类的大小

「总结」


一、类的概念

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.在C++中定义一个类

  1. class className //法一
  2. {
  3. // 类体:由成员函数和成员变量组成
  4. }; // 一定要注意后面的分号
  5. struct structName //法二
  6. {
  7. // 类体:由成员函数和成员变量组成
  8. }; // 一定要注意后面的分号
  9. 说明:
  10. class(或struct)为定义类的关键字
  11. className(structName)为类的名字
  12. {}中为类的主体,注意类定义结束时后面分号不能省略。
       类体中内容称为类的成员,类中的变量称为类的属性或成员变量;类中的函数称为类的方法或者成员函数

2.struct 和 class 的区别

        C++中既可以使用struct定义类,也可以使用class定义类,二者的区别在于: 在不加限定符的情况下,struct默认为公有,class默认为私有。

拓: 类的访问限定符       

3.成员函数的声明与定义

a. 声明与定义不分离

       成员函数的声明和定义全部放在类体中。需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。


b.声明与定义分离

       即成员函数的声明放在.h文件中,成员函数定义放在.cpp文件中。需注意:当声明和定义分离时,因为声明在类域中,故定义时成员函数名前需要加上类名和域作用限定符(::),才能找到该类。


类中成员函数声明和定义分离的意义在于方便阅读代码

拓1、成员函数如果在类中定义,编译器可能会将其当成内联函数处理。

       声明和定义分离后,类的成员函数就不会再被当成内联函数了。(内联函数的声明和定义不能分离,内联函数不会进符号表,链接时靠声明无法找到定义的地址)


拓2、对成员函数声明和定义分离时,如果定义时我们不加类的域名,这时类域中和全局域中就会出现两个同名函数,这是可行的且不构成函数重载。(在不同域中的函数不构成重载)   


四、封装及类的访问限定符

1.封装

1.1 是什么?

        本质上是对数据的管理:隐藏对象的属性、函数(方法)的实现细节。对外仅公开接口,且控制在程序中属性的读和修改的访问级别。


1.2 为什么?

        提高程序的安全性和可维护性(改进的难易程度)。


1.3 怎么实现?

         C++实现封装的方式:用类将属性与方法结合在一块,通过设置访问权限选择性的将其接口提供给外部的用户使用。  

2.类的访问限定符       

public(公有)、private(私有)、protected(保护)


五、类的作用域和生命周期

1.类的作用域

         类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域 


2.类的生命周期

        类的生命周期和存储位置有关,如:全局的类和静态的类存储在静态区,它的生命周期就是整个工程,在函数中的类,生命周期随着这个函数……


六、类的实例化

0. 概念

用类创建对象的过程,称为类的实例化。一个类可以实例化出多个不同对象。


        类不占空间,实例化成对象后才会占用空间,用于存储类成员变量。但是我们也可以通过类计算对象占用的空间大小。

        同理,定义结构体不占空间,定义结构体变量才会占用空间,但是我们也可以使用sizeof结构体名来计算结构体变量占用的空间大小。 

1. 隐式创建

由编译器调用默认构造函数,在栈中创建。

  1. class A{};
  2. A a;

2. 显式创建

在代码中主动调用构造函数,在栈中创建。

  1. class A{};
  2. A a = A();

3. 显式new创建

使用了new关键字,在堆中分配内存创建。

  1. class A{};
  2. A* pa = new A();

new出来的对象必须要用指针接收,并且需要显式delete销毁对象释放内存。


七、类、对象、类的成员的存储

1. 类在内存中的存储位置

  • 类不会储存在内存中,类只是一个模板告诉程序,如何创建一个对象、需要多少个字节、以及如何销毁等等。
  • 类实例化出的对象才会保存中内存中。

2.对象在内存中的存储位置

2.1 全局对象

        全局对象存放在全局(静态)存储区,程序结束后由系统调用析构函数释放。


2.2 非静态局部对象

        局部对象存放在中。其生命在作用域结束时结束,它的析构函数会自动被调用,即对象自动被清理。

        const修饰的对象仅表示该对象的值是只读的,而不是对象本身存储的位置发生了变化。


2.3 静态局部对象

        静态局部对象存放在全局(静态)存储区,其生命在作用域结束之后仍然存在,即此时对象的析构函数并不会被调用,直到整个程序结束。


2.4 动态对象

        动态对象(即new出来的对象)存放在中,其生命在它被 delete 之际结束。用于创建动态对象的指针存放在栈中。
        注:new 的对象,必须使用 delete 去显式的调用析构函数,否则程序不会去调用其析构函数,从而造成内存泄露。


【代码演示】 

  1. class A{
  2. };
  3. class B{
  4. };
  5. class C{
  6. };
  7. class D{
  8. };
  9. A a; //全局对象
  10. int main(){
  11. B b; //局部对象
  12. static C c; //静态局部对象
  13. D *d = new D; //动态对象
  14. delete d;
  15. return 0;
  16. }

【内存布局】

3. 成员函数存储方法

成员函数存储方法猜想一:将成员函数的地址和成员变量都存在对象中

        存在问题:调的成员函数都是一样的,所以没必要都每个对象都存一个成员函数的地址。


成员函数存储方法猜想二:将成员函数的地址单独存放进一张表中,然后存储时多存一个表的地址

        这种方法没有什么问题,但没有被采用。


成员函数存储方法猜想三:对象中只存成员变量,将成员函数统一存放在公共代码区。

        这个方案就是成员函数的存储方案,运行时不会在对象中找函数的地址,编译链接时才会根据函数名去公共代码区找到函数的地址。


总结:对象中只存成员变量,成员函数将统一存放在公共代码区。

        所以类在遇到如下情况,程序不会崩溃。(因为找成员函数的地址不是在类中找,是去公共代码区找,所以类被实例化为nullptr我们依然能够调用成员函数)

4. 成员变量在内存中的存储

1)类内的静态成员变量  ------------   静态区

2)类内的非静态成员变量  ------------   和对象的存储位置有关,类的非静态成员变量并不能决定自身的存储空间位置。决定存储位置的是对象的创建方式,即:

        a)如果是全局对象,则对象,对象的成员变量保存在静态区。

        b)如果是非静态局部对象,则对象,对象的成员变量保存在栈区。

        c)如果是静态局部对象,则对象,对象的成员变量保存在静态区。

        d)如果是动态对象,则对象,对象的成员变量保存在堆区。

(const修饰只是将变量限定为只读的,本质还是变量,存储位置看变量本身,所以const修饰的成员变量的存储位置仍要看对象的存储位置。)


【代码演示】 

  1. // 以下代码用于演示类内的非静态成员变量的存储位置和对象的存储位置有关
  2. class Student
  3. {
  4. public:
  5. int name;
  6. int age;
  7. };
  8. Student a = Student(); //全局对象
  9. int main()
  10. {
  11. Student b = Student(); //局部对象
  12. static Student c = Student(); //静态对象
  13. Student* d = new Student; //动态对象
  14. cout << "全局对象的地址:" << &a << ",全局对象的成员变量的地址:" << &(a.name) << " " << &(a.age) << endl;
  15. cout << "局部对象的地址:" << &b << ",局部对象的成员变量的地址:" << &(b.name) << " " << &(b.age) << endl;
  16. cout << "静态对象的地址:" << &c << ",静态对象的成员变量的地址:" << &(c.name) << " " << &(c.age) << endl;
  17. cout << "动态对象的地址:" << d << ",静态对象的成员变量的地址:" << &(d->name) << " " << &(d->age) << endl;
  18. return 0;
  19. }

更多关于对象及其成员在内存中的存储的分析请看:「C++ 类和对象篇 2」对象及其成员在内存中的存储-CSDN博客


八、计算类的大小

1.计算成员变量的大小

        类和结构体的一样,也遵守内存对齐规则,从第二个成员变量开始,起始位置要计算,在自己的大小和默认对齐数(VS编译器中默认对齐数为8)中选择较小的那个,起始位置为其倍数。最后整个类也必须要对齐:类的大小必须是默认对齐数的整数倍。

        当sizeof计算类的大小的时候会忽略静态成员变量的大小。


2.计算成员函数的大小

        算类的大小时,不用考虑成员函数,故不用加上函数指针的大小。


3.计算空类的大小

        注意,没有成员变量的类(包括空类)的大小为1字节,不存储数据,目的是为了占位,标识对象存在,区分不同的对象。


总结:

        计算类的大小就是按照内存对齐的方式计算类中所有(非静态)成员变量 的大小。

拓.内存对齐

        在编程中,内存对齐是一种提高内存访问效率的方法。简单来说,内存对齐就是将数据存储在特定的地址,这个地址是某个特定数值(对齐数)的整数倍。这样做的好处是可以提高CPU的内存访问效率,因为CPU访问内存时是按块进行的,如果数据没有对齐,CPU就需要多次访问内存才能获取到完整的数据,而如果数据对齐,CPU就可以一次访问就获取到完整的数据。

        在C++中,类和结构体也会遵守内存对齐规则。每个成员变量在内存中的位置都会从其上一个成员变量结束后起始位置的某个倍数开始。这个起始位置的计算方式是:取默认对齐数(在VS编译器中默认对齐数为8)和自己的大小中的较小值,然后选择这个数的整数倍作为起始位置。

        同时,整个类的大小也必须是对齐数的整数倍。这是因为如果类的大小不是对齐数的整数倍,那么在访问这个类的对象时,CPU就可能需要多次访问内存才能获取到完整的数据。

这种内存对齐的方式可以提高CPU的内存访问效率,但是它也会导致一定的内存浪费。因为即使有些成员变量之间有空隙,这些空隙也不能被利用。所以,内存对齐是一个权衡内存使用效率和内存访问效率的问题。


「总结」


------------------------END-------------------------

才疏学浅,谬误难免,欢迎各位批评指正。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/1020890
推荐阅读
相关标签
  

闽ICP备14008679号