【C++】类与对象(下)

这篇具有很好参考价值的文章主要介绍了【C++】类与对象(下)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

系列文章

 若想了解什么是类、封装的意义可以移步 【C++】类与对象(引入)

 若对六大成员函数或const成员函数有疑问的这篇文章可能可以帮到你 【C++】类与对象(上)


目录

系列文章

前言

1.初始化列表

1.1概念

1.2特性 

1.2.1必须使用初始化列表的情况

1.2.2初始化的顺序

2.explicit关键字

3.Static成员

3.1静态成员变量

3.2静态成员函数

3.3功能实现

4.友元

4.1友元函数

4.2友元类

5.内部类

6.匿名对象

6.1使用

6.2证明生命周期

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

总结


前言

🧋这一期博客算是给整个类与对象的系列做个收尾,补充类的一些其他功能,还有对类与对象更多的细节理解。

【C++】类与对象(下)


1.初始化列表

🧋虽然说我们已经学会了使用构造函数对函数进行初始化,但下面这个情况是否出乎你的意料呢?const 的成员变量,因其特性一开始我们便需要对其初始化但想在函数之中修改却又无法修改,这是为什么呢?

 【C++】类与对象(下)       【C++】类与对象(下)

 🧋有同学说,在定义变量的时候使用缺省不就好了吗?这个方式确实能够解决问题,但缺省的这个功能是 C++11 才出现的,在那之前难道就无法解决这个问题吗?究其本质,在构造函数体内的这种操作已经算是赋值了真正初始化变量的地方并不在这,而是我们接下来要讲的初始化列表。

【C++】类与对象(下)

1.1概念

🧋初始化列表:以 开头,接以    分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式

🧋位置位于构造函数名与函数内容之间

class A
{
public:
	A()
		:_a(5)    //初始化列表初始化_a和_b
		,_b(3)
	{}
private:
	int _a;
	const int _b;
};

int main()
{
	A a;
	return 0;
}

1.2特性 

🧋每个成员变量只能在初始化列表中出现一次。就像外部定义变量那样,同名的变量也不可以定义两次!

【C++】类与对象(下)

🧋正如上文所说,构造函数的函数体的本质只是给变量赋值,而初始化的这个步骤则交给了初始化列表,因此:

🧋不管是否显式写在初始化列表里,都会在初始化列表里初始化 。

1.2.1必须使用初始化列表的情况

🧋以下三种情况必须使用初始化列表进行初始化:

  • 引用成员变量
  • const成员变量
  • 没有默认构造函数的自定义类型成员 

🧋前面两种成员是因为其本身特性而导致不得不在初始化的时候就得赋值,但第三种不太一样,需要单独拎出来讲一讲。

🧋没有默认的构造函数即指类中至少有一个非全缺省的构造函数,这才满足类中没有默认构造函数的情况。我们知道对于自定义类型,编译器会自动调用目标类的默认构造函数,因此当前情况便会出现没有函数能够调用的情况

【C++】类与对象(下)

🧋使用初始化列表为该类的构造函数传入一个初始值,去调用该类的构造函数,便可完成该成员的初始化。 

class B
{
public:
	B(int b)
	{
		_b = b;
	}
private:
	int _b;
};

class A
{
public:
	A()
		:_a(5)    //初始化列表初始化_a
		,_bb(8)
	{}
private:
	int _a;
	B _bb;
};

int main()
{
	A a;
	return 0;
}

1.2.2初始化的顺序

🧋先看以下代码:

class A
{
public:
	A()
		:a1(2)   
		,a2(a1)
	{}
	void print()
	{
		printf("%d %d", a1, a2);
	}
private:
	int a2;
	int a1;
};

int main()
{
	A a;
	a.print();
	return 0;
}

🧋我们发现最后输出的结果竟然一个是 另一个是随机值。这是因为变量初始化的顺序是根据声明的顺序进行的。由于 a2 先声明,所以先初始化 a2 ,但由于此时 a1 还未被初始化,因此 a2 就被初始化成了随机值。

为了避免编译错误,尽量使用初始化列表初始化

2.explicit关键字

🧋我们都知道定义对象时还有这样的一种写法,并知道他是通过调用构造函数来进行初始化的。但真的是这样吗?其实在这个过程之中会产生一个隐式类型转换,用 来构建一个 的临时变量,之后用这个变量对 进行拷贝构造。之后编译器对这个过程进行了优化,才变成只出现调用一次构造函数。

A a = 1;

🧋如何证明这其中生成了临时变量呢?看下图,为什么使用普通的引用就会报错,而使用常引用没有出现错误了呢?

【C++】类与对象(下) 【C++】类与对象(下)

 🧋总所周知,临时变量具有常性,若用一个常变量给一个普通变量赋值,则会出现权限放大的情况,这时只要定义一个常变量便不会出现权限放大的情况。由此便可以证明我们上面所说的隐式类型转换是存在的

🧋若我们不想要让这种类型转换发生,就需要引入这个关键字 explict 

🧋在构造函数前增加这个关键字,上面的代码便无法通过编译。其针对的不仅仅是单参数的构造函数。

【C++】类与对象(下) 

🧋而多参数的构造函数需要这样使用才能够初始化(需在C++11的环境下) 

class A
{
public:
	A(int a,int b)
		:_a(a)
		,_b(b)
	{}
private:
	int _a;
	int _b;
};

int main()
{
	A a = { 1,1 };
	return 0;
}

🧋且这也同样能用 explicit 禁止类型转换。

【C++】类与对象(下) 

3.Static成员

3.1静态成员变量

🧋用 static 修饰的类成员称为类的静态成员,用 static 修饰的成员变量,被称为静态成员变量

🧋而静态成员变量不属于某个对象,而是属于整个类。因此静态成员变量一定要在类外进行初始化

🧋根据如下代码就完成了对静态成员变量的定义:

class A
{
public:
	A()
	{}
private:
	static int _a;       //声明静态成员变量
};

int A::_a = 5;           //初始化静态成员变量

int main()
{
	A a;
	return 0;
}

3.2静态成员函数

🧋静态成员函数的本质还是静态成员,同样,他也是所有类中共享的,一般用来访问静态成员变量

class A
{
public:
	A()
	{}
	static int get_a()   //静态成员函数
	{
		return _a;       //返回静态成员变量
	}
private:
	static int _a;       //声明静态成员变量
};

int A::_a = 5;           //初始化静态成员变量

int main()
{
	A a;
	cout << a.get_a(); 
	return 0;
}

3.3功能实现

🧋认识了静态成员后,我们就可以根据他们的这个性质实现一个类的小功能。我们能够实现统计当前类的实例化个数

class A
{
public:
	A()
	{
		_cnt++;             //实例化则增加1计数
	}
	A(const A& a)
	{
		_cnt++;             //实例化则增加1计数
	}
	~A()
	{
		_cnt--;             //析构则减少1计数
	}
	static int get_memnum()  //使用静态成员函数获取静态成员变量
	{
		return _cnt;
	}
private:
	static int _cnt;       //声明静态成员变量
};

int A::_cnt = 0;           //初始化静态成员变量为0

int main()
{
	A a;
	cout << a.get_memnum(); 
	return 0;
}

4.友元

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

其中分作友元函数友元类

4.1友元函数

🧋当我们对<<运算符进行重载时,会发现若在类中定义,则第一个操作数便固定为this指针,便与我们平时使用流提取的写法相反。

class A
{
public:
	A(int a,int b)
		:_a(a)
		,_b(b)
	{}

	ostream& operator<<(ostream& _cout)
	{
		_cout << _a << _b << endl;
		return _cout;
	}
private:
	int _a;
	int _b;
};

int main()
{
	A a = { 1,3 };
	a << cout;
	return 0;
}

 🧋因此,这个重载的函数便不能写在类中,但写在类外又访问不到类中的私有成员变量。因此便可以将这个函数定义成友元。就像是一种信任关系,对这个函数去掉访问限定符的限制。这样一来,外部的函数便可以访问到内部的的保护成员和私有成员

🧋使用时需要在类的内部声明,声明时需要加 friend 关键字。

class A
{
public:
	A(int a,int b)
		:_a(a)
		,_b(b)
	{}

	friend ostream& operator<<(ostream& _cout, const A& a);
private:
	int _a;
	int _b;
};


ostream& operator<<(ostream& _cout, const A& a)
{
	_cout << a._a << a._b << endl;
	return _cout;
}

int main()
{
	A a = { 1,3 };
	cout << a;
	return 0;
}

 [注意] 

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

4.2友元类

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

友元关系是单向的,不具有交换性。如下面的代码,在B中声明A为B的友元,在A中能够便能够访问B的私有成员,但B却无法访问A中的私有成员
友元关系不能传递,如果C是B的友元, B是A的友元,但C不是A的友元

友元关系也不能继承

class B
{
public:
	friend class A;    //声明A为B的友元
	B(int b)
		:_b(b)
	{}
private:
	int _b;
};

class A
{
public:
	A(int a)
		:_a(a)
		,b(2)
	{}
	void print()
	{
		cout << b._b << endl;  //在A中就能够访问b中的私有成员
	}
private:
	int _a;
	B b;
};

int main()
{
	A a = 1;
	a.print();
	return 0;
}

5.内部类

🧋如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,不能用外部类的对象去访问内部类的成员。即内部类是外部类的友元类内部类可以访问外部类的所有成员,但外部类却无法访问内部类的私有成员

 [注意] 

内部类外部类类域的限制

内部类可以定义在外部类的任意位置

内部类可以直接访问外部类中的 static 成员,不需要外部类的对象/类名。
sizeof (外部类) = 外部类的大小,和内部类没有任何关系。

class A
{
public:
	A(int a)
		:_a(a)
	{}
	class B                  //B天生是A的友元
	{
	public:
		B(int b)
			:_b(b)
		{}
		void print(const A& a)
		{
			cout << a._a << " " << _b << endl;  //b能够访问到a中的私有成员变量
		}
	private:
		int _b;
	};
private:
	int _a;
};

int main()
{
	A a = 1;
	A::B b = 5;
	b.print(a); 
	return 0;
}

6.匿名对象

6.1使用

当我们在某个时刻想要使用一个类,且仅使用一次以后便不再使用的情况下,可以试试匿名对象。

直接在类名后加上括号便可以使用,其生命周期只在定义的这一行内,这行结束便会自动销毁。

class A
{
public:
	A(int a)
		:_a(a)
	{}
	void print()
	{
		cout << _a << endl;   //输出成员变量的值
	}
private:
	int _a;
};

int main()
{
	A(3).print();    //使用匿名对象
	return 0;
}

6.2证明生命周期

【C++】类与对象(下)

 当我们调试到匿名对象的下一行语句时,该类的构造函数已经被调用了,即此时上一行的匿名对象已经被销毁了

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

🧋新的编译器在传参和传返回值时,会做一些优化,减少对对象的拷贝。当一行中满足以下条件便会进行优化。

构造 + 拷贝构造 -> 优化为直接构造

拷贝构造 + 拷贝构造 -> 1个拷贝构造

构造 + 构造 -> 1个直接构造

🧋由于传引用传参本质就是传一个别名,因此无优化

🧋因为是否优化的判断区间为当前一行,因此正确使用匿名对象有利于编译器的优化

🧋对象返回的总结

接收返回值对象,尽量以拷贝构造的方式接收不要赋值接收

函数中返回对象时,尽量返回匿名对象 

🧋函数传参的总结

尽量使用 const& 传参 。


总结

今天,我们在原来类的基础上补充了一些细节,讲述了初始化列表explicit 关键字,静态变量友元匿名对象,以及编译器在拷贝对象时的优化。每个知识点的内容都比较零散,因此要在自己理解后再用代码进行实现,才能真正的掌握。掌握编译器的规律来优化代码的效率,才能对语言的理解更近一步。

🧋好了,今天类与对象下半部分讲解到这里就结束了,如果这篇文章对你有用的话还请留下你的三连加关注。文章来源地址https://www.toymoban.com/news/detail-412671.html

