C++类对象创建全解析:从构造函数到内存管理

embedded/2025/3/18 19:27:06/

目录

对象的创建

对象的创建规则

对象的数据成员初始化

对象所占空间大小

总结

指针数据成员


对象的创建

在之前的 Computer 类中,通过自定义的公共成员函数 setBrand 和 setPrice 实现了对数据成员的初始化。实际上,C++ 为类提供了一种特殊的成员函数——构造函数来完成相同的工作。

  • 构造函数的作用:就是用来初始化数据成员的

  • 构造函数的形式:

    没有返回值,即使是void也不能有;

    函数名与类名相同,再加上函数参数列表。

构造函数在对象创建时自动调用,用以完成对象成员变量等的初始化及其他操作(如为指针成员动态申请内存等)

对象的创建规则

  1. 当类中没有显式定义构造函数时 ,编译器会自动生成一个默认 (无参) 构造函数 ,但并不会初始化数据成员;

    以Point类为例:

    #include <iostream>// 其次是C++的文件,第三方库文件放最下using std::cout;
    using std::endl;
    class Point {public:void print(){cout << "(" << _ix << "," << _iy<< ")" << endl;}Point(){cout << "print" << endl;}private:int _ix;int _iy;};void test0(){Point pt;pt.print();}//运行结果显示,pt的_ix,_iy都是不确定的值int main(){test0();return 0;}

    这说明了,当类中没有显式定义构造函数时 ,编译器会自动生成一个默认 (无参) 构造函数 ,但并不会初始化数据成员,这里我们将他不做任何处理,看是否会自动调用。

class Point {
public:void print(){cout << "(" << _ix << "," << _iy<< ")" << endl;}
private:int _ix;int _iy;
};void test0()
{Point pt;pt.print();
}
//运行结果显示,pt的_ix,_iy都是不确定的值

Point pt; 这种方式创建的对象,其数据成员没有被初始化,输出的会是不确定的值

image-20240307114915395

 2.  一旦当类中显式提供了构造函数时 ,编译器就不会再自动生成默认的构造函数;

class Point {
public:Point(){cout << "Point()" << endl;_ix = 0;_iy = 0;}void print(){cout << "(" << _ix << "," << _iy<< ")" << endl;}
private:int _ix;int _iy;
};void test0()
{Point pt;pt.print();
}
//这次创建pt对象时就调用了自定义的构造函数,而非默认构造函数

3. 编译器自动生成的默认构造函数是无参的,构造函数也可以接收参数,在对象创建时提供更大的自由度;

class Point {
public:Point(int ix, int iy){cout << "Point(int,int)" << endl;_ix = ix;_iy = iy;}void print(){cout << "(" << _ix << "," << _iy<< ")" << endl;}
private:int _ix;int _iy;
};void test0()
{Point pt;//error,没有默认的无参构造函数可供调用Point pt2(10,20);pt2.print();
}

4. 如果还希望通过默认构造函数创建对象, 则必须要手动提供一个默认构造函数;

class Point {
public:Point(){}Point(int ix, int iy){cout << "Point(int,int)" << endl;_ix = ix;_iy = iy;}void print(){cout << "(" << _ix << "," << _iy<< ")" << endl;}
private:int _ix;int _iy;
};void test0()
{Point pt;//okPoint pt2(10,20);pt2.print();
}

5. 构造函数可以重载

如上,一个类中可以有多种形式的构造函数,说明构造函数可以重载。事实上,真实的开发中经常会给一个类定义各种形式的构造函数,以提升代码的灵活性(可以用多种不同的数据来创建出同一类的对象)。

image-20240307120328878

对象的数据成员初始化

上述例子中,在构造函数的函数体中对数据成员进行赋值,其实严格意义上不算初始化(而是算赋值)。

在C++中,对于类中数据成员的初始化,推荐使用初始化列表完成。初始化列表位于构造函数形参列表之后,函数体之前,用冒号开始,如果有多个数据成员,再用逗号分隔,初始值放在一对小括号中。

class Point {
public://...Point(int ix = 0, int iy = 0): _ix(ix), _iy(iy){cout << "Point(int,int)" << endl;}//...
};

