本文介绍: 掌握了**的使用我们通过一段程序演示一下class Basepublic:private:Student()int id;Base tmp;s.name = “蒙奇·D·路飞”;s.tmp.setText(“我是要成为海贼王的男人!”);return 0;Base notes: 我是要成为海贼王的男人!Student name: 我是要成为海贼王的男人!

非受限联合体

联合体

在C++中,union 是一种特殊数据结构,允许在同一内存位置存储不同的数据类型union每个成员都从同一内存位置开始,这就意味着 union 中的所有成员共享同一块内存

union语法如下

union MyUnion {
    int intValue;
    double doubleValue;
    char charValue;
};

在这个例子中,MyUnion一个包含三个成员union,分别是 intValue整数)、doubleValue(双精度浮点数)和 charValue字符)。union 中的每个成员都从同一内存位置开始,其大小等于最大成员大小

代码示例:

#include <iostream&gt;
using namespace std;

union MyUnion {
    int intValue;
    double doubleValue;
    char charValue;
};

int main() {
    MyUnion myUnion;

    myUnion.intValue = 42;
    cout << "intValue: " << myUnion.intValue << endl;
    cout << "doubleValue: " << myUnion.doubleValue << endl; // -9.25596e+61
    cout << "intValue: " << myUnion.intValue << endl;
    cout << "charValue: " << myUnion.charValue << endl; // *

    myUnion.doubleValue = 3.14;
    cout << "doubleValue: " << myUnion.doubleValue << endl;
    cout << "intValue: " << myUnion.intValue << endl; // 1374389535

    myUnion.charValue = 'A';
    cout << "charValue: " << myUnion.charValue << endl;
    cout << "intValue: " << myUnion.intValue << endl; // 1374389569

    return 0;
}

程序输出结果为:

intValue: 42
doubleValue: -9.25596e+61
intValue: 42
charValue: *
doubleValue: 3.14
intValue: 1374389535
charValue: A
intValue: 1374389569

在这个例子中,myUnion 的三个成员共享同一块内存通过修改一个成员的值,其他成员的值也会受到影响。这是因为它们共享相同的内存空间

注意事项

非受限联合体(c++11之后的union)

在C++11之前我们使用联合体是有局限性的,主要有以下三点:

  1. 不允许联合体拥有非POD类型的成员

  2. 不允许联合体拥有静态成员

  3. 不允许联合体拥有引用类型的成员

在新的C++11标准中,取消了关于联合体对于数据成员类型的限定,规定任何非引用类型可以成为联合体的数据成员,这样的联合体称之为非受限联合体(Unrestricted Union)

非受限联合体的使用
静态类型的成员

对于非受限联合体来说,静态成员有两种分别是静态成员变量和静态成员函数我们来看一下下面的代码

union Test
{
    int age;
    long id;
    // int&amp; tmp = age; // error
    static char c;
    static int print()
    {
        cout << "c value: " << c << endl;
        return 0;
    }
};
char Test::c;
// char Test::c = 'a';

int main()
{
    Test t;
    Test t1;
    t.c = 'b';
    t1.c = 'c';
    t1.age = 666;
    cout << "t.c: " << t.c << endl;
    cout << "t1.c: " << t1.c << endl;
    cout << "t1.age: " << t1.age << endl;
    cout << "t1.id: " << t1.id << endl;
    t.print();
    Test::print();
    return 0;
}

执行程序输出结果如下:

t.c: c
t1.c: c
t1.age: 666
t1.id: 666
c value: c
c value: c

接下来我们逐一分析一下上面的代码:

非POD类型成员

在 C++11标准中会默认删除一些非受限联合体的默认函数。比如,非受限联合体有一个非 POD 的成员,而该非 POD成员类型拥有 非平凡的构造函数,那么**非受限联合体的默认构造函数将被编译器删除。其他的特殊成员函数,例如默认拷贝构造函数、拷贝赋值操作符以及析构函数等,也将遵从此规则。**下面来举例说明:

union Student
{
    int id;
    string name;
};

int main()
{
    Student s;
    return 0;
}

编译程序看到如下错误提示:

warning C4624: “Student”: 已将析构函数隐式定义为“已删除error C2280:Student::Student(void): 尝试引用删除的函数

上面代码中的非受限联合体Student中拥有一个非POD类型的成员string namestring 类中有非平凡构造函数,因此Student的构造函数被删除通过警告信息可以得知它的析构函数也被删除了)导致对象无法被成功创建出来。解决这个问题的办法就是由程序自己为非受限联合体定义构造函数,在定义构造函数的时候我们需要用到定位放置 new操作

placement new

一般情况下,使用new申请空间时,是从系统的堆(heap)中分配空间申请所得的空间的位置是根据当时的内存的实际使用情况决定的。但是,在某些特殊情况下,可能需要在已分配的特定内存创建对象,这种操作就叫做placement new定位放置 new

定位放置new操作语法形式不同于普通的new操作

使用new申请内存空间:Base* ptr = new Base;

使用定位放置new申请内存空间:

ClassName* ptr = new (定位的内存地址)ClassName;

我们来看下面的示例程序:

#include <iostream>
using namespace std;

class Base
{
public:
    Base() {}
    ~Base() {}
    void print()
    {
        cout << "number value: " << number << endl;
    }
private:
    int number;
};

