C++类与对象(三)再谈构造&&static

目录

1.再谈构造函数

  (1)引入初始化

  (2)初始化列表

  (3)初始化列表性质

  (4)explicit关键字

  (5)总结初始化

2.static成员

  (1)引入

  (2)特性与验证

  (3)总结


1.再谈构造函数

  (1)引入初始化

        在C++类与对象(二)中谈到了构造函数的初级应用,里面介绍了怎样利用构造函数给成员变量赋值。这里会讲解如何初始化成员变量。(赋值和初始化不能混为一谈)

        接下来验证一下,我们之前的构造函数是赋值,而不是初始化。

        代码一:我们都知道,const类型的变量需要初始化,如果在以前的构造函数中对它不能成功进行初始化,说明之前的构造函数是赋值,而不是初始化。

        此代码编译时直接报错,看来这并不是赋值,那么如何对成员变量进行初始化?(本文代码均在win10系统下的vs2019验证)

//代码一
#include "iostream"
using namespace std;

class Test {
public:
	int _a;
	const int _b;
public:
	Test(int a,int b) {
		_a = a;
		_b = 10;
	}
};

int main() {}

  (2)初始化列表

        C++中为了初始化成员变量,引入初始化列表。

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

        代码二:用法举例。使用初始化列表后,const成员初始化成功。

//代码二
#include "iostream"
using namespace std;

class Test {
public:
	int _a;
	const int _b;
public:
	Test(int a)
		:_a(a)
		,_b(10)
	{}
};

int main() {
	Test t1(5);
	cout << t1._b << endl;//输出 10
}

  (3)初始化列表性质

        <1>每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)。一旦出现多次编译器就会报错。

        <2>初始化列表可以初始化引用类型的变量。

        代码三:

//代码三
#include "iostream"
using namespace std;

class Test {
public:
	int _a;
	int& _b;
public:
	Test(int a)
		:_a(a)
		,_b(_a)
	{}
};

int main() {
	Test t1(5);
	cout << t1._b << endl;//输出 10
}

        <3>初始化列表可以初始化自定义类型成员。(该类没有默认构造函数)

        当类类型的成员变量没有默认构造函数时,必须在初始化列表进行初始化。因为当一个类显式实现了构造有参的构造函数(不包括无参构造函数和全缺省构造函数,因为他们也是默认的构造函数),编译器将不会生成默认的构造函数,也就无法调用了。

        如果类类型成员变量有默认的构造函数,那么就算不初始化,编译器也会自动调用它的默认的构造函数。

        代码四:该类类型变量有全缺省构造函数,可以不在初始化列表初始化。

//代码四
#include "iostream"
using namespace std;

class Time {
private:
	int _hour;
	int _minute;
	int _second;
public:
	Time(int a = 10) {
		cout << "Time构造函数被调用" << endl;
	}
};

class Date {
private:
	int _year;
	int _month;
	int _day;

	Time t1;
public:
	Date(int year,int month,int day)
		:_year(year)
		,_month(month)
		,_day(day)
	{}
};

int main() {
	Date d1(1,2,3);
}

        代码五:Time类中有非缺省的带参构造函数。所以Date的构造函数需要对t1对象进行初始化。

//代码五
#include "iostream"
using namespace std;

class Time {
private:
	int _hour;
	int _minute;
	int _second;
public:
	Time(int hour,int minute,int second) 
		:_hour(hour)
		,_minute(minute)
		,_second(second)
	{}
};

class Date {
private:
	int _year;
	int _month;
	int _day;

	Time t1;
public:
	Date(int year,int month,int day)
		:_year(year)
		,_month(month)
		,_day(day)
		,t1(14,59,50)
	{}
};

int main() {
	Date d1(1,2,3);
}

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

        代码六:验证特性<4>。按照初始化列表,使用year初始化_year,day初始化_day,_day初始化_month。那么最终打印结果应该是:_year: 2021 _month: 10_day: 10。

        但实际上_month打印后却是随机数,因为成员变量的初始化顺序是其在类中声明的顺序,与初始化列表的排列顺序无关。_day的初始化应该是在_month的后面,但_month在_day初始化之前就访问_day,这时候_day肯定没有初始化,所以是这样的结果。

//代码六
#include "iostream"
using namespace std;

class Date {
public:
	int _year;
	int _month;
	int _day;
public:
	Date(int year,int day,int month)
		:_year(year)
		, _day(day)
		,_month(_day)
	{}
};

int main() {
	Date d1(2021,10,5);
	cout << "_year: " << d1._year << " _month: " << d1._month << " _day: " << d1._day;
	//输出 _year: 2021 _month: -858993460 _day: 10
}

  (4)explicit关键字

        构造函数不仅可以构造和初始化对象,对于单个参数的构造函数,还具有类型转换的作用。

        先观察代码七,里面有一个很有意思的现象,整形的2022竟然可以为对象赋值。

        代码七:以前只知道内置类型存在隐式类型转换,难道对象也有吗?通过打印地址来进行分析。

//代码七
#include "iostream"
using namespace std;

class Date {
public:
	int _year;
	int _month;
	int _day;
public:
	Date(int year)
		:_year(year)
	{
		cout << "构造函数调用:" << this << endl;
	}

	Date& operator=(const Date& d) {
		_year = d._year;
		_month = d._month;
		_day = d._day;
		cout << "赋值重载调用:" << this << endl;
		cout << "赋值重载调用:" << &d << endl;
		return *this;
	}

	~Date() {
		cout << "析构函数调用:" << this << endl;
	}
};

