C++类和对象(下篇)

本篇是类和对象最后一篇文章,我们将补充最后的细节。

目录

1.再谈构造函数

2.static成员

     2.1 概念

     2.2特性

3.友元

     3.1友元函数

     3.2友元类

4.内部类

5.构造拷贝构造优化问题 


1.再谈构造函数

构造函数不仅可以构造与初始化对象, 对于单个参数或者除第一个参数无默认值其余均有默认值的构造函 数,还具有类型转换的作用

 上图中,我例举了一个情景,我说d2是隐式类型转化:是先构造一个临时变量再拷贝构造给d2,编译器优化最终和直接调用构造一样。根据我的说法应该会调用拷贝构造函数打印字符串,但是结果上并没有显示,是我错了吗?

这里我们就需要用到:explicit关键字

explicit修饰构造函数,禁止类型转化。

我们说在构造时会先创建一份临时变量,那就有一个问题:临时变量有常性

又回到了权限的问题,常性const不可修改,所以我们在定义引用变量的时候也要加const

补充:匿名对象:生命周期只有一行

2.static成员

     2.1 概念

声明为 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;
};

     2.2特性

1. 静态成员 所有类对象所共享 ,不属于某个具体的对象,存放在静态区
2. 静态成员变量 必须在 类外定义 ,定义时不添加 static 关键字,类中只是声明
3. 类静态成员即可用 类名 :: 静态成员 或者 对象 . 静态成员 来访问
4. 静态成员函数 没有 隐藏的 this 指针 ,不能访问任何非静态成员
5. 静态成员也是类的成员,受 public protected private 访问限定符的限制

3.友元

        友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
        友元分为:友元函数 友元类

     3.1友元函数

流插入(<<)运算符重载和流提取(>>)运算符重载就很好的采用了友元函数。

        我们可以看到流插入在ostream类型里是一个函数,我们只需要传递不同的参数构成函数重载,流提取在istream也是一样的,cout和cin分别是ostream和istream的对象

        所以在重载流运算符时,我们只需要再传递一个cout或者cin,调用类型写好的函数重载就可以了。   

        有一个问题,我们平时写输出cout<<....,cout是左操作数,但是我们在重载<<运算符时,如果把他作为成员函数,那么会使操作数不符,意义不同。

 我们显示的写,发现可以正常运行。

 流插入运算符2个操作数,第一个操作数是左操作数,第二个操作数是右操作数,这里日期类抢占了第一个参数位置,编译器不管,把cout放在右操作数有违底层模板。

        那么我们怎么解决呢?解决方法一定是把cout作第一个参数,那么我们就不能是成员函数,我们把他放在全局,那么如何读类的私有成员呢?这里就需要我们的友元函数。

 友元函数——这个函数内部可以使用Date对象访问私有保护成员

这里还有一个问题,我们没办法进行连续输出,为什么?

因为我们没有返回值,cout连续输出是因为左操作数一直是cout,返回值也是cout。

我们设置返回值为ostream&即可解决。

 最终如下:

1.流提取>>重载,是需要改变对象的值,所以日期对象不加const。

2.频繁调用,使用内联。

说明 :
  • 友元函数可访问类的私有和保护成员,但不是类的成员函数
  • 友元函数不能用const修饰
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数的调用原理相同

     3.2友元类

        友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

  • 友元关系是单向的,不具有交换性。 比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time 类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。
  • 友元关系不能传递 如果BA的友元,CB的友元,则不能说明CA的友元。
  • 友元关系不能继承

4.内部类

概念: 如果一个类定义在另一个类的内部,这个内部类就叫做内部类 。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意:
内部类就是外部类的友元类 ,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
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;
}

5.构造拷贝构造优化问题 

结论:连续一个表达式步骤中,连续构造一般都会优化 —— 合二为 一

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));
}

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值