类和对象详解

类和对象详解C 语言是面向过程的 关注的是过程 分析出求解问题的步骤 通过函数调用逐步解决问题

大家好,欢迎来到IT知识分享网。

前言

一、类和对象

1.类与对象的思想

面向对象的特点:抽象、封装、继承、多态
面向对象编程的特点:

  • 易维护:可读性高,即使改变了需求,由于继承的存在,只需要对局部模块进行修改,维护起来非常方便,维护的成本也比较低。
  • 质量高:可以重用以前项目中已经被测试过的类,使系统满足业务需求从而具有更高的质量
  • 效率高:在软件开发时,根据设计的需求要对现实世界的事物进行抽象,从而产生了类
  • 易扩展:由于继承、封装、多态的特性,可设计出高内聚、低耦合的系统结构,使系统更加灵活、更容易扩展,而且成本也比较低。

2.类定义的格式

  类是一种复杂的数据类型,它是将不同类型的数据和这些数据相关的操作封装在一起的集合体。类中的成员有数据成员函数成员两种。

  • 数据成员是用来描述对象属性的静态成员。
  • 函数成员是用来描述对象属性的动态成员。

类定义的格式如下:

class <类名> { 
    public: <数据成员或函数成员的说明> protected: <数据成员或函数成员的说明> private: <数据成员或函数成员的说明> }; 

  其中class是定义类的关键字,<类名>是一种标识符类名的首字符,通常大写。花括号内是类的说明部分,用来说明该类的成员。
  从访问权限上来,分类的成员分为公有的(public)私有的(private)保护的(protected) 3类。关键字public 、private 、protect,被称为访问权限修饰符或访问控制修饰符,公有成员用public来说明,公有成员可以在任意函数中访问私有成员;用private来说明,私有成员只能在本类的成员函数中访问;保护成员用protected说明,保护成员可以在本类及其派生类的成员函数中访问。

类的两种定义方式

  1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。
class Person { 
    public: void show() { 
    cout<<_name<<" "<<_sex<<" "<<_age<<endl; } private: char* _name; char* _sex; int _age; }; 

注意:这里Person类的成员函数show将会作为内联函数处理

  1. 类声明放在.h文件中,成员函数定义放在.cpp文件中,注意:成员函数名前需要加类名::
class Person { 
    public: void show(); private: char* _name; char* _sex; int _age; }; void Person::show() { 
    cout<<_name<<" "<<_sex<<" "<<_age<<endl; } 

3.类的访问限定符及封装

   C++封装:在类和对象阶段,主要是研究类的封装特性,那什么是封装呢?

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

 【访问限定符说明】 1. public修饰的成员在类外可以直接被访问 2. protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的) 3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止 4. 如果后面没有访问限定符,作用域就到 } 即类结束。 5. class的默认访问权限为private,struct为public(因为struct要兼容C) 

4.类的作用域

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

class Person { 
    public: void show(); private: char* _name; char* _sex; int _age; }; void Person::show() { 
    cout<<_name<<" "<<_sex<<" "<<_age<<endl; } 

指明 show 函数属于 Person

5.类的实例化

用类类型创建对象的过程,称为类的实例化

  1. 类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没
    有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个
    类,来描述具体学生信息。

  2. 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
    Person类是没有空间的,只有Person类实例化出的对象才有具体的年龄。
  3. 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设
    计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象
    才能实际存储数据,占用物理空间。

