【C++】类和对象3.0

浅浅介绍最近所学,希望有兴趣的读者老爷垂阅!

目录

1.再谈构造函数 

1.1.构造函数体赋值

1.2.初始化列表

1.3.构造函数的小知识

2. explicit关键字

3.static成员

3.1.static成员概念

3.2.static成员特性

4.友元 

4.1.友元函数

4.2.友元类 

5.内部类

5.1.内部类的概念

5.2.内部类的特性 

6.匿名对象 

 7.一些编译器优化


1.再谈构造函数 

本鼠在【C++】类和对象2.0中介绍说构造函数简单来说就是初始化用的,当然这个结论是对的。但是俺在【C++】类和对象2.0中一直是对构造函数的函数体进行操作,难道在构造函数的函数体内的语句是对对象的成员变量进行初始化操作吗?

本鼠都这么问了,那当然不是了,为什么构造函数的函数体内的语句不是对对象的成员变量进行初始化操作呢?

1.1.构造函数体赋值

在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。

class Date
{
	int _year;
	int _month;
	int _day;
public:
	Date(int year=2003,int month=12,int day=12)
	{
		_year = year;
		_month = month;
		_day = _day;
	}
};
int main()
{
	Date d(2024, 7, 1);
	return 0;
}

如上面的代码,给了对象d中各个成员变量一个合适的初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋值,而不能称作初始化。因为初始化只能初始 化一次,而构造函数体内可以多次赋值。

构造函数体内多次赋值:

class Date
{
	int _year;
	int _month;
	int _day;
public:
	Date(int year=2003,int month=12,int day=12)
	{
		_year = year;
		_month = month;
		_day = _day;
		_day = 10;//多次赋值
	}
};
int main()
{
	Date d(2024, 7, 1);
	return 0;
}

如果构造函数体内的语句是对对象的成员变量进行初始化操作的话,我们不应该允许多次赋值!

所以构造函数的函数体内的语句是赋值而不是初始化!换句话说,构造函数的函数体不是对象的成员变量初始化的地方!


还有一个问题有待解决:

我们知道,类定义时,类的成员变量只是声明,如:

class Date
{
	int _year;//成员变量声明
	int _month;//成员变量声明
	int _day;//成员变量声明
public:
	Date(int year=2003,int month=12,int day=12)//成员函数的声明和定义
	{
		_year = year;
		_month = month;
		_day = _day;
		_day = 10;//多次赋值
	}
};

当对象实例化时,类类型对象的成员变量就整体定义了,开空间了,如:

int main()
{
	Date d(2024, 7, 1);//对象实例化
	return 0;
}

对象整体定义的位置我们知道,但是对象的具体成员变量定义的位置如何界定?

难道是对象的具体成员变量定义的位置是在构造函数的函数体当中吗,当然不是,原因跟上面一样,函数体内关于某成员变量的语句可以多次出现,如果说关于某成员变量的语句第一次出现是其定义,那么当关于其成员变量的语句多次出现时,难道其成员变量可以多次定义吗?

显然对象具体的成员变量不能多次定义,那么构造函数的函数体就不是对象的成员变量定义的地方!

对象的成员变量只能定义和初始化一次,所以构造函数开辟了初始化列表来界定成员变量的定义和初始化


1.2.初始化列表

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟 一个放在括号中的初始值或表达式。如:

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

	}
};
int main()
{
	Date d(2024, 7, 1);
	return 0;
}

如上代码,初始化列表中:第一条语句就是成员变量_year定义的地方,2024是其初始值(给其初始化的值);第二条语句就是成员变量_month定义的地方,7就是其初始值;第三条语句……

语法上来说就是这样子的,运行成功!

 注意:

  • 注意1:每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次,定义亦然)

若是某成员变量在初始化列表多次出现,一定会报错!

class Date
{
	int _year;
	int _month;
	int _day;
public:
	Date(int year=2003,int month=12,int day=12)
		//初始化列表
		:_year(1)
		, _month(1)
		, _day(1)
		,_day(2)//error C2437: “_day”: 已初始化
	//函数体
	{

	}
};
  • 注意2:尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型对象的成员变量, 一定会先使用初始化列表初始化。 

