的union_基础篇: struct,class和union

目录:

  • C结构体、C++结构体 和 C++类的区别
  • 回顾:从C的POD到C++的类
  • C++结构体和类总结
  • C++ 类所占内存大小
  • C++中的union

想要回顾一下基本概念,请参考:

类和结构 (C++)​docs.microsoft.com

C结构体、C++结构体 和 C++类的区别

先来说说C和C++中结构体的不同:

  • C语言中的结构体不能为空,否则会报错
  • C语言中的结构体只涉及到数据结构,而不涉及到算法,也就是说在C中数据结构和算法是分离的。换句话说就是C语言中的结构体只能定义成员变量,但是不能定义成员函数。
  • 在C++中既可以定义成员变量又可以定义成员函数, C++中的结构体和类体现了数据结构和算法的结合。
  • 虽然C语言的结构体中不能定义成员函数,但是却可以定义函数指针,不过函数指针本质上不是函数而是指针,所以总的来说C语言中的结构体只是一个复杂数据类型 ,只能定义成员变量,不能定义成员函数,不能用于面向对象编程。

再来分析C++中的结构体与类,它们的相同之处:

  • 结构体也可以包含函数,也可以定义public、private、protected数据成员;定义了结构体之后,可以用结构体名来创建对象。
  • C++当中,结构体中可以有成员变量,可以有成员函数,可以从别的结构体继承,也可以被别的结构体继承,可以有虚函数。
  • 总之,class和struct的语法基本相同,从声明到使用,都很相似,但是struct的约束要比class多,理论上,struct能做到的class都能做到,但class能做到的stuct却不一定做的到。

再来说说两者的区别:对于成员访问权限以及继承方式,class中默认的是private,而struct中则是public。class还可以用于表示模板类型,struct则不行。

struct A
{
public:
    A(){};
    virtual void Dynamic()
    {
        cout << "A" << endl;
    }
protected:
    void fun();
private:
    int m_Data;
};
 
struct B:public A
{
public:
    virtual void Dynamic()
    {
        cout << "B" << endl;
    }
};
int main()
{
    A * pa = new B;
    pa->Dynamic();
}
//原文链接:https://blog.csdn.net/Loving_Forever_/article/details/51483828

总结一下就是:

概念:class和struct的语法基本相同,从声明到使用,都很相似,但是struct的约束要比class多,理论上,struct能做到的class都能做到,但class能做到的stuct却不一定做的到。

类型:struct是值类型,class是引用类型,因此它们具有所有值类型和引用类型之间的差异。

效率:由于堆栈的执行效率要比堆的执行效率高,但是堆栈资源却很有限,不适合处理逻辑复杂的大对象,因此struct常用来处理作为基类型对待的小对象,而class来处理某个商业逻辑。

关系:struct不仅能继承也能被继承 ,而且可以实现接口,不过Class可以完全扩展。内部结构有区别,struct只能添加带参的构造函数,不能使用abstract和protected等修饰符,不能初始化实例字段。

关于 Class性能好还是Struct性能好(换言堆性能好?还是栈性能好?) 那么什么时机该用呢 ,比较struct和 class的不同,以下总结可以参考一下:

(1) 在表示诸如点、矩形等主要用来存储数据的轻量级对象时,首选struct。

(2) 在表示数据量大、逻辑复杂的大对象时,首选class。

(3) 在表现抽象和多级别的对象层次时,class是最佳选择

————————————————

版权声明:以上总结来源于CSDN博主「Loving_初衷」的文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:详解C结构体、C++结构体 和 C++类的区别

回顾:从C的POD到C++的类

回顾一下C语言纯POD的结构体(struct)。

如果用C语言实现一个类似面向对象的类,应该怎么做呢?

// 写法一
#include <stdio.h>
typedef struct Actress {
    int height; // 身高
    int weight; // 体重
    int age;    // 年龄(注意,这不是数据库,不必一定存储生日)

    void (*desc)(struct Actress*);
} Actress;

void profile(Actress* obj) {
    printf("height:%d weight:%d age:%dn", obj->height, obj->weight, obj->age);
}

int main() {
    Actress a;
    a.height = 168;
    a.weight = 50;
    a.age = 20;
    a.desc = profile;

    a.desc(&a);
    return 0;
}

想达到面向对象中数据和操作封装到一起的效果,只能给struct里面添加函数指针,然后给函数指针赋值。在C语言的项目中你很少会看到这种写法,主要原因就是函数指针是有空间成本的。一般会按照如下的方法写:

// 写法二
#include <stdio.h>
typedef struct Actress {
    int height; // 身高
    int weight; // 体重
    int age;    // 年龄(注意,这不是数据库,不必一定存储生日)

} Actress;

void desc(Actress* obj) {
    printf("height:%d weight:%d age:%dn", obj->height, obj->weight, obj->age);
}

int main() {
    Actress a;
    a.height = 168;
    a.weight = 50;
    a.age = 20;

    desc(&a);
    return 0;
}:

再看一个普通的C++类:

