文章目录
三、类和对象的进阶
1、构造函数
(1)构造函数的作用
- 为了对对象进行初始化,C++提供了一种称为构造函数的机制,用于对对象进行初始化,实际上是用来为成员变量赋初值的。
- 构造函数是类中的特殊成员函数,它属于类的一部分。给出类定义时,由程序员编写构造函数。如果程序员没有编写类的任何构造函数,则由系统自动添加一个不带参数的构造函数。
- 声明对象后,可以使用new运算符为对象进行初始化,此时调用的是对象所属类的构造函数。构造函数的作用是完成对象的初始化工作,用来保证对象的初始状态是确定的。在对象生成时,系统自动调用构造函数,用户在程序中不会直接调用构造函数。
(2)构造函数的定义
定义一个类时,需要为类定义相应的构造函数。构造函数的函数名与类名相同,没有返回值 。一个类的构造函数可以有多个,即 构造函数允许重载。同一个类的多个构造函数的参数表一定不能完全相同。
构造函数的声明格式如下: 类名(形参1, 形参2, …,形参n);
- 在声明类的构造函数时可以同时给出函数体,这样的构造函数称为内联函数。也可以在类体外给出构造函数的定义。构造函数的声明中,形参的个数可以为0,即参数表为空。
- 当类中没有定义任何构造函数时,系统会自动添加一个参数表为空、函数体也为空的构造函数,称为默认构造函数。所以任何类都可以保证至少有一个构造函数。
- 如果程序员在程序中已经定义了构造函数,则系统不会再添加默认构造函数。
假设类的成员变量是x1,x2,…,xn,则在类体外定义构造函数时通常有如下3种形式:
形式一:
类名::类名(形参1,形参2,…,形参n)
{
x1=形参1;
x2=形参2; ……
xn=形参n;
}
形式二:
类名::类名(形参1,形参2,…,形参n):x1(形参1), x2(形参2), …, xn(形参n){ }
class myDate{
private:
int year,month,day;
public:
myDate();
myDate(int);
myDate(int,int);
myDate(int,int,int);
};
//构造函数的第一种声明方式:
myDate::myDate()
{
year='2008';
month=8;
day=8;
};
myDate::myDate(int year)
{
this->year = year;
month = 8;
day = 8;
}
//构造函数的第二种声明方式
myDate::myDate(int year,int month):year(year),month(month),day(8){};
myDate::myDate(int year,int month,int day):year(year),month(month),day(day){};
(3)构造函数的使用
C++语言规定,创建类的任何对象时都一定会调用构造函数进行初始化。对象需要占据内存空间,生成对象时,为对象分配的这段内存空间的初始化由构造函数完成。(定义对象时,类的构造函数会被自动调用)
特别地,如果程序中声明了对象数组,即数组的每个元素都是一个对象,则一定要为对象所属的这个类定义一个无参的构造函数。因为数组中每个元素都需要调用无参的构造函数进行初始化,所以必须要有一个不带参数的构造函数。
(4)复制构造函数 与 类型转换构造函数
- 复制构造函数是构造函数的一种,也称为拷贝构造函数。它的作用是使用一个已存在的对象去初始化另一个正在创建的对象。例如,类对象间的赋值是由复制构造函数实现的。
- 复制构造函数只有一个参数,参数类型是本类的引用。复制构造函数的参数可以是const引用,也可以是非const引用。
- 一个类中可以写两个复制构造函数,一个函数的参数是const引用,另一个函数的参数是非const引用。这样,当调用复制构造函数时,既能以常量对象(初始化后值不能改变的对象)作为参数,也能以非常量对象作为参数去初始化其他对象。
对于类A而言,复制构造函数的原型如下
格式一: A:A(const A&)
格式二: A:A(A &)
- 自动调用复制构造函数的情况有以下3种:
- ①当用一个对象去初始化本类的另一个对象时,会调用复制构造函数。例如,使用下列形式的说明语句时,即会调用复制构造函数。
- 类名 对象名2(对象名1);
- 类名 对象名2 = 对象名1;
- ②如果函数F的参数是类A的对象,那么当调用F时,会调用类A的复制构造函数。换句话说,作为形参的对象,是用复制构造函数初始化的,而且调用复制构造函数时的参数,就是调用函数时所给的实参。
- ③如果函数的返回值是类A的对象,那么当函数返回时,会调用类A的复制构造函数。也就是说,作为函数返回值的对象是用复制构造函数初始化的,而调用复制构造函数时的实参,就是retrun语句所返回的对象。
- ①当用一个对象去初始化本类的另一个对象时,会调用复制构造函数。例如,使用下列形式的说明语句时,即会调用复制构造函数。
- 注意,在复制构造函数的参数表中,加上const是更好的做法。这样复制构造函数才能接收常量对象作为参数,即才能以常量对象作为参数去初始化别的对象。
2、析构函数
- 与构造函数一样,析构函数也是成员函数的一种,它的名字也与类名相同,但要在类名前面加一个“〜”字符,以区别于构造函数。
- 析构函数没有参数,也没有返回值。一个类中有且仅有一个析构函数,如果程序中没有定义析构函数,则编译器自动生成默认的析构函数。析构函数不可以多于一个,不会有重载的析构函数。默认析构函数的函数体为空。
- 创建对象时自动调用构造函数,在对象消亡时自动调用析构函数。
- 析构函数的作用是做一些善后处理的工作。例如,如果在创建对象时使用new运算符动态分配了内存空间,则在析构函数中应该使用delete释放掉这部分占用的空间,保证空间可再利用。
- 当使用new运算符生成对象指针时,自动调用本类的构造函数。使用delete删除这个对象时,首先为这个动态对象调用本类的析构函数,然后再释放这个动态对象占用的内存。
析构函数的调用执行顺序与构造函数刚好相反。
构造函数:成员函数 > 类本身;
析构函数:类本身 > 成员函数;
3、类的静态成员
(1)静态变量
- 与C语言一样,可以使用static说明自动变量。根据定义的位置不同,分为静态全局变量和静态局部变量。
- 全局变量是指在所有花括号之外声明的变量,其作用域范围是全局可见的,即在整个项目文件内都有效。
- 使用static修饰的全局变量是静态全局变量,其作用域有所限制,仅在定义该变量的源文件内有效,项目中的其他源文件中不能使用它。
- 块内定义的变量是局部变量,从定义之处开始到本块结束处为止是局部变量的作用域。使用static修饰的局部变量是静态局部变量,即定义在块中的静态变量。静态局部变量具有局部作用域,但却具有全局生存期。
- 静态局部变量在程序的整个运行期间都存在,它占据的空间一直到程序结束时才释放,但仅在定义它的块中有效,在块外并不能访问它。
- 静态变量均存储在全局数据区,静态局部变量只执行一次初始化。
- 如果程序未显式给出初始值,则相当于初始化为0;
- 如果显式给出初始值,则在该静态变量所在块第一次执行时完成初始化。
(2)类的静态成员
- 类的静态成员有两种:静态成员变量和静态成员函数。在类体内定义类的成员时,在前面添加static关键字后,该成员即成为静态成员。类的静态成员被类的所有对象共享,不论有多少对象存在,静态成员都只有一份保存在公用内存中。对于静态成员变量,各对象看到的值是一样的。
- 定义类静态成员变量时,在类定义中声明静态成员变量,然后必须在类体外定义静态成员变量的初值,这个初值不能在类体内赋值。
- 给静态成员变量赋初值的格式如下: 类型 类名::静态成员变量=初值;
- 注意,在类体外为静态成员变量赋初值时,前面不能加static关键字,以免和一般的静态变量相混淆。在类体外定义成员函数时,前面也不能加static关键字。
访问静态成员时,成员前面既可以用类名作前缀,也可以使用对象名或对象指针作前缀。这与访问类成员时仅能使用对象名或对象指针作前缀是不同的。
访问类静态成员的一般格式如下:
类名::静态成员名
或是
对象名.静态成员名
或是
对象指针->静态成员名
类的静态成员函数没有this指针,不能在静态成员函数内访问非静态的成员,即通常情况下,类的静态成员函数只能处理类的静态成员变量。静态成员数内函也不能调用非静态成员函数。
- 对于普通成员变量,每个对象有各自的一份,而静态成员变量只有一份,被同类所有对象共享。
- 普通成员函数一定是作用在某个对象上的,而静态成员函数并不具体作用在某个对象上。
- 访问普通成员时,要通过“对象名.成员名”等方式,指明要访问的成员变量是属于哪个对象的,或要调用的成员函数作用于哪个对象;
- 访问静态成员时,则可以通过“类名::成员名”的方式访问,不需要指明被访问的成员属于哪个对象或作用于哪个对象。因此,甚至可以在还没有任何对象生成时就访问一个类的静态成员。
- 非静态成员的访问方式其实也适用于静态成员,也就是可以通过“对象名. 成员名”的方式访问,效果和“类名::成员名”这种访问方式没有区别。
#include <iostream>
using namespace std;
class A{
int a,b;
//类内声明:静态成员变量c,d;
static int c,d;
public:
A(int x,int y,int z,int n)
{
a=x,b=y,c=z,d=n;
};
void Show()
{
cout<<"a="<<a<<"\t b="<<b<<"\t c="<<c<<"\t d="<<d<<endl;
};
};
//类外定义:静态成员变量c,d
int A::c=0;
int A::d=0;
void main()
{
/** a.c和b.c静态成员变量 在内存中占据一个空间;
* a.d和b.d静态成员变量 在内存中占据一个空间;
*/
A a(2,3,4,5);
a.Show(); //输出:a=2,b=3,c=4,d=5;
A b(200,300,400,500);
b.Show(); //输出:a=200,b=300,c=400,d=500;
a.Show(); //输出:a=2,b=3,c=400,d=500;
};
4、变量及对象的生存期和作用域
- 变量的生存期是指变量所占据的内存空间由分配到释放的时期。变量有效的范围称为其作用域。
- 全局变量是程序中定义在所有函数(包括main函数)之外的任何变量,其作用域是程序从变量定义到整个程序结束的部分。这意味着全局变量可以被所有定义在全局变量之后的函数访问。全局变量及静态变量分配的空间在全局数据区,它们的生存期为整个程序的执行期间。
- 局部变量,如在函数内或程序块内说明的变量,被分配到局部数据区,如栈区等。这种分配是临时的,一旦该函数体或程序块运行结束,所分配的空间就会被撤销。局部变量的生存期从被说明处开始,到所在程序块结束处结束。
- 静态变量,如果没有进行初始化,系统会自动初始化为0。局部变量如果没有进行初始化,则其值是不确定的。
- 使用new运算符创建的变量具有动态生存期。从声明处开始,直到用delete运算符释放存储空间或程序结束时,变量生存期结束。类的对象在生成时调用构造函数,在消亡时调用析构函数,在这两个函数调用之间即是对象的生存期。
5、常量成员和常引用成员
- 在类中,也可以使用const关键字定义成员变量和成员函数,甚至是类的对象。由关键字const修饰的类成员变量称为类的常量成员变量。类的常量成员变量必须进行初始化,而且只能通过构造函数的成员初始化列表的方式进行。使用const修饰的函数称为常量函数。定义类的对象时如果在前面添加const关键字,则该对象称为常量对象。
- 在对象被创建以后,其常量成员变量的值就不允许被修改,只可以读取其值。对于常量对象,只能调用常量函数。总之,常量成员变量的值不能修改,常量对象中的各个属性值均不能修改。
定义常量对象或常量成员变量的一般格式如下:
const 数据类型 常量名=表达式;
定义常量函数的格式如下:
类型说明符 函数名(参数表)const;
6、成员对象和封闭类
- 一个类的成员变量如果是另一个类的对象,则该成员变量称为“成员对象”。这两个类为包含关系。包含成员对象的类叫作封闭类。
- 当生成封闭类的对象并进行初始化时,它包含的成员对象也需要被初始化,需要调用成员对象的构造函数。在定义封闭类的构造函数时,需要添加初始化列表,指明要调用成员对象的哪个构造函数。
- 先调用成员对象的构造函数,再调用封闭类对象的构造函数。
在封闭类构造函数中添加初始化列表的格式如下:
封闭类名::构造函数名(参数表): 成员变量1(参数表),成员变量2(参数表),…
{…}
初始化列表中的成员变量既可以是成员对象,也可以是基本数据类型的成员变量。对于成员对象,初始化列表的“参数表”中列出的是成员对象构造函数的参数(它指明了该成员对象如何初始化)。
- 如果封闭类的对象是用默认复制构造函数初始化的,那么它包含的成员对象也会用复制构造函数初始化。
7、友元
- 友元实际上并不是面向对象的特征,而是为了兼顾C语言程序设计的习惯与C++友元的概念破坏了类的封装性和信息隐藏,但有助于数据共享,能够提高程序执行的效率。信息隐藏的特点,而特意增加的功能。这是一种类成员的访问权限。
- friend关键字在哪出现,代表friend关键字后的函数或类,可以直接访问当前所在类的任意成员。【即:当前类给friend函数开通了特权】
(1)友元函数
- 在定义一个类的时候,可以把一些函数(包括全局函数 和 其他类中的成员函数)声明为“友元”,这样那些函数就成为本类的友元函数。在友元函数内部可以直接访问本类对象的私有成员。
- 友元函数不是类的成员函数,但允许访问类中的所有成员。在函数体中访问对象成员时,必须使用”对象名.对象成员名“的方式。
- 友元函数不受类中的访问权限关键字限制,可以把它放在类的公有、私有、保护部分、结果是一样的。
在类定义中,将一个全局函数声明为本类友元函数的格式如下:
friend 返回值类型 函数名(参数表);
当有某类A的定义后,将类A的成员函数说明为本类的友元函数的格式如下:(不能把其他类的私有成员函数声明为友元函数)
friend 返回值类型 类A::类A的成员函数名(参数表);
定义方式一:全局函数声明为本类友元函数
#include <iostream>
#include <cmath>
using namespace std;
class Point
{
private:
double x, y;
public:
Point(double a = 0, double b = 0):x(a),y(b){};
void print()
{
cout << "(" << x <<","<< y<< ")";
}
//把全局函数Distance声明为Point类的友元函数,目的是能访问Point类中的私有成员
friend double Distance(Point a, Point b);
};
//全局函数Distance定义
double Distance(Point a, Point b)
{
//若不把Distance设置为Point的友元类,那无法直接通过Point对象点属性名的方式访问私有成员。
double x = a.x - b.x;
double y = a.y - b.y;
return sqrt(x * x + y * y);
}
int main()
{
Point p1(3, 4), p2;
double d = Distance(p1, p2); //友元函数的调用
p1.print();
p2.print();
cout << " 距离为"<< d << endl;
return 0;
}
定义方式二:将某类中的成员函数(private除外)声明为本类的友元函数
#include <iostream>
using namespace std;
class B; //声明B类
class A //定义A类
{
private:
int a;
};
public:
A(int x = 0):a(x){};
void print()
{
cout << "A: a = " << a << endl;
}
void func(B &var);
class B //定义 B 类
{
public:
B(int y = 0):b(y){};
void print()
{
cout << "B: b = " << b << endl;
}
//将 A 类中的成员函数 func() 声明为 B 类的友元函数
//让A类的func成员函数能直接访问B类的私有成员
friend void A::func(B &var);
private:
int b;
};
void A::func(B &var) //友元函数的定义
{
a = var.b;
}
int main()
{
A m(2);
m.print();
B y(3);
y.print();
m.func(y); //友元函数的调用
m.print();
return 0;
}
(2)友元类
如果将一个类B说明为另一个类A的友元类,则类B中的所有函数都是类A的友元函数,在类B的所有成员函数中都可以访问类A中的所有成员。
在类定义中声明友元类的格式如下:
friend class 类名;
- 友元类的关系是单向的。若说明类B是类A的友元类,不等于类A也是类B的友元类。友元类的关系不能传递,即若类B是类A的友元类,而类C是类B的友元类,不等于类C是类A的友元类。
- 除非确有必要,一般不把整个类说明为友元类,而仅把类中的某些成员函数说明为友元函数。
8、指针
- C++语言规定,当调用一个成员函数时,系统自动向它传递一个隐含的参数。该参数是一个指向调用该函数的对象的指针,称为this指针,从而使成员函数知道对哪个对象进行操作。
- C++规定,在非静态成员函数内部可以直接使用this关键字,this就代表指向该函数所作用的对象的指针。
- 在一般情况下,在不引起歧义时,可以省略“this->”,系统采用默认设置。
- 静态成员是类具有的属性,不是对象的特征,this表示的是隐藏的对象的指针,所以静态成员函数没有this指针。