类和对象
类的构成:
class 类名
{
public:
公有数据成员;
公有成员函数:
protected:
保护数据成员;
保护成员函数:
private:
私有数据成员;
私有成员函数:
};
类的声明由关键字class打头,后跟类名,花括号中是类体,最后以一个分号“;”结束。
构造函数
- 是一种特殊的成员函数,名字必须和类名相同,可以有参数,但不能有返回值(void也不行)。
- 作用是对对象进行初始化,如给成员变量赋初值。
- 如果定义类时没写构造函数,则编译器会生成一个默认的无参构造函数。
- 如果定义了构造函数,则编译器不生成默认的无参数的构造函数。
- 对象生成时构造函数自动被调用。对象一旦生成,就再也不能在其上执行构造函数。
- 一个类可以有多个构造函数。
无参构造函数&带参构造函数
class Date
{
public:
//无参的构造函数
Date( )
{
cout<<"Date( )"<<endl;
}
//带参数的构造
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{
cout<<"Date(int year, int month, int day)"<<endl;
}
private:
int _year;
int _month;
int _day;
};
void test()
{
Date d1; //默认无参构造函数被调用
Date d2(2017,12,4); //带参构造函数被调用
Date d3( ); //使用无参构造函数创建对象时,不能使用“Date d3( ); ”,因为“Date d3( );”表示声明一个名为d3的普通函数,此函数的返回值示Date类型。
}
拷贝构造函数
拷贝构造函数是一种特殊的构造函数,其形参是本类对象的引用。
拷贝构造函数的作用是在建立一个新对象时,使用一个已经存在的对象去初始化这个新对象。
拷贝构造函数的特点:
(1)函数名与类名相同,没有返回值。
(2)只有一个参数,并且是同类对象的引用。
(3)每个类都必须有一个拷贝构造函数。(可以自定义拷贝构造函数,用于按照需要初始化新对象;如果没有定义类的拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数,用于复制出与数据成员值完全相同的新对象)
class Date
{
private:
int _year;
int _month;
int _day;
};
void test( )
{
Date d1; //调用缺省无参构造函数
Date d2(d1); //调用缺省的拷贝构造函数,将d2初始化成和d1一样
}
如果定义了自己的拷贝构造函数,则默认的拷贝构造函数不存在。
class Date
{
public:
Date( )
{ }
Date(const Date& d)
{
cout<<"Date(const Date& d)"<<endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
void test( )
{
Date d1; //调用缺省无参构造函数
Date d2(d1); //调用自己定义的拷贝构造函数
}
拷贝构造函数起作用的三种情况:
1.当用类的对象去初始化同类的另一个对象时。
Date d2(d1);
Date d2 = d1; //初始化语句,并非赋值语句。
2.当函数的形参是类的对象,调用函数进行形参和实参结合时。
void Func(A a1) //形参是类Date的对象a1
{ }
int main( )
{
A a
Func(a2); //调用Func时,实参a2是类Date的对象,将调用拷贝构造函数,初始化形参a1.
return 0;
}
3.当函数的返回值是对象,函数执行完成返回调用者时。
A Func1()
{
A a1(4);
return a1; //函数的返回值是对象
}
int main( )
{
A a2;
a2 = Func1(); //函数执行完成,返回调用者时,调用拷贝构造函数
return 0;
}
在函数Func1( )内,执行语句“return a1;”时,将会调用拷贝构造函数将a1的值复制到一个匿名对象中,
这个匿名对象是编译系统在主程序中临时创建的。函数执行结束时对象a1消失,但临时对象会存在于语句
“a2 = Func( )”中。执行完这个语句后,临时对象的使命也就完成了,该临时对象便自动消失了。
析构函数
它执行与析构函数相反的操作,通常用于撤销对象时的一些清理任务,如释放分配给对象的内存空间等。
- 与构造函数名字相同,但它前面必须加一个波浪号(~)。
- 没有参数,也没有返回值,而且不能重载。因此,在一个类中只能有一个析构函数。
- 当撤销对象时,编译系统会自动调用析构函数。
在以下情况中,当对象生命周期结束时,析构函数会被自动调用:
(1) 如果定义了一个全局对象,则在程序流程离开其作用域(如main( )函数结束或调用exit( )函数)时,调用该全局的析构函数。
(2)如果一个对象被定义在一个函数体内,则当这个函数被调用结束时,该对象应该释放,析构函数被自动调用。
(3)若一个对象是使用new运算符动态创建的,在使用delete运算符释放它时,delete会自动调用析构函数。
class Date
{
public:
//析构函数
~Date( )
{
cout<<"~Date( )"<<endl;
}
private:
int _year;
int _month;
int _day;
};
如果在一个对象完成其操作之前需要做一些内部清理,则应显式地定义析构函数,已完成所需的操作。
class Student
{
public:
Student(char* name, int id)
{
_name = new char[strlen(name)+1];
strcpy(_name, name);
_id = id;
}
//这时需要析构函数做清理工作
~Student()
{
delete []_name;
}
private:
char* _name;
int _id;
};
赋值运算符的重载
- 重载运算符以后,不能改变运算符的优先级/结合性/操作数个数。
- 赋值运算符的重载是对一个已存在的对象进行拷贝赋值。
- 5个C++不能重载的运算符:
.* :: sizeof ?: .
类对象存储
事实上,给对象赋值就是给对象的数据成员赋值,不同对象的存储单元中存放的数据值通常是不同的,而不同对象的函数代码是相同的,不论调用哪一个对象的成员函数,其实调用的是相同内容的代码。
因此,每个对象的存储空间都只是该对象的数据成员所占用的存储空间,而不包括成员函数代码所占用的空间,函数代码式存储在对象空间之外的。每个对象都有属于自己的数据成员,但是所有的成员函数代码却合用一份。
每个对象的大小为类中所有成员变量的大小之和。
自引用this指针
class A
{
public:
A(int x)
{
_x = x;
}
void disp()
{
cout<<"x="<<_x<<endl;
}
private:
int _x;
};
int main()
{
A a(1);
A b(2);
a.disp();
b.disp();
return 0;
}
运行结果:
不论是对象a还是对象b调用disp( )函数时都执行同一条语句 cout<<"x="<<_x<<endl; 那么,执行a.disp()时,成员函数disp()怎样知道现在输出的应该是对a的_x值的1呢?类似的b呢?
C++为成员函数提供了一个名字为this的指针,这个指针称为自引用指针。每当创建一个对象时,系统就自动把this指针作为一个隐含的参数传给该函数。不同的对象调用同一个成员函数时,C++编译器将根据成员函数的this指针所指向的对象来确定应该调用哪一个对象的数据成员。
例如:当调用成员函数a.disp()时,编译系统就把对象a的起始地址赋给this指针,于是在成员函数引用数据成员时,就按照this的指向找到对象a的数据成员。相当于执行cout<<"_x="<<this->_x<<endl;由于当前的this指向对象a,因此cout<<"_x="<<a._x<<endl;
显示this指针的值: