【C++】类和对象专题

内容总结

c++数据类型分为简单数据类型和复杂数据类型,前者也就是c++内置的基本数据类型,如int 、double等,后者又分为c++内置的复合类型和自定义的类型。所以,归根结底,我们现在自定义的类本质上来说就是作为c++的一种特殊的数据类型,我们将各种复杂的数据和操作封装到一个类里面,所以在形式上,类的使用和定义本质上与基本类型类似,但是正是由于其拓展了数据类型的形式,所以出现了各种各样的问题,从而引申出了友元函数、重载等各种各样的知识点,另外也出现了深复制浅复制等问题。在这里插入图片描述

结构体

首先,我们接触到了结构体,简单来说,结构体就是由多种成员构成的一种复合类型。把不同类型、不同含义的数据当作一个整体来处理,结构体也就是具有相同类型或不同类型的数据构成的数据集合,也叫结构。
使用结构体,必须要先声明一个结构体类型,再定义和使用结构体变量。

struct 类型名{
    数据类型1   成员名1;
    数据类型2   成员名2;} 变量名;

需要注意的是,结构体定义后需要加分号!!
在 C++ 中,允许在结构体中定义成员函数,也就是说C++的结构体成员包括数据成员和成员函数两部分。
成员函数还可以根据需要定义重载运算符。

类型名 operator 运算符 (const 类型名 变量)const{}

这里不要忘记const限定!
结构体的使用和普通类型类似,另外还可以在定义之后直接声明。(如上)
结构体和普通数据类型一样,也可以形成结构体数组。
结构体除了整体赋值操作外一般不对整体操作,一般对结构体变量的成员进行操作结构体变量名. 成员名

类是对具有相同属性和行为的一类客观事物的概括描述。是用户自定义的数据类型(程序设计语言角度)类的定义包括行为和属性两个部分。属性以数据表示,行为通过函数实现。
类的定义

class  类名
{
   public: //类的外部接口
           公有数据成员和成员函数;
   protected: //仅允许本类成员函数及派生类成员函数访问
           保护数据成员和成员函数;
   private: //仅允许本类成员函数访问
           私有数据成员和成员函数;
}// 各成员函数的实现;
   返回值类型  类名::成员函数名(参数表)
{
          函数体
} 

和结构体一样,一定不要忘记最后的分号!
其实归根结底类和结构体非常相近,唯一区别就是在没有明确指定类成员的访问权限时,C++结构体的成员是公有的,而类的成员是私有的。
对于类的定义,为了维护类的封装性,一般定义数据成员为私有的,而成员函数一般定义为共有的。因此,如果想要通过类外访问某一个类的数据成员,需要在成员函数定义get、set函数,通过这些共有的方法来访问私有成员。当然,在软件开发过程中,需要考虑权限的问题,也就是哪些类可以get、set,哪些类不行,从而保证数据的安全性。
这里还要注意 ,在类的定义中不能对数据成员进行初始化,真要初始化需要用初始化列表。
关于类的使用还要注意,一般不用定义全局变量,否则会破坏封装性,所以类的成员函数只能操作类的成员和类的局部变量。

->成员函数能操作的数据:

  • 类内的数据成员
  • 函数内定义的局部变量
  • 全局变量(一般不要定义全局变量)

类的成员函数通过操作数据成员来实现功能的。
若操作时需要从外界获取数据的时候需要加形参。
若只对数据成员操作,则没必要加形参

类的成员可以是其他类的对象,称为类的组合。但不能以类自身的对象作为本类的成员。但是可以是本身的指针或者引用。
具体应用:
不使用结构体实现单链表
另外,关于类的对象空间管理,每个对象的数据成员都有单独独立的空间,而所有的函数全部存在一个共有空间里。
在这里插入图片描述

对象

对象是类的实例或实体。根据上面提到的类的本质,类是一种自定义的数据类型,归根结底还是一种数据类型,所以,类和对象的关系其实就相当于C++基本数据类型和该类型的变量之间的关系。 对象的定义也和基本数据类型保持形式一致。
和结构体一样,必须在定义了类之后,才可以定义类的对象,这其实也好理解,就相当于没有爸爸哪来的儿子呢?
对象是类的实例化,要想使用类的功能,必须定义对象,所以对对象成员的访问包括:
●圆点访问形式:对象名.公有成员
●指针访问形式:对象指针变量名->公有成员

对象的创建一定要按照构造函数的格式创建!

构造函数

构造函数是用于创建对象的特殊的成员函数,可以为对象分配空间;对数据成员赋初值;请求其他资源。构造函数不光可以初始化对象,还可以进行其他资源的初始化。
关于构造函数的使用:

  • 构造函数名与类名相同:类名
  • 构造函数可以重载(构造函数一定重载!!)
  • 构造函数可以有任意类型的参数,但没有返回类型

用户没有定义的构造函数时,系统自动提供缺省版本的构造函数,而如果我们定义构造函数,系统则不会主动生成默认构造函数,当我们定义的对象没有给参数时是非常危险的。所以我们如果定义构造函数,最少要定义两个,一个带参,一个不带参。

默认值