就是说类类型对象的所有成员变量一定会先在初始化列表定义和初始化,不管你在初始化列表中是否显式写某成员变量,如果某成员变量没有显式写,编译器也会隐藏的将该成员变量“过一遍”初始化列表。当所有成员变量“过完”初始化列表,如果函数体有赋值语句,就会进入函数体进行赋值,从而赋值修改成员变量;如果函数体为空,那就不会赋值修改。

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

	}
};
int main()
{
	Date d(2024, 7, 1);
	return 0;
}

当成员变量_day没有在初始化列表中显式写的时候,_day也一定先在初始化列表“走”了一遍,并用随机值初始化_day,所以能看到_day初始值是随机值。函数体内为空,没有对成员变量赋值修改,所以所有成员变量仍然是初始值。如图: 

如果函数体内不为空,有赋值语句,我们看看:

class Date
{
	int _year;
	int _month;
	int _day;
public:
	Date(int year=2003,int month=12,int day=12)
		//初始化列表
		:_year(1)
		, _month(1)
	//函数体
	{
		_month = month;
		_day = day;
	}
};
int main()
{
	Date d(2024, 7, 1);
	return 0;
}

函数体内只对_month和_day进行了赋值修改,所以_month从1变成7,_day从随机值变成1:

  • 注意3:类中包含以下成员,必须放在初始化列表位置进行初始化,就是必须在初始化列表位置显式写: 
  1. const成员变量
  2. 引用成员变量
  3. 自定义类型成员且该类没有默认构造函数时

 为什么呢?

1.const成员变量必须只能在定义的时候初始化,一旦在定义的地方给定初始值就不能再修改了,所以必须在初始化列表显式写:

class Date
{
	int _year;
	int _month;
	const int _day;//const成员变量
public:
	Date(int year=2003,int month=12,int day=12)
		//初始化列表
		:_year(1)
		, _month(1)
		,_day(1)//const成员变量必须在初始化列表显式写
	//函数体
	{

	}
};
int main()
{
	Date d(2024, 7, 1);
	return 0;
}

对于const成员变量,一旦没有在初始化列表显示写,编译器就会报错:

class Date
{
	int _year;
	int _month;
	const int _day;//const成员变量
public:
	Date(int year=2003,int month=12,int day=12)
		//初始化列表
		:_year(1)
		, _month(1)
		//error C2789: “Date::_day”: 必须初始化常量限定类型的对象
	 //,_day(1)
	//函数体
	{

	}
};

对于const成员变量,一旦企图在函数体内修改其初始值,编译器也会报错: 

class Date
{
	int _year;
	int _month;
	const int _day;//const成员变量
public:
	Date(int year=2003,int month=12,int day=12)
		//初始化列表
		:_year(1)
		, _month(1)
	    ,_day(1)//const成员变量必须在初始化列表显式写
	//函数体
	{
		_month = month;
		_day = day;//error C2166: 左值指定 const 对象
	}
};

2.俺在【C++】C++入门2.0 中提到过引用的特性之二:引用在定义时必须初始化和引用一旦引用一个实体,再不能引用其他实体。所以,引用成员变量必须在初始化列表显示写:

class Date
{
	int _year;
	int _month;
    int& _day;//引用成员变量
public:
	Date(int year=2003,int month=12,int day=12)
		//初始化列表
		:_year(1)
		, _month(1)
	    ,_day(day)//引用成员变量必须在初始化列表显式写
	//函数体
	{
		
	}
};
int main()
{
	Date d(2024, 7, 1);
	return 0;
}

_day在初始化列表中被初始化成了形参day的引用,所以: 

我们看不同于const成员变量,对于引用成员变量,我们可以在函数体中对其操作:

class Date
{
	int _year;
	int _month;
    int& _day;//引用成员变量
public:
	Date(int year=2003,int month=12,int day=12)
		//初始化列表
		:_year(1)
		, _month(1)
	    ,_day(day)//引用成员变量必须在初始化列表显式写
	//函数体
	{
		_day = year;//这里不是改变_day的指向
	}
};
int main()
{
	Date d(2024, 7, 1);
	return 0;
}

我们要注意函数体内的语句是将形参year的值赋值给形参day的引用_day,而不是改变_day的指向。看结果:

