新手c++之类和对象(下)4.16

一.再谈构造函数

1.1构造函数赋值

 在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。

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初始化列表

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

这里我们来思考一个问题 代码如下:

​
​
class stack
{
 stack()
{

}

};
class MYQUEUE
{
public:
	//stack类中如果不具备默认构造,myqueue也无法生成默认构造
	MYQUEUE(int n)
	{

	}
		

private:
	stack _pushst;
	stack _popst;
	int _size;

};

​

​

在这个代码中 myqueue类中的成员变量 有_pushst 和_popst两个变量是通过stack变量来定义的 那么在构造时 但是stack这个类并不具备默认构造函数   这时该如何进行构造_pushst 和_popst?

这时我们唯一可以显示使用就是初始化列表来对_pushst 和_popst进行初始化

class stack
{
public:
	stack(int n)
	{

	}


private:
};
class MYQUEUE
{
public:
	//stack类中如果不具备默认构造,myqueue也无法生成默认构造
	MYQUEUE(int n)
		:_pushst(n)
		,_popst(n)
		,_size(0)
	{

	}
		

private:
	stack _pushst;
	stack _popst;
	int _size;

};

在这个代码中 就使用了初始化列表解决了问题

【注意】

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

2. 类中包含以下成员,必须放在初始化列表位置进行初始化: 引用成员变量

const成员变量 自定义类型成员(且该类没有默认构造函数时)

在这里的初始化列表本质就相当于每个对象中成员定义的地方  而我们的const修饰的常变量是不可改变的 而引用的变量是需要进行初始化的 且const变量只有一次初始化的机会 ,就是在定义的时候,所以const常变量和引用变量初始化都是必须通过初始化列表的

总结所有的成员都可以在初始化列表或者函数体内进行初始化 但有三类 必须在初始化列表中进行初始化  1.const变量 2. 引用变量 3.没有默认构造的自定义类型

class stack
{
public:
	stack(int n)
	{

	}


private:
};
class MYQUEUE
{
public:
	//stack类中如果不具备默认构造,myqueue也无法生成默认构造
	MYQUEUE(int n,int &rr)
		:_pushst(n)
		, _popst(n)
		,_x(0)
		,_ref(rr)
	{
		_size = 0;
	}
		

private:
	stack _pushst;
	stack _popst;
	int _size;
	const int _x;
	int& _ref;
};
int main()
{
	int xx = 0;
	const int y = 1;
	MYQUEUE q1(10,xx);
	MYQUEUE q2=q1;
	int& ry = xx;
	return 0;
}

理解:初始化列表的本质可以理解为每个对象中成员定义的地方

1.3 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使 用初始化列表初始化。

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

1.5 每个初始化列表 不管你走不走 每个成员变量都会先走一遍

自定义类型的成员会调用默认构造 而内置类型会自动调用缺省值 如果没有声明缺省值 则会看编译器 有的编译器就会处理 有的编译器不进行处理        

