【类和对象】收尾总结

目录

一、初始化列表

1.格式要求

(1) 初始化列表初始化

①括号中是初始值

②括号中是表达式

(2) 初始化列表和函数体混用

 2.特点

①初始化时先走初始化列表,再走函数体

②拷贝构造函数属于特殊的构造函数,函数内也可以使用初始化列表进行初始化

③成员变量实际初始化的顺序取决于声明顺序,与在初始化列表中出现的次序无关

④ 对同一变量,初始化列表进行初始化和函数体内进行赋值 并不冲突(合乎情理)

⑤大前提:内置类型的成员变量若没有在初始化列表中进行初始化,也没有在函数体内赋初值

若声明时给了缺省值,则使用该缺省值,若声明时没给缺省值,则是随机值

⑥ 自定义类型成员变量若没有在初始化列表中进行初始化,则会去调用它的默认构造函数,否则不会调用

3.应用场景

①引用成员变量

②const成员变量

③自定义类型成员且该成员没有默认构造函数时

二、static成员

1.特点:

(1) static修饰的静态成员为所有类对象共享,不属于某个具体对象,位于静态区

(2) 静态成员变量必须在类外面定义,定义时不加static关键字

(3) static修饰的成员变量不能在声明时给缺省值

(4) 静态成员函数没有隐藏的this指针,不能访问任何非静态的成员变量

(5) static成员函数和非static成员函数是可以互相调用的

2.应用

(1) 典型题目:计数

(2) OJ题目

三、友元

1.友元函数

(1) 引入背景:

(2) 特点:

①友元函数不是类的成员函数

②友元函数不能用const修饰

③友元函数可以在类定义的任何地方声明,不受类访问限定符限制

④一个函数可以是多个类的友元函数

2.友元类

特点

①友元关系是单向的,不具有交换性

②友元关系不能传递

③友元关系不能继承

四、内部类

1. 特性

(1) 内部类定义在外部类的public, private, protected均可以

(2) 内部类只是定义在了外部类的内部,内部类并不属于外部类,不能通过外部类的对象去访问内部类的成员,外部类对内部类没有任何优越的访问权限

(3) 内部类天生就是外部类的有元(外部类默认不是内部类的有元)

2.应用

五、匿名对象与隐式类型转换

1.匿名对象

(1) 特点---匿名对象的声明周期只在当前这一行

(2) 用途---简化代码

 (3) const引用匿名对象---延长匿名对象的生命周期

2.隐式类型转化

 (1) 基本用法

​编辑

(2) 用途---简化代码

(3) explicit关键字

六、拷贝对象时编译器的优化

1.优化类型一:一个表达式中,构造函数与拷贝构造函数紧接执行,优化成直接构造

2.优化类型二:连续的两次拷贝构造优化成一次拷贝构造

 3.优化类型三:连续的 一次构造+两次拷贝 优化成直接构造

(1) 匿名对象:

 (2)单参数隐式类型转化


一、初始化列表

初始化列表是构造函数的一部分,我们已经知道构造函数是用来初始化对象的,而之前在函数体内对成员变量的操作严谨来说不叫初始化,而是赋值,而初始化列表才是对成员变量真正进行初始化

1.格式要求

以冒号开始,接着是以逗号分割的成员列表,每个成员变量后跟一个放在括号中的初始值或表达式

初始化列表初始化

(1) 初始化列表初始化

①括号中是初始值

class Date
{
public:
	Date(int year, int month, int day) 
		//初始化列表
		:_year(year)
		,_month(month)
		,_day(day)
	{} //函数体
private:
	int _year;
	int _month;
	int _day;
};

②括号中是表达式

#include<iostream>
class Stack
{
public:
	Stack(int capacity = 4)
		//初始化列表
		:_top(0)
		, _capacity(capacity)
		, _a((int*)malloc(sizeof(int)* _capacity))
	{} //函数体
private:
	int _top;
	int _capacity;
	int* _a;
};

(2) 初始化列表和函数体混用

