C++类与对象(下)

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

0.前言

您好这里是limou3434的博文,感兴趣可以看看我的其他博文系列。

这一部分是对C++类的一些补充:初始化列表、友元、内部类。

1.初始化列表

1.1.函数体内初始化

在给对象进行初始化的时候,如果选择函数体内进行初始化是一件很麻烦的事情。

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Data1
{
public:
	Data1(int a1)//没有提供了默认构造函数
	{
		_a1 = a1;
	}
private:
	int _a1;
};
class Data2
{
public:
	Data2(int a2, int b2, int c2, int a1)
	{
		_a2 = a2;
		_b2 = b2;
		_c2 = c2;
		Data1 D(a1);//这里先去调用了Data的构造函数来形成D对象,然后才来_a2开始进行逐步逐步初始化
		_d2 = D;
	}
private:
	int _a2;
	int _b2;
	int _c2;
	Data1 _d2;
};
int main()
{
	int i = 2;
	Data1 a(i);
	Data2 b(5, 5, 5, 5);
	return 0;
}

但是这样写是没有办法过的,要成功运行的话只能写一个默认构造函数。

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Data1
{
public:
	Data1(int a1 = 0)//提供了默认构造函数
	{
		_a1 = a1;
	}
private:
	int _a1;
};
class Data2
{
public:
	Data2(int a2, int b2, int c2, int a1)
	{
		//函数体内初始化
		_a2 = a2;
		_b2 = b2;
		_c2 = c2;
		Data1 D(a1);
		_d2 = D;
	}
private:
	int _a2;
	int _b2;
	int _c2;
	Data1 _d2;
};
int main()
{
	int i = 2;
	Data1 a(i);
	Data2 b(5, 5, 5, 5);
	return 0;
}

虽然成功进行了初始化,但是这种初始化未免太过恶心和麻烦,因此C++又设计了“初始化列表”这种用法。

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Data1
{
public:
	Data1(int a1)//提供了默认构造函数
	{
		_a1 = a1;
	}
private:
	int _a1;
};
class Data2
{
public:
	Data2(int a2, int b2, int c2, int a1)
	//函数体外初始化
		:_a2(a2)
		,_b2(b2)
		,_c2(c2)
		,_d2(a1)//相当于显示调用上面的构造函数
	{}
private:
	int _a2;
	int _b2;
	int _c2;
	Data1 _d2;
};
int main()
{
	int i = 2;
	Data1 a(i);
	Data2 b(5, 5, 5, 5);
	return 0;
}

1.2.初始化列表初始化

初始化列表的格式:以一个冒号“:”开头,接着以一个逗号分割的数据成员列表,每一个成员变量后面跟着一个放在括号中的初始值或表达式。

在一般情况下,使用“函数体内初始化”和“函数体外初始化(初始化列表初始化)”没有太大区别,但是有一些情况就必须使用初始化列表进行初始化。
初始化列表可以认为是成员变量定义的地方。

  1. 引用成员变量
  2. const成员变量
  3. 自定义类型成员(且该类没有默认构造函数时,因此在使用自定义成员的时候,推荐使用初始化列表)

上面的问题代码实际上就是上面的情况3,下面介绍其他的情况

//const成员变量
//这是因为const必须在定义的地方初始化
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Data1
{
public:
	Data1(int a1 = 0)//提供了默认构造函数
	{
		_a1 = a1;
	}
private:
	int _a1;
};
class Data2
{
public:
	Data2(int a2, int b2, int c2, int a1, int e2)
	{
		_a2 = a2;
		_b2 = b2;
		_c2 = c2;
		Data1 D(a1);
		_d2 = D;
		_e2 = e2;
	}
private:
	int _a2;
	int _b2;
	int _c2;
	Data1 _d2;
	const int _e2;//这里虽然可以定义_e2,但是却不允许后续赋值了,因为已经提前定义了const变量,没有办法赋值了
};
int main()
{
	int i = 2;
	Data1 a(i);
	Data2 b(5, 5, 5, 5, 5);
	return 0;
}