3. 自定义类型成员且该类没有默认构造函数时,我们必须在初始化列表显式,也就是必须在初始化列表显示调用该类的构造函数:

注:默认构造函数有3个:无参的构造函数、全缺省参数的构造函数、我们没写编译器默认生成的构造函数。【C++】类和对象2.0介绍过的。

class day
{
	int _day;
	int _hour;
public:
	day(int day,int hour)
	{
		_day = day;
		_hour = hour;
	}
};
class Date
{
	int _year;
	int _month;
    day _day;//自定义类型成员
public:
	Date(int year=2003,int month=12,int day=12)
		//初始化列表
		:_year(1)
		, _month(1)
	    ,_day(day,100)
	//函数体
	{
	
	}
};
int main()
{
	Date d(2024, 7, 1);
	return 0;
}

当自定义类型成员且该类没有默认构造函数时,如果没有在初始化列表显式写该成员,必定报错:

class day
{
	int _day;
	int _hour;
public:
	day(int day,int hour)
	{
		_day = day;
		_hour = hour;
	}
};
class Date
{
	int _year;
	int _month;
    day _day;//自定义类型成员
public:
	Date(int year=2003,int month=12,int day=12)
		//初始化列表
		:_year(1)
		, _month(1)
	  //error C2512 : “day”: 没有合适的默认构造函数可用
	  //,_day(day,100)
	//函数体
	{
	
	}
};

再解释一下,如果自定义类型成员有默认构造函数的情况下,如果我们不在初始化列表显式写该成员也没关系,因为编译器将这个成员隐藏的“过一遍”初始化列表时,会去调用它的默认构造函数,默认构造函数编译器是知道如何调用的。。。。但是如果自定义类型成员没有默认构造函数的情况下,如果我们不在初始化列表显式写该成员,编译器是不知道该如何调用俺们显式实现的构造函数的,所以编译器会认为没有合适的默认构造函数可用,就报错了,所以这种情况我们必须在初始化列表显式写,也就是显式调用其构造函数。

  • 注意4:成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关:

俺们看这个代码的运行结果是什么?

A. 输出1  1   

B.程序崩溃

C.编译不通过

D.输出1  随机值 

#include<iostream>
using namespace std;
class A
{
public:
	A(int a)
		:_a1(a)
		, _a2(_a1)
	{}

	void Print() {
		cout << _a1 << " " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};
int main() {
	A aa(1);
	aa.Print();
    return 0;
}

看看结果:答案选D。

就是因为在初始化列表的初始化顺序就是成员变量在类中声明的顺序,导致先初始化_a2,再初始化_a1。 _a1在没有初始化时是随机值,当用_a1作为初始值先初始化_a2时,_a2的初始值就是随机值了,再用形参作为初始值初始化_a1,_a1就是1了。

所以,建议类中成员变量的声明顺序和初始化列表中成员变量显式写的顺序一致,免得出现大乌龙事件嘛!!!

1.3.构造函数的小知识

  •  小知识1:本鼠在【C++】类和对象2.0中提过:C++11 中针对对象内置类型成员不做处理的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值(缺省值)。那么这个缺省值其实就是给初始化列表使用的。

举个栗子:

class Date
{
	int _year = 1945;
	int _month = 10;
	int _day = 1;
public:
	Date(int year=2003,int month=12,int day=12)
	{
	
	}
};
int main()
{
	Date d(2024, 7, 1);
	return 0;
}

我们没有在初始化列表显式写任何一个成员变量,那么这些成员变量应该都被初始化成随机值,函数体内也没有对这些成员变量赋值修改,如果没有声明时给的缺省值的话,成员变量都应该是随机值。但是,但是,俺们在声明时给了缺省值,这些缺省值给了隐藏的初始化列表使用,所以结果就是成员变量被初始化成了缺省值:

  • 小知识2:上面提过初始化列表书写格式:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟 一个放在括号中的初始值或表达式。看清楚哦,这里可以给表达式的,所以我们可以调用函数。

举个栗子:

#include<stdlib.h>
#include<iostream>
using namespace std;
class A
{
	int* a;
	int b;
public:
	A()
	:a((int*)malloc(sizeof(int)))//调用函数
	, b(1)
	{

	}
};
int main()
{
	A trmp;
	return 0;
}

