【C++之类和对象】初始化列表,C++11初始化新玩法,static成员,友元

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

前言

这篇文章我们重点学习类和对象剩下的内容初始化列表C++11新玩法友元static成员。

一、初始化列表

  1. 初始化列表的功能 初始化列表通常都是在构造函数中存在需要注意的是如果我们不写构造函数编译器自动生成的默认构造函数也是具有初始化列表的初始化列表可以理解为是类实例化出的对象中成员变量定义初始化的地方。前面我们学习的构造函数主要是利用构造函数中的函数体对对象中的成员变量进行赋值而不能说是对成员变量进行初始化因为初始化只能初始化一次并且只能在初始化列表中进行赋值是可以进行多次赋值的
class Date
{
public:
	// 构造函数函数体进行赋值
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

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

像上述的代码中一旦对象创建就会自动调用构造函数上面实现的是在构造函数的函数体中对对象中的成员变量进行赋值操作。上面的构造函数中只有函数体其实是隐藏着初始化列表的初始化列表主要是完成对象中成员变量的创建和初始化那么既然我们没有显然写初始化列表其实只是没有对成员变量进行初始化但是还是会进行成员变量的定义的因为只有定义了成员变量对象中对应的成员变量才具有空间
2. 初始化列表的格式
初始化列表以一个冒号开始接着是一个以逗号分隔的数据成员列表每个"成员变量"后面跟一个放在括号中的初始值或表达式。

// 初始化列表进行初始化
	Date(int year = 1,int month = 1,int day = 1)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
  1. 初始化列表的一些注意事项
  • 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  • 类中包含以下成员必须放在初始化列表位置进行初始化
    引用成员变量const成员变量自定义类型成员(该类没有默认构造函数)
  • 尽量使用初始化列表初始化因为不管你是否使用初始化列表对于自定义类型成员变量一定会先使用初始化列表初始化如果没有在初始化列表中显然初始化构造函数中的初始化列表也会自动调用自定义类型的默认构造函数完成该成员的初始化
  • 成员变量在类中声明次序就是其在初始化列表中的初始化顺序与其在初始化列表中的先后次序无关
  • 代码1
class A
{
public:
	A(int a)
		:_a(a)
	{}
private:
	int _a;
};
class B
{
public:
	B(int a, int ref)
		:_aobj(a)
		, _ref(ref)
		, _n(10)
	{}
private:
	A _aobj; // 没有默认构造函数的自定义类型
	int& _ref; // 引用
	const int _n; // const
};

上面代码中出现的成员变量中的引用类型的变量const修饰的变量以及没有默认构造函数的自定义类型成员都必须在初始化列表进行初始化否则就会报错。其他的普通成员变量既可以在初始化列表初始化也可以在构造函数中的函数体进行初始化。

  • 代码2
class Time
{
public:
// 默认构造函数
	Time(int hour = 0)
	:_hour(hour)
	{
	cout << "Time()" << endl;
	}
private:
int _hour;
};
class Date
{
public:
// 初始化列表中会自动调用_t的默认构造函数完成对_t的初始化
	Date(int day)
	{}
private:
	int _day;
	Time _t;
};
int main()
{
	Date d(1);
}

上面代码中有两个类Date和Time类其中Date类中包含一个Time类的成员变量上面的类中Date类中自己实现了一个构造函数所以编译器不会生成默认构造函数Time类中有默认构造函数当我们创建Date类型的对象的时候编译器自动调用Date类的构造函数完成对象的初始化初始化的工作主要是在构造函数中的初始化列表进行的调用Date类的构造函数时首先会走初始化列表先定义_day再定义_t定义_t时会调用_t所在类的默认构造函数完成初始化调用Time类的构造函数时同样先走初始化列表Time类中显示实现了初始化列表对其中的_hour进行初始化。

  • 结果
    上面的代码是可以正常编译通过的
    在这里插入图片描述

但是如果将代码改一下将Time类中构造函数中的参数缺省值去掉这个时候Time类中就没有默认构造函数了此时在Date类中的构造函数初始化列表中想要调用Time类的默认构造函数对Date类中的Time类成员_t进行初始化就会调用失败所以报错Time类没有合适的默认构造函数可用

在这里插入图片描述
出现这种错误的解决方法

