首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >类和对象(下)

类和对象(下)

作者头像
waves浪游
发布2024-12-05 08:12:05
发布2024-12-05 08:12:05
12600
代码可运行
举报
文章被收录于专栏:C语言讲解C语言讲解
运行总次数:0
代码可运行

1. 再谈构造函数

  • 之前我们实现构造函数时,初始化成员变量主要使用函数体内赋值,构造函数初始化还有一种方式,就是初始化列表,初始化列表的使用方式是以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。
  • 每个成员变量在初始化列表中只能出现一次,语法理解上初始化列表可以认为是每个成员变量定义初始化的地方。
  • 引用成员变量,const成员变量,没有默认构造的类类型变量,必须放在初始化列表位置进行初始化,否则会编译报错。
代码语言:javascript
代码运行次数:0
运行
复制
#include <iostream>

using namespace std;

typedef int DataType;

class Stack
{
public:
	Stack(size_t capacity)
	{
		_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也无法生成默认构造
	//初始化列表
	//初始化列表本质可以理解为每个对象中成员定义的地方
	//所有的成员,你可以在初始化列表初始化,也可以在函数体内初始化
	//以下三种情况必须在初始化列表初始化
	//1、引用 2、const 3、没有默认构造的自定义类型成员(必须显式传参调构造)
	MyQueue(int n, int& rr)
		:_pushst(n)
		, _popst(n)
		, _x(1)
		, _ref(rr)
	{
		_size = 0;
		//_x = 1;//err
	}

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

	//必须在定义时初始化
	const int _x;

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

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

	/*const int y;
	y = 1;*/  //err

	const int y = 1;

	//int& ry;//err
	int& ry = xx;

	return 0;
}
  • C++11支持在成员变量声明的位置给缺省值,这个缺省值主要是给没有显式在初始化列表初始化的成员使用的。
  • 尽量使用初始化列表初始化,因为那些你不在初始化列表初始化的成员也会走初始化列表,如果这个成员在声明位置给了缺省值,初始化列表会用这个缺省值初始化。如果你没有给缺省值,对于没有显式在初始化列表初始化的内置类型成员是否初始化取决于编译器,C++并没有规定。对于没有显式在初始化列表初始化的自定义类型成员会调用这个成员类型的默认构造函数,如果没有默认构造会编译错误。
代码语言:javascript
代码运行次数:0
运行
复制
#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:
	//初始化列表,不管你写不写,每个成员变量都会先走一遍
	//自定义类型的成员会调用默认构造(没有默认构造就编译报错)
	//内置类型有缺省值用缺省值,没有的话,不确定,要看编译器,有的编译器会处理,有的不会处理
	//先走初始化列表 + 再走函数体
	//实践中:尽可能使用初始化列表初始化,不方便的再使用函数体初始化
	MyQueue()
		:_size(1)
		,_pushst(10)
		,_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;
}
  • 初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的先后顺序无关。因此,建议声明顺序和初始化列表顺序保持一致。
代码语言:javascript
代码运行次数:0
运行
复制
#include <iostream>

using namespace std;

class A
{
public:
	A(int a)
		:_a1(a)
		,_a2(_a1)
	{}

	void Print()
	{
		cout << _a1 << " " << _a2 << endl;//1 随机值
	}

private:
	int _a2;
	int _a1;
};

int main()
{
	A aa(1);
	aa.Print();

	return 0;
}

初始化列表总结:

  • 无论是否显式写初始化列表,每个构造函数都有初始化列表
  • 无论是否在初始化列表显式初始化,每个成员变量都要走初始化列表初始化
成员变量走初始化列表的逻辑
成员变量走初始化列表的逻辑

2. 类型转换

  • C++支持内置类型隐式类型转换为类类型对象,需要有相关内置类型为参数的构造函数。
  • 构造函数前面加explicit就不再支持隐式类型转换。
  • 类类型的对象之间也可以隐式转换,需要相应的构造函数支持。
代码语言:javascript
代码运行次数:0
运行
复制
#include <iostream>

using namespace std;

class A
{
public:
	//单参数构造函数
	//explicit A(int a)
	A(int a)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}

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

private:
	int _a;
};