 内置类型成员变量在类中声明时可以给默认值(缺省值),这个缺省值是给初始化列表使用的,所以这个缺省值也可以是表达式,当然也可以调用函数。

举个栗子:

#include<stdlib.h>
#include<iostream>
using namespace std;
class A
{
	int* a = (int*)malloc(sizeof(int));//缺省值为表达式
	int b;
public:
	A()
	:b(10)
	{

	}
};
int main()
{
	A trmp;
	return 0;
}

2. explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数构造函数,还具有类型转换的作用。 

举个栗子:

class Date
{
public:
	 Date(int year)//构造函数
		:_year(year)
	{}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d = 2;//隐式类型转换
	return 0;
}

我们看主函数里面的语句:Date d=2;

怪怪的是吧。看上去为什么可以用整形常量2通过拷贝构造函数构造Date类对象d呢?其实不是这样子的。

其实这里的知识就是单参数构造函数支持隐式类型的转换。 理论上或者原理上来说是:编译器背后会用2通过构造函数构造一个Date类类型的临时对象,最后用临时对象通过拷贝构造函数构造对象d。

但是实际上呢,编译器优化了,同一个表达式连续步骤的构造一般会合二为一。比如上面代码中编译器并没有产生Date类临时对象,直接用2通过构造函数构造了对象d。如果不相信会优化,大可显式实现拷贝构造函数,通过调试看看有没有调用拷贝构造函数,当然如果编译器过于老旧,一般不会优化的哦。虽然编译器优化了,但是原理上应该会产生Date类临时对象的。

直接用2通过构造函数构造对象d:


还要介绍一些:对于多参数的构造函数来说,C++98标准是不支持隐式类型转换的,但是C++11标准开始就支持了。所以,编译器太老旧也许就无法验证出来了。

多参数的构造函数支持隐式类型转换举例:

class Date
{
public:
	 Date(int year,int month=7,int day=3)//注意缺省参数要从右往左给
		:_year(year)
		 ,_month(month)
		 ,_day(day)
	{}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1 = {2003,12,12};//隐式类型转换
	Date d2 = { 2024,8 };//隐式类型转换
	Date d3 = { 2024 };//隐式类型转换
	Date d4 = 2;//隐式类型转换
	return 0;
}

注意这里多参数的构造函数支持隐式类型转换中的写法,如主函数语句可见。。。。。 

也是一样的,理论上或者原理上说:会产生Date类临时对象的。但是编译器不太老旧一般都优化了,例如主函数第一条语句直接用2003,12,12通过构造函数构造了对象d1,第二条语句直接用2024,8,3通过构造函数构造了对象d2,…………

 调试中的监视窗口:


