1.再探构造函数
- 我们之前实现构造函数,初始化成员变量主要使用函数体内赋值,构造函数还有另一种方式——初始化列表,使用方式:
:开始 ,分隔成员列表 每个成员变量后跟一个()存放初始值或者表达式
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
// ...
}
private:
int _year;
int _month;
int _day;
};
- 每个成员变量在初始化列表只能出现一次,初始化列表的本质是:每个成员变量定义初始化的地方
const
成员变量、引用成员变量、没有默认构造的成员变量,必须放在初始化列表位置初始化,否则会报错
// 初始化列表
/* const成员变量、引用成员变量、没有默认构造的成员变量,
必须放在初始化列表位置初始化,否则会报错
*/
class Time
{
public:
// 非默认构造
Time(int hour)
:_hour(hour)
{
cout << "调用Time的构造" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date(int year, int month, int day, int& x,int num)
:_year(year)
, _month(month)
, _day(day)
// ,_t(6) // 未在初始化列表初始化
// ,_ref(x) // 未在初始化列表初始化
// ,_n(num) // 未在初始化列表初始化
{
// error C2512: “Time”: 没有合适的默认构造函数可用
_t = 6;
// error C2530: “Date::_ref”: 必须初始化引用
ref = x;
// error C2789: “Date::_n”: 必须初始化常量限定类型的对象
_n = num;
}
void Print()const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
Time _t; // 没有默认构造
int& _ref;
const int _n;
};
int main()
{
int i = 0;
Date d1(2025,8,14,i,100);
d1.Print();
return 0;
}
const
成员变量和引用成员变量只有一次初始化的机会就是在定义的时候,在初始化列表中,自定义类型成员会调用它的默认构造,而上述演示Time
没有默认构造,所以报错了
以下是修正版本代码:
// 修正版本:
class Time
{
public:
// 非默认构造
Time(int hour)
:_hour(hour)
{
cout << "调用Time的构造" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date(int year, int month, int day, int& x, int num)
:_year(year)
, _month(month)
, _day(day)
,_t(6)
,_ref(x)
,_n(num)
{
// ...
}
void Print()const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
Time _t; // 没有默认构造
int& _ref;
const int _n;
};
int main()
{
int i = 0;
Date d1(2025, 8, 14, i, 100);
d1.Print();
return 0;
}
C++11
支持成员变量在声明的位置给缺省值,这个缺省值是给没有显式在初始化列表的成员使用的
// 在声明时给的缺省值是给初始化列表成员初始化使用
class Time
{
public:
// 非默认构造
Time(int hour)
:_hour(hour)
{
cout << "调用Time的构造" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date(int year, int month, int day, int& x, int num)
:_year(year)
, _month(month)
, _day(day)
{
// ...
}
void Print()const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
// C++11 给缺省值用于参数列表成员初始化 此处仅声明 未定义
Time _t = 6;
const int _n = 100;
const int& _ref = _n;
};
int main()
{
int i = 0;
Date d1(2025, 8, 14, i, 100);
d1.Print();
return 0;
}
- 建议一定要使用初始化列表初始化,那些不在初始化列表初始化的成员也会先走一遍初始化列表,如果这个成员给了缺省值,初始化列表会使用缺省值初始化,如果没有缺省值,对于没有出现在初始化列表的内置成员,是否初始化取决于编译器,对于没有出现在初始化列表的自定义类型成员,会调用这个成员类型的默认构造函数,没有默认构造函数会报错
class Time
{
public:
// 有默认构造
Time(int hour = 6)
:_hour(hour)
{
cout << "调用Time的构造" << endl;
}
private:
int _hour;
};
class Show
{
public:
// 没有初始化列表的构造函数
Show(int num2, char ch2, Time t2)
{
// 这里只是赋值,不是初始化
_num = num2;
_ch = ch2;
// _t = t2; // 即使这样写也会先尝试默认构造_t
}
void Print()const
{
cout << _num << " " << _ch << endl;
}
private:
int _num; // 内置类型,无缺省值
char _ch = 'a'; // 内置类型,有缺省值
Time _t; // 自定义类型,无默认构造,无缺省值
// Time _t2 = Time(10); // 如果这样给缺省值,也可以通过编译
};
int main()
{
Time t(10);
Show s(1, 'b', t);
return 0;
}
如果将Time
的构造改成非默认构造就会报错
- 初始化列表初始化的顺序和成员变量出现的顺序一致
分析下面的代码输出什么?
class A
{
public:
A(int a)
:_a1(a)
, _a2(_a1)
{
}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2 = 2;
int _a1 = 2;
};
int main()
{
A aa(1);
aa.Print();
return 0;
}
分析:
创建A
类变量aa
,并传参1给构造函数,按照成员变量声明顺序,先初始化_a2
,为随机值,后初始化_a1
为传递的参数1
所以最后输出:1 随机值
总结一下初始化列表:
我们记住核心逻辑即可:
构造函数的初始化列表用于初始化,函数体用于赋值
2. 类型转换
C++
支持内置类型隐式转换为类类型对象,需要有相关内置类型为参数的构造函数- 构造函数前加
explicit
关键字就不再支持类型转换 - 类类型对象之间也可以转换,需要相应构造函数支持
// 类型转换
class ShowA
{
public:
// 单参数构造
ShowA(int aa)
:_a(aa)
{
cout << "调用构造ShowA(int aa)" << endl;
}
// 多参数构造
ShowA(int aa, int aaa)
:_a(aa)
, _aa(aaa)
{
cout << "调用构造ShowA(int aa, int aaa)" << endl;
}
void Print()
{
cout << _a << " " << _aa << endl;
}
int GetA() const
{
return _a + _aa;
}
private:
int _a;
int _aa;
};
class ShowB
{
public:
ShowB(const ShowA& a)
:_b(a.GetA())
{
// ...
}
private:
int _b;
};
int main()
{
ShowA aaa1(10);
aaa1.Print();
// 拷贝构造
ShowA aaa2 = aaa1;
// 3(int)隐式类型转换成ShowA类 构造一个3的临时对象,再用临时对象拷贝构造aaa3
// 编译器遇到连续的构造+拷贝构造 会优化成->直接构造
ShowA aaa3 = 3;
aaa3.Print();
// C++11支持多参数转化
ShowA aaa4 = { 1,2 };
aaa4.Print();
ShowB bbb1 = aaa4;
// 引用的是aaa1构造的临时对象
const ShowB& rb =aaa1;
return 0;
}
3. static
成员
- 用
static
修饰的静态成员变量必须在类外初始化 - 静态成员变量为所有类对象共享,存放在静态区
class A
{
public:
private:
int _a = 1;
float _fa = 2.1;
// 静态成员变量在类内声明
static int _aa;
};
// 静态成员变量在类外初始化
int A::_aa = 100;
class B
{
public:
private:
int _b = 0;
};
int main()
{
cout << sizeof(A); // 8
return 0;
}
- 用
static
修饰的静态成员函数没有this
指针 - 静态成员函数可以访问其他静态成员,但不能访问非静态的成员,因为没有
this
指针 - 非静态的成员函数可以访问任意的静态成员变量和静态成员函数
- 静态成员也是类的成员,也受
public、protected、private
访问限定符的限制
实现一个类,计算程序出现多少个类对象
class Calculate
{
public:
Calculate()
{
++_count;
}
Calculate(const Calculate& copy)
{
++_count;
}
~Calculate()
{
--_count;
}
static int GetCount()
{
return _count;
}
private:
// 类内声明
static int _count;
};
// 类外初始化
int Calculate::_count = 0;
int main()
{
cout << Calculate::GetCount() << endl;
Calculate a1, a2;
Calculate a3(a1);
cout << Calculate::GetCount() << endl;
cout << a1.GetCount() << endl;
// error C2248: “Calculate::_count”: 无法访问 private 成员(在“Calculate”类中声明)
// cout << Calculate::_count << endl;
return 0;
}
class Sum
{
public:
Sum()
{
_sum += _num;
++_num;
}
static int GetSum()
{
return _sum;
}
private:
static int _num;
static int _sum;
};
int Sum:: _num = 1;
int Sum::_sum = 0;
class Solution {
public:
int Sum_Solution(int n) {
Sum arr[n];
return Sum::GetSum();
}
};
思路:创建一个Sum
类的边长数组,在类中定义两个静态变量_num _sum
,每调用一次构造函数_sum
就加_num
,随后_Num
加一,数组在完成n个元素初始化的过程中就完成了计算
4. 友元
- 在函数声明或者类声明的前面加
friend
,并把友元声明放到一个类中 - 友元函数仅仅是一种声明,并不是类的成员函数
- 友元的关系是单向的,
A
是B
的友元,但是B
不是A
的友元,同样友元没有传递性,A
是B
的友元,B
是C
的友元,但A
不是C
的友元 - 友元会增加耦合度,不建议多用
5. 内部类
-
一个类定义在另一个类的内部,这个在类内部的类就称为内部类,内部类是独立的类,仅受类域限制,所以外部类定义的对象中不包含内部类
-
内部类默认是外部类的友元
6. 匿名对象
- 用
类型(实参)
定义出来的对象叫做匿名对象 - 匿名对象的生命周期只在当前这一行,即用即销毁
// 匿名对象
class A
{
public:
A(int a)
:_a(a)
{
// ...
}
private:
int _a;
};
int main()
{
A a1(10); // 有名对象
A(3); // 匿名对象 生命周期仅在这样一行
return 0;
}