一、初始化列表
class A
{
public:
A(int a1)
:_a1(a1)
{}
private:
int _a1;
};
这就是经典的初始化列表,初始化列表紧跟在默认构造函数之后,形式比较奇怪:主要通过 :
、,
和 ()
实现初始化。
每个成员变量在初始化列表中只能出现⼀次,语法理解上初始化列表可以认为是每个成员变量定义 初始化的地⽅。
2.引⽤成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进⾏初始化,否则会编译报错。
#include<iostream>
using namespace std;
class Time
{
public:
Time(int hour)
:_hour(hour)
{
cout << "Time()" << endl;
cout << "" <<_hour<< endl;
}
private:
int _hour;
};
class Date
{
public:
Date(int& x, int year=1, int month=1, int day=1)
:_year(year)
, _month(month)
, _day(day)
//, _t(12)
//, _ref(x)
//, _n(1)
{
// error C2512: “Time”: 没有合适的默认构造函数可⽤
// error C2530 : “Date::_ref” : 必须初始化引⽤
// error C2789 : “Date::_n” : 必须初始化常量限定类型的对象
_t(12);
_ref(x);
_n(1);
}
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
cout << "" << _n << endl;
cout << "" << _ref << endl;
}
private:
int _year;
int _month;
int _day;
Time _t; // 没有默认构造
int& _ref; // 引⽤
const int _n; // const
};
int main()
{
int i = 0;
Date d1(i);
d1.Print();
return 0;
}
我们来看上面的代码,成员变量_t没有默认构造函数,_ret是引用,_n是const类型变量,这三种类型的变量如果不在初始化列表中定义的话,就会引发编译报错。
所以引⽤成员变量,const成员变量,没有默认构造的类类型变量这三种类型的变量一定要在初始化列表中定义。
3.C++11⽀持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显⽰在初始化列表初始化的成员使⽤的。
private:
int _year=2024;
int _month=7;
int _day=23;
Time _t; // 没有默认构造
int& _ref; // 引⽤
const int _n; // const
我们在成员变量声明的位置给一个值就是该成员变量的缺省值,如果我们没有将_year、_month和_day这三个成员变量初始化的话,这三个成员变量就会使用缺省值。
我们来看下面的代码:
#include<iostream>
using namespace std;
class Time
{
public:
Time(int hour)
:_hour(hour)
{
cout << "Time()" << endl;
cout << "" <<_hour<< endl;
}
private:
int _hour;
};
class Date
{
public:
Date(int& x, int year=1, int month=1, int day=1)
//:_year(year)
//, _month(month)
//, _day(day)
: _t(12)
, _ref(x)
, _n(1)
{
// error C2512: “Time”: 没有合适的默认构造函数可⽤
// error C2530 : “Date::_ref” : 必须初始化引⽤
// error C2789 : “Date::_n” : 必须初始化常量限定类型的对象
}
void Print() const
{
cout << _year << "-" << _month << "-" << _day << endl;
cout << "" << _n << endl;
cout << "" << _ref << endl;
}
private:
int _year=2024;
int _month=7;
int _day=23;
Time _t; // 没有默认构造
int& _ref; // 引⽤
const int _n; // const
};
int main()
{
int i = 0;
Date d1(i);
d1.Print();
return 0;
}
我们在初始化列表中将 _year、_month和_day这三个成员变量的初始化屏蔽了,这时我们打印年月日就会发现年月日都变为了缺省值。
相比较与使用构造函数进行初始化,我们更加推荐使用初始化列表进行初始化,因为那些你不在初始化列表初始化的成员也会⾛初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会⽤这个缺省值初始化。如果你没有给缺省值,对于没有显⽰在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没显⽰在初始化列表初始化的⾃定义类型成员会调⽤这个成员类型的默认构造函数,如果没有默认构造会编译错误。
4.初始化列表中按照成员变量在类中声明顺序进⾏初始化,跟成员在初始化列表出现的的先后顺序⽆关。建议声明顺序和初始化列表顺序保持⼀致。
我们来看下列代码:
#include<iostream>
using namespace std;
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();
}
大家觉得打印出来的结果会是多少呢?打印出来的结果是1和一个随机值,因为初始化列表中按照成员变量在类中声明顺序进⾏初始化,_a2先声明,所以先初始化_a2,用_a1初始化_a2,此时_a1还未被a初始化,所以_a1也是随机值,所以_a2是随机值,_a1被a初始化所以是1。
二、类型转换
=
右边的值拷贝给临时变量,再拷贝给
=
左边的值。
![](https://i-blog.csdnimg.cn/direct/0e1f6822abef43adb0b5c0a374ff250a.png)
而C++支持内置类型转换为类类型,我们来看下面的代码:
#include<iostream>
using namespace std;
class A
{
public:
A(int a1)
:_a1(a1)
{
cout << "A(int a1)" << endl;
}
A(const A& a)
{
_a1 = a._a1;
cout << "A(const A& a)" << endl;
}
void Print()
{
cout << _a1 << " " << endl;
}
private:
int _a1 = 1;
};
int main()
{
// 1构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa3
// 编译器遇到连续构造+拷⻉构造->优化为直接构造
A aa1 = 1 ;
aa1.Print();
return 0;
}
我们可以看到,结果只调用了一次构造函数,如果像上面所说的类型转换一样,不应该还要调用拷贝构造函数吗?这其实是编译器的优化,当编译器遇到连续构造+拷⻉构造时就会优化为直接构造。
2.构造函数前⾯加explicit就不再⽀持隐式类型转换
#include<iostream>
using namespace std;
class A
{
public:
// 构造函数explicit就不再⽀持隐式类型转换
// explicit A(int a1)
explicit A(int a1)
:_a1(a1)
{
cout << "A(int a1)" << endl;
}
A(const A& a)
{
_a1 = a._a1;
cout << "A(const A& a)" << endl;
}
void Print()
{
cout << _a1 << " " << endl;
}
private:
int _a1 = 1;
};
int main()
{
// 1构造⼀个A的临时对象,再⽤这个临时对象拷⻉构造aa3
// 编译器遇到连续构造+拷⻉构造->优化为直接构造
A aa1 = 1 ;
aa1.Print();
return 0;
}
我们在构造函数前加上explicit,这时编译器就会报错。
如果我们不想使用隐式类型转换,只需在构造函数前加上explicit。
三、static成员
static成员的特点:
#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;
// 编译报错:error C2248: “A::_scount”: ⽆法访问 private 成员(在“A”类中声明)
//cout << A::_scount << endl;
return 0;
}
这便是静态成员的一种基本的用法。
结果:
四、友元
友元提供了⼀种突破类访问限定符封装的⽅式,友元分为:友元函数和友元类,在函数声明或者类声明的前⾯加friend,并且把友元声明放到⼀个类的⾥⾯。
1.友元函数
#include<iostream>
using namespace std;
class Date
{
//friend void Print(const Date& d);
public:
Date (int year=2024,int month=7,int day=23)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
void Print(const Date& d)
{
cout << "" <<d._year << "-" <<d. _month << "-" << d._day;
}
int main()
{
Date d1;
Print(d1);
return 0;
}
因为成员变量为私有的,类外函数并不能访问类内的私有成员,此时会报错。
我们只要将类外函数声明为友元函数,这时类外函数就能够正常访问类的私有成员。
友元函数的特点:
1.友元声明可以写在类中的任意位置,不受类访问限定符限制。
2.⼀个函数可以是多个类的友元函数。
2.友元类
友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
我们来看下面的代码:
#include<iostream>
using namespace std;
class Time
{
friend class Date;
public:
Time(int hour=8,int minute=0,int second=0)
:_hour(hour)
,_minute(minute)
,_second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date (int year=2024,int month=7,int day=23)
:_year(year)
,_month(month)
,_day(day)
{}
void Print(const Time& t)
{
cout << "" << _year << "-" << _month << "-" << _day << endl;
cout << "" << t._hour << "-" << t._minute << "-" << t._second << endl;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
int main()
{
Date d1;
Time t1;
d1.Print(t1);
return 0;
}
上面的代码将Date类声明为Time类的友元类,所以Date类中的成员函数都是Time类的友元函数,所以在Print函数中可以访问到Time类中的私有成员。
结果:
友元类的特点:
1.友元类的关系是单向的,不具有交换性,⽐如A类是B类的友元,但是B类不是A类的友元。
2.友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是B的友元。
这里要注意的是,虽然友元有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多⽤。
五、内部类
#include<iostream>
using namespace std;
class A
{
public:
class B
{
public:
B(int b=1)
:_b(b)
{}
void Print(const A& a)
{
cout << "" << a._a << endl;
cout << "" << _b << endl;
}
private:
int _b;
};
A(int a=2)
:_a(a)
{}
private:
int _a;
};
int main()
{
A a1;
A::B b1;
b1.Print(a1);
return 0;
}
在上面的代码中我们将类B定义为类A的内部类,因为内部类默认是外部类的友元类,所以类B是类A的友元类,B中的成员函数能够访问类A的私有成员。
结果:
内部类的特点:
1.内部类和其外类是独立存在的,计算外类的大小时,是不包括内部类的大小的。
2.内部类受访问限定符的限定,如果为私有则无法直接被使用。
3.内部类天生就算外类的友元,即内部类可以访问外类的成员,而外类无法访问内部类。
六、匿名对象
#include<iostream>
using namespace std;
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A();
return 0;
}
我们定义了一个匿名对象,通过判断构造函数和析构函数的执行来判断匿名对象的生命周期。
结果:
从图中我们可以看出,执行完构造函数后便执行了析构函数,所以匿名对象的生命周期只在当前一行。
七、对象拷贝时的编译器优化
#include<iostream>
using namespace std;
class A
{
public:
A(int a = 0)
:_a1(a)
{
cout << "A(int a)" << endl;
}
A(const A& aa)
:_a1(aa._a1)
{
cout << "A(const A& aa)" << endl;
}
A& operator=(const A& aa)
{
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa)
{
_a1 = aa._a1;
}
return *this;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a1 = 1;
};
void f1(A aa)
{}
A f2()
{
A aa;
return aa;
}
int main()
{
// 传值传参
A aa1;
f1(aa1);
cout << endl;
// 隐式类型,连续构造+拷⻉构造->优化为直接构造
f1(1);
// ⼀个表达式中,连续构造+拷⻉构造->优化为⼀个构造
f1(A(2));
cout << endl;
return 0;
}
从结果中我们可以看出,在传值传参中会调用拷贝构造函数,而其他两种方法都会存在连续的构造和拷贝构造,所以编译器就将他们优化为一个构造。
八、总结
以上就是我对于类和对象(下)的理解,希望以上所讲能够对你有所帮助,有帮助的话记得一键三连哦,感谢各位。