 构造函数支持隐式类型转换是有诸多好处的,以后我们会深有体会。但是任何硬币都有两面,这样子代码的可读性就不是很好,所以用explicit修饰构造函数,将会禁止构造函数的隐式转换

class Date
{
public:
	explicit Date(int year)//构造函数
		:_year(year)
	{}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d = 2;//error C2440: “初始化”: 无法从“int”转换为“Date”
	return 0;
}

用explicit修饰构造函数,编译报错了!! 

3.static成员

抛砖引玉:如果我们想要知道一个类实例化了多少个对象这么办?

答案很简单,我们统计构造函数被调用了次数就行了,调用次数就是实例化对象个数。

比如:

#include<iostream>
using namespace std;
int number = 0;
class Date
{
	int _year;
	int _month;
	int _day;
public:
	Date()//构造函数
	{
		number++;
	}
	Date(const Date& d)//拷贝构造函数,构造函数之一
	{
		number++;
	}
};
int main()
{
	Date d1;
	Date d2;
	Date d3 = d1;
	Date d4(d3);
	cout << number << endl;
	return 0;
}

但是这样用全局变量number来统计构造函数的调用次数是不安全的,全局变量人人都可以修改,所以我们还有解决方案static成员:

3.1.static成员概念

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

#include<iostream>
using namespace std;
class Date
{
	int _year;
	int _month;
	int _day;
	static int number;//静态成员变量声明
public:
	Date()//构造函数
	{
		number++;
	}
	Date(const Date& d)//拷贝构造函数
	{
		number++;
	}
	static int GetN()//静态成员函数
	{
		return number;
	}
};
int Date::number = 0;//静态成员变量定义
int main()
{
	Date d1;
	Date d2;
	Date d3 = d1;
	Date d4(d3);
	cout << Date::GetN() << endl;
	cout << d1.GetN() << endl;
	return 0;
}

这样子用静态成员变量来统计构造函数的调用次数,体现了封装,防止被篡改数据。

3.2.static成员特性

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

俺们再来看上面的代码:

#include<iostream>
using namespace std;
class Date
{
	int _year;
	int _month;
	int _day;
	static int number;//静态成员变量声明
public:
	Date()//构造函数
	{
		number++;
	}
	Date(const Date& d)//拷贝构造函数
	{
		number++;
	}
	static int GetN()//静态成员函数
	{
		return number;
	}
};
int Date::number = 0;//静态成员变量定义
int main()
{
	Date d1;
	Date d2;
	Date d3 = d1;
	Date d4(d3);
	cout << Date::GetN() << endl;
	cout << d1.GetN() << endl;
	return 0;
}
  • 由于我们想要统计构造函数的调用次数,所以每调用一次构造函数我们希望number++,这个number我们期望是同一个number,静态成员为所有类对象所共享这个特性符合这个期望。
  • 静态成员变量number受到访问限定符private限制,不可在类外被访问,防止被篡改,体现封装。静态成员变量必须在类外定义,定义时要突破类域,使用类名::静态成员指名成员属于哪个类域,如:
int Date::number = 0;//静态成员变量定义
  • 使用静态成员函数访问静态成员变量,这个静态成员函数被public修饰,所以我们有两种访问办法,如下:
cout << Date::GetN() << endl;
cout << d1.GetN() << endl;

我们看看运行结果:

 

4.友元 

友元有点走后门的感觉,尽量少用吧!!!

4.1.友元函数

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

 举个栗子:

#include<iostream>
using namespace std;
class Date
{
	int _year;
	int _month;
	int _day;
public:
	Date(int year=2003, int month=12, int day=12)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	friend void Today(const Date& d);//友元声明
};
void Today(const Date& d)
{
	cout << d._year << '/' << d._month << '/' << d._day << endl;
}
int main()
{
	Date d;
	Today(d);
	return 0;
}

我们看,Today就成了Date类的友元函数了,可以访问Date类的私有成员,如_year。运行结果看看啊:

说明: 

  1.  友元函数可访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用const修饰
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  4. 一个函数可以是多个类的友元函数
  5. 友元函数的调用与普通函数的调用原理相同

4.2.友元类 

友元类的意思跟友元函数类似的。

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

栗子:

class Time
{
	int _hour;
	int _minute;
	int _second;
public:
	Time(int hour = 0, int minute = 0, int second = 0)
		: _hour(hour)
		, _minute(minute)
		, _second(second)
	{}
	friend class Date;//声明Date类为Time类的友元类,则在Date类中就直接访问Time类中的私有成员
};
class Date
{
	int _year;
	int _month;
	int _day;
	Time _t;
public:
	Date(int year = 1900, int month = 1, int day = 1)
		: _year(year)
		, _month(month)
		, _day(day)
	{}
	void SetTimeOfDate(int hour, int minute, int second)
	{
		// 直接访问Time类私有的成员变量
		_t._hour = hour;
		_t._minute = minute;
		_t._second = second;
	}
};

说明: 

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

比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。

  • 友元关系不能传递。

如果C是B的友元, B是A的友元,则不能说明C时A的友元。

  • 友元关系不能继承。

5.内部类

5.1.内部类的概念

概念:如果一个类定义在另一个类的内部,这个类就叫做内部类。内部类是一个独立的类, 它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。内部类只是被外部类的类域限制,也受到访问限定符的限制。

注意:内部类天生就是外部类的友元类。参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。 

5.2.内部类的特性 

  • 内部类可以定义在外部类的public、protected、private都是可以的,当然受到相应访问限定符的限制。

编译报错,因为Time类被private限制:

class Date
{
    int _year;
    int _month;
    int _day;
    class Time
    {
    public:
        int _hour = 1;
        int _minute = 1;
        int _second = 1;
    };
};
int main()
{
    Date::Time t;//error C2248: “Date::Time”: 无法访问 private class(在“Date”类中声明)
    return 0;
}

编译成功,因为Time类被public限制:

#include<iostream>
using namespace std;
class Date
{
    int _year;
    int _month;
    int _day;
public:
    class Time
    {
    public:
        int _hour = 1;
        int _minute = 1;
        int _second = 1;
    };
};
int main()
{
    Date::Time t;//注意要突破Date类域
    cout << t._hour << endl;
    return 0;
}

 

  • 注意内部类可以直接访问外部类中的static成员,不需要借助外部类的对象/类名。
#include<iostream>
using namespace std;
class Date
{
	int _year = 2003;
	int _month = 12;
	static int _day;
public:
	class Time//Time类天生是Date类的友元类
	{
		int _hour;
		int _minute;
		int _second;
	public:
		void Fund(const Date& d)
		{
			cout << d._year << endl;//需要借助外部类对象d来访问
			cout << _day << endl;//直接访问外部类static成员
		}
	};
};
int Date::_day = 12;
int main()
{
	Date d1;
	Date::Time t;
	t.Fund(d1);
	return 0;
}

  • sizeof(外部类)的结果和内部类没有任何关系。 

栗子:

#include<iostream>
using namespace std;
class Date1
{
    int _year;
    int _month;
    int _day;
    class Time
    {
    public:
        int _hour = 1;
        int _minute = 1;
        int _second = 1;
    };
};
class Date2
{
    int _year;
    int _month;
    int _day;
};
int main()
{
    cout << sizeof(Date1) << endl;
    cout << sizeof(Date2) << endl;
    return 0;
}

因为内部类不属于外部类,看结果是一模一样的: 

6.匿名对象 

 匿名对象就是没有名字的对象呗。。匿名对象的特点不用取名字。。

举一个匿名对象的栗子:

class Date
{
    int _year;
    int _month;
    int _day;
};
int main()
{
    Date();//匿名对象
    return 0;
}

注意:

  • 注意1:

俺在【C++】类和对象2.0 中介绍过:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明。

错误写法:

class people
{
	const char* _name;
	int _age;
public:
	people()//无参的构造函数
	{
		this->_name = "HD";
		this->_age = 20;
	}
};
int main()
{
	people HD();//warning C4930: “people HD(void)”: 未调用原型函数(是否是有意用变量定义的?)
	return 0;
}

错误写法报错原因:编译器无法识别是一个函数声明,还是对象定义 。

但是通过无参构造函数创建匿名对象时,对象后面可以跟括号,如:

class Date
{
    int _year;
    int _month;
    int _day;
public:
    Date()
    {
        _year = _month = _day = 1;
    }
};
int main()
{
    Date();//匿名对象
    return 0;
}
  • 注意2:

匿名对象生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数:

#include<iostream>
using namespace std;
class Date
{
    int _year;
    int _month;
    int _day;
public:
    Date()
    {
        _year = _month = _day = 1;
    }
    ~Date()
    {
        cout << "俺寄了" << endl;
    }
};
int main()
{
    Date();//匿名对象
    Date d;
    return 0;
}

看到调试状态下,执行完Date();这句语句后就调用了析构函数: 

 7.一些编译器优化

 一些编译器的优化,优化力度随编译器不同而不同,也随环境不同而不同。有兴趣就看看。。不必过于当真。

#include<iostream>
using namespace std;
class A
{
	int _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;
	}
};
void f1(A aa)
{}
A f2()
{
	A aa;
	return aa;
}
int main()
{
	A aa1;//构造

	//拷贝构造->无法优化
	f1(aa1);
	cout << endl;

	//构造+拷贝构造->优化为构造
	f2();
	cout << endl;

	//构造(隐式类型转换)+拷贝构造->优化为构造
	f1(1);
	cout << endl;

	//构造+拷贝构造->优化为构造
	f1(A(2));
	cout << endl;

	//构造+拷贝构造+拷贝构造->优化为构造
	A aa2 = f2();
	cout << endl;

	//构造(隐式类型转换)+拷贝构造->优化成构造
	A aa3 = 2;
	cout << endl;

	//构造(隐式类型转换)->无法优化:aa4是用2构造临时对象的引用
	const A& aa4 = 2;
	cout << endl;

	//构造+拷贝构造+赋值运算符重载->优化成构造+赋值运算符重载
	aa1 = f2();
	cout << endl;

	return 0;
}

 

