本篇是类和对象最后一篇文章,我们将补充最后的细节。
目录
1.再谈构造函数
2.static成员
2.1 概念
2.2特性
3.友元
3.1友元函数
3.2友元类
4.内部类
5.构造拷贝构造优化问题
构造函数不仅可以构造与初始化对象, 对于单个参数或者除第一个参数无默认值其余均有默认值的构造函 数,还具有类型转换的作用 。
上图中,我例举了一个情景,我说d2是隐式类型转化:是先构造一个临时变量再拷贝构造给d2,编译器优化最终和直接调用构造一样。根据我的说法应该会调用拷贝构造函数打印字符串,但是结果上并没有显示,是我错了吗?
这里我们就需要用到:explicit关键字
explicit修饰构造函数,禁止类型转化。
我们说在构造时会先创建一份临时变量,那就有一个问题:临时变量有常性
又回到了权限的问题,常性const不可修改,所以我们在定义引用变量的时候也要加const
补充:匿名对象:生命周期只有一行
声明为 static 的类成员 称为 类的静态成员 ,用 static 修饰的 成员变量 ,称之为 静态成员变量 ;用 static 修饰 的 成员函数 ,称之为 静态成员函数 。 静态成员变量一定要在类外进行初始化 。
面试题:实现一个类,计算程序中创建出了多少个类对象。
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;
void TestA()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
}
静态成员函数——没有this指针
class A
{
public:
A() { ++_scount; }
A(const A& t) { ++_scount; }
~A() { --_scount; }
static int GetACount() { return _scount; }
static int Get_a()
{
_a = 1;//报错,没有this指针,无法调用成员变量_a
}
private:
static int _scount;
int _a;
};
1. 静态成员 为 所有类对象所共享 ,不属于某个具体的对象,存放在静态区2. 静态成员变量 必须在 类外定义 ,定义时不添加 static 关键字,类中只是声明3. 类静态成员即可用 类名 :: 静态成员 或者 对象 . 静态成员 来访问4. 静态成员函数 没有 隐藏的 this 指针 ,不能访问任何非静态成员5. 静态成员也是类的成员,受 public 、 protected 、 private 访问限定符的限制
流插入(<<)运算符重载和流提取(>>)运算符重载就很好的采用了友元函数。
我们可以看到流插入在ostream类型里是一个函数,我们只需要传递不同的参数构成函数重载,流提取在istream也是一样的,cout和cin分别是ostream和istream的对象。
所以在重载流运算符时,我们只需要再传递一个cout或者cin,调用类型写好的函数重载就可以了。
有一个问题,我们平时写输出cout<<....,cout是左操作数,但是我们在重载<<运算符时,如果把他作为成员函数,那么会使操作数不符,意义不同。
我们显示的写,发现可以正常运行。
流插入运算符2个操作数,第一个操作数是左操作数,第二个操作数是右操作数,这里日期类抢占了第一个参数位置,编译器不管,把cout放在右操作数有违底层模板。
那么我们怎么解决呢?解决方法一定是把cout作第一个参数,那么我们就不能是成员函数,我们把他放在全局,那么如何读类的私有成员呢?这里就需要我们的友元函数。
友元函数——这个函数内部可以使用Date对象访问私有保护成员
这里还有一个问题,我们没办法进行连续输出,为什么?
因为我们没有返回值,cout连续输出是因为左操作数一直是cout,返回值也是cout。
我们设置返回值为ostream&即可解决。
1.流提取>>重载,是需要改变对象的值,所以日期对象不加const。
2.频繁调用,使用内联。
说明 :
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
- 友元关系是单向的,不具有交换性。 比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time 类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
- 友元关系不能传递 。如果B是A的友元,C是B的友元,则不能说明C时A的友元。
- 友元关系不能继承
概念: 如果一个类定义在另一个类的内部,这个内部类就叫做内部类 。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:内部类就是外部类的友元类 ,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:1. 内部类可以定义在外部类的 public 、 protected 、 private 都是可以的。2. 注意内部类可以直接访问外部类中的 static 成员,不需要外部类的对象 / 类名。3. sizeof( 外部类 )= 外部类,和内部类没有任何关系。
// 内部类
class A
{
private:
int _h;
static int k;
public:
// B定义在A的里面
// 1、受A的类域限制,访问限定符
// 2、B天生是A的友元
class B
{
public:
void foo(const A& a)
{
cout << k << endl;//OK——直接可以访问A的static成员
cout << a._h << endl;//OK -- 友元
}
private:
int _b;
};
};
int A::k = 1;
int main()
{
cout << sizeof(A) << endl; // 4——大小与内部类没关系,为sizeof(外部类)
A a;
A::B b; //受A的类域限制
return 0;
}
结论:连续一个表达式步骤中,连续构造一般都会优化 —— 合二为 一
class W
{
public:
W(int x = 0)
{
cout << "W()" << endl;
}
W(const W& w)
{
cout << "W(const W& w)" << endl;
}
W& operator=(const W& w)
{
cout << "W& operator=(const W& w)" << endl;
return *this;
}
~W()
{
cout << "~W()" << endl;
}
};
void f1(W w)
{
}
void f2(const W& w)
{
}
int main()
{
W w1;
f1(w1);
f2(w1);
cout << endl << endl;
f1(W()); // 本来构造+拷贝构造--编译器的优化--直接构造
// 结论:连续一个表达式步骤中,连续构造一般都会优化 -- 合二为一
W w2 = 1;
return 0;
}
f1(W()):先构造匿名对象再传值拷贝构造,连续构造优化为1次构造。
W w2=1:先构造临时变量再拷贝构造给w2,连续构造优化为1次构造。
W f3()
{
W ret;
return ret;
}
// 《深度探索C++对象模型》
int main()
{
f3(); // 1次构造 1次拷贝
cout << endl << endl;
W w1 = f3(); // 本来:1次构造 2次拷贝 -- 优化:1次构造 1次拷贝
cout << endl << endl;
W w2;
w2 = f3(); // 本来:1次构造 1次拷贝 1次赋值
return 0;
}
在栈帧中值返回会构建临时变量,其位置是在该栈帧与上一个栈帧之间,实际上更靠近上一个栈帧,可以把上一个栈帧中的w1对象直接作为临时变量接收返回值,优化省去了拷贝构造临时变量的过程。
本来是构造ret,返回拷贝构造临时变量,再拷贝构造w1。
优化后变为构造ret,拷贝构造w1。
让我们来用刚刚学到的知识解决下题:
360公司笔试题:以下代码共调用多少次拷贝构造函数:_360笔试题_牛客网 (nowcoder.com)
//360笔试题
Widget f(Widget u) {
Widget v(u);
Widget w = v;
return w;
}
main() {
Widget x;
Widget y = f(f(x));
}