C++类与对象(下)

初始化列表

构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  2. 类中包含以下成员,必须放在初始化列表位置进行初始化:
    引用成员变量
    const成员变量
    自定义类型成员(且该类没有默认构造函数时)
class A
{
public:
	A(int a = 0)
		:_a(a)
	{}
private:
	int _a;
};

class Date
{
public:
	// 初始化列表是每个成员定义的地方
	// 不管你写不写,每个成员都要走初始化列表
	Date(int year, int month, int day, int& i)
		: _year(year)
		, _month(month)
		,_a(1)
		,_refi(i)
	{
		// 赋值
		_day = day;
	}

	void func()
	{
		++_refi;
		++_refi;
	}

private:
	int _year;  // 每个成员声明
	int _month = 1;
	int _day = 1;
	// C++11支持给缺省值,这个缺省值给初始化列表
	// 如果初始化列表没有显示给值,就用这个缺省值
	// 如果显示给值了,就不用这个缺省值

	// 以下必须定义时初始化
	const int _x = 10;
	int& _refi;
	A _a;
};

初始化列表是构造函数的一部分
初始化列表是每个成员定义的地方
不管你写不写,每个成员都要走初始化列表
在构造函数的初始化列表阶段,如果不写,对内置类型用随机值去初始化(看编译器,有些编译器会处理成0 ),对于自定义类型,会去调用它的默认构造
初始化列表初始化的顺序跟声明的顺序有关

能用初始化列表就用初始化初始化列
有些场景还是需要初始化列表和函数体混着用

Stack(size_t capacity)
	:_array((DataType*)malloc(sizeof(DataType) * capacity))
	,_size(0)
	,_capacity(capacity)
{
	cout << "Stack()" << endl;
	if (NULL == _array)
	{
		perror("malloc申请空间失败!!!");
		return;
	}

	memset(_array, 0, sizeof(DataType) * _capacity);
}

A aa1(1);是可以的,还有一种写法:

单参数构造函数的隐式类型转换

单参数
private:
	int _a;

int可以转换为A,支持隐式类型转换
用2调用A构造函数生成一个临时对象,再用这个对象去拷贝构造aa2
编译器会再优化,优化用2直接构造
A aa2 = 2;

证明:
A& ref1 = 2;//这样写不行
原因是中间生成临时对象,临时对象具有常性,所以ref不可以引用这个临时对象,这是权限的放大,变成 const A& ref1 = 2;就可以了

如果不想让隐式类型发生,加explicit关键字

explicit A(int i)
		:_a(i)
	{
		cout << "A(int i)" << endl;
	}

在这里插入图片描述
如果是多参数:

C++11 支持多参数的隐式类型转换
	B bb1(1, 1);
	B bb2 = { 2, 2 };
	const B& ref2 = { 3,3 };

匿名对象:

    // 有名对象 特点:生命周期在当前局部域
	A aa6(6);

	// 匿名对象。特点:生命周期只在这一行
	A(7);

想只使用一次对象时可以用匿名对象,可以节省代码量

	A aa7(7);
	s.PushBack(aa7);

	s.PushBack(A(8));
Solution sl;
sl.Sum_Solution(10);

Solution().Sum_Solution(100);//Solution()定义了一个没有名字的对象
	Date d1(2023, 7, 28);
	cout << d1;
	
	cout << Date(2023, 7, 28);

static成员

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

实现一个类,计算程序中创建出了多少个类对象。

// 累积创建了多少个对象  
int n = 0;

// 正在使用的还有多少个对象
int m = 0;

class A
{
public:
	A()
	{
		++n;
		++m;
	}

	A(const A& t)
	{
		++n;
		++m;
	}

	~A() 
	{
		--m;
	}
private:
};

//A& Func(A& aa)
A Func(A aa)
{
	return aa;
}

int main()
{
	A aa1;
	A aa2;
	cout << n << " " << m << endl;
	
	// 可能被外面随意修改
	//--n;
	//++m;
	A();
	cout << n << " " << m << endl;


	Func(aa1);
	cout << n << " " << m << endl;


	return 0;
}
class A
{
public:
	A()
	{
		cout << "A()" << endl;
		++n;
		++m;
	}

	A(const A& t)
	{
		++n;
		++m;
	}

	~A()
	{
		--m;
	}
	

private:
	// 静态成员变量属于所有A对象,属于整个类
	// 声明
	// 累积创建了多少个对象  
	static int n;
	// 正在使用的还有多少个对象
	static int m;
};
//定义
int A::n = 0;
int A::m = 0;

不是单单属于某个对象,而是想属于所有A对象,属于整个类
缺省值是在初始化列表阶段使用的,但是静态成员变量不会走初始化列表,初始化列表是某个对象的成员的初始化,静态成员变量属于所有对象,所以不能给缺省值。

这两个静态成员不存在对象里面,存在静态区
因为类里面是声明,所以静态成员要在外面定义,相当于声明和定义分离,不是在类外面访问,在.h里定义会出问题

n和m是公有情况下
int main()
{
	A aa1;
	A aa2;
	cout << A::n << " " << A::m << endl;
	cout << aa1.n << " " << aa2.m << endl;
	A* ptr = nullptr;
	cout << ptr->n << " " << ptr->m << endl;

	// 可能被外面随意修改
	A();
	cout << n << " " << m << endl;

	Func(aa1);
	cout << n << " " << m << endl;

	return 0;
}

n、m属于整个类,可以用作用域限定符去拿到
从底层角度看,n和m不在aa1、aa2、ptr指向的对象里面,在静态区,ptr->n这种方式只是帮助其突破类域,虽然是空指针但是不会报错,有没有解引用要看数据在哪。

