1.再探构造函数
- 之前使用构造函数时都是在函数体内初始化成员变量,还有一种构造函数的用法,叫做初始化列表;那么怎么使用呢? 
  - 使用方法用冒号开始(" : ")要写多个就用逗号(" , ")隔开数据成队列
- 每个成员变量后面跟着一个括号,括号内就是初始化的内容;括号内可以是初始值或者一个表达式
 
- 每个成员变量只要初始化一次就行了,不能重复初始化;这里也是成员变量定义的地方 
  
- 引用成员变量、const成员变量、没有默认构造的类 类型变量,这三类必须在初始化列表位置进行初始化,否则会报错
- C++11支持在成员变量声明的位置给上缺省值,这里的缺省值是给初始化列表使用的,当没有显示写初始化列表就会用到缺省值
- 下列是思维动图形式的小总结 
- 初始化列表中是按照成员变量声明处的顺序来进行初始化的,与初始化列表中写的先后顺序无关,所以建议声明的顺序和初始化列表的保持一致;当然也是和内存存储顺序有关(地址处低到高)
- 每个构造函数都有初始化列表,没有显示写会自动生成
- 补充: 
  - 函数的缺省值是提供给实参使用的,成员变量处的缺省值是提供给初始化列表使用的
- 调用函数建立栈帧时对象就会申请空间,构造函数是用来初始化的;两者之间要区分
 

