栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C++类和对象(三)

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C++类和对象(三)

文章目录
    • 1.再谈构造函数
      • 1.1构造函数体赋值
      • 1.2初始化列表
    • explicit关键字
    • 2.static成员
      • 2.1概念
      • 2.2特性
    • 3.C++11 的成员初始化新玩法
    • 友元
      • 友元函数
      • 友元类
    • 内部类
      • 内部类概念
      • 内部类特性

1.再谈构造函数 1.1构造函数体赋值

在创建对象时,编译器会通过调用构造函数,给对象中的各个成员变量一个合适的初始值:

class Date
{
public:
	// 构造函数
	Date(int year = 0, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

虽然通过调用上述的构造函数后,对象中的每个成员变量都有了一个初始值,但是构造函数中的语句只能将其称作为赋初值,而不能称作为初始化。因为初始化只能初始化一次,而构造函数体内可以进行多次赋值。
接下来验证一下:

const int _N;//没初始化,报错
const int _N;// 正确

const 修饰的变量必须在定义前初始化。

class Date
{
public:
	//error :左值指定const 对象
	//这里报错说明到构造函数体内时,成员变量已经定义出来了。
	Date(int year = 0, int month = 1, int day = 1)
	{
		_N = 10;//报错
		_year = year;//第一次赋值
		//_year=1000;//第二次赋值
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
	const int _N;
};
1.2初始化列表

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

class Date
{
public:
	// 构造函数
	Date(int year = 0, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
		, _N(10)
	{}
private:
	int _year;
	int _month;
	int _day;
};

注意事项:
一、每个成员变量在初始化列表中只能出现一次
 因为初始化只能进行一次,所以同一个成员变量在初始化列表中不能多次出现。
二、类中包含以下成员,必须放在初始化列表进行初始化:
1、引用成员变量
2、const成员变量
3、自定义类型成员(该类没有默认构造函数)
解析:第3
默认构造分别是无参,全缺省构造和编译器默认生成的构造,编译器会默认调用自定义类型的默认构造函数。如下代码:

class A 
{
public:
	A(int a)
	{
		_a = a;
	}
private:
	int _a;
};
class Date
{
public:
	// 这里报错,编译器找不到_aa的默认构造,
	// 因为 A(int a)不属于默认构造,编译器不会调用
	Date(int year = 0, int month = 1, int day = 1)
		:_N(10)
	 //  ,_aa()  相当于编译器隐士调用了默认构造,所以报错
	// ,_aa(year) 正确初始化
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
	const int _N;
	A _aa;
};

三、尽量使用初始化列表初始化
在某些场景下可以减少一次调用默认构造。如下:

class A
{
public:
	A(int a = 0)
	{
		cout << "A(int a = 0)" << endl;
		_a = a;
	}

	A(const A& aa)
	{
		cout << "A(const A& aa)" << endl;
		_a = aa._a;
	}

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		_a = aa._a;

		return *this;
	}
private:
	int _a;
};

class Date
{
public:
	// 不使用初始化列表
	//Date(int year, int month, int day, const A& aa)
	// ,_aa()   //隐藏的调用默认构造  第二次
	//{
	//	_aa = aa; //调用 A& operator=(const A& aa)
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}
	// 使用初始化列表
	Date(int year, int month, int day, const A& aa)
		:_aa(aa) // 调用 A(const A& aa)
	{
		_year = year;
		_month = month;
		_day = day;
	}

private:
	int _year;  // 声明
	int _month;
	int _day;

	A _aa;
};

int main()
{
	A aa(10);//调用默认构造 A(int a = 0)   第一次
	Date d1(2022, 1, 19, aa); // 对象定义/对象实例化
	//Date d1(2022, 1, 19, A(10)); // 对象定义/对象实例化

	return 0;
}

运行结果对比:
解析:注释详细讲解了,分两种情况比较,使用初始列表和不使用初始化列表

四、成员变量在类中声明的次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后顺序无关

class A
{
public:
	A(int a)
	:_a1(a)
	,_a2(_a1)
{}
	void Print() {
	cout<<_a1<<" "<<_a2<
	A aa(1);
	aa.Print();
}

输出1 随机值

explicit关键字

构造函数不仅可以构造和初始化对象,对于单个参数的构造函数,还支持隐式类型转换。

// 隐式类型转换 - 相近类型 -- 表示意义相似的类型
	double d = 1.1;
	int i = d;
	const int& i = d;
//强制类型转换 - 无关类型
	int* p = &i;
	int j = (int)p;

#include 
using namespace std;
class Date
{
public:
	Date(int year = 0) //单个参数的构造函数
		:_year(year)
	{}
	void Print()
	{
		cout << _year << endl;
	}
private:
	int _year;
};
int main()
{
	Date d1 = 2021; //支持该操作
	d1.Print();
	return 0;
}

在语法上,代码中Date d1 = 2021等价于以下两句代码:

Date tmp(2021); //先构造
Date d1(tmp); //再拷贝构造

本来用2022构造一个临时对象Date(2022),再用这个对象拷贝构造d2,但是C++编译器在连续的一个过程中,多个构造会被优化,合二为一。 所以这里被优化为直接就是一个构造。

若是想禁止单参数构造函数的隐式转换,可以用关键字explicit来修饰构造函数。

explicit Date(int year = 0) //单个参数的构造函数
		:_year(year)
	{}
2.static成员 2.1概念

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的
成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化。
实现一个类,计算中程序中创建出了多少个类对象。

using namespace std;
class A
{
public:
	A(int a = 0)
	{
		_a = a;
		_scount++;
	}
	//静态成员函数,没有this指针
	static int GetStaticCount()
	{
		return _scount;
	}
private:
	int _a;
	static int _scount;//静态成员属于整个类
};
int A::_scount = 0;// 定义初始化
int main()
{
	A a(1);
	cout << a.GetStaticCount() << endl;// 对象访问
	cout << A::GetStaticCount()<< endl;// 类域访问
	return 0;
}
2.2特性
  1. 静态成员为所有类对象所共享,不属于某个具体的实例
  2. 静态成员变量必须在类外定义,定义时不添加static关键字
  3. 类静态成员即可用类名::静态成员或者对象.静态成员来访问
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值

【问题】

  1. 静态成员函数可以调用非静态成员函数吗?

不能,静态成员函数没有this指针,非静态成员函数需要传递this指针。

  1. 非静态成员函数可以调用类的静态成员函数吗?

可以。

3.C++11 的成员初始化新玩法

C++11支持非静态成员变量在声明时进行初始化赋值,但是要注意这里不是初始化,这里是给声明的成员变
量缺省值。

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

// C++11 -- 打补丁
class A
{
public:
	// 如果你再初始化列表阶段,没有对成员变量初始化,它就会使用缺省值初始化
	A()	
	{}
private:           // 要注意的是这里不是初始化,因为这里是声明,不能初始化
	int _a1 = 0;  // 这个里是给成员变量缺省值
	B _bb1 = 10; // 隐式转换
	B _bb2 = B(20);// 等价上条代码
	int* p = (int*)malloc(4*10);
	// vs2019支持
	//int arr[10] = { 1, 2, 3, 4, 5 };

	// 静态的不能这样给缺省值,必须在类外面全局位置定义初始化
	// static int _sCount = 0;
};

int main()
{
	A aa;
	return 0;
}
友元

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

友元函数

问题:现在我们尝试去重载operator<<,然后发现我们没办法将operator<<重载成成员函数。因为cout的
输出流对象和隐含的this指针在抢占第一个参数的位置。this指针默认是第一个参数也就是左操作数了。但是实际使用中cout需要是第一个形参对象,才能正常使用。所以我们要将operator<<重载成全局函数。但是这
样的话,又会导致类外没办法访问成员,那么这里就需要友元来解决。operator>>同理。

class Date
{
public:
Date(int year, int month, int day)
	: _year(year)
	, _month(month)
	, _day(day)
{}
ostream& operator<<(ostream& _cout)
{
	_cout<
	Date d(2017, 12, 24);
	d< 

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字。

class Date
{
friend ostream& operator<<(ostream& _cout, const Date& d);
friend istream& operator>>(istream& _cin, Date& d);
public:
Date(int year, int month, int day)
	: _year(year)
	, _month(month)
	, _day(day)
{}
private:
	int _year;
	int _month;
	int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
	_cout<>(istream& _cin, Date& d)
{
	_cin>>d._year;
	_cin>>d._month;
	_cin>>d._day;
	return _cin;
}
int main()
{
	Date d;
	cin>>d;
	cout< 

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

友元类

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

class A
{
// 在A类中声明B为友元类
	friend class B;
public:
	A(int a=0)
	{
		_a = a;
	}
private:
	int _a;
};
class B
{
public:
	B(int  b=0)
	{
		_b = b;
	}
	void SetAB(int a, int b)
	{
		_b = b;
		_aa._a = a;//不在A类里加友元,无法访问A类对象的私有成员
	}
private:
	int _b;
	A _aa;
};

我的成员函数可以访问友元类的私有成员。
友元类说明:
1、友元关系是单向的,不具有交换性。
比如上述A类和B类,在A类中声明B类为其友元类,那么可以在B类中直接访问A类的私有成员变量,但想在A类中访问B类中私有的成员变量则不行。
2、友元关系不能传递。
 如果A是B的友元,B是C的友元,不能推出A是C的友元。

内部类 内部类概念

概念:如果一个类定义在另一个类的内部,则这个类被称为内部类。

注意:
 1.此时的内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象区调用内部类。
 2、外部类对内部类没有任何优越的访问权限。
 3、内部类就是外部类的友元类,即内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。

内部类特性

1、内部类可以定义在外部类的public、private以及protected这三个区域中的任一区域。
2、内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
3、外部类的大小与内部类的大小无关。

class A
{
private:
	static int k;
	int h;
public:
	// 内部类
	// 1、内部类B和在全局定义是基本一样的,只是他的受外部类A类域限制,定义在A的类域中
	// 2、内部类B天生就是外部类A的友元,也就是B中可以访问A的私有,A不能访问B的私有
	class B
	{
		// friend class A;
	public:
		void foo(const A& a)
		{
			cout << k << endl;//OK
			cout << a.h << endl;//OK
		}

	private:
		int _b;
	};
	
	void f(B bb)
	{
		// A不是B的友元,不能访问B
		bb._b;
	}
};

int A::k = 1;

int main()
{
	cout << sizeof(A) << endl;
	A aa;
	A::B bb;

	return 0;
}

如果内部类与外部类有公有成员,可以选择放在外部类里,这样内部类和外部类都能访问。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1011135.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号