到了这里,关于【C++】类与对象(下)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】——类与对象(上)

    今天我们来学习C++初期最重要的知识点,类与对象,因为类与对象涉及的知识太多,所以我将分为上中下三个部分来为大家讲解类与对象。 C语言是面向过程的,关注的是过程,分析出求解问题的步骤,通过函数调用逐步解决问题。 C++是基于面向对象的,关注的是对象,将一

    2024年02月02日
    浏览(41)
  • C++:类与对象

    类是对一组具有共同属性特征和行为特征的对象的抽象,它可将相关数据和这些数据的操作(函数)组合在一起。 类定义格式: 类名 : class是声明类的,类名是标识符,且在其作用域内必须是唯一的。 注:C++规定,标识符以字母(大小写均可,但区分大小写)或下划

    2024年02月12日
    浏览(44)
  • [C++] 类与对象(上)

      目录 1、前言 2、类的引入 3、类的定义 3.1 类的两种定义方式 4、类的访问限定符 5、类的作用域 6、类的实例化 7、类对象模型 7.1 内存对齐规则 7.1 类对象的存储方式 8、this指针 8.1 this指针的特性 8.2 this指针是否可以为空 C语言是 面向过程 的, 关注 的是 过程 ,分析出求解

    2024年02月15日
    浏览(37)
  • C++类与对象(下)

    创建对象时,编译器会调用构造函数给对象中的成员变量一个合适的初始值。 虽然上述构造函数调用时,每个成员变量都有一个初始值了,但是这并不能称为类对象成员的初始化, 构造函数体中的语句只能称为赋初值 ,而不能称为初始化,因为 初始化只能初始一次,而构造

    2024年02月11日
    浏览(39)
  • C++ 类与对象(上)

    目录 本节目标  1.面向过程和面向对象初步认识  2.类的引入  3.类的定义 4.类的访问限定符及封装 4.1 访问限定符 4.2 封装 5. 类的作用域 6. 类的实例化  7.类对象模型 7.1 如何计算类对象的大小 7.2 类对象的存储方式猜测 7.3 结构体内存对齐规则  8.this指针 8.1 this指针的引出

    2024年02月19日
    浏览(42)
  • [C++]类与对象下篇

    目录 类与对象下篇::                             1.再谈构造函数                             2.static成员                             3.友元                             4.内部类                             5.匿名对象                             6.拷贝

    2023年04月25日
    浏览(43)
  • C++ 类与对象(下)

    目录 1. 再谈构造函数 1.1 构造函数体赋值  1.2 初始化列表  1.3 explicit  2. static成员 2.1 概念 2.2 特性  3.友元 3.1友元函数   3.2 友元类 4. 内部类 5.匿名对象  6.拷贝对象时的一些编译器优化  7. 再次理解类和对象 1. 再谈构造函数 2. Static成员 3. 友元 4. 内部类 5.匿名对象

    2024年02月19日
    浏览(39)
  • 【C++】——类与对象(下)

    本文是C++类与对象部分最后一篇文章,类与对象的重点是类与对象(中),本文主要是补充类与对象的剩余知识点及收尾。 在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。 构造函数体中的语句只能将其称为赋初值,而不能称作初始化。

    2024年02月06日
    浏览(32)
  • 【C++】类与对象(中)

    目录 1. 类的6个默认成员函数 2. 构造函数 2.1 概念 2.2 特性 3. 析构函数 3.1 概念 3.2 特性 4. 拷贝构造函数 4.1 概念 4.2 特征 5. 赋值运算符重载 5.1 运算符重载 5.2 赋值运算符重载 5.3 前置++和后置++重载 6. const成员 7. 取地址及const取地址操作符重载 如果一个类中什么成员都没有,

    2024年02月05日
    浏览(46)
  • c++类与对象详解

    在C++中,对象是类的实例。定义对象的语法为: 其中,class_name 是定义类时指定的类名,object_name 是对象的名称。 例如,在以下示例中,我们定义了一个名为 Rectangle 的类和两个对象 rect1 和 rect2: 这里我们创建了两个 Rectangle 类的对象:rect1 和 rect2。 在C++中,类可以通过以

    2024年02月13日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包