感谢阅读!!! 

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

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

相关文章

七、Linux二进制安装Redis集群

目录 七、Linux二进制安装Redis集群1 安装Redis所需依赖2 单机安装Redis&#xff08;7.2.4&#xff09;2.1 下载Redis2.2 安装Redis 3 分布式部署模式&#xff08;Redis Cluster&#xff09;3.1 分布式部署模式的配置文件3.2创建集群 4 主从复制模式&#xff08;Redis Sentinel…

jenkins搭建部署前端工程 ,从0到1

一.java环境配置 1 安装tomcatjdk17 这个也行 3 安装maven3.3.9 安装教程参考 4 安装Jenkins 下载地址 参考教程 二、相关配置 1 访问http://localhost:8080/jenkins&#xff0c;进入Jenkins初始化页面&#xff0c;第一次启动时间可能有点长&#xff0c;耐心等待。进入成功后会…

AndroidKille不能用?更新apktool插件-cnblog

AndroidKiller不更新插件容易报错 找到apktool管理器 填入apktool位置&#xff0c;并输入apktool名字 选择默认的apktool版本 x掉&#xff0c;退出重启 可以看到反编译完成了

《Windows API每日一练》8.3 scrollbar控件

在第三章SYSMETS2.C实例中&#xff0c;我们是通过CreateWindow函数创建窗口的参数窗口样式中添加垂直或水平滚动条。本节我们将讲述作为子窗口控件的滚动条。 本节必须掌握的知识点&#xff1a; 滚动条类 滚动条控件和着色 8.3.1 滚动条类 ■窗口滚动条与滚动条控件的异同 …

带你了解“Java新特性——模块化”

Java平台从Java 8向Java 9及更高版本的进化&#xff0c;其中引入了一个重要的新特性——模块系统&#xff08;Project Jigsaw&#xff09;。模块系统的目的是解决大型应用的依赖管理问题&#xff0c;提升性能&#xff0c;简化JRE&#xff0c;增强兼容性和安全性&#xff0c;并提…

一文了解常见DNS问题

当企业的DNS出现故障时&#xff0c;为不影响企业的正常运行&#xff0c;团队需要能够快速确定问题的性质和范围。那么有哪些常见的DNS问题呢&#xff1f; 域名解析失败&#xff1a; 当您输入一个域名&#xff0c;但无法获取到与之对应的IP地址&#xff0c;导致无法访问相应的网…

Blazor 逐键搜索并动态反馈到url

Blazor 逐键搜索并动态反馈到url 源码 前言: 今天打开了 spotify 网页版找歌, 突然发现这个功能很抓眼球,于是试试blazor能不能模仿一下. 1. 节省时间,直接用模板开搞 新建项目,使用 Bootstrap Blazor App 模板 , 命名为 b22dynamicURL BootstrapBlazor简介: BootstrapBlaz…

数据库概念题总结

1、 2、简述数据库设计过程中&#xff0c;每个设计阶段的任务 需求分析阶段&#xff1a;从现实业务中获取数据表单&#xff0c;报表等分析系统的数据特征&#xff0c;数据类型&#xff0c;数据约束描述系统的数据关系&#xff0c;数据处理要求建立系统的数据字典数据库设计…

SQL注入方法

文章目录 前言如何测试与利用注入点手工注入思路工具sqlmap-r-u-m--level--risk-v-p--threads-batch-smart--os-shell--mobiletamper插件获取数据的相关参数 前言 记录一些注入思路和经常使用的工具&#xff0c;后续有用到新的工具和总结新的方法再继续补充。 如何测试与利用注…

使用 OpenCV 和 Python 进行车道检测和物体检测(YOLO)

