C++初阶 | [四] 类和对象(下)

摘要:初始化列表,explicit关键字,匿名对象,static成员,友元,内部类,编译器优化

类是对某一类实体(对象)来进行描述的,描述该对象具有哪些属性、哪些方法,描述完成后就形成了一种新的自定义类型,才用该自定义类型就可以实例化具体的对象。


1. 初始化列表

构造函数函数体内是对成员变量进行赋值,而不是初始化!

初始化列表:成员变量定义的地方

为什么规定初始化列表是所有成员变量定义的地方?

对于以下三类特殊的成员变量类型,初始化列表的存在是必要的:

  • const 成员变量—— const 变量必须在定义时初始化
  • 引用 —— 引用必须初始化
  • 自己没有默认构造函数自定义类型变量

warning:Initialization is not assignment. Initialization happens when a variable is given a value when it is created. Assignement obliterates an object's current value and replaces that value with a new one.

 Four different ways  to define an int variable named units_sold and initialize it to 0:

  • int units_sold = 0;
  • int units_sold = {0}; //list initialization (C++11)
  • int units_solc{ 0 }; //list initialization (C++11)
  • int units_sold(0);

——《C++ Primer》

Use

  1. 冒号开始,逗号分隔,初始化值/表达式 用括号括起来。示例如下。
  2. 成员变量走初始化列表的顺序为成员变量声明的顺序.(如下图,先用v2初始化v1,而v2还未初始化所存储的为随机值)
  3. C++规定初始化列表是每个成员变量定义的地方,每个成员变量会走初始化列表(所以成员变量的初始化尽量使用初始化列表),定义的时候不给初始值——即在初始化列表没有显式写的成员变量——未被初始化。
  4. 在初始化列表没写的成员变量,对于内置类型将不做处理——初始化值为随机值(但因编译器不同而异),对于自定义类型默认调用它自己的构造函数

sum.能用初始化列表就用初始化列表,但同时有些场景需要初始化列表和构造函数体混用。(根据具体的需求而异)

补充:C++11 支持在成员变量声明的时候给缺省值,这个缺省值是给初始化列表用的,如果初始化列表没有显式给初始值,就用缺省值作为初始化值。


2. explicit 关键字

  • C++98 支持单参数隐式类型转化

  • C++11 进一步支持多参数隐式类型转化

 在构造函数前加 explicit 使得不能进行隐式类型转换:(示例如下)

class B
{
public:
	explicit B(const int b1, const int b2)
	{
		_b1 = b1;
		_b2 = b2;
	}
	explicit B(const B& b)
	{
		_b1 = b._b1;
		_b2 = b._b2;
	}
private:
	int _b1;
	int _b2;
};

3. 匿名对象

  • 匿名对象:生命周期仅在改匿名对象定义的这一行。e.g.A(7);
  • 有名对象:生命周期在该作用域。e.g.A aa(7);

用途:

  1. 传参(如果单/多参数隐式类型转化被禁)
  2. 定义对象就是为了调用成员函数 → 就可以用匿名对象去调用
  3. ……(匿名对象的用途是十分广泛的)

const引用会延长匿名对象的生命周期

const引用其实使得匿名对象变成了有名对象:引用给了匿名对象别名,这个“别名”的生命周期即为被引用的匿名对象的生命周期。e.g.const A& ref = A();


4. static 成员

如果我们有如下两个需求:

  • 统计累计创建了多少个对象
  • 统计正在使用的对象还有多少个

统计累计创建的对象:首先,我们可以选择创建全局变量来统计。同时,因为创建对象会自动调用构造函数,所以我们在构造函数体内对这个用于统计的变量进行++操作,这样每次创建一个对象,就会调用并执行构造函数,每次执行都会累计对这个全局变量++。

统计正在使用的对象:对象销毁会自动调用析构函数,创建的对象 - 已经析构的对象 = 正在使用的对象。

int e = 0;//统计累计创建的对象
int now_e = 0;//统计仍在使用的对象

class A
{
public:
	A(const int a = 0)
	{
		++e;
		++now_e;
		_a = a;
	}
	~A()
	{
		--now_e;
	}
private:
	int _a;
};