若所有参数都有默认值,则不加无参构造函数。函数形参的默认值要放到最后面,否则会产生二义性

析构函数(系统自动调用)

析构函数是用于取消对象的成员函数,当一个对象作用结束时,系统自动调用析构函数,析构函数的作用是进行对象消亡时的清理工作。析构函数不能重载
析构函数的使用:
析构函数名为: ~ 类名
析构函数没有参数,也没有返回类型
一般情况下,可以不定义析构函数
但如果类的数据成员中包含指针变量是从堆上进行存储空间分配的话,需要在析构函数中进行存储空间的回收
另外注意:
删除数组要加[]!否则只释放了首地址的空间

delete[] name;

初始化列表(系统自动调用)

构造函数初始化成员有两种方法:

  • 使用构造函数的函数体进行初始化
  • 使用构造函数的初始化列表进行初始化
funname(参数列表):成员名1(形参名1),成员名2(形参名2),成员名n(形参名n)
{  函数体,可以是空函数体  } 

!!必须使用初始化列表的几种情况:
1、数据成员为常量
2、数据成员为引用类型
3、数据成员为没有无参构造函数的类的对象

另外需要注意:
类成员的初始化的顺序:按照数据成员在类中的声明顺序进行初始化,与初始化成员列表中出现的顺序无关

this指针

this指针也就是此时本对象的地址。

需要显式引用this指针的三种情况
(1)在类的非静态成员函数中返回类对象本身或对象的引用的时候,直接使用 return *this,返回本对象的地址时,return this。
(2)当参数与成员变量名相同时,如this->x = x,不能写成x = x。
(3)避免对同一对象进行赋值操作,判断两个对象是否相同时,使用this指针。

复制构造函数

复制构造函数用一个已有同类对象创建新对象进行数据初始化
C++为类提供默认版本的复制构造函数

类名 :: 类名(const  类名  &  引用名  ,  …);

注意定义时的const限定。引用类型防止丢失,const限定防止修改

以下三种情况下由编译系统自动调用:

  • 声明语句中用类的一个已知对象初始化该类的另一个对象时。
  • 当对象作为一个函数实参传递给函数的形参时,需要将实参对象去初始化形参对象时,需要调用复制构造函数。
  • 当对象是函数的返回值时,由于需要生成一个临时对象作为函数返回结果,系统需要将临时对象的值初始化另一个对象,需要调用复制构造函数。

深复制、浅复制

在用一个对象初始化另一个对象时,只复制了数据成员,而没有复制资源,使两个对象同时指向了同一资源的复制方式称为浅复制。
即:对于复杂类型的数据成员只复制了存储地址而没有复制存储内容,默认复制构造函数所进行的是简单数据复制,即浅复制

关于深复制:
通过一个对象初始化另一个对象时,不仅复制了数据成员,也复制了资源的复制方式称为深复制。
自定义复制构造函数所进行的复制是浅复制。

简单来说,深复制不仅复制了对象的值,还复制了对象的资源,通俗点,深复制把人家原来对象的根全刨了,全部连根都拷贝到自己这来,最终结果深复制的对象和被复制的对象是完全一样的。
而浅复制就不一样了,浅复制只复制对象的值,没有复制资源。
对于一些简单的数据类型或者说没有涉及到更深层次的操作时,深复制和浅复制并没有太大的区别,甚至深复制更麻烦一些,但是如果在软件运行时真正出了问题,那就会直接找不到资源。
举个例子,比如复制数组,深复制是复制前后一共有两个数组,而浅复制由于只复制值,所以仅仅是将被复制的数组首地址赋给了该数组,导致最后还是一个数组属于两个对象,上面提到了,对象的数据空间要求单独、独立,这样显然不符合。另外可怕的是,万一被复制的数组改了储存位置或者这个数组被删除,那么另一个对象对会找不到资源,这是极其危险的。
所以以后写代码最好还是不要怕麻烦,直接深复制,免除后患。
另外注意,string类型封装了深复制,直接赋值就是深复制。

深复制的做法:对复杂类型的成员变量,使用new操作符进行空间的申请(真正有了自己的空间),然后进行相关的复制操作。

常成员

  • 常数据成员是指数据成员在实例化被初始化后,其值不能改变。
    如果在一个类中说明了常数据成员,那么构造函数就只能通过初始化列表对该数据成员进行初始化,而任何其他函数都不能对该成员赋值。
  • 类的成员函数说明后面可以加const关键字,则该成员函数成为常量成员函数。
    类型说明符 函数名(参数表) const;
    这里要注意const的位置,const放到最前面表示返回值是常量,而放到最后则表示是常成员函数。
    常成员函数只能使用成员值但是不能修改。
    常成员函数不能更新对象的数据! 但是可以用于输出
  • 如果在说明对象时用const修饰,则被说明的对象为常对象。在定义常对象时必须进行初始化,而且不能被更新。常对象只能调用它的常成员函数、静态成员函数、构造函数(具有公有访问权限)

静态成员

