当前位置: 首页 > article >正文

C++类:特殊的数据成员

目录

特殊的数据成员

常量数据成员

引用数据成员

对象成员

​编辑

构造顺序的强制性

静态数据成员


特殊的数据成员

在 C++ 的类中,有4种比较特殊的数据成员,分别是常量成员、引用成员、类对象成员和静态成员,它们的初始化与普通数据成员有所不同。

常量数据成员

当数据成员用 const 关键字进行修饰以后,就成为常量成员。一经初始化,该数据成员便具有“只读属性”,在程序中无法对其值修改。事实上,在构造函数体内对const 数据成员赋值是非法的,const数据成员需在初始化列表中进行初始化(C++11之后也允许在声明时就初始化)。

普通的const常量必须在声明时就初始化,初始化之后就不再允许修改值;

const成员初始化后也不再允许修改值。

class Point {
public:
    Point(int ix, int iy)
    : _ix(ix) //初始化列表本质就是进行初始化的
    , _iy(iy)
    {}
private:
    const int _ix;
    const int _iy;
};

image-20240308114326646

引用数据成员

引用数据成员在初始化列表中进行初始化,C++11之后允许在声明时初始化(绑定)。

之前的学习中,我们知道了引用要绑定到已经存在的变量,引用成员同样如此。

class Point {
public:
    Point(int ix, int iy)
    : _ix(ix)
    , _iy(iy)
    , _iz(_ix)
    {}
private:
    const int _ix;
    const int _iy;
    int & _iz;
};

思考:构造函数再接收一个参数,用这个参数初始化引用成员可以吗?

image-20240308120118431

_iz绑定传入的z,看起来虽然是确定的值,但是由于值传递会进行复制,所以实际上是去绑定一个临时变量,临时变量的生命周期只有函数,等到构造函数结束引用的指向就没了,就成为了一个悬空指针。

_iz绑定 _ix,因为数据成员的初始化顺序与声明顺序一致,此时 _ix已经完成了初始化,是一个确定的值,就没有问题。

我现在好奇Point 的大小是多少,我们来看一下,发现为16字节,证明&引用还是占用空间的,并且是一个指针的内存大小。

对象成员

有时候,一个类对象会作为另一个类对象的数据成员被使用。比如一个直线类Line对象中包含两个Point对象。

对象成员必须在初始化列表中进行初始化。

注意:

  1. 不能在声明对象成员时就去创建。

  2. 初始化列表中写的是需要被初始化的对象成员的名称,而不是对象成员的类名。

class Line {
public:
    Line(int x1, int y1, int x2, int y2)
    : _pt1(x1, y1)
    , _pt2(x2, y2)
    {
        cout << "Line(int,int,int,int)" << endl;
    }
private:
    Point _pt1;
    Point _pt2;
};

image-20240308144217447

注意:

如果在Line类的构造函数的初始化列表中没有显式地初始化Point类对象成员,编译器会自动去调用Point类型的默认无参构造;

如果不想用Point的无参构造,那么必须在Line类的初始化列表中对Point类的对象成员进行初始化

image-20240308144444067

构造顺序的强制性
  • 当类的对象包含其他类的对象作为成员时,这些成员对象的构造函数会在主类的构造函数体执行前自动调用

  • 如果未在初始化列表中显式指定成员对象的构造函数,编译器会尝试调用它们的默认构造函数(无参构造函数)。若成员对象的类没有默认构造函数,会导致编译错误

  • class Point {
    public:
        Point(int x, int y); // 没有默认构造函数
    };
    
    class Line {
        Point p1; // 必须通过初始化列表构造
        Point p2;
    public:
        Line(int x1, int y1, int x2, int y2) 
            : p1(x1, y1), p2(x2, y2) {} // 正确:显式调用有参构造函数
    };

  • 如果省略初始化列表,编译器会报错,因为Point没有默认构造函数。

此例子中,创建一个Line类的对象,会首先调用Line的构造函数,在此过程中调用Point的构造函数完成Point类对象成员的初始化;

Line对象销毁时会先调用Line的析构函数,析构函数执行完后,再调用Point的析构函数。(对象销毁系统才会自动调用析构函数)

image-20240308145237461

—— 与看起来的顺序有所不同。

静态数据成员

C++ 允许使用 static (静态存储)修饰数据成员,这样的成员在编译时就被创建并初始化的(与之相比,对象是在运行时被创建的),且其实例只有一个,被所有该类的对象共享,就像住在同一宿舍里的同学共享一个房间号一样。静态数据成员和之前介绍的静态变量一样,当程序执行时,该成员已经存在,一直到程序结束,任何该类对象都可对其进行访问,静态数据成员存储在全局/静态区,并不占据对象的存储空间

静态数据成员被整个类的所有对象共享。

class Computer {
public:
    //...    
private:
    char * _brand;
    double _price;
    //数据成员的类型前面加上static关键字
    //表示这是一个static数据成员(共享)
    static double _totalPrice;
};
double Computer::_totalPrice = 0;

静态成员规则:

  1. private的静态数据成员无法在类之外直接访问(显然)

  2. 对于静态数据成员的初始化,必须放在类外(一般紧接着类的定义,这是规则1的特殊情况),因为静态成员初始化一定要放在类外初始化,类外的空间有很多,所以编译器一般要求创建完类之后,紧接着对静态数据成员初始化。不能在类内进行初始化是因为他被所有该类对象所共享,如果在类内初始化就会每创建一个对象就会变化。

  3. 静态数据成员初始化时不能在数据类型前面加static,在数据成员名前面要加上类名+作用域限定符

  4. 如果有多条静态数据成员,那么它们的初始化顺序需要与声明顺序一致(规范)

image-20240308150638895

image-20240308151253143


http://www.kler.cn/a/589310.html

相关文章:

  • 鸿蒙跳转到系统设置app界面
  • JAVA(8)-数组
  • 07.Python基础4
  • Linux中安装MySQL
  • 我又又又又又又又更新了~~~纯手工编写C++画图,有注释~~~
  • 解决git fetch 成功后还是不能checkout到fetch分支
  • 深入理解嵌入式开发中的三个重要工具:零长度数组、container_of 和 typeof
  • QT编程之JSON处理
  • AI软件栈:推理框架(二)-Llama CPP1
  • Qt 窗口以及菜单栏介绍
  • embedding技术
  • Cascadeur 技术浅析(五):碰撞避免算法
  • 【python web】一文掌握 Flask 的基础用法
  • 黑龙江有多线IDC服务器托管机房吗?
  • 输入:0.5元/百万tokens(缓存命中)或2元(未命中) 输出:8元/百万tokens
  • vulhub/log4j2漏洞靶场----反弹shell
  • 【图片批量转换合并PDF】多个文件夹的图片以文件夹为单位批量合并成一个PDF,基于wpf的实现方案
  • Python爬虫:playwright的使用
  • python爬虫笔记(一)
  • 【MATLAB例程】AOA(到达角度)法,多个目标定位算法,三维空间、锚点数量自适应(附完整代码)