C++类与对象基础(7)

发布时间:2024年01月10日

目录

1. 构造函数补充:

1.1 初始化列表的引入:

1.2 初始化列表的使用方法:

1.3 初始化列表对于自定义类型的初始化:

1.4 缺省值与初始化列表:

1.5 初始化列表的初始化顺序:

2. static成员:

2.1 static成员的引入:

2.2?成员的使用:


1. 构造函数补充:

1.1 初始化列表的引入:

? 在?C++基础(3)——类与对象-CSDN博客中首次引入了构造函数的概念,下面给出一个构造函数:
(注:笔者在下面的代码中将构造函数放到一个命名空间violent中,是因为此构造函数与笔者代码中的文件有了冲突,如果读者需要根据复制下方代码,将命名空间删除即可。)

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

? ? ? ?构造函数的主要作用是用于初始化对象,即给对象中的成员变量一个初始值。但是上述行为并不能称为对象的初始化,而是给对象中的成员变量赋初值。对于成员变量的初始化,需要借助初始化列表来完成:

1.2 初始化列表的使用方法:

? ? ? ? 以冒号开始,逗号用于分隔每一个成员变量,每一个成员变量后面根一个括号,括号里面是初值或者表达式,例如:

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

? ? ? ?为了后续表述,此处,将在构造函数函数体内部进行的操作称为赋初值,将初始化列表部分的操作称之为初始化。

? ? ? 不难发现,对于_year,_month,_day这种内置类型,赋初值或者初始化都可以达成创建构造函数的目的。但是,对于以下三种类型的变量,则必须使用初始化列表进行初始化,而不能赋初值。这三种类型的变量分别是const成员变量、引用成员变量、自定义类型变量(且没有默认构造函数)例如,当在函数体内部来对上述类型赋值想要达到初始化的目的时,即:
?

namespace violent
{
	class Date
	{
	public:
		Date(int year, int month, int day)
			:_year(year)
			,_month(month)
			,_day(day)
		{
			_i = 1;
			j = 1;
		}
	private:
		int _year;
		int _month;
		int _day;
		int& _i;
		const int j;
	};
}

运行代码,此时编译器显示:

? ? ?代码运行显示,这两个类型的变量必须初始化,对于引用,在前面的文章C++基础——对于C语言缺点的补充(2)-CSDN博客?提到了引用的几个特点,其中有一条为:引用必须初始化,存在野指针,但是并不存在野引用。而对于const类型的变量,由于其在后续不能更改的特性,因此创建时必须进行初始化,而在文章开头提到,在构造函数的函数体内部进行的操作并不是真正意义上的初始化,而是赋值,因此,对于上述类型,必须在初始化列表中完成初始化,即:
?

namespace violent
{
	class Date
	{
	public:
		Date(int year, int month, int day)
			:_year(year)
			, _month(month)
			, _day(day)
			, _i(month)
			, j(year)
		{
		}
	private:
		int _year;
		int _month;
		int _day;
		int& _i;
		const int j;
	};
}

? ? ? ? ?对于初始化列表初始化以及构造函数函数体内赋值,这两种方法可以混合使用,例如:

namespace violent
{
	class Date
	{
	public:
		Date(int year, int month, int day)
			: _i(month)
			, j(year)
		{
			_year = year;
			_month = month;
			_day = day;
		}
	private:
		int _year;
		int _month;
		int _day;
		int& _i;
		const int j;
	};
}

? ? ? ?对于上述代码,_year、_month、_day虽然没有在初始化列表中显性定义,但是这些变量同样也会被自动定义,对于内置类型,会给予随机值,而对于自定义类型,则会去调用这个自定义类型的默认构造函数

(注:默认构造函数一般认为有三种:全缺省、不用传递参数、编译器自动生成)

1.3 初始化列表对于自定义类型的初始化:

? ? ? ? 为了方便演示不在初始化列表中显示定义自定义类型的效果,额外创建另一个自定义类型,即:

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

? ? ? ? ? 并对命名空间中的类做下面的修改:

namespace violent
{
	class Date
	{
	public:
		Date(int year, int month, int day)
			: _i(month)
			, j(year)
		{
			_year = year;
			_month = month;
			_day = day;
		}
	private:
		int _year;
		int _month;
		int _day;
		int& _i;
		const int j;
		A _a;
	};
}

? ? ? ? 通过监视窗口监视类中的成员变量,当代码进入构造函数时,即:


? ? ? ?各个变量的值如下:

? ? ? ?不难看到,此时没有在初始化列表中显示定义的内置类型,被编译器自动给予了随机值。

使用Fn+F11?让代码继续调试,当调试到下方图片展示的行数时:

? ? ? ?各个变量的值如下:

? ? ? 继续向下调试,此时代码不是直接向下继续运行,而是转去运营类A中的默认构造函数,即:

? ? ? ?当运行完类A后,会去自动回到类Date继续运行,即:

? ? ? ?此时各个变量的值为:

? ? ? ?通过此处可以看到,虽然初始化列表中并没有显示定义自定义类型_a的初始化,但是此时的自定义类型_a被初始化为0?,证明了对于自定义类型,如果不再初始化列表中显示定义其初始化,则编译器会去自动调用其默认构造函数。

? ? ? 上面的情况中,自定义类型中含有其自己的默认构造函数,当自定义类型中没有默认构造时,即,将类A中的默认构造函数由全缺省改为需要传递参数,即:
?

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

此时如果再运行代码;编译器会显示报错,即:

1.4 缺省值与初始化列表:

? ? ? ?在C++基础(3)——类与对象-CSDN博客中介绍构造函数时提到,对于内置类型,构造函数不作用,对于自定义类型会生成或者调用其自己的构造函数,而为了防止内置类型不能初始化,于是引入了缺省值来解决这个问题。缺省值,与初始化列表由一定的联系,为了解释二者之前的联系,文章首先给出没有缺省值时,构造函数的运行顺序:

1.

2.

3.

4.?

5.

(注:解释此部分与自定义类型的初始化无关,故直接跳过自定义类型A的初始化)

6.?

随后代码继续向下运行,给三个成员变量赋初值。

当给成员变量_year缺省值后,即:


		int _year = 10;

此时再调试代码,顺序如下:
1.

2.?

? ? ? ?此时,代码运行的顺序发生了改变,直接去运行给予缺省值的代码,?并且,此时_year不再是随机值,而是缺省值10,而没有给缺省值的两个成员变量依旧是随机值

此时继续向下运行代码:

代码重新回到没有缺省值运行时的第二步

以上针对一个成员变量给予了缺省值,假如,这个有缺省值的成员变量再初始化列表中显示定义,即:

namespace violent
{
	class Date
	{
	public:
		Date(int year, int month, int day)
			:_year(2)
			, _i(month)
			, j(year)
		{
			_year = year;
			_month = month;
			_day = day;
		}
	private:
		int _year = 10;
		int _month;
		int _day;
		int& _i;
		const int j;
		A _a;
	};
}

?代码的运行顺序如下:
1.

2.?

? ? ? ?此时可以观察到,代码的运行顺序又发生了改变,不运行带有缺省值的代码,而是直接运行初始化列表中显示定义的代码,且本行代码执行完毕后,成员变量_?year的值为:

通过上述例子,可以总结初始化列表与缺省值的关系:

? ? ? 缺省值主要是针对于初始化列表,若不在初始化列表中对内置类型显示定义,则有缺省值的成员变量优先使用缺省值,没有缺省值的变量会被编译器赋随机值。在初始化列表对内置类型显示定义的情况下,则不使用缺省值。

1.5 初始化列表的初始化顺序:

对于下面给出的代码:

namespace violent2
{
	class A
	{
	public:
		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();
	}
}

? ? 需要注意,在初始化列表中,虽然_a1在前,但是初始化列表初始化的顺序是由成员变量的声明的顺序决定的,与初始化列表无关,由此可见,初始化列表初始化的顺序为

, _a2(_a1)
:_a1(a)


2. static成员:

2.1 static成员的引入:

对于下面给出的代码,可以利用全局变量来知道代码在运行过程创建了多少对象:

#include<iostream>

using namespace std;
 
namespace violent
{
	int count = 0;
}
class A
{
public:
	A() { ++violent::count; }
	A(const A& dd) { ++violent::count; }
	~A(){};
	
private:

};

A fun()
{
	A aa;
	return aa;
}
int main()
{
	A a;
	fun();

	cout << violent::count << endl;

	return 0;
}

运行结果如下:

对于上述情况中,全局变量可以达成统计创建对象次数的目的。但是,使用全局变量这种方式仍然存在一定的短板,例如,当代码中不知存在一个类时,例如:
?

#include<iostream>

using namespace std;
 
namespace violent
{
	int count = 0;
}
class A
{
public:
	A() { ++violent::count; }
	A(const A& dd) { ++violent::count; }
	~A(){};
	
private:

};
 