// 写法二
#include <stdio.h>
class Actress {
public:
    int height; // 身高
    int weight; // 体重
    int age;    // 年龄(注意,这不是数据库,不必一定存储生日)

    void desc() {
        printf("height:%d weight:%d age:%dn", height, weight, age);
    }

};

int main() {
    Actress a;
    a.height = 168;
    a.weight = 50;
    a.age = 20;

    a.desc();
    return 0;
}

你觉得你这个class实际相当于C语言两种写法中的哪一个?

看着像写法一?其实相当于写法二。C++编译器实际会帮你生成一个类似C语言写法二的形式(这也算是C++ zero overhead指导方针的一个体现)。看到这里就明白了:C++中类和操作的封装只是对于程序员而言的。而编译器编译之后其实还是面向过程的代码。编译器帮你给成员函数增加一个额外的类指针参数,运行期间传入对象实际的指针。类的数据(成员变量)和操作(成员函数)其实还是分离的

每个函数都有地址(指针),不管是全局函数还是成员函数在编译之后几乎类似。更多细节,请参考如下回答:

C++为什么要弄出虚表这个东西?​www.zhihu.com
0a3ba86db740a05438a192ee8b8d75e2.png

C++结构体和类总结

  • 结构体和类最大的区别就是前者访问控制默认为public,而类的默认访问控制是private。而对于public,private,protected的访问控制都是在编译期间由编译器检查的,编译通过后,程序执行过程中就不存在什么访问限制了。笼统一点讲,它们在底层只是类型名称不同,原理都相同。
  • 类与对象,类是一个抽象的概念,而对象则是这个抽象概念里的一个具体实例。(如人–CXX…)类一般由数据成员和函数成员组成,而具体对象大小的计算只看数据成员,函数成员属于执行代码,不属于类对象的数据。除本身外,类中的数据成员可以是任何已知的数据类型。为什么类中不能定义自身的对象呢?因为类在实例化时,必须要知道它的大小,而如果有自身,会形成递归定义,没有出口。但注意自身的指针类型是可以的,因为任何类型的指针大小是已知的。
  • 类对象大小一般就是数据成员大小之和,但也有些特殊情况不符合这个公式。(1)空类,至少占1字节大小而不是不占内存空间,如果不占内存大小,那么空类就无法实例化。但是空类就算没有数据成员,也可以有函数成员的,所以仍然需要实例化,而这个至少会分配1字节空间给空类,就是用于实例化的。(2)字节对齐 (3)静态成员数据,类似局部静态数据,存在域全局,作用域局部,在编译期间就已经初始化,保存在全局数据区中。它的大小不算在类对象里。
  • this指针:使用过程中被编译器给隐藏起来了,它其实就是个指针,保存调用对象的首地址,指向当前调用者对象本身。对象的成员函数形参处隐式的有这个this,其实是在调用成员函数时,编译器做了一个小动作:利用寄存器ecx保存了对象的首地址(this),并以寄存器传参的方式传递到成员函数中。这也是在成员函数中能直接访问成员数据的原因(this->data),也是判断一个函数不是一般函数而是成员函数的依据。
  • 对象作为函数参数:这个不像数组做参数只传递首地址,而是将对象的所有数据成员拷贝一份全部传递过去。这个过程其实会调用系统的拷贝构造函数,就是简单的赋值过程(如果自己写了这个函数,会替换系统的),所以对于有动态内存分配的类,使用这种传参,调用系统的拷贝构造函数,会出现一个这样的错误,这种传参其实是实参与形参处的两个对象保存着相同的数据,而形参处对象在函数调用结束就会释放调用对应的析构函数,所以对于实参和参数处的两个对象都指向的动态空间被释放了。这时实参处对象的指针还在,但所指内存已经释放了。所以对于这种需要我们自己定义拷贝构造函数,一般有两种思路:(1)深拷贝数据,即对于指针数据不仅拷贝指针本身而且指针所指数据也同时拷贝(2)设置引用计数。但是对于对象传参我们一般使用指针或引用形式,这样即可以避免上面所说错误,同时效率也更高。
  • 对象作为返回值:情况和对象作为函数参数一样。

————————————————

版权声明:“C++结构体和类总结” 为CSDN博主「夜色魅影」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:C++--结构体和类_疯狂的程序员-CSDN博客_c++ 结构体 自省

C++ 类所占内存大小

如上一部分总结的第三条所述,类所占内存的大小是由成员变量(静态变量除外)决定的,成员函数(这是笼统的说,后面会细说)是不计算在内的。因为在编译器处理后,成员变量和成员函数是分离的。成员函数还是以一般的函数一样的存在。a.fun()是通过fun(a.this)来调用的。所谓成员函数只是在名义上是类里的。

其实成员函数的大小不在类的对象里面,同一个类的多个对象共享函数代码。而我们访问类的成员函数是通过类里面的一个指针实现,而这个指针指向的是一个table,table里面记录的各个成员函数的地址(当然不同的编译可能略有不同的实现)。所以我们访问成员函数是间接获得地址的。所以这样也就增加了一定的时间开销,这也就是为什么我们提倡把一些简短的,调用频率高的函数声明为inline形式(内联函数)。