对于const成员变量,就只能改成初始化列表了

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Data1
{
public:
	Data1(int a1 = 0)//提供了默认构造函数
	{
		_a1 = a1;
	}
private:
	int _a1;
};
class Data2
{
public:
	Data2(int a2, int b2, int c2, int a1, int e2)
	//函数体外初始化
		:_a2(a2)
		,_b2(b2)
		,_c2(c2)
		,_d2(a1)//相当于显示调用上面的构造函数
		,_e2(e2)
	{}
private:
	int _a2;
	int _b2;
	int _c2;
	Data1 _d2;
	const int _e2;//这里虽然可以定义_e2,但是却不允许后续赋值了,因为已经提前定义了const变量,没有办法赋值了
};
int main()
{
	int i = 2;
	Data1 a(i);
	Data2 b(5, 5, 5, 5, 5);
	return 0;
}

对于引用成员变量也是需要使用初始化列表的,这是因为引用也必须在定义的时候指定初始化,不可以通过赋值的方式,给一个变量起别名(“int& a; a = b;”这种写法是不被允许的,必须写成“int& a = b”)

#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class Data1
{
public:
	Data1(int a1 = 0)//提供了默认构造函数
	{
		_a1 = a1;
	}
private:
	int _a1;
};
class Data2
{
public:
	Data2(int a2, int b2, int c2, int a1, int e2, int& f2)
	//函数体外初始化
		:_a2(a2)
		,_b2(b2)
		,_c2(c2)
		,_d2(a1)
		,_e2(e2)
		,_f2(f2)
	{
    	_f2++;    
    }
private:
	int _a2;
	int _b2;
	int _c2;
	Data1 _d2;
	const int _e2;
	int& _f2;
};
int main()
{
	int i = 2;
	Data1 a(i);
	int j = 5;
	Data2 b(5, 5, 5, 5, 5, j);
	return 0;
}

其实我们可以发现C++在这块整得还是挺复杂的……

能使用初始化列表就使用初始化列表,肯定比在函数体内使用比较好。当然不能一话说到底,有些时候也是需要在函数体内初始化更好,比如对指针的检查……

 #define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
class A
{
public:
	A(int N)
		:_a((int*)malloc(sizeof(int)* N))
		,_N(N)
	{
		if (_a == NULL)
		{
			perror("malloc fail\n");
		}
		memset(_a, 0, sizeof(int) * N);
	}
private:
	int* _a;
	int _N;
};
int main()
{
	A aa0(100);
	return 0;
}

1.3.初始化列表的初始化顺序

注意初始化列表的初始化顺序问题:初始化列表是按照声明的先后顺序来依次进行初始化的,与初始化列表的顺序无关。

#include <iostream>
using namespace std;
class A
{
public:
	A(int a)
		: _a1(a)//初始化列表是按照声明的先后顺序来依次进行初始化的
		, _a2(_a1)//所以这里的_a2被初始化了随机值
	{}
	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;//只需要调换这里的定义先后顺序即可得到输出为“1 1”的情况
	int _a1;
};
int main()
{
	A aa(1);
	aa.Print();
	return 0;
}
//因此这里输出的应该是“1 随机值”

2.explicit关键字

explicit 关键字用于修饰只有一个参数的构造函数,它的作用是防止编译器进行隐式类型转换。具体来说,当使用 explicit 关键字修饰构造函数时,编译器将不会自动将参数类型转换为类类型,而要求显式地调用该构造函数。使用 explicit 关键字的主要目的是避免因隐式类型转换而引发的潜在问题和歧义,以增强代码的可读性和安全性。

