《C++ Primier Plus》笔记:类基础部分(ch10 对象和类 ch11使用类)

本文深入探讨了面向对象编程的核心概念,包括C++中的类、对象、封装、继承、多态和代码重用。介绍了类的声明与实现,强调了构造函数和析构函数的作用,以及默认构造函数的创建。此外,讨论了对象数组、指针、作用域和抽象数据类型。还涉及了运算符重载、友元以及类型转换,包括隐式和显式转换。最后,阐述了转换函数和友元函数在类设计中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Chapter10 对象和类

  • 面向对象编程的特性:
    • 抽象
    • 封装:将实现细节放在一起并将它们与抽象分开。数据隐藏是一种封装,将类函数定义和类声明放在不同文件中也是一种封装。
    • 继承
    • 多态
    • 代码的可重用性

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

  • 指定基本类型完成了下面三项工作

    • 决定数据对象需要的内存数量
    • 决定如何解释内存中的位(例:long和float在内存中占用的位数相同但解释方法不同)
    • 决定可使用数据对象执行的操作或方法
  • 类规范由两个部分组成:

    • 类声明:以数据成员的方式描述数据部分,以成员函数的方式描述共有接口
    • 类方法定义:描述如何实现类成员函数
  • 例:方法getline是istream类的公共接口的组成部分

  • 类声明让我们能声明该类型的变量:称为对象或实例

  • 使用类对象的程序都可以直接访问公有部分,但只能通过公有成员函数或友元函数来访问对象的私有成员

  • 不必再类声明中使用关键字private,因为这是类对象的默认访问控制;结构的默认访问类型是public

10.2.3 实现类成员函数

  • 类方法可以访问类的private组件

  • 定义位于类声明中的函数将自动称为内联函数。也可以再类声明外通过使用inline限定符定义内联函数。

  • 内联函数的特殊规则要求每个使用它们的文件中都对其定义,最简便的方法是将内联定义放在定义类的头文件中

  • 所创建的每个新对象都有自己的存储空间,用于存储其内部变量和类成员;但是同一个类的所有对象共享同一组类方法,每个方法只有一个副本

  • 要创建类对象,可以:

    1. 声明类变量
    2. 使用new类类对象分配存储空间
    3. 将对象作为函数的参数和返回值
    4. 将一个对象赋给另一个
  • 客户/服务器模型:

    • 客户只能通过公有方式定义的接口使用服务器,客户唯一的责任是了解该接口;服务器的责任是确保服务器根据该接口可靠并准确地执行。
    • 服务器设计人员只能修改类设计的实现细节,而不能修改接口。这样程序员独立地对客户和服务器改进,对服务器的修改不会对客户的行为造成意外的影响。

10.3.2

  • 每次创建类对象(甚至使用new动态分配内存时),C++都使用类构造函数
  • C++有两种使用构造函数来初始化对象的方式:
    • 显式调用构造函数:
      Stock food = Stock("World Cabbage", 250, 1.24);
      
    • 隐式调用构造函数:
      Stock garment("Furry Mason", 50, 2.5);
      
  • 也有将构造函数与new共同使用的方法:
    Stock *pstock = new Stock("EG", 19, 2.34)
    

10.3.3 默认构造函数

  • 默认构造函数是在未提供显式初始值时,用来创建对象的构造函数:
    Stock theStock      //注意没有括号,右括号就是函数原型了
    
    将创建theStock对象,但不初始化其成员,类似 int s;
  • 当且仅当没有定义任何构造函数时,编译器才会提供默认构造函数。因此为类定义了构造函数后,程序员就必须为它提供默认构造函数,如果还希望能使用默认构造方法。
  • 定义默认构造函数的方式有两种,注意不要同时使用两种方式,只能有一个默认构造函数:
    • 给已有构造函数的参数提供默认值:
    Stock(const string& co = "Error", int n = 0, double pr = 0.0);
    
    • 同构函数重载定义无参构造函数
    Stock::Stock(){
        ...
    }
    

10.3.4 析构函数

  • 用构造函数创建对象后,程序负责跟踪该对象,直到其过期为止。对象过期时将调用析构函数。
  • 如果构造函数使用new分配内存,则析构函数需要使用delete释放这些内存。
  • 析构函数也可以没有返回值和声明类型。与构造函数不同,析构函数没有参数
  • 调用析构函数的时机由编译器决定:
    • 如果创建的是静态存储类对象,则在程序结束时自动调用
    • 如果创建的是自动存储类对象。则在代码块结束时调用
    • 如果说通过new创建的,则使用delete时被调用
  • 如果程序员没有提供析构函数,将隐式声明一个默认析构函数,并在发现导致对象被删除的代码后提供默认析构函数的定义。