class Date
{
public:
	Date(int year, int month, int day)
		//初始化列表进行初始化
		:_year(year)
		, _month(month)
	{
		//赋值
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

 2.特点

①初始化时先走初始化列表,再走函数体

②拷贝构造函数属于特殊的构造函数,函数内也可以使用初始化列表进行初始化

③成员变量实际初始化的顺序取决于声明顺序,与在初始化列表中出现的次序无关

class Date
{
public:
	Date(int year, int month, int day)
		//初始化顺序是_year _month _day
		:_month(month)
		,_year(year)
		,_day(day)
	{}
private:
	//声明顺序是 _year _month  _day
	int _year;
	int _month;
	int _day;
};

④ 对同一变量,初始化列表进行初始化和函数体内进行赋值 并不冲突(合乎情理)

class Date
{
public:
	Date(int year, int month, int day)
		//初始化列表进行初始化
		:_year(year)
		,_month(month)
		,_day(day)
	{
		//赋值
		_day = 5;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d(2023, 8, 10);
	//d 最终是 2023  8  5
}

⑤大前提:内置类型的成员变量若没有在初始化列表中进行初始化,也没有在函数体内赋初值

若声明时给了缺省值,则使用该缺省值,若声明时没给缺省值,则是随机值

#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year, int month, int day)
		//初始化列表进行初始化
		:_year(year)
		,_month(month)
	{}
	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day = 1;
};
int main()
{
	Date d(2023, 8, 10);
	d.Print(); //2023年8月1日
}
#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year, int month, int day)
		//初始化列表进行初始化
		:_year(year)
		,_month(month)
	{}
	void Print()
	{
		cout << _year << "年" << _month << "月" << _day << "日" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d(2023, 8, 10);
	d.Print(); //2023年8月-858993460日
}

⑥ 自定义类型成员变量若没有在初始化列表中进行初始化,则会去调用它的默认构造函数,否则不会调用

//不会调用A的默认构造函数
#include<iostream>
using namespace std;
class A
{
public:
	A(int x = 1, int y = 1)
		:_x(x)
		,_y(y)
	{}
private:
	int _x;
	int _y;
};
class Date
{
public:
	Date(int year, int month, int day, A a)
		//初始化列表进行初始化
		:_year(year)
		,_month(month)
		,_day(day)
		,_a(a)
	{}
private:
	int _year;
	int _month;
	int _day;
	A _a;
};
int main()
{
	A a;
	Date d(2023, 8, 10, a);
}
//会调用A的默认构造函数
#include<iostream>
using namespace std;
class A
{
public:
	A(int x = 1, int y = 1)
		:_x(x)
		,_y(y)
	{}
private:
	int _x;
	int _y;
};
class Date
{
public:
	Date(int year, int month, int day, A a)
		//初始化列表进行初始化
		:_year(year)
		,_month(month)
		,_day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
	A _a;
};
int main()
{
	A a;
	Date d(2023, 8, 10, a);
}

3.应用场景

类中包含以下成员,必须使用初始化列表进行初始化

①引用成员变量

之前博客提到过,引用和指针是不一样的,引用类型的变量必须要初始化

#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year, int month, int day, int& i)
		//初始化列表进行初始化
		:_year(year)
		, _month(month)
		, _day(day)
		,_refi(i)
	{}
private:
	int _year;
	int _month;
	int _day;
	int& _refi;
};
int main()
{
	int n = 0;
	Date d(2023, 8, 10, n);
}

②const成员变量

#include<iostream>
using namespace std;
class Date
{
public:
	Date(int year, int month, int day, int i)
		//初始化列表进行初始化
		:_year(year)
		, _month(month)
		,_day(day)
		,_x(i)
	{}
private:
	int _year;
	int _month;
	int _day;
	const int _x;
};
int main()
{
	int i = 0;
	Date d(2023, 8, 10, i);
}

③自定义类型成员且该成员没有默认构造函数时

上述初始化列表特点⑥就说的是这点,此处就不再赘述了

二、static成员

被static修饰的成员变量称为静态成员变量,被static修饰的成员函数称为静态成员函数,两者统称为静态成员

1.特点:

(1) static修饰的静态成员为所有类对象共享,不属于某个具体对象,位于静态区

证明一下:

#include<iostream>
using namespace std;
class A
{
private:
	int _x;
	int _y;
	static int _z;
};
int main()
{
	A a;
	cout << sizeof(A) << endl; //8个字节
}

(2) 静态成员变量必须在类外面定义,定义时不加static关键字

#include<iostream>
using namespace std;
class A
{
public:
	A(int x, int y, int z)
		:_x(x)
		,_y(y)
		,_z(z) //(×)
	{}
private:
	int _x;
	int _y;
	static int _z;
};
int main()
{
	A a(1, 1, 1);
}
#include<iostream>
using namespace std;
class A
{
public:
	A(int x, int y, int z)
		:_x(x)
		,_y(y)
	{}
	void Print()
	{
		cout << _x << " " << _y << " " << _z;
	}
private:
	int _x;
	int _y;
	static int _z;
};

int A::_z = 2; //类外面进行初始化

int main()
{
	A a(1, 1, 1);
	a.Print(); //1 1 2
}

(3) static修饰的成员变量不能在声明时给缺省值

之前博客提到过,C++11允许在成员变量声明时给定缺省值, 但是若成员变量被static修饰了,就不能给缺省值了

#include<iostream>
using namespace std;
class A
{
public:
	A(int x, int y, int z)
		:_x(x)
		,_y(y)
	{}
private:
	int _x = 1;//(√)
	int _y = 1;//(√)
	static int _z = 1; //(×)
};
int main()
{
	A a(1, 1, 1);
}

ps:const修饰的静态整形成员变量可以在声明时给缺省值(这是个特例),其他类型(float,double等)都不可以(注意,char是属于整形家族的,因为存储和使用的是字符的ASCII值)

#include<iostream>
using namespace std;
class A
{
public:
	A(int x, int y, int z)
		:_x(x)
		,_y(y)
	{}
private:
	int _x = 1;//(√)
	int _y = 1;//(√)
	const static int _z = 1; //(√)
	const static char _z = 'w'; //(√)
	const static double _w = 1.1; //(×)
	const static float _w = 1.1; //(×)
};
int main()
{
	A a(1, 1, 1);
}

(4) 静态成员函数没有隐藏的this指针,不能访问任何非静态的成员变量

构造函数是不能被static修饰的,因为构造函数默认是有this指针的,且构造函数是针对类实例化出的具体对象进行初始化的,而static修饰的成员函数位于静态区

#include<iostream>
using namespace std;
class A
{
public:
	static void Print()
	{
		//cout << _x << _y << endl; //(×) 静态成员函数不能访问非静态成员变量
		cout << _z << endl;//(√)
	}
private:
	int _x = 1;
	int _y = 1;
	static int _z;
};
int A::_z = 2;
int main()
{
	A::Print(); //2
}

(5) static成员函数和非static成员函数是可以互相调用的

//非static调用static
#include<iostream>
using namespace std;
class A
{
public:
	A()
	{
		Print();
	}
	static void Print()
	{
		cout << "static void Print()" << endl;
	}
private:
	int _x = 1;
	int _y = 1;
	int _z = 1;
};

int main()
{
	A::A(); //static void Print()
}
//static调用非static
#include<iostream>
using namespace std;
class A
{
public:
	A()
	{
		cout << "A()" << endl;
	}
	static void Print()
	{
		A();
	}
private:
	int _x = 1;
	int _y = 1;
	int _z = 1;
};
int main()
{
	A::Print(); //A()
}

2.应用

(1) 典型题目:计数

