目录
一.再谈构造函数
1.1构造函数体赋值
1.2初始化列表
1.3explicit关键字
二.static成员
2.1概念
编辑
2.2特性
三.友元
3.1友元函数
3.2友元类
4.内部类
一.再谈构造函数
1.1构造函数体赋值
class Date
{
public:
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
在创建对象时,编译器可以通过调用构造函数,给对象中的各个成员变量一个合适的初始值。
但,这个过程并不能称之为初始化,只能称之为赋初值。
这是因为初始化只能有一次,但构造函数体内可以多次赋值。
1.2初始化列表
如何初始化呢?这就需要用到我们的初始化列表了。
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号内的初始值或表达式。
作用是会将括号内的内容初始化给成员变量
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{
}
需要注意的是:初始化列表需要写在构造函数的函数体外面,参数列表的后面。
下面提几个注意点
1.在这里需要注意的是,有一些成员必须放在初始化列表内进行初始化
- 引用成员变量
- const成员变量
- 自定义类型成员且该类没有默认构造函数(即没有不传参就可以用的构造函数)
class A
{
public:
A(int a)
:_a(a)
{
}
private:
int _a;
};
class B
{
public:
B(int a,int ref)
:_ref(ref)
,_obj(10)
,_i(a)
{}
private:
int& _ref;//无默认构造
A _obj;//引用
const int _i;//静态
};
在这里,它们都需要我们使用初始化列表来进行初始化。
2.初始化列表是怎么工作的呢?他是按照什么顺序给成员初始化的呢?
class A
{
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
void Print() {
cout << _a1 << " " << _a2 << endl;
}
private:
int _a2;
int _a1;
};
int main()
{
A a(1);
a.Print();
return 0;
}
猜一下,这段程序会打印出来什么东西?
我们发现,_a2正常打印了,但是_a1却是随机值,这是为什么呢?
这时因为初始化列表是按照声明顺序进行初始化的,也就是说,它会先初始化_a2,再初始化_a1,而由于_a2是用_a1初始化的,而_a1此时还没有初始化,因此_a2打印结果是随机值。
3.尽量使用初始化列表初始化,因为不管是否使用初始化列表初始化,对于自定义类型的成员,一定会优先使用初始化列表初始化。
class Time
{
public:
Time(int hour = 0)
:_hour(hour)
{
cout << "我用了初始化列表哦" << endl;
}
private:
int _hour;
};
class Date
{
public:
Date(int day)
{}
private:
int _day;
Time _t;
};
int main()
{
Date a(10);
return 0;
}
在这段代码中,Date类中含有一个Time类的成员,但是并没有在构造函数中对Time类成员初始化或赋初值,那么这段代码会运行吗?运行结果是什么呢?
可以看到,虽然我们没有对Time类成员进行操作,依旧使用了初始化列表对其初始化。
1.3explicit关键字
构造函数不仅可以构造与初始化对象,对于接收单个参数的构造函数,还具有类型转换的作用。
class A {
public:
A(int value) {
// 初始化代码
}
};
void func(A a) {
// 函数体
}
int main() {
func(10);
return 0;
}
在这段代码中,func函数中的调用了A类型的参数,但我们在main函数中传递了一个int类型的参数。但是这段代码是可以通过编译的,这时因为单个参数的构造函数具有隐式类型转换的作用。
可以从上图看出,单个参数是有类型转换的作用的,这里我们可以将整型的10隐式转化为A类型。
那么,如何禁止类型转换呢?
这就需要用到我们的explicit关键字了,它的作用是禁止隐式类型转换。
由于C++设计者认为多个参数的情况下隐式类型转换比较安全,所以一般都在单个参数的情况下使用。
在传递的是一个参数的前提下,我们可以使用explicit关键字,譬如以下场景:
- 构造函数只有一个参数
- 构造函数有多个参数,除第一个参数没有默认值外,其余参数都有默认值
- 全缺省构造参数
这里我们放出了两种上述情况,请大家注意,这里为了便于大家看就都没有注释掉,如果大家要复制到自己的编译器实践的话,需要注释掉一个。
class Date
{
public:
//注意,以下情况要注释掉一个。
//1.只有一个参数
explicit Date(int year)
:_year(year)
{}
// 2. 虽然有多个参数,但是创建对象时后两个参数可以不传递
explicit Date(int year, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022);
d1 = 2023;//实际编译器背后会用2023构造一个临时对象,最后用临时对象给d1对象进行赋值
}
注释掉一个的话,也会编译失败,这是因为我们使用了explicit关键字,我们无法将2023隐式转换为Date类型。
二.static成员
2.1概念
声明时加上static的类成员称为类的静态成员;
用static修饰的成员变量称之为静态成员变量;
用static修饰的成员函数称之为静态成员函数。
静态成员变量要在类外初始化。
做一道题:
现在我们实现一个类,要求计算程序中创建出了多少个类对象。
class A
{
public:
A()
{
++_count;
}
~A()
{
--_count;
}
static int getcount()
{
return _count;
}
private:
static int _count;
};
int A::_count = 0;
int main()
{
cout << A::getcount() << endl;
A a;
A b;
cout << A::getcount() << endl;
return 0;
}
2.2特性
- 静态成员存放在静态区,不属于某个具体的对象,是所有类对象共享的
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员用类名::静态成员或对象.静态成员来访问
- 静态成员函数中没有隐含的this指针,不可用于访问类内的非静态成员。(没有this)
- 静态成员也受到访问限定符的限制
三.友元
友元是一种突破封装的方式,有时它很便利,有时会增加耦合度,从而破坏封装,所以友元不宜多用。
友元可分为:友元函数和友元类
3.1友元函数
如果我们尝试去重载operator<<操作符,就会发现一个问题,我们无法将其重载为成员函数。
为什么呢?我们根据代码来进行理解:
//<<有两个操作数,分别是cout和d1.
正常使用:cout<<d1;
//但是重载为成员函数,我们的第一个参数就是*this
//我们使用起来就要这么使用了:d1<<cout或者d1.operator<<(&d1,cout)
//这样是不符合我们的使用习惯的
那么,我们就只能将其实现在类外,那么就又产生了一个新的问题,我们应该如何访问类内的私有成员呢?
这就需要用到我们的friend了,friend是朋友的意思,既然都是朋友了,我用用你的私有物品也没关系的吧?
因此,被friend修饰的函数,可以访问到类内的私有成员
class Date
{
//友元函数不受访问限定符的限定
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year, int month, int day)
:_year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout,const Date& d)
{
_cout << d._year << d._month << d._day;
return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
_cin >> d._day >> d._month >> d._year;
return _cin;
}
说明:
- 友元函数可以访问类的私有成员和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用和普通函数的调用原理是一样的
3.2友元类
友元类的所有成员函数都可以是另一个类的友元函数,也都可以访问另一个类中的非共有成员。
下面我们用代码来实践一下:
//Time类
class Time
{
//声明友元于B
friend class Date;
public:Time(int hour = 0, int minute = 0, int second = 0)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
//Date类
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;
};
友元类有以下性质:
- 友元关系是单向的,不具有交换性。
- 友元关系不具有传递性
- 友元关系不可继承
通俗的形容:你把我当朋友,但我不一定把你当朋友我把你当朋友;
你把他当朋友,但是我可能不喜欢他,我不会跟他做朋友。
4.内部类
概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
注意点:
- 内部类可以定义在外部类的public、protected、private都是可以的。
- 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。
- sizeof(外部类)=外部类,和内部类没有任何关系。
我们可以用如下代码来实践:
class A
{
private:
static int k;
int h;
public:
class B//B是A的友元
{
public:
void foo(const A& a)
{
cout << k << endl;
cout << a.h << endl;
}
};
};
int A::k = 1;//静态
int main()
{
A::B b;
b.foo(A());
return 0;
}
博主的C++专栏:点击进入CPP专栏
码字不易,如果你觉得博主写的不错,给个三联关注评论把!!!