class B
{
public:
	B() { ++violent::count; }
	B(const B& d1) { ++violent::count; }
	~B() { ++violent::count; }

private:
};

A fun()
{
	A aa;
	return aa;
}
int main()
{
	A a;
	fun();
	B b;

	cout << violent::count << endl;
	return 0;
}

此时运行结果为:

? ? ? ? 由于count是全局变量,因此,结果是两个类创建的对象的数量的总和,并不能统计各个类创建的对象数目。对于此问题的解决办法,就是将用于统计创建对象数目的变量作为一个类的成员变量,例如:

class A
{
public:
	A() { ++count; }
	A(const A& dd) { ++count; }
	~A(){};
	
private:
	int count;
};

?但是,对于此办法仍然存在问题,因为此时的成员变量count的统计结果是相互独立的,即:

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

对于创建对象aa和创建对象a时,二者的count结果是相互独立的。而想要统计的count是整个类A在创建对象时count的值,此时,如果对代码做出下列更改:

int main()
{
	A a;
	fun();
;
cout << A::count << endl;
	return 0;
}

编译器会报错,并显示下列内容:

为了解决上述问题,需要引入static成员

2.2?static成员的使用:

具体使用方法如下:

#include<iostream>

using namespace std;
class A
{
public:
	A() { ++count; }
	A(const A& dd) { ++count; }
	~A(){};
	
public:
	static int count;
};
 
int A::count = 0;

A fun()
{
	A aa;
	return aa;
}
int main()
{
	A a;
	fun();
;
cout << A::count << endl;
	return 0;
}

在使用时,需要注意以下几个点:
? ? ? ?1.?static成员需要现在类中进行声明,并且在声明的时候要加上关键字static,即:

static int count;

? ? ? ?在类中声明后,需要在类外进行定义,即:

int A::count = 0;

? ? ? ?2. 对于一个类中的static成员而言,它并不属于一个特定的对象,而是为类的所有对象共享,因此,在对static成员的声明时,不能像声明常规的成员变量一样给缺省值。因此缺省值针对的是构造函数中的初始化列表,而初始化列表是针对对某个对象中的成员。不适用于static成员这种为类所有对象共享的成员。

? ? ? ?对于一个static成员的访问,在类的访问限定符的类型为public的情况下,有两种访问方式,一种针对于类,另一种针对于某个对象,例如:

int main()
{
	A a;
	fun();
    cout << A::count << endl;
	cout << a.count << endl;

	return 0;
}

? ? ? ? ?运行结果为:


? ? ? ?通过打印结果可以看到,不管通过何种方式对static成员进行访问,其打印结果都是一样的。

在类访问限定符为private的情况下,即私有的情况下,访问static成员变量的方法有如下几个:?

? ? ? ? 1. 提供一个供类外访问成员变量的函数,为了方便表述,此处将这个函数命名为Get

using namespace std;

class A
{
public:
	A() { ++count; }
	A(const A& dd) { ++count; }
	~A(){};

	int Get()
	{
		return count;
	}
	
private:
	static int count;
};
 
int A::count = 0;

A fun()
{
	A aa;
	return aa;
}
int main()
{
	A a;
	fun();
	cout << a.Get() << endl;

	return 0;
}

运行结果如下:

? ? ? ?不过,对于此种的访问方法,需要额外创建一个对象。如果,想要只通过类而不通过具体的对象来访问这个成员变量,则可以使用static成员函数来完成,具体操作方法,只需要在类中已有函数的的类型前面加上关键字static即可:

class A
{
public:
	A() { ++count; }
	A(const A& dd) { ++count; }
	~A(){};

	static int Get()
	{
		return count;
	}
	
private:
	static int count;
};
 
int A::count = 0;

A fun()
{
	A aa;
	return aa;
}
int main()
{
	fun();
	cout << A::Get() << endl;
	return 0;
}

? ? ? 对于static成员函数,其最大的特点就是没有隐藏的this指针,而在类中,任何成员变量或者成员函数调用以及访问都是通过this指针达成的,因此,在static成员 函数中,不能访问非static类型的成员变量以及非static类型的成员函数

3. 勘误:

? ? ? ?由于个人能力有限,书中难免出现汉字拼写错误、代码意义解释错误、内容逻辑以及理解错误等不同类型的错误。首先感谢各位大佬能花掉自己宝贵的时间阅读此文章,愿大佬们斧正,发现错误可以通过私信联系,本人不胜感激。

文章来源:https://blog.csdn.net/2301_76836325/article/details/135468887
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。