  • 在Time类中实现一个默认构造函数
  • 在Time类中没有默认构造函数时Date类中的Time成员变量采用在Date类中构造函数的初始化列表上进行初始化代码如下
class Time
{
public:
	// 构造函数非默认构造函数此时Time类中就没有默认构造函数了
	Time(int hour)
		:_hour(hour)
	{
		cout << "Time()" << endl;
	}
private:
int _hour;
};
class Date
{
public:
	Date(int day)
		:_t(1)
	{}
private:
	int _day;
	Time _t;
};
int main()
{
	Date d(1);
}

总结如果类中有三种成员变量引用类型的成员变量const修饰的成员变量没有默认构造函数的自定义类型的成员变量一定要在初始化列表中进行初始化这三种成员变量其他变量可在初始化列表初始化也可以在函数体中进行初始化。

  • 代码3
class A
{
public:
	// 根据声明顺序可知_a2先声明所以先初始化_a2再初始化_a1
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}
	void Print() 
	{
		cout << _a1 << " " << _a2 << endl;
	}
private:
	// 成员变量的声明顺序决定成员变量在初始化列表中的初始化顺序
	int _a2;
	int _a1;
};
int main()
{
	A aa(1);
	aa.Print();
}

输出结果
在这里插入图片描述

分析
初始化列表中的初始化顺序和成员变量在类中的声明顺序保持一致显然_a2先声明_a1后声明所以在初始化列表先初始化_a2,再初始化_a1,初始化_a2的时候_a1是随机值所以此时会将_a1的随机值赋给_a2,然后初始化_a1的时候就是将形参的值传给_a1所以_a1的值是1。

二、explicit关键字

explicit这个关键字主要是用来修饰类中的构造函数的也别是对于构造函数中只有一个参数的在C++中对于单个参数的构造函数还具有类型转换的作用。隐式类型转换

  • 代码例子
class Stack
{
public:
	Stack(int capacity = 10)
	{
		_a = new int[capacity];
		_top = 0;
		_capacity = capacity;
	}

	void Print()
	{
		cout << "capacity:" << _capacity << endl;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack st1;
	Stack st2(1);
	Stack st3 = 3;// 隐式类型转换
	
	st1.Print();
	st2.Print();
	st3.Print();

	return 0;
}

编译
在这里插入图片描述
运行结果
在这里插入图片描述
对于st1st2我们是能够正常理解但是st3的创建为啥能够进行而不报错呢原因就是C++中单参数的构造函数支持隐式类型转换。其转换的过程首先是使用3去构造一个st类型的对象然后再调用拷贝构造将构造的对象拷贝构造一个新的对象编译器做了优化就直接是使用3构造一个新的对象。

如果我们想阻止以上隐式类型转换过程该怎么办呢
C++提供了一个关键字explicit这个关键字一旦加在类中的单参数的构造函数前面那么这个构造函数就不能支持隐式类型转换了。

  • 修改后代码
class Stack
{
public:
	explicit Stack(int capacity = 10)
	{
		_a = new int[capacity];
		_top = 0;
		_capacity = capacity;
	}

	void Print()
	{
		cout << "capacity:" << _capacity << endl;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack st1;
	Stack st2(1);
	Stack st3 = 3;

	st1.Print();
	st2.Print();
	st3.Print();

	return 0;
}
  • 编译
    在这里插入图片描述

三、static成员

前面我们学习的是类中的普通成员函数和普通成员变量在这里我们将学习类中的static修饰的成员。static修饰的成员包含静态成员变量静态成员变量

1. static成员变量

我们需要知道的是类中的static修饰的成员是不属于类创建的某一个对象的而是属于整个类的这种静态成员变量的作用之一就是能够统计一个类创建出多少个对象或者统计一个类的某一个成员函数被调用了多少次静态的成员变量一定要在类外进行初始化并且只能初始化一次

特性