 实现一个类,计算程序中创建出了多少个对象以及正在使用多少个对象

#include<iostream>
using namespace std;

int n = 0; //累计创建了多少个对象
int m = 0; //正在使用的有多少个对象
class A
{
public:
	A()
	{
		++n;
		++m;
	};
	A(const A& a)
	{
		++n;
		++m;
	};
	~A()
	{
		--m;
	}
};

A Func(A aa)
{
	return aa;
}
int main()
{
	A aa1;
	cout << "累计创建对象: " << n << " " << "正在使用对象: " << m << endl; // 1 1
	
	A aa2;
	Func(aa2);
	cout << "累计创建对象: " << n << " " << "正在使用对象: " << m << endl; //4 2
}

上述代码利用全局变量解决了题目要求,但是并没有很好的体现C++封装的特点,这时static修饰的成员变量就派上用场了

#include<iostream>
using namespace std;
class A
{
public:
	A()
	{
		++_n;
		++_m;
	};
	A(const A& a)
	{
		++_n;
		++_m;
	};
	~A()
	{
		--_m;
	}
	void Print()
	{
		cout << "累计创建对象: " << _n << " " << "正在使用对象: " << _m << endl; 
	}
private:
	static int _n;
	static int _m;
};
//静态成员变量初始化
int A::_n = 0; //累计创建了多少个对象
int A::_m = 0; //正在使用的有多少个对象
A Func(A aa)
{
	return aa;
}
int main()
{
	A aa1;
	aa1.Print(); //1 1

	A aa2;
	Func(aa2);
	aa2.Print(); //4 2
}

(2) OJ题目

OJ题目链接:求1+2+3+···n

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 a[n]; //g++支持变长数组 Sum a[n]
        return Sum::GetRet();
    }
};