静态数据成员为同类对象共享。静态成员函数与静态数据成员协同操作。
静态成员不属于某一个单独的对象,而是为类的所有对象所共有。而对象的数据成员空间是独立的,如下图。
在这里插入图片描述
静态成员函数的作用不是为了对象之间的沟通,而是为了能处理静态数据成员: 保证在不依赖于某个对象的情况下,访问静态数据成员。
对于类的普通数据成员,每一个对象都各自拥有一个副本。(分配不同的存储空间)
对于静态数据成员,每个类都拥有一个副本 。(在静态存储区分配一个存储空间,对所有对象都是可见的)

类成员仅有一个空间,也就是就算是被继承了,那么在子类里访问的也是原始类的类成员所在的空间。子类修改继承父类的成员时,父类的成员同步修改。也就是,某个静态成员被所有对象和所有派生类共同拥有一个空间。或者说任何基类和派生类的对象都可以修改该基类的静态成员

公有访问权限的静态成员,可以通过下面的形式进行访问

  • 类名::静态成员的名字
  • 对象名.静态成员名字
  • 对象指针->静态成员的名字

在静态成员函数内部,直接访问。
注意:静态成员函数不访问类中的非静态数据成员。如有需要,只能通过对象名(或指向对象的指针)访问该对象的非静态成员。

友元

友元函数会破坏类的封装性,只有在运算符重载时才会使用,其他时候不要使用。
如果在本类(类A)以外的其他地方定义了一个函数(函数B)在类体中用friend对其(函数B)进行声明,此函数就称为本类(类A)的友元函数。
友元函数(函数B)可以访问这个类(类A)中的私有成员。友元类和友元函数类似,只是扩大了开放范围了,也就是若F类是A类的友元类,则F类的所有成员函数都是A类的友元函数,所有成员函数都可以访问A类。

类的包含

类的包含是程序设计中一种软件重用技术。即定义一个新的类时,通过编译器把另一个类 “抄”进来。
当一个类中含有已经定义的类类型成员,带参数的构造函数对数据成员初始化,须使用初始化语法形式。建立一个类的对象时,==要先执行成员对象自己的构造函数,再执行当前类的构造函数。==成员对象的构造函数调用次序和成员对象在类中的说明次序一致(声明顺序为:a1、b1、b2),与它们在成员初始化列表中出现的次序无关(初始化列表顺序为:b1、b2、a1)。
析构函数的调用顺序相反

构造函数 ( 形参表 ) : 对象成员1(形参表 ) ,, 对象成员n (形参表 )

运算符重载

重载运算符函数可以对运算符作出新的解释,但原有基本语义不变,也就是可以对原有运算操作针对特对的类对象进行拓展,而不能改变原有的基本运算规则。
运算符函数可以重载为成员函数或友元函数
其实质还是一个重载函数

其区别:
(1) 成员运算符函数比友元运算符函数少带一个参数(后置的++、–需要增加一个形参)。
(2) 双目运算符一般可以被重载为友元运算符函数或成员运算符函数,但当操作数类型不相同时,必须使用友元函数。

->重载为成员函数,解释为:
ObjectL . operator op ( ObjectR )

左操作数由ObjectL通过this指针传递,右操作数由参数ObjectR传递 

->重载为友元函数,解释为:
operator op ( ObjectL, ObjectR )

左右操作数都由参数传递 ,常用于运算符的左右操作数类型不同的情况

透过参数的情况就可以看出,重载为友元函数更为灵活,因为前者的重载必须需要ObjectL为本类的对象,而当涉及到其他类对象时会出现错误(如复数的计算),所以这种情况不得不采用后者。

C++中不能用友元函数重载的运算符有

= (赋值) ()(调用函数运算符) [] (取元素运算符) ->(访问成员)

上述运算符要想重载只能采用成员函数的形式。

而输入输出流必须要重载成友元函数。

学习感受

见证历史的我们线上学习一个余月来,个人感觉和学校学习区别不大,反而感觉在家里学习有更好的环境,前提是有自控力的约束下,效率比在学校高得多。也有了更多的时间自己研究代码,深究其道理,发现程序语言的神奇与巧妙。从学过java语言、类和对象基本思想的角度来说,学习这一部分的理解更深一些,感触更多一些,学习类和对象这一部分也是深有感触,正如前面所说,类和对象这一部分正是c++拓展其数据表示形式的一部分,但是在扩展的过程中遇到了种种问题,而我们本专题的学习正是本着这种遇到问题——解决问题的思路的形式解决的。
本学期算是刚开始接触c++的灵魂,所以现在在编写代码以软件系统的开发为主,学习软件开发的基本思路。这部分系统开发无外乎增删查改这几部分。对于类这一部分,软件开发的类大概定义为两大类,一类数据类,对应于数据库,另一类是操作类,对应功能的具体实现。每个类必须要有的:至少两个构造函数(一个带参、一个无参)、get、set、展示函数(一定程度起到调试的目的)。在数据类还根据情况进行重载。操作类是实现功能的。调试时注意要一个一个函数分块的调试,方便找错。最好先写add函数和展示函数,测试一下,没问题开始分块写函数,每写好一个函数就跟着一个main函数测试一遍,没问题之后再继续写。要养成好的编程习惯,常常会事半功倍。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值