10.3.5 改进Stock类

  • 与给结构赋值一样,在默认情况下给类对象赋值时,将把一个对象的成员复制给另一个
Stock stock1("NanoSmart", 12, 20.0);    //隐式调用构造函数
stock1 = Stock("Nifty Foods", 10,4 0.9);

stock1对象已经存在,因此该语句不是对stock1对象初始化,而是将新值赋给它。这是通过让构造函数创建一个新的,临时的对象,然后将其内容复制给stock1实现的。随后程序调用析构函数,以删除该临时对象

  • 在C++11中可以使用将列表初始化语法用于类:
    Stock hot_tip = {"DPP", 100, 45.0}
    
  • C++11还提供了名为std::initialize_list的类,可以将其用作函数参数的类型。这个类可以表示任意长度的列表,只要所有列表项的类型都相同或可转换为相同的类型。
  • 将const关键字放在函数的括号后面可以保证函数不会修改调用对象:
    void Show() cosnt;
    void Stock::show() const;
    

10.3.6 构造函数和析构函数小结

  • 如果构造函数只有一个参数,则将对象初始化为一个与参数的类型相同的值时,该构造函数会被调用:这种特性可能导致问题,可以关闭这种特性(explicit关键词)
    Bozo(int age);
    Bozo tubby = 12;
    

10.4 指针

  • this指针指向用来调用成员函数的对象,this被作为隐藏参数传递给方法
  • 每个成员函数(包括构造函数和析构函数)都有一个this指针,如果方法要引用整个调用对象,可以使用*this
  • 在函数的括号后使用const限定符将this限定为const,将不能使用this来修改对象的值

[外链图片转存中…(img-eLdGEkn9-1658150238611)]

10.5 对象数组

  • 声明对象数组:
    Stock mystuff[10]; //调用默认构造函数
    
  • 可以用构造函数来初始化数组元素,也可以混用无参的,缺省的余下2个元素将用默认构造函数:
    Stock stocks[4] = {
        Stock("NS", 12.4, 20);
        Stock();
    }
    
  • 最初的UNIX实现使用C++前端cfront将C++程序转换为C程序,this指针就是指向本对象的指针

10.6 类作用域

  • 在类中定义的名称(如类数据成员名和类成员函数名)的作用域都是整个类

10.6.1 作用域为类的常量

  • 以下代码是行不通的:
    class Bakery{
        private:
            const int Months = 12;
            ...
    }
    
    因为声明类只是描述了对象的形式,并没有创建对象,因此在创建对象前将没有用于存储值的空间。想要为类指定常量,可以1. 在类中声明一个枚举 2. 使用static关键字
    1. 声明枚举

      class Bakary{
          private:
              enum {Months = 12};
      }
      

      这种方法不会创建类数据成员,所有的对象中都不包含枚举。Months这里只是一个符号名称,在作用域为整个类的代码中遇到它时,编译器将用12来替换它。

    2. 使用static关键字

      Class Bakary{
          private:
              static const int Months = 12;
      }
      

      将创建一个名为Months的常量,与其他静态变量存储在一起,而不是存储在对象中。只有一个Months常量被所有Bakary对象共享

10.6.2 作用域内枚举(C++11)

  • C++提供了一种新枚举,其作用域为类
    enum class egg{Small, Medium, Large, Jumbo};
    enum struct egg{Small, Medium, Large, Jumbo};
    
    egg choice = eeg::Large;
    
  • 在一些情况下,常规枚举将自动转换为整型,如将其赋值给int变量或用于比较表达式时,但作用域内枚举不能隐式转换为整型:这提高了作用域内枚举的类型安全。必要时可以显式转换(int egg::Small)
  • 可以指定底层类型:
    enum class: short pizza {Small, Medium, Large, XLarge}
    

10.7 抽象数据类型

  • 设计stack类时,私有部分表明栈是用数组实现的,而公有部分隐藏了这一点。因此可以使用动态数组代替数组,而不会改变类的接口。

Chapter11 使用类