int main()
{
    int n = 100;
    Base* b = new (&amp;n)Base;
    b->print();
    return 0;
}

程序运行输出的结果为:

number value: 100

程序的第20行,使用定位放置的方式指针b申请了一块内存,也就是说此时指针 b指向的内存地址和变量 n对应的内存地址是同一块(栈内存),而在Base类中成员变量 number的起始地址和Base对象的起始地址是相同的,所以打印number 的值为100也就是整形变量 n 的值。

最后,给大家总结一下关于placement new的一些细节

  1. 使用定位放置new操作,既可以在栈(stack)上生成对象,也可以在堆(heap)上生成对象,这取决于定位时指定的内存地址是在堆还是在栈上。
  2. 表面上看,定位放置new操作是申请空间,其本质利用已经申请好的空间,真正的申请空间的工作是在此之前完成的。
  3. 使用定位放置new 创建对象时会自动调用对应类的构造函数,但是由于对象的空间不会自动释放,如果需要释放堆内存必须显示调用类的析构函数。
  4. 使用定位放置new操作,我们可以反复动态申请到同一块堆内存,这样可以避免内存的重复创建销毁,从而提高程序执行效率(比如网络通信中数据的接收发送)。
自定义非受限联合体构造函数

掌握了**placement new的使用,我们通过一段程序来演示一下如何在非受限联合体中自定义构造函数**:

#include <iostream>
using namespace std;

class Base
{
public:
    void setText(string str)
    {
        notes = str;
    }
    void print()
    {
        cout << "Base notes: " << notes << endl;
    }
private:
    string notes;
};

union Student
{
    Student()
    {
        new (&amp;name)string;
    }
    ~Student() {}

    int id;
    Base tmp;
    string name;
};

int main()
{
    Student s;
    s.name = "蒙奇·D·路飞";
    s.tmp.setText("我是要成为海贼王的男人!");
    s.tmp.print();
    cout << "Student name: " << s.name << endl;
    return 0;
}

程序打印的结果如下

Base notes: 我是要成为海贼王的男人!
Student name: 我是要成为海贼王的男人!

我们在上面的程序里边给非受限制联合体显示的指定了构造函数和析构函数,在程序的**第31行需要创建一个非受限联合体对象,这时便调用了联合体内部的构造函数,在构造函数的第20行通过定位放置 new的方式将构造出的对象地址定位到了联合体的成员string name的地址上了,这样联合体内部其他非静态成员也就可以访问这块地址了(通过输出的结果可以看到对联合体内的tmp对象赋值,会覆盖name对象中的数据)**。

匿名的非受限联合体

一般情况下我们使用的非受限联合体都是具名的(有名字),但是我们也可以定义匿名的非受限联合体,一个比较实用的场景就是配合着类的定义使用。我们来设定一个场景

木叶村要进行第99次人口普查,人员的登记方式如下- 学生只需要登记所在学校的编号
    - 本村学生以外的人员需要登记其身份证号码
    - 本村外来人员需要登记户口所在地+联系方式
#include <iostream>
using namespace std;

// 外来人口信息
struct Foreigner
{
    Foreigner(string s, string ph) : addr(s), phone(ph) {}
    string addr;
    string phone;
};

// 登记人口信息
class Person
{
public:
    enum class Category : char { Student, Local, Foreign };
    Person(int num) : number(num), type(Category::Student) {}
    Person(string id) : idNum(id), type(Category::Local) {}
    Person(string addr, string phone) : foreign(addr, phone), type(Category::Foreign) {}
    ~Person() {}

    void print()
    {
        cout << "Person category: " << (int)type << endl; // 打印0代表学生, 1代表Local...
        switch (type)
        {
        case Category::Student:
            cout << "Student school number: " << number << endl;
            break;
        case Category::Local:
            cout << "Local people ID number: " << idNum << endl;
            break;
        case Category::Foreign:
            cout << "Foreigner address: " << foreign.addr
                << ", phone: " << foreign.phone << endl;
            break;
        default:
            break;
        }
    }

private:
    Category type;
    union
    {
        int number;
        string idNum;
        Foreigner foreign;
    };
};

int main()
{
    Person p1(9527);
    Person p2("1101122022X");
    Person p3("砂隐村村北", "1301810001");
    p1.print();
    p2.print();
    p3.print();
    return 0;
}

程序输出的结果:

Person category: 0
Student school number: 9527
Person category: 1
Local people ID number: 1101122022X
Person category: 2
Foreigner address: 砂隐村村北, phone: 1301810001

根据需求我们将木叶村的人口分为了三类并通过枚举记录了下来,在Person类中添加了一个匿名的非受限联合体用来存储人口信息,仔细分析之后就会发现这种处理方式的优势非常明显:尽可能地节省了内存空间。

  • Person类可以直接访问匿名非受限联合体内部的数据成员。
  • 不使用匿名非受限联合体申请的内存空间等于 number、 idNum 、 foreign 三者内存之和。
  • 使用匿名非受限联合体之后number、 idNum 、 foreign 三者共用同一块内存。

因为不同类型的Person对象用到不同的数据, 用到部分数据的Foreigner类型, 我们把这种类型的两种信息放到了一个结构体中, 把这个结构体类型放到union中, 需要的时候访问.

原文地址:https://blog.csdn.net/f593256/article/details/134738669

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_21002.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注