【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,…………

 调试中的监视窗口:

 再看一个构造函数支持类型转换的栗子:

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()
{
	const Date& d = { 1,2,3 };
	return 0;
}

主函数第一条语句:编译器用1,2,3通过构造函数构造了1个Date类类型的临时对象,d是这个临时对象的常引用。如果不是常引用编译器会报错,因为临时对象具有常性。。 


 构造函数支持隐式类型转换是有诸多好处的,以后我们会深有体会。但是任何硬币都有两面,这样子代码的可读性就不是很好,所以用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;
}

 

感谢阅读!!! 

  • 31
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

X_chengonly

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值