int main()
{
	A aa1(1);

	//拷贝构造
	A aa2 = aa1;
	
	//隐式类型转换
	//内置类型转换为自定义类型
	//3构造一个A的临时对象,再用这个临时对象拷贝构造aa3
	//编译器遇到连续构造 + 拷贝构造 -> 优化为直接构造
	A aa3 = 3;

	//raa引用的是类型转换中用3构造的临时对象
	const A& raa = 3;

	A aa4 = 3.3;

	//A aa5 = "1111111";//err

	return 0;
}
隐式类型转换
隐式类型转换
代码语言:javascript
代码运行次数:0
运行
复制
#include <iostream>

using namespace std;

class A
{
public:
	A(int a1, int a2)
		:_a(0)
		,_a1(a1)
		,_a2(a2)
	{}

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

int main()
{
	A aaa1(1, 2);
	A aaa2 = { 1, 2 };
	const A& aaa3 = { 1, 2 };

	return 0;
}

使用场景的举例:

代码语言:javascript
代码运行次数:0
运行
复制
#include <iostream>
#include <list>

using namespace std;

class A
{
public:
	//单参数构造函数
	//explicit A(int a)
	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;
};

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

	//...
};

int main()
{
	Stack st;

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

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

	st.Push(2);
	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;
}

补充:

成员变量缺省值的各种写法:

代码语言:javascript
代码运行次数:0
运行
复制
#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 A
{
public:
	//单参数构造函数
	//explicit A(int a)
	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;
};

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

int main()
{
	BB bb;

	return 0;
}

3. static成员

  • 用static修饰的成员变量,称之为静态成员变量,静态成员变量一定要在类外进行初始化。
  • 静态成员变量为所有类对象所共享,不属于某个具体的对象,不存在对象中,存放在静态区。
  • 用static修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
  • 静态成员函数中可以访问其他的静态成员,但是不能访问非静态的,因为没有this指针。
  • 非静态的成员函数,可以访问任意的静态成员变量和静态成员函数。
  • 突破类域就可以访问静态成员,可以通过 类名::静态成员 或者 对象.静态成员 来访问静态成员变量和静态成员函数。
  • 静态成员也是类的成员,受public、protected、private 访问限定符的限制。
  • 静态成员变量不能在声明位置给缺省值初始化,因为缺省值是给构造函数初始化列表的,静态成员变量不属于某个对象,不走构造函数初始化列表。
代码语言:javascript
代码运行次数:0
运行
复制
//实现一个类,计算程序中创建出了多少个类对象

#include<iostream>

using namespace std;

class A
{
public:
	A()
	{
		++_scount;
	}

	A(const A& t)
	{
		++_scount;
	}

	~A()
	{
		--_scount;
	}

	//没有this指针,只能访问静态成员
	static int GetCount()
	{
		return _scount;
	}

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

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

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

A func()
{
	A aa4;

	return aa4;
}

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

	A aa2;
	A aa3(aa1);

	func();

	//aa1._scount++;
	//cout << A::_scount << endl;
	cout << A::GetCount() << endl;

	return 0;
}

例题:

使用static成员的例题
使用static成员的例题
代码语言:javascript
代码运行次数:0
运行
复制
class Sum
{
public:
	Sum()
	{
		_ret += _i;
		++_i;
	}

	static int GetRet()
	{
		return _ret;
	}

private:
	static int _i;
	static int _ret;
};

int Sum::_i = 1;
int Sum::_ret = 0;

class Solution
{
public:
	int Sum_Solution(int n)
	{
		//变长数组
		Sum arr[n];

		return Sum::GetRet();
	}
};

4. 友元

  • 友元提供了一种突破类访问限定符封装的方式,友元分为:友元函数和友元类,在函数声明或者类声明的前面加friend,并且把友元声明放到一个类的里面。
  • 外部友元函数可访问类的私有和保护成员,友元函数仅仅是一种声明,它不是类的成员函数。
  • 友元函数可以在类定义的任何地方声明,不受类访问限定符限制。
  • 一个函数可以是多个类的友元函数。
  • 友元类中的成员函数都可以是另一个类的友元函数,都可以访问另一个类中的私有和保护成员。
  • 友元类的关系是单向的,不具有交换性,比如A类是B类的友元,但是B类不是A类的友元。
  • 友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是C的友元。
  • 友元有时提供了便利,但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
