OSX + Xcode + C++ (6)

类和对象

1. 为什么要设计“类”的概念

我们知道,c++和java一样,都是面向对象的语言。所谓的面向对象,就是把属于同一类的程序对象统一在一起。例如圆,大圆小圆、左边的圆和右边的圆,其实都属于圆这一类,它们有相似的属性,比如半径radias,有相似的操作,比如计算面积。我们把圆这个类的属性(数据)和它的操作(对数据的处理代码)“封装”在一起,就形成了这个类Circle。

既然有了封装,那么类的主要作用也就明显了,一是为了增强代码的安全性;二是为了简化编程,也就是外部只需要知道类的访问接口,而不需要知道类内部的实现细节,就可以使用类的功能。

2. 类的访问权限

c++中使用public、private、protected关键字来对类的访问进行限制。被public修饰的数据和代码不仅可以在类内函数的内部访问,还可以在外部访问,也就是类的外部访问接口。被private修饰的部分只能在类的内部函数进行访问。至于protected,将会在后面的内容进行讲述。

3. 类的基本结构

我们通过如下的代码片段来学习类的基本结构。因为对java比较熟悉,来对比一下他俩的区别。

区别 java C++
class关键字 大写 小写
类成员声明 先属性后方法,每个属性和函数都有访问控制字段 同一类访问控制权限的函数或属性只有一个关键字,在冒号后列举
函数实现 在类内实现 在类外实现
//类名,使用class关键字标识这是一个类
class Clock {    
//public关键字标识的外部访问接口
public:          
    //默认构造函数default constructor
    Clock();     
    //构造函数
    Clock(int newH, int newM, int newS);
    //普通函数
    void setTime(int newH, int newM, int newS);
    void showTime();
//private关键字标识的内部属性
private:
    int H, M, S;
};
//默认构造函数
Clock::Clock(): H(0), M(0), S(0) {}
//重载构造函数
Clock::Clock(int newH, int newM, int newS): H(newH), M(newM), S(newS) {}
//普通成员函数
void Clock::setTime(int newH, int newM, int newS) {
    H = newH;
    M = newM;
    S = newS;
}
void Clock::showTime() {
    cout << H << ":" << M << ":" << S << "\n";
}

4.构造函数

所谓构造函数,就是在定义一个类的对象时,应该如何对这个对象的各个属性进行赋值,或者说,如何分配内存空间,以及这些内存空间的值是什么。构造函数在类内定义,没有返回值,函数名必须和类名相同;在类外实现,以class_name::class_name的格式来标识属于哪一类。

区别 java C++
构造函数赋值操作 在函数体内,使用赋值语句:this.attribute = data 在函数名和参数列表之后,函数体外使用赋值语句:attribute(data)

4.1 默认构造函数

参数列表为空的构造函数称为默认构造函数。在不定义构造函数时,编译器会自动生成一个默认的构造函数,如果自定义了构造函数,编译器就不再生成。需要注意的是,即使是我们已经写了构造函数,还是要再写一个默认构造函数,这是因为,很多人的编程习惯是定义一个类的对象而不去初始化它,此时,如果没有默认构造函数,编译就会出错。如下图,在main函数声明一个Clock类c1,编译器报错是初始化时找不到匹配的构造函数。
这里写图片描述
也可以使用default关键字来指定默认构造函数,例如:
Clock(int newH, int newM, int newS) = default

4.2 复制构造函数

有时候,我们希望用已有的一个对象去初始化新的对象,需要特殊的构造函数:复制构造函数copy constructor。同默认构造函数一样,如果没有自定义复制构造函数,编译器也会自动生产一个,实现两个数据对象成员之间的一一对应的复制。
复制构造函数的声明:
1. 复制构造函数的参数列表是同类型对象的引用
2. 在参数之前需要加const限定符,const是限制常量的作用,我们知道,引用传递是双向传递,如果不添加const限定符,对新对象的操作就会更改原对象,而限制常量的作用就是使得这种双向数据传递不再有效。
复制构造函数的实现:
与普通构造函数一样,只要完成属性的赋值即可

class Clock {
public:
    Clock();
    Clock(int newH, int newM, int newS);
    //复制构造函数声明
    Clock(const Clock &c);
    void setTime(int newH, int newM, int newS);
    void showTime();
private:
    int H, M, S;
};

Clock::Clock(): H(0), M(0), S(0) {}
//复制构造函数的实现
Clock::Clock(int newH, int newM, int newS): H(newH), M(newM), S(newS) {}
Clock::Clock(const Clock &c): H(c.H), M(c.M), S(c.S){};

复制构造函数被调用的情况
1.定义一个对象时,以本类的另一个对象作为初始值,发生复制构造。
2. 如果函数的形参是类的对象,在调用该函数时,用实参对象初始化形参对象时,发生复制构造。
3. 如果一个函数的返回值是类的对象,在返回到主调函数时,将使用return语句中的对象初始化一个临时无名对象,传递给主调函数,发生复制构造。
运行如下代码:

//
//  main.cpp
//  ClassAndObject
//
//  Created by Evelyn on 2018/8/2.
//  Copyright © 2018年 Evelyn. All rights reserved.
//

#include <iostream>
using namespace std;

class Clock {
public:
    //declare default constructor
    Clock();
    //declare normal constructor
    Clock(int newH, int newM, int newS);
    //declare copy constructor
    Clock(const Clock &c);
    //declare member functions
    void setTime(int newH, int newM, int newS);
    void showTime();
private:
    //declare member variables
    int H, M, S;
};
//implement constructors
Clock::Clock(): H(0), M(0), S(0) {}
Clock::Clock(int newH, int newM, int newS): H(newH), M(newM), S(newS) {}
Clock::Clock(const Clock &c): H(c.H), M(c.M), S(c.S){
    cout << "calling copy constructor...\n";
};
//implement member functions
void Clock::setTime(int newH, int newM, int newS) {
    H = newH;
    M = newM;
    S = newS;
}

void Clock::showTime() {
    cout << H << ":" << M << ":" << S << "\n";
}
//use object as formal parameter
void func1(Clock c) {
    c.showTime();
}
//use object as return value
Clock func2() {
    Clock c;
    return c;
}

int main(int argc, const char * argv[]) {
    Clock c(2, 3, 4);
    //c.setTime(1, 0, 0);
    //c.showTime();
    Clock c1(c);
    func1(c1);
    c1 = func2();
    return 0;
}

正常编译时,输出结果为:
calling copy constructor...
calling copy constructor...
2:3:4

单步调试时发现c1 = func2()这个语句没有执行复制构造函数,原因是g++编译器进行了优化:RVO(return value optimization)。将RVO优化关闭,可以对g++增加选项-fno-elide-constructors,新的运行结果为:
calling copy constructor...
calling copy constructor...
2:3:4
calling copy constructor...

不希望生成复制构造函数
C++11标准支持使用delete关键字指示编译器不生成默认构造函数,例如:
Clock(const Clock &c) = delete

4.3 委托构造函数

在4.2小节最后一个示例程序中,我们看到定义了三个构造函数,然后对这三个构造函数依次实现,十分繁琐,c++11提供了委托构造函数的机制,来简化这种工作,也就是说,在已经有了构造函数的定义时,对相对简单的构造函数委托已经定义了的构造函数来实现。它的语法如下:
class_name::constructor1(args[]): constructor2(args) {}
使用这个方法来改写上一个程序示例,为:

//implement constructors
Clock::Clock(int newH, int newM, int newS): H(newH), M(newM), S(newS) {}
//use delegating constructor
Clock::Clock(): Clock(0,0,0) {}

4.3 析构函数

我们知道,当声明并定义一个对象时,它的构造函数会被自动调用,并分配相应的内存空间;那么当对象消亡时,相应的也需要收回这些内存空间,c++提供了析构函数机制,来处理对象被删除前的一些清理工作(并不是释放内存)。如果没有自定义析构函数,编译器会自动为类构造一个,函数体为空。析构函数的参数列表为空,也没有return语句,它的声明语法如下:
~class_name()

猜你喜欢

转载自blog.csdn.net/qq_42857385/article/details/81368849