一、初始化列表
我们在 类和对象(中) 说明构造函数的时候提到这样一段内容
编译器自动生成的构造对于内置类型的成员变量初始化是没有特别要求的,也就是说,编译器自动生成的构造函数是否会初始化成员变量是取决于编译器的。而如果是自定义类型的变量,就会特别要求去调用该变量的构造函数来对此变量进行初始化,如果该变量没有默认构造函数的话,程序就会报错,此时就会需要利用初始化列表来解决这个问题。
因此,在这部分我们会说明初始化列表是什么及它是如何解决自定义类型变量没有构造函数时的问题
- 初始化列表的特点
- 类中定义的每个成员变量,在初始化列表中只能出现一次,初始化列表可以认为是每个成员变量定义初始化的地方
- 引用成员变量、const 成员变量及无默认构造函数的自定义类型变量都必须放在初始化列表进行初始化,否则编译器会报错
- 初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序无关
C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使用
// 例子 class Date{ public: // 初始化列表 Date(int year, int day, int month):_year(year),_month(month),_day(day){}; private: int _year = 2025; int _month = 3; int _day = 15; }; /* 需要注意的是不要把这里的缺省值当作是直接对变量进行初始化。这里的缺省值是当初始化列表中没有对某一个变量显示初始化时,默认使用这里给的缺省值 */
- 整体而言,尽量都使用初始化列表初始化,因为那些不在初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没有显示在初始化列表初始化的内置类型成员是否初始化是取决于编译器。对于没有显示在初始化列表的自定义类型成员会调用它的默认成员函数,如果没有默认成员函数就会报错
二、explicit 关键字
如果存在有相关内置类型为参数的构造函数,C++支持内置类型隐式类型转换为类类型对象
而如果不希望内置隐式转换为类类型对象,就可以使用 explicit 关键字
注:类类型的对象之间也可以隐式转换,也需要相应的构造函数支持
#include <iostream>
using namespace std;
class A
{
public:
// 构造函数 explicit就不再支持隐式类型转换
// explicit A(int a1);
A(int a1) : _a1(a1) {}
// explicit A(int a1, int a2):
A(int a1, int a2) : _a1(a1), _a2(a2) {}
void Print()
{
cout << _a1 << " " << _a2 << endl;
}
int Get() const
{
return _a1 + _a2;
}
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
B(const A &A) : _b(A.Get())
{
}
private:
int _b = 0;
};
int main()
{
// 1 构造一个A的临时对象,在用这个临时对象拷贝给 aa1
// 当编译器遇到 连续构造 + 拷贝构造 会优化成直接构造函数
A aa1 = 1;
aa1.Print();
const A &aa2 = 1;
// aa1隐式类型转换为b对象 原理跟上面类似
B b = aa1;
const B &rb = aa1;
return 0;
}
三、 Static 关键字
- 由 Static 修饰的成员变量称为静态成员变量,而静态成员变量必须要在类外初始化
- 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区
- 由 Static 修饰的成员函数称为静态成员函数,静态成员函数中不会隐式传入 this 指针
- 静态函数可以访问静态成员,但不能访问非静态成员( 因为没有 this 指针 )
- 非静态的成员函数,可以访问任意的静态成员变量和静态成员函数
- 突破类域就可以访问静态成员,可以通过类名::静态成员或者对象.静态成员来访问静态成员变量和静态成员函数
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
- 静态成员变量不能在声明位置给缺省值初始化。因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表
// 实现一个类,计算程序中创建出了多个类对象
#include <iostream>
using namespace std;
class A
{
public:
A()
{
++_scount;
}
A(const A &t)
{
++_scount;
}
~A()
{
--_scount;
}
static int GetACount()
{
return _scount;
}
private:
// 类里面声明
static int
_scount;
};
// 类外初始化
int A::_scount = 0;
int main()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
cout << a1.GetACount() << endl;
// cout << A::_scount << endl -- 编译报错:error: '_scount' is a private member of 'A'
return 0;
}
四、友元( friend ) – 不建议过度使用,避免破坏封装
友元提供了一种突破类访问限定符封装的方式,在声明的前面加上关键字 friend,并且把友元声明放到一个类的里面
友元分为友元函数和友元类,在函数声明或者类加上我 friend 并把友元声明放到类中
外部友元函数可访问类的私有和成员,友元函数仅仅是一种声明,他不是类的成员函数
友元函数可以在类定义的任何地方声明,不受类访问限定符限制
一个函数可以是多个类的友元函数
友元类中的成员函数都可以访问另一个类中的私有和保护成员
友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。友元类关系不能传递,如果A是B的友元,B是C的友元,但是A不是C的友元
// 友元函数
#include <iostream>
using namespace std;
class B; // 因为例子中要传入 B类对象的引用,所以这里要先声明,不然会报错
class A
{
friend void Func1(const A &aa, const B &bb);
public:
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
friend void Func1(const A &aa, const B &bb);
// func1 既是 A 类的友元 也是 B 类的友元
private:
int _b1 = 3;
int _b2 = 4;
};
void Func1(const A &aa, const B &bb)
{
cout << aa._a1 << aa._a2 << endl;
cout << bb._b1 << bb._b2 << endl;
}
int main()
{
A aa1;
B bb1;
Func1(aa1, bb1);
return 0;
}
Output
// 友元类
#include <iostream>
using namespace std;
class A
{
friend class B; // 声明 B类 是A类的友元
private:
int _a1 = 1;
int _a2 = 2;
};
class B
{
public:
void Func1(const A &aa)
{
cout << aa._a1 << " " << _b1 << endl;
}
void Func2(const A &aa)
{
cout << aa._a2 << " " << _b2 << endl;
}
private:
int _b1 = 3;
int _b2 = 4;
};
int main()
{
A a1;
B b1;
b1.Func1(a1);
b1.Func2(a1);
return 0;
}
Output
五、内部类
如果一个类定义在另一个类内,这个类就叫做内部类。内部类是一个独立的类,跟定义在全局相比,只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类
特性
- 内部类默认是外部类的友元
- 当B类实现出来就是要给A类使用的,就可以把B类设计为A类的内部类。如果把B类放到private or protected 则B类就是只有A类可以使用
#include <iostream>
using namespace std;
class A
{
public:
class B
{
public:
void foo(const A &aa)
{
cout << _k << endl;
cout << aa._h << endl;
}
};
private:
static int _k;
int _h = 1;
};
int A::_k = 1;
int main()
{
A a1;
cout << sizeof(a1) << endl;
A::B b1;
b1.foo(a1);
return 0;
}
Output
在 C++ 中,类的大小是由其成员变量所决定。因此,class A 的大小不包含 class B,因为内部类 B 只是 A 的一部分命名空间,但它不会作为 A 的成员变数存在
六、匿名对象
用类型 (实参) 定义出来的对象叫做匿名对象,相比之前我们定义的类型对象名 (实参) 定义出来的叫有名对象
匿名对象的特点是生命周期只在当前一行,一般临时定义一个对象当前用一下,就可以定义匿名对象
#include <iostream>
using namespace std;
class A
{
public:
A(int a = 0) : _a(a)
{
cout << "A(int a = 0)调用" << endl;
}
~A()
{
cout << "~A()调用" << endl;
}
private:
int _a;
};
class Solution
{
public:
int Sum_Solution(int n)
{
return n;
}
};
int main()
{
A aa1;
// A aa1(); //不能这样定义对象,编译器会不知道是函数声明还是对象定义
A(); // 匿名对象
A(10);
Solution().Sum_Solution(10);
return 0;
}
Output