如果没有在构造函数的初始化列表中显式地初始化成员,则该成员将在构造函数体之前执行默认初始化。如在“对象的创建规则”示例代码中,有参的构造函数中 _ix 和 _iy 都是先执行默认初始化后,再在函数体中执行赋值操作。

image-20240307143824881

补充:数据成员的初始化并不取决于其在初始化列表中的顺序,而是取决于声明时的顺序(与声明顺序一致)

  • 构造函数的参数也可以按从右向左规则赋默认值,同样的,如果构造函数的声明和定义分开写,只用在声明或定义中的一处设置参数默认值,一般建议在声明中设置默认值。

    class Point {
    public:Point(int ix, int iy = 0);//默认参数设置在声明时//...
    };Point::Point(int ix, int iy)
    : _ix(ix)
    , _iy(iy)
    {cout << "Point(int,int)" << endl;
    }void test0(){Point pt(10);
    }

  • C++11之后,普通的数据成员也可以在声明时就进行初始化。但一些特殊的数据成员初始化只能在初始化列表中进行,故一般情况下统一推荐在初始化列表中进行数据成员初始化。

class Point {
public://...int _ix = 0;//C++11int _iy = 0;
};

  • 数据成员初始化的顺序与其声明的顺序保持一致,与它们在初始化列表中的顺序无关(但初始化列表一般保持与数据成员声明的顺序一致)。

对象所占空间大小

之前在讲引用的知识点时,我们提过使用引用作为函数的返回值可以避免多余的复制。内置类型的变量最大也就是long double,占16个字节。但是现在我们学习了类的定义,自定义类型对象的大小可以非常大。

使用sizeof查看一个类的大小和查看该类对象的大小,得到的结果是相同的(类是对象的模板);

void test0(){Point pt(1,2);cout << sizeof(Point) << endl;cout << sizeof(pt) << endl;}

成员函数并不影响对象的大小,对象的大小与数据成员有关(后面学习继承、多态,对象的内存布局会更复杂);

现阶段,在不考虑继承多态的情况下,我们做以下测试。发现有时一个类所占空间大小就是其数据成员类型所占大小之和,有时则不是,这就是因为有内存对齐的机制。

class A{int _num;double _price;
};
//sizeof(A) = 16class B{int _num;int _price;
};
//sizeof(D) = 8

  • 为什么要进行内存对齐?

    1.平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

    2.性能原因:CPU 对内存的读取不是连续的,而是分成块读取的,块的大小只能是1、2、4、8、16 ... 字节。若不进行内存对齐,可能需要做两次内存访问,性能会大打折扣;而进行过内存对齐仅需要一次访问。

    image-20240223163737665

    64位系统默认以8个字节的块大小进行读取。

    如果没有内存对齐机制,CPU读取_price时,需要两次总线周期来访问内存,第一次读取 _price数据前四个字节的内容,第二次读取后四个字节的内容,还要经过计算,将它们合并成一个数据。

    有了内存对齐机制后,以浪费4个字节的空间为代价,读取_price时只需要一次访问,所以编译器会隐式地进行内存对齐。

    规则一:

    按照类中占空间最大的数据成员大小的倍数对齐;

    如果数据成员再多一些,我们发现自定义类型所占的空间大小还与这些数据成员的顺序有关

    class C{int _c1;int _c2;double _c3;
    };
    //sizeof(C) = 16class D{int _d1;double _d2;int _d3;
    };
    //sizeof(D) = 24

    image-20240223163111420

    如果数据成员中有数组类型,会按照除数组以外的其他数据成员中最大的那一个的倍数对齐

    class E{double _e;char _eArr[20];double _e1;int _e2;
    };
    //sizeof(E) = 48class F{char _fArr[20];
    };
    //sizeof(F) = 20

    再判断一下,G类所占的空间是多少?

    class G{char _gArr[20];int _g1;double _g2;
    };//32

在C语言的涉及的结构体代码中,我们可能会看到#pragma pack的一些设置,#pragma pack(n)即设置编译器按照n个字节对齐,n可以取值1,2,4,8,16.在C++中也可以使用这个设置,最终的对齐效果将按照 #pragma pack 指定的数值和类中最大的数据成员长度中,比较小的那个的倍数进行对齐。

总结

除数组外,其他类型的数据成员中,以较大的数据成员所占空间的倍数去对齐。

