跳至主要內容

类的使用

LincZero大约 10 分钟

类的使用

类的使用

通用格式

class className
{
private:
    // data member declarations
public:
    // member function prototypes
}

使用demo

代码较多,此处不再赘述,详见配套源码

使用

创建对象的n种方式

方式写法区别补充
默认A a;调用默认构造函数
【使用限制】仅当没定义构造函数 / 定义了无参数或全有默认值参数的构造函数时可用
——————————————————————
单变量简写A a = val;调用对应的单参数构造函数,本质是隐式类型转换
【使用限制】仅当定义了单参数的构造函数,并且没有用explicit关闭该特性时可用
显式A a = A(...);显式调用构造函数,可能调用赋值运算符?
【缺点】该写法可能会创建临时变量
隐式A a(...);隐式调用构造函数
【注意点】当不传参数时使用该写法有歧义,应不写后面的括号
【注意点】否则可能会理解为返回值为该类的函数的原型
new方式A *a = new A(...);调用构造函数
【注意点】动态分配内存(注意该写法需要手动delete来间接调用析构函数)
定位newA *a = new(add)A(...);调用构造函数
【注意点】动态分配内存(注意该写法可能不能delete且要直接调用析构函数)
————————————————————
类引用创建 x 单变量A a = a2;(同上,把构造函数具体化为(默认)复制构造函数
类引用创建 x显示A a = A(a2);(同上,把构造函数具体化为(默认)复制构造函数
类引用创建 x 隐式A a(a2);(同上,把构造函数具体化为(默认)复制构造函数
类引用创建 x newA *a = new A(a2);(同上,把构造函数具体化为(默认)复制构造函数
类引用创建 x 定位A *a = new(add)A(a2);(同上,把构造函数具体化为(默认)复制构造函数
————————————————————
列表初始化 x 显示A a = {...};【使用限制】C++11新增
列表初始化 x 隐式A a {...};【使用限制】C++11新增
列表初始化 x newA *a = new A{...};【使用限制】C++11新增
列表初始化 x 定位A *a = new(add)A{...};【使用限制】C++11新增
————————————————————
引用时这个应该不行
(Java)A a = new A(...);(Java隐藏起了指针,这里的a实质是一个智能引用)
(Python)a = A(...)

补充:当...中的内容为A的引用时

补充:关于A a = A(...);的写法底层

  • 几种写法的底层区别:(下面这几种写法都分别显示/隐式调用了构造函数,但有所区别)

    • A a(...);
      • 行为:调用一次构造函数
    • A a = A(...);
      • 行为:根据编译器不同而行为不同
    • A a; a = A(...);
      • 行为:第一条表达式调用一次构造函数;第二条表达式行为同A a = A(...);
  • A a = A(...);编译器有两种方式来执行该语法

    • 第一种:与A a(...);的行为完全相同
      • 行为:调用一次构造函数
    • 第二种:允许等号右侧创建一个临时变量,然后将该临时对象赋值到左侧的变量并丢弃
      • 补充:这里所说的丢弃:编译器可能让立刻删除临时对象,但也可能会等一段时间
      • 行为:调用一次构造函数,一次析构函数,一次复制构造函数==(未清楚移动语义下的情况)==
  • 选择建议

    • 视有构造时有无参而交替使用A a();A a;,其效率更高

一些零散特性

this指针

  • 使用场景
    • 举例:const Stock & topval(const Stock & s) const中使用了两个对象,比较并返回结果大的那个
  • 使用(类似于Python中的self)
    • 举例:return *this
  • 本质原理
    • 调用成员函数时,会通过this指针隐式传入一个参数,该参数为this指针,指向调用成员函数的实例对象本身

类作用域

描述

  • 在类中定义的名称,作用域都为整个类。作用域为整个类的名称只在类中是已知的,在类外是不可知的
  • 在类中外,需要根据上下文使用直接成员运算符.、间接成员运算符->、作用域解析运算符::,如
    • 客户代码中,想要调用公有成员函数,需要通过对象
    • 实现代码中,想要定义成员函数时,需要使用作用域解析运算符

作用域为类的常量

有几种实现方案

  • 直接加const(不可行)
    • 举例:class A{ private: const int Months = 12; ...,不行
    • 失败原因:声明类只是描述了对象的形式,并没有创建对象
  • 使用枚举(enum
    • 举例:class A{ private: enum {Months = 12}; ... 或者也可以使用作用域内枚举(C++11)
    • 缺点:记得枚举只能定义整型,如果是浮点就不行了
    • 实现原理:创建符号常量 注意这里是匿名枚举的用法,没有创建数据成员(只定义了枚举量,没有定义枚举变量,甚至连枚举名都没声明) 即所有对象实例都不包含枚举对象
  • 创建静态变量的方法(static
    • 举例:class A{ private: static const int Months = 12; ...
    • 实现原理:该变量将与其他静态变量存储在一起,而不是存储在对象中 因此只有一个常量副本,被所有该类的对象共享 (在C++98中,只能使用这种技术声明整数或枚举的静态常量而不能存储double常量。C++11消除了这种限制)

explicit

详见自动类型转换一节

初始化列表语法

详见构造函数的扩展

【功能扩展】类

类本身的扩展

【功能扩展】类 x 命名空间

  • 一般形式:
#ifndef VECTOR_H_
#define VECTOR_H_
#include <iostream>
namespace VECTOR
{
    class Vector
    {
        //...
    }
}
#endif
/* 在QT中的使用 */
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    // ...
}

【功能扩展】类 x new

  • 写法
    • 创建与销毁
      • A *a = new A(...)
      • delete a
    • 删除
      • 不同于动态变量或静态变量的对象,当程序块或程序结束自动调用析构函数
      • new创建时,需要显示使用delete以间接调用析构函数
      • 定位new非堆创建时,不能使用delete而应直接调用析构函数

【功能扩展】静态类 = 类 x 静态(没这个概念)

  • Q

    • C++有没有静态类这个概念?
  • 其他

    • C++类静态变量和类静态成员函数这两个我知道,Java中有静态类这个概念我也知道,虽然不知道是不是我想的那个。就问问C++后面的版本有没有这个概念或这样写行不行?
  • 使用场景

    • 开发的时候所有成员变量和成员函数都是static类型。想到这个整个类其实不需要实例,或者说只需要一份(其实用单例模式也可以,我就是实验心态弄着来试试)
  • 网络资料与实验

    • 搜了一下static静态类,有人说C++没这个概念,但也有人这样用了。而我自己实测,会有一个警告
    • 英报错:warning C4091: ‘static ’ : ignored on left of ‘ClassName’ when no variable is declared
    • 中报错:warning: C4091: “static ”: 没有声明变量时忽略“ClassName”的左侧
    • 但是可以正常运行,没问题
  • 参考写法1(静态类,没有这个概念)

    • static class View
      {
      private:
          static Student  student;
      public:
          static int much ;
          static void  addStudent();
      };
      
  • 参考写法2(直接在.h中创建静态实例,但没单例模式安全)

    • static class View
      {
      private:
          static Student  student;
      public:
          static int much ;
          static void  addStudent();
      };
      static View * view = new View();
      
  • 参考写法3(单例模式,较安全的写法,可以与写法2相结合)

类成员的扩展

【功能扩展】类成员 x 内联函数

  • 一般而言在头文件中不能定义函数,但可以定义内联函数

    但在类声明(类作用域)中,可以不加关键字inline而直接定义函数。编译器会视作其为内联函数

【功能扩展】类成员 x new

类中创建的动态成员

  • 注意要点和常规解决方法
    • 注意不仅仅是构造函数复制构造函数 [和赋值运算符] 都要手动new对象
      • 而且应使用相同的方式new(指针初始化为空除外)
    • 注意不仅仅是析构函数 [和赋值运算符] 也要手动delete对象(防止内存泄露)
      • 而且应使用与new对应的方式delete
    • 注意默认复制构造函数默认赋值运算符的默认行为是浅赋值,这会带来的问题,通常解决方案是转换为深拷贝
    • 赋值运算符技巧
      • 可以先判断一下是不是自我复制,如果是,则什么都不做。以避免不必要的性能损耗
      • 赋值运算符中一般会先把旧的动态对象delete掉,再new一个新的动态对象 但若极个别情况,能确定大小相同时。其实也可以不delete再重新new,只需注意深拷贝的问题即可 但一般动态性对象大小都是随机的,如动态处理字符串长度,则需要先delete再new
  • 伪私有方法的解决方法
    • 简概
      • 如果觉得常规解决方法太麻烦,可以将方法定义为伪私有方法
      • 与其面对无法预料的运行故障,不如得到一个易于跟踪的编译错误
    • 方法
      • 即在private里定义复制构造函数和赋值运算符
    • 作用
      • 避免本来自动生成的默认方法定义
      • 这些方法是私有的,不能被广泛使用。即A a1(a2);a1 = a2;会产生编译错误
  • delete禁用方法的解决方法(C++11新增)

【功能扩展】静态变量 = 类成员 x 静态变量

  • 特点:静态变量的特点
  • 访问:静态变量可以被静态类成员函数访问
  • 作用:实现 “作用域为类的常量” 方法之一

类对象的扩展

【功能扩展】对象 x 函数返回

返回类型的选择(4种)

  • 返回指向const对象的引用

    • 优点:效率高、不修改
  • 返回指向非const对象的引用

    • 优点:效率高、可修改(连续使用)
    • 场景
      • 重载运算符=(效率高,可避免调用复制构造函数来创建一个新对象)(当等式右边是cosnt引用时才会使用复制构造函数) 可以用于连续赋值,如a1 = a2 = a3;
      • 重载与cout一起使用的<<运算符(效率高,连续修改cout对象) 可以用于串联输出,如cout << a1 << a2;
  • 返回对象

    • 场景

      • 如果被返回的对象是局部变量,则无法按引用方式返回,一般重载算术运算符属于这一类

        a = a1 + a2;(不能修改a1、a2

  • 返回const对象

    • 场景

      • 返回非const对象的话,有个奇异的属性,如可以这样写:a1 + a2 = a3;,虽然并没有什么意义

        如果不想产生这种行为,可以返回const对象,该语句是非法的