C++入门 初始化列表 & 隐式类型转换

目录

初始化列表

构造函数体赋值

初始化列表格式

初始化列表特性

每个成员变量在初始化列表中只能出现一次

类中以下成员必须初始化

尽量使用初始化列表初始化

数组初始化 

声明次序就是初始化顺序

多参数初始化列表

再谈隐式类型转换

拷贝

引用

explicit关键字

定义

用法

缺省值的多种方式

static成员

概念

特性


初始化列表

构造函数体赋值

构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

初始化列表格式

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

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 4)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	void Push(DataType data)
	{
		// CheckCapacity();
		_array[_size] = data;
		_size++;
	}
	// 其他方法...
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;   
};

我们实现用两个栈实现队列该怎么写类呢?

class myqueue
{
public:
    // 初始化列表:本质可以理解为每个对象中成员定义的地方
	myqueue(int n, int& rr)
        // stack不具备默认构造,myqueue也无法生成默认构造
		:_pushst(n)
		,_popst(n)
	{
		_size = 0;
	}
private:
	// 声明
	Stack _pushst;
	Stack _popst;
	int _size;
};

初始化列表特性

每个成员变量在初始化列表中只能出现一次

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

类中以下成员必须初始化

所有的成员,可以在初始化列表初始化,也可以在函数体内初始化(即构造函数),以下三个必须初始化:

1、引用 2、const 3、没有默认构造自定义类型成员(必须显示传参调构造)

class myq
{
public:
	myq(int n, int& rr)
		,_x(1)
		,_ref(rr)    //_ref是rr的别名
	{
		//_x = 1;
	}
private:
	// 必须在定义时初始化
	const int _x = 2;

	// 必须在定义时初始化
	int& _ref;
};

 _x在类成员变量里给了缺省值 2 ,当我们初始化_x时,初始化列表给_x初始化的值优先于类成员变量给的补丁初始化,如果初始化列表没有给值初始化,就调用补丁里的值。

尽量使用初始化列表初始化

记住以下几点:

  1. 初始化列表,不管你写不写,每个成员变量都会先走一遍。
  2. 自定义类型的成员会调用默认构造(没有默认构造就编译报错)
  3. 内置类型有缺省值用缺省值,没有的话,不确定,要看编译器,有的编译器会处理,有的不会处理。
  4. 先走初始化列表 , 再走函数体。
  5. 实践中:尽可能使用初始化列表初始化,不方便再使用函数体初始化。
数组初始化 
class arrayy
{
public:
	arrayy()    //开辟一个存放十个int的空间
		: _arr((int*)malloc(sizeof(int) * 10))
	{
		memset(_arr, 0, sizeof(int)*10);  //10个元素置0
	}
private:
	int* _arr;
};

int main()
{
	arrayy a;
	return 0;
}

声明次序就是初始化顺序

成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后 次序无关。如以下代码所示:

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();

	return 0;
}

//输出1 1  还是输出1 随机值  ?

答案是输出1  随机值,原因:在声明时(private)_a2在_a1前面,初始化列表先给_a2初始化,_a1赋值给_a2,但是_a1是随机值,_a2接收了一个随机值,然后又用1给_a1进行初始化。

多参数初始化列表

class A
{
public:
	//单参数构造函数	
	A(int a)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	//多参数构造函数
	A(int a1, int a2)
		:_a(0)
		,_a1(a1)
		,_a2(a2)
	{}
private:
	int _a;
	int _a1;
	int _a2;
};

int main()
{	
	//A aaa1(1, 2);	  //此处能通过是因为这里是逗号表达式,返回2调用了单参数构造函数	
	
	A aaa2 = { 1, 2 };

	const A& aaa3 = { 1, 2 };  //产生隐式类型转换
    //上面aaa2,aaa3的等号可以省略,但不建议
	return 0;
}

 注意多参数初始化列表调用时使用的是{ }

再谈隐式类型转换

拷贝

先看以下代码:

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

int main() 
{
	A aa1(1);

	// 拷贝构造
	A aa2 = aa1;

	// 隐式类型转换  (内置类型转换为自定义类型)
	A aa3 = 3;

	return 0;
}

aa1拷贝构造给aa2可以理解,为什么3(int类型)可以给aa3(自定义类型)拷贝构造呢?

 由上图可知,编译器会先用3构造一个A类型的临时变量,然后用这个临时变量拷贝构造给aa3,但是深入发现:编译器遇到连续构造+拷贝构造->优化为直接构造,如aa3。

引用

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

int main() 
{
	A& ra = 3;        //报错

	const A& ra = 3;  //通过

	return 0;
}

为什么第一句无法实现,而加上const就可以了呢?下图解释: 

 这里和上面的拷贝构造一样,优化为直接拷贝,注意加上const。

explicit关键字

定义

构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值 的构造函数,还具有类型转换的作用。

 即用explicit修饰构造函数,将会禁止构造函数的隐式转换。

用法

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

int main() 
{
	const A& ra = 3;    //报错

	return 0;
}

这时候3不能构造给临时对象,无法进行隐式类型转换。 

缺省值的多种方式