  1. 静态成员为所有类对象所共享不属于某个具体的实例是属于整个类的
  2. 静态成员变量必须在类外定义定义时不添加static关键字但是需要指明类域
  3. 类静态成员即可用类名::静态成员或者对象.静态成员来访问因为静态成员变量是属于这个类的所有对象的所以也可用用对象来进行访问
  4. 静态成员和类的普通成员一样也有public、protected、private3种访问级别
  • 代码1统计函数调用次数
class A
{
public:
	A()
	{ ++_scount; }
	A(const A& t)
	{ ++_scount; }
	// 静态成员函数
	static int GetACount() 
	{ return _scount; }
private:
// 静态成员变量的声明相当于在普通成员变量的声明前面加上static关键字
	static int _scount;
};
// 静态成员变量的定义不需要在写static但是需要指明是哪个类的
int A::_scount = 0;
void TestA()
{
	cout << A::GetACount() << endl;
	A a1, a2;
	A a3(a1);
	cout << A::GetACount() << endl;
}

int main()
{
	TestA();

	return 0;
}

运行结果
在这里插入图片描述

分析静态成员变量只会初始化一次也就是在第一次使用的时候上述代码中将该静态成员变量初始化为0然后当对象调用构造函数和拷贝构造函数时其值都会进行++因此上述代码统计的是调用构造函数和拷贝构造函数的总次数代码中先创建对象a1和a2然后再使用a1去拷贝构造生成a3所以显然调用构造函数和拷贝构造函数的总次数为3

2. static成员函数

static修饰的成员函数称为静态成员函数静态成员函数和静态成员变量一样都是不属于类创建的某一个特定对象的而是属于整个类的基于此我们可用通过类域来调用静态成员函数也可以通过对象来调用静态成员函数

特性

  1. 静态成员函数中不存在this指针因为静态成员函数不属于某一个对象属于整个类
  2. 静态成员函数既可以通过类域来调用也可以通过对象来调用

实现成静态成员函数的一个好处就是可不需要通过对象来调用当我们没有创建对象的时候我们也可用通过类域来调用这个静态成员函数。上述的GetACount()函数就是一个静态成员函数。

  • 问题1静态成员函数可以调用非静态成员函数吗
    不可以因为我们知道调用某一个非静态成员函数时通常是需要通过某一个对象去调用的静态成员函数不属于某一个对象其函数中不存在this指针因此无法在静态成员函数中调用非静态成员函数

代码演示

class A
{
public:
// 静态成员函数
	static void Func()
	{
	// 在静态成员函数中调用普通成员函数报错
		Print();
	}
// 普通成员函数
	void Print()
	{

	}

private:
	int _a;
};

int main()
{
	A a;

	return 0;
}

编译
在这里插入图片描述

  • 问题2非静态成员函数可以调用类的静态成员函数吗
    可以静态成员函数属于整个类属于类创建的所有对象所以静态成员函数可以通过对象来进行调用在非静态成员函数中存在this指针所以我们可以在非静态成员函数中通过this指针指向的对象来调用静态成员函数

代码演示

class A
{
public:
	static void Func()
	{

	}

	void Print()
	{
		Func();
	}

private:
	int _a;
};

int main()
{

	A a;
	a.Print();
	return 0;
}

编译结果
在这里插入图片描述

四、C++11 的成员初始化新玩法

通过前面的学习我们知道当我们没有自己实现构造函数时编译器会自动生成一个默认构造函数这个默认构造函数的作用对类中的内置类型不做任何处理对自定义类型的成员会去调用这个成员的默认构造函数。显然对于自定义类型的成员就处理对于内置类型的成员就不做处理不合理所以C++提出了一种初始化的新玩法在内置类型声明的时候可以给一个缺省值这个缺省值的作用当构造函数没有显示初始化内置类型的时候就会使用这个缺省值对内置类型进行初始化。
先看一个例子

  • 代码1编译器自动生成的构造函数+内置类型的成员变量不给缺省值
class Date
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

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

int main()
{
	Date d;
	d.Print();

	return 0;
}

运行结果
在这里插入图片描述
听过前面的分析我们知道编译器自动生成的默认构造函数不会对内置类型做任何处理所以结果就是日期中的成员变量都是随机值。

  • 代码2声明内置类型的成员变量时给缺省值
class Date
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

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

int main()
{
	Date d;
	d.Print();

	return 0;
}
  • 运行结果
    在这里插入图片描述
    分析
    显然当我们在成员变量声明的时候给了缺省值构造函数在初始化的时候对于内置类型的成员就会使用这个缺省值对内置类型的成员变量进行初始化。
  • 代码3自己实现全缺省的默认构造函数+成员变量声明时给缺省值
class Date
{
public:

	Date(int year = 2,int month = 2,int day = 2)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

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

int main()
{
	Date d;
	d.Print();

	return 0;
}
  • 运行结果
    在这里插入图片描述
    分析当自己实现全缺省的默认构造函数和成员变量声明时给缺省值时全缺省的默认构造函数优先

