C++基础教程面向对象(学习笔记5(1))

构造函数初始化列表

在上一课中的学习过程中,为简单起见,我们使用赋值运算符在构造函数中初始化了类成员数据。例如:

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something()
    {
        // 这些全都是赋值而不是初始化
        m_value1 = 1;
        m_value2 = 2.2;
        m_value3 = 'c';
    }
};

执行类的构造函数时,将创建m_value1,m_value2和m_value3。然后运行构造函数的主体,其中成员数据变量被赋值。这类似于非面向对象的C ++中以下代码的流程:

int m_value1;
double m_value2;
char m_value3;
 
m_value1 = 1;
m_value2 = 2.2;
m_value3 = 'c';

虽然这在C ++语言的语法中是有效的,但它没有表现出良好的风格(并且可能初始化效率低)。

但是,正如您在前面的课程中学到的,某些类型的数据(例如const和引用变量)必须在声明它们的行上初始化。请考虑以下示例:

class Something
{
private:
    const int m_value;
 
public:
    Something()
    {
        m_value = 1; // 错误: const 变量不能被赋值
    } 
};

这会生成类似于以下内容的代码:

const int m_value; //错误: const变量一定要用一个值去初始化
m_value = 5; //  错误: const 变量不能被赋值

在某些情况下,在构造函数体中为const或引用成员变量赋值显然是不够用的。

成员初始化列表

为了解决这个问题,C ++提供了一种方法,用于通过成员初始化列表(通常称为“成员初始化列表”)初始化类成员变量(而不是在创建它们之后为它们赋值)。不要将这些与我们可以用来为数组赋值的类似命名的初始化列表混淆。
初始化和赋值中,您了解到可以通过三种方式初始化变量:复制,直接初始化和通过统一初始化(仅限C ++ 11)。

int value1 = 1; // 赋值
double value2(2.2); // 直接初始化
char value3 {'c'} // 统一初始化

使用初始化列表几乎与直接初始化(或C ++ 11中的统一初始化)相同。

这是通过示例最好地学习的东西。重新审视我们在构造函数体中执行赋值的代码:

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something()
    {
        // 这里全部都是赋值,不是初始化
        m_value1 = 1;
        m_value2 = 2.2;
        m_value3 = 'c';
    }
};

现在让我们使用初始化列表编写相同的代码:

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something() : m_value1(1), m_value2(2.2), m_value3('c') // 哈哈,这就是直接初始化
    {
    // 这里就不再需要赋值的代码了
    }
 
    void print()
    {
         std::cout << "Something(" << m_value1 << ", " << m_value2 << ", " << m_value3 << ")\n";
    }
};
 
int main()
{
    Something something;
    something.print();
    return 0;
}

这打印:

Soomething(1,2.2,c)
在构造函数参数之后插入成员初始值设定项列表。它以冒号(:)开头,然后列出要初始化的每个变量以及用逗号分隔的该变量的值。

注意,我们不再需要在构造函数体中执行赋值,因为初始化列表替换了该功能。另请注意,初始化列表不以分号结尾。

当然,当我们允许调用者传入初始化值时,构造函数更有用:

#include <iostream>
 
class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something(int value1, double value2, char value3='c')
        : m_value1(value1), m_value2(value2), m_value3(value3) // 直接初始化我莪们的成员变量
    {
    // 这里不需要赋值
    }
 
    void print()
    {
         std::cout << "Something(" << m_value1 << ", " << m_value2 << ", " << m_value3 << ")\n";
    }
 
};
 
int main()
{
    Something something(1, 2.2); // value1 = 1, value2=2.2, value3 的默认值是 'c'
    something.print();
    return 0;
}

这打印:

Something(1,2.2,c)
注意,您可以使用默认参数来提供默认值,以防用户忘记。

这是一个具有const成员变量的类的示例:

class Something
{
private:
    const int m_value;
 
public:
    Something(): m_value(5) // 直接初始化我们的静态成员变量
    {
    } 
};

这是有效的,因为我们可以初始化const变量(但不能分配给它们!)。

规则使用成员初始值设定项列表初始化类成员变量而不是赋值

C ++ 11中的统一初始化

在C ++ 11中,可以使用统一初始化而不是直接初始化:

class Something
{
private:
    const int m_value;
 
public:
    Something(): m_value { 5 } // 统一初始化成员变量
    {
    } 
};

我强烈建议您开始使用这种新语法(即使您没有使用const或引用成员变量),因为在进行组合和继承时需要初始化列表(我将在稍后介绍)。

规则:如果编译器与C ++ 11兼容,则优先于直接初始化进行统一初始化

使用成员初始化列表初始化数组成员

考虑一个带有数组成员的类:

class Something
{
private:
    const int m_array[5];
 
};

在C ++ 11之前,您只能通过成员初始化列表将数组成员归零:

class Something
{
private:
    const int m_array[5];
 
public:
    Something(): m_array {} // 将成员数组归零
    {
        // 如果我们希望数组有值,我们必须在这里使用赋值
    }
 
};

但是,在C ++ 11中,您可以使用统一初始化完全初始化成员数组:


class Something
{
private:
    const int m_array[5];
 
public:
    Something(): m_array { 1, 2, 3, 4, 5 } // 用同一初始化出事我们的成员变量
    {
    }
 
};

初始化作为类的成员变量
成员初始化列表也可用于初始化类的成员。


#include <iostream>
 
class A
{
public:
    A(int x) { std::cout << "A " << x << "\n"; }
};
 
class B
{
private:
    A m_a;
public:
    B(int y)
         : m_a(y-1) // 调用 A(int) 构造函数来初始化成员 m_a
    {
        std::cout << "B " << y << "\n";
    }
};
 
int main()
{
    B b(5);
    return 0;
}

这打印:

A 4
B 5
构造变量b时,将使用值5调用B(int)构造函数。在构造函数的主体执行之前,初始化m_a,调用值为4的A(int)构造函数。这将打印“A 4”。然后控制返回到B构造函数,并执行B构造函数的主体,打印“B 5”。

格式化初始化列表

C ++为您提供了如何格式化初始化程序列表的灵活性,这取决于您希望如何继续。但这里有一些建议:
如果初始化列表与函数名称在同一行上,那么将所有内容放在一行上就可以了

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something() : m_value1(1), m_value2(2.2), m_value3('c') // 所有的初始化都在一行完成
    {
    }
};

如果初始化列表与函数名称不在同一行,那么它应该在下一行缩进。

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something(int value1, double value2, char value3='c') // 这行已经有很多的东西了,我们更习惯将它放在下一行
        : m_value1(value1), m_value2(value2), m_value3(value3) //所以我们可以把所有内容缩进到下一行
    {
    }
 
};

如果所有初始化程序都不适合单行(或初始化程序不重要),那么您可以将它们分开,每行一个:

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
    float m_value4;
 
public:
    Something(int value1, double value2, char value3='c', float value4=34.6) // 这行已经有很多的东西了
        : m_value1(value1), //一行一个,末尾逗号
        m_value2(value2),
        m_value3(value3),
        m_value4(value4) 
    {
    }
 
};

初始化列表顺序
也许令人惊讶的是,初始化列表中的变量未按初始化列表中指定的顺序初始化。相反,它们按照在类中声明的顺序进行初始化。
为了获得最佳结果,应遵循以下建议:
1)不要使初始化成员变量依赖于首先初始化的其他成员变量(换句话说,即使初始化的顺序不同,那也确保你的初始化成员正确的初始化)。
2)初始化初始化列表中的变量的顺序与它们在类中声明的顺序相同。只要遵循先前的建议,这不是严格要求的,但如果您不这样做并且您已打开所有警告,您的编译器可能会给您一个警告。

summary:

成员初始化列表允许我们初始化我们的成员,而不是为它们分配值。这是初始化在初始化时需要值的成员的唯一方法,例如const或引用成员,并且它比在构造函数体中赋值更具魅力。成员初始化列表既可以使用基本类型,也可以使用类本身的成员,例如A(int)。

Quiz time

1)编写一个名为RGBA的类,它包含4个类型为std :: uint8_t的成员变量m_red,m_green,m_blue和m_alpha(#include cstdint访问类型为std :: uint8_t)。将默认值0分配给m_red,m_green和m_blue,将255分配给m_alpha。创建一个使用成员初始化列表的构造函数,该列表允许用户初始化m_red,m_blue,m_green和m_alpha的值。包含一个print()函数,用于输出成员变量的值。

提示:如果print()函数无法正常工作,请确保将uint8_t转换为int。

应运行以下代码:

int main()
{
	RGBA teal(0, 127, 127);
	teal.print();
 
	return 0;
}

并产生结果:

r = 0
g = 127
b = 127
a = 255

这里我们给出解决方案:

#include<iostream>
#include<cstdint>//加入unit_8
using namespace std;

class RGBA
{
public:

	RGBA(uint8_t red=0, uint8_t green=0, uint8_t blue=0, uint8_t alpha=255) :m_red(red), m_green(green), m_blue(blue), m_alpha(alpha)
	{
	}
	void print();
private:
	uint8_t	m_red;
	uint8_t m_green;
	uint8_t m_blue;
	uint8_t m_alpha;

};

void RGBA::print()
{
	cout << "r=" << static_cast<int>(m_red)<< endl;//static_cast<int>为强制类型转换
	cout << "g=" << static_cast<int>(m_green) << endl;
	cout << "b=" << static_cast<int>(m_blue) << endl;
	cout << "a:" << static_cast<int>(m_alpha) << endl;
}





int main()
{
	RGBA teal(0, 127, 127);
	teal.print();

	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_41879485/article/details/82946255