typedef int DataType;
class Stack
{
public:
	Stack(size_t capacity = 4)
	{
		_array = (DataType*)malloc(sizeof(DataType) * capacity);
		if (NULL == _array)
		{
			perror("malloc申请空间失败!!!");
			return;
		}
		_capacity = capacity;
		_size = 0;
	}
	// 其他方法...
	~Stack()
	{
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	DataType* _array;
	int _capacity;
	int _size;
};

class A
{
public:
	//单参数构造函数	
	A(int a)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	//多参数构造函数
	A(int a1, int a2)	
		:_a(0)		//可以给变量,也可以给常量
		,_a1(a1)	//一般建议给常量
		,_a2(a2)
	{}
private:
	int _a;
	int _a1;
	int _a2;
};

class BB
{
public:
	BB()
	{

	}
private:
	// 声明给缺省值
	int _b1 = 1;
	int* _ptr = (int*)malloc(40);  //开辟一个40字节的空间
	Stack _pushst = 10;    //capacity初始化为10
	A _a1 = 1;		  //单参数构造函数
	A _a2 = { 1,2 };  //多参数构造函数
	A _a3 = _a2;      
};

int main()
{
	BB bb;
	return 0;
}

方式在成员变量那里展示!

static成员

概念

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化

class A
{
public:
	A() //构造函数
	{ 
		++_scount;
	}

	A(const A & t) //拷贝构造函数
	{ 
		//GetCount();

		++_scount;
	}

	~A()//析构函数
	{ 
		//--_scount;
	}
    
	static int _scount;

private:
	// 声明
	int _a1 = 1;
	int _a2 = 1;
};

// 定义
int A::_scount = 0;

为什么_scount在哪里都可以呢?原因是他存放在静态区,不存在对象中,并且不能给缺省值,因为缺省值是给初始化列表,他在静态区不在对象中,不走初始化列表。他属于所有整个类,属于所有对象。

特性

  1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
  2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
  3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问(类静态成员必须在public中)
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制
int main()
{
	A aa1;
	cout << sizeof(aa1) << endl;    //输出8  
    //因为_scount在静态区,不占aa1的空间
    
	//aa1._scount++;
	//cout << A::_scount << endl;
    //以上语句只能静态成员在public中才能实现

	return 0;
}

 那静态成员变量受private限制应该怎么访问呢?以下代码可以实现:

class A
{
public:
	A() //构造函数
	{ 
		++_scount;
	}

	A(const A & t) //拷贝构造函数
	{ 
		//GetCount();

		++_scount;
	}

	~A()//析构函数
	{ 
		//--_scount;
	}

	// 没有this指针,只能访问静态成员
	static int GetCount()
	{
		//_a1 = 1;  //无法访问
		return _scount;
	}
	
private:
	// 声明
	int _a1 = 1;
	int _a2 = 1;

	static int _scount;
};

// 定义
int A::_scount = 0;

这里的静态成员函数没有this指针,只能访问静态成员变量,我们要静态成员变量有什么用呢?

A func()
{
	A aa4;    //构造  //_scount = 4

	//返回值调用了一次拷贝构造,原因是传值调用
    //vs2019会+1,vs2022不会+1
	return aa4;		
}

int main()
{
	A aa1;    //构造  //_scount = 1
	A aa2;    //构造  //_scount = 2
	A aa3(aa1); //拷贝构造  //_scount = 3

	func();  //_scount = 4

	cout << A::GetCount() << endl;  //输出4

	return 0;
}

联系上A类里的成员函数和成员变量,我们通过静态成员变量记录拷贝构造和构造的次数,main函数里共构造了4个变量,所以输出为4。

  • 43
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++ 中的隐式类型转换是指在某些情况下,编译器会自动将一种数据类型转换为另一种数据类型。隐式类型转换可以让代码更简洁,但也容易引起一些问题,例如精度丢失和类型不匹配等问题。以下是一些常见的隐式类型转换: 1. 整数类型的隐式转换 C++ 中的整数类型有很多种,例如 int、long、short、char 等。在进行赋值或运算时,如果两个整数类型不同,编译器会自动将其中一个整数类型转换为另一个整数类型。例如: ```cpp int a = 10; long b = a; // int 转换为 long ``` 2. 浮点数类型的隐式转换 C++ 中的浮点数类型有 float、double 等。在进行赋值或运算时,如果两个浮点数类型不同,编译器会自动将其中一个浮点数类型转换为另一个浮点数类型。例如: ```cpp float a = 1.23; double b = a; // float 转换为 double ``` 3. 数组类型的隐式转换 C++ 中的数组类型可以通过指针隐式转换为另一种数组类型。例如: ```cpp int a[10]; double* b = a; // int* 转换为 double* ``` 4. 类类型的隐式转换 C++ 中的类类型可以定义自己的转换函数和转换构造函数,在一些情况下可以进行隐式类型转换。例如: ```cpp class MyInt { public: MyInt(int n) : m_n(n) {} operator int() { return m_n; } // 转换函数 private: int m_n; }; void foo(int n) { std::cout << n << std::endl; } int main() { MyInt a(10); foo(a); // MyInt 转换为 int return 0; } ``` 在这个示例中,我们定义了一个 MyInt 类,它可以通过转换函数 operator int() 将自己转换为 int 类型。在 main() 函数中,我们将一个 MyInt 对象传给了一个函数,编译器会自动调用 operator int() 进行隐式类型转换。 需要注意的是,隐式类型转换虽然方便,但也容易引起一些问题,因此在实际编程中需要谨慎使用。为了避免出现类型不匹配的问题,可以使用显式类型转换来明确地指定需要转换的类型。例如: ```cpp double a = 1.23; int b = static_cast<int>(a); // 显式将 double 转换为 int ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值