找回密码
 立即注册
  • QQ空间
  • 回复
  • 收藏

C++ | 类与对象基础

admin 2019-11-8 21:30 135人围观 C++相关

过程性编程和面向对象编程

C语言是面向过程编程,回想一下C语言编程的过程:主函数,定义变量,调用函数然后实现。面向过程编程是一种非常具体,要面面俱到的的编程方式。而面向对象是以对象为单位来进行编程,比较像正常人的思维。

下面我们举个例子,比如开车、加速、减速、刹车。

用面向过程来说就是你要先有一个车,然后这四个分别是4件事,也就是说你要写4个函数,分别是开车、加速、减速、刹车,这分别是四个事件,如果使用的话要调用4个函数。

但是对于面向对象的编程来说,我们关心的是车这个类,而不是开车、加速、减速和刹车这四个过程。这4个过程是车这个类的一部分,只是其中的一种行为,而且对于行为的顺序没有强制要求。

总之,采用过程性编程方法时,首先考虑要遵循的步骤,然后考虑如何表示这些数据;采用OOP方法时,首先从用户的角度考虑对象——描述对象所需的数据以及描述用户与数据交互所需的操作。完成对接口的操作后,要确定如何实现接口和数据存储。最后,使用新的设计方案创建出程序。

两种思想的对比

面向过程是具体的东西,面向过程是面向对象的基础。面向对象可以说是面向过程的抽象,比如汽车有开车,加减速和刹车,关于汽车的操作有好多,每一个都需要一个具体的过程来实现,把这些过程抽象的总结起来就可以形成一个类,这个类包括的汽车所有的东西,所有的操作。

总结来说就是,面向过程是一种基础的方法,它考虑的是实际的实现,一般情况下,面向过程是自顶向下逐步求精,其最重要的是模块化的思想方法。因此在模块化编程的时候才会有“低耦合,高内聚”的思想来提高效率。面向对象的方法主要是把事物给对象化,包括其属性和行为。当程序较小的时候,面向过程就会体现出一种优势,其程序流程十分清楚。但是,面向对象编程更贴近实际生活的思想。

面向过程和面向对象的本质理解

面向过程是具体化的,流程化的。解决一个问题,需要一步一步分析需要怎样,然后需要怎样,一步一步实现的。面向对象是模型化的,抽象出一个类,这是一个封闭的环境,在这个环境中有数据有解决问题的方法,你如果需要什么功能直接使用就可以了,至于是怎么实现的,你不用知道。

从代码层面来看,面向对象和面向过程的主要区别就是数据是单独存储还是与操作存储在一起。在类的里边,实现具体的功能还是需要流程化、具体化的代码去实现的,在类里还是需要具体的算法来实现的。总结来说面向对象的底层还是面向过程,面向过程抽象成类,然后封装,方便使用就是面向对象。

面向过程与面向对象的优缺点

面向过程

优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源,比如单片机、嵌入式开发、Linux/Unix等一般采用面向过程开发,性能是最重要的因素。

缺点:没有面向对象易维护、易复用、易扩展

面向对象

优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性, 可以设计出低耦合的系统,使系统更加灵活、更加易于维护

缺点:性能比面向过程低

如何理解OOP

面向对象语言的四大特征是 抽象、封装和数据隐藏、继承、多态( 三大特征:封装和数据隐藏 继承 多态 )

抽象

数据抽象是指只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。

数据抽象是一种依赖于接口和实现分离的编程(设计)技术。

简单来说,抽象性是指程序有能力忽略正在处理中信息的某些方面,即对信息主要方面关注的能力。

封装和数据隐藏

封装也叫做信息封装,指在设计和确定模块时,使得一个模块内包含的信息(过程或数据),对于不需要这些信息的模块来说,是不能访问的。

数据隐藏:防止程序直接访问数据被称为数据隐藏,数据隐藏不仅可以防止直接访问数据,还让开发者(类的用户)无需了解数据是如何被表示的。

继承

如果一个类A继承自另一个类B,就把这个A称为"B的子类",而把B称为"A的父类"。继承可以使得子类具有父类的各种属性和方法,而不需要再次编写相同的代码。在令子类继承父类的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类的原有属性和方法,使其获得与父类不同的功能。另外,为子类追加新的属性和方法也是常见的做法。

多态

同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果,这就是多态性。简单的说:就是用基类的引用指向子类的对象。

类的概念

什么是类

类是面向对象程序设计中的概念,是面向对象编程的基础。

类是对现实生活中一类具有共同特征的事物的抽象。类的实质是一种数据类型,类似于int、char等基本类型,不同的是它是一种复杂的数据类型。因为它的本质是类型,而不是数据,所以不存在于内存中,不能被直接操作,只有被实例化为对象时,才会变得可操作。类是实体类型的一个抽象描述,类是不占内存的,类实例化出一个对象,对象对应实体,对象是占内存的。