三、友

<<日期类总结>>这篇文章已经浅浅提到了友元函数,我们现在再来总结一下

友元提供了一种突破封装(类域)的方式,有时可以提供遍历,但是友元函数会增加耦合度,所以友元函数不宜多用

友元分为友元函数和友元类

1.友元函数

 友元函数顾名思义,本来类外面的函数是无法访问类中的私有成员变量的,但是若func函数是B对象的有元函数,其实就是func函数是B的好朋友,因此就可以访问B对象的私有成员变量了

(1) 引入背景:

    《日期类汇总》中流插入与流提取运算符重载部分

(2) 特点:

①友元函数不是类的成员函数

②友元函数不能用const修饰

③友元函数可以在类定义的任何地方声明,不受类访问限定符限制

④一个函数可以是多个类的友元函数

2.友元类

有元函数讲的是一个函数是另外一个类的好朋友,而有元类A指的是A类中的所有函数都是类B的好朋友,A中的所有成员函数都可以访问到类B的私有成员变量

特点

①友元关系是单向的,不具有交换性

 在Time类中,声明了Date是Time的朋友,因此Date类中的SetTimeofDate()函数可以直接访问Time的私有成员变量_hour,_minute,_second;但是Time类中的ReverseUse函数无法访问Date的私有成员变量_year, _month, _day

class Time
{
	//声明友元类
	friend class Date;
public:
	Time(int hour = 1, int minute = 1, int second = 1)
	{
		_hour = hour;
		_minute = minute;
		_second = second;
	}
	void ReverseUse(int year = 1, int month = 1, int day = 1)
	{
		_d._year = year; //(×)
		_d._month = month; //(×)
		_d._day = day; //(×)
	}
private:
	int _hour;
	int _minute;
	int _second;
	Date _d;
};

class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	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;
};

②友元关系不能传递

 Date类是Time的友元,ClassThird类是Date的有元,推不出来ClassThird是Time的友元