如果n和m是私有那上面三种方法都访问不了,可以通过成员函数访问,公有和函数去找m不同的是公有的m可以被修改,函数的方法一般不会去修改m,想修改可以返回引用修改。

int GetM()
{
	return m;
}

void Print()
{
	cout << m <<" " << n << endl;
}

但调用成员函数需要创建对象
用匿名对象调用函数,也会多创建一个对象出来,干扰逻辑
想不创建对象也能访问——静态成员函数

//静态成员函数的特点:没有this指针
static int GetM()
{
	return m;
}

	// ...

static void Print()
{
	// x++; // 不能访问非静态,因为没有this

	cout << m <<" " << n << endl;
}

以前需要对象去调用,是因为需要去类里找还有就是要传this指针

A::Print();现在不需要传this指针了,只需要突破类域就可以

静态成员函数不能访问非静态成员,因为没有this指针

小总结:

  1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
  2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
  3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制

友元

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

友元函数

class Date
{
    friend ostream& operator<<(ostream& _cout, const Date& d);
    friend istream& operator>>(istream& _cin, Date& d);
};

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

  • 友元函数可访问类的私有和保护成员,但不是类的成员函数
  • 友元函数不能用const修饰(没有this指针)
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数的调用原理相同

友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

  • 友元关系是单向的,不具有交换性。
    比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接
    访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。

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

class Time
{
   friend class Date;   // 声明日期类为时间类的友元类,则在日期类中就直接访问Time类中的私有成员变量
}

内部类

1、B类受A类域和访问限定符的限制,其实他们是两个独立的类
2、内部类默认就是外部类的友元类

class A
{
	//public:
	class B
	{
	public:
		void FuncB(int year)
		{
			A aa;
			year=aa.c;
			_d++;
		}
	private:
		int _b;
	};

	void func()
	{
		B bb;
		//bb._b = 1;//这样是不行的
	}

private:
	const static int _a;
	static int _d;
	int c = 2;
};

const int A::_a = 1;
int A::_d = 3;

int main()
{
	cout << sizeof(A) << endl;

	A aa;
	//A::B bb1;//访问不了

	return 0;
}
  1. 内部类可以定义在外部类的public、protected、private都是可以的。
  2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
    (非static成员访问需要对象)。
  3. sizeof(外部类)=外部类,和内部类没有任何关系。

匿名对象

    // 有名对象 特点:生命周期在当前局部域
	A aa6(6);

	// 匿名对象。特点:生命周期只在这一行
	A(7);

想只使用一次对象时可以用匿名对象,可以节省代码量

	A aa7(7);
	s.PushBack(aa7);

	s.PushBack(A(8));
Solution sl;
sl.Sum_Solution(10);

Solution().Sum_Solution(100);//Solution()定义了一个没有名字的对象
	Date d1(2023, 7, 28);
	cout << d1;
	
	cout << Date(2023, 7, 28);

匿名对象、临时对象具有常性

void f1(const A& aa = A())
{
	aa.Print();
}

int main()
{
	A aa1;
	f1(aa1);

	f1(A(1));
	f1(2);
	f1();

	A();

	// const引用会延长匿名对象声明周期
	// ref出了作用域,匿名对象就销毁了
	const A& ref = A();
	A aa2;
	return 0;
}

匿名对象、临时对象具有常性,不可以修改
所以不能直接用引用接收,应该在引用前在加const才可以

const引用同时可以延长匿名对象的生命周期

了解:编译器优化

C++并没有规定要优化,不过现在大多数编译器都会优化

int main()
{
	A aa1;
	f1(aa1);

	cout << "--------------------------" << endl;
	// 一个表达式,连续的步骤里面,连续的构造会被合并
	f1(A(1));

	cout << "--------------------------" << endl;
	f1(1);

	cout << "--------------------------" << endl;
	A aa2 = 1;

	cout << "--------------------------" << endl;
	A aa3 = A(2);

	return 0;
}

在这里插入图片描述

本来一次构造,两次拷贝构造

在函数返回之前,本来要去拷贝临时对象,编译器优化之后

在这里插入图片描述
ret2没有优化的原因是:
第一点:同类型才能优化,比如拷贝构造和构造都是构造才能优化,拷贝构造和赋值不能合并
第二点:在两个步骤里,不在同一个步骤里

在这里插入图片描述

要构造又要拷贝构造,又在同一个步骤里,就优化成直接构造ret

在这里插入图片描述
了解这些是让我们知道怎样写能触发编译器的优化,更高效

练习题

求和OJ题
求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

class Sum{
public:
    Sum()
    {
        _m+=_n;
        _n++;
    }
    static int GetM()
    {
        return _m;
    }
private:
static int _n;
static int _m;
};
int Sum:: _n=1; 
int Sum:: _m=0; 

class Solution {
public:
    int Sum_Solution(int n) {
        Sum a[n];
        return Sum::GetM();
    }
};

改进:

class Solution {
public:
    class Sum{
    public:
        Sum()
        {
            _m+=_n;
            _n++;
        }
    };
    int Sum_Solution(int n) {
        Sum a[n];
        return _m;
    }
private:
static int _n;
static int _m;
};

int Solution:: _n=1; 
int Solution:: _m=0;

如果不用内部类,得排除一次构造函数带来的干扰:

class Solution {
public:
    // class Sum{
    // public:
    //     Sum()
    //     {
    //         _m+=_n;
    //         _n++;
    //     }
    // };
    Solution()
    {
        _m+=_n;
        _n++;
    }
    int Sum_Solution(int n) {
        Solution a[n];
        return _m;
    }
private:
static int _n;
static int _m;
};

int Solution:: _n=0; 
int Solution:: _m=0; 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值