【C++ 5.this指针和构造函数】

  • Post author:
  • Post category:其他




0.结构体存储方式

1.通过对程序的执行发现,d1,d2调用的是同一种方法,当程序编译完成后,成员函数的入口地址不会再发生改变。既然不会改变,每个对象都存储成员函数的入口地址,无疑会使对象变化,浪费时间。

成员函数可以将其单独存储一份。

/日期类
class Date
{
public:
	void Init(int year, int month, int day)//初始化
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()//打印输出
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	d1.Init(2008, 10, 1);
	d1.Print();
	//如果要求对象有多大?就必须知道对象包含那些内容
	//及通过对象打点的方式
	//成员函数可以单独存储一份,给成员变量一个指针,指向成员函数的地址
	cout << sizeof(d1) << endl;
	Date d2;
	d2.Init(2009, 10, 1);
	d2.Print();
	return 0; 
}
2008-10-1
12
2009-10-1

结论:对象中只包含成员变量,没有存储成员函数及其相关的内容。

2.空类大小:在主流编译器中(vs/g++)空类大小为1.
class A
{
public:
	void func()
	{
		_a = 10;
		cout << _a << endl;
	}
private:
	int _a;
};
class B
{
	//没有成员变量
public:
	void func()
	{
		cout << "B::func()" << endl;
	}
};
class C
{
	//没有成员变量
};
int main()
{
	cout << sizeof(A) << endl;//A类大小
	cout << sizeof(B) << endl;//B类大小
	cout << sizeof(C) << endl;//C类大小
	return 0;
}
4
1
1



1. this指针

d1,d2最总调用的是同一个Init和Print函数但是在这两个函数内部并没有关于对象的区分信息,那么Init和Print是怎么知道要操作的对象的?

C++编译器给每个“非静态的成员函数“增加了一个

隐藏的指针参数

,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。命周期内只调用一次。



a. this指针的引出

编译器编译类时,会对类中”成员变量”进行修改

1.还原this指针

2.所有”成员变量”都是通过this访问的

注:在C语言中需要自己传递Date的地址参数,而在C++中编译器会自己完成this指针。所有的一切都回到了C语言。



i.C++实现

//日期
class Date
{
public:
	void Init(int year, int month, int day)
	{
	//this指针不能被修改--Date* const
		cout << this << endl;//this指针指向的位置
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	cout << &d1 << endl;
	d1.Init(2008, 10, 1);
	d1.Print();
	
	Date d2;
	cout << &d2 << endl;
	d2.Init(2009, 10, 1);
	d2.Print();
	return 0;
}
00F3F9C8
00F3F9C8
2008-10-1
00F3F9B4
00F3F9B4
2009-10-1



ii.C语言实现

//如果使用C语言实现以上情况
struct Date
{
	int year;
	int month;
	int day;
};

void Init(Date* pd, int year, int month, int day)
{
	pd->year = year;
	pd->month = month;
	pd->day = day;
}

void Print(Date* pd)
{
	printf("%d-%d-%d\n", pd->year, pd->month, pd->day);
}

int main()
{
	Date d1;
	Init(&d1,2008, 10, 1);
	Print(&d1);

	Date d2;
	Init(&d2, 2009, 10, 1);
	Print(&d2);
	return 0;
}
2008-10-1
2009-10-1



b. this指针的特性

由上述C++实现则知:

取d1对象地址放在而出现寄存器中,寄存器可以当其是一个全局变量,在vs2022中,this实际上是通过ecx寄存器来传递的,将ecx中存放的对象的地址交给this。

当然,在vs2022中,并不是所有的this都是通过ecx寄存器传递有些是通过参数压栈的方式进行传递

009B1C97  lea         ecx,[d1]  
00F919DD  mov         dword ptr [this],ecx  
     _year = year;
009B1A19  mov         eax,dword ptr [this]  
009B1A1C  mov         ecx,dword ptr [year]  
009B1A1F  mov         dword ptr [eax],ecx  
  	_month = month;
009B1A21  mov         eax,dword ptr [this]  
009B1A24  mov         ecx,dword ptr [month]  
009B1A27  mov         dword ptr [eax+4],ecx  
  	_day = day;
009B1A2A  mov         eax,dword ptr [this]  
009B1A2D  mov         ecx,dword ptr [day]  
009B1A30  mov         dword ptr [eax+8],ecx  

i. this指针的类型:类类型* const

ii. 只能在“成员函数”的内部使用

iii. this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针

iv. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递



this指针存在哪里?

在vs2022中this实际存储在栈上。



this指针实际可以为空吗?

有可能为空,如果给的成员函数通过指针调用,则有可能为空,指针指向空。

当this指针指向nullptr时,只要在成员函数中没有访问“成员变量”,则代码不会崩溃。如果访问“成员变量”则代码一定崩溃。



2. 类的六个默认成员函数

六个默认成员函数:
	1. 初始化和清理
		a. 构造函数主要完成初始化工作
		b. 析构函数主要完成清理工作
	2. 拷贝复制
		a. 拷贝构造是使用同类对象初始化构造创建对象
		b. 复制重载主要是把一个对象赋值给另一个对象
	3. 取地址重载
        a.主要是普通对象和const对象取地址,这两个很少会自己实现
//类的6个默认成员函数
//空类:空类不是说类中什么都没有,编译器会给空类生成六个默认的成员函数
class Date
{

};



3. 构造函数



a.概念

构造函数是特殊的成员函数,需要注意的是,构造函数的名称虽然叫构造,但是构造函数的主要任务并不是开空间创对象,而是初始化对象。



b. 特性

无参构造函数和带参构造函数形成重载。

d1,d2调用带参构造函数;d3不是创建了一个对象,而是声明了一个返回值类型为Date,函数名为d3的一个无参的函数;d4调用无参构造函数。


其特征如下



1.函数名与类名形同

2.无返回值

3.对象实例化时编译器自动调用对应的构造函数,用户不能调用,在创建对象是由编译器自动调用,并且在整个随想生命周期内只调用一次

4.构造函数可以重载

//日期
class Date
{
public:
	//1.无参构造函数
	Date()
	{
		_year = 1999;
		_month = 9;
		_day = 9;
	}
	//2.带参构造函数
	Date(int year,int month,int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2022,4,7);
	d1.Print();
	Date d2(2002, 2, 2);
	d2.Print();
	