问题:不够封装,全局变量可能会被随意修改,将造成需求之外的影响。 → “封装” → 将变量放入类中  →  私有变量无法访问 → static 成员

static 成员变量

static 成员变量的性质:

  1. 属于所有对象——即整个类,而不是某一个对象。
  2. 不走初始化列表,初始化列表是某个对象的初始化,而 static 成员变量不属于某个对象。
  3. 不能给缺省值。(缺省值是给初始化列表用的,不走初始化列表固然不能给缺省值)
  4. 位于静态区。

static 成员变量的声明与定义:类内声明,类外定义(静态成员变量一定要在类外进行初始化)

class A
{
private:
	static int m;//static成员变量声明
};//这样写A相当于空类

int A::m = 0;//static成员变量定义

访问 static 成员变量的两种情况下的方式:

  • 如果 static 成员变量是 public 的:①创建对象访问;②通过指针访问(这里的指针只是为了突破类域,而不会解引用,之前在类和对象上的this指针关于空指针的内容有详细解释);③直接访问。
    class A
    {
    public:
    	static int m;//static成员变量声明
    };//这样写A相当于空类
    
    int A::m = 0;//static成员变量定义
    
    int main()
    {
    	A aa;
    	aa.m;//①创建对象访问;
    
    	A* p = nullptr;
    	p->m;//②通过指针访问
    
    	A::m = 1;//③直接访问
    	return 0;
    }
    
  • 如果 static 成员变量是 非public 的:提供函数接口访问,这样可以使得 static 成员变量是只读不可修改的(引用返回可修改,这个根据需求自己控制),不同于public的情况。
    class A
    {
    public:
    	int GetM()
    	{
    		return m;
    	}
    private:
    	static int m;//static成员变量声明
    };
    
    int A::m = 0;//static成员变量定义
    
    int main()
    {
    	A aa;
    	cout << aa.GetM() << endl;
    	return 0;
    }

static 成员函数

static 成员函数的性质:

  1. 没有 this 指针,不能访问非静态成员变量
  2. 访问方式同 static 成员变量——突破类域即可访问 

5. 友元

  • 友元函数

特性:

  1. 可以访问类的 private 和protected 的成员,但友元函数不是类的成员函数。
  2. 不能用 const 修饰(因为不是成员函数更没有 this 指针,const 修饰的是 this 指针)。
  3. 可在类定义的任何地方声明,不受访问限定符限制。
  4. 一个函数可以是多个类的友元函数。
  5. 友元函数的调用同普通函数
class Date
{
	
friend ostream& operator<<(ostream& out, Date d);//友元函数声明

private:
	int _year;
	int _month;
	int _day;
};


ostream& operator<<(ostream& out, Date d)//函数定义
{
	out << d._year << "/" << d._month << "/" << d._day << endl;
	return out;
}

  • 友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。(如下代码,B类在A类友元声明之后,B类中的所有成员函数相当于在A类中都进行了友元声明——可以访问A类的非公有成员。注意:A类不可以访问B类的非公有成员。)

友元类的性质:

①单向性(如下,B可以访问A,但A不可以访问B);

②友元关系不传递(B是A的友元,A是C的友元,不能得出B是C的友元);

③不继承。

class A
{
	friend class B;

private:
	int _a;
};



class B
{
	void Print(A _aa)
	{
		cout<<_aa._a;
	}

	//A aa;//注意:这个只是声明,不开空间
};

6. 内部类

内部类:在类中再定义一个类。内部类受外部类的类域和访问限定符限制,此外内部类与外部类是两个独立的类。

使用场景:内部类可以应用于当你想定义一个类而仅在某个类中使用时。

注意:如上图,C不可以访问D的非公有成员, D在C类域之内又封装了一层,并且根据友元类的单向性也是如此。


7. 拷贝对象时编译器的一些优化

(该部分内容仅做了解即可)

  • 一个表达式里,连续的步骤中,连续的构造可能会被合并

  • 即:同类型且同步骤才可能会被优化,不同类型或者分步不能被优化


END

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

畋坪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值