class Time
{
	friend class Date; //Date是Time的朋友
	/*friend class ClassThird; */
public:
	Time(int hour = 1, int minute = 1, int second = 1)
	{
		_hour = hour;
		_minute = minute;
		_second = second;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
	friend class ClassThird; //ClassThird是Date的朋友
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

class ClassThird
{
public:
	void Test(int hour = 1, int minute = 1, int second = 1) 
	{
		//不能说 ClassThird 是 Time 的朋友
		_t._hour = hour; //(×)
		_t._minute = minute; //(×)
		_t._second = second; //(×)
	}
private:
	int _x;
	int _y;
	int _z;
	Time _t;
};

③友元关系不能继承

继承我们还没有介绍到,后续再解释该点

四、内部类

B类定义在了A类的里面,则称B类是A类的内部类,A是外部类

1. 特性

(1) 内部类定义在外部类的public, private, protected均可以

class A
{
public://(√)
private://(√)
protected://(√)
	class B
	{
	private:
		int _b;
	};
private:
	int _a;
};

(2) 内部类只是定义在了外部类的内部,内部类并不属于外部类,不能通过外部类的对象去访问内部类的成员,外部类对内部类没有任何优越的访问权限

 验证内部类不属于外部类:

#include<iostream>
using namespace std;
class A
{
public:
	class B
	{
	private:
		int _b;
	};
private:
	int _a;
};
int main()
{
	cout << sizeof(A) << endl; //4
}

(3) 内部类天生就是外部类的有元(外部类默认不是内部类的有元)

#include<iostream>
using namespace std;
class A
{
public:
	class B
	{
	public:
		void Print(const A& a)
		{
			cout << a._a << endl;
		}
	private:
		int _b;
	};
private:
	int _a = 1;
};
int main()
{
	A a;
	A::B b;
	b.Print(a);
}
#include<iostream>
using namespace std;
class A
{
public:
	class B
	{
	public:
		void Print()
		{
			cout << _aa << endl;
		}
	private:
		int _b;
	};
private:
	static int _aa;
};
int A::_aa = 1;
int main()
{
	A::B b;
	b.Print();
}

2.应用

//内部类的使用简化OJ题目---1+2+3+···n
class Solution {
    class Sum {
    public:
        Sum() {
            _ret += _i;
            _i++;
        }
    };
public:
    int Sum_Solution(int n) {
        Sum a[n]; //g++支持变长数组
        return _ret;;
    }
private:
    static int _i;
    static int _ret;
};
int Solution::_i = 1;
int Solution::_ret = 0;

五、匿名对象与隐式类型转换

1.匿名对象

一直以来我们定义对象的方式都是 类名 + 对象名,这种方式定义出来的对象叫做有名对象, 而匿名对象顾名思义就是名字隐藏起来了,没有给对象起名字,这时我们就说创建了一个匿名对象

(1) 特点---匿名对象的声明周期只在当前这一行

有名对象的声明周期是在局部作用域的,而匿名对象只在当前一行,因此匿名对象只能用一次

class A
{
public:
	~A()
	{
		cout << "~A()" << endl;
	}
private:
	int _a;
};
int main()
{
	A(); //匿名对象
	cout << "hello wolrd" << endl;
}

(2) 用途---简化代码

 如果对象只使用一次,那么我们可以直接采用匿名对象就能避免创建有名对象之后再去调用函数

class A
{
public:
	A(int x = 1, int y = 1)
	{
		_x = x;
		_y = y;
	}
private:
	int _x;
	int _y;
};
typedef A DataType;
//栈中存储的是A类型的对象

class Stack
{
public:
	Stack(int capacity = 10)
	{
		_top = 0;
		_capacity = capacity;
		_a = (DataType*)malloc(sizeof(DataType) * _capacity);
	}
	void Push(DataType x)
	{
		_a[_top] = x;
		_top++;
	}
private:
	 DataType* _a;
	 int _top;
	 int _capacity;
};

int main()
{
	Stack s;
	//正常入栈写法
	A a1(1, 2);
	s.Push(a1);
	A a2(3, 4);
	s.Push(a2);
	A a3(5, 6);
	s.Push(a3);
	//匿名对象简化代码
	s.Push(A(1, 2));
	s.Push(A(3, 4));
	s.Push(A(5, 6));
}

 (3) const引用匿名对象---延长匿名对象的生命周期

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

int main()
{
	const A& ref = A();
	cout << "hello world" << endl;
}

可以看到,在匿名对象使用那一行代码执行完之后,并没有调用析构函数,而是当main函数即将结束时,调用了析构函数,说明const引用延长了匿名对象生命周期, 和ref生命周期一致

2.隐式类型转化

 (1) 基本用法

int main()
{
	int a = 0;
	double b = a;
}

这就是一个很简单的隐式类型转换的例子,"隐式"二字表明类型转换是编译器自动完成的

而我们下面重点介绍的是整形隐式转化成类对象的过程

class A
{
public:
	A(int i)
		:_a(i)
	{
		cout << "A(int i)" << endl;
	}
	A(const A& aa)
		:_a(aa._a)
	{
		cout << "A(const A& aa)" << endl;
	}
private:
	int _a;
};
int main()
{
	//正常创建对象
	A aa1(1);
	
	A aa2 = 2; //这句代码和上面代码本质是一样的,都是在创建A类型的对象
	//具体过程:编译器使用整形2调用构造函数生成一个临时对象,然后再用这个临时对象拷贝构造aa2
	//上述过程称为单参数构造函数的隐式类型转换
}

运行代码之后发现没有并没有调用拷贝构造,这时因为编译器做了优化而已,这点我们下面再讲

证明一下生成了临时对象(具有常性):

 A& ref = 2;//×(权限放大)
 const A& ref = 2; //√(权限平移)

值得一提的是,上面介绍的是单参数构造函数的隐式类型转化,除了单参数外,C++11还支持多参数构造函数的隐式类型转化

class B
{
public:
	B(int b1, int b2)
		:_b1(b1)
		, _b2(b2)
	{
		cout << "B(int b1, int b2)" << endl;
	}
private:
	int _b1;
	int _b2;
};
int main()
{
	//C++11 支持多参数的隐式类型转换
	B bb1(1, 1);
	B bb2 = { 2, 2 };
	const B& ref2 = { 3,3 };
}

(2) 用途---简化代码

匿名对象能简化代码,隐式类型转化同样可以简化代码,还是举个栈的栗子~

class A
{
public:
	A(int x = 1)
	{
		_x = x;
	}
private:
	int _x;
};
typedef A DataType;
//栈中存储的是A类型的对象
class Stack
{
public:
	Stack(int capacity = 10)
	{
		_top = 0;
		_capacity = capacity;
		_a = (DataType*)malloc(sizeof(DataType) * _capacity);
	}
	void Push(DataType x)
	{
		_a[_top] = x;
		_top++;
	}
private:
	DataType* _a;
	int _top;
	int _capacity;
};
int main()
{
	Stack s;
	//正常入栈写法
	A a1(1);
	s.Push(a1);
	A a2(2);
	s.Push(a2);
	A a3(3);
	s.Push(a3);
	//隐式类型转化简化代码
	s.Push(1);
	s.Push(2);
	s.Push(3);
}

(3) explicit关键字

explicit关键字是专门用来修饰构造函数,禁止其发生隐式类型转化的~

class A
{
public:
	explicit A(int a)
		:_a(a)
	{
		cout << "explicit A(int a)" << endl;
	}
private:
	int _a;
};
int main()
{
	A a1(1);
	A a2 = 2; //(×)
	const A& ref3 = 3; //(×)
}

六、拷贝对象时编译器的优化

在代码实际执行过程中,编译器经常会做很多优化,此处我们讲解的是涉及到拷贝对象时编译器进行的优化操作,提高了程序运行的效率

1.优化类型一:一个表达式中,构造函数与拷贝构造函数紧接执行,优化成直接构造

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A()" << endl;
	}
	A(const A& a)
		:_a(a._a)
	{
		cout << "const A& a" << endl;
	}
private:
	int _a = 0;
};
void f1(A aa)
{}
int main()
{
	//第①组
	//先调用构造函数创建aa1对象,再调用拷贝构造函数将aa1对象拷贝给aa对象
	A aa1(1);
    f1(aa1);
	cout << "-----------------------------" << endl;
	
	//第②组
	//本来:先调用构造函数创建匿名对象,再调用拷贝构造将匿名对象拷贝给aa对象
	//编译器优化:一个表达式,连续的步骤里,连续的构造会被合并
	//实际:直接调用构造函数传1创建aa对象
	f1(A(1));
	cout << "-----------------------------" << endl;

	//第③组
	//本来:先调用构造函数创建匿名对象,再调用拷贝构造将匿名对象拷贝给aa3对象
	//实际:直接调用构造函数传1创建aa3对象
	A aa3 = A(1);
	cout << "-----------------------------" << endl;

	//第④组
	//本来:先进行构造函数的单参数的隐式类型转换创建临时对象,再调用拷贝构造函数将临时对象拷贝给aa对象
	//实际:直接调用构造函数传1创建aa对象
	f1(1);
	cout << "-----------------------------" << endl;

	//第⑤组
	//本来:先进行构造函数的单参数的隐式类型转换创建临时对象,再调用拷贝构造函数将临时对象拷贝给aa2对象
	//实际:直接调用构造函数传1创建aa2对象
	A aa2 = 1;
}

 

