初始化列表、static成员和友元函数

1.初始化列表

构造函数体中的语句只能将其称为赋初值 ,而不能称作初始化。因为 初始化只能初始化一次,而构造函数体 内可以多次赋值 。下面来介绍初始化列表:
初始化列表:以一个 冒号开始 ,接着是一个以 逗号分隔的数据成员列表 ,每个 " 成员变量 " 后面跟一个 放在括 号中的初始值或表达式。
#include<iostream>
using namespace std;
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:
	// Stack不具备默认构造。MyQueue也无法生成默认构造
	// 初始化列表   初始化列表用来解决Stack不具备默认构造的问题
	// 初始化列表本质可以理解为每个对象中成员定义的地方,哪个对象初始化就是哪个对象成员定义的地方
	// 所有的成员,你可以在初始化列表初始化,也可以在函数体内初始化
	// 1、引用 2、const 3、没有默认构造自定义类型成员(必须显示传参调构造)  必须在初始化列表初始化
	MyQueue(int n, int& rr)
		:_pushst(n)   //这里是调用有参构造
		,_popst(n)
		,_x(1)
		,_ref(rr)
	{
		_size = 0;//初始化列表和函数体内初始化可以混着用
		//_x = 1; //函数体内只能赋值  const修饰的_x只能在初始化列表初始化
	}

private:
	// 声明
	Stack _pushst;
	Stack _popst;
	int _size;
   
	//const变量只有一次初始化的机会,所以const变量 必须在定义时初始化,也即_x必须在初始化列表初始化
	const int _x;//

	// 引用必须在定义时初始化,所以_ref也在定义的时候初始化
	int& _ref;
};

int main()
{
	int xx = 0;
	MyQueue q1(10, xx);
	//MyQueue q2;

	const int y = 1;//const变量必须初始化

	int& ry = xx;//引用必须初始化

	return 0;
}
class MyQueue
{
public:
	// 初始化列表,不管你写不写,每个成员变量都会先走一遍:自定义类型去调默认构造,内置类型不做处理
	//类似于会自动执行如下代码
	 MyQueue()
			:_pushst
		    ,popst
	 {

	 }
	// 自定义类型的成员会调用默认构造(没有默认构造就编译报错)
	// 内置类型有缺省值用缺省值,没有的话,不确定,要看编译器,有的编译器会处理,有的不会处理
	// 先走初始化列表 + 再走函数体
	// 实践中:尽可能使用初始化列表初始化,不方便再使用函数体初始化
	MyQueue()
		:_size(1)//显示写了就和缺省值没有什么关系了   
		, _ptr((int*)malloc(40))//括号里面的值很自由,这个部分就像传实参一样 可以是值、常量、表达式和函数调用等
	{
		memset(_ptr, 0, 40);//要调用函数初始化就得在函数体里面
	}

private:
	// 声明
	Stack _pushst;
	Stack _popst;

	// 缺省值  给初始化列表用的
	int _size = 0;
	const int _x = 10; //这里是缺省值,缺省值是给初始化列表用的

	int* _ptr;
};

int main()
{
	MyQueue q;

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

输出1 和 随机值,可见初始化顺序和成员变量的声明顺序一致,先初始化_a2,再初始化_a1。

2.explicit关键字

构造函数不仅可以构造与初始化对象, 对于接收单个参数的构造函数,还具有类型转换的作用
用explicit修饰构造函数,将会禁止构造函数的隐式转换
接收单个参数的构造函数具体表现:
①构造函数只有一个参数
构造函数有多个参数,除第一个参数没有默认值外,其余参数都有默认值
全缺省构造函数
#include<iostream>
using namespace std;
class A
{
public:
	// 单参数构造函数
	//explicit A(int a)//加上explicit关键字以后,就可以禁止隐式类型转换了
	A(int a)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}

	// 多参数构造函数
	A(int a1, int a2)
		:_a(0)
		,_a1(a1)
		,_a2(a2)
	{}

	A(const A& aa)
		:_a(aa._a)
	{
		cout << "A(const A& aa)" << endl;
	}

private:
	int _a;
	int _a1;
	int _a2;
};

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

	// 隐式类型转换
	// 内置类型转换为自定义类型
	// 3构造一个A的临时对象,再用这个临时对象拷贝构造aa3
	// 编译器遇到连续构造+拷贝构造->优化为直接构造,注意是在一个表达式中
	A aa3 = 3;//表面上是先构造,再拷贝构造,实际上只有一个构造
    A aa4 = 3.3;//也是隐式类型转换,将3.3转换成3后再转换
 
	// raa 引用的是类型转换中用3构造的临时对象 
	const A& raa = 3;//raa不是直接引用的3,因为引用必须是同类型的变量,
      //这里用3去构造了一个临时对象,并且只有构造,没有拷贝构造

	
	A aaa1(1, 2);
	A aaa2 = { 1, 2 };//表面上是先构造,再拷贝构造,实际上优化为直接构造,也是隐式类型转换
	const A& aaa3 = { 1, 2 };//可以省略赋值符号

	return 0;
}

