本章将会对类和对象中用类构造对象过程中出现的特殊情况分析讲解,因为有可能代码在不同的编译器上执行,不同编译器对特殊情况的处理不一样,可能会在不同的平台上的结果都不尽相同,所以本章仅为补充内容,供大家参考。
注: 本篇博文代码运行的环境为VS编译器系列的2019版本。
代码如下:
class A
{
public:
A()
{
cout << "A()" << endl;
}
A(const A& aa)
{
cout << "const A& aa" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
int _b;
};
int main()
{
A aa;
cout << endl;
A();
cout << endl;
return 0;
}
构造函数,拷贝构造,析构函数等都只是打印了一下,方便观察。
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。
class Date
{
public:
Date(int year)
:_year(year)
{}
private:
int _year;
};
int main()
{
Date d1(2022);//构造
//隐式类型的转换
Date d2 = 2022; //构造 + 拷贝构造 -> 优化 合二为一
//中间会生成临时变量
return 0;
}
相当于用2022构造了一个临时对象或者匿名对象(具有常性),再用临时对象拷贝构造一个d2
本来是先构造再拷贝构造:Date(2022) —> Date d2(Date(2022)) ,但是结果是编译器对其进行了优化,直接优化成直接构造
构造 + 拷贝构造 —> 优化 合二为一
如下图所示,两种构造结果是一样的:
标准并没有规定,取决于编译器自身的行为.
单参构造函数,没有使用explicit修饰,具有类型转换作用
explicit修饰构造函数,禁止类型转换—explicit去掉之后,代码可以通过编译
class Date
{
public:
explicit Date(int year, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2022);
d1 = 2023;
return 0;
}
用explicit修饰构造函数,将会禁止构造函数的隐式转换。
下面程序的运行结果是什么:
class Weight
{
public:
Weight()
{
cout << "Weight()" << endl;
}
Weight(const Weight& w)
{
cout << "Weight(cosnt Weight& w)" << endl;
}
~Weight()
{
cout << "~Weight()" << endl;
}
};
Weight f(Weight u)
{
Weight v(u);
Weight w = v;
return w;
}
int main()
{
Weight x;
f(x);
return 0;
}
单纯为了传参,弄一个匿名对象
int main()
{
//单纯为了传参,弄一个匿名对象
f(Weight());
return 0;
}
这时候有两种情况:
知识关联:
和之前的那个隐式类型转换一样:Date d2 = 2022,本来应该是先用2022构造一个匿名对象,再用这个匿名对象去拷贝构造一样,但是最后被编译器优化,直接用2022去构造对象了
下面程序拷贝构造的次数是多少:
class Weight
{
public:
Weight()
{
cout << "Weight()" << endl;
}
Weight(const Weight& w)
{
cout << "Weight(cosnt Weight& w)" << endl;
}
Weight& operator=(const Weight& w)
{
cout << "Weight& operator=(const Weight& w)" << endl;
return *this;
}
~Weight()
{
cout << "~Weight()" << endl;
}
};
Weight f(Weight u)
{
Weight v(u);
Weight w = v;
return w;
}
int main()
{
Weight x;
Weight ret = f(x);
return 0;
}
按照传统,中规中矩的,一步一步来构造拷贝构造,拷贝构造应该如下图所示:
在这里编译器也会有优化:
如下代码就能将优化阻断掉:
int main()
{
Weight x;
Weight ret;
ret = f(x);
return 0;
}
公布答案:
这就是中规中矩的,一步一步构造拷贝构造的来,还调用了赋值重载。
解释如下:
上述红点标出的均为拷贝构造。
《深度探索C++对象模型》中有提到
下面程序拷贝构造的次数是多少:
class Widget
{
public:
Widget()
{
cout << "Widget()" << endl;
}
Widget(const Widget& w)
{
cout << "Widget(cosnt Widget& w)" << endl;
}
Widget& operator=(const Widget& w)
{
cout << "Widget& operator=(const Widget& w)" << endl;
return *this;
}
~Widget()
{
cout << "~Widget()" << endl;
}
};
Widget f(Widget u)
{
Widget v(u);
Widget w = v;
return w;
}
int main()
{
Widget x;
Widget y = f(f(x));
return 0;
}
公布答案:
解释如下:
这就是中规中矩的,一步一步构造拷贝构造的来。
优化:
所以总共调用7次拷贝构造。
在一些情况下面,是需要对类的私有数据,保护数据进行访问的
不提供友元的话,也可以有其他的方式,在类里面写一个函数将想要的值返回的形式带出去(java喜欢这种方式)
C++中提出友元的概念:
前倾回顾,友元函数:
如下代码:
class Date
{
//友元函数 - 我是你的朋友就能访问你的私有
friend ostream& operator<<(ostream& out, const Date& d);
public:
//双操作数的运算符,第一个数是左操作数,第二个数是右操作数
//ostream& operator<<(ostream& out)
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
//通过对象去访问私有
out << d._year << "/" << d._month << "/" << d._day << endl;
return out;
}
int main()
{
Date d;
//d << cout;
cout << d;//这样更性形象
//为了更形象,且保留可读性,就要写成全局的
return 0;
}
详情参考上一篇博客: 传送门
友元类:
//前置声明告诉编译器Date是个类的名字
class Date;
class Time
{
friend void Func(const Date& d, const Time& t);
//友元类
friend class Date;
//声明日期类为时间类的友元类
//则在日期类中就直接访问Time类中的私有成员变量
public:
//构造函数
Time(int hour = 0, int minute = 0, int second = 0)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
//拷贝构造
Time(const Time& T)
{
}
private:
int _hour;
int _minute;
int _second;
};
//友元函数放到类里面的任意位置
//一个函数也可以是多个类的友元
class Date
{
friend void Func(const Date& d, const Time& t);
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)
{
//直接访问时间类私有的成员变量 - 在Time类外面访问不了
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
//谁想访问我,谁就要变成我的友元
void Func(const Date& d, const Time& t)
{
//既要访问Date的私有,又要访问Time的私有
cout << d._year << endl;
cout << t._hour << endl;
}
int main()
{
Date d;
Time t;
return 0;
}
建议:
友元不建议多用,相当于把访问限定开了一个口子,不到万不得已不要用。
概念:
如果一个类定义在另一个类的内部,这个内部类就叫做内部类。 内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:
内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
class A
{
private:
//不算大小
static int k;
int h;
public:
//A类里面没有一个B对象
//内部类
//B天生就是A的友元
class B
{
public:
void foo(const A& a)
{
cout << k << endl;//OK
cout << a.h << endl;//OK
}
private:
int _b;
};
A不是B的友元
//void Print(const B& b)
//{
// b._b = 0;
//}
};
int A::k = 1;
int main()
{
//用B类型定义一个对象 - 去A这个类域里面找B这个类型
//将内部类放在私有里面这个类就是专属的类,在类外面用不了
A::B b;
b.foo(A());
//算这个类型的本质是算,这个类型定义的对象的大小
cout << sizeof(A) << endl;
return 0;
}
公布答案: