【类和对象】收尾总结

目录

一、初始化列表

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)单参数隐式类型转化

七、 C++的一些特殊处理

1.被const和static同时修饰的成员变量

2.const对象可以调用非const成员函数


一、初始化列表

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

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++的一些特殊处理

1.被const和static同时修饰的成员变量

class A
{
private:
	//编译器的特殊处理,当成员变量被static和const同时修饰时,如果变量为整形, 可以给缺省值
	//其他类型都不能给缺省值,给了缺省值,编译器就报错!
	const static int _a = 1; 
	//const static double _b = 1.1; //err
};

2.const对象可以调用非const成员函数

#include <iostream>
using namespace std;

class A
{
public:
	A(int a = 1)
		:_a(a)
	{}

	void print()
	{
		cout << _a << endl;
	}

private:
	int _a;
};
int main()
{
	//匿名对象具有常性
	//A& ref = A(); //err
	const A& ref = A(); //√

	//按理来说,const对象不能调用非const成员函数,但是C++做了特殊处理
	A().print(); //1
}

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

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

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值