C++类和对象2
Posted 山舟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++类和对象2相关的知识,希望对你有一定的参考价值。
文章目录
一、this指针
1.this指针的引入
这里实现一个日期的类,之后的内容以这个类为例。
代码如下(示例):
class Date
{
public:
private:
int _year;//年
int _month;//月
int _day;//日
};
但是想要修改或使用的成员变量都是private的,在类外无法访问,所以需要在public中定义成员函数对成员变量进行操作。
代码如下(示例):
class Date
{
public:
void Init(int year = 1970, int month = 1, int day = 1)
{
//C++类中的成员变量命名风格应与形参不同
//这样才能分清楚哪个是成员变量,哪个是形参
_year = year;
_month = month;
_day = day;
//把形参赋值给成员变量
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
//C++类中的成员函数命名风格应与形参不同
int _year;//年
int _month;//月
int _day;//日
};
int main()
{
Date d1, d2;
d1.Init(2021, 7, 8);
d2.Init();
d1.Print();
d2.Print();
return 0;
}
上面的代码调试查看反汇编代码可知:d1、d2调用的Init和Print函数是同一个(函数地址相同)。
结果如下:
那么这些函数是如何知道应该改变d1的成员变量还是应该改变d2的成员变量呢?
C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过不需要我们来传递,编译器自动完成。
2.this指针的特性
(1)只能在“成员函数”的内部使用。
(2)this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传给this形参。所以对象中不存储this指针。
(3)this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要我们手动传递
所以,上面的代码经过编译器处理会转变成下面的代码,但是注意,下面代码不一定能全部编译通过,仅仅是帮助理解this指针。
代码如下(示例):
class Date
{
public:
void Init(Date* this, int year = 1970, int month = 1, int day = 1)
{
this->_year = year;
this->_month = month;
this->_day = day;
}
void Print(Date* this)
{
cout << this->_year << "-" << this->_month << "-" << this->_day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1, d2;
d1.Init(&d1, 2021, 7, 8);
d2.Init(&d2);
d1.Print(&d1);
d2.Print(&d2);
return 0;
}
上文多次强调this指针是隐含的,所以不能在函数调用或函数定义时显式地写出
代码如下(示例):
// error
void Init(Date* this, int year = 1970, int month = 1, int day = 1)
{
//...
}
// error
d2.Init(&d2);
但是在成员函数中可以直接用this指针
代码如下(示例):
void Init(int year = 1970, int month = 1, int day = 1)
{
this->_year = year;//直接用
this->_month = month;//可以写
_day = day;//也可以不写
}
3.两个小问题
(1)this指针存储在哪里?
this指针是存在栈上的,因为形参都是存在栈上的(有时也被存在寄存器上)。
(2)this指针可以为空(nullptr)吗?
代码如下(示例):
class A
{
public:
void Print1()
{
cout << _a << endl;
}
void Print2()
{
cout << "Print2()" << endl;
}
private:
int _a;
};
int main()
{
A a;
a.Print1();
a.Print2();
return 0;
}
上面的代码中,a.Print1()崩溃,a.Print2()正常运行。
下面的代码可以帮助理解(但是不能通过编译)
代码如下(示例):
//运行时,下面的this为nullptr
void Print1(Date* this)
{
cout << this->_a << endl;
}
void Print2(Date* this)
{
cout << "Print2()" << endl;
}
很明显Print1中对空指针进行了解引用,所以崩溃;而Print2没有,所以正常运行。
二、类的6个默认成员函数
如果一个类中什么成员都没有,简称为空类。但空类中什么都没有吗?并不是的,任何一个类在我们自己不写的情况下,都会自动生成6个默认成员函数。
(1)构造函数:初始化对象
(2)析构函数:清理
(3)拷贝构造:使用同类对象初始化创建一个 (新的)对象
(4)赋值重载:把一个对象赋值给另一个 (已经存在的)对象
(5)普通对象取地址重载
(6)const对象取地址重载
其中(5)、(6)很少需要自己实现
三、构造函数
1.概念
构造函数是一个特殊的成员函数,函数名与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。
2.特性
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
(1)函数名与类名相同。
(2)无返回值。
这里一定要注意与void返回值区分:构造函数没有返回值;返回值为void表示有返回值但返回值为空。
(3)对象实例化时编译器自动调用对应的构造函数。
在用C语言实现数据结构时,会因忘记调用初始化函数而产生很多bug;C++实现构造函数后编译器自动调用,这样就能够保证对象一定被初始化。
(4)构造函数可以重载。
代码如下(示例):
class Date
{
public:
//函数重载
// 1.无参构造函数
Date()//无返回值!!!
{
_year = 1970;
_month = 1;
_day = 1;
}
// 2.带参构造函数
Date(int year, int month, int day)//无返回值!!!
{
_year = year;
_month = month;
_day = day;
}
//上面两个Date函数也可以通过给出默认值合二为一
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;//调用无参构造函数
Date d2(2021, 7, 8);//调用含参构造函数,注意写法
//注意下面的写法是声明了一个返回值为Date,函数名为d3的函数
//而不是创建了一个名为d3的对象
//所以通过无参构造函数创建对象时,对象后面不能有括号,否则就成了函数声明
Date d3();
return 0;
}
(5)类中没有显式给出构造函数,则编译器自动生成
如果类中显式写出了构造函数,则编译器不再生成。
代码如下(示例):
class Date
{
public:
void print()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.print();
return 0;
}
运行结果如下:
似乎编译器默认生成的默认构造函数没发挥什么作用,d1内的三个成员变量仍全部都是随机值。但事实并不是这样的。
代码如下(示例):
class A
{
public:
A(int a = 0)
{
_aa = a;
}
void printA()
{
cout << "printA()" << endl;
cout << _aa << endl;
}
private:
int _aa;
};
class Date
{
public:
void print()
{
cout << _year << " " << _month << " " << _day << endl;
_a.printA();
}
private:
int _year;
int _month;
int _day;
A _a;
};
int main()
{
Date d1;
d1.print();
return 0;
}
运行结果如下
可以得出,编译器生成的默认构造函数并不是什么都不做,只是对内置类型和自定义类型区别对待。
对内置类型(int,char……)及其指针不做处理。
对自定义类型(struct,class……定义的类型)去调用它们的构造函数来初始化。
上面第二段代码,编译器生成的默认构造函数对_year、_month、_day三个int型变量不做处理,而对_a这个A类型的变量调用了默认构造函数初始化。
(6)默认构造函数
无参构造函数、全缺省构造函数、编译器生成的构造函数,都可以认为是默认构造函数。
总之就是不需要传参即可调用的构造函数就是默认构造函数,默认构造函数有且只有一个。
代码如下(示例):
class Date
{
public:
Date(int year)
{}
void print()
{
cout << _year << " " << _month << " " << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.print();
return 0;
}
编译结果如下:
上面的代码中因为已经显式定义了构造函数,所以编译器不再生成,但是这个构造函数需要传一个参数才能调用,所以Date类没有合适的默认构造函数可用,编译不通过。
感谢阅读,如有错误请批评指正
以上是关于C++类和对象2的主要内容,如果未能解决你的问题,请参考以下文章