11.1 运算符重载

  • 运算符重载是一种形式的C++多态
  • 运算符函数的格式如下:
    operatorop(argument-list)
    
    op必须是有效的C++运算符,不能虚构一个新的符号
  • 运算符转换:
    district2 = sid + sara
    district2 = sid.operator+(sara)
    
    该表达式隐式地使用了sid, 而显式地使用了sara对象

11.2.2 重载限制(补表11.1)

  • 重载后的运算符至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载运算符

  • 使用运算符时不能违反运算符原来的句法规则(如操作数),且不能修改运算符的优先级

  • 不能创建新运算符

  • 不能重载以下运算符:

    • sizeof
    • .
    • .*
    • ::
    • ?:
    • typeid
    • const_cast
    • dynamic_cast
    • reinterpret_cast
    • static_cast
  • 下面的运算符只能通过成员函数重载

    • =
    • ()
    • []
    • ->

可以重载的运算符:表11.1

11.3 友元

  • 友元有3种:

    • 友元函数:可以访问类的私有成员的非成员函数
    • 友元类
    • 友元成员函数
  • 为类重载二元运算符需要友元

    A = 2.5 * B  
    A = operator*(2.5, B);
    

11.3.1 创建友元

  • 创建友元的需要
    1. 将其原型放在类声明中并加上关键词friend
      friend Time operator* (double m, const Time& t);
      
      虽然函数定义在类声明中,但并不是成员函数,不能用成员运算符调用
    2. 编写函数定义(不用加类型:😃

11.3.2 常用的友元:重载<<运算符

  • 对<<进行重载使其能与cout一起来显式对象的内容

    void operator<<(ostream& os, const Time& t){
        ...
        rerturn os;
    }
    
  • 只有在类声明的原型中才能使用friend关键字。除非函数定义也是原型,否则不能再函数定义中使用该关键字

11.4 重载运算符:作为成员函数还是非成员函数

  • 在定义运算符时,必须选择其中一种格式,否则将导致二义性错误:
    T1 = T2.operator+(T3);
    T1 = operator+(T2, T3);
    

11.6 类的自动转换和强制类型转换

  • 下面的语句都将导致类型转换:
    long count = 8;
    double time = 11;
    int side = 3.33;
    
  • 隐式转换:例用于将double类型的值转换为Stonewt类型:
    Stonewt(double lbs);
    Stonewt myCat;
    myCat = 19.6;
    
    程序将使用构造函数创建一个临时的Stonewt对象,并将19.6作为初始化值。随后用逐成员赋值方式将临时对象的内容复制到myCat中。
  • 隐式转换可以用于(以Stonewt为例):
    • 将Stonewt对象初始化为double值
    • 将double值赋给Stonewt对象时
    • 将double值(或没有二义性时可以自动转换为double)传递给接受Stonewt参数的函数时
    • 返回值被声明为Stonewt的函数试图返回double值时
  • 只有接受一个参数的构造函数才能作为转换构造函数;但如果给除第一个外所有参数提供默认值则可以
  • C++新增了关键字explict用于关闭这种自动特性:
    explicit Stonewt(double lbs);
    
    Stonewt myCat;
    myCat = 19.6            //不允许
    myCat = Stonewt(19.6)   //允许
    myCat = (Stonewt) 19.6  //允许
    

11.6.1 转换函数

  • 转换构造函数只用于某种类型转换为类类型,把类类型转换为某种类型要用转换函数
    Stonewt wolfe(285.6);
    double host = double (wolfe);
    double thinker = (double) wolfe;
    
  • 要转换为typeName类型,需要使用转换函数:
    operator typeName();
    
  • 转换函数必须是类方法
  • 转换函数不能指定返回类型
  • 转换函数不能有参数
  • 重载<<用于cout输出时,要注意同时提供类转换成int和double会有二义性错误,因为编译器不知道cout<< wolfe转换成int还是double
  • 最好使用显示转换,避免隐式转换。C++98中explict不能用于转换函数,但C++11消除了这种限制:
    explicit operator int();
    

11.6.2 转换函数和友元函数

  • 将加法定义为友元可以让程序更容易使用自动类型转换:两个操作数都成为函数参数,因此与函数原型匹配。而另一种方法容易引起二义性,比如类加double,可能把类转换成double,再执行double的加法。
    operator + (const Stonewt &, const Stonewt &)
    
  • 想要实现类加double,也可以将加法运算符重载为显式使用double参数的函数:
    Stonewt operator+ (double x);       //成员函数
    friend Stonewt operator+(double x, Stonewt & s);
    
    与第一种相比不用每次都调用两次转换构造函数,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值