• 《C++ primer plus》第10章:对象和类(1)


    面向对象编程(OOP)是一种特殊的、设计程序的概念性方法,C++通过一些特性改进了C语言,使得应用这种方法更容易。下面是最重要的 OOP 特性:

    • 抽象
    • 封装和数据隐藏
    • 多态
    • 继承
    • 代码的可重用性

    为了实现这些特性并将它们组合在一起,C++所做的最重要的改进是提供了类。本章还将讨论如何定义类、如何为类提供公有部分和私有部分以及如何创建使用类数据的成员函数。另外,还将介绍构造函数和析构函数,它们是特殊的成员函数,用于创建和删除术语当前类的对象。最后介绍 this 指针,对于有些类编程而言,它是至关重要的。后面的章节还将把讨论扩展到运算符重载(另一种多态)和继承,它们是代码重用的基础。

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

    采用过程性编程方法时,首先考虑要遵循的步骤,然后考虑如何表示这些数据。

    采用OOP方法时,首先从用户的角度考虑对象——描述对象所需的数据以及描述用户与数据交互所需的操作。完成对接口的描述后,需要确定如何实现接口和数据存储。

    抽象和类

    数据类型

    指定基本类型完成了三项工作:

    • 决定数据对象需要的内存数量;
    • 决定如何解释内存中的位( long 和 float 在内存中占用的位数相同,但将它们转换为数值的方法不同);
    • 决定可使用数据对象执行的操作或方法

    对于内置类型来说,有关操作的信息被内置到编译器中。但在C++中定义用户自定义的类型时,必须自己提供这些信息。付出这些劳动换来了根据实际需要定制新数据类型的强大功能和灵活性。

    C++中的类

    类是一种将抽象转换为用户定义类型的C++工具,它将数据表示和操纵数据的方法组合成一个整洁的包。

    一般来说,类规范由两个部分组成

    • 类声明:以数据成员的方式描述数据部分,以成员函数(被称为方法)的方式描述公有接口。
    • 类方法定义:描述如何实现类成员函数。

    对于类,接口让程序能够使用类对象。

    通常,C++程序员将接口的声明放在头文件中,并将实现放在源代码文件中,也可以就地定义接口。

    1. 访问控制

      public - 公有;private - 私有:public成员可以直接通过对象访问;private的成员只能通过公有成员函数或者友元函数访问。

      C++ 还提供了 protected

    2. 控制对成员的访问,是公有还是私有

      组成类接口的成员函数放在公有部分,否则就无法调用这些函数了。而使用私有成员函数来处理不属于公有接口的实现细节。

      对类对象的默认访问控制是 private

      类和结构:结构的默认访问类型是public,而类为private。C++程序员通常使用类来实现类描述,而把结构限制为只表示存粹的数据对象(常被称为普通老式数据)

    实现类成员函数

    • 定义成员函数时,使用作用域解析运算符::来表示函数所属的类;
    • 类方法可以访问类的 private 组件(无论是私有成员函数还是公共成员函数都可以访问类的private组件)
    1. 内联

      内联方法:定义位于类声明中的函数都将自动成为内联函数。如果希望,也可以使在类声明之外定义的成员函数成为内联函数,只需要在类实现部分中定义函数时使用 inline限定符即可。

      内联函数的特殊规则要求在每个使用它们的文件中都对其进行定义。确保内联定义对多文件程序中的所有文件都是可用的、最简便的方法是:将内联定义放在定义类的头文件中(有些开发系统包含智能链接程序,允许将内联定义放在一个独立的实现文件)。

    2. 方法使用哪个对象
      创建对象时,每个新对象都有自己的存储空间,用于存储其内部变量和类成员:但同一个类的所有对象共享同一组类方法,即每种方法只有一个副本,只是该方法会使用调用它的对象自己的数据。

    使用类

    C++的目标是使得使用类与使用基本的内置类型尽可能相同。

    要创建类对象,可以声明类变量,也可以使用new为类对象分配存储空间。可以将对象作为函数的参数和返回值,也可以将一个对象赋给另一个。
    C++提供了一些工具,可用于初始化对象、让cin和cout识别对象,甚至在相似的类对象之间进行自动类型转换。

    类的构造函数和析构函数

    C++希望像初始化 int 或结构那样来初始化 Stock 对象。

    int year = 2000;
    struct thing {
    	char * pn;
    	int m;
    };
    thing amabob = {"wodget", -23};
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    为此,C++提供了一个特殊的成员函数——类构造函数,专门用于构造新对象、将值赋给它们的数据成员。

    构造函数的名称与类名相同,且构造函数没有返回类型。

    声明和定义构造函数

    需要在类声明中声明构造函数,然后定义构造函数。

    注意:不能将类成员名称用作构造函数的参数名。构造函数的参数表示的不是类成员,而是赋给类成员的值。为避免容易造成的混乱,常见的做法是在数据成员名中使用m_前缀或者后缀_

    使用构造函数

    1. 显式地调用构造函数
      Stock food = Stock("World Cabbage", 250, 1.25);
      
      • 1
    2. 隐式地调用构造函数
      Stock garment = Stock("Furry Mason", 50, 2.5);
      
      • 1
    3. 构造函数与new一起使用
      Stock *pstock = new Stock("Electroshock Games", 10, 19.0);
      
      • 1
      这条语句创建一个Stock对象,将其初始化为参数提供的值,并将该对象地地址赋给pstock指针。在这种情况下,对象没有名称,但可以使用指针来管来该对象。

    注意:无法使用对象来调用构造函数,因为构造函数构造出对象之前,对象是不存在的。

    默认构造函数

    默认构造函数是在未提供显式初始值时,用来创建对象的构造函数。比如:

    Stock fluffy_the_cast;	//
    
    • 1

    如果,没有提供任何构造函数,则C++将自动提供默认构造函数。它是默认构造函数的隐式版本,不做任何工作。

    注意:当且仅当类声明没有声明任何构造函数时,编译器才会提供默认构造函数。如果提供了非默认的构造函数,C++将不会再提供默认构造函数,如果多个构造函数的重载,程序员一定要自己提供默认构造函数,否则程序会出错。
    这样做的原因可能是想禁止创建未初始化的对象,然而,如果要创建对象而不显式地初始化,则必须定义一个不接受任何参数的默认构造函数。

    定义默认构造函数的方式有两种:

    一种是给已有构造函数的所有参数提供默认值:

    Stock(const string & co = "Error", int n = 0, double pr = 0.0);
    
    • 1

    另一种方式是通过函数重载来定义另一个构造函数——一个没有参数的构造函数:

    stock();
    
    • 1

    提示:在设计类时,通常应提供对所有类成员做隐式初始化的默认构造函数。

    Stock::Stock(){		// default constructor
    	company = "no name";
    	shares = 0;
    	share_val = 0.0;
    	total_val = 0.0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    创建了默认构造函数后,便可以声明对象变量,而不对它们进行显式初始化:

    	Stock first;
    	Stock first1 = Stock();
    	Strock *pstock = new Stock;
    
    • 1
    • 2
    • 3

    注意:隐式地调用构造函数时,不要使用圆括号:

    	Stock first("Concrete Conglomerate");
    	Stock second();
    	Stock third;
    
    • 1
    • 2
    • 3

    第一个声明调用非默认构造函数,即接受参数的构造函数;
    第二个声明指出,second()是一个返回Stock对象的函数;
    第三个声明隐式地调用默认构造函数。
    再看到上面一段代码,显式地调用构造函数需要使用圆括号,不要混淆了。

    警告,接受一个参数的构造函数允许使用赋值语法将对象初始化为一个值:

    Classname object = value;
    
    • 1

    这种特性可能导致问题,但这种特性是可以关闭的。

    析构函数

    用构造函数创建对象后,程序负责跟踪该对象,直到其过期为止。对象过期时,程序将自动调用一个特殊的成员函数——析构函数。

    如果构造函数使用new来分配内存,则析构函数将使用delete来释放这些内存;如果使用构造函数时没有使用new,析构函数实际上没有需要完成的任务,这种情况下只需让编译器生成一个什么都不做的隐式析构函数即可。

    与构造函数不同的是,析构函数没有参数,因此Stock析构函数的声明必须是这样的:

    ~Stock();
    
    • 1

    由于 析构函数不承担任何重要的工作,因此可以将它编写为不执行任何操作的函数:

    Stock::~Stock(){
    }
    
    • 1
    • 2

    为了看出析构函数何时被调用,可以将它编写为这样:

    Stock::~stock(){
    	cout << "Bye, " << company << "!\n";
    }
    
    • 1
    • 2
    • 3

    什么时候应调用析构函数呢?
    如果创建的是静态存储类对象,则其析构函数将在程序结束时自动被调用;
    如果创建的是自动存储类对象,则其析构函数将在程序执行完代码块时自动被调用;
    如果对象是通过new创建的,则当使用delete释放内存时,其析构函数将自动被调用。

    另外,注意:如果构造函数内部使用了new,则必须提供使用delete的析构函数。

    初始化语句和赋值语句的区别:

    Stock stock2 = Stock ("Boffo Objects", 2, 2.0);
    stock1 = Stock("Niffy Foods", 10, 50.0);	// temporary object
    
    • 1
    • 2

    第一条语句是初始化,它直接创建有指定值的对象,不会创建临时对象;
    第二条语句是赋值,像这样在赋值语句中使用构造函数总会导致在赋值前创建一个临时变量,然后将临时变量赋值给stock1,赋值完之后再将临时变量删除。
    如果既可以通过初始化,也可以通过赋值语句来设置对象的值,则应采用初始化方式。通常这种方式的效率更高。

    C++11列表初始化

    可以将列表初始化语法用于类,只要提供与某个构造函数的参数列表匹配的内容,并用大括号将它们括起:

    Stock hot_tip = {"Derivatives Plus Plus", 100, 45.0};
    Stock jock{"Sport Age Storage, Inc"};
    Stock temp {};
    
    • 1
    • 2
    • 3

    前两个声明中,用大括号括起的列表与下面的构造函数匹配:

    Stock::Stock(const std::string & co, long n = 0, double pr = 0.0);
    
    • 1

    其中,第二个声明创建对象jock时,第二个和第三个参数将为默认值0和0.0。
    第三个声明使用默认构造函数创建对象 temp

    const 成员函数

    请看下面的代码片段:

    const Stock land = Stock("Kludgehorn Properties");
    land.show();
    
    • 1
    • 2

    编译器将拒绝第二行,因为show()代码无法保证调用对象无法被修改。以前通过将函数参数声明为const引用或者指向const的指针来解决这种问题,但这里行不通。首先,show()函数没有参数,其次,在show()函数的函数体内可能存在这改变调用对象的成员的代码。C++的解决办法是将const关键字放在函数的括号后面。也就是说,show()声明应像这样:

    void show() const;
    
    • 1

    同样,函数定义的开头也应该改成这样:

    void stock::show() const	
    
    • 1

    以这种方式声明和定义的类函数被称为 const 成员函数。
    就像应尽可能使用 const 引用和 指针用作函数形参一样,只要类方法不修改调用对象,就应将其声明为 const。

  • 相关阅读:
    【MySql】mysql之基础语句
    【Vue 组件化开发 三】父组件给子组件传递数据、组件通信(父传子、子传父)、父访问子(children、ref)、动态组件(is、component)
    [附源码]java毕业设计果蔬网络销售平台
    leetcode第362场周赛
    软考高项-计算题(3)
    IDEA中集成Git(GIT分支)
    c++ 11 多线程支持 条件变量 (condition_variable_any)(三)
    【深度学习】笔记1-权重参数的初始化-使用python
    用Promise发起请求,失败后再次请求,几次后不再执行
    DELPHI使用C++生成DLL文件里面定义的类
  • 原文地址:https://blog.csdn.net/weixin_40064300/article/details/128184684