类和对象(四)

构造函数中的初始化列表

之前在实现构造函数时,主要是在函数体内进行赋值,而构造函数还有另一种初始化方式,通过初始化列表进行初始化。

初始化列表的使⽤⽅式是以⼀个冒号开始,接着是⼀个以逗号分隔的数据成员列表,每个"成员变量"后⾯跟⼀个放在括号中的初始值或表达式。每个成员变量在初始化列表中只能出现⼀次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地⽅。

要注意的是,初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的的先后顺序是无关的。但一般建议声明顺序和初始化列表顺序保持⼀致。

cpp 复制代码
#include<iostream>

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{

	}

	void Print()
	{
		std::cout << _year << "-" << _month << "-" << _day << std::endl;
	}

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

int main()
{
	Date d1(2024, 7, 20);
	d1.Print();
	return 0;
}

要区分,函数参数中给的缺省值并不能初始化成员变量。两者是不同的东西,要加一区分。

cpp 复制代码
#include<iostream>

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
	{

	}

	void Print()
	{
		std::cout << _year << "/" << _month << "/" << _day << std::endl;
	}

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

int main()
{
	Date d1(2024, 7, 20);
	d1.Print();
	return 0;
}

这里 _day 并未初始化,为随机值。

cpp 复制代码
#include<iostream>

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
	{
		// _day未在初始化列表中初始化,在函数体内也能初始化,如果都没有就为随机值
		_day = day;
	}

	void Print()
	{
		std::cout << _year << "/" << _month << "/" << _day << std::endl;
	}

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

int main()
{
	Date d1(2024, 7, 20);
	d1.Print();
	return 0;
}

构造函数在初始化时,会先走初始化列表进行初始化,若初始化列表没有初始化,再走构造函数内部看是否初始化,若都没有,就只能要看编译器是否初始化,但一般都为随机值。

一般的内置类型的成员普通的构造函数就可以实现,不需要初始化列表。但有些成员变量只能通过初始化列表进行初始化。

前面说过,初始化列表可以认为是每个成员变量定义初始化的地方,而有些变量只能在定义的时候进行初始化,如:

cpp 复制代码
// const常量只能在定义时初始化一次
const int a = 1;
//引用也只能在初始化时定义
int x = 10;
int& y = x;

还有一个,没有默认构造的类类型变量。如:

该默认构造无需传参,其初始化时若无参数,则会使用缺省值,故可完成初始化。但如果没有缺省值,则程序会编译出错。

cpp 复制代码
#include<iostream>

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

private:
	int _a;
};

class Date
{
public:
	Date(int& xx, int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		,_day(day)
		,a(1)
		,x(xx)
	{
		
	}

	void Print()
	{
		std::cout << _year << "/" << _month << "/" << _day << std::endl;
	}

private:
	int _year;
	int _month;
	int _day;
	const int a;
	int& x;
	A y;
};

int main()
{
	int x = 1;
	Date d1(x, 2024, 7, 20);

	return 0;
}

其编译会报错

这里必须要传入参数,所以我们就需要在初始化列表进行初始化。

cpp 复制代码
#include<iostream>

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

private:
	int _a;
};

class Date
{
public:
	Date(int& xx, int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		,_day(day)
		,a(1)
		,x(xx)
		,y(1)
	{
		
	}

	void Print()
	{
		std::cout << _year << "/" << _month << "/" << _day << std::endl;
	}

private:
	int _year;
	int _month;
	int _day;
	const int a;
	int& x;
	A y;
};

int main()
{
	int x = 1;
	Date d1(x, 2024, 7, 20);

	return 0;
}

此外,C++11⽀持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显示在初始化列表初始化的成员使⽤的。

cpp 复制代码
#include<iostream>

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

private:
	int _a;
};

class Date
{
public:
	Date(int& xx, int year = 1, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		,_day(day)
		,x(xx)
	{
		
	}

	void Print()
	{
		std::cout << _year << "/" << _month << "/" << _day << std::endl;
	}

private:
	int _year;
	int _month;
	int _day;
	const int a = 1;
	int& x;
	A y = 1;
};

int main()
{
	int x = 1;
	Date d1(x, 2024, 7, 20);

	return 0;
}

但尽量使⽤初始化列表初始化,因为那些你不在初始化列表初始化的成员也会⾛初始化列表,如果这 个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。

但如果没有给缺省值,对于没有显示在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显示在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。所以尽量使用初始化列表。


类型转换

举最简单的例子来说:

cpp 复制代码
#include<iostream>

int main()
{
	int a = 10;
	double b = a;
	return 0;
}

这是一个简单的类型转换。

C++⽀持内置类型隐式类型转换为类类型对象,但需要有相关内置类型为参数的构造函数。

cpp 复制代码
#include<iostream>

class A
{
public:
	//构造函数
	A(int a)
	{
		_a = a;
	}
	void Print()
	{
		std::cout << _a  << std::endl;
	}
private:
	int _a;
};

int main()
{
	// 6构造⼀个A的临时对象,再用这个临时对象拷贝构造a1
	// 但若编译器遇到连续构造+拷贝构造就会优化为直接构造
	A a1 = 6;
	a1.Print();

	//这个隐式类型转换过程为:
	
	//构造函数
	A a2(1);
	//拷贝构造,注:该类只有内置类型的成员变量,编译器自动生成的拷贝构造就能实现目的
	A a3 = a2;
	//所以就直接通过隐式类型转换来使用
	
	return 0;
}

在C++11之前,其是不支持多参数转化,一些较老的编译器就不支持多参数转化。

cpp 复制代码
#include<iostream>

class A
{
public:
	//构造函数
	A(int a1 = 1, int a2 = 2)
	{
		_a1 = a1;
		_a2 = a2;
	}

	void Print()
	{
		std::cout << _a1 << " " << _a2 << std::endl;
	}

private:
	int _a1;
	int _a2;
};

int main()
{
	A a = { 6,66 };
	a.Print();

	//相当于:
	A a1(1, 11);
	A a2 = a1;
	a2.Print();
	
	return 0;
}

构造函数前⾯加关键字 explicit 就可以不再⽀持隐式类型转换。


static成员

⽤static修饰的成员变量,称之为静态成员变量,静态成员变量⼀定要在类外进⾏初始化。其为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。

cpp 复制代码
#include<iostream>

class A
{
public:
	
private:
	int a;
	static int b;
};

//在类外初始化静态成员变量
int A::b = 1;

int main()
{
	return 0;
}

而⽤static修饰的成员函数,称之为静态成员函数,静态成员函数是没有this指针的。静态成员函数可以访问其他的静态成员,但是不能访问⾮静态的,因为没有this指针。但⾮静态的成员函数,可以访问任意的静态成员变量和静态成员函数。

cpp 复制代码
#include<iostream>

class A
{
public:
	static int Get()
	{
		return b;
	}

	void func()
	{
		std::cout << a << ' ' << b << std::endl;
	}

private:
	int a = 1;
	static int b;
};

//在类外初始化静态成员变量
int A::b = 1;

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

一定要注意,静态成员变量不能在声明位置给缺省值初始化,因为缺省值是个构造函数初始化列表的,静态成员变量不属于某个对象,不⾛构造函数初始化列表。

由于静态成员也是类的成员,所以其受public、protected、private等访问限定符和类域的限制。

相关推荐
JSU_曾是此间年少8 分钟前
数据结构——线性表与链表
数据结构·c++·算法
此生只爱蛋1 小时前
【手撕排序2】快速排序
c语言·c++·算法·排序算法
何曾参静谧1 小时前
「C/C++」C/C++ 指针篇 之 指针运算
c语言·开发语言·c++
lulu_gh_yu2 小时前
数据结构之排序补充
c语言·开发语言·数据结构·c++·学习·算法·排序算法
ULTRA??3 小时前
C加加中的结构化绑定(解包,折叠展开)
开发语言·c++
凌云行者3 小时前
OpenGL入门005——使用Shader类管理着色器
c++·cmake·opengl
凌云行者3 小时前
OpenGL入门006——着色器在纹理混合中的应用
c++·cmake·opengl
~yY…s<#>4 小时前
【刷题17】最小栈、栈的压入弹出、逆波兰表达式
c语言·数据结构·c++·算法·leetcode
可均可可4 小时前
C++之OpenCV入门到提高004:Mat 对象的使用
c++·opencv·mat·imread·imwrite
白子寰5 小时前
【C++打怪之路Lv14】- “多态“篇
开发语言·c++