右边黑色方框是对应调用的构造

接收参数进行类型转换的构造函数的作用如下:

class Stack
{
public:
	void Push(const A& aa)
	{
		//...
	}

	//...
};

class string
{
public:
	string(const char* str)
	{}
};

int main()
{
	Stack st;

	A a1(1);
	st.Push(a1);

	A a2(2);
	st.Push(a2);

	st.Push(2);//等价于下面两行
    A a2(2);
    st.Push(a2);
 
	st.Push(4);

	A aa1(1, 2);
	st.Push(aa1);

	st.Push({ 1,2 });//和上面两行等价
  //隐式类型转换中间先生成临时对象,调用有参构造
	list<string> lt;
	string s1("111");
	lt.push_back(s1);

	lt.push_back("1111");//等价于上面两行

	return 0;
}

声明给缺省值还有如下几种方式:

class BB
{
public:
	BB()
	{

	}

private:
	//声明给缺省值还有以下几种方式,注意缺省值是给初始化列表的
	int _b1 = 1;
	int* _ptr = (int*)malloc(40);
	Stack _pushst = 10;
	A _a1 = 1;
	A _a2 = { 1,2 };
	A _a3 = _a2;
};

3.static成员

声明为 static 的类成员 称为 类的静态成员 ,用 static 修饰的 成员变量 ,称之为 静态成员变量 ;用 static 修饰 成员函数 ,称之为 静态成员函数 静态成员变量一定要在类外进行初始化
#include<iostream>
using namespace std;
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;

//public:
	// 声明
	// 静态区,不存在对象中
	// 不能给缺省值,因为缺省值是给初始化列表
	// 他在静态区不在对象中,不走初始化列表,初始化列表处理的是对象中的成员 
	// 属于所有整个类,属于所有对象
	static int _scount;
};

// 定义
int A::_scount = 0;//由于声明和定义分离,所以类外也可以访问私有成员
 //不定义只声明会报错无法解析的什么什么


int main()
{
	A aa1;
	cout << sizeof(aa1) << endl;//8

	A aa2;
	A aa3(aa1);

	//aa1._scount++;//这两种方式突破类域,编译器编译的时候知道去哪找 
	//cout << A::_scount << endl;//由于scount属于所有对象,所以可以这样访问
    //这两种方式的前提是_scount的访问限定符是public
	cout << A::GetCount() << endl;

	return 0;
}

static成员特性:

①  静态成员 所有类对象所共享 ,不属于某个具体的对象,存放在静态区
②  静态成员变量 必须在 类外定义 ,定义时不添加 static 关键字,类中只是声明
③  类静态成员即可用 类名 :: 静态成员 或者 对象 . 静态成员 来访问
④  静态成员函数 没有 隐藏的 this 指针 ,不能访问任何非静态成员
⑤  静态成员也是类的成员,受 public protected private 访问限定符的限制
注意:静态成员函数不可以调用非静态成员函数
          但是非静态成员函数可以调用类的静态成员函数