类的内部封装了方法(即成员方法,又称成员函数),用于操作自身的成员。类是对某种对象的定义,具有行为(be-havior),它描述一个对象能够做什么以及做的方法(method),它们是可以对这个对象进行操作的程序和过程。它包含有关对象行为方式的信息,包括它的名称、方法、属性和事件。

类的构成包括数据成员和成员函数。数据成员对应类的属性,类的数据成员也是一种数据类型,并不需要分配内存。成员函数则用于操作类的各项属性,是一个类具有的特有的操作,比如“学生”可以“上课”,而“水果”则不能。类和外界发生交互的操作称为接口。

类通过访问限定符(public private protected)来体现OOP的封装思想,成员方法的定义有两个地方:类里边直接定义、在类外定义。

计算对象内存占用大小的时候,只计算对象的成员变量, 不包括对象的成员方法,一个类定义的很多对象,它们都有自己的一份成员变量,但是它们共享方法,在 .text段存储

类的三大特性

1、封装性

将数据和操作封装为一个有机的整体,由于类中私有成员都是隐藏的,只向外部提供有限的接口,所以能够保证内部的高内聚性和与外部的低耦合性。用者不必了解具体的实现细节,而只是要通过外部接口,以特定的访问权限来使用类的成员,能够增强安全性和简化编程。

2、继承性

继承性更符合认知规律,使程序更易于理解,同时节省不必要的重复代码。

3、多态性

同一操作作用于不同对象,可以有不同的解释,产生不同的执行结果。在运行时,可以通过指向基类的指针,来调用实现派生类中的方法。

类与结构体的区别

类是C++中面向对象独有的,但是C和C++中都有结构体。

C和C++中结构体的区别

1、C结构体中不能包含函数,C++结构体仍然可以定义类,允许有内部成员函数。

2、C语言的结构体是不可以继承的,C++的结构体是可以从其他的结构体或者类继承过来的,和类一样,实现了代码的复用。

4、访问权限:C的结构体对内部成员变量的访问权限只能是public,而C++允许public,protected,private三种。

5、C定义结构体变量时需要加struct关键字,C++中定义结构体变量时可以不加struct关键字。

6、gcc下分别用C和C++定义空结构体,sizeof() 后的结果分别为0 和 1,vs下C 要求一个结构或联合至少有一个成员。一个类能够实例化,编译器就需给它分配内存空间,来指示类对象的地址。这里编译器默认分配了一个字节(如:char),以便标记可能初始化的类对象,同时使空类占用的空间也最少(即1字节)。

C++的结构体和C++类的区别

主要是访问权限的区别:

1、C++结构体内部成员变量及成员函数默认的访问级别是public,而c++类的内部成员变量及成员函数的默认访问级别是private。

2、C++结构体的继承默认是public,而c++类的继承默认是private。

类的构造函数和析构函数

https://blog.csdn.net/ZYZMZM_/article/details/87898389

构造函数

类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。

构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值。

一个类可以有多个构造函数,即类的构造函数可以重载,通过不同的形参列表来区分。

构造函数分为以下几类:

默认构造函数

带参构造函数

拷贝构造函数

赋值构造函数

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

赋值初始化

列表初始化

默认构造函数

