C++类和对象(3)
构造函数
初始化列表
初始化列表是成员变量定义的地方,不论是否显式地写出来,类中所有成员变量都会按照成员变量声明的顺序,经过初始化列表进行初始化。并秉持C++的风格:
(1)如果内置类型没有写进初始化列表:一般编译器不做处理(随机值);
(2)如果自定义类型没有写进初始化列表:编译器会调用这个自定义类型的默认构造函数。
class A {
public:A():_a(0),_ai(nullptr){}private:int _a; // 初始化为 0int* _ai; // 初始化为 空指针B b; // 调用 B 类的默认构造
};
另外,从 C++11 开始,支持在类成员变量的声明处给缺省值。如果没有在初始化列表中显式初始化,那么默认使用这个成员级的缺省值进行初始化。如果构造函数中也给了缺省值,那么优先使用构造函数中的缺省值。
有 3 类成员变量,必须在被定义时就初始化,也就是需要在初始化列表初始化:
(1)const 成员变量;
(2)引用;
(3)没有默认构造的自定义类型(构造函数是带参且没有缺省参数)。
class B {
public:B(char* c):_arr(c){}
private:char* _arr;
};class A {
public:A(int ref, char* a):_a(0),_refi(ref),_b(a){}
private:const int _a;int& _refi;B _b;
};
explicit 关键字
C++98支持单参数构造函数的隐式类型转换,C++11支持多参数构造函数的隐式类型转换。
class A
{
public:A(int a = 6):_a(a){}
private:int _a;
};class B
{
public:B(int b = 6, char c = '\0'):_b(b),_c(c){}
private:int _b;char _c;
};int main()
{A a(2); // 正常初始化 A 类对象A a1 = 1; // 单参数构造函数的隐式类型转换B b1(1, 'a'); // 正常初始化 B 类对象B b2 = { 6, 'b' }; // 多参数构造函数的隐式类型转换return 0;
}
这种隐式类型转换的过程是:使用参数调用该类的构造函数生成一个临时对象,再用该临时对象拷贝构造 a1 / b2 对象;但一般编译器会优化,将 构造+拷贝构造 的过程优化为 使用参数直接构造对象。
如果需要禁止这种隐式类型转换,使用 explicit 修饰构造函数即可:
class A
{
public:explicit A(int a = 6):_a(a){}
private:int _a;
};
匿名对象
A a1(1); // 有名对象A(2); // 匿名对象const A& aa = A(3); // 对匿名对象的 const 引用
匿名对象的生命周期只在这一行语句。但如果被 const 引用,生命周期会延长,直到这个引用的生命周期也结束。
如果是为了调用某个类中的成员函数,而创建的对象;那么可以使用匿名对象调用,简化代码。
A().Print();
static 成员
使用 static 修饰的成员变量称之静态成员变量;修饰的成员函数称之为静态成员函数。
static 修饰的类成员属于这个类的所有对象,受访问限定符的限制,存储在静态区,不会在对象中开辟空间存储。
静态成员变量
必须在类外定义,类中只是声明:
class A
{
public:
private:static int _a;
};
int A::_a = 0;
静态成员函数
静态成员函数没有隐藏的 this 指针,就不能得知这个非静态成员来自哪个对象,所以不能访问任何非静态成员:
class A
{
public:static char* Getc(){return _c; // 报错,不能访问非静态成员}void Print(){cout << _a << endl;}static void GetPrint(){Print(); // 报错}private:static int _a;char* _c = nullptr;
};
int A::_a = 0;
但是非静态的成员函数可以调用类的静态成员函数,因为静态成员函数又不需要传 this 指针...
class A
{
public:void Print(){GetPrint(); // 非静态成员函数,可调用静态成员函数}static void GetPrint(){cout << _a << endl;}private:static int _a;char* _c = nullptr;
};
int A::_a = 0;
友元
友元提供了一种突破封装的方式,提供便利的同时,也会增加耦合度破坏封装,不宜多用。
友元函数
现在尝试重载 A 类的 << 流插入运算符:
class A
{
public:A(char c = 'a'):_c(c),_a(0){}ostream& operator<<(ostream& out) // 重载流插入{cout << _c << endl;return out;}
private:char _c;size_t _a;
};int main()
{A a;//cout << a; // 报错a << cout; // a.operator<<(cout)return 0;
}
发现,由于 << 作为 A 类的成员函数进行重载,在参数列表中已经隐含了第一个参数 对象的 this 指针,所以调用时,a 对象在左,cout 在右;但这样显然不符合平时标准调用的逻辑;
所以需要调换参数列表中的 this 和 out,那么只能在类外实现:
ostream& operator<<(ostream& out, const A& a) // 重载流插入
{cout << a._c << endl; // 类外访问私有成员 报错return out;
}
但类外不能直接访问私有成员,提供两种方法访问:
(1)使用友元函数
将 << 运算符重载函数声明为 A 类的友元函数,友元函数可以直接访问 A 类的私有成员:
class A
{friend ostream& operator<<(ostream& out, const A& a);// friend关键字
public:A(char c = 'a'):_c(c),_a(0){}
private:char _c;size_t _a;
};
(2)提供成员函数间接获取
class A
{
public:A(char c = 'a'):_c(c),_a(0){}const char& Get_c() const // 注意 const 修饰{return _c;}
private:char _c;size_t _a;
};ostream& operator<<(ostream& out, const A& a) // 重载流插入
{cout << a.Get_c() << endl; // 类外访问私有成员return out;
}
友元类
友元类的所有成员函数都可以访问另一个类中的非公有成员。
注意:友元关系是单向的,且不会传递,不能继承。
class A
{friend class B; // 友元类
public:......略
};class B
{
public:void Print(){cout << A()._c << endl; // 使用匿名对象访问了A类的私有成员}
};
内部类
定义在另一个类中的类,就是内部类。
内部类是一个独立的类,不属于外部类;且外部类也没有对内部类成员的访问权限;
内部类是外部类的友元类,可通过外部类的对象参数,访问外部类的成员;
内部类可以直接访问外部类的 static 成员,而不需要突破 类域 的限制(因为本身就已经在类域中了)。
大小:计算外部类的大小时,与内部类无关。
class A
{
public:A(char c = 'a'):_c(c),_a(0){}class B //内部类{public:void Print(){cout << A()._c << endl; // 无需声明友元类,即可访问A的成员}};
private:char _c;size_t _a;
};