#include<iostream>
using namespace std;
class Date
{
public:
	Date(int day = 100)
	{
		_day = day;
	}
private:
	int _day;
};
class A
{
public:
	A(int a = 1, int b = 1, int c = 1)
		: _a(a)
		, _b(b)
		,_ca(c)
		,_quote(a)
		, _ptr((int*)malloc(12))//还可以是表达式
	{
		if (_ptr == nullptr)
		{
			perror("_prt fail");
		}
		else
		{
			memset(_ptr, 1, 12);//给内存初始化为 全1
		}
	}
	void Print()
	{
		cout << _a << '/' << _b << '/' << _c << endl;
	}
private:
    //声明
	int _a = 10; // 这里的是给初始化列表使用的缺省值
	int _b = 20;
	int _c = 30; //没有显示写时,缺省值就会用上
	int* _ptr;
	//下列的都必须使用初始化列表
	const int _ca;//必须要有初始化
	int& _quote;
	Date _ddy;// 没有默认构造会报错
};
int main()
{
	A aa(2024,8,23);
	aa.Print();
	return 0;
}2.类型转换
- C++支持内置类型隐式类型转换为类类型对象,需要对应的内置类型为参数(实参)的构造函数
- 如果不需要隐式类型转换了,构造函数前面加explicit就不再支持隐式类型转换
- 类类型的对象之间也可以隐式转换,需要对应构造函数支持
class AB
{
public:
	//explicit AB(char a = 0, char b = 0)
	AB(char a = 0,char b = 0)
	{
		_a = a;
		//_b = b;
	}
	void Print()
	{
		cout << _a << _b << endl;
	}
private:
	char _a;
	char _b = 'b';
};
class Stack
{
public:
	Stack()
	{
	}
	void Push(const AB& abp)
	{
		//...
	}
private:
	AB _arr[10];
	int _size;
};
int main()
{
	//隐式类型转换
	//65构造AB类的临时对象,然后用这个临时对象拷贝给au
	//编译器将 连续构造 + 拷贝构造 ,优化成直接构造
	AB au = 65;
	//AB au = "abcd";字符串不可以隐式类型转换
	au.Print();
	AB& aup = au;
	const AB& p = 2; // 2 会产生临时对象,具有常性,需要const
	Stack st;
	AB aa1(60, 70);
	st.Push(aa1);
	AB aa2 = { 88,99 };//支持多参数
	
	// C++11 才支持的
	const AB& aa3 = { 77,78 };
	//上面的方法麻烦,还需要创建类再插入;这里可以直接插入
	//因为AB是多参数的
	st.Push({ 77,78 });
	return 0;
}3.static成员
- 用static修饰的成员变量, 叫做静态成员变量,静态成员变量一定要在类外面初始化
- 静态成员变量为 所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区;当前同类型的对象也可以访问
- 被static修饰的成员函数,叫静态成员函数,静态成员函数是没有this指针的;如果要返回静态成员变量需要静态成员函数
- 静态成员函数中可以访问其他的静态成员,但是不能访问非静态成员,因为没有this指针
- 非静态的成员函数,可以任意访问静态成员函数和静态成员变量
- 突破类域的限制就可以访问静态成员,通过 fun3::Getb() 或者 fufu.Getb();就可以访问静态成员函数和静态成员变量
- 虽然可以任意访问静态成员,但静态成员也是类的成员,也会受到public、protected、private访问限定符的限制
- 静态成员变量,不走构造函数的初始化列表;因为不属于某一个类,自然给初始化列表的缺省值也不能用
class fun2
{
public:
	static int _aa;
	fun2()
	{
	}
	void sum()
	{
		_a--;
	}
	static int Geta()//不可以访问非静态的,非静态的可以访问静态的
	{
		_a += 2;
		return _a;//虽然都可以访问静态成员变量,但是正常情况下是需要this指针才能当作返回值
		//得出如果要返回静态成员变量需要静态成员函数
	}
private:
	static int _a;
};
class fun3
{
public:
	static int Getb()
	{
		//调用函数 返回结果
		return fun2::Geta();//因为是静态函数所以都可访问,但也受访问限定符限制 
	}
private:
	static int _b;
};
//在类外初始化
int fun3::_b = 30;
int fun2::_aa = 22;
int fun2::_a = 10;
int main()
{
	int at = 1;
	fun2 f1;
	f1.sum();
	cout << fun2::Geta() << endl;
	//大小是1,标识这个对象,静态成员变量在静态区
	cout << sizeof(f1) << endl;
	//都可以访问静态函数
	cout << fun3::Getb() << endl;
	//所有类都可以访问,但是会受到类域限制
	cout << fun2::_aa << endl;
	//访问静态成员函数两种方式,常用 :: 符号访问
	fun3 fufu;
	cout << fun3::Getb() << endl;
	cout << fufu.Getb() << endl;
	return 0;
}补充:
- 静态的变量第一次走到那里才会初始化;全局的静态会在,main函数之前初始化。
- 编译阶段会有语法检查;变量初始化时,如果不使用会被优化;
4.友元
- 友元提供一种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明前面加上friend,把友元声明放到类里
- 外部的友元函数可以访问类的私有和保护成员,就是一个声明,不是类的成员,也不会占用额外空间
- 友元函数可以在类的任何地方定义,不受类访问限定符限制;一般都放到类的最开头
- 一个函数可以是多个类的友元函数
- 友元类中的成员函数都可以是另一个类的友元函数,可以访问另一个类的私有和保护
- 不过友元类是单向的,A类是B类的友元,但是B类不可以是A类的友元
- 友元类没有传递性,A类是B类的友元,B类可以是C类的友元,但是A类不可以是C类的友元;如果希望是那么在C类写上A类的友元声明
- 友元也有弊端,友元会增加耦合度,破坏了封装,尽量不用
//前置声明
void Print();
class yy
{
	//友元函数声明
	friend int sum(const yy& x1, const yy& x2);
	friend void Print();
	//友元类声明,单向的;yy类不可访问cl类
	friend class cl;
public:
	void print2()
	{
		Print();
	}
private:
	int _a = 10;
	int _b = 20;
};
int sum(const yy& x1,const yy& x2)
{
	
	return x1._a + x2._b;
}
void Print()
{
	cout << 11 << endl;
}
class cl
{
	int sum1(const yy& x1, const yy& x2)
	{
		return x1._a + x2._b;
	}
	int sum3(const yy& x1, const yy& x2)
	{
		return x1._a + x2._b;
	}
};
int main()
{
	yy x1, x2;
	int ret = sum(x1, x2);
	cout << ret << endl;
	//此时证明写了友元函数声明,可以双方互相访问
	x1.print2();
	return 0;
}5.内部类
- 如果一个类定义在另一个类的内部,那么这个类就叫做内部类;内部类是一个独立的类(计算外部类的空间时,不会开辟内部类的),只受到访问限定符的限制和外部类的类域限制,所以外部类定义的对象中不包含内部类
- 内部类本质也是一种封装,但A类和B类达成合作关系(B类频繁使用A类),此时可以考虑写一个专属内部类,就是把A类放到private/protected位置;这样其他地方想创建A类,就创建不了
- 可以看看这题,更好的理解