本项目旨在开发一个集车道检测与物体检测功能于一体的智能视觉分析系统&#xff0c;利用先进的计算机视觉技术和深度学习模型&#xff0c;实现实时的道路场景理解和目标识别。系统主要依托OpenCV这一强大的计算机视觉库&#xff0c;以及Python作为编程语言&#xff0c;融合了车…

CentOS7.9下yum升级Apache HTTP Server2.4.6到2.4.60

1、CentOS7.9系统默认的Apache版本 在CentOS7.9上&#xff0c;如果使用yum安装Apache HTTP Server是最多到2.4.6版本的&#xff0c;这是因为el7下官方仓库的最高版本就是2.4.6&#xff0c;证据如下&#xff1a; $ yum info httpd ...... Installed Packages Name : ht…

Jetpack Compose实战教程(五)

Jetpack Compose实战教程&#xff08;五&#xff09; 第五章 如何在Compose UI中使用基于命令式UI的自定义View 文章目录 Jetpack Compose实战教程&#xff08;五&#xff09;一、前言二、本章目标三、开始编码3.1 先让自定义控件能跑起来3.2给自定义控件使用compose的方式赋值…

【设计模式】工厂模式(定义 | 特点 | Demo入门讲解)

文章目录 定义简单工厂模式案例 | 代码Phone顶层接口设计Meizu品牌类Xiaomi品牌类PhoneFactory工厂类Customer 消费者类 工厂方法模式案例 | 代码PhoneFactory工厂类 Java高级特性---工厂模式与反射的高阶玩法方案&#xff1a;反射工厂模式 总结 其实工厂模式就是用一个代理类帮…

Java项目:基于SSM框架实现的学生公寓管理中心系统【ssm+B/S架构+源码+数据库+毕业论文】

一、项目简介 本项目是一套基于SSM框架实现的学生公寓管理中心系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操作简单、…

【CentOS 7.6】Linux版本 portainer本地镜像导入docker安装配置教程,不需要魔法拉取!(找不着镜像的来看我)

吐槽 我本来根本不想写这篇博客&#xff0c;但我很不解也有点生气&#xff0c;CSDN这么大没有人把现在需要魔法才能拉取的镜像放上来。 你们都不放&#xff0c;根本不方便。我来上传资源。 portainer-ce-latest.tar Linux/amd64 镜像下载地址&#xff1a; 链接&#xff1a;h…

加法器的基本操作

基本单元 与门(AND) 全1为1&#xff0c;有0为0 或门(OR) 全0为0&#xff0c;有1为1 非门(NOT) 为1则0&#xff0c;为0则1 异或门(XOR) 两个输入端&#xff0c;相同为0&#xff0c;不同为1 与非门(NADD) 全1为0&#xff0c;有0为1 或非门(NOR) 全0为1&#xff0c;有1为0。刚…

H5 Canvas实现转盘效果,控制指定数字

效果图 实现思路&#xff1a; 用Canvas画圆&#xff0c;然后再画扇形&#xff0c;然后中奖的开始用一张图片代替&#xff0c;点击的时候触发转动效果。 实现代码&#xff1a; <!DOCTYPE html> <html> <head><meta charset"utf-8"><tit…

UNDO 表空间使用率高 active段占用高 无对应事务执行

UNDO表空间使用率告警&#xff0c;查看占用情况 active段占比很高 select tablespace_name,status,sum(bytes/1024/1024) mb from dba_undo_extents group by tablespace_name,status;不同状态的含义&#xff1a;**ACTIVE **&#xff1a;有活动事务在使用 Undo&#xff0c;这…

【JavaSE】数据类型与变量

目录 1. 字面常量 2. 数据类型 3. 变量 3.1 变量概念 3.2 语法格式 3.3 整型变量 3.3.1 整型变量 3.3.2 长整型变量 3.3.3 短整型变量 3.3.4 字节型变量 3.4 浮点型变量 3.4.1 双精度浮点型 3.4.2 单精度浮点型 3.5 字符型变量 3.6 布尔型变量 3.7 类型转换 3…

WEB安全-靶场

1 需求 2 语法 3 示例 男黑客|在线渗透测试靶场|网络安全培训基地|男黑客安全网 4 参考资料