代码语言:javascript
代码运行次数:0
运行
复制
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)
	{}

	void SetTimeOfDay(int hour, int minute, int second)
	{
		//直接访问时间类私有的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}

private:
	int _year;
	int _month;
	int _day;

	Time _t;
};

5. 内部类

  • 如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,跟定义在全局相比,它只是受外部类类域限制和访问限定符限制,所以外部类定义的对象中不包含内部类。
  • 内部类默认是外部类的友元类。
  • 内部类本质也是一种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使用,那么可以考虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其他地方都用不了。
代码语言:javascript
代码运行次数:0
运行
复制
#include <iostream>

using namespace std;

class A
{
private:
	static int k;
	int h;

public:
	void fun()
	{}

	//内部类
	//独立的类,放到A里面
	//仅仅受到类域限制
	class B //B天生就是A的友元
	{
	public:
		void foo(const A& a)
		{
			cout << k << endl;//OK
			cout << a.h << endl;//OK
		}

	private:
		int _b;
	};
};

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

	A a1;
	A::B b1;

	return 0;
}

因此,上面讲到的例题可以这样修改:

代码语言:javascript
代码运行次数:0
运行
复制
//1.Sum变成Solution的专属类,封装起来
//2.内部类是外部类的友元
class Solution
{
	class Sum
	{
	public:
		Sum()
		{
			_ret += _i;
			++_i;
		}
	};

	static int _i;
	static int _ret;

public:
	int Sum_Solution(int n)
	{
		//变长数组
		Sum arr[n];

		return _ret;
	}
};

int Solution::_i = 1;
int Solution::_ret = 0;

6. 匿名对象

  • 用 类型(实参) 定义出来的对象叫做匿名对象,相比之前我们定义的 类型 对象名(实参) 定义出来的叫有名对象。
  • 匿名对象生命周期只在当前一行,一般临时定义一个对象当前用一下即可,就可以定义匿名对象。
代码语言:javascript
代码运行次数:0
运行
复制
#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;
}

7. 对象拷贝时的编译器优化

  • 现代编译器会为了尽可能提高程序的效率,在不影响正确性的情况下会尽可能减少一些传参和传返回值的过程中可以省略的拷贝。
  • 如何优化C++标准并没有严格规定,各个编译器会根据情况自行处理。当前主流的相对新一点的编译器对于连续一个表达式步骤中的连续拷贝会进行合并优化,有些更新更"激进"的编译器还会进行跨行跨表达式的合并优化。
代码语言:javascript
代码运行次数:0
运行
复制
#include <iostream>

using namespace std;

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

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

	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;

		if (this != &aa)
		{
			_a = aa._a;
		}

		return *this;
	}

	~A()
	{
		cout << "~A()" << endl;
	}

private:
	int _a;
};

//void f1(const A& aa)
//{}
//
//int main()
//{
//	//引用传参
//	A aa1;
//	f1(aa1);
//	cout << endl;
//
//	f1(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()
{
	//连续一个表达式中,拷贝构造 + 拷贝构造 -> 优化为一个拷贝构造
	A ret = f2();
	cout << endl;

	//无法优化,建议尽量不要这样写
	A ret2;
	ret2 = f2();//赋值运算符重载
	cout << endl;

	return 0;
}

补充一个题:

计算日期到天数转换
计算日期到天数转换

这里用到的方法类似于我们实现日期类时的GetMonthDay函数:

代码语言:javascript
代码运行次数:0
运行
复制
#include <iostream>

using namespace std;

int main()
{
    int year, month, day;
    cin >> year >> month >> day;
    //计算1-N月的累计天数
    int monthDay1_N[12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
    int n = monthDay1_N[month - 1] + day;

    if (month > 2 && ((0 == year % 4 && year % 100 != 0) || (0 == year % 400)))
    {
        ++n;
    }

    cout << n << endl;

    return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-12-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 再谈构造函数
  • 2. 类型转换
  • 3. static成员
  • 4. 友元
  • 5. 内部类
  • 6. 匿名对象
  • 7. 对象拷贝时的编译器优化
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档