内存对齐还与数据成员的声明顺序有关。

指针数据成员

类的数据成员中有指针时,意味着创建该类的对象时要进行指针成员的初始化,需要申请堆空间。

在初始化列表中申请空间,在函数体中复制内容。

class Computer {
public:Computer(const char * brand, double price): _brand(new char[strlen(brand) + 1]()), _price(price){strcpy(_brand,brand);}private:char * _brand;double _price;
};void test0(){Computer pc("Apple",12000);
}

思考一下,以上代码有没有问题?

image-20240307152000320

代码运行没有报错,但使用memcheck工具检查发现发生了内存泄漏。有new表达式被执行,就要想到通过delete表达式来进行回收。如果没有对应的回收机制,对象被销毁时,它所申请的堆空间不会被回收,就会发生内存泄漏。


http://www.ppmy.cn/embedded/173660.html

相关文章

Linux-数据结构-线性表-顺序表

一.数据结构的基本概念 【1】数据结构&#xff1a; 相互之间存在一种或多种特定关系的数据元素的集合。 &#xff08;1&#xff09;逻辑结构 集合&#xff0c;所有数据在同一个集合中&#xff0c;关系平等。 线性&#xff0c;数据和数据之间是一对一的关…

第十六届蓝桥杯康复训练--1

题目链接&#xff1a;92. 递归实现指数型枚举 - AcWing题库 思路&#xff1a;因为题目要求必须升序输出&#xff0c;所以在递归遍历的时候从1开始就好&#xff0c;然后遍历过的变量打个标记&#xff0c;避免重复遍历&#xff0c;到n个就输出路径上所有的数&#xff0c;需要注意…

2025系统架构师(一考就过):案例之五:典型架构、架构演化、人工智能、云计算、大数据

六、中间件技术、典型架构 ◆中间件:在一个分布式系统环境中处于操作系统和应用程序之间的软件&#xff0c;可以在不同的技术之间共享资源&#xff0c;将不同的操作系统、数据库、异构的网络环境以及若干应用结合成一个有机的协同工作整体。 ◆中间件位于客户机/服务器的操作系…

项目--五子棋(前置知识)

本项目使用的系统环境是Ubuntu20.04 环境搭建 下载工具的安装 先来补充一个小知识&#xff1a;Ubuntu系统和CentOS系统的 包管理机制不同&#xff0c;用来查询软件源的命令也不同&#xff1a; Ubuntu系统使用的是apt包管理系统&#xff1a;rpm命令主要用于基于RPM包管理的系…

基于MPC8377的MCPU 3U机箱CPCI板卡

板卡简介&#xff1a; 本板为主控板&#xff08;MCPU&#xff09;&#xff0c;主要负责逻辑控制、数据的处理、板卡的通信管理、系统安全保护切换以及数据存储等功能。 性能规格&#xff1a; 电源&#xff1a;DC5V CPU&#xff1a;MPC8377 核数&#xff1a;单核 32位 主频…

[新能源]新能源汽车快充与慢充说明

接口示意图 慢充接口为交流充电口&#xff08;七孔&#xff09;&#xff0c;快充接口为直流充电口&#xff08;九孔&#xff09;。 引脚说明 上图给的是充电口的引脚图&#xff0c;充电枪的为镜像的。 慢充接口引脚说明 快充接口引脚说明 充电流程 慢充示意图 慢充&…

路由器与防火墙配置命令

路由器与防火墙配置命令 小明啊&#xff0c;你不是学计算机的嘛&#xff0c;叔叔家的路由器坏了&#xff0c;可以过来帮叔叔看看吗 命令可以用缩写&#xff0c;造就一堆容易造成歧义的缩写&#xff0c;比如add是address的缩写&#xff0c;sh是shutdown的缩写。 默认为Cisco路…

Tomcat新手登峰指南:从零到部署的原子化实践

开篇&#xff1a;为什么选择Tomcat&#xff1f; 2024年StackOverflow调查显示&#xff0c;Tomcat以68.9%占有率蝉联Java Web服务器榜首。但新手常陷入三大误区&#xff1a; 直接使用IDE内置Tomcat导致生产环境配置失准权限配置不当引发安全漏洞内存参数未优化造成性能瓶颈 本…