C++初阶(长期更新)第6讲:类和对象(下)

C++初阶(长期更新)第6讲:

类和对象(下)

++跟着潼心走,轻松拿捏C++,困惑通通走,一去不回头~欢迎开始今天的学习内容,++ ++你的支持就是博主最大的动力++ 。博主主页:潼心1412o-CSDN博客

前言

今天我们结束类和对象的内容,gogogo,出发喽~

6.1 构造函数的初始化

有两种方式,前面我们主要使用的方式是在函数体内直接赋值,第二种方式就是初始化列表

cpp 复制代码
#include<iostream>
using namespace std;

class Time
{
public:
	Time(int hour=1)
	 : _hour(hour)
	{
		 cout << "Time()" << endl;
	}
private:
	int _hour;
	};
class Date
{
public:
	Date(int& x, int year = 1, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
		, _t(12)
		, _ref(x)
		, _n(1)
		 {
		 }
	
		void Print() const
		{
		 cout << _year << "/" << _month << "/" << _day << endl;
		}
private:
	 int _year;
	 int _month;
	 int _day;
	
	Time _t; // 没有默认构造
	 int& _ref; // 引⽤
	 const int _n; // const
	 };
int main()
{
	int i = 0;
	Date d1(i);
	d1.Print();
	 return 0;
}

每个成员变量在初始化列表最多出现一次

引用成员变量 const成员变量 没有默认构造的类类型变量 必须在初始化列表位置进行初始化

C++11支持在变量声明的的位置给缺省值

所有成员变量都会走初始化列表,且初始化顺序以声明顺序进行

cpp 复制代码
class MyQueue
{
public:
	MyQueue(int n=4)
		:_pushst(n)
		,_popst(n)
		,_size(0)
	{ }
private:
	Stack _pushst;
	Stack _popst;
	int _size;
};
	
//单链表
struct SListNode
{
	SListNode* _next;
	int _val;
};

class SList
{
public:
private:
	SListNode* _head = nullptr;
};

//双向循环链表
struct DListNode
{
	DListNode* _prev;
	DListNode* _next;
	int _val;
};

class DList
{
public:
	DList(int n=4)
		:_head((DListNode*)malloc(sizeof(DListNode)*4))
	{ 
		if (_head == nullptr)
		{
			perror("malloc fail!");
			return;
		}
		_head->_next = _head->_prev = _head;
	}
private:
	DListNode* _head;
};

总而言之,显示在初始化列表初始化的成员变量按初始化列表,

未显示在初始化列表的成员变量,若类中声明位置有缺省值则按缺省值初始化,

声明无缺省值内置类型成员编译器随机0或随机值,自定义类型调用成员的默认构造函数

6.2 类型转换

C++支持内置类型和类类型的隐式转换,需要有相关内置类型为参数的构造函数

构造函数前加explicit就不再支持隐式类型转换

类类型之间也可转换,需提供对应构造函数

cpp 复制代码
class A
{
public:
	A(int aa)
		:_a1(aa)
	{
		cout << "A(int aa)" << endl;
	}

	A(int a1, int a2)
		:_a1(a1)
		, _a2(a2)
	{
	}

	A(const A& aa)
		:_a1(aa._a1)
		, _a2(aa._a2)
	{
		cout << "A(const A& aa)" << endl;
	}

	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}

private:
//缺省值
	int _a1 = 0;
	int _a2 = 0;
};

int main()
{
	A aa1 = 1;
	aa1.Print();
    //临时对象具有常性 必须加const
    //const A& r1 = 1;
    //const A& r2 = {1,2};
	return 0;
}

实际上这里发生了两次构造,第一次拷贝是1传参构造一个A类型的临时对象,第二次则是临时对象进行拷贝构造给aa1,但是编译器会进行优化,直接为一次构造,比如运行以上代码在VSCode经过优化只会打印A(int aa)而非还有一句A(const A& aa)

6.3 static成员

static修饰的成员变量叫做静态成员变量

静态成员变量属于整个类所有对象,而不属于某个对象,在类内进行声明,类外进行初始化

cpp 复制代码
//计算一个程序出现了多少个类对象
class A
{
public:
	A(int x = 0)
	{
		++_acount;
	}
	A(const A& a)
	{
		++_acount;
	}
	static int Getacount()
	{
		return _acount;
	}
private:
	//声明
	static int _acount;
};
//初始化
int A::_acount = 0;

int main()
{
	cout << A::Getacount() << endl;
	return 0;
}

下面看道题

支持变长数组代码如下

cpp 复制代码
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();
	}
};
cpp 复制代码
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 = new Sum[n];
		return Sum::GetRet();
	}
};
int main()
{
	Solution s;
	cout << s.Sum_Solution(10) << endl;
	return 0;
}

6.4 友元

友元提供了一种突破得访问限定符封装的方式,分为友元函数和友元类

在类中进行友元函数友元类的声明,并在前面加上friend

外部友元函数可访问类的私有和保护成员,友元函数仅仅是⼀种声明,他不是类的成员函数。
友元函数可以在类定义的任何地⽅声明,不受类访问限定符限制。
⼀个函数可以是多个类的友元函数。
友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
友元类的关系是单向的,不具有交换性,⽐如A类是B类的友元,但是B类不是A类的友元。 •
友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是C的友元。
有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多⽤。

cpp 复制代码
//前置声明
class B;

class A
{
public:
	friend void func(const A& aa, const B& bb);
private:
	int _a1 = 1;
	int _a2 = 2;
};

class B
{
public:
	friend void func(const A& aa, const B& bb);
private:
	int _b1 = 3;
	int _b2 = 4;
};

void func(const A& aa, const B& bb)
{

}

6.5 内部类

一个类定义在类的内部,就叫内部类