#include <iostream>
using namespace std;
class A
{
public:
	void Print()
	{
		cout << _a << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a = 10;
	class B
	{
	private:
		int _x;
		double _y;
	};
};
//匿名对象
int main()
{
	A aa;
	aa.Print();
	//A aa(); //此时不确定是函数还是类
	cout << sizeof(aa) << endl;//计算的大小是4,并不包含B类
	return 0;
}6.匿名对象
- 用类型(实参),这种对象叫做匿名对象;之前的那种是有名对象 类型 对象名(实参),是有名对象
- 匿名对象的生命周期只有一行,和编译器生成的临时对象一样;一般临时用一下,可以用匿名对象
#include <iostream>
using namespace std;
class A
{
public:
	void Print()
	{
		cout << _a << endl;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a = 10;
};
int main()
{
	A aa;
	aa.Print();
	//A aa(); //此时不确定是函数还是类
	//匿名对象,和临时对象一样声明周期只有一行,
	A();
	//匿名对象,可以直接调用函数;对比上面要少写一行
	A().Print();
	return 0;
}7.对象拷贝时的编译器优化
- 现在的编译器为了提高程序的效率,不影响正确性的情况下都会进行优化;尽可能减少传值和传返回值的过程可省略的部分
- 有像 构造临时对象 + 拷贝构造 优化成直接构造
- 至于怎么优化看各自的编译器,C++并没有严格的规定;当前主流的比较新的编译器会优化连续拷贝并进行合并优化,有些编译器会更加激进的合并优化
- 初始化对象时,强制类型转换也是根据构造函数来看的,是需要看构造函数的参数的
#include <iostream>
using namespace std;
class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a = 1)" << endl;
	}
	//拷贝构造
	A(const A& xx)
		:_a(xx._a)
	{
		cout << "A(const A & xx)" << endl;
	}
	~A()
	{
		cout << _a << "~A()" << endl;
	}
	void Print()
	{
		cout << _a << endl;
	}
	A& operator++()
	{
		_a+= 100;
		return *this;
	}
	A& operator=(const A& xx)
	{
		cout << "A& operator=(const A& xx)" << endl;
		if (this != &xx)//不能和自己相同,否则就不对了
		{
			_a = xx._a;
		}
		return *this;
	}
private:
	int _a;
};
void fun1(A aa)
{
	
}
//初始化对象时
//int main()
//{
//	A tmp = 1;// 构造一个临时对象 + 拷贝构造  都会转化成直接构造
//
//	const A& at = 10;//直接会强制类型转换,这个强制类型转换也是根据构造函数来看的
//	return 0;
//}
//传参时的优化
//int main()
//{
//	A aa(1);
//	fun1(aa);//没有使用引用,会产生拷贝构造,出函数临时对象销毁
//
//	cout << endl;
//
//	//有优化
//	fun1(A(20));
//
//	cout << endl;
//
//	//有优化
//	fun1(30);//构造临时对象 + 拷贝构造 优化成直接构造
//	return 0;
//}
//返回值
A fun2()
{
	A aa(1);
	++aa;
	cout << "------" << endl;
	return aa;//vs2022 优化比19版 更加激进;把aa的构造,拿101直接构造临时对象,并作为返回值
}
//int main()
//{
//	fun2().Print();//使用函数的临时对象调用函数,并且临时对象的生命周期只在这一行
//	cout << "********" << endl;
//	return 0;
//}
int main()
{
	A ret;
	ret = fun2();// 拿临时对象去赋值拷贝构造; 只优化了fun2()的拷贝构造
	ret.Print();
	cout << "**********" << endl << endl;
	return 0;
}做好自己,减少竞争性的努力,走好自己的路,超越昨天的自己





















