• 认真地记录技术中遇到的坑!

8.3构造函数(类中最重要的知识点之一)

C/C++ Moxun 2年前 (2018-01-23) 478次浏览 0个评论

[toc]1.构造函数的初始值列表
如果没有在构造函数的初始值列表中显式的初始化成员,则该成员将在构造函数体之前执行默认初始化。

构造函数的初始值有时必不可少:
如果成员是const或者是引用的话,那么必须将其初始化(这时就不能忽略初始化和赋值的区别了)。类似的,当成员属于某种类类型且该类没有定义默认构造函数时也必须要将这个成员初始化。
随着构造函数体一执行,初始化就完成了,我们初始化const或者引用类型的的数据唯一的机会就是通过构造函数初始值列表,例如:
class A{
const int i;
int &ci();

    A(int ii) : i(ii),ci(ii){};
    }

    初始化和赋值在效率上的区别:初始化直接初始化成员,赋值则先初始化,再赋值。

成员初始化顺序:
构造函数初始值列表只说明用于初始化成员的值,而不限定初始化的顺序,成员初始化的顺序和它在类定义中出现的顺序是一致的,构造函数初始值列表中的前后位置不会影响实际的初始化顺序。但最后令构造函数初始值列表中成员初始化的顺序和类定义中成员出现的位置是一样的。
如果有可能的话,最好用构造函数的参数作为函数的初始值。

默认实参和构造函数:
例如:
class A
{
A(int i =0){};
}

[toc]8.3.2 委托构造函数
一个委托构造函数使用它所属的类的其它构造函数执行它自己的初始化过程,或者说它把自己的一些或者全部职责委托给了其它构造函数。一个委托构造函数也有一个成员初始值的列表和一个函数体。在委托构造函数内,成员初始值列表只有一个唯一的入口,就是类名本身。和其它成员初始值一样,类名后面紧跟圆括号括起来的参数列表,参数列表必须与类中另外一个构造函数匹配。例如:
class Sales_data()
{
//非委托构造函数
Sales_data(std::string s,unsigned int cnt,double price):bookNo(s),units_sold(cnt),revenue(cntprice){}
//委托构造函数
Sales_data():Sales_data(“”,0,0){}
Sales_data(std::string s):Sales(s,0,0){}
Sales_data(std::isstream & is):Sales_data()
{
read(is,
this);
}
}

    当一个构造函数委托给另外一个构造函数时,受委托的构造函数的初始值列表和函数体依次执行。

[toc]8.3.3默认构造函数的作用
当对象被默认初始化或者值初始化时自动执行默认构造函数
1.当我们在块作用域内不使用任何初始值定义一个非静态变量或者数组时
2.当一个类本身含有类类型的成员且使用合成的默认构造函数时
3.当类类型的成员没有在构造函数初始值列表中显式初始化时
值初始化在下列情况下发生:
1.在数组初始化的过程汇总如果我们提供的初始值数量少于数组大小时
2.当我们不使用初始值定义一个局部静态变量时
3.当我们通过书写形如T()的表达式显式的请求值初始化是。
类必须包含一个默认构造函数以便在上述情况下使用。

[toc]8.5.4隐式的类类型转换
如果定义了一个只接受一个参数的构造函数,那么实际上就是定义了转换为此类类型的隐式转换规则,有时把这种构造函数称为转换构造函数。

    只允许一步类类型转换,编译器只允许进行一步类类型转换。

类类型转换并不总是有效:

抑制构造函数定义的隐式转换:
将转换构造函数声明为explict,就可以阻止上述隐式类型转换。关键字explict只对单参数构造函数有效,只在声明构造函数时使用explicit,在类外部定义时不能使用。

explict构造函数只能用于直接初始化(不用等号的那种初始化形式)另外当执行拷贝形式初始化的时候也会发生隐式类型转换。

为转换显式的使用构造函数:
例如:
item.conbine(Sales_data(null_book))
它直接使用了Sales_data接收string类型的构造函数,null_book是个string类型,构造一个临时对象给conbine使用。
item.conbine(static_cast(cin))

标准库中含有显式构造函数的类:
1.接收一个单参数coonst char *的string的构造函数
2.接受一个容量参数的vector的构造函数是explict的。