4.友元
友元分为:友元函数和友元类
友元函数 可以 直接访问 类的 私有 成员,它是 定义在类外部 普通函数 ,不属于任何类,但需要在类的内部声明,声明时需要加friend 关键字。
下面就是一个友元函数的例子:
class Date
{
     friend ostream& operator<<(ostream& _cout, const Date& d);
     friend istream& operator>>(istream& _cin, Date& d);
public:
     Date(int year = 1900, int month = 1, int day = 1)
      : _year(year)
      , _month(month)
      , _day(day)
      {}
private:
      int _year;
      int _month;
      int _day;
};
ostream& operator<<(ostream& _cout, const Date& d)
{
     _cout << d._year << "-" << d._month << "-" << d._day;
     return _cout;
}
istream& operator>>(istream& _cin, Date& d)
{
     _cin >> d._year;
     _cin >> d._month;
     _cin >> d._day;
     return _cin;
}
int main()
{
     Date d;
     cin >> d;
     cout << d << endl;
     return 0;
}
说明:
①友元函数 可访问类的私有和保护成员,但 不是类的成员函数
②友元函数 不能用 const修饰 
const 修饰 this ,所以不能用 const 修饰,并且友元函数不是类的成员函数,就是一个
全局函数
③友元函数 可以在类定义的任何地方声明, 不受类访问限定符限制
④一个函数可以是多个类的友元函数
⑤友元函数的调用与普通函数的调用原理相同
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
class Time
{
	// 声明 Date是Time的友元
	// Date中可以访问Time的私有
	// 但是Time中不能访问Date的私有
	friend class Date;
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{
		_t._hour++;
	}
	void SetTimeOfDate(int hour, int minute, int second)
	{
		// 直接访问时间类私有的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};
友元类的注意事项:
①友元关系是单向的,不具有交换性。
比如上述 Time 类和 Date 类,在 Time 类中声明 Date 类为其友元类,那么可以在 Date 类中直接访问 Time 类的私有成员变量,但想在Time 类中访问 Date 类中私有的成员变量则不行。
②友元关系不能传递
如果 B A 的友元, C B 的友元,则不能说明 C A 的友元。
③友元关系不能继承
5.内部类
内部类: 如果一个类定义在另一个类的内部,这个内部类就叫做内部类
内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。
注意: 内部类就是外部类的友元类 ,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
①内部类可以定义在外部类的 public protected private 都是可以的。
②  注意内部类可以直接访问外部类中的 static 成员,不需要外部类的对象 / 类名。
③ sizeof( 外部类 )= 外部类,和内部类没有任何关系。
#include<iostream>
using namespace std;
class A
{
private:
	static int k;
	int h = 0;
public:
	void func()
	{}
//private:
	// 内部类
	// 独立的类,放到A里面
	// 仅仅受到类域限制,也受访问限定符的限制
	class B // B天生就是A的友元,内部类可以访问外部类的私有,外部类不可以访问内部类的私有
	{       //也即B可以访问A的私有,A不可以访问B的私有
	public:
		void foo(const A& a)
		{
			cout << k << endl;//OK
			cout << a.h << endl;//OK
		}

	private:
		int _b;
	};//注意:A里面没有B成员,C++内部类和外部类的关系就是两个平行独立的类
};
int A::k = 1;
int main()
{
	cout << sizeof(A) << endl;//4

	A a1;
	//B b1;//是不可以的,找不到,默认情况下只会到全局搜索,不会到类里面搜索
	A::B b1;
	b1.foo(a1);
	return 0;
}

6.匿名对象

#include<iostream>
using namespace std;

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


class Solution {
public:
	int Sum_Solution(int n) {
		//...
		return n;
	}
};

int main()
{
	A aa1;
	A aa2(1);//有名对象的生命周期是当前作用域

	// 匿名对象,生命周期只在当前这一行, 即用即销毁
	A(2);

	A aa3(1);

	Solution s1;
	s1.Sum_Solution(10);//有名对象的调用

	Solution().Sum_Solution(10);//匿名对象的调用

	return 0;
}

引用传参的情况:

void f1(const A& aa)
{}

int main()
{
	// 引用传参
	A aa1;
	f1(aa1);
	cout << endl;

	f1(A(2));//A(2)是匿名对象,匿名对象和临时对象都具有常性
   //这一行走完了,匿名对象的生命周期就到了
	cout << endl;

	f1(2);//单参数构造函数隐式类型转换,生成临时对象
	cout << endl;

	return 0;
}

传值传参的情况:

void f1(A aa)
{}

int main()
{
	// 传值传参
	A aa1;
	f1(aa1);//传值传参先要拷贝构造
	cout << endl;

   // 连续一个表达式中,构造+拷贝构造-》优化为直接构造
   //不优化的话,中间的构造临时的对象就白白浪费了
	f1(A(2));//函数调用也是一个表达式
	cout << endl;

	f1(2);
	cout << endl;
    //编译器遇到连续构造+拷贝构造->优化为直接构造,注意是在一个表达式中
	A aa3 = 3;//表面上是先构造,再拷贝构造,实际上只有一个构造
	cout << endl;

	return 0;
}

返回值为自定义表达式的情况:

A f2()
{
	A aa;

	return aa;
}

int main()
{
	// 连续一个表达式中,拷贝构造 + 拷贝构造 - 》优化为一个拷贝构造
	const A& ret = f2();
	//引用的是中间的拷贝的临时对象,中间的临时对象不那么确定,不建议这么写
	//优化一般是放在函数的返回值中,函数的返回值写成引用
	A ret1 = f2();
	cout << endl;

	// 无法优化,建议尽量不要这样写
	A ret2;
	ret2 = f2();
	cout << endl;

	return 0;
}

7.自定义类型构造和析构的顺序

void f2()
{
  // 局部的静态生命周期是全局的,但是局部的静态是在第一个调用时构造初始化
 //第二次调用不会构造,只会构造初始化一次
	static A aa3;
}

 //全局对象在main函数之前初始化,在main函数之前构造
A aa0;

int main()
{
	// 后定义先析构  满足后进先出
	A aa1;
	A aa2;

	f2();
	cout << "1111111111111111111" << endl;
	f2();
	cout << "1111111111111111111" << endl;

	return 0;
}

  • 21
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值