class Person { 
    public: void show(); private: char* _name; char* _sex; int _age; }; void Person::show() { 
    cout<<_name<<" "<<_sex<<" "<<_age<<endl; } int main() { 
    Person A; //对象的实例化 } 

创建 Person 类对象 A 的过程,就称为对象的实例化

  这里提一句 :用户存储空间分为程序区(代码区)、静态存储区(数据区)、和动态存储区(栈区和堆区)。代码区存放程序代码。程序运行前就分配存储空间。数据区存放常量静态变量(对象),全局变量(对象)等。程序运行前就分配存储空间一直保留到程序结束,栈区存放局部变量(对象),函数参数,函数返回值和临时变量(对象)等。编译时程序在栈区中留出一定的空间。程序运行时按先进后出原则进入栈区。

6.类对象模型

计算类对象的大小

所以实际情况是:

  • 代码只保存一份,在对象中保存存放代码的地址
  • 只保存成员变量,成员函数存放在公共的代码段

7.this指针

this指针的引出

class Date { 
    public: void Init(int year, int month, int day) { 
    _year = year; _month = month; _day = day; } void Print() { 
    cout <<_year<< "-" <<_month << "-"<< _day <<endl; } private: int _year; int _month; int _day; }; int main() { 
    Date d1, d2; d1.Init(2022,1,11); d2.Init(2022, 1, 12); d1.Print(); d2.Print(); return 0; } 

关于this指针,有以下几点总结:

  1. this指针指向当前对象,可以访问当前对象的所有成员变量。包括private、protected、public。
  2. this指针是const指针,一切企图修改该指针的操作,如赋值(改变指向)、增减都是不允许的!
  3. this指针只有在成员函数中才有定义。因此,在创建一个对象后,也不能通过对象使用this指针。所以,我们也无法知道一个对象的this指针的位置(只有在成员函数里才有this指针的位置)。当然,在成员函数里,你是可以知道this指针的位置的(可以&this获得),也可以直接使用的。
  4. 只有创建对象后,this指针才有意义。
  5. static静态成员函数不能使用this指针。原因静态成员函数属于类,而不属于某个对象,所以static静态成员函数压根就没有this指针。
  6. this在成员函数的开始执行前构造的,在成员函数的执行结束后清除。至于如何清除的,由编译器实现,程序员不关心。this是通过函数参数的首参数来传递的。

二. 类的6个默认成员函数

C++共有以下6个默认成员函数:

构造函数、析构函数、拷贝构造函数、赋值运算符重载、const成员函数、取地址及const取地址操作符重载

这些默认成员函数的特点是如果程序员没有自己编写,那么系统会自动生成,但如果编写了默认的就不再生成

1.构造函数

  构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次。

class Date { 
    public: void Init(int year, int month, int day) { 
    _year = year; _month = month; _day = day; } void Print() { 
    cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { 
    Date d1; d1.Init(2022, 7, 5); d1.Print(); Date d2; d2.Init(2022, 7, 6); d2.Print(); return 0; } 

无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

  注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。

  构造函数的特点如下:

  • 构造函数是与所在类同名的成员函数。
  • 构造函数无返回值。
  • 构造函数可以重载。
  • 构造函数的在创建类时自动调用,不允许像其他函数那样由用户调用。
  • 系统默认的构造函数为 X(){},即空构造函数。
  • 只要定义了一个构造函数,系统默认的构造函数(空构造)就不存在。

2.析构函数

  析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
  析构函数在一个对象退出生存期时,被系统自动调用,因此可以将对象消亡前需要做的一些工作定义在析构函数中。在析构函数中可以释放该对象占用的一些资源,比如说该对象有指针成员,该指针成员在程序运行过程中申请的动态空间,这些动态空间在对象消亡前要进行释放,这样操作就可以在析构函数中完成。如果在类中没有定义虚构函数,则系统自动为类提供一个默认析构函数,这个析构函数的函数体是空的,什么也不做。

class Test { 
    public: Test() { 
    arr=(int*)malloc(sizeof(int)*10); } ~Test() { 
    free(arr); arr=NULL; } private: int* arr; }; int main() { 
    Test A; } 

在退出 main 函数时系统自动调用析构函数,释放掉动态分配的数组,并置空;

析构函数不返回任何值,没有返回类型,也没有函数参数。由于没有函数参数,因此它不能被重载。换言之,一个类可以有多个构造函数,但是只能有一个析构函数
  析构函数的特点如下:

  • 函数名同类名,函数名前加~。
  • 析构函数无返回类型,无参数,不能重载,访问权限一般为public。
  • 析构函数在对象消亡前自动调用。
  • 构造函数的在创建类时自动调用,不允许像其他函数那样由用户调用。
  • 系统默认的析构函数为 ~X(){}
  • 析构函数调用次序与构造函数调用次序相反,即后构造的对象先析构。

3.拷贝构造函数

  拷贝构造:拷贝构造函数,又称构造函数,是一种特殊的构造函数,它由编译器调用来完成一些基于同一类的其他对象的构造及初始化。

class Array { 
    int *a; int n; public: Array(int aa[],int nn) //构造函数 { 
    n=nn; a=(int*)new int[n]; for(int i=0;i<n;i++) { 
    a[i]=aa[i]; } } ~Array() //析构函数 { 
    delete[] a; } void print() { 
    for(int i=0;i<n;i++) { 
    cout<<a[i]<<" "; } cout>>endl; } } int main() { 
    int aa[6]={ 
   1,2,3,4,5,6}; Array a1(aa,6); Array b1(a1); a1.print(); b1.print(); } 

上述程序中定义了两个数组类 a1 和 a2 ; 对象 b1 是通过 a1 来创建的,创建 b1 时调用默认的赋值构造函数,该默认的赋值构造函数如下。

Array(Array &src) { 
    (*this).a = src.a; (*this).n = src.n; } 

  默认的复制构造函数只将指针指向原来的数组,称为“浅复制”。以此达到复制的效果,实际上 a1 和 b1 的数组都共用同一块空间,那么当程序运行结束的时候,b1 先调用析构函数,将数组所占用的空间释放掉,当 a1 调用析构函数时,将会再一次对 a 所指向的数组空间进行释放,然而数组已经被 b1 的析构函数释放过,再次释放将会造成同一块空间释放两次,从而导致程序出错 。
  所以为了解决这个问题,需要我们自己写一个拷贝构造函数,为 b1 的 a 声请一块新的内存空间,同时吧 a1 的数组内容拷贝过来 ,以此达到真正意义上的复制。称为“深复制”。具体代码实现如下:

Array::Array(Array & aa) { 
    n=aa.n; a=new int[n]; for(int i=0;i<n;i++) { 
    a[i]=aa.a[i]; } } 

这样 a1 b1 的都有属于自己的数据成员,没有共用同一块空间的情况,在析构时也就不会出错了。

  注意:这里必须用引用的方式,若只是按值传递,在实参象形参传递时,需要复制一份数据(函数传参时将实参拷贝一份给形参的),但因为没有拷贝构造函数(系统自带的无法使用,因为你定义了拷贝构造,默认的就不会出现了),所以不可能完成复制,则会循环嵌套调用你定义的非引用方式的拷贝构造函数。因此,在这里必须使用引用的方式。

未完待续…

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/122324.html

(0)
上一篇 2025-10-17 15:20
下一篇 2025-10-17 15:33

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信