C++ 默认构造函数是对类中的参数提供默认值的构造函数,一般情况下,是一个没有参数值的空函数,也可以提供一些的默认值的构造函数,如果用户没有定义构造函数,那么编译器会给类提供一个默认的构造函数,但是只要用户自定义了任意一个构造函数,那么编译器就不会提供默认的构造函数,这种情况下,容易编译报错,所以正确的写法就是用户在定义构造函数的时候,也需要添加一个默认的构造函数,这样就不会造成编译报错。
    classCGoods{public: // 成员方法一般实现成公有的 CGoods() // 默认构造函数 {cout << "CGoods()" << endl; }private: // 一般把成员变量都定义成私有private的,把成员方法都定义成共有public的char mName[20]; // 商品的名称int mAmount; // 商品的数量double mPrice; // 商品的价格};
    intmain(){ CGoods good; // 创建对象便会调用构造函数}
    运行结果:



    带参构造函数

      classCGoods{public: CGoods(char *name, int mount, double price) // 带参构造函数 {strcpy(mName, name); mAmount = mount; mPrice = price;cout << "CGoods(name, mount, price)" << endl; }voidShow(){cout << "name:" << mName << endl;cout << "amount:" << mAmount << endl;cout << "price:" << mPrice << endl; }
      private:char mName[20];int mAmount;double mPrice;};
      intmain(){CGoods good("电视", 100, 2999); good.Show();}
      运行结果:



      拷贝构造函数 与 赋值运算符

      类构造函数初始化列表

      构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化形式。例如:

        CGoods(char *name, int mount, double price) : mAmount(mount), mPrice(price){strcpy(mName, name);}
        初始化和赋值对内置类型的成员没有什么大的区别。对非内置类型成员变量,为了避免两次构造,推荐使用类构造函数初始化列表。

        必须使用类构造函数初始化列表的情况:
        const类成员或被声明为引用的类成员。因为const对象或引用类型只能初始化,不能对它们赋值。
        易错程序示例
          classTest{public: Test(int data = 100) : mb(data), ma(mb){} // 初始化列表voidShow(){cout << "ma = " << ma << endl;cout << "mb =" << mb << endl; }private:int ma;int mb;};
          intmain(){ Test test; test.Show();}
          运行结果:



          程序分析:由运行结果可知,ma为无效值,mb为100,我们分析下述语句:
            Test(int data = 100) : mb(data), ma(mb){}
            注意:数据成员的初始化顺序与它们在 类中声明的顺序 相同,与初始化列表中的排列顺序无关。那么,ma先被初始化,但mb还是无效值,则ma(mb)相当于用一个无效值初始化ma,那么ma最终结果就为无效值,然后用100初始化mb,mb打印100。

            析构函数

            类的析构函数是类的一个成员函数,格式为 ~ 类名() 。它是执行与构造函数相反的操作:

            释放对象使用的资源,并销毁非static成员。

            析构函数的特点

            1、函数名是在类名前加上~,无参数且无返回值。

            2、一个类只能有且有一个析构函数,如果没有显式的定义,系统会生成一个缺省的析构函数(合成析构函数)。

            3、析构函数不能重载。每有一次构造函数的调用就会有一次析构函数的调用。

            程序示例
              classTest{public: Test(char *str) {strcpy(mStr, str);cout << str << " --> Test()" << endl; }
              ~Test() {cout << mStr << " --> ~Test()" << endl; }private:char mStr[100];};
              intmain(){Test test1("test1");Test test2("test2");Test test3("test3");}
              运行结果:



              由上述运行结果可知:

              在一般情况下,调用析构函数的次序正好与调用构造函数的次序相反,可以简记为:先构造的后析构,后构造的先析构,它相当于一个栈,先进后出。

              this指针

              什么是this指针?

              this指针是隐含在非静态成员函数中的特殊指针,它是当前正在调用此成员函数的对象的指针。当类对象调用成员方法时,把该对象的地址当做实参传递进去;那么所有成员方法在编译时期就会自动添加一个形参,该形参就是this指针,在成员方法中,用来区分不同的对象。调用方法,this指针就指向谁。

              程序示例
                classTest{public: Test(int data = 100) : mb(data), ma(mb){}voidShow(){cout << "ma = " << this->ma << endl;cout << "mb =" << this->mb << endl; }private:int ma;int mb;};
                this指针的使用

                1、在类的非静态成员函数中返回对象的本身时候,直接用 return *this (常用于操作符重载和赋值、拷贝等函数)。

                2、传入函数的形参与成员变量名相同时,例如:this->n = n (不能写成n = n)

                关于this指针的几个问题

                1、为什么this指针不能再静态函数中使用?

                静态成员函数并不是针对某个类的实例对象,而是属于整个类的,为所有的对象实例所共有。其作用域的范围内是全局的,独立于类的对象之外的,它只对类内部的静态成员变量做操作。当实例化一个类的对象时候,里面不存在静态成员的。this指针是相当于一个类的实例的指针,this是用来操作对象实例的内容的,既然静态成员函数和变量都是独立于类的实例对象之外的,它就不能使用this指针,也不能操作静态成员。

                2、this指针是什么时候创建的?

                this在成员函数的开始执行前构造的,在成员的执行结束后清除。

                3、this指针如何传递给类中函数的?绑定?还是在函数参数的首参数就是this指针,那么this指针又是如何找到类实例后函数的?

                this是通过函数参数的首参数来传递的。this指针是在调用之前生成的。类实例后的函数,没有这个说法。类在实例化时,只分配类中的成员变量空间,并没有为成员函数分配空间。

                this指针只有在成员函数中才有定义。因此,你获得一个对象后,也不能通过对象使用this指针。所以,我们也无法知道一个对象的this指针的位置(只有在成员函数里才有this指针的位置)。当然,在成员函数里,是可以知道this指针的位置的(可以&this获得)。


                ----------------------------------------------------------------------------------------------------------------------
                我们尊重原创,也注重分享,文章来源于微信公众号:程序员学习站,建议关注公众号查看原文。如若侵权请联系qter@qter.org。
                ----------------------------------------------------------------------------------------------------------------------

                鲜花

                握手

                雷人

                路过

                鸡蛋

                yafeilinux和他的朋友们微信公众号二维码

                微信公众号

                专注于Qt嵌入式Linux开发等。扫一扫立即关注。

                Qt开源社区官方QQ群二维码

                QQ交流群

                欢迎加入QQ群大家庭,一起讨论学习!

                我有话说......