简单类

特殊成员函数

特殊成员函数与普通函数、运算符重载函数的一个显著的区分方法就是——没有返回值。

C++98必要组成:构造函数、拷贝构造函、赋值构造函数、析构函数。

C++11 :多了两个——移动构造函数、移动赋值构造函数

编译器会自动生成上述特殊成员函数。

默认构造函数:编译器生成的无参数构造函数。只要程序猿定义了有参构造函数,编译器就不会再提供无参构造函数了,所以为了防止出错,在显示定义有参构造函数前,要先定义一个无参构造函数。

构造函数分三类:无参构造函数,有参构造函数

有参构造函数可分为:转换构造函数、普通构造函数

转换构造函数部分初始化了成员,普通构造函数所有成员的初始化则需要由程序员提供。

在程序员未提供构造函数时,编译器会提供默认的无参构造函数。

与构造函数相关的关键字

noexcept

异常 noexcept 在函数的后面加上noexcept,代表这个函数不会抛出异常,如果抛出异常程序就会终止。使用 noexcept 表明函数或操作不会发生异常,会给编译器更大的优化空间。然而,并不是加上noexcept就能提高效率。

以下情形鼓励使用noexcept,不出意外noexcept只用于这三种函数:

1 移动构造函数(move constructor)

2 移动分配函数(move assignment)

3 析构函数(destructor)

explicit

explicit 关闭隐式类型转换,该关键字只用于转换构造函数

什么时候会发生隐式类型转换:赋值操作”=”且没有类型转化关键字的时候。比如double a=1就发生了int到double的隐式类型转换。

转换构造函数

把普通类型转化为类类型。其结构同有参构造函数一样,只不过部分参数被默认初始化了。

 explicit Complex(double real): m_real(real), m_imag(0.0){ } //把double类型转化为complex类型

 Complex(double real): m_real(real), m_imag(0.0){ } //区别在于complex a=2.0 是合法的,因为进行了隐式类型转化;加了explicit后,complex a=2.0语法错误,因为不能进行隐式类型转换,正确的格式为 complex a = complex(2.0)

类型转换函数

普通类型到类类型转换,类型转换函数结构与重载运算符类似,重载int等关键字。与一般运算符重载不同的是,类放在关键字后的()里。

class A

    {

    public:

        int a;

        A() {}

        explicit A(int a) :a(a) {}

        operator int()

        {

            return a;

        }

};

int main()

{

    A test(3);

    //用法1

    int d = test;

    //用法2

    int c = 2 + int(test);

    cout << d << endl;//3

    cout << c << endl;//5

    return 0;

}

类和类之间的类型转换

通过转换构造函数实现,与普通类型到类类型不同的是:1、编译器不提供隐式类型转换;2、未初始化的成员变量不一定只有一个,可能有多个。

=delete

C++11 中,可在想要 “禁止使用” 的特殊成员函数声明后加 “= delete”。比如编译器会生成多种构造函数,对于用不到的构造函数,后面添加delete即可。

=default

只要显式定义了任意一个特殊成员函数,该类就不属于POD类型了。但在显式定义的特殊成员函数后加上=default,该类就仍然是POD类型

为什么要区分出POD

POD,是Plain Old Data的缩写,普通旧数据类型,是C++中的一种数据类型概念。POD类型与C编程语言中使用的类型兼容,POD数据类型可以使用C库函数进行操作,也可以使用std::malloc创建,可以使用std::memmove等进行复制,并且可以使用C语言库直接进行二进制形式的数据交换。

什么是POD类型

当类或结构体同时满足如下几个条件时是普通类型:

 没有虚函数或虚基类;

 由C++编译器提供默认的特殊成员函数(默认的构造函数、拷贝构造函数、移动构造函数、赋值运算符、移动赋值运算符和析构函数);故自定义构造函数加一个=default,该类才属于pod类型。

判断是否为POD的方法

C++ 给定对象类型取决于其特定的内存布局方式,一个对象是普通、标准布局还是 POD 类型,可以根据标准库函数模板来判断:

#include

1)is_trivial

2)is_standard_layout

3)is_pod

调用时机

什么时候调用构造函数

声明类对象时调用构造函数,若声明时未初始化,则调用默认构造函数;若在声明时初始化,则调用相应的初始化构造函数。

 class demo *ptr=new(demo a);//调用默认构造函数

 class demo a=1;//调用类型转换构造函数

 demo a(1);//调用有参构造函数

 初始化形参时调用拷贝构造函数或者有参构造函数

什么时候调用析构函数

销毁类时调用析构函数

比如:delete时、离开函数作用域时、多线程下呢?

默认析构函数的缺陷

如果程序员没有定义析构函数,系统将自动生成和调用一个默认析构函数,默认析构函数只能释放对象的数据成员所占用的空间,但不包括堆内存空间。

故凡是存在指针成员的类,一定要自定义析构函数。

拷贝构造函数

浅拷贝(图1)和深拷贝(图2)



如图,浅拷贝就是简单的赋值,指针也是赋值;深拷贝,指针成员不是简单赋值,而是重新分配一块内存。浅拷贝存在的问题就是释放其中任意一个对象,另一个对象会产生野指针。


如图,释放stu2后,stu1便产生了野指针。

类的内存分配



虚表、static成员函数、其它成员函数、数据成员

 首项存放虚函数表指针,包含虚函数的类才会有虚函数表,同属于一个类的对象共享虚函数表;

 接下来的内存空间依次存放数据成员;

 成员函数不占用类的内存空间,成员函数地址通过编译器找到,并没有存放在类中。static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放。这就意味着,static 成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。

静态成员

1. 一个类中可以有一个或多个静态成员变量,所有的对象都共享这些静态成员变量,都可以引用它。注意,是共享,共享,共享,也即是只会占同一份静态存储空间。

2. static 成员变量和普通 static 变量一样,都在内存分区中的全局数据区分配内存,到程序结束时才释放。这就意味着,static 成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。而普通成员变量在对象创建时分配内存,在对象销毁时释放内存。根本原因是静态成员变量和对象的存储空间不同,是在不同的时期去分配的。

3. 静态成员变量必须初始化,而且只能在类体外进行。例如:int Point::count = 0;

虚表

虚表同static成员变量一样存放在全局数据区。

虚表指针并非指向虚表的表头,而是指向表头+16的位置。表头的前两个槽位(各占8个字节)。

然后存放type_info结构体指针,typeinfo内存放有与类的属性相关的信息。typeid(a)返回一个typeinfo对象,typeid(a).name()即typeinfo.name()返回a的类型名称。

你可能感兴趣的:(简单类)