class CBase 
{ 
}; 
// sizeof(CBase)=1;
为什么空的什么都没有是1呢?
c++要求每个实例在内存中都有独一无二的地址。//注意这句话!!!!!!!!!!
空类也会被实例化,所以编译器会给空类隐含的添加一个字节,这样空类实例化之后就有了独一无二的地址了。所以空类的sizeof为1。
class CBase 
{ 
    int a; 
    char p; 
}; 
// sizeof(CBase)=8;
记得对齐的问题。int 占4字节//注意这点和struct的对齐原则很像!!!!!
char占一字节,补齐3字节
class CBase 
{ 
public: 
    CBase(void); 
    virtual ~CBase(void); 
private: 
    int  a; 
    char *p; 
}; 
// sizeof(CBase)=12
C++ 类中有虚函数的时候有一个指向虚函数的指针(vptr),在32位系统分配指针大小为4字节。无论多少个虚函数,只有这一个指针,4字节。
//注意一般的函数是没有这个指针的,而且也不占类的内存。
class CChild : public CBase 
{ 
public: 
    CChild(void); 
    ~CChild(void); 
    virtual void test();
private: 
    int b; 
}; 
// sizeof(CChild)=16;
可见子类的大小是本身成员变量的大小加上父类的大小。
//其中有一部分是虚拟函数表的原因,一定要知道父类子类共享一个虚函数指针
class A {};    // sizeof(A)=1
 
class B{};     // sizeof(B)=1 
 
class C:public A{
    virtual void fun()=0;
};             // sizeof(C)=4     
  
class D:public B,public C{};   // sizeof(D)=8
类D的大小更让人疑惑吧,类D是由类B,C派生过来的,它的大小应该为二者之和5,为什么却是8 呢?这是因为为了提高实例在内存中的存取效率。类的大小往往被调整到系统的整数倍。并采取就近的法则,距离哪个最近的倍数,就是该类的大小,所以类D的大小为8个字节。

总结:

空的类是会占用内存空间的,而且大小是1,原因是C++要求每个实例在内存中都有独一无二的地址

(一)类内部的成员变量:

  • 普通的变量:是要占用内存的,但是要注意对齐原则(这点和struct类型很相似)。
  • static修饰的静态变量:不占用内容,原因是编译器将其放在全局变量区。

(二)类内部的成员函数:

  • 普通函数:不占用内存。
  • 虚函数:要占用4个字节,用来指定虚函数的虚拟函数表的入口地址。所以一个类的虚函数所占用的地址是不变的,和虚函数的个数是没有关系的。

结构体所占内存分析,请参考:

C++ 类和结构体所占内存大小​www.cnblogs.com

C++中的union

共用体,也叫联合体,在一个“联合”内可以定义多种不同的数据类型, 一个被说明为该“联合”类型的变量中,允许装入该“联合”所定义的任何一种数据,这些数据共享同一段内存,以达到节省空间的目的。union变量所占用的内存长度等于最长的成员的内存长度。

union与struct比较

先看一个关于struct的例子:

struct student
{
     char mark;
     long num;
     float score;
};

其struct的内存结构如下,sizeof(struct student)的值为12bytes。

下面是关于union的例子:

union test
{
     char mark;
     long num;
     float score;
};

sizeof(union test)的值为4。因为共用体将一个char类型的mark、一个long类型的num变量和一个float类型的score变量存放在同一个地址开始的内存单元中,而char类型和long类型所占的内存字节数是不一样的,但是在union中都是从同一个地址存放的,也就是使用的覆盖技术,这三个变量互相覆盖,而这种使几个不同的变量共占同一段内存的结构,称为“共用体”类型的结构。

因union中的所有成员起始地址都是一样的,所以&a.mark、&a.num和&a.score的值都是一样的。

不能如下使用:

union test a; printf("%d", a); //错误

由于a的存储区有好几种类型,分别占不同长度的存储区,仅写共用体变量名a,这样使编译器无法确定究竟输出的哪一个成员的值。

printf("%d", a.mark);  //正确

C++中union

上面总结的union使用法则,在C++中依然适用。如果加入对象呢?

#include <iostream>
using namespace std;

class CA
{
     int m_a;
};

union Test
{
     CA a;
     double d;
};

int main()
{
     return 0;
}

上面代码运行没有问题。

如果在类CA中添加了构造函数,或者添加析构函数,就会发现程序会出现错误。由于union里面的东西共享内存,所以不能定义静态、引用类型的变量。由于在union里也不允许存放带有构造函数、析构函数和复制构造函数等的类的对象,但是可以存放对应的类对象指针。编译器无法保证类的构造函数和析构函数得到正确的调用,由此,就可能出现内存泄漏。所以,在C++中使用union时,尽量保持C语言中使用union的风格,尽量不要让union带有对象。

以上union的内容参考: https://www. cnblogs.com/jeakeven/p/ 5113508.html
LWenlong:C++ union、struct 和 class​zhuanlan.zhihu.com

如有错误,欢迎大家指正,谢谢!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值