2.优化类型二:连续的两次拷贝构造优化成一次拷贝构造


class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A()" << endl;
	}
	A(const A& a)
		:_a(a._a)
	{
		cout << "const A& a" << endl;
	}
private:
	int _a = 0;
};
A f2()
{
	A aa;
	return aa; //返回局部变量或对象时,会生成该变量或者对象的临时拷贝
}
int main()
{
	//本来应该是两次拷贝构造,但经过编译器的优化,aa直接拷贝构造ret1,省去了拷贝构造临时对象的步骤
	A ret1 = f2(); 
}

 3.优化类型三:连续的 一次构造+两次拷贝 优化成直接构造

(1) 匿名对象:

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A()" << endl;
	}
	A(const A& a)
		:_a(a._a)
	{
		cout << "const A& a" << endl;
	}
private:
	int _a = 0;
};
A f2()
{
	return A(1); //本来应该先调用构造函数传1创建匿名对象,再拷贝构造给临时对象
}
int main()
{
	A ret1 = f2(); //再将临时对象拷贝给ret1
	//经过编译器优化,直接用1构造对象A
}

 (2)单参数隐式类型转化

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A()" << endl;
	}
	A(const A& a)
		:_a(a._a)
	{
		cout << "const A& a" << endl;
	}
private:
	int _a = 0;
};
A f2()
{
	return 2; //本来先调用构造函数,将2隐式类型转化成类类型,创建出临时对象1,再调用拷贝构造函数将临时对象1拷贝构造给临时对象2
}
int main()
{
	A ret2 = f2(); //再将临时对象2拷贝构造给ret2
	//编译器优化后,直接用2构造对象ret2
}

 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

