C++中的类:构造函数(上)

 

在大多数C++程序中,类都是至关重要的:我们能够使用类来定义为要解决的问题定制的数据类型,从而得到更加易于编写和理解的应用程序。设计良好的类类型可以像内置类型一样容易使用。

 

类的定义和声明

 

类的定义格式如下:

class ClassName {

    members;    // 类的成员,可以是数据成员或成员函数

}objectNames;   // objectNames是可选的

注意:类定义后的分号是必不可少的。

 

在理解类的定义之前,先记住下面几个术语:

类成员:类可以定义多个成员,成员可以是数据、函数或类型别名。类也可以没有成员,但没有太大的实际意义。所有成员必须在类的内部声明,即在定义类时的{}之内声明,一旦类定义完成后,就没有任何方式可以增加成员了。

构造函数:构造函数是一个特殊的、与类名同名的成员函数,用于给每个数据成员设置适当的初始值。只要创建类类型的新对象,都要执行构造函数,因此构造函数一定要声明为public成员。

访问标号:决定类成员的访问权限,可以使用的关键字是publicprivateprotected

public:位于public之后的类成员,程序中的所有部分都可以访问。

private:位于private之后的类成员,只能在类的内部访问。

protected:位于protected之后的类成员,同private,但是可被该类的派生类对象访问。

通过下面这个简单的例子,加深对构造函数的理解:

class MyFirstClass {

public:         // 访问标号public是必不可少的

    MyFirstClass(){

        cout << "This is my first class!" << endl;

    }

};

 

int main()

{

    MyFirstClass myObj; // 调用构造函数MyFirstClass(){}

    return 0;

}

运行结果:

This is my first class!

测试:去掉public标号,编译程序,看看会发生什么?

构造函数的名字与类的名字相同,并且不能指定返回类型,像其它任何函数一样,可以没有形参,也可以定义多个形参,马上我们将会看到定义形参的用法。没有形参的构造函数(或者是为所有形参提供默认实参的构造函数)我们称之为默认构造函数。只要在创建一个对象时没有提供初始化式,类就会调用默认构造函数。

上面的MyFirstClass类没有定义数据成员,也没有实际的意义,仅仅是为了演示构造函数的调用。下面我们定义一个稍微复杂一点的类,该类包含数据成员和一些构造函数。为了更好的和现实联系起来,我们把人抽象成一个类Person。人有姓名和年龄,所以我们把nameage作为Person类的两个数据成员,并定义一个成员函数print打印nameage

class Person {

public:

    Person(){ // 默认构造函数

        name = "";

        age = 0;

    }

    Person(string N, int A){ // 带有形参的构造函数

        name = N;

        age = A;

    }

    void print(){

        cout << "My name is " << name << ",and I'm " << age << " years old." << endl;

    }

private:   // 一般把数据成员定义成私有成员

    string name;

    int age;

};

构造函数也和普通的函数一样可以被重载,只要每个构造函数的形参表是唯一的。Person类中定义了两个构造函数,带形参的和不带形参的。如果我们想创建一个名字叫Tyrone,20岁的人,可以用下面的代码实现:

Person me("Tyrone",20); // 自动调用Person(string N, int A)构造函数

    me.print();// 对象通过点操作符访问public成员,不能访问private成员

运行结果:

My name is Tyrone,and I’m 20 years old.

如果我们在创建对象me的时候没有指定实参,会调用默认构造函数:

    Person me; // 注意对象名me后没有(),自动调用默认构造函数Person()

    me.print();

运行结果:

My name is ,and I’m 0 years old.

采用下面的方式创建对象是错误的:

Person me();

编译器会把me()解释为一个函数的声明,这个函数不接受参数并返回一个Person类型的对象。使用默认构造函数定义一个对象的正确方式是去掉最后的空括号,或者用下面的方式:

Person me = Person();

 

试想,如果我们没有定义任何构造函数会发生什么呢?编译器会自动生成一个默认构造函数,称作合成的默认构造函数。只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。一个类哪怕只定义了一个构造函数,编译器就不会生成默认构造函数。

合成的默认构造函数使用与变量初始化相同的规则来初始化成员。具有类类型的成员通过运行各自的默认构造函数来进行初始化。内置和复合类型的成员,如指针和数组,只对定义在全局作用域的对象才初始化。当对象定义在局部作用域中时,内置或复合类型的成员不进行初始化。

下面我们把Person类的构造函数去掉:

class Person {

public:

    void print(){

        cout << "My name is " << name << ",and I'm " << age << " years old." << endl;

    }

private:

    string name;

    int age;

};

自己动手运行下面两段代码,观察运行结果,看看有什么不同,并思考为什么?

代码一:

Person me;

int main()

{

    me.print();

    return 0;

}

代码二:

int main()

{

    Person me;

    me.print();

    return 0;

}

 

简化构造函数:使用构造函数初始化列表

现在我们应该已经知道,构造函数的工作就是保证每个对象的数据成员具有合适的初始值。构造函数和普通函数的区别就是没有返回类型,另外,还有一个区别是,构造函数可以包含一个初始化列表。

构造函数初始化列表以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员后面跟一个放在圆括号中的初始化式。

值的注意的是,构造函数初始化式只在构造函数的定义中而不是声明中指定。

在结合函数默认实参的性质,可以简化构造函数,下面我们重新定义Person类:

class Person {

public:

    Person(string N = "", int A = 0):name(N),age(A){};

    void print(){

        cout << "My name is " << name << ",and I'm " << age << " years old." << endl;

    }

private:

    string name;

    int age;

};

前面我们说过,为所有形参都提供默认实参的构造函数也是默认构造函数,所以下面两种创建对象的方式都是正确的:

Person me;  // 默认实参

Person me("Tyrone", 20);

构造函数初始化式不仅仅是为了简化构造函数,有些成员必须在构造函数初始化列表中进行初始化,对于这样的成员,在构造函数体中对它们赋值不起作用。没有默认构造函数的类类型的成员,以及const或引用类型的成员,都必须在构造函数初始化列表中进行初始化。以const成员为例,在Person类中,我们增加一个const成员性别sex:

const string sex;

那么就要修改构造函数为:

Person(string N = "", int A = 0, const string S = "female"):name(N),age(A), sex(S){};

在构造函数体内对其赋值是错误的:

    Person(string N = "", int A = 0, const string S = "female"):name(N),age(A){

        sex = S;

    };

构造函数初始化列表仅指定用于初始化成员的值,并不指定这些初始化执行的次序。成员被初始化的次序就是定义成员的次序。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值