int main() {
	Date d1(1999);
	cout << "从此处开始观察" << endl;
	cout << "======================" << endl;
	d1 = 2022;
}

        下图是代码七的打印结果,对其进行分析:

        首先打印出来的地址一定是d1对象的,那么另一个自然是赋值重载中的参数对象的地址。并且这个地址也调用过构造函数,所以其实系统是先为2022创建了一个对象,然后调用赋值运算符重载来完成复制,而不是看到的直接赋值。

         那么如何阻止这种情况发生呢?只需要在单参构造函数名前加上explicit关键字,就不会出现这样的情况了。如:

        代码八:

//代码八
explicit Date(int year)
		:_year(year)
	{
		cout << "构造函数调用:" << this << endl;
	}

  (5)总结初始化

        <1>每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)。

        <2>初始化列表可以初始化引用类型的变量。

        <3>初始化列表可以初始化自定义类型成员。(该类没有默认构造函数)

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

2.static成员

  (1)引入

        来想一个问题,如何统计类创建了多少个对象?第一反应是,使用全局变量。

        代码九:通过全局变量来记数。根据打印结果看出,全局变量可行,首先调用两次构造函数,count从1到2,生命周期结束,调用析构函数,cont从2到0。

//代码九
#include "iostream"
using namespace std;

int count = 0;

class Date {
public:
	int _year;
public:
	Date(int year) 
		:_year(year)
	{
		::count++;
		cout << ::count << " ";
	}

	~Date() {
		::count--;
		cout << ::count << " ";
	}
};

void Test() {
	Date d1(1999);
	Date d2(2000);
}

int main() {
	Test();//打印 1 2 1 0
}

        采用全局变量的弊端:全局变量也可以被其他函数使用或修改,不安全。但它给了我们提示:我们需要在类中创建一个可以被该类所有对象所共享的变量来计数,并且这个变量要安全。

这就需要static关键字了。

        在类中被static修饰的成员变量被称为静态成员变量。

  (2)特性与验证

        <1>静态成员变量必须在类外定义,因为在类中只是声明,定义时不添加static关键字。

        代码十:定义与声明方式如下。注意:定义时需要加上 类名::,但不需要加static。

//代码十
#include "iostream"
using namespace std;

int count = 0;

class Date {
public:
	int _year;
	static int count;
public:
	Date(int year) 
		:_year(year)
	{}
};

int Date::count = 0;

int main() {}

        <2>静态成员为所有类对象所共享,是类的属性,不属于某个具体的实例,没有存储在对象中。

        代码十一:通过计算代码十中对象大小验证。d对象大小是4,说明对象中只有_year。

//代码十一
#include "iostream"
using namespace std;


class Date {
public:
	int _year;
	static int count;
public:
	Date(int year) 
		:_year(year)
	{}
};

int Date::count = 0;

int main() {
	Date d(1999);
	cout<<sizeof(d);//输出 4
}

        <3>类静态成员可使用类名::静态成员或者对象.静态成员来访问。

        静态成员函数:用static修饰的类的成员函数。

        为什么要有静态成员函数?原因:为安全起见,我们需要将计数变量 count 设置为private权限,这就导致无法在类外直接访问 count ,所以我们就需要在类中定义 public 方法获取 count 的大小。

        问题来了,既然可以用公有方法获取 count 的值,为什么还需要静态成员函数?原因:普通函数需要用 对象.成员函数 的方式调用。如果没有创建对象,就无法调用方法。那如果既不想创建对象,还想获取 count 怎么办?将获取 count 的函数修饰为静态成员函数,原因就是因为特性<3>,如上文。静态成员可使用类名::静态成员 这样的方式调用而不需要创建对象。

        代码十二:

//代码十二
#include "iostream"
using namespace std;

class Date {
public:
	int _year;
	static int count;
public:
	Date(int year) 
		:_year(year)
	{}

	static void GetPrin() {
		cout << "对象个数:" << count << endl;
	}
};

int Date::count = 0;

int main() {
	Date::GetPrin();//输出 对象个数:0
}

        <4>静态成员函数没有隐藏的this指针,不能访问任何非静态成员。 

        代码十三:在静态成员中试图打印 this 指针时报错。这意味着,静态成员函数中没有隐藏的this指针,在静态成员函数中将无法访问任何非静态的成员。

//代码十三
#include "iostream"
using namespace std;

class Date {
public:
	int _year;
	static int count;
public:
	Date(int year)
		:_year(year)
	{}

	static void GetPrin() {
		cout << this << endl;
		cout << "对象个数:" << count << endl;
	}
};

int Date::count = 0;

int main() {
	Date::GetPrin();
}

         <5>静态成员函数不能设置为 const 成员函数,因为const成员函数本质就是修饰this指针。

        <6>静态成员和类的普通成员一样,也有public、protected、private这三种访问权限,也可以有返回值。

        <7>静态成员变量不能在构造函数的初始化列表中初始化。因为初始化列表只初始化对象中存储的变量,经过上面的验证,静态成员变量并没有在对象中存储,所以不可以。

        <8>静态成员函数不能作为虚函数。(以后会写关于它的)

  (3)总结

        <1>静态成员为所有类对象所共享,是类的属性,不属于某个具体的实例。没有存储在对象中。

        <2>静态成员变量必须在类外定义,定义时不添加static关键字。

        <3>类静态成员即可使用类名::静态成员或者对象.静态成员来访问。

        <4>静态成员函数没有隐藏的this指针,不能访问任何非静态成员。

        <5>静态成员函数不能设置为 const 成员函数,因为const成员函数本质就是修饰this指针。

        <6>静态成员和类的普通成员一样,也有public、protected、private这三种访问权限,也可以有返回值。

        <7>静态成员变量不能在构造函数的初始化列表中初始化。

        <8>静态成员函数不能作为虚函数。

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值