typedef int datatype;
class Stack
{
public:
	Stack(size_t capacity = 3)
	{
		cout << "Stack()" << endl;
		_array = (datatype*)malloc(sizeof(datatype)*capacity);
		if (_array == 0)
		{
			perror("malloc fail");
			return;
		}
		_capacity = capacity;
		_size = 0;

	}
	void push(datatype data)
	{
		_array[_size] = data;
		_size++;
	}
	~Stack()//析构函数
	{
		cout << "~stack" << endl;
		if (_array)
		{
			free(_array);
			_array = NULL;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	datatype* _array;
	int _capacity;
	int _size;
};

class MYQUEUE
{
public:
	//stack类中如果不具备默认构造,myqueue也无法生成默认构造
	MYQUEUE()
	{
		
	}
		

private:
	Stack _pushst;
	Stack _popst;
	int _size = 0;

};
int main()
{
	MYQUEUE q;
	return 0;
}

如下图 如果我们只写了一个_size的初始化 那么这时就会走初始化列表 而不在去访问缺省值

且在初始化列表这一块是比较自由的 可以写n+4 或者malloc之类的初始化 且顺序是先初始化列表再走函数体 

在实践中 一般是先走初始化列表 当走初始化列表感到麻烦时再走函数体

这时为了将_ptr中的空间都初始化为1 走函数体使用memset是更加方便的

class MYQUEUE
{
public:
	//stack类中如果不具备默认构造,myqueue也无法生成默认构造
	MYQUEUE()
		:_size(1)
		,_ptr((int*)malloc(40))
	{
		memset(_ptr,1,40);
	}
		

private:
	/*Stack _pushst;
	Stack _popst;*/
	int _size = 0; 
	int* _ptr;
};
int main()
{
	MYQUEUE q;
	return 0;
}

这道题是选择D的 初始化列表的顺序并不代表初始化的先后顺序 代表初始化先后顺序的是声明时的顺序 在这段代码中 先声明_a2后声明_a1 所以先初始_a2为随机值 之后初始化_a1为1

在实践中 尽量保持声明顺序和初始化列表顺序保持一致

2 隐式类型转化

class A
{
public:
	A(int a)
		:_a(a)
	{}
private:
	int _a;
};
int main()
{
	A a1(1);//构造

	A a2 = a1;//拷贝构造

	A a3 = 3;//隐式类型转化 内置类型转换成自定义类型

	const A& a33 = a3;//转化是会产生临时变量 具有常性 需要用const 修饰 避免权限的扩大

	const A& a4 = 3;//这里能够通过是 通过构造函数构造了3的临时变量 然后a4是给3的临时变量取别名
	return 0;
}

 且在创建a3中的过程中  3首先通过构造函数将自己进行转换为相关结构临时变量  之后通过拷贝构造将临时变量赋值给a3   这其中经历两个过程 但是编译器会进行优化为构造 也就是 构造加拷贝构造会直接优化为构造

class A
{
public:
	A(int a)
		:_a(a)
	{}
private:
	int _a;
};
class stack
{
public:
	void push(const A & aa)
	{

	}
};
int main()
{
	stack st;
	A a1(2);
	st.push(a1);

	A a2(4);
	st.push(a2);
	//
	st.push(2);
	
	st.push(4);
	return 0;
}

应用类型转化可以更快更方便的完成操作

3 explicit关键字

 3.1 单参构造函数,没有使用explicit修饰,具有类型转换作用

 explicit修饰构造函数,禁止类型转换---explicit去掉之后,代码可以通过编译

3.2多参数构造函数 

在正常情况下aa2是无法构造的 这种形式不允许。

但如果存在单构造函数是可以运行的 原因就是aa2去调用了单构造函数。且会将aa2中的成员a赋值为3。

解决方式是可以用花括号写。 这里也是构造产生临时变量 拷贝构造给 aa2

这里是通过构造生成 临时变量 之后在用aa3来引用临时变量来取别名

class A
{
public:
	//A(int a)//单参构造函数
	//	:_a(a)
	//	
	//{}

	A(int a,int a1,int a2)//多参构造函数
		:_a(a)
		,_a2(a1)
		,_a3(a2)
	{}
private:
	int _a;
	int _a2;
	int _a3;
};
class stack
{
public:
	void push(const A & aa)
	{
	}
};
int main()
{
	stack st;
	A aa1 = { 1,2,3 };
	st.push(aa1);

	st.push({ 1,2,3 });//这里没有先调用push 而是先调用构造函数去创建临时变量 之后在调用push函数
	return 0;
}

所以多参数构造函数也支持隐式类型转化 通过花括号实现 用explicit修饰构造函数,将会禁止构造函数的隐式转换。

这里缺省值不仅可以给整数 还可以直接malloc空间 以及给自定义类型缺省值  这样声明即使是自动生成的默认初始化列表也可以进行初始化  在调试时 会从声明中一个个过 但实际上还是从初始胡列表中一个个过 这是因为从声明上经过更好展示调试过程

4. static成员

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

 正常的成员函数是可以调用静态成员的  因为静态成员声明和定义分离 即使 声明在私有区也可以正常使用  如果只有声明没有定义是会报错的

在静态成员公有的情况下 ,可以通过上图中进行访问静态成员

应用 可以用于统计先情况有多少个对象存活

class A

{

public:
	A() { ++_scount; }
	A(const A& t) { ++_scount; }
	~A() {  }
	static int GetACount() { return _scount; }

private:
	static int _scount;
};


int A::_scount = 0;


void TestA()
{
	cout << A::GetACount() << endl;
	A a1, a2;
	A a3(a1);
	cout << A::GetACount() << endl;
}
A FUNC()
{
	A aa1;
	return aa1;
}
int main()
{
	TestA();
	FUNC();
	cout << A::GetACount();
	return 0;
}

我们上面所讲的内容 都是建立在静态成员时公有的情况下 如果静态成员时私有的 这时候就无法访问了 这时我们提供了静态成员函数 来进行访问静态成员变量 

静态成员函数没有this指针是无法访问成员变量的 只能访问静态成员变量 

因为我们的的成员变量都是通过this指针去进行访问的

如何去调用静态成员函数

直接进行调用

 特性

1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区

2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明

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

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

5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制

5. 友元

友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多 用。 友元分为:友元函数和友元类

友元函数

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声 明,声明时需要加friend关键字。

说明:

友元函数可访问类的私有和保护成员,但不是类的成员函数 友元函数不能用const修饰

友元函数可以在类定义的任何地方声明,不受类访问限定符限制

一个函数可以是多个类的友元函数

友元函数的调用与普通函数的调用原理相同

友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。 友元关系是单向的,不具有交换性。 比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time

类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。

这时data就可以直接访问time 但是time能否访问data就不能确定了 

就好比我喜欢你 我把你当做朋友可以随时随地的来访问我  而你确不一定喜欢我 不一定把我当做你的朋友  不能认为你对我好 那么我就该理所当然的对你好 

友元关系不能传递 如果B是A的友元,C是B的友元,则不能说明C时A的友元。

友元关系不能继承。

总结 友元不要多用 在一定程度上破坏了封装

内部类

概念:如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外 部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。(两者是类似于平行的关系) 注意:内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中 的所有成员。但是外部类不是内部类的友元。 特性:

1. 内部类可以定义在外部类的public、protected、private都是可以的。

2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。

3. sizeof(外部类)=外部类,和内部类没有任何关系。

  • 15
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值