  • 代码4自定义类型的成员变量在声明的时候给了缺省值+该类存在默认构造函数

class B
{
public:
	B(int b = 0)
		:_b(b)
	{}
	int _b;
};
class A
{
public:
	void Print()
	{
		cout << a << endl;
		cout << b._b << endl;
		cout << p << endl;
	}
private:
	// 非静态成员变量可以在成员声明时给缺省值。
	int a = 10;
	B b = 20;
	int* p = (int*)malloc(4);
	static int n;
};
int A::n = 10;
int main()
{
	A a;
	a.Print();
	return 0;
}
  • 运行结果
    在这里插入图片描述
  • 变形
class Date
{
public:

	Date(int year = 2)
		:_year(year)
		
	{}
	void Print()
	{
		cout << _year << endl;
	}

	int _year = 1;
	
};

class A
{
public:
	void Print()
	{
		cout << d._year << endl;
	}
private:
	Date d = 2023;
};

int main()
{
	A a;
	a.Print();

	return 0;
}

运行结果
在这里插入图片描述

分析上面的代码中A类中没有实现构造函数所以编译器会自动生成一个默认构造函数A类中存在一个自定义类型的Date类成员并且该成员还给了缺省值Date类成员对应的类中存在一个全缺省的默认构造函数此时A类的做法是使用该成员的缺省值进行初始化。

总结对于C++11给的类中声明内置类型时给的缺省值只有在编译器自动生成的默认构造函数的情况下对于内置类型不处理的现象会使用到自定义类型如果给了缺省值在构造函数中的初始化列表和函数体中都没有初始化也会用到如果类中存在全缺省的默认构造函数或者存在我们自己写的构造函数我们在创建对象的时候是有传参的那么这个时候都不需要使用到成员变量声明时给的缺省值。

五、友元函数和友元类

1. 友元函数

友元函数的关系的单向的使用场景当一个函数想要访问一个类的私有成员时由于被私有访问限定符限制住所以类外函数是访问不了类的私有成员的如果此时硬要访问该类的私有成员可以考虑将该函数设置成该类的友元函数做法在该类中声明该函数前面+friend。然后正常定义该函数即可。前面学习的日期类中的流插入运算符重载函数和流提取运算符重载函数就是设置成了日期类的友元函数。

  • 日期类的流插入函数
class Date
{
public:
	friend ostream& operator<<(ostream& out, const Date& d);
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

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


ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "-" << d._month << "-" << d._day << endl;
		return out;
}


int main()
{
	Date d(2023, 2, 5);
	cout << d << endl;

	return 0;
}

运行结果
在这里插入图片描述
如果我们不将这个函数设置成日期类的友元函数则无法访问日期类中的私有成员。 友元函数可以直接访问类的私有成员它是定义在类外部的普通函数不属于任何类但需要在类的内部声明声明时需要加friend关键字

友元函数的特性

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

2. 友元类

我们可能会遇到这样的场景一个类想要访问另一个类的私有成员但是由于访问限定符的限制访问不了此时就需要友元类出马了。友元类的关系也是单向的比如如果我们将A类设置成B类的友元类则我们可以说A类是B类的友元类反过来则不成立。也就是说A类中可以访问B类的私有成员但是B类不能访问A类的私有成员除非将B类设置成A类的友元类。

  • 代码1A类是B类的友元类
class B
{
	friend class A;
public:
	B(int b = 0)
		:_b(b)
	{}

private:
	int _b;
};

class A
{
public:
	
	A(int a = 0)
		:_a(a)
	{}

	void Print()
	{
		cout << _a << endl;
		cout << _B._b << endl;
	}

private:
	int _a;
	B _B;
};

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

运行结果
在这里插入图片描述

  • 代码2Date类是Time类的友元类
class Date; // 前置声明
class Time
{
	friend class Date; // 声明日期类为时间类的友元类则在日期类中就直接访问Time类中的私有成员变量
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	void SetTimeOfDate(int hour, int minute, int second)
	{
		// 直接访问时间类私有的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}

	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" 
			<< _t._hour << "时" << _t._minute << "分" << _t._second << "秒" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

int main()
{
	Date d(2023, 2, 5);
	d.SetTimeOfDate(13, 14, 50);
	d.Print();

	return 0;
}

运行结果
在这里插入图片描述

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: c++

“【C++之类和对象】初始化列表,C++11初始化新玩法,static成员,友元” 的相关文章