C++ 5.this指针和构造函数
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;
}
-
在我们不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?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()。调用了默认成员函数。