目录
初始化列表
构造函数与初始化
在之前我们学习到了几个成员函数,其中的构造函数的作用就是为创建的对象进行初始化,不过大家需要注意的是,构造函数体中的语句只能将其称作为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。
真正的初始化工作是在函数接口与函数体之间的初始化列表中进行的,此处才是真正的初始化的位置。
初始化列表的语法
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个 “成员变量” 后面跟一个放在括号中的初始值或表达式。
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
类型变量的值是不能进行修改的,所以函数体内部的赋值操作是不被允许的,只有在初始化列表中,才能进行对const
类型变量的初始化;- 自定义类型成员(该类没有默认构造函数);
如果类 A 的对象 a 是类 B 的成员变量,且 A 没有默认的构造函数时,当创建一个类 B 的对象 b 时,如果 B 的构造函数没有参数列表,或者参数列表中没有对 a 的初始化,那么编译器就会报错,因为 A 中没有默认构造函数,而创建 b 时会自动调用 B 的构造函数,并随之调用 A 的构造函数,但是编译器不知道用什么值来进行初始化,所以就会报错;
- 引用成员变量;
class A{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
class B{
public:
B(int a, int ref)
:_aobj(a)
,_ref(ref)
,_n(10)
{}
private:
A _aobj; // 没有默认构造函数
int& _ref; // 引用
const int _n; // const
};
- 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化,调用该类型的构造函数,那么此时,自定义类型变量成员变量就不能直接使用构造函数进行变量的赋值了,因为前面已经使用过了。举例如下:
class Time{
public:
Time(int hour = 0)
:_hour(hour)
{
cout << "Time()" << endl;
}
private:
int _hour;
};
class Date{
public:
Date(int day)
//此处没有初始化列表,但是仍然在此处对自定义类型进行了初始化了
//此处进行初始化,比下面函数体的简单多了 _t(1); 即可进行初始化
{
//在函数体重对于 _t 的赋值就不能是这样的形式 _t(1);
//如果想对 _t 进行赋值,就只能使用如下方式,因为 _t 已经被初始化过了,初始化只能有一次
Time tmp(1);
_t = tmp;
}
private:
int _day;
Time _t;
};
int main(){
Date d(1);
}
- 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关;下面函数的输出结果为:1,随机值;
class A{
public:
A(int a)
//此处的排列与进行初始化的顺序没有任何关系
//所以是先用 _a1 对 _a2 进行初始化,然后再用 a 对 _a1 进行初始化
:_a1(a)
,_a2(_a1)
{}
void Print() {
cout<<_a1<<" "<<_a2<<endl;
}
private:
//此处的顺序决定了初始化列表中进行初始化的顺序
int _a2;
int _a1;
}
int main() {
A aa(1);
aa.Print();
}
explicit
关键字
构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用,实现单参构造函数的隐式转换,如果在构造函数前面加上explicit
的话,将可以禁止此种操作。
class Date{
public:
//这样写构造函数,是允许下面测试函数中的写法的
Date(int year)
:_year(year)
{}
//如果在构造函数前加上 explicit,那么就不能使用下面测试函数的写法了
explicit Date(int year)
:_year(year)
{}
private:
int _year;
int _month:
int _day;
};
void TestDate(){
Date d1(2018);
//用一个整形变量给日期类型对象赋值,实际编译器背后会用 2019 构造一个无名对象,最后用无名对象给d1对象进行赋值
d1 = 2019;
}
static
成员
静态成员为所有类对象所共享,不属于某个具体的实例;静态成员和类的普通成员一样,也有public
、protected
、private
3 种访问级别,也可以具有返回值;
static
成员变量
- 静态成员变量只能在类外进行初始化,可以不用加
static
关键字,但是得指明作用域; - 静态成员变量可用
类名::静态成员
或者对象.静态成员
来访问;
static
成员函数
- 静态成员函数可用
类名::静态成员
或者对象.静态成员
来调用; - 由于静态成员函数存在两种调用方式,所以静态成员函数没有隐藏的
this
指针,不能访问任何非静态成员; - 静态成员函数不可以调用非静态成员函数,非静态成员函数可以调用类的静态成员函数;
class A{
public:
//构造函数
A() {
++_scount;
}
//拷贝构造函数
A(const A& t) {
++_scount;
}
//静态成员函数
static int GetACount() {
return _scount;
}
private:
//静态成员变量
static int _scount;
};
//在类外对静态成员变量进行初始化
int A::_count = 0;
void TestA(){
//由于静态成员具有共有性,使用类名也可以调用,所以在没有创建任何对象时,也可以调用
cout<<A::_scount<<endl;
cout<<A::GetACount()<<endl;
A a1;
A a2;
A a3(a1);
cout<<A::GetACount()<<endl;
}
C++11 的成员初始化新特性
C++11 支持非静态成员变量在声明时进行初始化赋值,但是要注意这里不是初始化,这里是给声明的成员变量赋了缺省值,如果后续出现其他初始化、赋值操作,以最后一次操作为最终数据;
class B{
public:
B(int b = 0)
:_b(b)
{}
int _b;
};
class C{
public:
C(int c = 0, int d = 0)
:_c(c)
,_d(d)
{}
int _c;
int _d
};
class A{
public:
void Print(){
cout << a << endl;
cout << b._b<< endl;
cout << p << endl;
}
private:
// 非静态成员变量,可以在成员声明时给缺省值。
int a = 10;
B b = 20;
//当自定义类型多参时,需要使用列表来赋缺省值
C c = {1, 2};
int* p = (int*)malloc(4);
static int n;
};
//静态成员只能在类外初始化
int A::n = 10;
int main(){
A a;
a.Print();
return 0;
}
友元
友元分为:友元函数和友元类;友元顾名思义,就是朋友的意思,使用的关键字是friend
,对通过友元声明的函数、类,我们可以像好朋友一样访问到其私有的部分;友元提供了一种突破封装的方式,有时提供了便利,但使用过多会增加耦合度,破坏封装性,所以友元不宜多用。
友元函数
当我么在做重载operator<<
这样的一个尝试时,我么会发现,如果当作为成员函数重载时,该函数的第一个参数是指向类对象的this
指针,第二个参数才会是ostream
类的cout
,如果是这样的话,那么我们在使用重载运算符<<
输出对象时,就会出现如下情况:
Date d1;
//写完整的调用语句
d1.operator<<(cout);
//下面的语句会报错
//cout << d1;
//下面的语句才能通过
d1 << cout;
上面出现的情况,看起来会不符合我们的常理认知,毕竟大家都是将cout
写在<<
的前面,因此我么就只能将<<
的重载写在类外面,此时我们自己来确定参数的顺序,就可以实现正常情况的输出了,但是在类外的函数,是无法访问到类的私有成员变量,此时就需要使用到friend
关键字了,友元函数可以访问到类的私有成员,使用时只需将函数在类内声明一下,并在函数前面加上friend
即可;
在重载<<
函数时,返回的是cout
的引用,这样做的目的是为了能够连续输出,重载函数operator>>
也是同理这么做;
class Date
{
public:
//友元函数可以声明在类中的任何地方
friend ostream& operator<<(ostream& _cout, const Date& d);
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
prvate:
int _year;
int _month;
int _day
};
//运算符重载,返回引用,目的是为了连续使用,参数前加const,使得参数数据变为只读
ostream& operator<<(ostream& _cout, const Date& d){
_cout<<d._year<<"-"<<d._month<<"-"<<d._day;
return _cout;
}
int main(){
Date d(2017, 12, 24);
//连续输出
cout << d << endl;
cout << d << d << endl;
return 0;
}
友元函数说明
- 友元函数可访问类的私有和保护成员,但不是类的成员函数;
- 友元函数不能用
const
修饰; - 友元函数可以在类定义的任何地方声明,不受类访问限定符限制;
- 一个函数可以是多个类的友元函数;
- 友元函数的调用和原理与普通函数的调用和原理相同;
友元类
当类 A 中的许多成员函数需要用到类 B 的私有成员时,一个函数一个函数的声明友元会非常的繁琐,而且也不美观,因此我们可以通过声明友元类来很方便的解决这样的问题,我们在类 B 中声明友元类 A,那么在类 A 中的成员函数则可以对类 B 中的私有成员进行访问;
//前置声明,在一个类中声明一个未在其前面创建的类,需要进行简单的声明
class Date;
class Time{
// 声明日期类为时间类的友元类,则在日期类中就直接访问 Time 类中的私有成员变量
friend class Date;
public:
//构造函数
Time(int hour, int minute, int second)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date{
public:
//构造函数
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second){
//直接访问时间类私有的成员变量
_t._hour = hour;
_t._minute = minute;
_t.second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
友元类说明
- 友元类的所有成员函数都是另一个类的友元函数,每个成员函数都可以访问另一个类中的非公有成员;
- 友元关系是单向的,不具有交换性;声明了类 Date 是类 Time 的友元类,但是类 Time 并不是类 Date 的友元类;
- 友元关系不能传递,类 B 是类 A 的友元类,类 C 是类 B 的友元类,并不代表类 C 是类 A 的友元类;
内部类
概念
如果类 A 定义在类 B 的内部,那么称类 A 为类 B 的内部类,此时,类 A 可以通过类 B 的对象来访问类 B 的所有成员。
特性
- 内部类可以定义在外部类的
public
、protected
、private
这些权限中,并且这些权限会影响到内部类的可见范围; - 与友元类的区别:内部类可以直接访问外部类中的
static
、枚举成员,友元类访问static
、枚举成员则需要类的对象/类名; - 内部类是一个独立的类,它不属于外部类,
sizeof(外部类) = 外部类的大小
,和内部类没有任何关系;
class A{
private:
static int k;
int h;
public:
class B{
public:
void foo(const A& a){
//内部类可以直接访问外部类的静态变量
cout << k << endl;
cout << a.h << endl;
}
};
};
//外部初始化静态变量
int A::k = 1;
int main(){
//内部类定义在 public 作用下,所以外部不可以访问到
A::B b;
A a;
b.foo(a);
return 0;
}
简单应用
-
求 1 + 2 + 3 + … + n,要求不能使用乘除法、
- 这道题不能使用常规的解法,那么就可以借助内部类来实现,我们在外部内类中创建内部类数组,然后在内部类的构造函数中使用外部类的静态变量计数累加,那么就可以转换方法进行累加,最后得到答案;
for
、
while
、
if
、
else
、
switch
、
case
等关键字及条件判断语句:
class Count{
public:
//内部类
class Sum{
public:
//内部类中的构造函数用来累加计数
Sum(){
sum += num;
num++;
}
};
//在外部类中创建需要累加的 n 个内部类对象,那么就会累加到 n
void fun(int n){
//将其置为初始值,可以直接进行下一次累加计算
sum = 0;
num = 1;
//创建 n 个内部类对象
Sum arr[n];
return sum;
}
private:
static int num;
static int sum;
};
//静态变量外部初始化
int Count::num = 1;
int Count::sum = 0;