	Date d3();//d3不是创建了一个对象,而是声明了一个返回值类型为Date,函数名为d3的一个无参的函数

	Date d4;//调用的是无参的函数
	d4.Print();
	//发生函数重载
	//需求:想要让d1创建好后里面就有具体的日期
	return 0;
}
2022-4-7
2002-2-2
1999-9-9

5.如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

如果定义了构造函数,则编译器不再生成。

没有定义构造函数,对象也可以创建成功,因此此处调用的是编译器生成的默认构造函数

6. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。


无参构造函数和全缺省构造函数

,统称为构造函数,并且不能共存。

//时间类
class Time
{
public:
	Time()
	{
		_hour = 12;
		_minute = 13;
		_second = 14;
	}
private:
	int _hour;
	int _minute;
	int _second;

};
//日期类
class Date
{
public:
	//无参构造函数和全缺省构造函数,统称为构造函数,并且不能共存
	//全缺省构造函数
	Date(int year=2000, int month=10, int day=12)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	//无参构造函数
	Date()
	{
		_year = 1999;
		_month = 9;
		_day = 9;
	}
	//如果用户显式定义了任何构造函数,则编译器不会再生成任何无参的构造函数
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};

int main()
{
	Date d1;//会报错,编译器调用无参构造函数可以,调用全缺省构造函数也可以
	return 0;
}
  1. 在我们不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象year/month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用吗?


    NO!!!
class Date
{
public:
	
	//如果用户没有显示提供任何构造函数,则编译器会生成一个无参的默认构造函数
	//Date()
	//{}
	//当Date d; 发现d对象创建好后,d对象的成员变量全都是随机值
	//也就是说编译器生成无参构造函数没有意义
	//是不是说:编译器生成的无参构造函数就一定没有意义?    
	//答案:no
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如int/char…,自定义类型就是我们使用class/struct/union自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。

//时间类
class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 12;
		_minute = 13;
		_second = 14;
	}
private:
	int _hour;
	int _minute;
	int _second;

};
//日期类
class Date
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	//C++11中:为了解决默认构造函数不对内置类型初始化问题
	//可以给其带上数值
	int _year=1999;
	int _month=12;
	int _day=25;
	Time _t;
};
int main()
{
	Date d1;
	d1.Print();
	return 0;
}
 
Time()
1999-12-25

编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数,输出显示Time()。调用了默认成员函数。



版权声明:本文为qq_63979115原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。