聚合类:
聚合类使得用户可以直接访问其成员,并且具有特殊的初始化语法形式,当一个类满足以下情况时我们说它是聚合的:
1.所有成员都是public的
2.没有定义任何构造函数
3.没有类内初始值
4.没有继承也没有virtual函数,例如:
struct A{
int i;
string s;
}
像A就是一个聚合类。我们可以提供一个花括号括起来的成员初始值列表,并用它初始化聚合类的数据成员:
A a = {0,”Anna”};需要注意的是初始值的顺序必须是和声明的顺序是一致的。

[toc]8.3.6 字面值常量类
字面值类类型可能含有constexpr函数成员,这样的函数成员必须符合const函数的所有要求,它们是隐式const的。
数据成员都是字面值类型的聚合类是字面值常量类,如果一个类不是聚合类,但符合下列要求,那么它也是一个字面值常量类
1.数据成员必须是字面值类型
2.类中至少含有一个constexpr类型的构造函数
3.如果一个数据成员含有类内初始值,则内置类型数据成员的初始值必须是一条常量表达式,或者如果成员属于某种类类型,则初始值必须使用成员自己的constexpr构造函数。
4.类必须使用析构函数的默认定义,该成员负责销毁类的对象。

    算数类型、指针、引用是字面值类型。

    constexpr构造函数:可以声明为=default的,或者是删除形式,否则constexpr构造函数就必须符合构造函数的要求(意味着不能包含返回类型),又符合constexpr函数的要求(意味着它能拥有的唯一可执行语句就是return 语句),因此constexpr构造函数体一般是空的,通过前置关键字就可以声明一个constexpr构造函数了。例如:
    class Debug
    {
    public:
        constexpr Debug(bool b =true) :hw(b),io(b),other(b){}
        constexpr bool any() {return hw||io||other;}
        void set_io(bool b) { io = b}
        void set_hw(bool b) {hw = b}
        void set_other(bool b){other = b}
        private :
        bool io;
        bool hw;
        boool other;
    }

    constexpr构造函数必须初始化所有数据成员,初始值或者使用constexpr构造函数或者是一条常量表达式。
    constexpr构造函数常用于生成constexpr对象以及constexpr函数的参数或者返回类型:
    constexpr Debug io_sub(false,true,false)
    if(io_sub.any()) 等价于if(true)

[toc]8.3.6类的静态成员
类的静态成员属于类本身而不属于类的对象,声明类的静态成员要在成员声明语句的最前面加关键字static,静态成员可以是public也可以是private的,静态成员可以是常量、引用、指针、类型等。
类的静态成员存在于任何对象之外,对象中不包含任何与静态成员有关的数据。

    同样,类的静态成员不与任何对象绑定在一起,它们不包含this指针,作为结果静态成员函数不能声明为const的,而且我们也不能在static函数体内使用this指针。这一限制即使用于this的显式使用也对调用非静态成员的隐式使用有效。

使用类的静态成员
可以使用作用域运算符直接访问类的静态成员,例如:
A::staticFunc;
尽管静态成员不属于类的某个对象,但是我们还是可以使用类对象、类对象的指针、类对象引用来访问静态成员。
成员函数不用使用作用域运算符就能使用静态成员。

定义静态成员:
既可以在类内部定义静态成员也可以在类外部定义静态成员,当在类外部定义静态成员时不能重复static关键字。该关键字只能出现在声明语句中。
静态成员属于类,所以它不是在对象被创建时被定义的这意味着它们不是由类的构造函数初始化的而且一般来说我们不能在类的内部初始化静态成员。相反的,必须在类的外部定义和初始化每个静态成员,静态成员同样的只能定义一次,静态数据成员定义于任何函数之外,定义静态数据成员的方式和在类的外部定义成员函数类似。

    静态成员的类内初始化:
    一般,静态成员不允许在类内初始化,但是,可以为静态成员提供const整数类型的类内初始值,不过这要求静态成员必须是字面值常量类型的constexpr。初始值必须是常量表达式,因为这些成员本身就是常量表达式,
    就是说在下列情况下才可以在类内初始化:
    static constexpr int period =30;

    静态成员独立与任何对象,因此,在某些非静态数据成员可能非法的场合,静态成员还可以正常使用。静态成员可以是不完全类型,另外,静态数据成员的类型可以是它所属的类类型。
    另外,我们可以使用静态成员作为默认实参。非静态数据成员可以不能作为默认实参,因为它本身就是对象的一部分。

喜欢 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址