内部类是一个独立的类,跟定义在全局的类相比,只是多了类域和访问限定符的限制

外部类定义的对象中不包含内部类

内部类默认是外部类的友元
内部类本质也是⼀种封装,当A类跟B类紧密关联,A类实现出来主要就是给B类使⽤,那么可以考
虑把A类设计为B的内部类,如果放到private/protected位置,那么A类就是B类的专属内部类,其
他地⽅都⽤不了。

cpp 复制代码
class A
{
public:
	class B
	{
	public:
		friend void func(const A& aa, const B& bb);
	private:
		int _b1 = 3;
		int _b2 = 4;
	};
	friend void func(const A& aa, const A::B& bb);
private:
	int _a1 = 1;
	int _a2 = 2;
};

void func(const A& aa, const A::B& bb)
{
	cout << aa._a1 << endl;
	cout << bb._b1 << endl;
}

void Test03()
{
	A a;
	A::B b;
	func(a,b);
}

再看上面那道题

cpp 复制代码
class Solution {
public:
	class Sum
	{
	public:
		Sum()
		{
			_ret += _i;
			++_i;
		}
		static int GetRet()
		{
			return _ret;
		}
	};
	int Sum_Solution(int n) {
		Sum* arr = new Sum[n];
		return Sum::GetRet();
	}
private:
	static int _i;
	static int _ret;
};

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

void Test04()
{
	Solution s;
	cout << s.Sum_Solution(10) << endl;
}

6.6 匿名对象

顾名思义,匿名对象就是没有名字的对象

与有名对象生命周期存在于整个类域相对,匿名对象的生命周期只在一行

cpp 复制代码
void Test05()
{
	//有名对象
	A a1;
	A a2(2);
	//匿名对象
	A();
	A(4);
}
cpp 复制代码
void Test04()
{
	Solution s;
	cout << s.Sum_Solution(10) << endl;
    //两种写法等效
	cout << Solution().Sum_Solution(10) << endl;
}

6.7 拷贝构造编译器优化问题

现代编译器会为了尽可能提⾼程序的效率,在不影响正确性的情况下会尽可能减少⼀些传参和传返
回值的过程中可以省略的拷⻉。
如何优化C++标准并没有严格规定,各个编译器会根据情况⾃⾏处理。当前主流的相对新⼀点的编
译器对于连续⼀个表达式步骤中的连续拷⻉会进⾏合并优化,有些更新更"激进"的编译器还会进⾏
跨⾏跨表达式的合并优化。
linux下可以将下⾯代码拷⻉到test.cpp⽂件,编译时⽤ g++ test.cpp -fno-elide
constructors 的⽅式关闭构造相关的优化。

cpp 复制代码
#include<iostream>
using namespace std;
class A
{
public:
	A(int a = 0)
		:_a1(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& aa)
		:_a1(aa._a1)
	{
		cout << "A(const A& aa)" << endl;
	}
	A& operator=(const A& aa)
	{
		cout << "A& operator=(const A& aa)" << endl;
		if (this != &aa)
		{
			_a1 = aa._a1;
		}
		return *this;
	}
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a1 = 1;
};
void f1(A aa){}
A f2()
{
	A aa;
	return aa;
}
int main()
{
// 传值传参
// 构造+拷⻉构造
	A aa1;
	f1(aa1);
	cout << endl;
// 隐式类型,连续构造+拷⻉构造->优化为直接构造
	f1(1);
	// ⼀个表达式中,连续构造+拷⻉构造->优化为⼀个构造
	f1(A(2));
	cout << endl;
	cout << "***********************************************" << endl;
	// 传值返回
	// 不优化的情况下传值返回,编译器会⽣成⼀个拷⻉返回对象的临时对象作为函数调⽤表达式的返回值
	// ⽆优化 (vs2019 debug)
	// ⼀些编译器会优化得更厉害,将构造的局部对象和拷⻉构造的临时对象优化为直接构造(vs2022 debug)
	f2();
	cout << endl;
	// 返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造 (vs2019 debug)
	// ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,将构造的局部对象aa和拷⻉的临时对象和接收返回值对象aa2优化为⼀个直接构造。(vs2022 debug)
	A aa2 = f2();
	cout << endl;
	// ⼀个表达式中,开始构造,中间拷⻉构造+赋值重载->⽆法优化(vs2019 debug)
	// ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,将构造的局部对象aa和拷⻉临时对象合并为⼀个直接构造(vs2022 debug)
	aa1 = f2();
	cout << endl;
	return 0;
	}

++好了今天的学习内容就到这里啦,谢谢你的陪伴,我是潼心,下次再见~如果这篇文章对你有帮助的话。请务必给主播一个一键三连,球球了,这对主播很重要~++

博主主页:潼心1412o-CSDN博客

相关推荐
星光开发者1 小时前
基于决策树随机森林算法的小说推荐系统的设计与实现-计算机毕设【课程设计】78485
vue.js·算法·微信小程序·django·php·课程设计·express
夜晚回家1 小时前
Python 零基础入门 | 输入的数字为什么不能直接算
开发语言·python
时间的拾荒人1 小时前
Qt 事件机制全解析:从事件处理到事件过滤器
开发语言·qt·面试
-森屿安年-1 小时前
按身高排序
c++·算法
于指尖飞舞1 小时前
java面试题总结
java·开发语言
程序员清风2 小时前
算法与数据结构入门:复杂度、数组与链表
数据结构·算法·链表
无定义_2 小时前
Codeforces Round 1124 (Div. 2)
算法
(Charon)2 小时前
【C++面试】vector底层原理:扩容、拷贝移动与迭代器失效
c++·面试
zhangzeyuaaa3 小时前
深入理解 Ruby 运算符:本质、分类、坑点与重载实战
开发语言·前端·ruby