#include <iostream>
using namespace std;
class Data1
{
public:
	Data1(int year)
		:_year(year)
	{
		cout << "Data1(int year)" << endl;
	}
	Data1(const Data1& d)
	{
		cout << "Data1(const Data1& d)" << endl;
	}
private:
	int _year;
};
int main()
{
	Data1 dd1(2021);
	Data1 dd2 = 2022;
	//上述两种写法效果一样,但是过程不一样
	//1.dd1是:“直接调用构造函数”
	//2.dd2是:隐式转化---->“构造函数”+“拷贝构造函数”+编译器优化---->“直接调用构造函数”
	//如果在构造函数前面加上explicit关键字,就会取消这种隐式转化

	//而上面的dd2的创建过程实际上就是一种隐式类型转化,所有的隐式类型转化都会生成临时变量,临时变量具有常属性
	int i = 10;
	//double& d = i;//这样做是不被允许的
	const double& d = i;//这里引用的是“i赋值给d时产生”的临时变量,所以是被允许的

	const Data1& dd3 = 2023;
	return 0;
}调用构造函数” //2.dd2是:隐式转化---->“构造函数”+“拷贝构造函数”+编译器优化---->“直接调用构造函数” //如果在构造函数前面加上explicit关键字,就会取消这种隐式转化 //而上面的dd2的创建过程实际上就是一种隐式类型转化,所有的隐式类型转化都会生成临时变量,临时变量具有常属性 int i = 10; //double& d = i;//这样做是不被允许的 const double& d = i;//这里引用的是“i赋值给d时产生”的临时变量,所以是被允许的 const Data1& dd3 = 2023; return 0; }

3.static成员

声明为static的类成员被称为类的静态成员。

  1. 用static修饰的成员变量,称之为静态成员变量
  2. 用static修饰的成员函数,称之为静态成员函数

静态成员变量一定要在类外进行初始化。
静态成员的特性:

  1. 静态成员变量为所有相同类的对象所共享,不属于某个具体的对象,存放在静态区
  2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中的只是声明
  3. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员(但是非静态成员函数可以访问所有的成员变量,包括静态成员变量)
  4. 类静态成员可用“类名::静态成员”或者“对象.静态成员”来访问
  5. 静态成员也是类的成员,受访问限定符的限制
#include <iostream>
using namespace std;
class A
{
public:
	A() { ++_scount; }
	A(const A& t) { ++_scount; }
	void Print(void)
	{
		cout << _scount << endl;
	}
	static int _scount_x;
private:
	static int _scount;//1.静态成员
};
int A::_scount = 1;//6.所以只能在类外面定义和初始化
int A::_scount_x = 0;

int main()
{
	A a1;
	//2.出现链接错误,因为只有静态成员变量只有声明没有定义,普通成员是跟着类一起定义的
	//3.但是普通成员C++规定最好好需要使用初始化列表初始化
	//4.而静态成员变量是不能使用初始化列表的初始化的
	//5.并且静态成员变量也不能给缺省值,这是因为缺省值给的是初始化列表的,因此还是不能用
	
	//a1._scount;//7.由于受到访问限定符的影响,所以不能直接进行访问

	a1.Print();//8.但是可以通过类来间接使用_scount,也证明了静态成员变量确实是被对象们所共有的
	A a2;
	a2.Print();

	//9.不受访问限定符限制,所以有两种方式访问静态成员变量
	cout << a1._scount_x << endl;
	A::_scount_x++;
	cout << a1._scount_x << endl;
	cout << a2._scount_x << endl;
	return 0;
}

静态的一些使用场景:创建一个只能在栈上开辟的类

#include <iostream>
using namespace std;
class B
{
public:
	static B CreateObj()//3.提供一个接口,来间接使用构造函数
	{
		B b;
		return b;
	}
private:
	B(int x = 0, int y = 0)//2.在构造的时候只能在栈上开辟空间,否则无法使用类,因为这里访问限定符是private
		:_x(x)
		,_y(y)
	{}
private:
	int _x;
	int _y;
};
int main()
{
	//static B data1;//1.无法创建
	B data2 = B::CreateObj();//4.依靠接口间接创建了data2对象,并且保证一定是在栈上开辟
	//5.CreateObj前面之所以加上static
	//是为了避免“先有鸡,还是先有蛋”的问题
	//因为假设没有static,要使用CreateObj()就要先创建一个B类型的对象才能使用
	//但是由于限定符private的原因没有办法直接使用构造函数创建变量
	//使得能够直接使用CreateObj()
	return 0;
}

4.友元

之前在上半篇部分有用过一点友元的知识,本次我将带您真正了解友元的知识。

友元的关键字是“friend”友元提供了一种突破封装的方式,有时候会比较便利,也有的时候会破坏封装,友元是不建议多用的。 友元又分为“友元函数”和“友元类”。

4.1.友元函数

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

