C++程序设计【三】类和对象的进阶

三、类和对象的进阶

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指针
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值