【类和对象】内容就此完结,这部分知识是C++中很关键的部分,将为后续的学习打下坚实础~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/68804.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【c语言】指针进阶(超详细)

文章目录 ✈ 指向函数指针数组的指针&#x1f4cc;指向函数指针数组的指针的定义&#x1f4cc;指向函数指针数组的数组指针的使用 ✈回调函数&#x1f4cc; 回调函数的定义&#x1f4cc; 回调函数的使用 ✈qsort函数&#x1f4cc; qsort函数的作用&#x1f4cc;qsort函数的定义…

Rikka with Square Numbers 2023“钉耙编程”中国大学生算法设计超级联赛(8)hdu7370

Problem - 7370 题目大意&#xff1a;给出两个数a&#xff0c;b&#xff0c;每次操作可以使其中一个数加上或减去一个任意的完全平方数&#xff0c;问要使a&#xff0c;b相等需要的最少操作次数是多少 1<a,b<1e9,a!b 思路&#xff1a;我们可以将问题转化为将a和b的差w…

最强的表格组件—AG Grid使用以及License Key Crack

PS: 想要官方 License Key翻到最后面 Ag Grid简介 Ag-Grid 是一个高级数据网格&#xff0c;适用于JavaScript/TypeScript应用程序&#xff0c;可以使用React、Angular和Vue等流行框架进行集成。它是一种功能强大、灵活且具有高度可定制性的表格解决方案&#xff0c;提供了丰富…

UNIX基础知识:UNIX体系结构、登录、文件和目录、输入和输出、程序和进程、出错处理、用户标识、信号、时间值、系统调用和库函数

引言&#xff1a; 所有的操作系统都为运行在其上的程序提供服务&#xff0c;比如&#xff1a;执行新程序、打开文件、读写文件、分配存储区、获得系统当前时间等等 1. UNIX体系结构 从严格意义上来说&#xff0c;操作系统可被定义为一种软件&#xff0c;它控制计算机硬件资源&…

博客项目(Spring Boot)

1.需求分析 注册功能&#xff08;添加用户操纵&#xff09;登录功能&#xff08;查询操作)我的文章列表页&#xff08;查询我的文章|文章修改|文章详情|文章删除&#xff09;博客编辑页&#xff08;添加文章操作&#xff09;所有人博客列表&#xff08;带分页功能&#xff09;…

Games101学习笔记2

参考博客&#xff1a;GAMES101 梳理 / 个人向图形学笔记_games101笔记_river_of_sebajun的博客-CSDN博客 lecture 05 Rasterization 1(Triangles) 光栅化 把东西画在屏幕上的过程就是光栅化的过程 视口变换 为什么模型用三角形&#xff1f; 最基本的几何平面&#xff1b;保…

matplotlib fig.legend()常用参数 包括位置调整和字体设置等

一、四种方法 legend() legend(handles, labels) legend(handleshandles) legend(labels)1 legend() labels自动通过绘图获取&#xff08;Automatic detection of elements to be shown in the legend&#xff09; # 第一种方法 ax.plot([1, 2, 3], labelInline label) ax.l…

JVM、JRE、JDK三者之间的关系

JVM、JRE和JDK是与Java开发和运行相关的三个重要概念。 再了解三者之前让我们先来了解下java源文件的执行顺序&#xff1a; 使用编辑器或IDE(集成开发环境)编写Java源文件.即demo.java程序必须编译为字节码文件&#xff0c;javac(Java编译器)编译源文件为demo.class文件.类文…

力扣:59. 螺旋矩阵 II(Python3)

题目&#xff1a; 给你一个正整数 n &#xff0c;生成一个包含 1 到 n2 所有元素&#xff0c;且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全…

日期切换

组件&#xff1a;<template><div class"time-picker"><el-radio-group size"small" v-model"timeType" change"changePickerType"><el-radio-button label"hour" v-if"isShow">时</el…

Open_PN笔记

>>>仅用作学习用途 1.准备好需要用到的工具 官网下载地址&#xff1a; openvpn 客户端下载地址&#xff1a; https://swupdate.openvpn.org/community/releases/openvpn-install-2.4.5-I601.exe EasyRSA下载地址&#xff1a; https://githu…

Koan自动重装和Cobbler_web

Koan是Cobbler的辅助工具&#xff0c;可以实现很多功能&#xff0c;使用koan配合Cobbler可以实现快速重装Linux系统&#xff1a; 1、安装koan&#xff1a; yum install -y epel-releaseyum install -y koan 安装截图&#xff1a; 2、在客户机上&#xff0c;用koan选择要重装的…

可以重复烧写的语音ic有哪些特征和优势

目录 一、简介可擦写的语音芯片&#xff0c;其实就是MCUflash的架构&#xff0c;无其他说法&#xff0c;就这一种说法。这个就是它最大的特征尤其是SOP8的封装类型的芯片&#xff0c;是区别于OTP类型的另一个品类&#xff0c;基本上OTP的语音芯片适用的场景。他都可以满足和替代…

文件系统目录结构

1. 目录结构 linux的文件系统是采用级层式的树状目录结构&#xff0c;在此结构中的最上层是根目录/ &#xff0c;然后在此目录下再创建其他的目录。 在linux中&#xff0c;一切皆文件(Linux将所有的设备、文件、进程等都当做文件来处理) 2. 目录作用具体介绍 目录名解析/b…

js玩儿爬虫

前言 提到爬虫可能大多都会想到python&#xff0c;其实爬虫的实现并不限制任何语言。 下面我们就使用js来实现&#xff0c;后端为express&#xff0c;前端为vue3。 实现功能 话不多说&#xff0c;先看结果&#xff1a; 这是项目链接&#xff1a;https://gitee.com/xi1213/w…

【云原生】kubernetes中容器的资源限制

目录 1 metrics-server 2 指定内存请求和限制 3 指定 CPU 请求和限制 资源限制 在k8s中对于容器资源限制主要分为以下两类: 内存资源限制: 内存请求&#xff08;request&#xff09;和内存限制&#xff08;limit&#xff09;分配给一个容器。 我们保障容器拥有它请求数量的…

【Spring专题】Spring之Bean的生命周期源码解析——阶段一(扫描生成BeanDefinition)

目录 前言阅读指引阅读建议 课程内容一、生成BeanDefinition1.1 简单回顾1.2 概念回顾1.3 核心方法讲解 二、方法讲解2.1 ClassPathBeanDefinitionScanner#scan2.2 ClassPathBeanDefinitionScanner#doScan2.3 ClassPathScanningCandidateComponentProvider#findCandidateCompon…

SpringBoot 整合JDBC

SpringData简介 Sping Data 官网&#xff1a;https://spring.io/projects/spring-data数据库相关的启动器 &#xff1a;可以参考官方文档&#xff1a;https://docs.spring.io/spring-boot/docs/2.6.5/reference/htmlsingle/#using-boot-starter 整合JDBC 创建测试项目测试数据…

访企聚力促创新:长安大学来访闪马智能

7月31日&#xff0c;长安大学运输工程学院院长葛颖恩教授、学院副书记李婷以及学办主任董彬一行来访闪马智能&#xff0c;闪马智能创始人兼CEO彭垚、城市交通行业总经理兼营销副总裁詹诚以及公共交通行业总经理熊天圣等出席了本次交流会。 长安大学运输工程学院院长葛颖恩教授…

人人都是系统装机高手,利用windows官方的工具,安装超简单

前言 电脑出故障了或者C盘文件饱满、电脑系统卡顿&#xff0c;第一个想法就是重装系统&#xff0c;在网上搜一下&#xff0c;各种重装系统的镜像层出不穷&#xff0c;该怎么去选择呢&#xff1f;我适合很多方法&#xff0c;最后选择了微软官方的系统安装工具。因为系统纯净&am…
最新文章