1.在C++中,const成员函数的目的是确保在函数内部不会修改对象的状态,并且它们具有一个隐含的this指针类型为const指针。这意味着在const成员函数内部,对象被视为常量,不能通过this指针对其进行修改。然而,在友元函数中,由于友元函数不属于类的成员函数,不存在this指针。因为友元函数不连接到任何特定的对象,无法访问对象的成员变量或成员函数,也无法使用this指针来引用对象。
2.由于没有this指针的存在,我们无法在友元函数中使用const来限定该函数。const关键字用于修饰隐含的this指针,但由于友元函数没有this指针,所以使用const是无效的。
3.因此,因为友元函数不是类的成员函数且没有this指针,我们不能在友元函数中使用const修饰符。友元函数可以自由地修改对象的成员,包括私有成员,而不受const的限制。

#include <iostream>
using namespace std;
class Data
{
	friend void function(void);//1.把function()设置为Data类的友元函数
public:
	Data(int a = 0, int b = 0, int c = 0)
		:_a(a), _b(b), _c(c)
	{
		cout << "Data()" << endl;
	}
private:
	int _a;
	int _b;
	int _c;
};
void function(void)
{
	Data D;
	cout << D._a << " " << D._b << " " << D._b << " " << endl;//2.可以访问Data对象受保护的成员
}
int main()
{
	function();
	return 0;
}

4.2.友元类

  1. 友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员
  2. 友元关系是单向的,不具有交换性
  3. 友元关系不能传递(C是B的友元,B是A的友元,但是C不是A的友元)
  4. 友元关系不能继承,在以后讲解继承的时候在做补充
#include <iostream>
using namespace std;
class Data
{
	friend class FriendData;//1.给Data类设置了友元类FriendData
	friend void function(void);
public:
	Data(int a = 0, int b = 0, int c = 0)
		:_a(a), _b(b), _c(c)
	{
		cout << "Data()" << endl;
	}
	void Print(void)
	{
		cout << _a << " " << _b << " " << _b << " " << endl;
	}
private:
	int _a;
	int _b;
	int _c;
};
class FriendData
{
public:
	void Fun1()
	{
		Data D;
		cout << D._a << endl;//2.可以毫无顾忌访问Data对象中的成员变量
	}
	void Fun2()
	{
		Data D;
		cout << D._a << endl;//3.除了变量还能访问Data对象中的成员函数
		D.Print();
	}
	void Fun3()
	{
		Data D;
		cout << D._a << endl;
	}
};
void function(void)
{
	Data D;
	cout << D._a << " " << D._b << " " << D._b << " " << endl;
	D.Print();
	FriendData FD;
	FD.Fun1();
	FD.Fun2();
}
int main()
{
	function();
	return 0;
}

5.内部类

把一个类定义另外一个类里面,就叫“内部类”,Java会用的比较多,但是C++会比较少。

#include <iostream>
using namespace std;
class Data1
{
public:
	class Data2//2.Data2定义在Data1内部,仅仅只是:
	//2.1.被A的类域限制
	//2.2.Data2天生是Data1的友元类
	{
	public:
		void Function(const Data1& a)
		{
			cout << a._a1 << endl;//3.友元的体现
		}
	private:
		int _a2;//4.需要注意的是A不是B的友元,A无法访问B的成员
		int _b2;
		int _c2;
	};
private:
	int _a1;
	int _b1;
	int _c1;
};
int main()
{
	cout << sizeof(Data1) << endl;
	//1.输出12,说明Data1没有计算Data2大小
	Data1::Data2 D;
	return 0;
}

抛去“类域限制”和“天生友元”,基本外部类和内部类没有太大关系……在地位关系上可以认为是并列的。文章来源地址https://www.toymoban.com/news/detail-512807.html

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

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

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

相关文章

  • 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++】类与对象(引入)   若对六大成员函数或const成员函数有疑问的这篇文章可能可以帮到你  【C++】类与对象(上) 目录 系列文章 前言 1.初始化列表 1.1概念 1.2特性  1.2.1必须使用初始化列表的情况 1.2.2初始化的顺序 2.expli

    2023年04月14日
    浏览(34)
  • c++类与对象详解

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

    2024年02月13日
    浏览(37)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包